mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-24 21:46:52 +01:00
Merge pull request #1409 from tgstation/1167-FinallyFixedThis [TGSDeploy]
Directory copying improvements (5.2.1)
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@
|
||||
<!-- Integration tests will ensure they match across the board -->
|
||||
<Import Project="ControlPanelVersion.props" />
|
||||
<PropertyGroup>
|
||||
<TgsCoreVersion>5.2.0</TgsCoreVersion>
|
||||
<TgsCoreVersion>5.2.1</TgsCoreVersion>
|
||||
<TgsConfigVersion>4.4.0</TgsConfigVersion>
|
||||
<TgsApiVersion>9.7.0</TgsApiVersion>
|
||||
<TgsApiLibraryVersion>10.1.0</TgsApiLibraryVersion>
|
||||
|
||||
@@ -251,6 +251,7 @@ namespace Tgstation.Server.Host.Components
|
||||
repositoryCommands,
|
||||
repoIoManager,
|
||||
eventConsumer,
|
||||
postWriteHandler,
|
||||
gitRemoteFeaturesFactory,
|
||||
loggerFactory.CreateLogger<Repository.Repository>(),
|
||||
loggerFactory.CreateLogger<RepositoryManager>());
|
||||
|
||||
@@ -91,6 +91,11 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// </summary>
|
||||
readonly ICredentialsProvider credentialsProvider;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IPostWriteHandler"/> for the <see cref="Repository"/>.
|
||||
/// </summary>
|
||||
readonly IPostWriteHandler postWriteHandler;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IGitRemoteFeatures"/> for the <see cref="Repository"/>.
|
||||
/// </summary>
|
||||
@@ -119,6 +124,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <param name="ioMananger">The value of <see cref="ioMananger"/>.</param>
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/>.</param>
|
||||
/// <param name="credentialsProvider">The value of <see cref="credentialsProvider"/>.</param>
|
||||
/// <param name="postWriteHandler">The value of <see cref="postWriteHandler"/>.</param>
|
||||
/// <param name="gitRemoteFeaturesFactory">The <see cref="IGitRemoteFeaturesFactory"/> to provide the value of <see cref="gitRemoteFeatures"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
/// <param name="onDispose">The value if <see cref="onDispose"/>.</param>
|
||||
@@ -128,6 +134,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
IIOManager ioMananger,
|
||||
IEventConsumer eventConsumer,
|
||||
ICredentialsProvider credentialsProvider,
|
||||
IPostWriteHandler postWriteHandler,
|
||||
IGitRemoteFeaturesFactory gitRemoteFeaturesFactory,
|
||||
ILogger<Repository> 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<string> { ".git" }, cancellationToken);
|
||||
await ioMananger.CopyDirectory(
|
||||
ioMananger.ResolvePath(),
|
||||
path,
|
||||
new List<string> { ".git" },
|
||||
(src, dest) =>
|
||||
{
|
||||
if (postWriteHandler.NeedsPostWrite(src))
|
||||
postWriteHandler.HandleWrite(dest);
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -42,6 +42,11 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// </summary>
|
||||
readonly IEventConsumer eventConsumer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IPostWriteHandler"/> for the <see cref="RepositoryManager"/>.
|
||||
/// </summary>
|
||||
readonly IPostWriteHandler postWriteHandler;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IGitRemoteFeaturesFactory"/> for the <see cref="RepositoryManager"/>.
|
||||
/// </summary>
|
||||
@@ -69,6 +74,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <param name="commands">The value of <see cref="commands"/>.</param>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/>.</param>
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/>.</param>
|
||||
/// <param name="postWriteHandler">The value of <see cref="postWriteHandler"/>.</param>
|
||||
/// <param name="gitRemoteFeaturesFactory">The value of <see cref="gitRemoteFeaturesFactory"/>.</param>
|
||||
/// <param name="repositoryLogger">The value of <see cref="repositoryLogger"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
@@ -77,6 +83,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
ILibGit2Commands commands,
|
||||
IIOManager ioManager,
|
||||
IEventConsumer eventConsumer,
|
||||
IPostWriteHandler postWriteHandler,
|
||||
IGitRemoteFeaturesFactory gitRemoteFeaturesFactory,
|
||||
ILogger<Repository> repositoryLogger,
|
||||
ILogger<RepositoryManager> 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,
|
||||
() =>
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -88,7 +88,12 @@ namespace Tgstation.Server.Host.IO
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CopyDirectory(string src, string dest, IEnumerable<string> ignore, CancellationToken cancellationToken)
|
||||
public async Task CopyDirectory(
|
||||
string src,
|
||||
string dest,
|
||||
IEnumerable<string> ignore,
|
||||
Func<string, string, Task> 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));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -368,37 +372,49 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <param name="src">The source directory path.</param>
|
||||
/// <param name="dest">The destination directory path.</param>
|
||||
/// <param name="ignore">Files and folders to ignore at the root level.</param>
|
||||
/// <param name="postCopyCallback">The optional callback called for each source/dest file pair post copy.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="IEnumerable{T}"/> of <see cref="Task"/>s representing the running operation.</returns>
|
||||
IEnumerable<Task> CopyDirectoryImpl(string src, string dest, IEnumerable<string> ignore, CancellationToken cancellationToken)
|
||||
IEnumerable<Task> CopyDirectoryImpl(
|
||||
string src,
|
||||
string dest,
|
||||
IEnumerable<string> ignore,
|
||||
Func<string, string, Task> 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<Task>();
|
||||
var fileCopyTasks = new List<Task>();
|
||||
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();
|
||||
|
||||
@@ -51,9 +51,15 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <param name="src">The source directory path.</param>
|
||||
/// <param name="dest">The destination directory path.</param>
|
||||
/// <param name="ignore">Files and folders to ignore at the root level.</param>
|
||||
/// <param name="postCopyCallback">The optional callback called for each source/dest file pair post copy.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task CopyDirectory(string src, string dest, IEnumerable<string> ignore, CancellationToken cancellationToken);
|
||||
Task CopyDirectory(
|
||||
string src,
|
||||
string dest,
|
||||
IEnumerable<string> ignore,
|
||||
Func<string, string, Task> postCopyCallback,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Check that the file at <paramref name="path"/> exists.
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
/// </summary>
|
||||
interface IPostWriteHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if a given <paramref name="sourceFilePath"/> will need <see cref="HandleWrite(string)"/> called on a copy of it.
|
||||
/// </summary>
|
||||
/// <param name="sourceFilePath">The path of the source file to check.</param>
|
||||
/// <returns><see langword="true"/> if <see cref="HandleWrite(string)"/> should be called on copies of <paramref name="sourceFilePath"/>, <see langword="false"/> otherwise.</returns>
|
||||
public bool NeedsPostWrite(string sourceFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// For handling system specific necessities after a write.
|
||||
/// </summary>
|
||||
|
||||
@@ -25,6 +25,18 @@ namespace Tgstation.Server.Host.IO
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void HandleWrite(string filePath)
|
||||
{
|
||||
|
||||
@@ -7,6 +7,15 @@ namespace Tgstation.Server.Host.IO
|
||||
/// </summary>
|
||||
sealed class WindowsPostWriteHandler : IPostWriteHandler
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool NeedsPostWrite(string sourceFilePath)
|
||||
{
|
||||
if (sourceFilePath == null)
|
||||
throw new ArgumentNullException(nameof(sourceFilePath));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void HandleWrite(string filePath)
|
||||
{
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
postWriteHandler = new PosixPostWriteHandler(Mock.Of<ILogger<PosixPostWriteHandler>>());
|
||||
|
||||
Assert.ThrowsException<ArgumentNullException>(() => postWriteHandler.HandleWrite(null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => 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))
|
||||
|
||||
@@ -1073,6 +1073,7 @@ namespace Tgstation.Server.Tests
|
||||
Mock.Of<Host.IO.IIOManager>(),
|
||||
Mock.Of<IEventConsumer>(),
|
||||
Mock.Of<ICredentialsProvider>(),
|
||||
Mock.Of<Host.IO.IPostWriteHandler>(),
|
||||
Mock.Of<IGitRemoteFeaturesFactory>(),
|
||||
Mock.Of<ILogger<Repository>>(),
|
||||
() => { });
|
||||
|
||||
Reference in New Issue
Block a user