diff --git a/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs b/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs
index 45b02e528f..c05a40923b 100644
--- a/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs
+++ b/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs
@@ -23,6 +23,10 @@ namespace Tgstation.Server.Api.Rights
///
/// User may list files
///
- List = 4
+ List = 4,
+ ///
+ /// User may delete empty folders
+ ///
+ Delete = 8
}
}
diff --git a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs
index 4f35d86321..0d4574db60 100644
--- a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs
+++ b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs
@@ -30,6 +30,9 @@ namespace Tgstation.Server.Client.Components
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
}
+ ///
+ public Task DeleteEmptyDirectory(string directory, CancellationToken cancellationToken) => apiClient.Delete(Routes.Configuration + directory ?? throw new ArgumentNullException(nameof(directory)), cancellationToken);
+
///
public Task> List(string directory, CancellationToken cancellationToken)
{
@@ -49,4 +52,4 @@ namespace Tgstation.Server.Client.Components
///
public Task Write(ConfigurationFile file, CancellationToken cancellationToken) => apiClient.Update(Routes.Configuration, file ?? throw new ArgumentNullException(nameof(file)), instance.Id, cancellationToken);
}
-}
\ No newline at end of file
+}
diff --git a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs
index 950c346775..ec8a5c9396 100644
--- a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs
+++ b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs
@@ -31,7 +31,15 @@ namespace Tgstation.Server.Client.Components
///
/// The file to write
/// The for the operation
- /// A representing the running operation
+ /// A resulting in the new
Task Write(ConfigurationFile file, CancellationToken cancellationToken);
+
+ ///
+ /// Delete an empty
+ ///
+ /// The path to directory to delete
+ /// The for the operation
+ /// A representing the running operation
+ Task DeleteEmptyDirectory(string directory, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
index 90c7a4d218..1e0a42c7bc 100644
--- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
+++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
@@ -103,36 +103,40 @@ namespace Tgstation.Server.Host.Components.StaticFiles
///
public async Task 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
///
public async Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken)
{
- await EnsureDirectories(cancellationToken).ConfigureAwait(false);
async Task SymlinkBase(bool files)
{
Task> 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);
+ }
}
///
@@ -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;
}
+
+ ///
+ public async Task 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;
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs
index ebccc9c24e..29b8b66da0 100644
--- a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs
+++ b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs
@@ -57,6 +57,15 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// A resulting in if the directory already existed, otherwise
Task CreateDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken);
+ ///
+ /// Attempt to delete an empty directory at
+ ///
+ /// The path of the empty directory to delete
+ /// The for the operation. If , the operation will be performed as the user of the
+ /// The for the operation
+ /// if the directory was empty and deleted, otherwise
+ Task DeleteDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken);
+
///
/// Writes to a given
///
diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
index 34ded3ea45..f1ad7587a1 100644
--- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
@@ -168,5 +168,29 @@ namespace Tgstation.Server.Host.Controllers
return Forbid();
}
}
+
+ [HttpDelete("{*directoryPath}")]
+ [TgsAuthorize(ConfigurationRights.Delete)]
+ public async Task 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();
+ }
+ }
}
}
diff --git a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs
index f361004dfe..19ad6fba61 100644
--- a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs
+++ b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs
@@ -39,6 +39,13 @@ namespace Tgstation.Server.Host.IO
/// A array representing the contents of the file at
byte[] ReadFile(string path);
+ ///
+ /// Deletes a directory at if it's empty
+ ///
+ /// The path of the directory to delete
+ /// if the directory does not exist or is empty and was deleted. otherwise
+ bool DeleteDirectory(string path);
+
///
/// Write to a file at a given .
///
diff --git a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs
index 9a60da2313..fa394a644c 100644
--- a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs
+++ b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs
@@ -21,6 +21,22 @@ namespace Tgstation.Server.Host.IO
return false;
}
+ ///
+ 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;
+ }
+
///
public IEnumerable 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;
}
}