diff --git a/build/Version.props b/build/Version.props index c94784c13d..1b16b06328 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ - 5.2.0 + 5.2.1 4.4.0 9.7.0 10.1.0 diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 57a44f29b2..ff4fc85c11 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -251,6 +251,7 @@ namespace Tgstation.Server.Host.Components repositoryCommands, repoIoManager, eventConsumer, + postWriteHandler, gitRemoteFeaturesFactory, loggerFactory.CreateLogger(), loggerFactory.CreateLogger()); diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 9990d584ce..9e0bea04f3 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -91,6 +91,11 @@ namespace Tgstation.Server.Host.Components.Repository /// readonly ICredentialsProvider credentialsProvider; + /// + /// The for the . + /// + readonly IPostWriteHandler postWriteHandler; + /// /// The for the . /// @@ -119,6 +124,7 @@ namespace Tgstation.Server.Host.Components.Repository /// The value of . /// The value of . /// The value of . + /// The value of . /// The to provide the value of . /// The value of . /// The value if . @@ -128,6 +134,7 @@ namespace Tgstation.Server.Host.Components.Repository IIOManager ioMananger, IEventConsumer eventConsumer, ICredentialsProvider credentialsProvider, + IPostWriteHandler postWriteHandler, IGitRemoteFeaturesFactory gitRemoteFeaturesFactory, ILogger logger, Action onDispose) @@ -137,6 +144,7 @@ namespace Tgstation.Server.Host.Components.Repository this.ioMananger = ioMananger ?? throw new ArgumentNullException(nameof(ioMananger)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.credentialsProvider = credentialsProvider ?? throw new ArgumentNullException(nameof(credentialsProvider)); + this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler)); if (gitRemoteFeaturesFactory == null) throw new ArgumentNullException(nameof(gitRemoteFeaturesFactory)); @@ -500,7 +508,18 @@ namespace Tgstation.Server.Host.Components.Repository if (path == null) throw new ArgumentNullException(nameof(path)); logger.LogTrace("Copying to {0}...", path); - await ioMananger.CopyDirectory(ioMananger.ResolvePath(), path, new List { ".git" }, cancellationToken); + await ioMananger.CopyDirectory( + ioMananger.ResolvePath(), + path, + new List { ".git" }, + (src, dest) => + { + if (postWriteHandler.NeedsPostWrite(src)) + postWriteHandler.HandleWrite(dest); + + return Task.CompletedTask; + }, + cancellationToken); } /// diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs index 46e3712f62..ba78fb23c5 100644 --- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs @@ -42,6 +42,11 @@ namespace Tgstation.Server.Host.Components.Repository /// readonly IEventConsumer eventConsumer; + /// + /// The for the . + /// + readonly IPostWriteHandler postWriteHandler; + /// /// The for the . /// @@ -69,6 +74,7 @@ namespace Tgstation.Server.Host.Components.Repository /// The value of . /// The value of . /// The value of . + /// The value of . /// The value of . /// The value of . /// The value of . @@ -77,6 +83,7 @@ namespace Tgstation.Server.Host.Components.Repository ILibGit2Commands commands, IIOManager ioManager, IEventConsumer eventConsumer, + IPostWriteHandler postWriteHandler, IGitRemoteFeaturesFactory gitRemoteFeaturesFactory, ILogger repositoryLogger, ILogger logger) @@ -85,6 +92,7 @@ namespace Tgstation.Server.Host.Components.Repository this.commands = commands ?? throw new ArgumentNullException(nameof(commands)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); + this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler)); this.gitRemoteFeaturesFactory = gitRemoteFeaturesFactory ?? throw new ArgumentNullException(nameof(gitRemoteFeaturesFactory)); this.repositoryLogger = repositoryLogger ?? throw new ArgumentNullException(nameof(repositoryLogger)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -206,6 +214,7 @@ namespace Tgstation.Server.Host.Components.Repository ioManager, eventConsumer, repositoryFactory, + postWriteHandler, gitRemoteFeaturesFactory, repositoryLogger, () => diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index 98d63bf5d8..7b84ce8ebf 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -180,7 +180,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles 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); - var copyTask = ioManager.CopyDirectory(CodeModificationsSubdirectory, destination, null, cancellationToken); + var copyTask = ioManager.CopyDirectory(CodeModificationsSubdirectory, destination, null, null, cancellationToken); await Task.WhenAll(dmeExistsTask, headFileExistsTask, tailFileExistsTask, copyTask); diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index bc46817039..2fd09a6d07 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -88,7 +88,12 @@ namespace Tgstation.Server.Host.IO } /// - public async Task CopyDirectory(string src, string dest, IEnumerable ignore, CancellationToken cancellationToken) + public async Task CopyDirectory( + string src, + string dest, + IEnumerable ignore, + Func postCopyCallback, + CancellationToken cancellationToken) { if (src == null) throw new ArgumentNullException(nameof(src)); @@ -97,8 +102,7 @@ namespace Tgstation.Server.Host.IO src = ResolvePath(src); dest = ResolvePath(dest); - foreach (var directoryCopy in CopyDirectoryImpl(src, dest, ignore, cancellationToken)) - await directoryCopy; + await Task.WhenAll(CopyDirectoryImpl(src, dest, ignore, postCopyCallback, cancellationToken)); } /// @@ -368,37 +372,49 @@ namespace Tgstation.Server.Host.IO /// The source directory path. /// The destination directory path. /// Files and folders to ignore at the root level. + /// The optional callback called for each source/dest file pair post copy. /// The for the operation. /// A of s representing the running operation. - IEnumerable CopyDirectoryImpl(string src, string dest, IEnumerable ignore, CancellationToken cancellationToken) + IEnumerable CopyDirectoryImpl( + string src, + string dest, + IEnumerable ignore, + Func postCopyCallback, + CancellationToken cancellationToken) { var dir = new DirectoryInfo(src); - var atLeastOneSubDir = false; foreach (var subDirectory in dir.EnumerateDirectories()) { if (ignore != null && ignore.Contains(subDirectory.Name)) continue; - foreach (var copyTask in CopyDirectoryImpl(subDirectory.FullName, Path.Combine(dest, subDirectory.Name), null, cancellationToken)) - { - atLeastOneSubDir = true; + foreach (var copyTask in CopyDirectoryImpl(subDirectory.FullName, Path.Combine(dest, subDirectory.Name), null, postCopyCallback, cancellationToken)) yield return copyTask; - } } async Task CopyThisDirectory() { - if (!atLeastOneSubDir) - await CreateDirectory(dest, cancellationToken); // save on createdir calls + await CreateDirectory(dest, cancellationToken); - var tasks = new List(); + var fileCopyTasks = new List(); foreach (var fileInfo in dir.EnumerateFiles()) { if (ignore != null && ignore.Contains(fileInfo.Name)) return; - tasks.Add(CopyFile(fileInfo.FullName, Path.Combine(dest, fileInfo.Name), cancellationToken)); + + var sourceFile = fileInfo.FullName; + var destFile = Path.Combine(dest, fileInfo.Name); + + async Task CopyThisFile() + { + await CopyFile(sourceFile, destFile, cancellationToken); + if (postCopyCallback != null) + await postCopyCallback(sourceFile, destFile); + } + + fileCopyTasks.Add(CopyThisFile()); } - await Task.WhenAll(tasks); + await Task.WhenAll(fileCopyTasks); } yield return CopyThisDirectory(); diff --git a/src/Tgstation.Server.Host/IO/IIOManager.cs b/src/Tgstation.Server.Host/IO/IIOManager.cs index 6f7dd75bbf..2e50ee317d 100644 --- a/src/Tgstation.Server.Host/IO/IIOManager.cs +++ b/src/Tgstation.Server.Host/IO/IIOManager.cs @@ -51,9 +51,15 @@ namespace Tgstation.Server.Host.IO /// The source directory path. /// The destination directory path. /// Files and folders to ignore at the root level. + /// The optional callback called for each source/dest file pair post copy. /// The for the operation. /// A representing the running operation. - Task CopyDirectory(string src, string dest, IEnumerable ignore, CancellationToken cancellationToken); + Task CopyDirectory( + string src, + string dest, + IEnumerable ignore, + Func postCopyCallback, + CancellationToken cancellationToken); /// /// Check that the file at exists. diff --git a/src/Tgstation.Server.Host/IO/IPostWriteHandler.cs b/src/Tgstation.Server.Host/IO/IPostWriteHandler.cs index e99a2eb1e4..9b21ab9ef8 100644 --- a/src/Tgstation.Server.Host/IO/IPostWriteHandler.cs +++ b/src/Tgstation.Server.Host/IO/IPostWriteHandler.cs @@ -5,6 +5,13 @@ /// interface IPostWriteHandler { + /// + /// Check if a given will need called on a copy of it. + /// + /// The path of the source file to check. + /// if should be called on copies of , otherwise. + public bool NeedsPostWrite(string sourceFilePath); + /// /// For handling system specific necessities after a write. /// diff --git a/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs b/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs index b2f9bf9143..a69f77ac74 100644 --- a/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs +++ b/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs @@ -25,6 +25,18 @@ namespace Tgstation.Server.Host.IO this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } + /// + public bool NeedsPostWrite(string sourceFilePath) + { + if (sourceFilePath == null) + throw new ArgumentNullException(nameof(sourceFilePath)); + + if (Syscall.stat(sourceFilePath, out var stat) != 0) + throw new UnixIOException(Stdlib.GetLastError()); + + return stat.st_mode.HasFlag(FilePermissions.S_IXUSR); + } + /// public void HandleWrite(string filePath) { diff --git a/src/Tgstation.Server.Host/IO/WindowsPostWriteHandler.cs b/src/Tgstation.Server.Host/IO/WindowsPostWriteHandler.cs index bec6986208..76c4b0f9c6 100644 --- a/src/Tgstation.Server.Host/IO/WindowsPostWriteHandler.cs +++ b/src/Tgstation.Server.Host/IO/WindowsPostWriteHandler.cs @@ -7,6 +7,15 @@ namespace Tgstation.Server.Host.IO /// sealed class WindowsPostWriteHandler : IPostWriteHandler { + /// + public bool NeedsPostWrite(string sourceFilePath) + { + if (sourceFilePath == null) + throw new ArgumentNullException(nameof(sourceFilePath)); + + return false; + } + /// public void HandleWrite(string filePath) { diff --git a/tests/Tgstation.Server.Host.Tests/IO/TestPostWriteHandler.cs b/tests/Tgstation.Server.Host.Tests/IO/TestPostWriteHandler.cs index 40864bcba2..8c6e1cbaef 100644 --- a/tests/Tgstation.Server.Host.Tests/IO/TestPostWriteHandler.cs +++ b/tests/Tgstation.Server.Host.Tests/IO/TestPostWriteHandler.cs @@ -24,6 +24,7 @@ namespace Tgstation.Server.Host.IO.Tests postWriteHandler = new PosixPostWriteHandler(Mock.Of>()); Assert.ThrowsException(() => postWriteHandler.HandleWrite(null)); + Assert.ThrowsException(() => postWriteHandler.NeedsPostWrite(null)); } [TestMethod] @@ -40,12 +41,14 @@ namespace Tgstation.Server.Host.IO.Tests var tmpFile = Path.GetTempFileName(); try { + Assert.IsFalse(postWriteHandler.NeedsPostWrite(tmpFile)); postWriteHandler.HandleWrite(tmpFile); if (isWindows) return; //you do nothing //ensure it is now executable + Assert.IsTrue(postWriteHandler.NeedsPostWrite(tmpFile)); File.WriteAllBytes(tmpFile, Encoding.UTF8.GetBytes("#!/bin/sh\n")); using (var process = Process.Start(tmpFile)) diff --git a/tests/Tgstation.Server.Tests/IntegrationTest.cs b/tests/Tgstation.Server.Tests/IntegrationTest.cs index 7de3b4cdb4..09e7592142 100644 --- a/tests/Tgstation.Server.Tests/IntegrationTest.cs +++ b/tests/Tgstation.Server.Tests/IntegrationTest.cs @@ -1073,6 +1073,7 @@ namespace Tgstation.Server.Tests Mock.Of(), Mock.Of(), Mock.Of(), + Mock.Of(), Mock.Of(), Mock.Of>(), () => { });