diff --git a/ScriptBeeWebApp/src/Common/Common/Extensions/FileSystemExtensions.cs b/ScriptBeeWebApp/src/Common/Common/Extensions/FileSystemExtensions.cs index 296edf10..a2cbb1e1 100644 --- a/ScriptBeeWebApp/src/Common/Common/Extensions/FileSystemExtensions.cs +++ b/ScriptBeeWebApp/src/Common/Common/Extensions/FileSystemExtensions.cs @@ -25,4 +25,37 @@ public static void EnsureDirectoryExists(this string path) Directory.CreateDirectory(path); } } + + public static void CopyTo( + this DirectoryInfo sourceDir, + string destinationDir, + bool recursive = true + ) + { + if (!sourceDir.Exists) + { + throw new DirectoryNotFoundException( + $"Source directory not found: {sourceDir.FullName}" + ); + } + + Directory.CreateDirectory(destinationDir); + + foreach (var file in sourceDir.GetFiles()) + { + var targetFilePath = Path.Combine(destinationDir, file.Name); + file.CopyTo(targetFilePath, true); + } + + if (!recursive) + { + return; + } + + foreach (var subDir in sourceDir.GetDirectories()) + { + var newDestinationDir = Path.Combine(destinationDir, subDir.Name); + subDir.CopyTo(newDestinationDir, true); + } + } } diff --git a/ScriptBeeWebApp/src/Common/Model/Config/ConfigFolders.cs b/ScriptBeeWebApp/src/Common/Model/Config/ConfigFolders.cs index 0a3ce0ed..4f151423 100644 --- a/ScriptBeeWebApp/src/Common/Model/Config/ConfigFolders.cs +++ b/ScriptBeeWebApp/src/Common/Model/Config/ConfigFolders.cs @@ -1,22 +1,20 @@ -namespace ScriptBee.Domain.Model.Config; +namespace ScriptBee.Domain.Model.Config; public static class ConfigFolders { - private const string Root = ".scriptbee"; - - private const string PluginsFolder = "plugins"; - - private const string ProjectsFolder = "projects"; - - public const string SrcFolder = "src"; - private static readonly string PathToUserFolder = Environment.GetFolderPath( Environment.SpecialFolder.UserProfile ); - public static readonly string PathToRoot = Path.Combine(PathToUserFolder, Root); + private static readonly string PathToRoot = Path.Combine(PathToUserFolder, ".scriptbee"); + + public static readonly string PathToPlugins = Path.Combine(PathToRoot, "plugins"); - public static readonly string PathToPlugins = Path.Combine(PathToRoot, PluginsFolder); + public static readonly string PathToGatewayPlugins = Path.Combine( + PathToRoot, + "gateway", + "plugins" + ); - public static readonly string PathToProjects = Path.Combine(PathToRoot, ProjectsFolder); + public static readonly string PathToProjects = Path.Combine(PathToRoot, "projects"); } diff --git a/ScriptBeeWebApp/src/Gateway/Adapters/Web/Config/PluginsSettings.cs b/ScriptBeeWebApp/src/Gateway/Adapters/Web/Config/PluginsSettings.cs index afe8a357..3a24b95b 100644 --- a/ScriptBeeWebApp/src/Gateway/Adapters/Web/Config/PluginsSettings.cs +++ b/ScriptBeeWebApp/src/Gateway/Adapters/Web/Config/PluginsSettings.cs @@ -2,5 +2,7 @@ namespace ScriptBee.Web.Config; public class PluginsSettings { - public string? InstallationFolder { get; set; } + public string? InstallationFolder { get; init; } + + public string? GatewayInstallationFolder { get; init; } } diff --git a/ScriptBeeWebApp/src/Gateway/Adapters/Web/Extensions/ConfigurePluginServiceExtension.cs b/ScriptBeeWebApp/src/Gateway/Adapters/Web/Extensions/ConfigurePluginServiceExtension.cs index 013b5a62..11ca24ea 100644 --- a/ScriptBeeWebApp/src/Gateway/Adapters/Web/Extensions/ConfigurePluginServiceExtension.cs +++ b/ScriptBeeWebApp/src/Gateway/Adapters/Web/Extensions/ConfigurePluginServiceExtension.cs @@ -19,7 +19,11 @@ string pluginConfigurationSection services.AddOptions().BindConfiguration(pluginConfigurationSection); return services .AddSingleton() - .AddSingleton() + .AddSingleton() + .AddSingleton(sp => sp.GetRequiredService()) + .AddSingleton(sp => + sp.GetRequiredService() + ) .AddPluginReader() .AddPluginInstaller() .AddPluginLoader(); diff --git a/ScriptBeeWebApp/src/Gateway/Adapters/Web/Services/PluginPathProvider.cs b/ScriptBeeWebApp/src/Gateway/Adapters/Web/Services/PluginPathProvider.cs index b0f9dea2..56e81553 100644 --- a/ScriptBeeWebApp/src/Gateway/Adapters/Web/Services/PluginPathProvider.cs +++ b/ScriptBeeWebApp/src/Gateway/Adapters/Web/Services/PluginPathProvider.cs @@ -1,19 +1,24 @@ using Microsoft.Extensions.Options; using ScriptBee.Domain.Model.Config; using ScriptBee.Domain.Model.Project; -using ScriptBee.Plugins; +using ScriptBee.Service.Gateway.Plugins; using ScriptBee.Web.Config; namespace ScriptBee.Web.Services; public sealed class PluginPathProvider(IOptions pluginSettings) - : IPluginPathProvider + : IGatewayPluginPathProvider { public string GetPathToPlugins() { return pluginSettings.Value.InstallationFolder ?? ConfigFolders.PathToPlugins; } + public string GetInstallationFolderPath() + { + return pluginSettings.Value.GatewayInstallationFolder ?? ConfigFolders.PathToGatewayPlugins; + } + public string GetPathToPlugins(ProjectId projectId) { return Path.Combine(ConfigFolders.PathToProjects, projectId.Value, "plugins"); diff --git a/ScriptBeeWebApp/src/Gateway/Application/Service.Gateway/Plugins/IGatewayPluginPathProvider.cs b/ScriptBeeWebApp/src/Gateway/Application/Service.Gateway/Plugins/IGatewayPluginPathProvider.cs new file mode 100644 index 00000000..a1b868f0 --- /dev/null +++ b/ScriptBeeWebApp/src/Gateway/Application/Service.Gateway/Plugins/IGatewayPluginPathProvider.cs @@ -0,0 +1,8 @@ +using ScriptBee.Plugins; + +namespace ScriptBee.Service.Gateway.Plugins; + +public interface IGatewayPluginPathProvider : IPluginPathProvider +{ + string GetInstallationFolderPath(); +} diff --git a/ScriptBeeWebApp/src/Gateway/Application/Service.Gateway/Plugins/PluginManager.cs b/ScriptBeeWebApp/src/Gateway/Application/Service.Gateway/Plugins/PluginManager.cs index 9f857791..fbd793f5 100644 --- a/ScriptBeeWebApp/src/Gateway/Application/Service.Gateway/Plugins/PluginManager.cs +++ b/ScriptBeeWebApp/src/Gateway/Application/Service.Gateway/Plugins/PluginManager.cs @@ -5,16 +5,16 @@ namespace ScriptBee.Service.Gateway.Plugins; -public class PluginManager( +public sealed class PluginManager( IPluginReader pluginReader, IPluginLoader pluginLoader, - IPluginPathProvider pluginPathProvider, + IGatewayPluginPathProvider pluginPathProvider, ILogger logger ) : IManagePluginsUseCase { public void LoadPlugins() { - var pluginFolderPath = pluginPathProvider.GetPathToPlugins(); + var pluginFolderPath = pluginPathProvider.GetInstallationFolderPath(); logger.LogInformation("Loading plugins from {Folder}", pluginFolderPath); var plugins = pluginReader.ReadPlugins(pluginFolderPath); diff --git a/ScriptBeeWebApp/src/Workspace/Artifacts/Artifacts/ConfigFoldersService.cs b/ScriptBeeWebApp/src/Workspace/Artifacts/Artifacts/ConfigFoldersService.cs index a30ab472..f1ddba47 100644 --- a/ScriptBeeWebApp/src/Workspace/Artifacts/Artifacts/ConfigFoldersService.cs +++ b/ScriptBeeWebApp/src/Workspace/Artifacts/Artifacts/ConfigFoldersService.cs @@ -7,11 +7,6 @@ public class ConfigFoldersService : IConfigFoldersService { public string GetPathToSrcFolder(ProjectId projectId, string path) { - return Path.Combine( - ConfigFolders.PathToProjects, - projectId.ToString(), - ConfigFolders.SrcFolder, - path - ); + return Path.Combine(ConfigFolders.PathToProjects, projectId.ToString(), "src", path); } } diff --git a/ScriptBeeWebApp/test/Common/Common.Tests/Extensions/FileSystemExtensionsTests.cs b/ScriptBeeWebApp/test/Common/Common.Tests/Extensions/FileSystemExtensionsTests.cs index 63c25814..9044d974 100644 --- a/ScriptBeeWebApp/test/Common/Common.Tests/Extensions/FileSystemExtensionsTests.cs +++ b/ScriptBeeWebApp/test/Common/Common.Tests/Extensions/FileSystemExtensionsTests.cs @@ -69,4 +69,74 @@ public void GivenExistingDirectory_WhenEnsureDirectoryExists_ThenNothingHappens( Directory.Exists(path).ShouldBeTrue(); } + + [Fact] + public void CopyTo_ThrowsDirectoryNotFoundException_WhenSourceDoesNotExist() + { + // Arrange + var nonExistentDir = new DirectoryInfo( + Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()) + ); + + // Act & Assert + Assert.Throws(() => nonExistentDir.CopyTo("any_destination")); + } + + [Fact] + public void CopyTo_CopiesFiles_WhenNotRecursive() + { + // Arrange + var sourcePath = fixture.CreateSubFolder("Source1"); + var destPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + + File.WriteAllText(Path.Combine(sourcePath, "test.txt"), "hello"); + var subDir = Directory.CreateDirectory(Path.Combine(sourcePath, "Sub")); + File.WriteAllText(Path.Combine(subDir.FullName, "hidden.txt"), "should not copy"); + + var sourceDir = new DirectoryInfo(sourcePath); + + // Act + sourceDir.CopyTo(destPath, recursive: false); + + // Assert + Assert.True(File.Exists(Path.Combine(destPath, "test.txt"))); + Assert.False(Directory.Exists(Path.Combine(destPath, "Sub"))); + } + + [Fact] + public void CopyTo_CopiesEverything_WhenRecursive() + { + // Arrange + var sourcePath = fixture.CreateSubFolder("Source2"); + var destPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + + var subDirPath = Path.Combine(sourcePath, "Level1"); + Directory.CreateDirectory(subDirPath); + File.WriteAllText(Path.Combine(sourcePath, "root.txt"), "root"); + File.WriteAllText(Path.Combine(subDirPath, "child.txt"), "child"); + + var sourceDir = new DirectoryInfo(sourcePath); + + // Act + sourceDir.CopyTo(destPath, recursive: true); + + // Assert + Assert.True(File.Exists(Path.Combine(destPath, "root.txt"))); + Assert.True(File.Exists(Path.Combine(destPath, "Level1", "child.txt"))); + } + + [Fact] + public void CopyTo_CreatesDestinationDirectory_IfItDoesNotExist() + { + // Arrange + var sourcePath = fixture.CreateSubFolder("Source3"); + var destPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var sourceDir = new DirectoryInfo(sourcePath); + + // Act + sourceDir.CopyTo(destPath); + + // Assert + Assert.True(Directory.Exists(destPath)); + } } diff --git a/ScriptBeeWebApp/test/Gateway/Application/Service.Gateway.Tests/Plugins/PluginManagerTests.cs b/ScriptBeeWebApp/test/Gateway/Application/Service.Gateway.Tests/Plugins/PluginManagerTests.cs index 33e93593..0cf6d54b 100644 --- a/ScriptBeeWebApp/test/Gateway/Application/Service.Gateway.Tests/Plugins/PluginManagerTests.cs +++ b/ScriptBeeWebApp/test/Gateway/Application/Service.Gateway.Tests/Plugins/PluginManagerTests.cs @@ -13,8 +13,8 @@ public class PluginManagerTests private readonly IPluginReader _pluginReader = Substitute.For(); private readonly IPluginLoader _pluginLoader = Substitute.For(); - private readonly IPluginPathProvider _pluginPathProvider = - Substitute.For(); + private readonly IGatewayPluginPathProvider _pluginPathProvider = + Substitute.For(); private readonly ILogger _logger = Substitute.For>(); @@ -33,7 +33,7 @@ public PluginManagerTests() [Fact] public void GivenEmptyPlugins_WhenLoadPlugins_ThenNoPluginsLoaded() { - _pluginPathProvider.GetPathToPlugins().Returns("plugin/path"); + _pluginPathProvider.GetInstallationFolderPath().Returns("plugin/path"); _pluginReader.ReadPlugins("plugin/path").Returns(new List()); _pluginManager.LoadPlugins(); @@ -44,7 +44,7 @@ public void GivenEmptyPlugins_WhenLoadPlugins_ThenNoPluginsLoaded() [Fact] public void GivenAllValidPlugins_WhenLoadPlugins_ThenAllPluginsAreLoaded() { - _pluginPathProvider.GetPathToPlugins().Returns("plugin/path"); + _pluginPathProvider.GetInstallationFolderPath().Returns("plugin/path"); _pluginReader .ReadPlugins("plugin/path") .Returns( @@ -70,7 +70,7 @@ public void GivenSomeInvalidPlugins_WhenLoadPlugins_ThenAllValidPluginsAreLoaded Plugin testPlugin2 = new TestPlugin(new PluginId("id", new Version(0, 0, 1, 1))); Plugin testPlugin3 = new TestPlugin(new PluginId("id", new Version(0, 0, 2, 1))); - _pluginPathProvider.GetPathToPlugins().Returns("plugin/path"); + _pluginPathProvider.GetInstallationFolderPath().Returns("plugin/path"); _pluginReader .ReadPlugins("plugin/path") .Returns(new List { testPlugin1, testPlugin2, testPlugin3 });