Implement new verb for deleting empty directories, remove the old delete on empty behaviour

This commit is contained in:
Cyberboss
2018-09-04 09:58:54 -04:00
parent e90cf984c0
commit 2c588908e9
8 changed files with 149 additions and 59 deletions
@@ -23,6 +23,10 @@ namespace Tgstation.Server.Api.Rights
/// <summary>
/// User may list files
/// </summary>
List = 4
List = 4,
/// <summary>
/// User may delete empty folders
/// </summary>
Delete = 8
}
}
@@ -30,6 +30,9 @@ namespace Tgstation.Server.Client.Components
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
}
/// <inheritdoc />
public Task DeleteEmptyDirectory(string directory, CancellationToken cancellationToken) => apiClient.Delete(Routes.Configuration + directory ?? throw new ArgumentNullException(nameof(directory)), cancellationToken);
/// <inheritdoc />
public Task<IReadOnlyList<ConfigurationFile>> List(string directory, CancellationToken cancellationToken)
{
@@ -49,4 +52,4 @@ namespace Tgstation.Server.Client.Components
/// <inheritdoc />
public Task<ConfigurationFile> Write(ConfigurationFile file, CancellationToken cancellationToken) => apiClient.Update<ConfigurationFile, ConfigurationFile>(Routes.Configuration, file ?? throw new ArgumentNullException(nameof(file)), instance.Id, cancellationToken);
}
}
}
@@ -31,7 +31,15 @@ namespace Tgstation.Server.Client.Components
/// </summary>
/// <param name="file">The <see cref="ConfigurationFile"/> file to write</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
/// <returns>A <see cref="Task{TResult}"/> resulting in the new <see cref="ConfigurationFile"/></returns>
Task<ConfigurationFile> Write(ConfigurationFile file, CancellationToken cancellationToken);
/// <summary>
/// Delete an empty <paramref name="directory"/>
/// </summary>
/// <param name="directory">The path to directory to delete</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task DeleteEmptyDirectory(string directory, CancellationToken cancellationToken);
}
}
@@ -103,36 +103,40 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// <inheritdoc />
public async Task<ServerSideModifications> CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken)
{
await EnsureDirectories(cancellationToken).ConfigureAwait(false);
//just assume no other fs race conditions here
var dmeExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, dmeFile), cancellationToken);
var headFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsHeadFile), cancellationToken);
var tailFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsTailFile), cancellationToken);
await Task.WhenAll(dmeExistsTask, headFileExistsTask, tailFileExistsTask).ConfigureAwait(false);
if (!dmeExistsTask.Result && !headFileExistsTask.Result && !tailFileExistsTask.Result)
return null;
var copyTask = ioManager.CopyDirectory(CodeModificationsSubdirectory, destination, null, cancellationToken);
if (dmeExistsTask.Result)
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
{
await EnsureDirectories(cancellationToken).ConfigureAwait(false);
//just assume no other fs race conditions here
var dmeExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, dmeFile), cancellationToken);
var headFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsHeadFile), cancellationToken);
var tailFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsTailFile), cancellationToken);
await Task.WhenAll(dmeExistsTask, headFileExistsTask, tailFileExistsTask).ConfigureAwait(false);
if (!dmeExistsTask.Result && !headFileExistsTask.Result && !tailFileExistsTask.Result)
return null;
var copyTask = ioManager.CopyDirectory(CodeModificationsSubdirectory, destination, null, cancellationToken);
if (dmeExistsTask.Result)
{
await copyTask.ConfigureAwait(false);
return new ServerSideModifications(null, null, true);
}
if (!headFileExistsTask.Result && !tailFileExistsTask.Result)
{
await copyTask.ConfigureAwait(false);
return null;
}
string IncludeLine(string filePath) => String.Format(CultureInfo.InvariantCulture, "#include \"{0}\"", filePath);
await copyTask.ConfigureAwait(false);
return new ServerSideModifications(null, null, true);
return new ServerSideModifications(headFileExistsTask.Result ? IncludeLine(CodeModificationsHeadFile) : null, tailFileExistsTask.Result ? IncludeLine(CodeModificationsTailFile) : null, false);
}
if (!headFileExistsTask.Result && !tailFileExistsTask.Result)
{
await copyTask.ConfigureAwait(false);
return null;
}
string IncludeLine(string filePath) => String.Format(CultureInfo.InvariantCulture, "#include \"{0}\"", filePath);
await copyTask.ConfigureAwait(false);
return new ServerSideModifications(headFileExistsTask.Result ? IncludeLine(CodeModificationsHeadFile) : null, tailFileExistsTask.Result ? IncludeLine(CodeModificationsTailFile) : null, false);
}
string ValidateConfigRelativePath(string configurationRelativePath)
@@ -180,10 +184,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles
}));
}
if (systemIdentity == null)
ListImpl();
else
await systemIdentity.RunImpersonated(ListImpl, cancellationToken).ConfigureAwait(false);
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
if (systemIdentity == null)
ListImpl();
else
await systemIdentity.RunImpersonated(ListImpl, cancellationToken).ConfigureAwait(false);
return result;
}
@@ -240,10 +245,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles
}
}
if (systemIdentity == null)
await Task.Factory.StartNew(ReadImpl, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
else
await systemIdentity.RunImpersonated(ReadImpl, cancellationToken).ConfigureAwait(false);
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
if (systemIdentity == null)
await Task.Factory.StartNew(ReadImpl, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
else
await systemIdentity.RunImpersonated(ReadImpl, cancellationToken).ConfigureAwait(false);
return result;
}
@@ -251,7 +257,6 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// <inheritdoc />
public async Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken)
{
await EnsureDirectories(cancellationToken).ConfigureAwait(false);
async Task SymlinkBase(bool files)
{
Task<IReadOnlyList<string>> task;
@@ -275,7 +280,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles
})).ConfigureAwait(false);
}
await Task.WhenAll(SymlinkBase(true), SymlinkBase(false)).ConfigureAwait(false);
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
{
await EnsureDirectories(cancellationToken).ConfigureAwait(false);
await Task.WhenAll(SymlinkBase(true), SymlinkBase(false)).ConfigureAwait(false);
}
}
/// <inheritdoc />
@@ -328,13 +337,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles
}
}
if (systemIdentity == null)
await Task.Factory.StartNew(WriteImpl, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
else
await systemIdentity.RunImpersonated(WriteImpl, cancellationToken).ConfigureAwait(false);
if (result != null && data == null) //make sure these directories always exist
await EnsureDirectories(cancellationToken).ConfigureAwait(false);
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
if (systemIdentity == null)
await Task.Factory.StartNew(WriteImpl, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
else
await systemIdentity.RunImpersonated(WriteImpl, cancellationToken).ConfigureAwait(false);
return result;
}
@@ -347,10 +354,12 @@ namespace Tgstation.Server.Host.Components.StaticFiles
bool? result = null;
void DoCreate() => result = synchronousIOManager.CreateDirectory(path, cancellationToken);
if (systemIdentity == null)
await Task.Factory.StartNew(DoCreate, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
else
await systemIdentity.RunImpersonated(DoCreate, cancellationToken).ConfigureAwait(false);
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
if (systemIdentity == null)
await Task.Factory.StartNew(DoCreate, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
else
await systemIdentity.RunImpersonated(DoCreate, cancellationToken).ConfigureAwait(false);
return result.Value;
}
@@ -387,5 +396,24 @@ namespace Tgstation.Server.Host.Components.StaticFiles
}
return true;
}
/// <inheritdoc />
public async Task<bool> DeleteDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
{
await EnsureDirectories(cancellationToken).ConfigureAwait(false);
var path = ValidateConfigRelativePath(configurationRelativePath);
var result = false;
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
{
void CheckDeleteImpl() => result = synchronousIOManager.DeleteDirectory(path);
if (systemIdentity != null)
await systemIdentity.RunImpersonated(CheckDeleteImpl, cancellationToken).ConfigureAwait(false);
else
CheckDeleteImpl();
}
return result;
}
}
}
@@ -57,6 +57,15 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if the directory already existed, <see langword="false"/> otherwise</returns>
Task<bool> CreateDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken);
/// <summary>
/// Attempt to delete an empty directory at <paramref name="configurationRelativePath"/>
/// </summary>
/// <param name="configurationRelativePath">The path of the empty directory to delete</param>
/// <param name="systemIdentity">The <see cref="ISystemIdentity"/> for the operation. If <see langword="null"/>, the operation will be performed as the user of the <see cref="Core.Application"/></param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns><see langword="true"/> if the directory was empty and deleted, <see langword="false"/> otherwise</returns>
Task<bool> DeleteDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken);
/// <summary>
/// Writes to a given <paramref name="configurationRelativePath"/>
/// </summary>
@@ -168,5 +168,29 @@ namespace Tgstation.Server.Host.Controllers
return Forbid();
}
}
[HttpDelete("{*directoryPath}")]
[TgsAuthorize(ConfigurationRights.Delete)]
public async Task<IActionResult> Delete(string directoryPath, CancellationToken cancellationToken)
{
if (ForbidDueToModeConflicts())
return Forbid();
try
{
return await instanceManager.GetInstance(Instance).Configuration.DeleteDirectory(directoryPath, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false) ? (IActionResult)Ok() : Conflict(new ErrorMessage
{
Message = "Directory not empty!"
});
}
catch (NotImplementedException)
{
return StatusCode((int)HttpStatusCode.NotImplemented);
}
catch (UnauthorizedAccessException)
{
return Forbid();
}
}
}
}
@@ -39,6 +39,13 @@ namespace Tgstation.Server.Host.IO
/// <returns>A <see cref="byte"/> array representing the contents of the file at <paramref name="path"/></returns>
byte[] ReadFile(string path);
/// <summary>
/// Deletes a directory at <see cref="path"/> if it's empty
/// </summary>
/// <param name="path">The path of the directory to delete</param>
/// <returns><see langword="true"/> if the directory does not exist or is empty and was deleted. <see langword="false"/> otherwise</returns>
bool DeleteDirectory(string path);
/// <summary>
/// Write <paramref name="data"/> to a file at a given <paramref name="path"/>.
/// </summary>
@@ -21,6 +21,22 @@ namespace Tgstation.Server.Host.IO
return false;
}
/// <inheritdoc />
public bool DeleteDirectory(string path)
{
if (File.Exists(path))
return false;
if (!Directory.Exists(path))
return true;
if (Directory.EnumerateFileSystemEntries(path).Any())
return false;
Directory.Delete(path);
return true;
}
/// <inheritdoc />
public IEnumerable<string> GetDirectories(string path, CancellationToken cancellationToken)
{
@@ -109,16 +125,7 @@ namespace Tgstation.Server.Host.IO
}
}
if (data == null)
{
File.Delete(path);
if (!cancellationToken.IsCancellationRequested)
//delete the entire folder if possible
try
{
Directory.Delete(directory);
}
catch (IOException) { }
}
return true;
}
}