diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IIdentifiableUserCommandService.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IIdentifiableUserCommandService.cs index 9151d56..012a060 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IIdentifiableUserCommandService.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IIdentifiableUserCommandService.cs @@ -4,7 +4,7 @@ namespace FileTime.App.Core.Services; public interface IIdentifiableUserCommandService { - void AddIdentifiableUserCommandFactory(string identifier, IIdentifiableUserCommand commandFactory); + void AddIdentifiableUserCommand(IIdentifiableUserCommand command); IIdentifiableUserCommand? GetCommand(string identifier); IReadOnlyCollection GetCommandIdentifiers(); IReadOnlyDictionary IdentifiableUserCommands { get; } diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Services/UserCommandHandler/AggregatedUserCommandHandler.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Services/UserCommandHandler/AggregatedUserCommandHandler.cs new file mode 100644 index 0000000..3e39886 --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Services/UserCommandHandler/AggregatedUserCommandHandler.cs @@ -0,0 +1,26 @@ +namespace FileTime.App.Core.Services.UserCommandHandler; + +public abstract class AggregatedUserCommandHandler : IUserCommandHandler +{ + private readonly List _userCommandHandlers = new(); + + public bool CanHandleCommand(UserCommand.IUserCommand command) => _userCommandHandlers.Any(h => h.CanHandleCommand(command)); + + public async Task HandleCommandAsync(UserCommand.IUserCommand command) + { + var handler = _userCommandHandlers.Find(h => h.CanHandleCommand(command)); + + if (handler is null) return; + await handler.HandleCommandAsync(command); + } + + protected void AddCommandHandler(IUserCommandHandler userCommandHandler) => _userCommandHandlers.Add(userCommandHandler); + + protected void AddCommandHandler(IEnumerable commandHandlers) + { + foreach (var userCommandHandler in commandHandlers) + { + AddCommandHandler(userCommandHandler); + } + } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/TypeUserCommandHandler.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Services/UserCommandHandler/TypeUserCommandHandler.cs similarity index 100% rename from src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/TypeUserCommandHandler.cs rename to src/AppCommon/FileTime.App.Core.Abstraction/Services/UserCommandHandler/TypeUserCommandHandler.cs diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/CommandSchedulerUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/CommandSchedulerUserCommandHandlerService.cs index 77cdf8e..04a02dc 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/CommandSchedulerUserCommandHandlerService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/CommandSchedulerUserCommandHandlerService.cs @@ -10,7 +10,7 @@ public class CommandSchedulerUserCommandHandlerService : UserCommandHandlerServi public CommandSchedulerUserCommandHandlerService(ICommandScheduler commandScheduler) { _commandScheduler = commandScheduler; - AddCommandHandlers(new IUserCommandHandler[] + AddCommandHandler(new IUserCommandHandler[] { new TypeUserCommandHandler(PauseCommandScheduler), new TypeUserCommandHandler(StartCommandScheduler), diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/IdentifiableUserCommandService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/IdentifiableUserCommandService.cs index bb7e1cf..88651dd 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/IdentifiableUserCommandService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/IdentifiableUserCommandService.cs @@ -12,8 +12,8 @@ public class IdentifiableUserCommandService : IIdentifiableUserCommandService IdentifiableUserCommands = _identifiableUserCommands.AsReadOnly(); } - public void AddIdentifiableUserCommandFactory(string identifier, IIdentifiableUserCommand commandFactory) - => _identifiableUserCommands.Add(identifier, commandFactory); + public void AddIdentifiableUserCommand(IIdentifiableUserCommand command) + => _identifiableUserCommands.Add(command.UserCommandID, command); public IIdentifiableUserCommand? GetCommand(string identifier) { diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs index 29c26c2..a743530 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs @@ -62,7 +62,7 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi _markedItems = appState.SelectedTab.Map(t => t?.MarkedItems).Switch(); - AddCommandHandlers(new IUserCommandHandler[] + AddCommandHandler(new IUserCommandHandler[] { new TypeUserCommandHandler(CopyAsync), new TypeUserCommandHandler(DeleteAsync), diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs index 9afb8e3..610dbc4 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs @@ -59,7 +59,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase appState.ViewMode.Subscribe(v => _viewMode = v); - AddCommandHandlers(new IUserCommandHandler[] + AddCommandHandler(new IUserCommandHandler[] { new TypeUserCommandHandler(CloseTab), new TypeUserCommandHandler(EnterRapidTravel), diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs index f0a4c4d..3df9ca0 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs @@ -45,7 +45,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase SaveCurrentSelectedItem(i => _currentSelectedItem = i); SaveSelectedTab(t => _currentSelectedTab = t); - AddCommandHandlers(new IUserCommandHandler[] + AddCommandHandler(new IUserCommandHandler[] { new TypeUserCommandHandler(OpenInDefaultFileExplorer), new TypeUserCommandHandler(CopyNativePath), diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/UserCommandHandler.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/UserCommandHandler.cs deleted file mode 100644 index 4d15fda..0000000 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/UserCommandHandler.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace FileTime.App.Core.Services.UserCommandHandler; - -public sealed class UserCommandHandler : IUserCommandHandler -{ - private readonly Func _canHandle; - private readonly Func _handle; - - public UserCommandHandler(Func canHandle, Func handle) - { - _canHandle = canHandle; - _handle = handle; - } - - public bool CanHandleCommand(UserCommand.IUserCommand command) => _canHandle(command); - public async Task HandleCommandAsync(UserCommand.IUserCommand command) => await _handle(command); -} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/UserCommandHandlerServiceBase.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/UserCommandHandlerServiceBase.cs index dbede0b..96720d5 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/UserCommandHandlerServiceBase.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/UserCommandHandlerServiceBase.cs @@ -8,9 +8,8 @@ using FileTime.Core.Timeline; namespace FileTime.App.Core.Services.UserCommandHandler; -public abstract class UserCommandHandlerServiceBase : IUserCommandHandler +public abstract class UserCommandHandlerServiceBase : AggregatedUserCommandHandler { - private readonly List _userCommandHandlers = new(); private readonly IAppState? _appState; private readonly ITimelessContentProvider? _timelessContentProvider; @@ -22,28 +21,6 @@ public abstract class UserCommandHandlerServiceBase : IUserCommandHandler _timelessContentProvider = timelessContentProvider; } - public bool CanHandleCommand(UserCommand.IUserCommand command) => _userCommandHandlers.Any(h => h.CanHandleCommand(command)); - - public async Task HandleCommandAsync(UserCommand.IUserCommand command) - { - var handler = _userCommandHandlers.Find(h => h.CanHandleCommand(command)); - - if (handler is null) return; - await handler.HandleCommandAsync(command); - } - - protected void AddCommandHandler(IUserCommandHandler userCommandHandler) => _userCommandHandlers.Add(userCommandHandler); - - protected void AddCommandHandlers(IEnumerable commandHandlers) - { - foreach (var userCommandHandler in commandHandlers) - { - AddCommandHandler(userCommandHandler); - } - } - - protected void RemoveCommandHandler(IUserCommandHandler userCommandHandler) => _userCommandHandlers.Remove(userCommandHandler); - protected IDisposable SaveSelectedTab(Action handler) => RunWithAppState(appState => appState.SelectedTab.Subscribe(handler)); protected IDisposable SaveCurrentSelectedItem(Action?> handler) diff --git a/src/AppCommon/FileTime.App.Core/StartupServices/DefaultIdentifiableCommandHandlerRegister.cs b/src/AppCommon/FileTime.App.Core/StartupServices/DefaultIdentifiableCommandHandlerRegister.cs index b6745cb..e996894 100644 --- a/src/AppCommon/FileTime.App.Core/StartupServices/DefaultIdentifiableCommandHandlerRegister.cs +++ b/src/AppCommon/FileTime.App.Core/StartupServices/DefaultIdentifiableCommandHandlerRegister.cs @@ -69,5 +69,5 @@ public class DefaultIdentifiableCommandHandlerRegister : IStartupHandler public Task InitAsync() => Task.CompletedTask; private void AddUserCommand(IIdentifiableUserCommand command) - => _userCommandHandlerService.AddIdentifiableUserCommandFactory(command.UserCommandID, command); + => _userCommandHandlerService.AddIdentifiableUserCommand(command); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentReader.cs b/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentReader.cs index d70d99c..c71a63e 100644 --- a/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentReader.cs +++ b/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentReader.cs @@ -7,4 +7,5 @@ public interface IContentReader : IDisposable Task ReadBytesAsync(int bufferSize, int? offset = null); void SetPosition(long position); + Stream AsStream(); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentWriter.cs b/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentWriter.cs index 65da2a1..70bf305 100644 --- a/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentWriter.cs +++ b/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentWriter.cs @@ -6,4 +6,5 @@ public interface IContentWriter : IDisposable Task WriteBytesAsync(byte[] data, int? index = null, CancellationToken cancellationToken = default); Task FlushAsync(CancellationToken cancellationToken = default); + Stream AsStream(); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Interactions/IOptionsInputElement.cs b/src/Core/FileTime.Core.Abstraction/Interactions/IOptionsInputElement.cs index 8764de7..afa0b23 100644 --- a/src/Core/FileTime.Core.Abstraction/Interactions/IOptionsInputElement.cs +++ b/src/Core/FileTime.Core.Abstraction/Interactions/IOptionsInputElement.cs @@ -3,5 +3,5 @@ namespace FileTime.Core.Interactions; public interface IOptionsInputElement : IInputElement { object Value { get; set; } - IEnumerable Options { get; } + IReadOnlyCollection Options { get; } } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Interactions/OptionsInputElement.cs b/src/Core/FileTime.Core.Abstraction/Interactions/OptionsInputElement.cs index 6d17e48..9a3a9d6 100644 --- a/src/Core/FileTime.Core.Abstraction/Interactions/OptionsInputElement.cs +++ b/src/Core/FileTime.Core.Abstraction/Interactions/OptionsInputElement.cs @@ -5,19 +5,30 @@ namespace FileTime.Core.Interactions; public partial class OptionsInputElement : InputElementBase, IOptionsInputElement, INotifyPropertyChanged { - public IEnumerable> Options { get; } + public IReadOnlyCollection> Options { get; } [Notify] private T? _value; - IEnumerable IOptionsInputElement.Options => Options; + IReadOnlyCollection IOptionsInputElement.Options => Options; + object? IOptionsInputElement.Value { - get => Value; - set => Value = (T?)value; + get => Options.FirstOrDefault(o => o.Value?.Equals(_value) ?? false); + set + { + if (value is T newValue) + { + Value = newValue; + } + else if (value is OptionElement optionElement) + { + Value = optionElement.Value; + } + } } public OptionsInputElement(string label, IEnumerable> options) : base(label, InputType.Options) { - Options = options; + Options = options.ToList(); } } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs index 91162d4..fb5b1ed 100644 --- a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs +++ b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs @@ -33,11 +33,15 @@ public class CopyCommand : CommandBase, ITransportationCommand ITimelessContentProvider timelessContentProvider, ICommandSchedulerNotifier commandSchedulerNotifier, ILogger logger, - IReadOnlyCollection? sources, - TransportMode? mode, - FullName? targetFullName) + IReadOnlyCollection sources, + TransportMode mode, + FullName targetFullName) : base("Copy - Calculating...") { + ArgumentNullException.ThrowIfNull(sources); + ArgumentNullException.ThrowIfNull(mode); + ArgumentNullException.ThrowIfNull(targetFullName); + _timelessContentProvider = timelessContentProvider; _commandSchedulerNotifier = commandSchedulerNotifier; _logger = logger; @@ -54,12 +58,8 @@ public class CopyCommand : CommandBase, ITransportationCommand .Switch() .Subscribe(SetCurrentProgress); - if (sources is null) throw new ArgumentException(nameof(Sources) + " can not be null"); - if (targetFullName is null) throw new ArgumentException(nameof(Target) + " can not be null"); - if (mode is null) throw new ArgumentException(nameof(TransportMode) + " can not be null"); - Sources = new List(sources).AsReadOnly(); - TransportMode = mode.Value; + TransportMode = mode; Target = targetFullName; var recentSpeed = DeclarativePropertyHelpers.CombineLatest( diff --git a/src/FileTime.sln b/src/FileTime.sln index 2e31973..ddb2064 100644 --- a/src/FileTime.sln +++ b/src/FileTime.sln @@ -107,6 +107,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Server.Common.Abst EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Server.Web", "Server\FileTime.Server.Web\FileTime.Server.Web.csproj", "{9062F7D2-34DE-44B7-A2D6-8B3AFDEBB606}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Tools.Compression", "Tools\FileTime.Tools.Compression\FileTime.Tools.Compression.csproj", "{58243F14-15A6-4601-A071-F99BE896D027}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Tools.Compression.Core", "Tools\FileTime.Tools.Compression.Core\FileTime.Tools.Compression.Core.csproj", "{EE1721A0-D15A-4E40-BEA5-8AB6BAB8FD44}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -285,6 +289,14 @@ Global {9062F7D2-34DE-44B7-A2D6-8B3AFDEBB606}.Debug|Any CPU.Build.0 = Debug|Any CPU {9062F7D2-34DE-44B7-A2D6-8B3AFDEBB606}.Release|Any CPU.ActiveCfg = Release|Any CPU {9062F7D2-34DE-44B7-A2D6-8B3AFDEBB606}.Release|Any CPU.Build.0 = Release|Any CPU + {58243F14-15A6-4601-A071-F99BE896D027}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58243F14-15A6-4601-A071-F99BE896D027}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58243F14-15A6-4601-A071-F99BE896D027}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58243F14-15A6-4601-A071-F99BE896D027}.Release|Any CPU.Build.0 = Release|Any CPU + {EE1721A0-D15A-4E40-BEA5-8AB6BAB8FD44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE1721A0-D15A-4E40-BEA5-8AB6BAB8FD44}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE1721A0-D15A-4E40-BEA5-8AB6BAB8FD44}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE1721A0-D15A-4E40-BEA5-8AB6BAB8FD44}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -334,6 +346,8 @@ Global {181BC62C-EDEA-4DD1-8837-A4B1CBF8BE42} = {778AAF38-20FF-438C-A9C3-60850C8B5A27} {C82B417F-94E6-4D4D-B261-0CAF40551D5E} = {778AAF38-20FF-438C-A9C3-60850C8B5A27} {9062F7D2-34DE-44B7-A2D6-8B3AFDEBB606} = {778AAF38-20FF-438C-A9C3-60850C8B5A27} + {58243F14-15A6-4601-A071-F99BE896D027} = {8C3CFEFE-78A5-4940-B388-D15FCE02ECE9} + {EE1721A0-D15A-4E40-BEA5-8AB6BAB8FD44} = {8C3CFEFE-78A5-4940-B388-D15FCE02ECE9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF} diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml.cs index 5ce3207..d47b0bf 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml.cs @@ -9,6 +9,7 @@ using FileTime.GuiApp.Font; using FileTime.GuiApp.ViewModels; using FileTime.GuiApp.Views; using FileTime.Server.Common; +using FileTime.Tools.Compression; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -25,6 +26,7 @@ public class App : Application .AddFrequencyNavigation() .AddCommandPalette() .AddSearch() + .AddCompression() .AddConfiguration(configuration) .ConfigureFont(configuration) .RegisterLogging() diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/FileTime.GuiApp.App.csproj b/src/GuiApp/Avalonia/FileTime.GuiApp.App/FileTime.GuiApp.App.csproj index e0b1fd8..75465ef 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/FileTime.GuiApp.App.csproj +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/FileTime.GuiApp.App.csproj @@ -45,6 +45,7 @@ + diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Styles.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Styles.axaml index c4f67b4..7195137 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Styles.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Styles.axaml @@ -1,4 +1,7 @@ - + @@ -78,10 +81,11 @@ + VerticalAlignment="Center"> + + diff --git a/src/Providers/FileTime.Providers.Local/LocalContentReader.cs b/src/Providers/FileTime.Providers.Local/LocalContentReader.cs index a69bb3d..a14cda1 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentReader.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentReader.cs @@ -45,6 +45,8 @@ public class LocalContentReader : IContentReader Position = position; } + public Stream AsStream() => _binaryReader.BaseStream; + ~LocalContentReader() { Dispose(false); diff --git a/src/Providers/FileTime.Providers.Local/LocalContentWriter.cs b/src/Providers/FileTime.Providers.Local/LocalContentWriter.cs index f002a50..b3196a0 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentWriter.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentWriter.cs @@ -34,6 +34,8 @@ public class LocalContentWriter : IContentWriter return Task.CompletedTask; } + public Stream AsStream() => _binaryWriter.BaseStream; + ~LocalContentWriter() { Dispose(false); diff --git a/src/Providers/FileTime.Providers.Remote/RemoteContentWriter.cs b/src/Providers/FileTime.Providers.Remote/RemoteContentWriter.cs index 4ad2b39..27a0fc5 100644 --- a/src/Providers/FileTime.Providers.Remote/RemoteContentWriter.cs +++ b/src/Providers/FileTime.Providers.Remote/RemoteContentWriter.cs @@ -1,4 +1,5 @@ -using FileTime.Core.Models; +using FileTime.Core.ContentAccess; +using FileTime.Core.Models; using FileTime.Server.Common; namespace FileTime.Providers.Remote; @@ -43,6 +44,8 @@ public class RemoteContentWriter : IRemoteContentWriter await _remoteConnection.FlushWriterAsync(_transactionId, cancellationToken); } + public Stream AsStream() => new ContentAccessStream(this); + private async Task InitializeRemoteWriter(NativePath nativePath) { _isRemoteWriterInitialized = true; diff --git a/src/Tools/FileTime.Tools.Compression.Core/CompressOperation.cs b/src/Tools/FileTime.Tools.Compression.Core/CompressOperation.cs new file mode 100644 index 0000000..95af719 --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression.Core/CompressOperation.cs @@ -0,0 +1,70 @@ +using FileTime.Core.ContentAccess; +using FileTime.Core.Models; +using SharpCompress.Archives; +using SharpCompress.Common; + +namespace FileTime.Tools.Compression; + +public class CompressOperation : ICompressOperation + where TEntry : IArchiveEntry + where TVolume : IVolume +{ + private readonly IContentAccessorFactory _contentAccessorFactory; + private readonly AbstractWritableArchive _archive; + private readonly Action _saveTo; + private bool _disposed; + + public CompressOperation( + IContentAccessorFactory contentAccessorFactory, + AbstractWritableArchive archive, + Action saveTo + ) + { + _contentAccessorFactory = contentAccessorFactory; + _archive = archive; + _saveTo = saveTo; + } + + public async Task> CompressElement(IElement element, string key) + { + if (element.Provider.SupportsContentStreams) + { + var contentReader = await _contentAccessorFactory.GetContentReaderFactory(element.Provider).CreateContentReaderAsync(element); + + var contentReaderStream = contentReader.AsStream(); + + _archive.AddEntry(key, contentReaderStream); + + return new IDisposable[] {contentReader, contentReaderStream}; + } + + return Enumerable.Empty(); + } + + public void SaveTo(Stream stream) + => _saveTo(stream); + + ~CompressOperation() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _archive.Dispose(); + } + } + + _disposed = true; + } +} \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression.Core/FileTime.Tools.Compression.Core.csproj b/src/Tools/FileTime.Tools.Compression.Core/FileTime.Tools.Compression.Core.csproj new file mode 100644 index 0000000..a5ce8c8 --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression.Core/FileTime.Tools.Compression.Core.csproj @@ -0,0 +1,18 @@ + + + + net7.0 + enable + enable + FileTime.Tools.Compression + + + + + + + + + + + diff --git a/src/Tools/FileTime.Tools.Compression.Core/ICompressOperation.cs b/src/Tools/FileTime.Tools.Compression.Core/ICompressOperation.cs new file mode 100644 index 0000000..40e38a4 --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression.Core/ICompressOperation.cs @@ -0,0 +1,9 @@ +using FileTime.Core.Models; + +namespace FileTime.Tools.Compression; + +public interface ICompressOperation : IDisposable +{ + Task> CompressElement(IElement element, string key); + void SaveTo(Stream stream); +} \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/CompressCommand.cs b/src/Tools/FileTime.Tools.Compression/CompressCommand.cs new file mode 100644 index 0000000..0f0baf2 --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression/CompressCommand.cs @@ -0,0 +1,164 @@ +using FileTime.Core.Command; +using FileTime.Core.ContentAccess; +using FileTime.Core.Enums; +using FileTime.Core.Interactions; +using FileTime.Core.Models; +using FileTime.Core.Timeline; +using FileTime.Tools.Extensions; +using SharpCompress.Archives; +using SharpCompress.Common; +using SharpCompressCompressionType = SharpCompress.Common.CompressionType; + +namespace FileTime.Tools.Compression; + +public class CompressCommand : CommandBase, IExecutableCommand, ITransportationCommand, IRequireInputCommand +{ + private readonly IList _inputs; + private readonly IUserCommunicationService _userCommunicationService; + private readonly ITimelessContentProvider _timelessContentProvider; + private readonly IContentAccessorFactory _contentAccessorFactory; + private readonly OptionsInputElement _compressionType; + private readonly CancellationTokenSource _cancellationTokenSource = new(); + private readonly TextInputElement _targetFileName; + public IReadOnlyList Sources { get; } + public FullName Target { get; } + public TransportMode TransportMode { get; } + + + internal CompressCommand( + IUserCommunicationService userCommunicationService, + ITimelessContentProvider timelessContentProvider, + IContentAccessorFactory contentAccessorFactory, + IReadOnlyCollection sources, + TransportMode mode, + FullName targetFullName) + { + _userCommunicationService = userCommunicationService; + _timelessContentProvider = timelessContentProvider; + _contentAccessorFactory = contentAccessorFactory; + ArgumentNullException.ThrowIfNull(sources); + ArgumentNullException.ThrowIfNull(mode); + ArgumentNullException.ThrowIfNull(targetFullName); + + Sources = new List(sources).AsReadOnly(); + TransportMode = mode; + Target = targetFullName; + + _targetFileName = new TextInputElement("File name"); + _compressionType = new OptionsInputElement( + "CompressionMethod", + Enum.GetValues() + .Select(t => new OptionElement(t.ToString(), t)) + ); + + _inputs = new List + { + _targetFileName, + _compressionType + }; + } + + + public override Task CanRun(PointInTime currentTime) + { + //TODO: + return Task.FromResult(CanCommandRun.True); + } + + public override Task SimulateCommand(PointInTime currentTime) + { + var differences = new List + { + new(DifferenceActionType.Create, new AbsolutePath(_timelessContentProvider, currentTime, Target, AbsolutePathType.Element)) + }; + return Task.FromResult(currentTime.WithDifferences(differences)); + } + + public override void Cancel() + => _cancellationTokenSource.Cancel(); + + public async Task Execute() + { + var disposables = Enumerable.Empty(); + + ICompressOperation? compressOperation = null; + try + { + var compressionType = _compressionType.Value; + + compressOperation = compressionType switch + { + CompressionType.Gzip => SharpCompress.Archives.GZip.GZipArchive.Create().Map(a => GetCompressOperation(a, (s) => a.SaveTo(s, new SharpCompress.Writers.WriterOptions(SharpCompressCompressionType.GZip)))), + CompressionType.Zip => SharpCompress.Archives.Zip.ZipArchive.Create().Map(a => GetCompressOperation(a, a.SaveTo)), + CompressionType.Tar => SharpCompress.Archives.Tar.TarArchive.Create().Map(a => GetCompressOperation(a, (s) => a.SaveTo(s, new SharpCompress.Writers.WriterOptions(SharpCompressCompressionType.None)))), + CompressionType.TarBz2 => SharpCompress.Archives.Tar.TarArchive.Create().Map(a => GetCompressOperation(a, (s) => a.SaveTo(s, new SharpCompress.Writers.WriterOptions(SharpCompressCompressionType.BZip2)))), + CompressionType.TarLz => SharpCompress.Archives.Tar.TarArchive.Create().Map(a => GetCompressOperation(a, (s) => a.SaveTo(s, new SharpCompress.Writers.WriterOptions(SharpCompressCompressionType.LZip)))), + _ => throw new NotImplementedException() + }; + + disposables = await TraverseTree(Sources, "", compressOperation); + + var resolvedParent = await _timelessContentProvider.GetItemByFullNameAsync( + Target, + PointInTime.Present + ); + + var newItemName = Target.GetChild(_targetFileName.Value!); + await _contentAccessorFactory.GetItemCreator(resolvedParent.Provider).CreateElementAsync(resolvedParent.Provider, newItemName); + var targetElement = (IElement) await _timelessContentProvider.GetItemByFullNameAsync(newItemName, PointInTime.Present); + using var contentWriter = await _contentAccessorFactory.GetContentWriterFactory(resolvedParent.Provider).CreateContentWriterAsync(targetElement); + await using var contentWriterStream = contentWriter.AsStream(); + compressOperation.SaveTo(contentWriterStream); + + await contentWriterStream.FlushAsync(_cancellationTokenSource.Token); + } + finally + { + compressOperation?.Dispose(); + + foreach (var disposable in disposables) + { + disposable.Dispose(); + } + } + } + + private async Task> TraverseTree( + IEnumerable sources, + string basePath, + ICompressOperation operations, + CancellationToken cancellationToken = default) + { + var disposables = Enumerable.Empty(); + foreach (var source in sources) + { + var item = await _timelessContentProvider.GetItemByFullNameAsync( + source, + PointInTime.Present + ); + + if (item is IContainer container) + { + var items = container.Items; + + var childItems = items.Select(i => i.Path).ToList(); + var path = string.IsNullOrEmpty(basePath) ? container.Name : basePath + "\\" + container.Name; + disposables = disposables.Concat(await TraverseTree(childItems, path, operations, cancellationToken)); + } + else if (item is IElement element) + { + var path = string.IsNullOrEmpty(basePath) ? element.Name : basePath + "\\" + element.Name; + disposables = disposables.Concat(await operations.CompressElement(element, path)); + } + } + + return disposables; + } + + private CompressOperation GetCompressOperation(AbstractWritableArchive archive, Action saveTo) + where TEntry : IArchiveEntry where TVolume : IVolume + => new(_contentAccessorFactory, archive, saveTo); + + public async Task ReadInputs() + => await _userCommunicationService.ReadInputs(_inputs); +} \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/CompressCommandFactory.cs b/src/Tools/FileTime.Tools.Compression/CompressCommandFactory.cs new file mode 100644 index 0000000..f5e414f --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression/CompressCommandFactory.cs @@ -0,0 +1,34 @@ +using FileTime.Core.Command; +using FileTime.Core.ContentAccess; +using FileTime.Core.Interactions; +using FileTime.Core.Models; +using FileTime.Core.Timeline; + +namespace FileTime.Tools.Compression; + +public class CompressCommandFactory : ITransportationCommandFactory +{ + private readonly IUserCommunicationService _userCommunicationService; + private readonly ITimelessContentProvider _timelessContentProvider; + private readonly IContentAccessorFactory _contentAccessorFactory; + + public CompressCommandFactory( + IUserCommunicationService userCommunicationService, + ITimelessContentProvider timelessContentProvider, + IContentAccessorFactory contentAccessorFactory) + { + _userCommunicationService = userCommunicationService; + _timelessContentProvider = timelessContentProvider; + _contentAccessorFactory = contentAccessorFactory; + } + + public CompressCommand GenerateCommand(IReadOnlyCollection sources, TransportMode mode, FullName targetFullName) + => new( + _userCommunicationService, + _timelessContentProvider, + _contentAccessorFactory, + sources, + mode, + targetFullName + ); +} \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/CompressUserCommand.cs b/src/Tools/FileTime.Tools.Compression/CompressUserCommand.cs new file mode 100644 index 0000000..c28eb66 --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression/CompressUserCommand.cs @@ -0,0 +1,15 @@ +using FileTime.App.Core.UserCommand; + +namespace FileTime.Tools.Compression; + +public class CompressUserCommand : IIdentifiableUserCommand +{ + public const string CommandName = "compress"; + public static readonly CompressUserCommand Instance = new(); + private CompressUserCommand() + { + } + + public string UserCommandID => CommandName; + public string Title => "Compress"; +} \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/CompressionType.cs b/src/Tools/FileTime.Tools.Compression/CompressionType.cs new file mode 100644 index 0000000..f6b9840 --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression/CompressionType.cs @@ -0,0 +1,10 @@ +namespace FileTime.Tools.Compression; + +public enum CompressionType +{ + Gzip, + Zip, + Tar, + TarBz2, + TarLz +} \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/CompressionUserCommandHandler.cs b/src/Tools/FileTime.Tools.Compression/CompressionUserCommandHandler.cs new file mode 100644 index 0000000..048e075 --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression/CompressionUserCommandHandler.cs @@ -0,0 +1,49 @@ +using System.Collections.ObjectModel; +using DeclarativeProperty; +using FileTime.App.Core.Services; +using FileTime.App.Core.Services.UserCommandHandler; +using FileTime.App.Core.ViewModels; +using FileTime.Core.Models; + +namespace FileTime.Tools.Compression; + +public class CompressionUserCommandHandler : AggregatedUserCommandHandler +{ + private readonly IClipboardService _clipboardService; + private readonly IDeclarativeProperty?> _markedItems; + private readonly IDeclarativeProperty _selectedItem; + + public CompressionUserCommandHandler( + IAppState appState, + IClipboardService clipboardService) + { + _clipboardService = clipboardService; + _markedItems = appState.SelectedTab.Map(t => t.MarkedItems).Switch(); + _selectedItem = appState.SelectedTab.Map(t => t.CurrentSelectedItem).Switch(); + + AddCommandHandler(new[] + { + new TypeUserCommandHandler(Compress) + }); + } + + private Task Compress() + { + _clipboardService.Clear(); + _clipboardService.SetCommand(); + + if (_markedItems.Value is {Count: > 0} markedItems) + { + foreach (var markedItem in markedItems) + { + _clipboardService.AddContent(markedItem); + } + } + else if (_selectedItem.Value?.BaseItem?.FullName is { } fullname) + { + _clipboardService.AddContent(fullname); + } + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Tools/FileTime.Tools.Compression/FileTime.Tools.Compression.csproj b/src/Tools/FileTime.Tools.Compression/FileTime.Tools.Compression.csproj new file mode 100644 index 0000000..4077082 --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression/FileTime.Tools.Compression.csproj @@ -0,0 +1,16 @@ + + + + net7.0 + enable + enable + + + + + + + + + + diff --git a/src/Tools/FileTime.Tools.Compression/Startup.cs b/src/Tools/FileTime.Tools.Compression/Startup.cs new file mode 100644 index 0000000..7f5cfb4 --- /dev/null +++ b/src/Tools/FileTime.Tools.Compression/Startup.cs @@ -0,0 +1,24 @@ +using FileTime.App.Core.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace FileTime.Tools.Compression; + +public class StartupHandler : IStartupHandler +{ + public StartupHandler(IIdentifiableUserCommandService identifiableUserCommandService) + { + identifiableUserCommandService.AddIdentifiableUserCommand(CompressUserCommand.Instance); + } + public Task InitAsync() => Task.CompletedTask; +} + +public static class Startup +{ + public static IServiceCollection AddCompression(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + return services; + } +} \ No newline at end of file diff --git a/src/Tools/FileTime.Tools/Extensions/ObjectExtensions.cs b/src/Tools/FileTime.Tools/Extensions/ObjectExtensions.cs index 81080be..e8ab126 100644 --- a/src/Tools/FileTime.Tools/Extensions/ObjectExtensions.cs +++ b/src/Tools/FileTime.Tools/Extensions/ObjectExtensions.cs @@ -2,6 +2,8 @@ namespace FileTime.Tools.Extensions; public static class ObjectExtensions { + public static TResult Map(this T obj, Func mapper) + => mapper(obj); public static TResult? MapNull(this T obj, Func nullHandler, Func valueHandler) => obj == null ? nullHandler() : valueHandler(obj);