diff --git a/src/SharpSite.Plugins/PluginAssemblyValidator.cs b/src/SharpSite.Plugins/PluginAssemblyValidator.cs index ab57175c..efc3fc8a 100644 --- a/src/SharpSite.Plugins/PluginAssemblyValidator.cs +++ b/src/SharpSite.Plugins/PluginAssemblyValidator.cs @@ -11,7 +11,6 @@ namespace SharpSite.Plugins; /// public class PluginAssemblyValidator(ILogger logger) { - private static readonly string HashRegistryPath = Path.Combine("plugins", "_assembly-hashes.json"); private static readonly object _hashFileLock = new(); /// @@ -132,4 +131,6 @@ private void SaveHashRegistry(Dictionary registry) File.WriteAllText(HashRegistryPath, json); } } + + private static string HashRegistryPath => SharpSitePathProvider.AssemblyHashRegistryPath; } diff --git a/src/SharpSite.Plugins/SharpSitePathProvider.cs b/src/SharpSite.Plugins/SharpSitePathProvider.cs new file mode 100644 index 00000000..7a8200b8 --- /dev/null +++ b/src/SharpSite.Plugins/SharpSitePathProvider.cs @@ -0,0 +1,32 @@ +namespace SharpSite.Plugins; + +public static class SharpSitePathProvider +{ + private static string _ContentRootPath = Directory.GetCurrentDirectory(); + + public static void Initialize(string? contentRootPath) + { + if (!string.IsNullOrWhiteSpace(contentRootPath)) + { + _ContentRootPath = contentRootPath; + } + } + + public static string ContentRootPath => _ContentRootPath; + + public static string PluginsRootPath => Path.Combine(_ContentRootPath, "plugins"); + + public static string UploadedPluginsRootPath => Path.Combine(PluginsRootPath, "_uploaded"); + + public static string PluginWebRootPath => Path.Combine(PluginsRootPath, "_wwwroot"); + + public static string DefaultPluginsRootPath => Path.Combine(_ContentRootPath, "defaultplugins"); + + public static string ApplicationStatePath => Path.Combine(PluginsRootPath, "applicationState.json"); + + public static string AssemblyHashRegistryPath => Path.Combine(PluginsRootPath, "_assembly-hashes.json"); + + public static string GetPluginInstallationPath(string pluginFolderName) => Path.Combine(PluginsRootPath, pluginFolderName); + + public static string GetPluginPrivateDirectoryPath(string directoryName) => Path.Combine(PluginsRootPath, "_" + directoryName); +} diff --git a/src/SharpSite.Web/ApplicationState.cs b/src/SharpSite.Web/ApplicationState.cs index 7dfc28c9..b0f6961e 100644 --- a/src/SharpSite.Web/ApplicationState.cs +++ b/src/SharpSite.Web/ApplicationState.cs @@ -113,7 +113,7 @@ public void SetTheme(PluginManifest manifest) private string GetApplicationStateFileContents() { // read the applicationState.json file in the root of the plugins folder -var appStateFile = Path.Combine("plugins", "applicationState.json"); +var appStateFile = SharpSitePathProvider.ApplicationStatePath; if (File.Exists(appStateFile)) { return File.ReadAllText(appStateFile); @@ -205,7 +205,7 @@ private Task PostLoadApplicationState(IServiceProvider services) public async Task Save() { // save application state to applicationState.json in the root of the plugins folder -var appStateFile = Path.Combine("plugins", "applicationState.json"); +var appStateFile = SharpSitePathProvider.ApplicationStatePath; var json = JsonSerializer.Serialize(this, SerializerOptions); await File.WriteAllTextAsync(appStateFile, json); diff --git a/src/SharpSite.Web/PluginManager.cs b/src/SharpSite.Web/PluginManager.cs index 0124527a..55367e03 100644 --- a/src/SharpSite.Web/PluginManager.cs +++ b/src/SharpSite.Web/PluginManager.cs @@ -30,12 +30,13 @@ public class PluginManager( private const long MaxTotalExtractedSize = 100L * 1024 * 1024; // 100MB private const long MaxSingleFileSize = 50L * 1024 * 1024; // 50MB private const double MaxCompressionRatio = 100.0; // 100:1 - - public static void Initialize() + + public static void Initialize(string? contentRootPath = null) { - Directory.CreateDirectory("plugins"); - Directory.CreateDirectory(Path.Combine("plugins", "_uploaded")); - Directory.CreateDirectory(Path.Combine("plugins", "_wwwroot")); + SharpSitePathProvider.Initialize(contentRootPath); + Directory.CreateDirectory(SharpSitePathProvider.PluginsRootPath); + Directory.CreateDirectory(SharpSitePathProvider.UploadedPluginsRootPath); + Directory.CreateDirectory(SharpSitePathProvider.PluginWebRootPath); } @@ -185,7 +186,7 @@ public async Task LoadPluginsAtStartup() _ServiceDescriptors.AddMemoryCache(); } - foreach (var pluginFolder in Directory.GetDirectories("plugins")) + foreach (var pluginFolder in Directory.GetDirectories(SharpSitePathProvider.PluginsRootPath)) { var pluginName = Path.GetFileName(pluginFolder); if (pluginName.StartsWith("_")) continue; @@ -378,7 +379,7 @@ private void ValidateArchiveSecurity(ZipArchive archive) DirectoryInfo pluginLibFolder; ZipArchive archive; - var pluginFolder = Directory.CreateDirectory(Path.Combine("plugins", "_uploaded")); + var pluginFolder = Directory.CreateDirectory(SharpSitePathProvider.UploadedPluginsRootPath); var filePath = Path.Combine(pluginFolder.FullName, $"{pluginManifest.IdVersionToString()}.sspkg"); using var pluginAssemblyFileStream = File.OpenWrite(filePath); @@ -386,7 +387,7 @@ private void ValidateArchiveSecurity(ZipArchive archive) logger.LogInformation("Plugin saved to {FilePath}", filePath); // Create a folder named after the plugin name under /plugins - pluginLibFolder = Directory.CreateDirectory(Path.Combine("plugins", pluginManifest.IdVersionToString())); + pluginLibFolder = Directory.CreateDirectory(SharpSitePathProvider.GetPluginInstallationPath(pluginManifest.IdVersionToString())); using var pluginMemoryStream = new MemoryStream(plugin.Bytes); archive = new ZipArchive(pluginMemoryStream, ZipArchiveMode.Read, true); @@ -397,7 +398,7 @@ private void ValidateArchiveSecurity(ZipArchive archive) if (hasWebContent) { - pluginWwwRootFolder = Directory.CreateDirectory(Path.Combine("plugins", "_wwwroot", pluginManifest.IdVersionToString())); + pluginWwwRootFolder = Directory.CreateDirectory(Path.Combine(SharpSitePathProvider.PluginWebRootPath, pluginManifest.IdVersionToString())); } foreach (var entry in archive.Entries) @@ -490,7 +491,7 @@ public Task CreateDirectoryInPluginsFolder(string name) { throw new InvalidFolderException($"Invalid path for folder: {name}"); } - return Task.FromResult(Directory.CreateDirectory(Path.Combine("plugins", "_" + name))); + return Task.FromResult(Directory.CreateDirectory(SharpSitePathProvider.GetPluginPrivateDirectoryPath(name))); } public T? GetPluginProvidedService() where T : class @@ -514,7 +515,7 @@ public Task MoveDirectoryInPluginsFolder(string oldName, string n { // check if the oldName directory exists - if (!Directory.Exists(Path.Combine("plugins", "_" + oldName))) + if (!Directory.Exists(SharpSitePathProvider.GetPluginPrivateDirectoryPath(oldName))) { throw new DirectoryNotFoundException($"Directory {oldName} not found in plugins folder."); } @@ -525,17 +526,17 @@ public Task MoveDirectoryInPluginsFolder(string oldName, string n // move the directory specified, which is prefixed with an underscore, to a new name Directory.Move( - Path.Combine("plugins", "_" + oldName), - Path.Combine("plugins", "_" + newName) + SharpSitePathProvider.GetPluginPrivateDirectoryPath(oldName), + SharpSitePathProvider.GetPluginPrivateDirectoryPath(newName) ); - return Task.FromResult(new DirectoryInfo(Path.Combine("plugins", "_" + newName))); + return Task.FromResult(new DirectoryInfo(SharpSitePathProvider.GetPluginPrivateDirectoryPath(newName))); } public DirectoryInfo GetDirectoryInPluginsFolder(string name) { - return new DirectoryInfo(Path.Combine("plugins", "_" + name)); + return new DirectoryInfo(SharpSitePathProvider.GetPluginPrivateDirectoryPath(name)); } private static readonly char[] _InvalidChars = Path.GetInvalidPathChars(); @@ -588,7 +589,7 @@ private static bool IsValidDirectory(string name) private static void EnsurePluginNotInstalled(PluginManifest? manifest, ILogger logger) { - if (manifest is not null && Directory.Exists(Path.Combine("plugins", manifest.IdVersionToString()))) + if (manifest is not null && Directory.Exists(SharpSitePathProvider.GetPluginInstallationPath(manifest.IdVersionToString()))) { var errMsg = string.Format(Locales.SharedResource.sharpsite_plugin_exists, manifest.IdVersionToString()); PluginException ex = new(errMsg); @@ -601,7 +602,7 @@ private static void EnsurePluginNotInstalled(PluginManifest? manifest, ILogger l public async Task InstallDefaultPlugins() { - var defaultPluginFolder = new DirectoryInfo("defaultplugins"); + var defaultPluginFolder = new DirectoryInfo(SharpSitePathProvider.DefaultPluginsRootPath); if (!defaultPluginFolder.Exists) return; foreach (var file in defaultPluginFolder.GetFiles("*.sspkg")) diff --git a/src/SharpSite.Web/PluginManagerExtensions.cs b/src/SharpSite.Web/PluginManagerExtensions.cs index a447b354..bce04095 100644 --- a/src/SharpSite.Web/PluginManagerExtensions.cs +++ b/src/SharpSite.Web/PluginManagerExtensions.cs @@ -11,7 +11,7 @@ public static class PluginManagerExtensions public static ApplicationState AddPluginManagerAndAppState(this WebApplicationBuilder builder) { - PluginManager.Initialize(); + PluginManager.Initialize(builder.Environment.ContentRootPath); var appState = new ApplicationState(); builder.Services.AddSingleton(appState); @@ -27,7 +27,7 @@ public static WebApplication ConfigurePluginFileSystem(this WebApplication app) { var pluginRoot = new PhysicalFileProvider( - Path.Combine(app.Environment.ContentRootPath, @"plugins/_wwwroot")); + SharpSitePathProvider.PluginWebRootPath); app.UseStaticFiles(); app.UseStaticFiles(new StaticFileOptions() { diff --git a/tests/SharpSite.Tests.Web/Startup/Step2DependencyTests.cs b/tests/SharpSite.Tests.Web/Startup/Step2DependencyTests.cs new file mode 100644 index 00000000..a8141c9e --- /dev/null +++ b/tests/SharpSite.Tests.Web/Startup/Step2DependencyTests.cs @@ -0,0 +1,43 @@ +using System.Reflection; +using Microsoft.AspNetCore.Components; +using SharpSite.Abstractions.FileStorage; +using Xunit; + +namespace SharpSite.Tests.Web.Startup; + +public class Step2DependencyTests +{ + [Fact] + public void Step2_ShouldNotInjectFileStorageDirectly() + { + // Arrange + var componentType = typeof(SharpSite.Web.Components.Startup.Step2); + + // Act + var injectedFileStorageProperties = componentType + .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Where(property => + property.PropertyType == typeof(IHandleFileStorage) && + property.GetCustomAttribute() is not null); + + // Assert + Assert.Empty(injectedFileStorageProperties); + } + + [Fact] + public void Step2_ShouldInjectPluginManagerForOptionalFileStorageLookup() + { + // Arrange + var componentType = typeof(SharpSite.Web.Components.Startup.Step2); + + // Act + var pluginManagerProperty = componentType + .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .SingleOrDefault(property => + property.PropertyType == typeof(SharpSite.Web.PluginManager) && + property.GetCustomAttribute() is not null); + + // Assert + Assert.NotNull(pluginManagerProperty); + } +}