diff --git a/src/Core/FileTime.Core.Abstraction/Models/FullName.cs b/src/Core/FileTime.Core.Abstraction/Models/FullName.cs index 71a6fd5..11d0f09 100644 --- a/src/Core/FileTime.Core.Abstraction/Models/FullName.cs +++ b/src/Core/FileTime.Core.Abstraction/Models/FullName.cs @@ -22,6 +22,8 @@ public record FullName(string Path) public string GetName() => Path.Split(Constants.SeparatorChar).Last(); - public FullName GetChild(string childName) - => new FullName(Path + Constants.SeparatorChar + childName); + public FullName GetChild(string childName) + => new(Path + Constants.SeparatorChar + childName); + + public override string ToString() => Path; } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Models/NativePath.cs b/src/Core/FileTime.Core.Abstraction/Models/NativePath.cs index 3a1b7e7..d8c9310 100644 --- a/src/Core/FileTime.Core.Abstraction/Models/NativePath.cs +++ b/src/Core/FileTime.Core.Abstraction/Models/NativePath.cs @@ -1,3 +1,6 @@ namespace FileTime.Core.Models; -public record NativePath(string Path); \ No newline at end of file +public record NativePath(string Path) +{ + public override string ToString() => Path; +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Local/FileTime.Providers.Local.csproj b/src/Providers/FileTime.Providers.Local/FileTime.Providers.Local.csproj index cec897a..6f8e695 100644 --- a/src/Providers/FileTime.Providers.Local/FileTime.Providers.Local.csproj +++ b/src/Providers/FileTime.Providers.Local/FileTime.Providers.Local.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Providers/FileTime.Providers.Local/LocalItemCreator.cs b/src/Providers/FileTime.Providers.Local/LocalItemCreator.cs index 21e4f6d..78dd70f 100644 --- a/src/Providers/FileTime.Providers.Local/LocalItemCreator.cs +++ b/src/Providers/FileTime.Providers.Local/LocalItemCreator.cs @@ -1,6 +1,7 @@ using FileTime.Core.ContentAccess; using FileTime.Core.Models; using FileTime.Providers.LocalAdmin; +using Microsoft.Extensions.Logging; namespace FileTime.Providers.Local; @@ -8,50 +9,75 @@ public class LocalItemCreator : ItemCreatorBase { private readonly IAdminContentAccessorFactory _adminContentAccessorFactory; private readonly IAdminContentProvider _adminContentProvider; + private readonly ILogger _logger; public LocalItemCreator( IAdminContentAccessorFactory adminContentAccessorFactory, - IAdminContentProvider adminContentProvider) + IAdminContentProvider adminContentProvider, + ILogger logger) { _adminContentAccessorFactory = adminContentAccessorFactory; _adminContentProvider = adminContentProvider; + _logger = logger; } public override async Task CreateContainerAsync(ILocalContentProvider contentProvider, FullName fullName) { + _logger.LogTrace("Start creating container {FullName}", fullName); var path = contentProvider.GetNativePath(fullName).Path; - if (Directory.Exists(path)) return; + if (Directory.Exists(path)) + { + _logger.LogTrace("Container with path {Path} already exists", path); + return; + } try { + _logger.LogTrace("Trying to create container with path {Path}", path); Directory.CreateDirectory(path); } - catch (UnauthorizedAccessException) + catch (UnauthorizedAccessException e) { - if (!_adminContentAccessorFactory.IsAdminModeSupported) throw; + _logger.LogDebug(e, "Failed to create container with path {Path}", path); + if (!_adminContentAccessorFactory.IsAdminModeSupported) + { + _logger.LogTrace("Admin mode is disabled, not trying to create {Path} as admin", path); + throw; + } - var adminContentAccessor = await _adminContentAccessorFactory.CreateAdminItemCreatorAsync(); - await adminContentAccessor.CreateContainerAsync(_adminContentProvider, fullName); + var adminItemCreator = await _adminContentAccessorFactory.CreateAdminItemCreatorAsync(); + await adminItemCreator.CreateContainerAsync(_adminContentProvider, fullName); } } public override async Task CreateElementAsync(ILocalContentProvider contentProvider, FullName fullName) { + _logger.LogTrace("Start creating element {FullName}", fullName); var path = contentProvider.GetNativePath(fullName).Path; - if (File.Exists(path)) return; - + if (File.Exists(path)) + { + _logger.LogTrace("Element with path {Path} already exists", path); + return; + } + try { + _logger.LogTrace("Trying to create element with path {Path}", path); await using (File.Create(path)) { } } - catch (UnauthorizedAccessException) + catch (UnauthorizedAccessException e) { - if (!_adminContentAccessorFactory.IsAdminModeSupported) throw; + _logger.LogDebug(e, "Failed to create element with path {Path}", path); + if (!_adminContentAccessorFactory.IsAdminModeSupported) + { + _logger.LogTrace("Admin mode is disabled, not trying to create {Path} as admin", path); + throw; + } - var adminContentAccessor = await _adminContentAccessorFactory.CreateAdminItemCreatorAsync(); - await adminContentAccessor.CreateElementAsync(_adminContentProvider, fullName); + var adminItemCreator = await _adminContentAccessorFactory.CreateAdminItemCreatorAsync(); + await adminItemCreator.CreateElementAsync(_adminContentProvider, fullName); } } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Local/LocalItemDeleter.cs b/src/Providers/FileTime.Providers.Local/LocalItemDeleter.cs index d2e18a7..9bbd39a 100644 --- a/src/Providers/FileTime.Providers.Local/LocalItemDeleter.cs +++ b/src/Providers/FileTime.Providers.Local/LocalItemDeleter.cs @@ -1,27 +1,65 @@ using FileTime.Core.ContentAccess; using FileTime.Core.Models; +using FileTime.Providers.LocalAdmin; +using Microsoft.Extensions.Logging; namespace FileTime.Providers.Local; public class LocalItemDeleter : IItemDeleter { - public Task DeleteAsync(ILocalContentProvider contentProvider, FullName fullName) + private readonly IAdminContentAccessorFactory _adminContentAccessorFactory; + private readonly IAdminContentProvider _adminContentProvider; + private readonly ILogger _logger; + + public LocalItemDeleter( + IAdminContentAccessorFactory adminContentAccessorFactory, + IAdminContentProvider adminContentProvider, + ILogger logger) { + _adminContentAccessorFactory = adminContentAccessorFactory; + _adminContentProvider = adminContentProvider; + _logger = logger; + } + + public async Task DeleteAsync(ILocalContentProvider contentProvider, FullName fullName) + { + _logger.LogTrace("Start deleting item {FullName}", fullName); var nativePath = contentProvider.GetNativePath(fullName).Path; - if (File.Exists(nativePath)) + try { - File.Delete(nativePath); + if (File.Exists(nativePath)) + { + _logger.LogTrace("File exists with path {Path}", nativePath); + File.Delete(nativePath); + } + else if (Directory.Exists(nativePath)) + { + _logger.LogTrace("Directory exists with path {Path}", nativePath); + Directory.Delete(nativePath, true); + } + else + { + _logger.LogTrace("No file or directory exists with path {Path}", nativePath); + throw new FileNotFoundException(nativePath); + } } - else if (Directory.Exists(nativePath)) + catch (Exception e) { - Directory.Delete(nativePath, true); - } - else - { - throw new FileNotFoundException(nativePath); - } + _logger.LogDebug(e, "Failed to delete item with path {Path}", nativePath); - return Task.CompletedTask; + if (!_adminContentAccessorFactory.IsAdminModeSupported + || e is not UnauthorizedAccessException and not IOException) + { + _logger.LogTrace( + "Admin mode is disabled or exception is not an access denied one, not trying to create {Path} as admin", + nativePath + ); + throw; + } + + var adminItemDeleter = await _adminContentAccessorFactory.CreateAdminItemDeleterAsync(); + await adminItemDeleter.DeleteAsync(_adminContentProvider, fullName); + } } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.LocalAdmin.Abstractions/IAdminContentAccessorFactory.cs b/src/Providers/FileTime.Providers.LocalAdmin.Abstractions/IAdminContentAccessorFactory.cs index b124332..ec975a9 100644 --- a/src/Providers/FileTime.Providers.LocalAdmin.Abstractions/IAdminContentAccessorFactory.cs +++ b/src/Providers/FileTime.Providers.LocalAdmin.Abstractions/IAdminContentAccessorFactory.cs @@ -6,4 +6,5 @@ public interface IAdminContentAccessorFactory { bool IsAdminModeSupported { get; } Task CreateAdminItemCreatorAsync(); + Task CreateAdminItemDeleterAsync(); } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.LocalAdmin/AdminContentAccessorFactory.cs b/src/Providers/FileTime.Providers.LocalAdmin/AdminContentAccessorFactory.cs index 7d7c32c..52ad992 100644 --- a/src/Providers/FileTime.Providers.LocalAdmin/AdminContentAccessorFactory.cs +++ b/src/Providers/FileTime.Providers.LocalAdmin/AdminContentAccessorFactory.cs @@ -33,4 +33,18 @@ public class AdminContentAccessorFactory : IAdminContentAccessorFactory .GetRequiredService(); return adminItemCreator; } + + public async Task CreateAdminItemDeleterAsync() + { + await _adminElevationManager.CreateAdminInstanceIfNecessaryAsync(); + var connection = await _adminElevationManager.CreateConnectionAsync(); + + Debug.Assert(connection != null); + + var adminItemDeleter = _serviceProvider.GetInitableResolver( + connection, + _adminElevationManager.ProviderName) + .GetRequiredService(); + return adminItemDeleter; + } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Remote.Abstractions/IRemoteItemDeleter.cs b/src/Providers/FileTime.Providers.Remote.Abstractions/IRemoteItemDeleter.cs new file mode 100644 index 0000000..f6518bd --- /dev/null +++ b/src/Providers/FileTime.Providers.Remote.Abstractions/IRemoteItemDeleter.cs @@ -0,0 +1,10 @@ +using FileTime.Core.ContentAccess; +using FileTime.Server.Common; +using InitableService; + +namespace FileTime.Providers.Remote; + +public interface IRemoteItemDeleter : IItemDeleter, IInitable +{ + +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Remote/RemoteItemDeleter.cs b/src/Providers/FileTime.Providers.Remote/RemoteItemDeleter.cs new file mode 100644 index 0000000..cd91e4c --- /dev/null +++ b/src/Providers/FileTime.Providers.Remote/RemoteItemDeleter.cs @@ -0,0 +1,17 @@ +using FileTime.Core.Models; +using FileTime.Server.Common; + +namespace FileTime.Providers.Remote; + +public class RemoteItemDeleter : IRemoteItemDeleter +{ + private IRemoteConnection _remoteConnection = null!; + private string _remoteContentProviderId = null!; + public void Init(IRemoteConnection remoteConnection, string remoteContentProviderId) + { + _remoteConnection = remoteConnection; + _remoteContentProviderId = remoteContentProviderId; + } + public async Task DeleteAsync(IRemoteContentProvider contentProvider, FullName fullName) + => await _remoteConnection.DeleteItemAsync(_remoteContentProviderId, fullName); +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Remote/Startup.cs b/src/Providers/FileTime.Providers.Remote/Startup.cs index 59a975e..fe2cf4d 100644 --- a/src/Providers/FileTime.Providers.Remote/Startup.cs +++ b/src/Providers/FileTime.Providers.Remote/Startup.cs @@ -10,6 +10,7 @@ public static class Startup { serviceCollection.TryAddSingleton(); serviceCollection.TryAddTransient(); + serviceCollection.TryAddTransient(); return serviceCollection; } diff --git a/src/Server/FileTime.Server.Common.Abstractions/IRemoteConnection.cs b/src/Server/FileTime.Server.Common.Abstractions/IRemoteConnection.cs index ee3e79e..52388cc 100644 --- a/src/Server/FileTime.Server.Common.Abstractions/IRemoteConnection.cs +++ b/src/Server/FileTime.Server.Common.Abstractions/IRemoteConnection.cs @@ -7,4 +7,5 @@ public interface IRemoteConnection Task Exit(); Task CreateContainerAsync(string contentProviderId, FullName fullName); Task CreateElementAsync(string contentProviderId, FullName fullName); + Task DeleteItemAsync(string contentProviderId, FullName fullName); } \ No newline at end of file diff --git a/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRHub.cs b/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRHub.cs index 7487070..94f90ee 100644 --- a/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRHub.cs +++ b/src/Server/FileTime.Server.Common/Connections/SignalR/ISignalRHub.cs @@ -5,4 +5,5 @@ public interface ISignalRHub Task Exit(); Task CreateContainerAsync(string contentProviderId, string fullName); Task CreateElementAsync(string contentProviderId, string fullName); + Task DeleteItemAsync(string contentProviderId, string fullName); } \ No newline at end of file diff --git a/src/Server/FileTime.Server.Common/Connections/SignalR/SignalRConnection.cs b/src/Server/FileTime.Server.Common/Connections/SignalR/SignalRConnection.cs index 0993da5..b47f447 100644 --- a/src/Server/FileTime.Server.Common/Connections/SignalR/SignalRConnection.cs +++ b/src/Server/FileTime.Server.Common/Connections/SignalR/SignalRConnection.cs @@ -39,4 +39,10 @@ public class SignalRConnection : IRemoteConnection, IAsyncInitable var client = CreateClient(); await client.CreateElementAsync(contentProviderId, fullName.Path); } + + public async Task DeleteItemAsync(string contentProviderId, FullName fullName) + { + var client = CreateClient(); + await client.DeleteItemAsync(contentProviderId, fullName.Path); + } } \ No newline at end of file diff --git a/src/Server/FileTime.Server.Web/ConnectionHub.cs b/src/Server/FileTime.Server.Web/ConnectionHub.cs index 99b8032..2ad2613 100644 --- a/src/Server/FileTime.Server.Web/ConnectionHub.cs +++ b/src/Server/FileTime.Server.Web/ConnectionHub.cs @@ -42,4 +42,11 @@ public class ConnectionHub : Hub, ISignalRHub var itemCreator = _contentAccessorFactory.GetItemCreator(contentProvider); await itemCreator.CreateElementAsync(contentProvider, new FullName(fullName)); } + + public async Task DeleteItemAsync(string contentProviderId, string fullName) + { + var contentProvider = _contentProviderRegistry.ContentProviders.First(p => p.Name == contentProviderId); + var itemDeleter = _contentAccessorFactory.GetItemDeleter(contentProvider); + await itemDeleter.DeleteAsync(contentProvider, new FullName(fullName)); + } } \ No newline at end of file diff --git a/src/Server/FileTime.Server/Program.cs b/src/Server/FileTime.Server/Program.cs index f64e150..07c4fee 100644 --- a/src/Server/FileTime.Server/Program.cs +++ b/src/Server/FileTime.Server/Program.cs @@ -5,6 +5,7 @@ using FileTime.Providers.Local; using FileTime.Server.App; using FileTime.Server.Common; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Serilog; @@ -52,7 +53,8 @@ IContainer CreateRootDiContainer(IConfigurationRoot configuration) var serviceCollection = DependencyInjection .RegisterDefaultServices(configuration) .AddLocalProviderServices() - .AddServerServices(); + .AddServerServices() + .AddLogging(loggingBuilder => loggingBuilder.AddSerilog()); serviceCollection.TryAddSingleton( new ApplicationStopper(() => applicationCancellation.Cancel())