Compare commits
63 Commits
feature/av
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a99a800ac | |||
| c3a7d56bd4 | |||
| 899363b2b0 | |||
| c8748644d2 | |||
| cef8e5de18 | |||
| 4952c68ce9 | |||
| 697339768f | |||
| 5734126c31 | |||
| 92f8e828b5 | |||
| 2ccd887d87 | |||
| 087bc906cf | |||
| 6eb02a1a08 | |||
| 382d158d7e | |||
| cd04e992f1 | |||
| 13a021c030 | |||
| 5a88911ca7 | |||
| 502f80313a | |||
| 83b86f2b50 | |||
| 5072d828e6 | |||
| 15dc956064 | |||
| ef5c3a9caf | |||
| f110a8442b | |||
| d1ceb95884 | |||
| 27a974e3e2 | |||
| f9a40d5a56 | |||
| 6f2a3d0dc4 | |||
| f809f0a640 | |||
| 89d891918c | |||
| 20d26340b0 | |||
| 45defb4021 | |||
| b11d009195 | |||
| 9661155cdd | |||
| 7019918496 | |||
| afb72ae491 | |||
| 48926e0f17 | |||
| ec03067af1 | |||
| 70180b9f12 | |||
| dda636fd9c | |||
| 552362c6b3 | |||
| 755793c85c | |||
| 2b5ab06b57 | |||
| b6d57869b1 | |||
| 7489e0eb1c | |||
| dfe44cfb16 | |||
| c061f658aa | |||
| bca940372a | |||
| f9c675eb7c | |||
| 2e43cca9fd | |||
| 96cba48f04 | |||
| 2e832f72d2 | |||
| 3f8309dc0f | |||
| 478d161b8a | |||
| 2ff1aa366e | |||
| 1319d0bb98 | |||
| 3da246b3ca | |||
| 1ea1012703 | |||
| ee0c6f7a0b | |||
| f40c84a123 | |||
| d795ddcd11 | |||
| 3e553dd448 | |||
| c4bf417ba3 | |||
| bcf0a32e0a | |||
| f92884d1f6 |
51
.vscode/launch.json
vendored
51
.vscode/launch.json
vendored
@@ -8,55 +8,28 @@
|
||||
"name": ".NET Core Launch (console)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
"preLaunchTask": "build cli",
|
||||
"program": "${workspaceFolder}/src/ConsoleApp/FileTime.ConsoleUI/bin/Debug/net6.0/FileTime.ConsoleUI.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/src/ConsoleApp/FileTime.ConsoleUI",
|
||||
"console": "internalConsole",
|
||||
"stopAtEntry": false
|
||||
},
|
||||
{
|
||||
"name": "GUI",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build gui",
|
||||
"program": "${workspaceFolder}/src/GuiApp/FileTime.Avalonia/bin/Debug/net6.0/FileTime.Avalonia.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/src/GuiApp/FileTime.Avalonia/bin/Debug/net6.0",
|
||||
"console": "internalConsole",
|
||||
"stopAtEntry": false
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach"
|
||||
},
|
||||
{
|
||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||
// Use hover for the description of the existing attributes
|
||||
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||
"name": "Skia.GTK (Debug)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build-skia-gtk",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/src/GuiApp/FileTime.Uno/FileTime.Uno.Skia.Gtk/bin/Debug/net6.0/FileTime.Uno.Skia.Gtk.dll",
|
||||
"args": [],
|
||||
"env": {
|
||||
"DOTNET_MODIFIABLE_ASSEMBLIES": "debug"
|
||||
},
|
||||
"cwd": "${workspaceFolder}/src/GuiApp/FileTime.Uno/FileTime.Uno.Skia.Gtk",
|
||||
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||
"console": "internalConsole",
|
||||
"stopAtEntry": false
|
||||
},
|
||||
{
|
||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||
// Use hover for the description of the existing attributes
|
||||
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||
"name": "Skia.Linux.FrameBuffer (Debug)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build-skia-gtk",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/src/GuiApp/FileTime.Uno/FileTime.Uno.Skia.Linux.FrameBuffer/bin/Debug/net6.0/FileTime.Uno.Skia.Linux.FrameBuffer.dll",
|
||||
"args": [],
|
||||
"env": {
|
||||
"DOTNET_MODIFIABLE_ASSEMBLIES": "debug"
|
||||
},
|
||||
"cwd": "${workspaceFolder}/src/GuiApp/FileTime.Uno/FileTime.Uno.Skia.Linux.FrameBuffer",
|
||||
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||
"console": "internalConsole",
|
||||
"stopAtEntry": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
70
.vscode/tasks.json
vendored
70
.vscode/tasks.json
vendored
@@ -2,7 +2,7 @@
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"label": "build cli",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
@@ -14,7 +14,19 @@
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish",
|
||||
"label": "build gui",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/src/GuiApp/FileTime.Avalonia/FileTime.Avalonia.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish cli",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
@@ -26,7 +38,7 @@
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish singlefile linux",
|
||||
"label": "publish singlefile linux cli",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
@@ -43,7 +55,7 @@
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish singlefile windows",
|
||||
"label": "publish singlefile windows cli",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
@@ -59,6 +71,18 @@
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish gui",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/src/GuiApp/FileTime.Avalonia/FileTime.Avalonia.csproj",
|
||||
"-c",
|
||||
"Release",
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"command": "dotnet",
|
||||
@@ -71,42 +95,6 @@
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "build-skia-gtk",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/src/GuiApp/FileTime.Uno/FileTime.Uno.Skia.Gtk/FileTime.Uno.Skia.Gtk.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish-skia-gtk",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/src//GuiApp/FileTime.Uno/FileTime.Uno.Skia.Gtk/FileTime.Uno.Skia.Gtk.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "build-skia-framebuffer",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/src/GuiApp/FileTime.Uno/FileTime.Uno.Skia.Linux.FrameBuffer/FileTime.Uno.Skia.Linux.FrameBuffer.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.App.Core.Clipboard
|
||||
{
|
||||
public class Clipboard : IClipboard
|
||||
{
|
||||
private readonly List<AbsolutePath> _content;
|
||||
public IReadOnlyList<AbsolutePath> Content { get; }
|
||||
private List<AbsolutePath> _content;
|
||||
public IReadOnlyList<AbsolutePath> Content { get; private set; }
|
||||
public Type? CommandType { get; private set; }
|
||||
|
||||
public Clipboard()
|
||||
{
|
||||
_content = new List<AbsolutePath>();
|
||||
Content = _content.AsReadOnly();
|
||||
ResetContent();
|
||||
}
|
||||
|
||||
public void AddContent(AbsolutePath absolutePath)
|
||||
{
|
||||
foreach (var content in _content)
|
||||
{
|
||||
if (content.IsEqual(absolutePath)) return;
|
||||
if (content.Equals(absolutePath)) return;
|
||||
}
|
||||
|
||||
_content.Add(new AbsolutePath(absolutePath));
|
||||
@@ -30,7 +28,7 @@ namespace FileTime.App.Core.Clipboard
|
||||
{
|
||||
for (var i = 0; i < _content.Count; i++)
|
||||
{
|
||||
if (_content[i].IsEqual(absolutePath))
|
||||
if (_content[i].Equals(absolutePath))
|
||||
{
|
||||
_content.RemoveAt(i--);
|
||||
}
|
||||
@@ -39,7 +37,7 @@ namespace FileTime.App.Core.Clipboard
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_content.Clear();
|
||||
ResetContent();
|
||||
CommandType = null;
|
||||
}
|
||||
|
||||
@@ -47,5 +45,11 @@ namespace FileTime.App.Core.Clipboard
|
||||
{
|
||||
CommandType = typeof(T);
|
||||
}
|
||||
|
||||
private void ResetContent()
|
||||
{
|
||||
_content = new List<AbsolutePath>();
|
||||
Content = _content.AsReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,34 +2,65 @@ namespace FileTime.App.Core.Command
|
||||
{
|
||||
public enum Commands
|
||||
{
|
||||
None,
|
||||
|
||||
AutoRefresh,
|
||||
ChangeTimelineMode,
|
||||
CloseTab,
|
||||
Compress,
|
||||
Copy,
|
||||
CopyHash,
|
||||
CopyPath,
|
||||
CreateContainer,
|
||||
CreateElement,
|
||||
Cut,
|
||||
Edit,
|
||||
EnterRapidTravel,
|
||||
FindByName,
|
||||
FindByNameRegex,
|
||||
GoToHome,
|
||||
GoToPath,
|
||||
GoToProvider,
|
||||
GoToRoot,
|
||||
GoUp,
|
||||
Delete,
|
||||
HardDelete,
|
||||
Mark,
|
||||
MoveCursorDown,
|
||||
MoveCursorDownPage,
|
||||
MoveCursorUp,
|
||||
MoveCursorUpPage,
|
||||
MoveToBottom,
|
||||
MoveToFirst,
|
||||
MoveToLast,
|
||||
MoveToTop,
|
||||
NextTimelineBlock,
|
||||
NextTimelineCommand,
|
||||
Open,
|
||||
OpenInFileBrowser,
|
||||
OpenOrRun,
|
||||
PasteMerge,
|
||||
PasteOverwrite,
|
||||
PasteSkip,
|
||||
Select,
|
||||
ToggleHidden,
|
||||
Rename,
|
||||
Dummy,
|
||||
PinFavorite,
|
||||
PreviousTimelineBlock,
|
||||
PreviousTimelineCommand,
|
||||
Refresh,
|
||||
Rename,
|
||||
RunCommand,
|
||||
ScanContainerSize,
|
||||
ShowAllShotcut,
|
||||
SoftDelete,
|
||||
SwitchToLastTab,
|
||||
SwitchToTab1,
|
||||
SwitchToTab2,
|
||||
SwitchToTab3,
|
||||
SwitchToTab4,
|
||||
SwitchToTab5,
|
||||
SwitchToTab6,
|
||||
SwitchToTab7,
|
||||
SwitchToTab8,
|
||||
TimelinePause,
|
||||
TimelineRefresh,
|
||||
TimelineStart,
|
||||
ToggleAdvancedIcons,
|
||||
ToggleHidden,
|
||||
}
|
||||
}
|
||||
9
src/AppCommon/FileTime.App.Core/Models/IHaveContainer.cs
Normal file
9
src/AppCommon/FileTime.App.Core/Models/IHaveContainer.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.App.Core.Models
|
||||
{
|
||||
public interface IHaveContainer
|
||||
{
|
||||
IContainer? Container { get; }
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ namespace FileTime.App.Core.Tab
|
||||
|
||||
foreach (var content in _markedItems[container])
|
||||
{
|
||||
if (content.IsEqual(path)) return;
|
||||
if (content.Equals(path)) return;
|
||||
}
|
||||
|
||||
var tabItem = new AbsolutePath(path);
|
||||
@@ -50,7 +50,7 @@ namespace FileTime.App.Core.Tab
|
||||
var markedItems = _markedItems[container];
|
||||
for (var i = 0; i < markedItems.Count; i++)
|
||||
{
|
||||
if (markedItems[i].IsEqual(path))
|
||||
if (markedItems[i].Equals(path))
|
||||
{
|
||||
await ItemUnmarked.InvokeAsync(this, markedItems[i]);
|
||||
markedItems.RemoveAt(i--);
|
||||
@@ -59,6 +59,24 @@ namespace FileTime.App.Core.Tab
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ClearMarkedItems(IContainer container)
|
||||
{
|
||||
if (_markedItems.ContainsKey(container))
|
||||
{
|
||||
var markedItems = _markedItems[container];
|
||||
for (var i = 0; i < markedItems.Count; i++)
|
||||
{
|
||||
await ItemUnmarked.InvokeAsync(this, markedItems[i]);
|
||||
markedItems.RemoveAt(i--);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ClearCurrentMarkedItems()
|
||||
{
|
||||
await ClearMarkedItems(await Tab.GetCurrentLocation());
|
||||
}
|
||||
|
||||
public bool ContainsMarkedItem(IContainer container, AbsolutePath path)
|
||||
{
|
||||
if (!_markedItems.ContainsKey(container)) return false;
|
||||
@@ -83,7 +101,7 @@ namespace FileTime.App.Core.Tab
|
||||
: new List<AbsolutePath>().AsReadOnly();
|
||||
}
|
||||
|
||||
public async Task MakrCurrentItem()
|
||||
public async Task MarkCurrentItem()
|
||||
{
|
||||
var currentLocation = await Tab!.GetCurrentLocation();
|
||||
if (currentLocation != null)
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
using FileTime.App.Core.Clipboard;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.CommandHandlers;
|
||||
using FileTime.Core.ContainerSizeScanner;
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Core.StateManagement;
|
||||
using FileTime.Core.Services;
|
||||
using FileTime.Core.Timeline;
|
||||
using FileTime.Providers.Favorites;
|
||||
using FileTime.Providers.Local;
|
||||
using FileTime.Providers.Sftp;
|
||||
using FileTime.Providers.Smb;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@@ -18,12 +22,31 @@ namespace FileTime.App.Core
|
||||
return serviceCollection
|
||||
.AddSingleton<IClipboard, Clipboard.Clipboard>()
|
||||
.AddSingleton<TopContainer>()
|
||||
.AddSingleton<LocalContentProvider>()
|
||||
.AddSingleton<IContentProvider, LocalContentProvider>(sp => sp.GetService<LocalContentProvider>() ?? throw new Exception($"No {nameof(LocalContentProvider)} instance found"))
|
||||
.AddSingleton<IContentProvider, SmbContentProvider>()
|
||||
.AddSingleton<ElementCreationStates>()
|
||||
.AddSingleton<CommandExecutor>()
|
||||
.AddSingleton<TimeRunner>();
|
||||
.AddSingleton<TimeRunner>()
|
||||
.AddSingleton<ContainerScanSnapshotProvider>()
|
||||
.AddSingleton<IContentProvider, ContainerScanSnapshotProvider>(p => p.GetService<ContainerScanSnapshotProvider>() ?? throw new ArgumentException(nameof(ContainerScanSnapshotProvider) + " is not registered"))
|
||||
.AddSingleton<ItemNameConverterService>()
|
||||
.AddLocalServices()
|
||||
.AddSmbServices()
|
||||
.AddSftpServices()
|
||||
.AddFavoriteServices()
|
||||
.RegisterCommandHandlers();
|
||||
}
|
||||
|
||||
internal static IServiceCollection RegisterCommandHandlers(this IServiceCollection serviceCollection)
|
||||
{
|
||||
var commandHandlers = new List<Type>()
|
||||
{
|
||||
typeof(StreamCopyCommandHandler)
|
||||
};
|
||||
|
||||
foreach (var commandHandler in commandHandlers)
|
||||
{
|
||||
serviceCollection.AddTransient(typeof(ICommandHandler), commandHandler);
|
||||
}
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,9 @@
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Providers\FileTime.Providers.Favorites\FileTime.Providers.Favorites.csproj" />
|
||||
<ProjectReference Include="..\..\Providers\FileTime.Providers.Sftp\FileTime.Providers.Sftp.csproj" />
|
||||
<ProjectReference Include="..\..\Tools\FileTime.Tools.Compression\FileTime.Tools.Compression.csproj" />
|
||||
<ProjectReference Include="..\FileTime.App.Core\FileTime.App.Core.csproj" />
|
||||
<ProjectReference Include="..\..\Providers\FileTime.Providers.Local\FileTime.Providers.Local.csproj" />
|
||||
<ProjectReference Include="..\..\Providers\FileTime.Providers.Smb\FileTime.Providers.Smb.csproj" />
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace FileTime.ConsoleUI.App
|
||||
{
|
||||
if (_selectedTab != null)
|
||||
{
|
||||
await _tabStates[_selectedTab].MakrCurrentItem();
|
||||
await _tabStates[_selectedTab].MarkCurrentItem();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,13 +148,13 @@ namespace FileTime.ConsoleUI.App
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(newContainerName))
|
||||
{
|
||||
await currentLocation.CreateContainer(newContainerName);
|
||||
await currentLocation.CreateContainerAsync(newContainerName);
|
||||
}
|
||||
}
|
||||
|
||||
async Task Validator(string newPath)
|
||||
{
|
||||
if (await currentLocation.IsExists(newPath))
|
||||
if (await currentLocation.IsExistsAsync(newPath))
|
||||
{
|
||||
_coloredConsoleRenderer.ForegroundColor = _styles.ErrorColor;
|
||||
}
|
||||
@@ -169,8 +169,16 @@ namespace FileTime.ConsoleUI.App
|
||||
{
|
||||
IList<AbsolutePath>? itemsToDelete = null;
|
||||
|
||||
var currentSelectedItems = (await _tabStates[_selectedTab!].GetCurrentMarkedItems()).Select(p => p.Resolve()).ToList();
|
||||
var currentSelectedItem = await _selectedTab?.GetCurrentSelectedItem();
|
||||
var currentSelectedItems = new List<IItem>();
|
||||
foreach (var item in await _tabStates[_selectedTab!].GetCurrentMarkedItems())
|
||||
{
|
||||
var resolvedItem = await item.ResolveAsync();
|
||||
if (resolvedItem != null) currentSelectedItems.Add(resolvedItem);
|
||||
}
|
||||
|
||||
IItem? currentSelectedItem = null;
|
||||
if (_selectedTab != null) currentSelectedItem = await _selectedTab.GetCurrentSelectedItem();
|
||||
|
||||
if (currentSelectedItems.Count > 0)
|
||||
{
|
||||
var delete = true;
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace FileTime.ConsoleUI.App
|
||||
new CommandBinding("open", Commands.Open, new[] { new ConsoleKeyInfo('→', ConsoleKey.RightArrow, false, false, false) }, Open),
|
||||
new CommandBinding(
|
||||
"go to top",
|
||||
Commands.MoveToTop,
|
||||
Commands.MoveToFirst,
|
||||
new[]
|
||||
{
|
||||
new ConsoleKeyInfo('g', ConsoleKey.G, false, false, false),
|
||||
@@ -99,7 +99,7 @@ namespace FileTime.ConsoleUI.App
|
||||
MoveCursorToTop),
|
||||
new CommandBinding(
|
||||
"go to bottom",
|
||||
Commands.MoveToBottom,
|
||||
Commands.MoveToLast,
|
||||
new[]
|
||||
{
|
||||
new ConsoleKeyInfo('G', ConsoleKey.G, true, false, false)
|
||||
@@ -114,7 +114,7 @@ namespace FileTime.ConsoleUI.App
|
||||
new ConsoleKeyInfo('h', ConsoleKey.H, false, false, false)
|
||||
},
|
||||
ToggleHidden),
|
||||
new CommandBinding("select", Commands.Select, new[] { new ConsoleKeyInfo(' ', ConsoleKey.Spacebar, false, false, false) }, Select),
|
||||
new CommandBinding("select", Commands.Mark, new[] { new ConsoleKeyInfo(' ', ConsoleKey.Spacebar, false, false, false) }, Select),
|
||||
new CommandBinding(
|
||||
"copy",
|
||||
Commands.Copy,
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace FileTime.ConsoleUI.App.UI
|
||||
foreach (var input in fields)
|
||||
{
|
||||
_application.MoveToIOLine();
|
||||
_coloredConsoleRenderer.Write(input.Text + ": ");
|
||||
_coloredConsoleRenderer.Write(input.Label + ": ");
|
||||
|
||||
results.Add(await _consoleReader.ReadText(placeHolder: input.InputType == InputType.Password ? '*' : null));
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace FileTime.ConsoleUI.App.UI
|
||||
if (paneState == null) throw new Exception($"{nameof(paneState)} can not be null");
|
||||
|
||||
Tab = pane;
|
||||
Tab.CurrentLocationChanged.Add((_, _) => { _currentDisplayStartY = 0; return Task.CompletedTask; });
|
||||
Tab.CurrentLocationChanged.Add((_, _, _) => { _currentDisplayStartY = 0; return Task.CompletedTask; });
|
||||
|
||||
TabState = paneState;
|
||||
}
|
||||
@@ -70,7 +70,7 @@ namespace FileTime.ConsoleUI.App.UI
|
||||
|
||||
if (currentLocation.GetParent() is var parentContainer && parentContainer is not null)
|
||||
{
|
||||
await parentContainer.Refresh();
|
||||
await parentContainer.RefreshAsync();
|
||||
|
||||
await PrintColumn(
|
||||
currentVirtualContainer != null
|
||||
@@ -96,7 +96,7 @@ namespace FileTime.ConsoleUI.App.UI
|
||||
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
await currentLocation.Refresh();
|
||||
await currentLocation.RefreshAsync();
|
||||
|
||||
await CheckAndSetCurrentDisplayStartY();
|
||||
await PrintColumn(
|
||||
@@ -112,7 +112,7 @@ namespace FileTime.ConsoleUI.App.UI
|
||||
|
||||
if (currentSelectedItem is IContainer selectedContainer)
|
||||
{
|
||||
await selectedContainer.Refresh();
|
||||
await selectedContainer.RefreshAsync();
|
||||
|
||||
selectedContainer = currentVirtualContainer != null
|
||||
? currentVirtualContainer.CloneVirtualChainFor(selectedContainer, v => v.IsTransitive)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<ProjectReference Include="..\..\AppCommon\FileTime.App.DependencyInjection\FileTime.App.DependencyInjection.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
|
||||
@@ -2,55 +2,68 @@
|
||||
{
|
||||
public class AsyncEventHandler<TSender, TArg>
|
||||
{
|
||||
private readonly List<Func<TSender, TArg, Task>> _handlers;
|
||||
private readonly Action<Func<TSender, TArg, Task>> _add;
|
||||
private readonly Action<Func<TSender, TArg, Task>> _remove;
|
||||
private readonly object _guard = new();
|
||||
private readonly List<Func<TSender, TArg, CancellationToken, Task>> _handlers;
|
||||
private readonly Action<Func<TSender, TArg, CancellationToken, Task>> _add;
|
||||
private readonly Action<Func<TSender, TArg, CancellationToken, Task>> _remove;
|
||||
|
||||
public IReadOnlyList<Func<TSender, TArg, Task>> Handlers { get; }
|
||||
public IReadOnlyList<Func<TSender, TArg, CancellationToken, Task>> Handlers { get; }
|
||||
|
||||
public AsyncEventHandler(Action<Func<TSender, TArg, Task>>? add = null, Action<Func<TSender, TArg, Task>>? remove = null)
|
||||
public AsyncEventHandler(Action<Func<TSender, TArg, CancellationToken, Task>>? add = null, Action<Func<TSender, TArg, CancellationToken, Task>>? remove = null)
|
||||
{
|
||||
_handlers = new List<Func<TSender, TArg, Task>>();
|
||||
_handlers = new List<Func<TSender, TArg, CancellationToken, Task>>();
|
||||
Handlers = _handlers.AsReadOnly();
|
||||
_add = add ?? AddInternal;
|
||||
_remove = remove ?? RemoveInternal;
|
||||
}
|
||||
|
||||
public void Add(Func<TSender, TArg, Task> handler)
|
||||
public void Add(Func<TSender, TArg, CancellationToken, Task> handler)
|
||||
{
|
||||
lock (_guard)
|
||||
{
|
||||
_add.Invoke(handler);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(Func<TSender, TArg, Task> handler)
|
||||
public void Remove(Func<TSender, TArg, CancellationToken, Task> handler)
|
||||
{
|
||||
lock (_guard)
|
||||
{
|
||||
_remove.Invoke(handler);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddInternal(Func<TSender, TArg, Task> handler)
|
||||
private void AddInternal(Func<TSender, TArg, CancellationToken, Task> handler)
|
||||
{
|
||||
_handlers.Add(handler);
|
||||
}
|
||||
|
||||
private void RemoveInternal(Func<TSender, TArg, Task> handler)
|
||||
private void RemoveInternal(Func<TSender, TArg, CancellationToken, Task> handler)
|
||||
{
|
||||
_handlers.Remove(handler);
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(TSender sender, TArg args)
|
||||
public async Task InvokeAsync(TSender sender, TArg args, CancellationToken token = default)
|
||||
{
|
||||
foreach(var handler in _handlers)
|
||||
List<Func<TSender, TArg, CancellationToken, Task>>? handlers;
|
||||
lock (_guard)
|
||||
{
|
||||
await handler(sender, args);
|
||||
handlers = new List<Func<TSender, TArg, CancellationToken, Task>>(_handlers);
|
||||
}
|
||||
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
await handler(sender, args, token);
|
||||
}
|
||||
}
|
||||
|
||||
public static AsyncEventHandler<TSender, TArg> operator +(AsyncEventHandler<TSender, TArg> obj, Func<TSender, TArg, Task> handler)
|
||||
public static AsyncEventHandler<TSender, TArg> operator +(AsyncEventHandler<TSender, TArg> obj, Func<TSender, TArg, CancellationToken, Task> handler)
|
||||
{
|
||||
obj.Add(handler);
|
||||
return obj;
|
||||
}
|
||||
|
||||
public static AsyncEventHandler<TSender, TArg> operator -(AsyncEventHandler<TSender, TArg> obj, Func<TSender, TArg, Task> handler)
|
||||
public static AsyncEventHandler<TSender, TArg> operator -(AsyncEventHandler<TSender, TArg> obj, Func<TSender, TArg, CancellationToken, Task> handler)
|
||||
{
|
||||
obj.Remove(handler);
|
||||
return obj;
|
||||
@@ -58,11 +71,11 @@
|
||||
}
|
||||
public class AsyncEventHandler<TArg> : AsyncEventHandler<object?, TArg>
|
||||
{
|
||||
public AsyncEventHandler(Action<Func<object?, TArg, Task>>? add = null, Action<Func<object?, TArg, Task>>? remove = null) : base(add, remove) { }
|
||||
public AsyncEventHandler(Action<Func<object?, TArg, CancellationToken, Task>>? add = null, Action<Func<object?, TArg, CancellationToken, Task>>? remove = null) : base(add, remove) { }
|
||||
}
|
||||
|
||||
public class AsyncEventHandler : AsyncEventHandler<AsyncEventArgs>
|
||||
{
|
||||
public AsyncEventHandler(Action<Func<object?, AsyncEventArgs, Task>>? add = null, Action<Func<object?, AsyncEventArgs, Task>>? remove = null) : base(add, remove) { }
|
||||
public AsyncEventHandler(Action<Func<object?, AsyncEventArgs, CancellationToken, Task>>? add = null, Action<Func<object?, AsyncEventArgs, CancellationToken, Task>>? remove = null) : base(add, remove) { }
|
||||
}
|
||||
}
|
||||
|
||||
9
src/Core/FileTime.Core/Command/CanHandleCommand.cs
Normal file
9
src/Core/FileTime.Core/Command/CanHandleCommand.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public enum CanHandleCommand
|
||||
{
|
||||
CanHandle,
|
||||
Fallback,
|
||||
CanNotHandle
|
||||
}
|
||||
}
|
||||
53
src/Core/FileTime.Core/Command/CommandBase.cs
Normal file
53
src/Core/FileTime.Core/Command/CommandBase.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public abstract class CommandBase : ICommand
|
||||
{
|
||||
private readonly List<string> _canRunMessages = new();
|
||||
public Dictionary<AbsolutePath, List<OperationProgress>> OperationStatuses { get; set; } = new();
|
||||
protected OperationProgress? CurrentOperationProgress { get; set; }
|
||||
public virtual string DisplayLabel { get; protected set; }
|
||||
public virtual IReadOnlyList<string> CanRunMessages { get; protected set; }
|
||||
public virtual int Progress { get; protected set; }
|
||||
public virtual int CurrentProgress { get; protected set; }
|
||||
public virtual AsyncEventHandler ProgressChanged { get; } = new AsyncEventHandler();
|
||||
|
||||
public abstract Task<CanCommandRun> CanRun(PointInTime startPoint);
|
||||
public abstract Task<PointInTime> SimulateCommand(PointInTime startPoint);
|
||||
|
||||
protected CommandBase()
|
||||
{
|
||||
CanRunMessages = _canRunMessages.AsReadOnly();
|
||||
DisplayLabel = "";
|
||||
}
|
||||
|
||||
public async Task UpdateProgress()
|
||||
{
|
||||
var total = 0L;
|
||||
var current = 0L;
|
||||
|
||||
foreach (var folder in OperationStatuses.Values)
|
||||
{
|
||||
foreach (var item in folder)
|
||||
{
|
||||
current += item.Progress;
|
||||
total += item.TotalCount;
|
||||
}
|
||||
}
|
||||
|
||||
Progress = total == 0 ? 0 : (int)(current * 100 / total);
|
||||
if (CurrentOperationProgress == null)
|
||||
{
|
||||
CurrentProgress = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentProgress = CurrentOperationProgress.TotalCount == 0 ? 0 : (int)(CurrentOperationProgress.Progress * 100 / CurrentOperationProgress.TotalCount);
|
||||
}
|
||||
await ProgressChanged.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,19 @@
|
||||
using FileTime.Core.Timeline;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class CommandExecutor
|
||||
{
|
||||
private readonly List<ICommandHandler> _commandHandlers;
|
||||
private readonly ILogger<CommandExecutor> _logger;
|
||||
|
||||
public CommandExecutor(IEnumerable<ICommandHandler> commandHandlers)
|
||||
public CommandExecutor(
|
||||
IEnumerable<ICommandHandler> commandHandlers,
|
||||
ILogger<CommandExecutor> logger)
|
||||
{
|
||||
_commandHandlers = commandHandlers.ToList();
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task ExecuteCommandAsync(ICommand command, TimeRunner timeRunner)
|
||||
@@ -19,7 +24,15 @@ namespace FileTime.Core.Command
|
||||
}
|
||||
else
|
||||
{
|
||||
await _commandHandlers.Find(c => c.CanHandle(command))?.ExecuteAsync(command, timeRunner);
|
||||
var commandHandler = _commandHandlers.Find(c => c.CanHandle(command));
|
||||
if (commandHandler != null)
|
||||
{
|
||||
await commandHandler.ExecuteAsync(command, timeRunner);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("No command handler for command {Command}", command.GetType().Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
45
src/Core/FileTime.Core/Command/Copy/CalculateOperation.cs
Normal file
45
src/Core/FileTime.Core/Command/Copy/CalculateOperation.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Command.Copy
|
||||
{
|
||||
public class CalculateOperation : ICopyOperation
|
||||
{
|
||||
private readonly Dictionary<AbsolutePath, List<OperationProgress>> _operationStatuses;
|
||||
public IReadOnlyDictionary<AbsolutePath, List<OperationProgress>> OperationStatuses { get; }
|
||||
|
||||
public CalculateOperation()
|
||||
{
|
||||
_operationStatuses = new Dictionary<AbsolutePath, List<OperationProgress>>();
|
||||
OperationStatuses = new ReadOnlyDictionary<AbsolutePath, List<OperationProgress>>(_operationStatuses);
|
||||
}
|
||||
|
||||
public Task ContainerCopyDoneAsync(AbsolutePath path)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task CopyAsync(AbsolutePath from, AbsolutePath to, OperationProgress? operation, CopyCommandContext context)
|
||||
{
|
||||
var parentPath = to.GetParent();
|
||||
List<OperationProgress> operationsByFolder;
|
||||
if (_operationStatuses.ContainsKey(parentPath))
|
||||
{
|
||||
operationsByFolder = _operationStatuses[parentPath];
|
||||
}
|
||||
else
|
||||
{
|
||||
operationsByFolder = new List<OperationProgress>();
|
||||
_operationStatuses.Add(parentPath, operationsByFolder);
|
||||
}
|
||||
|
||||
var resolvedFrom = await from.ResolveAsync();
|
||||
operationsByFolder.Add(new OperationProgress(from.Path, resolvedFrom is IElement element ? await element.GetElementSize() ?? 0L : 0L));
|
||||
}
|
||||
|
||||
public Task CreateContainerAsync(IContainer target, string name)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
119
src/Core/FileTime.Core/Command/Copy/CopyCommand.cs
Normal file
119
src/Core/FileTime.Core/Command/Copy/CopyCommand.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using FileTime.Core.Interactions;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command.Copy
|
||||
{
|
||||
public class CopyCommand : CommandBase, ITransportationCommand
|
||||
{
|
||||
public IList<AbsolutePath> Sources { get; } = new List<AbsolutePath>();
|
||||
|
||||
public AbsolutePath? Target { get; set; }
|
||||
|
||||
public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge;
|
||||
internal TimeRunner? TimeRunner { get; private set; }
|
||||
|
||||
public bool TargetIsContainer => true;
|
||||
public List<InputElement> Inputs { get; } = new();
|
||||
public List<object>? InputResults { get; set; }
|
||||
|
||||
public CopyCommand()
|
||||
{
|
||||
DisplayLabel = "Copy";
|
||||
}
|
||||
|
||||
public override async Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
if (Sources == null) throw new ArgumentException(nameof(Sources) + " can not be null");
|
||||
if (Target == null) throw new ArgumentException(nameof(Target) + " can not be null");
|
||||
if (TransportMode == null) throw new ArgumentException(nameof(TransportMode) + " can not be null");
|
||||
|
||||
var simulateOperation = new SimulateOperation();
|
||||
|
||||
await TraverseTree(Sources, Target, TransportMode.Value, simulateOperation);
|
||||
|
||||
return startPoint.WithDifferences(simulateOperation.NewDiffs);
|
||||
}
|
||||
|
||||
public async Task Execute(Func<AbsolutePath, AbsolutePath, OperationProgress?, CopyCommandContext, Task> copy, TimeRunner timeRunner)
|
||||
{
|
||||
if (Sources == null) throw new ArgumentException(nameof(Sources) + " can not be null");
|
||||
if (Target == null) throw new ArgumentException(nameof(Target) + " can not be null");
|
||||
if (TransportMode == null) throw new ArgumentException(nameof(TransportMode) + " can not be null");
|
||||
|
||||
TimeRunner = timeRunner;
|
||||
|
||||
await CalculateProgress();
|
||||
|
||||
var copyOperation = new CopyOperation(copy, this);
|
||||
|
||||
await TraverseTree(Sources, Target, TransportMode.Value, copyOperation);
|
||||
await TimeRunner.RefreshContainer.InvokeAsync(this, Target);
|
||||
}
|
||||
|
||||
private async Task CalculateProgress()
|
||||
{
|
||||
if (Sources == null) throw new ArgumentException(nameof(Sources) + " can not be null");
|
||||
if (Target == null) throw new ArgumentException(nameof(Target) + " can not be null");
|
||||
if (TransportMode == null) throw new ArgumentException(nameof(TransportMode) + " can not be null");
|
||||
|
||||
var calculateOperation = new CalculateOperation();
|
||||
await TraverseTree(Sources, Target, TransportMode.Value, calculateOperation);
|
||||
OperationStatuses = new Dictionary<AbsolutePath, List<OperationProgress>>(calculateOperation.OperationStatuses);
|
||||
}
|
||||
|
||||
private async Task TraverseTree(
|
||||
IEnumerable<AbsolutePath> sources,
|
||||
AbsolutePath target,
|
||||
TransportMode transportMode,
|
||||
ICopyOperation copyOperation)
|
||||
{
|
||||
var resolvedTarget = (IContainer?)await target.ResolveAsync();
|
||||
|
||||
foreach (var source in sources)
|
||||
{
|
||||
var item = await source.ResolveAsync();
|
||||
|
||||
if (item is IContainer container)
|
||||
{
|
||||
var targetContainer = target.GetChild(item.Name, AbsolutePathType.Container);
|
||||
if (resolvedTarget != null)
|
||||
{
|
||||
await resolvedTarget.RefreshAsync();
|
||||
|
||||
if (!await resolvedTarget.IsExistsAsync(item.Name))
|
||||
{
|
||||
await copyOperation.CreateContainerAsync(resolvedTarget, container.Name);
|
||||
}
|
||||
}
|
||||
|
||||
await TraverseTree((await container.GetItems())!.Select(i => new AbsolutePath(i)), targetContainer, transportMode, copyOperation);
|
||||
await copyOperation.ContainerCopyDoneAsync(new AbsolutePath(container));
|
||||
}
|
||||
else if (item is IElement element)
|
||||
{
|
||||
var newElementName = await Helper.CommandHelper.GetNewNameAsync(resolvedTarget, element.Name, transportMode);
|
||||
if (newElementName == null) continue;
|
||||
|
||||
OperationProgress? operation = null;
|
||||
var newElementPath = target.GetChild(newElementName, AbsolutePathType.Element);
|
||||
|
||||
if (OperationStatuses.TryGetValue(target, out var targetPathOperations))
|
||||
{
|
||||
var path = new AbsolutePath(element).Path;
|
||||
operation = targetPathOperations.Find(o => o.Key == path);
|
||||
}
|
||||
CurrentOperationProgress = operation;
|
||||
|
||||
await copyOperation.CopyAsync(new AbsolutePath(element), newElementPath, operation, new CopyCommandContext(UpdateProgress));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
//TODO: implement
|
||||
return Task.FromResult(CanCommandRun.True);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/Core/FileTime.Core/Command/Copy/CopyCommandContext.cs
Normal file
14
src/Core/FileTime.Core/Command/Copy/CopyCommandContext.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace FileTime.Core.Command.Copy
|
||||
{
|
||||
public class CopyCommandContext
|
||||
{
|
||||
private readonly Func<Task> _updateProgress;
|
||||
|
||||
public CopyCommandContext(Func<Task> updateProgress)
|
||||
{
|
||||
_updateProgress = updateProgress;
|
||||
}
|
||||
|
||||
public async Task UpdateProgress() => await _updateProgress.Invoke();
|
||||
}
|
||||
}
|
||||
44
src/Core/FileTime.Core/Command/Copy/CopyOperation.cs
Normal file
44
src/Core/FileTime.Core/Command/Copy/CopyOperation.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Command.Copy
|
||||
{
|
||||
public class CopyOperation : ICopyOperation
|
||||
{
|
||||
private readonly Func<AbsolutePath, AbsolutePath, OperationProgress?, CopyCommandContext, Task> _copy;
|
||||
private readonly CopyCommand _copyCommand;
|
||||
|
||||
public CopyOperation(Func<AbsolutePath, AbsolutePath, OperationProgress?, CopyCommandContext, Task> copy, CopyCommand copyCommand)
|
||||
{
|
||||
_copy = copy;
|
||||
_copyCommand = copyCommand;
|
||||
}
|
||||
|
||||
public async Task CopyAsync(AbsolutePath from, AbsolutePath to, OperationProgress? operation, CopyCommandContext context)
|
||||
{
|
||||
await _copy(from, to, operation, context);
|
||||
if (operation != null)
|
||||
{
|
||||
operation.Progress = operation.TotalCount;
|
||||
}
|
||||
await _copyCommand.UpdateProgress();
|
||||
}
|
||||
|
||||
public async Task CreateContainerAsync(IContainer target, string name) => await target.CreateContainerAsync(name);
|
||||
|
||||
public async Task ContainerCopyDoneAsync(AbsolutePath path)
|
||||
{
|
||||
if (_copyCommand.OperationStatuses.ContainsKey(path))
|
||||
{
|
||||
foreach (var item in _copyCommand.OperationStatuses[path])
|
||||
{
|
||||
item.Progress = item.TotalCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (_copyCommand.TimeRunner != null)
|
||||
{
|
||||
await _copyCommand.TimeRunner.RefreshContainer.InvokeAsync(this, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/Core/FileTime.Core/Command/Copy/ICopyOperation.cs
Normal file
11
src/Core/FileTime.Core/Command/Copy/ICopyOperation.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Command.Copy
|
||||
{
|
||||
public interface ICopyOperation
|
||||
{
|
||||
Task ContainerCopyDoneAsync(AbsolutePath path);
|
||||
Task CopyAsync(AbsolutePath from, AbsolutePath to, OperationProgress? operation, CopyCommandContext context);
|
||||
Task CreateContainerAsync(IContainer target, string name);
|
||||
}
|
||||
}
|
||||
39
src/Core/FileTime.Core/Command/Copy/SimulateOperation.cs
Normal file
39
src/Core/FileTime.Core/Command/Copy/SimulateOperation.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command.Copy
|
||||
{
|
||||
public class SimulateOperation : ICopyOperation
|
||||
{
|
||||
private readonly List<Difference> _newDiffs;
|
||||
public IReadOnlyList<Difference> NewDiffs { get; }
|
||||
|
||||
public SimulateOperation()
|
||||
{
|
||||
_newDiffs = new List<Difference>();
|
||||
NewDiffs = _newDiffs.AsReadOnly();
|
||||
}
|
||||
public Task ContainerCopyDoneAsync(AbsolutePath path)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task CopyAsync(AbsolutePath from, AbsolutePath to, OperationProgress? operation, CopyCommandContext context)
|
||||
{
|
||||
_newDiffs.Add(new Difference(DifferenceActionType.Create, to));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task CreateContainerAsync(IContainer target, string name)
|
||||
{
|
||||
var newContainerDiff = new Difference(
|
||||
DifferenceActionType.Create,
|
||||
AbsolutePath.FromParentAndChildName(target, name, AbsolutePathType.Container)
|
||||
);
|
||||
|
||||
_newDiffs.Add(newContainerDiff);
|
||||
|
||||
return Task.FromResult((IContainer)null!);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class CopyCommand : ITransportationCommand
|
||||
{
|
||||
private Action<AbsolutePath, AbsolutePath>? _copyOperation;
|
||||
private Func<IContainer, string, Task<IContainer>>? _createContainer;
|
||||
private TimeRunner? _timeRunner;
|
||||
|
||||
public IList<AbsolutePath> Sources { get; } = new List<AbsolutePath>();
|
||||
|
||||
public IContainer? Target { get; set; }
|
||||
|
||||
public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge;
|
||||
|
||||
public async Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
if (Sources == null) throw new ArgumentException(nameof(Sources) + " can not be null");
|
||||
if (Target == null) throw new ArgumentException(nameof(Target) + " can not be null");
|
||||
if (TransportMode == null) throw new ArgumentException(nameof(TransportMode) + " can not be null");
|
||||
|
||||
var newDiffs = new List<Difference>();
|
||||
|
||||
_copyOperation = (_, to) =>
|
||||
{
|
||||
var target = to.GetParentAsAbsolutePath().Resolve();
|
||||
newDiffs.Add(new Difference(
|
||||
target is IElement
|
||||
? DifferenceItemType.Element
|
||||
: DifferenceItemType.Container,
|
||||
DifferenceActionType.Create,
|
||||
to
|
||||
));
|
||||
};
|
||||
|
||||
_createContainer = async (IContainer target, string name) =>
|
||||
{
|
||||
var newContainerDiff = new Difference(
|
||||
DifferenceItemType.Container,
|
||||
DifferenceActionType.Create,
|
||||
AbsolutePath.FromParentAndChildName(target, name)
|
||||
);
|
||||
|
||||
newDiffs.Add(newContainerDiff);
|
||||
|
||||
return (IContainer)(await newContainerDiff.AbsolutePath.Resolve())!;
|
||||
};
|
||||
|
||||
await DoCopy(Sources, Target, TransportMode.Value);
|
||||
|
||||
return startPoint.WithDifferences(newDiffs);
|
||||
}
|
||||
|
||||
public async Task Execute(Action<AbsolutePath, AbsolutePath> copy, TimeRunner timeRunner)
|
||||
{
|
||||
if (Sources == null) throw new ArgumentException(nameof(Sources) + " can not be null");
|
||||
if (Target == null) throw new ArgumentException(nameof(Target) + " can not be null");
|
||||
if (TransportMode == null) throw new ArgumentException(nameof(TransportMode) + " can not be null");
|
||||
|
||||
_copyOperation = copy;
|
||||
_createContainer = async (IContainer target, string name) => await target.CreateContainer(name);
|
||||
_timeRunner = timeRunner;
|
||||
|
||||
await DoCopy(Sources, Target, TransportMode.Value);
|
||||
}
|
||||
|
||||
private async Task DoCopy(
|
||||
IEnumerable<AbsolutePath> sources,
|
||||
IContainer target,
|
||||
TransportMode transportMode)
|
||||
{
|
||||
if (_copyOperation == null) throw new ArgumentException("No copy operation were given.");
|
||||
if (_createContainer == null) throw new ArgumentException("No container creation function were given.");
|
||||
|
||||
foreach (var source in sources)
|
||||
{
|
||||
var item = await source.Resolve();
|
||||
|
||||
if (item is IContainer container)
|
||||
{
|
||||
var targetContainer = (await target.GetContainers())?.FirstOrDefault(d => d.Name == container.Name) ?? (await _createContainer?.Invoke(target, container.Name)!);
|
||||
|
||||
var childDirectories = (await container.GetContainers())!.Select(d => new AbsolutePath(d));
|
||||
var childFiles = (await container.GetElements())!.Select(f => new AbsolutePath(f));
|
||||
|
||||
await DoCopy(childDirectories.Concat(childFiles), targetContainer, transportMode);
|
||||
_timeRunner?.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
|
||||
}
|
||||
else if (item is IElement element)
|
||||
{
|
||||
var targetName = element.Name;
|
||||
|
||||
var targetNameExists = await target.IsExists(targetName);
|
||||
if (transportMode == Command.TransportMode.Merge)
|
||||
{
|
||||
for (var i = 0; targetNameExists; i++)
|
||||
{
|
||||
targetName = element.Name + (i == 0 ? "_" : $"_{i}");
|
||||
}
|
||||
}
|
||||
else if (transportMode == Command.TransportMode.Skip && targetNameExists)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_copyOperation?.Invoke(new AbsolutePath(element), AbsolutePath.FromParentAndChildName(target, targetName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
//TODO: implement
|
||||
return Task.FromResult(CanCommandRun.True);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,34 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
namespace FileTime.Core.Command.CreateContainer
|
||||
{
|
||||
public class CreateContainerCommand : IExecutableCommand
|
||||
{
|
||||
public AbsolutePath Container { get; }
|
||||
public string NewContainerName { get; }
|
||||
|
||||
public int Progress => 100;
|
||||
public int CurrentProgress => 100;
|
||||
|
||||
public AsyncEventHandler ProgressChanged { get; } = new();
|
||||
public string DisplayLabel { get; }
|
||||
public IReadOnlyList<string> CanRunMessages { get; } = new List<string>().AsReadOnly();
|
||||
|
||||
public CreateContainerCommand(AbsolutePath container, string newContainerName)
|
||||
{
|
||||
Container = container;
|
||||
NewContainerName = newContainerName;
|
||||
DisplayLabel = $"Create container {newContainerName}";
|
||||
}
|
||||
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
var possibleContainer = await Container.Resolve();
|
||||
var possibleContainer = await Container.ResolveAsync();
|
||||
if (possibleContainer is IContainer container)
|
||||
{
|
||||
await container.CreateContainer(NewContainerName);
|
||||
await container.CreateContainerAsync(NewContainerName);
|
||||
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
|
||||
}
|
||||
//TODO: else
|
||||
@@ -29,18 +38,18 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
var newDifferences = new List<Difference>()
|
||||
{
|
||||
new Difference(DifferenceItemType.Container, DifferenceActionType.Create, new AbsolutePath(Container.ContentProvider, Container.Path + Constants.SeparatorChar + NewContainerName, Container.VirtualContentProvider))
|
||||
new Difference(DifferenceActionType.Create, Container.GetChild(NewContainerName, AbsolutePathType.Container))
|
||||
};
|
||||
return Task.FromResult(startPoint.WithDifferences(newDifferences));
|
||||
}
|
||||
|
||||
public async Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
var resolvedContainer = await Container.Resolve();
|
||||
var resolvedContainer = await Container.ResolveAsync();
|
||||
if (resolvedContainer == null) return CanCommandRun.Forceable;
|
||||
|
||||
if (resolvedContainer is not IContainer container
|
||||
|| await container.IsExists(NewContainerName))
|
||||
|| await container.IsExistsAsync(NewContainerName))
|
||||
{
|
||||
return CanCommandRun.False;
|
||||
}
|
||||
@@ -1,25 +1,33 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
namespace FileTime.Core.Command.CreateElement
|
||||
{
|
||||
public class CreateElementCommand : IExecutableCommand
|
||||
{
|
||||
public AbsolutePath Container { get; }
|
||||
public string NewElementName { get; }
|
||||
|
||||
public int Progress => 100;
|
||||
public int CurrentProgress => 100;
|
||||
public AsyncEventHandler ProgressChanged { get; } = new();
|
||||
public string DisplayLabel { get; }
|
||||
public IReadOnlyList<string> CanRunMessages { get; } = new List<string>().AsReadOnly();
|
||||
|
||||
public CreateElementCommand(AbsolutePath container, string newElementName)
|
||||
{
|
||||
Container = container;
|
||||
NewElementName = newElementName;
|
||||
DisplayLabel = $"Create element {newElementName}";
|
||||
}
|
||||
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
var possibleContainer = await Container.Resolve();
|
||||
var possibleContainer = await Container.ResolveAsync();
|
||||
if (possibleContainer is IContainer container)
|
||||
{
|
||||
await container.CreateElement(NewElementName);
|
||||
await container.CreateElementAsync(NewElementName);
|
||||
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
|
||||
}
|
||||
}
|
||||
@@ -28,18 +36,18 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
var newDifferences = new List<Difference>()
|
||||
{
|
||||
new Difference(DifferenceItemType.Element, DifferenceActionType.Create, new AbsolutePath(Container.ContentProvider, Container.Path + Constants.SeparatorChar + NewElementName, Container.VirtualContentProvider))
|
||||
new Difference(DifferenceActionType.Create, Container.GetChild(NewElementName, AbsolutePathType.Element))
|
||||
};
|
||||
return Task.FromResult(startPoint.WithDifferences(newDifferences));
|
||||
}
|
||||
|
||||
public async Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
var resolvedContainer = Container.Resolve();
|
||||
var resolvedContainer = await Container.ResolveAsync();
|
||||
if (resolvedContainer == null) return CanCommandRun.Forceable;
|
||||
|
||||
if (resolvedContainer is not IContainer container
|
||||
|| await container.IsExists(NewElementName))
|
||||
|| await container.IsExistsAsync(NewElementName))
|
||||
{
|
||||
return CanCommandRun.False;
|
||||
}
|
||||
122
src/Core/FileTime.Core/Command/Delete/DeleteCommand.cs
Normal file
122
src/Core/FileTime.Core/Command/Delete/DeleteCommand.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using System.Reflection.Metadata;
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command.Delete
|
||||
{
|
||||
public class DeleteCommand : IExecutableCommand
|
||||
{
|
||||
private Func<IContainer, Task>? _deleteContainer;
|
||||
private Func<IElement, Task>? _deleteElement;
|
||||
|
||||
public int Progress => 100;
|
||||
public int CurrentProgress => 100;
|
||||
|
||||
public AsyncEventHandler ProgressChanged { get; } = new();
|
||||
|
||||
public IList<AbsolutePath> ItemsToDelete { get; } = new List<AbsolutePath>();
|
||||
public string DisplayLabel { get; } = "DeleteCommand";
|
||||
|
||||
public bool HardDelete { get; set; }
|
||||
public IReadOnlyList<string> CanRunMessages { get; } = new List<string>().AsReadOnly();
|
||||
|
||||
public async Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
var newDifferences = new List<Difference>();
|
||||
|
||||
_deleteContainer = (c) =>
|
||||
{
|
||||
newDifferences.Add(new Difference(
|
||||
DifferenceActionType.Delete,
|
||||
new AbsolutePath(c)
|
||||
));
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
_deleteElement = (e) =>
|
||||
{
|
||||
newDifferences.Add(new Difference(
|
||||
DifferenceActionType.Delete,
|
||||
new AbsolutePath(e)
|
||||
));
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
foreach (var item in ItemsToDelete)
|
||||
{
|
||||
await TraverseTree((await item.ResolveAsync())!);
|
||||
}
|
||||
|
||||
return startPoint.WithDifferences(newDifferences);
|
||||
}
|
||||
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
_deleteContainer = async (c) =>
|
||||
{
|
||||
await c.Delete(HardDelete);
|
||||
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(c));
|
||||
};
|
||||
_deleteElement = async (e) => await e.Delete(HardDelete);
|
||||
|
||||
foreach (var item in ItemsToDelete)
|
||||
{
|
||||
await TraverseTree((await item.ResolveAsync())!);
|
||||
}
|
||||
|
||||
foreach (var updatedParent in ItemsToDelete.Select(i => i.GetParent()).Distinct())
|
||||
{
|
||||
await timeRunner.RefreshContainer.InvokeAsync(this, updatedParent);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TraverseTree(IItem item)
|
||||
{
|
||||
if (item is IContainer container)
|
||||
{
|
||||
if (!HardDelete && container.SupportsDirectoryLevelSoftDelete)
|
||||
{
|
||||
if (_deleteContainer != null) await _deleteContainer.Invoke(container);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (container.AllowRecursiveDeletion)
|
||||
{
|
||||
foreach (var child in (await container.GetItems())!)
|
||||
{
|
||||
await TraverseTree(child);
|
||||
}
|
||||
}
|
||||
|
||||
if (_deleteContainer != null) await _deleteContainer.Invoke(container);
|
||||
}
|
||||
}
|
||||
else if (item is IElement element)
|
||||
{
|
||||
if (_deleteElement != null) await _deleteElement.Invoke(element);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
var result = CanCommandRun.True;
|
||||
foreach (var itemPath in ItemsToDelete)
|
||||
{
|
||||
var resolvedItem = await itemPath.ResolveAsync();
|
||||
if (resolvedItem != null
|
||||
&& (
|
||||
resolvedItem.CanDelete == SupportsDelete.False
|
||||
|| (resolvedItem.CanDelete == SupportsDelete.HardDeleteOnly && !HardDelete)
|
||||
)
|
||||
)
|
||||
{
|
||||
result = CanCommandRun.Forceable;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
using FileTime.Core.Extensions;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class DeleteCommand : IExecutableCommand
|
||||
{
|
||||
public IList<AbsolutePath> ItemsToDelete { get; } = new List<AbsolutePath>();
|
||||
|
||||
public async Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
var newDifferences = new List<Difference>();
|
||||
|
||||
foreach (var itemToDelete in ItemsToDelete)
|
||||
{
|
||||
var item = await itemToDelete.Resolve();
|
||||
newDifferences.Add(new Difference(
|
||||
item.ToDifferenceItemType(),
|
||||
DifferenceActionType.Delete,
|
||||
itemToDelete
|
||||
));
|
||||
}
|
||||
return startPoint.WithDifferences(newDifferences);
|
||||
}
|
||||
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
foreach (var item in ItemsToDelete)
|
||||
{
|
||||
await DoDelete((await item.Resolve())!, timeRunner);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DoDelete(IItem item, TimeRunner timeRunner)
|
||||
{
|
||||
if (item is IContainer container)
|
||||
{
|
||||
foreach (var child in (await container.GetItems())!)
|
||||
{
|
||||
await DoDelete(child, timeRunner);
|
||||
await child.Delete();
|
||||
}
|
||||
|
||||
await item.Delete();
|
||||
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
|
||||
}
|
||||
else if (item is IElement element)
|
||||
{
|
||||
await element.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
var result = CanCommandRun.True;
|
||||
foreach (var itemPath in ItemsToDelete)
|
||||
{
|
||||
var resolvedItem = await itemPath.Resolve();
|
||||
if (!(resolvedItem?.CanDelete ?? true))
|
||||
{
|
||||
result = CanCommandRun.Forceable;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public interface ICommand
|
||||
{
|
||||
string DisplayLabel { get; }
|
||||
IReadOnlyList<string> CanRunMessages { get; }
|
||||
Task<CanCommandRun> CanRun(PointInTime startPoint);
|
||||
Task<PointInTime> SimulateCommand(PointInTime startPoint);
|
||||
int Progress { get; }
|
||||
int CurrentProgress { get; }
|
||||
AsyncEventHandler ProgressChanged { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using FileTime.Core.Interactions;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
@@ -5,7 +6,10 @@ namespace FileTime.Core.Command
|
||||
public interface ITransportationCommand : ICommand
|
||||
{
|
||||
IList<AbsolutePath> Sources { get; }
|
||||
IContainer? Target { get; set;}
|
||||
AbsolutePath? Target { get; set; }
|
||||
TransportMode? TransportMode { get; set; }
|
||||
bool TargetIsContainer { get; }
|
||||
List<InputElement> Inputs { get; }
|
||||
List<object>? InputResults { get; set; }
|
||||
}
|
||||
}
|
||||
35
src/Core/FileTime.Core/Command/Move/MoveCommand.cs
Normal file
35
src/Core/FileTime.Core/Command/Move/MoveCommand.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Interactions;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command.Move
|
||||
{
|
||||
public class MoveCommand : ITransportationCommand
|
||||
{
|
||||
public IList<AbsolutePath> Sources { get; } = new List<AbsolutePath>();
|
||||
|
||||
public AbsolutePath? Target { get; set; }
|
||||
public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge;
|
||||
|
||||
public int Progress => 100;
|
||||
public int CurrentProgress => 100;
|
||||
public AsyncEventHandler ProgressChanged { get; } = new();
|
||||
public string DisplayLabel { get; } = "MoveCommand";
|
||||
public IReadOnlyList<string> CanRunMessages { get; } = new List<string>().AsReadOnly();
|
||||
public bool TargetIsContainer => true;
|
||||
|
||||
public List<InputElement> Inputs { get; } = new();
|
||||
public List<object>? InputResults { get; set; }
|
||||
|
||||
public Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class MoveCommand : ITransportationCommand
|
||||
{
|
||||
public IList<AbsolutePath> Sources { get; } = new List<AbsolutePath>();
|
||||
|
||||
public IContainer? Target { get; set; }
|
||||
public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge;
|
||||
|
||||
public Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/Core/FileTime.Core/Command/OperationProgress.cs
Normal file
16
src/Core/FileTime.Core/Command/OperationProgress.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class OperationProgress
|
||||
{
|
||||
public string Key { get; }
|
||||
public long Progress { get; set; }
|
||||
public long TotalCount { get; }
|
||||
public bool IsDone => Progress == TotalCount;
|
||||
|
||||
public OperationProgress(string key, long totalCount)
|
||||
{
|
||||
Key = key;
|
||||
TotalCount = totalCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/Core/FileTime.Core/Command/Rename/RenameCommand.cs
Normal file
53
src/Core/FileTime.Core/Command/Rename/RenameCommand.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command.Rename
|
||||
{
|
||||
public class RenameCommand : IExecutableCommand
|
||||
{
|
||||
public AbsolutePath Source { get; }
|
||||
public string Target { get; }
|
||||
|
||||
public int Progress => 100;
|
||||
public int CurrentProgress => 100;
|
||||
public AsyncEventHandler ProgressChanged { get; } = new();
|
||||
public string DisplayLabel { get; } = "RenameCommand";
|
||||
public IReadOnlyList<string> CanRunMessages { get; } = new List<string>().AsReadOnly();
|
||||
|
||||
public RenameCommand(AbsolutePath source, string target)
|
||||
{
|
||||
Source = source;
|
||||
Target = target;
|
||||
}
|
||||
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
var itemToRename = await Source.ResolveAsync();
|
||||
if (itemToRename != null)
|
||||
{
|
||||
await itemToRename.Rename(Target);
|
||||
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(itemToRename.GetParent()!));
|
||||
}
|
||||
}
|
||||
|
||||
public Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
var newDifferences = new List<Difference>()
|
||||
{
|
||||
new Difference(
|
||||
DifferenceActionType.Delete,
|
||||
Source),
|
||||
new Difference(
|
||||
DifferenceActionType.Create,
|
||||
Source.GetParent().GetChild(Target, Source.Type))
|
||||
};
|
||||
return Task.FromResult(startPoint.WithDifferences(newDifferences));
|
||||
}
|
||||
|
||||
public async Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
return await Source.ResolveAsync() != null ? CanCommandRun.True : CanCommandRun.False;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class RenameCommand : IExecutableCommand
|
||||
{
|
||||
public AbsolutePath Source { get; }
|
||||
public string Target { get; }
|
||||
|
||||
public RenameCommand(AbsolutePath source, string target)
|
||||
{
|
||||
Source = source;
|
||||
Target = target;
|
||||
}
|
||||
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
var itemToRename = await Source.Resolve();
|
||||
if (itemToRename != null)
|
||||
{
|
||||
await itemToRename.Rename(Target);
|
||||
timeRunner.RefreshContainer?.InvokeAsync(this, new AbsolutePath(itemToRename.GetParent()!));
|
||||
}
|
||||
}
|
||||
|
||||
public Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Command.Copy;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.CommandHandlers
|
||||
{
|
||||
public class StreamCopyCommandHandler : ICommandHandler
|
||||
{
|
||||
public bool CanHandle(object command)
|
||||
{
|
||||
if (command is not CopyCommand copyCommand) return false;
|
||||
|
||||
return (copyCommand.Target?.ContentProvider.SupportsContentStreams ?? false)
|
||||
&& copyCommand.Sources.All(p => p.ContentProvider.SupportsContentStreams);
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(object command, TimeRunner timeRunner)
|
||||
{
|
||||
if (command is not CopyCommand copyCommand) throw new ArgumentException($"Can not execute command of type '{command.GetType()}'.");
|
||||
|
||||
await copyCommand.Execute(CopyElement, timeRunner);
|
||||
}
|
||||
|
||||
public static async Task CopyElement(AbsolutePath sourcePath, AbsolutePath targetPath, OperationProgress? operationProgress, CopyCommandContext copyCommandContext)
|
||||
{
|
||||
var parent = (IContainer?)(await targetPath.GetParent().ResolveAsync())!;
|
||||
var elementName = targetPath.GetName();
|
||||
if (!await parent.IsExistsAsync(elementName))
|
||||
{
|
||||
await parent.CreateElementAsync(elementName);
|
||||
}
|
||||
|
||||
var source = (IElement?)(await sourcePath.ResolveAsync())!;
|
||||
var target = (IElement?)(await targetPath.ResolveAsync())!;
|
||||
|
||||
using var reader = await source.GetContentReaderAsync();
|
||||
using var writer = await target.GetContentWriterAsync();
|
||||
|
||||
byte[] dataRead;
|
||||
|
||||
do
|
||||
{
|
||||
dataRead = await reader.ReadBytesAsync(writer.PreferredBufferSize);
|
||||
if (dataRead.Length > 0)
|
||||
{
|
||||
await writer.WriteBytesAsync(dataRead);
|
||||
await writer.FlushAsync();
|
||||
if (operationProgress != null) operationProgress.Progress += dataRead.LongLength;
|
||||
await copyCommandContext.UpdateProgress();
|
||||
}
|
||||
}
|
||||
while (dataRead.Length > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,46 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Helper;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Search;
|
||||
|
||||
namespace FileTime.Core.Components
|
||||
{
|
||||
public class Tab
|
||||
{
|
||||
private IItem? _currentSelectedItem;
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
private IContainer _currentLocation;
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
|
||||
private readonly List<AbsolutePath> _history = new();
|
||||
|
||||
private string? _lastPath;
|
||||
|
||||
private bool _currentlySelecting;
|
||||
|
||||
private readonly object _guardSetCurrentSelectedItemCTS = new();
|
||||
private CancellationTokenSource? _setCurrentSelectedItemCTS;
|
||||
|
||||
public int CurrentSelectedIndex { get; private set; }
|
||||
|
||||
public bool AutoRefresh { get; set; }
|
||||
|
||||
public IReadOnlyList<AbsolutePath> History { get; }
|
||||
|
||||
public AsyncEventHandler CurrentLocationChanged = new();
|
||||
public AsyncEventHandler CurrentSelectedItemChanged = new();
|
||||
public AsyncEventHandler HistoryChanged { get; } = new();
|
||||
public AsyncEventHandler<bool> CurrentSelectedItemChanged = new();
|
||||
|
||||
public Tab()
|
||||
{
|
||||
_currentLocation = null!;
|
||||
History = _history.AsReadOnly();
|
||||
}
|
||||
|
||||
public async Task Init(IContainer currentPath)
|
||||
{
|
||||
await SetCurrentLocation(currentPath);
|
||||
}
|
||||
|
||||
public Task<IContainer> GetCurrentLocation()
|
||||
public Task<IContainer> GetCurrentLocation(CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(_currentLocation);
|
||||
}
|
||||
@@ -33,40 +52,95 @@ namespace FileTime.Core.Components
|
||||
if (_currentLocation != null)
|
||||
{
|
||||
_currentLocation.Refreshed.Remove(HandleCurrentLocationRefresh);
|
||||
if (_currentLocation is SearchContainer searchContainer)
|
||||
{
|
||||
searchContainer.SearchTaskBase.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
_currentLocation = value;
|
||||
await AddHistoryItem(_currentLocation);
|
||||
await CurrentLocationChanged.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
|
||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||
await SetCurrentSelectedItem(await GetItemByLastPath() ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null));
|
||||
var currentLocationItems = await (await GetCurrentLocation()).GetItems();
|
||||
if (currentLocationItems == null) throw new Exception("Could not get current location items.");
|
||||
await SetCurrentSelectedItem(await GetItemByLastPath() ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null), true);
|
||||
_currentLocation.Refreshed.Add(HandleCurrentLocationRefresh);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddHistoryItem(IContainer container)
|
||||
{
|
||||
var containerPath = new AbsolutePath(container);
|
||||
if (_history.Count == 0 || _history.Last() != containerPath)
|
||||
{
|
||||
_history.Add(containerPath);
|
||||
}
|
||||
|
||||
while (_history.Count > 50)
|
||||
{
|
||||
_history.RemoveAt(0);
|
||||
}
|
||||
|
||||
await HistoryChanged.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
}
|
||||
|
||||
public Task<IItem?> GetCurrentSelectedItem()
|
||||
{
|
||||
return Task.FromResult(_currentSelectedItem);
|
||||
}
|
||||
|
||||
public async Task SetCurrentSelectedItem(IItem? value)
|
||||
/*public async Task<bool> SetCurrentSelectedItem(IItem? value, bool secondary = false, CancellationToken token = default)
|
||||
{
|
||||
if (_currentSelectedItem != value)
|
||||
|
||||
}*/
|
||||
public async Task<bool> SetCurrentSelectedItem(IItem? value, bool locationChanged = false, CancellationToken token = default)
|
||||
{
|
||||
if (_currentlySelecting) return false;
|
||||
|
||||
try
|
||||
{
|
||||
_currentlySelecting = true;
|
||||
|
||||
IItem? itemToSelect = null;
|
||||
if (value != null)
|
||||
{
|
||||
itemToSelect = (await _currentLocation.GetItems())?.FirstOrDefault(i =>
|
||||
itemToSelect = (await _currentLocation.GetItems(token))?.FirstOrDefault(i =>
|
||||
i.FullName == null && value?.FullName == null
|
||||
? i.Name == value?.Name
|
||||
: i.FullName == value?.FullName);
|
||||
if (itemToSelect == null) throw new IndexOutOfRangeException("Provided item does not exists in the current container.");
|
||||
if (itemToSelect == null) throw new IndexOutOfRangeException($"Provided item ({value.FullName ?? "unknwon"}) does not exists in the current container ({_currentLocation.FullName ?? "unknwon"}).");
|
||||
}
|
||||
|
||||
CancellationToken newToken;
|
||||
lock (_guardSetCurrentSelectedItemCTS)
|
||||
{
|
||||
if (token.IsCancellationRequested) return false;
|
||||
_setCurrentSelectedItemCTS?.Cancel();
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
_setCurrentSelectedItemCTS = new CancellationTokenSource();
|
||||
newToken = _setCurrentSelectedItemCTS.Token;
|
||||
}
|
||||
else
|
||||
{
|
||||
_setCurrentSelectedItemCTS = new CancellationTokenSource();
|
||||
newToken = CancellationTokenSource.CreateLinkedTokenSource(_setCurrentSelectedItemCTS.Token, token).Token;
|
||||
}
|
||||
}
|
||||
|
||||
_currentSelectedItem = itemToSelect;
|
||||
_lastPath = GetCommonPath(_lastPath, itemToSelect?.FullName);
|
||||
CurrentSelectedIndex = await GetItemIndex(itemToSelect);
|
||||
await CurrentSelectedItemChanged.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
_lastPath = PathHelper.GetLongerPath(_lastPath, itemToSelect?.FullName);
|
||||
|
||||
CurrentSelectedIndex = await GetItemIndex(itemToSelect, CancellationToken.None);
|
||||
|
||||
await CurrentSelectedItemChanged.InvokeAsync(this, locationChanged, newToken);
|
||||
|
||||
return !newToken.IsCancellationRequested;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentlySelecting = false;
|
||||
}
|
||||
}
|
||||
public async Task<IItem?> GetItemByLastPath(IContainer? container = null)
|
||||
@@ -75,118 +149,92 @@ namespace FileTime.Core.Components
|
||||
var containerFullName = container.FullName;
|
||||
|
||||
if (_lastPath == null
|
||||
|| !container.IsLoaded
|
||||
|| (containerFullName != null && !_lastPath.StartsWith(containerFullName))
|
||||
)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
var itemNameToSelect = _lastPath
|
||||
.Split(Constants.SeparatorChar)
|
||||
.Skip(
|
||||
containerFullName == null
|
||||
? 0
|
||||
: containerFullName
|
||||
.Split(Constants.SeparatorChar)
|
||||
.Count())
|
||||
.Skip((containerFullName?.Split(Constants.SeparatorChar).Length) ?? 0)
|
||||
.FirstOrDefault();
|
||||
|
||||
return (await container.GetItems())?.FirstOrDefault(i => i.Name == itemNameToSelect);
|
||||
}
|
||||
|
||||
private string GetCommonPath(string? oldPath, string? newPath)
|
||||
{
|
||||
var oldPathParts = oldPath?.Split(Constants.SeparatorChar) ?? new string[0];
|
||||
var newPathParts = newPath?.Split(Constants.SeparatorChar) ?? new string[0];
|
||||
|
||||
var commonPathParts = new List<string>();
|
||||
|
||||
var max = oldPathParts.Length > newPathParts.Length ? oldPathParts.Length : newPathParts.Length;
|
||||
|
||||
for (var i = 0; i < max; i++)
|
||||
{
|
||||
if (newPathParts.Length <= i)
|
||||
{
|
||||
commonPathParts.AddRange(oldPathParts.Skip(i));
|
||||
break;
|
||||
}
|
||||
else if (oldPathParts.Length <= i || oldPathParts[i] != newPathParts[i])
|
||||
{
|
||||
commonPathParts.AddRange(newPathParts.Skip(i));
|
||||
break;
|
||||
}
|
||||
else if (oldPathParts[i] == newPathParts[i])
|
||||
{
|
||||
commonPathParts.Add(oldPathParts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join(Constants.SeparatorChar, commonPathParts);
|
||||
}
|
||||
|
||||
private async Task HandleCurrentLocationRefresh(object? sender, AsyncEventArgs e)
|
||||
private async Task HandleCurrentLocationRefresh(object? sender, AsyncEventArgs e, CancellationToken token = default)
|
||||
{
|
||||
var currentSelectedName = (await GetCurrentSelectedItem())?.FullName ?? (await GetItemByLastPath())?.FullName;
|
||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||
var currentLocationItems = (await (await GetCurrentLocation(token)).GetItems(token))!;
|
||||
if (currentSelectedName != null)
|
||||
{
|
||||
await SetCurrentSelectedItem(currentLocationItems.FirstOrDefault(i => i.FullName == currentSelectedName) ?? currentLocationItems.FirstOrDefault());
|
||||
await SetCurrentSelectedItem(currentLocationItems.FirstOrDefault(i => i.FullName == currentSelectedName) ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null), token: token);
|
||||
}
|
||||
else if (currentLocationItems.Count > 0)
|
||||
{
|
||||
await SetCurrentSelectedItem(currentLocationItems[0]);
|
||||
await SetCurrentSelectedItem(currentLocationItems[0], token: token);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SelectFirstItem()
|
||||
public async Task SelectFirstItem(CancellationToken token = default)
|
||||
{
|
||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||
var currentLocationItems = (await (await GetCurrentLocation(token)).GetItems(token))!;
|
||||
if (currentLocationItems.Count > 0)
|
||||
{
|
||||
await SetCurrentSelectedItem(currentLocationItems[0]);
|
||||
await SetCurrentSelectedItem(currentLocationItems[0], token: token);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SelectLastItem()
|
||||
public async Task SelectLastItem(CancellationToken token = default)
|
||||
{
|
||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||
var currentLocationItems = (await (await GetCurrentLocation(token)).GetItems(token))!;
|
||||
if (currentLocationItems.Count > 0)
|
||||
{
|
||||
await SetCurrentSelectedItem(currentLocationItems[currentLocationItems.Count - 1]);
|
||||
await SetCurrentSelectedItem(currentLocationItems[currentLocationItems.Count - 1], token: token);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SelectPreviousItem(int skip = 0)
|
||||
public async Task SelectPreviousItem(int skip = 0, CancellationToken token = default)
|
||||
{
|
||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||
var currentLocationItems = (await (await GetCurrentLocation(token)).GetItems(token))!;
|
||||
var possibleItemsToSelect = currentLocationItems.Take(CurrentSelectedIndex).Reverse().Skip(skip).ToList();
|
||||
|
||||
if (possibleItemsToSelect.Count == 0) possibleItemsToSelect = currentLocationItems.ToList();
|
||||
await SelectItem(possibleItemsToSelect);
|
||||
await SelectItem(possibleItemsToSelect, token);
|
||||
}
|
||||
|
||||
public async Task SelectNextItem(int skip = 0)
|
||||
public async Task SelectNextItem(int skip = 0, CancellationToken token = default)
|
||||
{
|
||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||
var currentLocationItems = (await (await GetCurrentLocation(token)).GetItems(token))!;
|
||||
var possibleItemsToSelect = currentLocationItems.Skip(CurrentSelectedIndex + 1 + skip).ToList();
|
||||
|
||||
if (possibleItemsToSelect.Count == 0) possibleItemsToSelect = currentLocationItems.Reverse().ToList();
|
||||
await SelectItem(possibleItemsToSelect);
|
||||
await SelectItem(possibleItemsToSelect, token);
|
||||
}
|
||||
|
||||
private async Task SelectItem(IEnumerable<IItem> currentPossibleItems)
|
||||
private async Task SelectItem(IEnumerable<IItem> currentPossibleItems, CancellationToken token = default)
|
||||
{
|
||||
if (!currentPossibleItems.Any()) return;
|
||||
|
||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||
if (token.IsCancellationRequested) return;
|
||||
var currentLocation = await GetCurrentLocation(token);
|
||||
var currentLocationItems = (await currentLocation.GetItems(token))!;
|
||||
|
||||
if (await GetCurrentSelectedItem() != null)
|
||||
{
|
||||
(await GetCurrentLocation())?.Refresh();
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
IItem? newSelectedItem = null;
|
||||
try
|
||||
{
|
||||
_currentlySelecting = true;
|
||||
if (AutoRefresh && currentLocation != null)
|
||||
{
|
||||
await currentLocation.RefreshAsync(token);
|
||||
if (token.IsCancellationRequested) return;
|
||||
}
|
||||
|
||||
foreach (var item in currentPossibleItems)
|
||||
{
|
||||
if (currentLocationItems.FirstOrDefault(i => i.Name == item.Name) is var possibleNewSelectedItem
|
||||
@@ -197,16 +245,20 @@ namespace FileTime.Core.Components
|
||||
}
|
||||
}
|
||||
|
||||
if(newSelectedItem != null)
|
||||
if (newSelectedItem != null)
|
||||
{
|
||||
newSelectedItem = (await (await GetCurrentLocation()).GetItems())?.FirstOrDefault(i => i.Name == newSelectedItem.Name);
|
||||
newSelectedItem = (await (await GetCurrentLocation(token)).GetItems(token))?.FirstOrDefault(i => i.Name == newSelectedItem.Name);
|
||||
}
|
||||
|
||||
await SetCurrentSelectedItem(newSelectedItem ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentlySelecting = false;
|
||||
}
|
||||
await SetCurrentSelectedItem(newSelectedItem ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null), token: token);
|
||||
}
|
||||
else
|
||||
{
|
||||
await SetCurrentSelectedItem(currentLocationItems.Count > 0 ? currentLocationItems[0] : null);
|
||||
await SetCurrentSelectedItem(currentLocationItems.Count > 0 ? currentLocationItems[0] : null, token: token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,40 +295,78 @@ namespace FileTime.Core.Components
|
||||
if (lastCurrentLocation is VirtualContainer lastCurrentVirtualContainer)
|
||||
{
|
||||
await SetCurrentLocation(lastCurrentVirtualContainer.CloneVirtualChainFor(parent, v => v.IsPermanent));
|
||||
await SetCurrentSelectedItem(lastCurrentVirtualContainer.GetRealContainer());
|
||||
await SetCurrentSelectedItem(lastCurrentVirtualContainer.GetRealContainer(), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await SetCurrentLocation(parent);
|
||||
var newCurrentLocation = (await (await GetCurrentLocation()).GetItems())?.FirstOrDefault(i => i.Name == lastCurrentLocation.Name);
|
||||
await SetCurrentSelectedItem(newCurrentLocation);
|
||||
await SetCurrentSelectedItem(newCurrentLocation, true);
|
||||
}
|
||||
|
||||
foreach (var lastLocationItem in currentLocationItems.OfType<IContainer>())
|
||||
{
|
||||
lastLocationItem.Destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Open()
|
||||
{
|
||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||
if (_currentSelectedItem is IContainer childContainer)
|
||||
if (_currentSelectedItem is ChildSearchElement searchElement && searchElement.GetParent() is IContainer parentContainer)
|
||||
{
|
||||
if (await GetCurrentLocation() is VirtualContainer currentVirtuakContainer)
|
||||
await OpenContainer(parentContainer);
|
||||
var elementToSelect = await (await GetCurrentLocation()).GetByPath(searchElement.DisplayName);
|
||||
if (elementToSelect != null)
|
||||
{
|
||||
await SetCurrentLocation(currentVirtuakContainer.CloneVirtualChainFor(childContainer, v => v.IsPermanent));
|
||||
await SetCurrentSelectedItem(elementToSelect);
|
||||
}
|
||||
}
|
||||
else if (_currentSelectedItem is ChildSearchContainer searchContainer)
|
||||
{
|
||||
await OpenContainer(searchContainer.BaseContainer);
|
||||
}
|
||||
else if (_currentSelectedItem is IContainer childContainer)
|
||||
{
|
||||
if (await GetCurrentLocation() is VirtualContainer currentVirtualContainer)
|
||||
{
|
||||
await SetCurrentLocation(currentVirtualContainer.CloneVirtualChainFor(childContainer, v => v.IsPermanent));
|
||||
}
|
||||
else
|
||||
{
|
||||
await SetCurrentLocation(childContainer);
|
||||
}
|
||||
}
|
||||
else if (_currentSelectedItem is ISymlinkElement symlinkElement)
|
||||
{
|
||||
if (symlinkElement.RealItem is IContainer realContainer)
|
||||
{
|
||||
await SetCurrentLocation(realContainer);
|
||||
}
|
||||
else if (symlinkElement.RealItem is IElement realElement)
|
||||
{
|
||||
if (realElement.GetParent() is IContainer parent)
|
||||
{
|
||||
await SetCurrentLocation(parent);
|
||||
if (await _currentLocation.IsExistsAsync(realElement.Name))
|
||||
{
|
||||
var newRealElement = await _currentLocation.GetByPath(realElement.Name);
|
||||
|
||||
if (newRealElement != null) await SetCurrentSelectedItem(newRealElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OpenContainer(IContainer container) => await SetCurrentLocation(container);
|
||||
|
||||
private async Task<int> GetItemIndex(IItem? item)
|
||||
private async Task<int> GetItemIndex(IItem? item, CancellationToken token)
|
||||
{
|
||||
if (item == null) return -1;
|
||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||
var currentLocationItems = (await (await GetCurrentLocation(token)).GetItems(token))!;
|
||||
|
||||
if (token.IsCancellationRequested) return -1;
|
||||
for (var i = 0; i < currentLocationItems.Count; i++)
|
||||
{
|
||||
if (currentLocationItems[i] == item) return i;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Threading.Tasks;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.ContainerSizeScanner
|
||||
{
|
||||
public class ContainerScanSnapshotProvider : ContentProviderBase<ContainerScanSnapshotProvider>
|
||||
{
|
||||
|
||||
public ContainerScanSnapshotProvider() : base("size", "size://", false)
|
||||
{
|
||||
}
|
||||
|
||||
public override Task<IContainer> CreateContainerAsync(string name) => throw new NotSupportedException();
|
||||
|
||||
public override Task<IElement> CreateElementAsync(string name) => throw new NotSupportedException();
|
||||
|
||||
public async Task AddSnapshotAsync(ContainerSizeContainer snapshot)
|
||||
{
|
||||
if (RootContainers != null)
|
||||
{
|
||||
RootContainers.Add(snapshot);
|
||||
while (RootContainers.Count > 10)
|
||||
{
|
||||
RootContainers.RemoveAt(0);
|
||||
}
|
||||
await RefreshAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RemoveSnapshotAsync(ContainerSizeContainer snapshot)
|
||||
{
|
||||
if (RootContainers?.Contains(snapshot) == true)
|
||||
{
|
||||
RootContainers.Remove(snapshot);
|
||||
await RefreshAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Core.Providers.ContainerProperty;
|
||||
|
||||
namespace FileTime.Core.ContainerSizeScanner
|
||||
{
|
||||
public class ContainerSizeContainer : LazyLoadingContainer<ContainerScanSnapshotProvider, ContainerSizeContainer, ContainerSizeElement>, IItemWithSize, IHaveCreatedAt, IHaveAttributes
|
||||
{
|
||||
public override bool IsExists => true;
|
||||
|
||||
public long? Size { get; private set; }
|
||||
public AsyncEventHandler<long?> SizeChanged { get; } = new();
|
||||
|
||||
public bool AllowSizeScan { get; private set; } = true;
|
||||
|
||||
private readonly IContainer _baseContainer;
|
||||
|
||||
public string Attributes => _baseContainer is IHaveAttributes haveAttributes ? haveAttributes.Attributes : "";
|
||||
|
||||
public DateTime? CreatedAt => _baseContainer is IHaveCreatedAt haveCreatedAt ? haveCreatedAt.CreatedAt : null;
|
||||
|
||||
public ContainerSizeContainer(ContainerScanSnapshotProvider provider, IContainer parent, IContainer baseContainer, string? displayName = null) : base(provider, parent, baseContainer.Name)
|
||||
{
|
||||
_baseContainer = baseContainer;
|
||||
CanDelete = SupportsDelete.True;
|
||||
AllowRecursiveDeletion = false;
|
||||
CanHandleEscape = true;
|
||||
if (displayName != null)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<IContainer> CloneAsync() => Task.FromResult((IContainer)this);
|
||||
|
||||
public override Task<IContainer> CreateContainerAsync(string name) => throw new NotSupportedException();
|
||||
|
||||
public override Task<IElement> CreateElementAsync(string name) => throw new NotSupportedException();
|
||||
|
||||
public override async Task Delete(bool hardDelete = false)
|
||||
{
|
||||
if (GetParent() is ContainerScanSnapshotProvider provider)
|
||||
{
|
||||
await provider.RemoveSnapshotAsync(this);
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task RefreshAsync(CancellationToken token = default)
|
||||
{
|
||||
if (Refreshed != null) await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<IItem>> RefreshItems(CancellationToken token = default) => throw new NotImplementedException();
|
||||
|
||||
public override Task Rename(string newName) => throw new NotSupportedException();
|
||||
|
||||
public override async Task AddContainerAsync(ContainerSizeContainer container)
|
||||
{
|
||||
await base.AddContainerAsync(container);
|
||||
container.SizeChanged.Add(ChildContainerSizeChanged);
|
||||
}
|
||||
|
||||
public override async Task AddElementAsync(ContainerSizeElement element)
|
||||
{
|
||||
await base.AddElementAsync(element);
|
||||
}
|
||||
|
||||
public IEnumerable<IItemWithSize> GetItemsWithSize() => Containers.Cast<IItemWithSize>().Concat(Elements);
|
||||
|
||||
private async Task ChildContainerSizeChanged(object? sender, long? size, CancellationToken token) => await UpdateSize();
|
||||
|
||||
public async Task UpdateSize()
|
||||
{
|
||||
Size = Containers.Aggregate(0L, (sum, c) => sum + c.Size ?? 0)
|
||||
+ Elements.Aggregate(0L, (sum, e) => sum + e.Size);
|
||||
|
||||
await SizeChanged.InvokeAsync(this, Size);
|
||||
}
|
||||
|
||||
public override Task<ContainerEscapeResult> HandleEscape()
|
||||
{
|
||||
if (AllowSizeScan)
|
||||
{
|
||||
AllowSizeScan = false;
|
||||
return Task.FromResult(new ContainerEscapeResult(true));
|
||||
}
|
||||
|
||||
return Task.FromResult(new ContainerEscapeResult(_baseContainer));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.ContainerSizeScanner
|
||||
{
|
||||
public class ContainerSizeElement : AbstractElement<ContainerScanSnapshotProvider>, IItemWithSize, IFile
|
||||
{
|
||||
private readonly IElement _element;
|
||||
|
||||
public long Size { get; }
|
||||
|
||||
long? IItemWithSize.Size => Size;
|
||||
|
||||
public string Attributes => _element is IFile file ? file.Attributes : _element.GetType().Name.Split('.').Last();
|
||||
|
||||
public DateTime CreatedAt => _element is IFile file ? file.CreatedAt : DateTime.MinValue;
|
||||
|
||||
public ContainerSizeElement(ContainerScanSnapshotProvider provider, IContainer parent, IElement element, long size) : base(provider, parent, element.Name)
|
||||
{
|
||||
Size = size;
|
||||
CanDelete = SupportsDelete.False;
|
||||
_element = element;
|
||||
}
|
||||
|
||||
public override Task Delete(bool hardDelete = false) => throw new NotSupportedException();
|
||||
|
||||
public override Task<string> GetContent(CancellationToken token = default) => Task.FromResult("NotImplementedException");
|
||||
|
||||
public override Task<IContentReader> GetContentReaderAsync() => throw new NotSupportedException();
|
||||
|
||||
public override Task<IContentWriter> GetContentWriterAsync() => throw new NotSupportedException();
|
||||
|
||||
public override Task<long?> GetElementSize(CancellationToken token = default) => Task.FromResult((long?)Size);
|
||||
|
||||
public override string? GetPrimaryAttributeText() => null;
|
||||
|
||||
public override Task Rename(string newName) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
128
src/Core/FileTime.Core/ContainerSizeScanner/ScanSizeTask.cs
Normal file
128
src/Core/FileTime.Core/ContainerSizeScanner/ScanSizeTask.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.ContainerSizeScanner
|
||||
{
|
||||
public class ScanSizeTask
|
||||
{
|
||||
private readonly object _scanGuard = new();
|
||||
private bool _scannig;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
public IContainer ContainerToScan { get; }
|
||||
public ContainerSizeContainer Snapshot { get; }
|
||||
|
||||
public bool Scanning
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_scanGuard)
|
||||
{
|
||||
return _scannig;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ScanSizeTask(ContainerScanSnapshotProvider provider, IContainer containerToScan)
|
||||
{
|
||||
ContainerToScan = containerToScan;
|
||||
Snapshot = new ContainerSizeContainer(provider, provider, containerToScan, "Size scan on " + containerToScan.DisplayName);
|
||||
Task.Run(async () => await provider.AddSnapshotAsync(Snapshot)).Wait();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
lock (_scanGuard)
|
||||
{
|
||||
if (_scannig) return;
|
||||
_scannig = true;
|
||||
}
|
||||
|
||||
new Thread(BootstrapScan).Start();
|
||||
|
||||
void BootstrapScan()
|
||||
{
|
||||
try
|
||||
{
|
||||
Task.Run(async () => await Snapshot.RunWithLoading(async (token) => await ScanAsync(ContainerToScan, Snapshot, token))).Wait();
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_scanGuard)
|
||||
{
|
||||
_scannig = false;
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ScanAsync(IContainer container, ContainerSizeContainer targetContainer, CancellationToken token)
|
||||
{
|
||||
if (IsScanCancelled(targetContainer)) return;
|
||||
|
||||
var childElements = await container.GetElements(token);
|
||||
if (childElements != null)
|
||||
{
|
||||
foreach (var childElement in childElements)
|
||||
{
|
||||
if (token.IsCancellationRequested
|
||||
|| IsScanCancelled(targetContainer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var newSizeElement = new ContainerSizeElement(targetContainer.Provider, targetContainer, childElement, await childElement.GetElementSize(token) ?? 0);
|
||||
await targetContainer.AddElementAsync(newSizeElement);
|
||||
}
|
||||
}
|
||||
|
||||
var childContainers = await container.GetContainers(token);
|
||||
if (childContainers != null)
|
||||
{
|
||||
var newSizeContainers = new List<(ContainerSizeContainer, IContainer)>();
|
||||
foreach (var childContainer in childContainers)
|
||||
{
|
||||
var newSizeContainer = new ContainerSizeContainer(targetContainer.Provider, targetContainer, childContainer);
|
||||
await targetContainer.AddContainerAsync(newSizeContainer);
|
||||
newSizeContainers.Add((newSizeContainer, childContainer));
|
||||
}
|
||||
|
||||
foreach (var (newSizeContainer, childContainer) in newSizeContainers)
|
||||
{
|
||||
if (token.IsCancellationRequested
|
||||
|| IsScanCancelled(newSizeContainer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
await newSizeContainer.RunWithLoading(async (token) => await ScanAsync(childContainer, newSizeContainer, token), token);
|
||||
}
|
||||
}
|
||||
|
||||
await targetContainer.UpdateSize();
|
||||
}
|
||||
|
||||
private static bool IsScanCancelled(ContainerSizeContainer container)
|
||||
{
|
||||
IContainer? parent = container;
|
||||
while (parent is ContainerSizeContainer sizeContainer)
|
||||
{
|
||||
if (!sizeContainer.AllowSizeScan)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
parent = parent.GetParent();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
lock (_scanGuard)
|
||||
{
|
||||
if (_scannig || _cancellationTokenSource == null) return;
|
||||
_cancellationTokenSource.Cancel();
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Extensions
|
||||
{
|
||||
public static class TimelineExtensions
|
||||
{
|
||||
public static DifferenceItemType ToDifferenceItemType(this IItem? item)
|
||||
{
|
||||
if (item is IContainer) return DifferenceItemType.Container;
|
||||
else if (item is IElement) return DifferenceItemType.Element;
|
||||
else return DifferenceItemType.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\AppCommon\FileTime.App.Style\FileTime.App.Style.csproj" />
|
||||
<ProjectReference Include="..\AsyncEvent\AsyncEvent.csproj" />
|
||||
|
||||
27
src/Core/FileTime.Core/Helper/CommandHelper.cs
Normal file
27
src/Core/FileTime.Core/Helper/CommandHelper.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Helper
|
||||
{
|
||||
public static class CommandHelper
|
||||
{
|
||||
public static async Task<string?> GetNewNameAsync(IContainer? resolvedTarget, string name, Command.TransportMode transportMode)
|
||||
{
|
||||
var newName = name;
|
||||
var targetNameExists = resolvedTarget != null && await resolvedTarget.IsExistsAsync(newName);
|
||||
if (transportMode == Command.TransportMode.Merge && resolvedTarget != null)
|
||||
{
|
||||
for (var i = 0; targetNameExists; i++)
|
||||
{
|
||||
newName = name + (i == 0 ? "_" : $"_{i}");
|
||||
targetNameExists = await resolvedTarget.IsExistsAsync(newName);
|
||||
}
|
||||
}
|
||||
else if (transportMode == Command.TransportMode.Skip && targetNameExists)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return newName;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
src/Core/FileTime.Core/Helper/PathHelper.cs
Normal file
55
src/Core/FileTime.Core/Helper/PathHelper.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Helper
|
||||
{
|
||||
public static class PathHelper
|
||||
{
|
||||
public static string GetLongerPath(string? oldPath, string? newPath)
|
||||
{
|
||||
var oldPathParts = oldPath?.Split(Constants.SeparatorChar) ?? Array.Empty<string>();
|
||||
var newPathParts = newPath?.Split(Constants.SeparatorChar) ?? Array.Empty<string>();
|
||||
|
||||
var commonPathParts = new List<string>();
|
||||
|
||||
var max = oldPathParts.Length > newPathParts.Length ? oldPathParts.Length : newPathParts.Length;
|
||||
|
||||
for (var i = 0; i < max; i++)
|
||||
{
|
||||
if (newPathParts.Length <= i)
|
||||
{
|
||||
commonPathParts.AddRange(oldPathParts.Skip(i));
|
||||
break;
|
||||
}
|
||||
else if (oldPathParts.Length <= i || oldPathParts[i] != newPathParts[i])
|
||||
{
|
||||
commonPathParts.AddRange(newPathParts.Skip(i));
|
||||
break;
|
||||
}
|
||||
else if (oldPathParts[i] == newPathParts[i])
|
||||
{
|
||||
commonPathParts.Add(oldPathParts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join(Constants.SeparatorChar, commonPathParts);
|
||||
}
|
||||
public static string GetCommonPath(string? path1, string? path2)
|
||||
{
|
||||
var path1Parts = path1?.Split(Constants.SeparatorChar) ?? Array.Empty<string>();
|
||||
var path2Parts = path2?.Split(Constants.SeparatorChar) ?? Array.Empty<string>();
|
||||
|
||||
var commonPathParts = new List<string>();
|
||||
|
||||
var max = path1Parts.Length > path2Parts.Length ? path2Parts.Length : path1Parts.Length;
|
||||
|
||||
for (var i = 0; i < max; i++)
|
||||
{
|
||||
if (path1Parts[i] != path2Parts[i]) break;
|
||||
|
||||
commonPathParts.Add(path1Parts[i]);
|
||||
}
|
||||
|
||||
return string.Join(Constants.SeparatorChar, commonPathParts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,38 @@ namespace FileTime.Core.Interactions
|
||||
{
|
||||
public class InputElement
|
||||
{
|
||||
public string Text { get; }
|
||||
public string Label { get; }
|
||||
public InputType InputType { get; }
|
||||
public string? DefaultValue { get; }
|
||||
public List<object>? Options { get; }
|
||||
|
||||
public InputElement(string text, InputType inputType, string? defaultValue = null)
|
||||
protected InputElement(string text, InputType inputType, string? defaultValue = null)
|
||||
{
|
||||
Text = text;
|
||||
Label = text;
|
||||
InputType = inputType;
|
||||
DefaultValue = defaultValue;
|
||||
}
|
||||
|
||||
protected InputElement(string text, InputType inputType, List<object> defaultValue)
|
||||
{
|
||||
Label = text;
|
||||
InputType = inputType;
|
||||
Options = defaultValue;
|
||||
}
|
||||
|
||||
public static InputElement ForText(string label, string? defaultValue = null)
|
||||
{
|
||||
return new InputElement(label, InputType.Text, defaultValue);
|
||||
}
|
||||
|
||||
public static InputElement ForPassword(string label, string? defaultValue = null)
|
||||
{
|
||||
return new InputElement(label, InputType.Password, defaultValue);
|
||||
}
|
||||
|
||||
public static InputElement ForOptions(string label, IEnumerable<object> defaultValue)
|
||||
{
|
||||
return new InputElement(label, InputType.Options, new List<object>(defaultValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@ namespace FileTime.Core.Interactions
|
||||
{
|
||||
public enum InputType
|
||||
{
|
||||
Text,
|
||||
Options,
|
||||
Password,
|
||||
Bool
|
||||
Text,
|
||||
}
|
||||
}
|
||||
@@ -3,25 +3,28 @@ using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public sealed class AbsolutePath
|
||||
public sealed class AbsolutePath : IEquatable<AbsolutePath>
|
||||
{
|
||||
public IContentProvider ContentProvider { get; }
|
||||
public IContentProvider? VirtualContentProvider { get; }
|
||||
|
||||
public string Path { get; }
|
||||
public AbsolutePathType Type { get; }
|
||||
|
||||
public AbsolutePath(AbsolutePath from)
|
||||
{
|
||||
ContentProvider = from.ContentProvider;
|
||||
Path = from.Path;
|
||||
VirtualContentProvider = from.VirtualContentProvider;
|
||||
Type = from.Type;
|
||||
}
|
||||
|
||||
public AbsolutePath(IContentProvider contentProvider, string path, IContentProvider? virtualContentProvider)
|
||||
public AbsolutePath(IContentProvider contentProvider, string path, AbsolutePathType type, IContentProvider? virtualContentProvider)
|
||||
{
|
||||
ContentProvider = contentProvider;
|
||||
Path = path;
|
||||
VirtualContentProvider = virtualContentProvider;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public AbsolutePath(IItem item)
|
||||
@@ -43,55 +46,82 @@ namespace FileTime.Core.Models
|
||||
ContentProvider = item.Provider;
|
||||
Path = item.FullName!;
|
||||
}
|
||||
|
||||
Type = item switch
|
||||
{
|
||||
IContainer => AbsolutePathType.Container,
|
||||
IElement => AbsolutePathType.Element,
|
||||
_ => AbsolutePathType.Unknown
|
||||
};
|
||||
}
|
||||
|
||||
public static AbsolutePath FromParentAndChildName(IContainer parent, string childName)
|
||||
public static AbsolutePath FromParentAndChildName(IContainer parent, string childName, AbsolutePathType childType)
|
||||
{
|
||||
IContentProvider? contentProvider;
|
||||
IContentProvider? virtualContentProvider;
|
||||
string? path;
|
||||
var contentProvider = parent.Provider;
|
||||
var path = parent.FullName! + Constants.SeparatorChar + childName;
|
||||
|
||||
if (parent is TimeContainer timeContainer)
|
||||
var virtualContentProvider = parent switch
|
||||
{
|
||||
contentProvider = timeContainer.Provider;
|
||||
virtualContentProvider = timeContainer.VirtualProvider;
|
||||
path = timeContainer.FullName! + Constants.SeparatorChar + childName;
|
||||
}
|
||||
else
|
||||
{
|
||||
contentProvider = parent.Provider;
|
||||
path = parent.FullName! + Constants.SeparatorChar + childName;
|
||||
virtualContentProvider = null;
|
||||
TimeContainer timeContainer => timeContainer.VirtualProvider,
|
||||
_ => null
|
||||
};
|
||||
|
||||
return new AbsolutePath(contentProvider, path, childType, virtualContentProvider);
|
||||
}
|
||||
|
||||
return new AbsolutePath(contentProvider, path, virtualContentProvider);
|
||||
public AbsolutePath GetChild(string childName, AbsolutePathType childType)
|
||||
{
|
||||
var path = Path + Constants.SeparatorChar + childName;
|
||||
|
||||
return new AbsolutePath(ContentProvider, path, childType, VirtualContentProvider);
|
||||
}
|
||||
|
||||
public bool IsEqual(AbsolutePath path)
|
||||
public async Task<IItem?> ResolveAsync()
|
||||
{
|
||||
//TODO: sure??
|
||||
return path.ContentProvider == ContentProvider && path.Path == Path;
|
||||
}
|
||||
|
||||
public async Task<IItem?> Resolve()
|
||||
{
|
||||
var result = VirtualContentProvider != null && (await VirtualContentProvider.IsExists(Path))
|
||||
var result = VirtualContentProvider != null && (await VirtualContentProvider.IsExistsAsync(Path))
|
||||
? await VirtualContentProvider.GetByPath(Path)
|
||||
: null;
|
||||
|
||||
if (ContentProvider == null) return null;
|
||||
|
||||
result ??= await ContentProvider.GetByPath(Path);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string GetParent()
|
||||
public string GetParentPath()
|
||||
{
|
||||
var pathParts = Path.Split(Constants.SeparatorChar);
|
||||
return string.Join(Constants.SeparatorChar, pathParts);
|
||||
return string.Join(Constants.SeparatorChar, pathParts[..^1]);
|
||||
}
|
||||
|
||||
public AbsolutePath GetParentAsAbsolutePath() => new(ContentProvider, GetParent(), VirtualContentProvider);
|
||||
public AbsolutePath GetParent() => new(ContentProvider, GetParentPath(), AbsolutePathType.Container, VirtualContentProvider);
|
||||
|
||||
public string GetName() => Path.Split(Constants.SeparatorChar).Last();
|
||||
|
||||
public override bool Equals(object? obj) => this.Equals(obj as AbsolutePath);
|
||||
|
||||
public bool Equals(AbsolutePath? other) =>
|
||||
other is not null && other.ContentProvider == ContentProvider && other.Path == Path;
|
||||
|
||||
public override int GetHashCode() => (ContentProvider.Name, Path).GetHashCode();
|
||||
|
||||
public static bool operator ==(AbsolutePath? lhs, AbsolutePath? rhs)
|
||||
{
|
||||
if (lhs is null)
|
||||
{
|
||||
if (rhs is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only the left side is null.
|
||||
return false;
|
||||
}
|
||||
// Equals handles case of null on right side.
|
||||
return lhs.Equals(rhs);
|
||||
}
|
||||
|
||||
public static bool operator !=(AbsolutePath? lhs, AbsolutePath? rhs) => !(lhs == rhs);
|
||||
}
|
||||
}
|
||||
31
src/Core/FileTime.Core/Models/AbsolutePathDto.cs
Normal file
31
src/Core/FileTime.Core/Models/AbsolutePathDto.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public class AbsolutePathDto
|
||||
{
|
||||
public string? Path { get; set; }
|
||||
public AbsolutePathType? Type { get; set; }
|
||||
public string? ContentProviderName { get; set; }
|
||||
public string? VirtualContentProviderName { get; set; }
|
||||
|
||||
public AbsolutePathDto() { }
|
||||
public AbsolutePathDto(AbsolutePath path)
|
||||
{
|
||||
Path = path.Path;
|
||||
Type = path.Type;
|
||||
ContentProviderName = path.ContentProvider.Name;
|
||||
VirtualContentProviderName = path.VirtualContentProvider?.Name;
|
||||
}
|
||||
|
||||
public AbsolutePath Resolve(IEnumerable<IContentProvider> providers)
|
||||
{
|
||||
var contentProvider = providers.FirstOrDefault(p => p.Name == ContentProviderName) ?? throw new Exception($"Could not found content provider with name {ContentProviderName}");
|
||||
var virtualContentProvider = VirtualContentProviderName != null ? providers.FirstOrDefault(p => p.Name == VirtualContentProviderName) : null;
|
||||
|
||||
if (Path is null) throw new Exception(nameof(Path) + " can not be null.");
|
||||
if (Type is not AbsolutePathType type) throw new Exception(nameof(Type) + " can not be null.");
|
||||
return new AbsolutePath(contentProvider, Path, type, virtualContentProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/Core/FileTime.Core/Models/AbsolutePathType.cs
Normal file
9
src/Core/FileTime.Core/Models/AbsolutePathType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public enum AbsolutePathType
|
||||
{
|
||||
Unknown,
|
||||
Container,
|
||||
Element
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,6 @@ namespace FileTime.Core.Models
|
||||
public static class Constants
|
||||
{
|
||||
public const char SeparatorChar = '/';
|
||||
public const string ContentProviderProtocol = "ctp://";
|
||||
}
|
||||
}
|
||||
18
src/Core/FileTime.Core/Models/ContainerEscapeResult.cs
Normal file
18
src/Core/FileTime.Core/Models/ContainerEscapeResult.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public class ContainerEscapeResult
|
||||
{
|
||||
public bool Handled { get; }
|
||||
public IContainer? NavigateTo { get; }
|
||||
|
||||
public ContainerEscapeResult(bool handled)
|
||||
{
|
||||
Handled = handled;
|
||||
}
|
||||
|
||||
public ContainerEscapeResult(IContainer navigateTo)
|
||||
{
|
||||
NavigateTo = navigateTo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,21 +4,57 @@ namespace FileTime.Core.Models
|
||||
{
|
||||
public interface IContainer : IItem
|
||||
{
|
||||
IReadOnlyList<Exception> Exceptions { get; }
|
||||
Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default);
|
||||
Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default);
|
||||
Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default);
|
||||
bool AllowRecursiveDeletion { get; }
|
||||
bool Loading { get; }
|
||||
bool CanHandleEscape { get; }
|
||||
|
||||
Task Refresh();
|
||||
Task<IItem?> GetByPath(string path);
|
||||
Task<IContainer> CreateContainer(string name);
|
||||
Task<IElement> CreateElement(string name);
|
||||
Task RefreshAsync(CancellationToken token = default);
|
||||
public async Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false)
|
||||
{
|
||||
if (path == null) return this;
|
||||
var paths = path.Split(Constants.SeparatorChar);
|
||||
|
||||
Task<bool> IsExists(string name);
|
||||
var item = (await GetItems())?.FirstOrDefault(i => i.Name == paths[0]);
|
||||
|
||||
Task<IContainer> Clone();
|
||||
if (paths.Length == 1)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
if (item is IContainer container)
|
||||
{
|
||||
IItem? result = null;
|
||||
try
|
||||
{
|
||||
result = await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)), acceptDeepestMatch);
|
||||
}
|
||||
catch { }
|
||||
return result == null && acceptDeepestMatch ? this : result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Task<IContainer> CreateContainerAsync(string name);
|
||||
Task<IElement> CreateElementAsync(string name);
|
||||
|
||||
Task<bool> IsExistsAsync(string name);
|
||||
|
||||
Task<IContainer> CloneAsync();
|
||||
Task<bool> CanOpenAsync();
|
||||
void Unload();
|
||||
|
||||
Task<ContainerEscapeResult> HandleEscape();
|
||||
Task RunWithLoading(Func<CancellationToken, Task> func, CancellationToken token = default);
|
||||
|
||||
bool IsLoaded { get; }
|
||||
bool SupportsDirectoryLevelSoftDelete { get; }
|
||||
|
||||
AsyncEventHandler Refreshed { get; }
|
||||
AsyncEventHandler<bool> LoadingChanged { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,15 @@
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public interface IElement : IItem
|
||||
{
|
||||
bool IsSpecial { get; }
|
||||
string GetPrimaryAttributeText();
|
||||
string? GetPrimaryAttributeText();
|
||||
Task<string> GetContent(CancellationToken token = default);
|
||||
Task<long?> GetElementSize(CancellationToken token = default);
|
||||
|
||||
Task<IContentReader> GetContentReaderAsync();
|
||||
Task<IContentWriter> GetContentWriterAsync();
|
||||
}
|
||||
}
|
||||
8
src/Core/FileTime.Core/Models/IFile.cs
Normal file
8
src/Core/FileTime.Core/Models/IFile.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public interface IFile : IElement
|
||||
{
|
||||
string Attributes { get; }
|
||||
DateTime CreatedAt { get; }
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,17 @@ namespace FileTime.Core.Models
|
||||
public interface IItem
|
||||
{
|
||||
string Name { get; }
|
||||
string DisplayName { get; }
|
||||
string? FullName { get; }
|
||||
string? NativePath { get; }
|
||||
bool IsHidden { get; }
|
||||
bool CanDelete { get; }
|
||||
bool IsDestroyed { get; }
|
||||
bool IsExists { get; }
|
||||
SupportsDelete CanDelete { get; }
|
||||
bool CanRename { get; }
|
||||
IContentProvider Provider { get; }
|
||||
Task Delete();
|
||||
void Destroy();
|
||||
Task Delete(bool hardDelete = false);
|
||||
Task Rename(string newName);
|
||||
IContainer? GetParent();
|
||||
}
|
||||
|
||||
7
src/Core/FileTime.Core/Models/IItemWithSize.cs
Normal file
7
src/Core/FileTime.Core/Models/IItemWithSize.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public interface IItemWithSize : IItem
|
||||
{
|
||||
long? Size { get; }
|
||||
}
|
||||
}
|
||||
7
src/Core/FileTime.Core/Models/ISymlinkElement.cs
Normal file
7
src/Core/FileTime.Core/Models/ISymlinkElement.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public interface ISymlinkElement
|
||||
{
|
||||
IItem RealItem { get; }
|
||||
}
|
||||
}
|
||||
14
src/Core/FileTime.Core/Models/ItemNamePart.cs
Normal file
14
src/Core/FileTime.Core/Models/ItemNamePart.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public class ItemNamePart
|
||||
{
|
||||
public string Text { get; set; }
|
||||
public bool IsSpecial { get; set; }
|
||||
|
||||
public ItemNamePart(string text, bool isSpecial = false)
|
||||
{
|
||||
Text = text;
|
||||
IsSpecial = isSpecial;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/Core/FileTime.Core/Models/SupportsDelete.cs
Normal file
9
src/Core/FileTime.Core/Models/SupportsDelete.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public enum SupportsDelete
|
||||
{
|
||||
False,
|
||||
True,
|
||||
HardDeleteOnly,
|
||||
}
|
||||
}
|
||||
@@ -20,23 +20,37 @@ namespace FileTime.Core.Models
|
||||
public IReadOnlyList<IElement>? Elements { get; private set; }
|
||||
|
||||
public string Name => BaseContainer.Name;
|
||||
public string DisplayName => BaseContainer.DisplayName;
|
||||
|
||||
public string? FullName => BaseContainer.FullName;
|
||||
public string? NativePath => BaseContainer.NativePath;
|
||||
|
||||
public bool IsHidden => BaseContainer.IsHidden;
|
||||
public bool IsLoaded => BaseContainer.IsLoaded;
|
||||
public bool CanDelete => BaseContainer.CanDelete;
|
||||
public SupportsDelete CanDelete => BaseContainer.CanDelete;
|
||||
public bool CanRename => BaseContainer.CanRename;
|
||||
|
||||
public IContentProvider Provider => BaseContainer.Provider;
|
||||
public IReadOnlyList<Exception> Exceptions => BaseContainer.Exceptions;
|
||||
|
||||
public bool SupportsDirectoryLevelSoftDelete => BaseContainer.SupportsDirectoryLevelSoftDelete;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; }
|
||||
|
||||
private void RefreshAddBase(Func<object?, AsyncEventArgs, Task> handler)
|
||||
public bool IsDestroyed => BaseContainer.IsDestroyed;
|
||||
public bool IsExists => BaseContainer.IsExists;
|
||||
public bool AllowRecursiveDeletion => BaseContainer.AllowRecursiveDeletion;
|
||||
|
||||
public bool Loading => BaseContainer.Loading;
|
||||
public AsyncEventHandler<bool> LoadingChanged { get; protected set; } = new();
|
||||
|
||||
public bool CanHandleEscape => BaseContainer.CanHandleEscape;
|
||||
|
||||
private void RefreshAddBase(Func<object?, AsyncEventArgs, CancellationToken, Task> handler)
|
||||
{
|
||||
BaseContainer.Refreshed.Add(handler);
|
||||
}
|
||||
private void RefreshRemoveBase(Func<object?, AsyncEventArgs, Task> handler)
|
||||
private void RefreshRemoveBase(Func<object?, AsyncEventArgs, CancellationToken, Task> handler)
|
||||
{
|
||||
BaseContainer.Refreshed.Add(handler);
|
||||
}
|
||||
@@ -49,7 +63,7 @@ namespace FileTime.Core.Models
|
||||
bool isTransitive = false,
|
||||
string? virtualContainerName = null)
|
||||
{
|
||||
Refreshed = new (RefreshAddBase, RefreshRemoveBase);
|
||||
Refreshed = new(RefreshAddBase, RefreshRemoveBase);
|
||||
BaseContainer = baseContainer;
|
||||
_containerTransformators = containerTransformators;
|
||||
_elementTransformators = elementTransformators;
|
||||
@@ -64,10 +78,10 @@ namespace FileTime.Core.Models
|
||||
await InitItems();
|
||||
}
|
||||
|
||||
private async Task InitItems()
|
||||
private async Task InitItems(CancellationToken token = default)
|
||||
{
|
||||
Containers = _containerTransformators.Aggregate((await BaseContainer.GetContainers())?.AsEnumerable(), (a, t) => t(a!))?.ToList()?.AsReadOnly();
|
||||
Elements = _elementTransformators.Aggregate((await BaseContainer.GetElements())?.AsEnumerable(), (a, t) => t(a!))?.ToList()?.AsReadOnly();
|
||||
Containers = _containerTransformators.Aggregate((await BaseContainer.GetContainers(token))?.AsEnumerable(), (a, t) => t(a!))?.ToList()?.AsReadOnly();
|
||||
Elements = _elementTransformators.Aggregate((await BaseContainer.GetElements(token))?.AsEnumerable(), (a, t) => t(a!))?.ToList()?.AsReadOnly();
|
||||
|
||||
Items = (Elements != null
|
||||
? Containers?.Cast<IItem>().Concat(Elements)
|
||||
@@ -75,14 +89,14 @@ namespace FileTime.Core.Models
|
||||
?.ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
public async Task<IItem?> GetByPath(string path) => await BaseContainer.GetByPath(path);
|
||||
public async Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false) => await BaseContainer.GetByPath(path, acceptDeepestMatch);
|
||||
|
||||
public IContainer? GetParent() => BaseContainer.GetParent();
|
||||
|
||||
public async Task Refresh()
|
||||
public async Task RefreshAsync(CancellationToken token = default)
|
||||
{
|
||||
await BaseContainer.Refresh();
|
||||
await InitItems();
|
||||
await BaseContainer.RefreshAsync(token);
|
||||
await InitItems(token);
|
||||
}
|
||||
|
||||
public IContainer GetRealContainer() =>
|
||||
@@ -133,9 +147,9 @@ namespace FileTime.Core.Models
|
||||
: baseContainer;
|
||||
}
|
||||
|
||||
public async Task<IContainer> CreateContainer(string name) => await BaseContainer.CreateContainer(name);
|
||||
public async Task<IElement> CreateElement(string name) => await BaseContainer.CreateElement(name);
|
||||
public async Task<bool> IsExists(string name) => await BaseContainer.IsExists(name);
|
||||
public async Task<IContainer> CreateContainerAsync(string name) => await BaseContainer.CreateContainerAsync(name);
|
||||
public async Task<IElement> CreateElementAsync(string name) => await BaseContainer.CreateElementAsync(name);
|
||||
public async Task<bool> IsExistsAsync(string name) => await BaseContainer.IsExistsAsync(name);
|
||||
|
||||
public Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||
{
|
||||
@@ -150,11 +164,11 @@ namespace FileTime.Core.Models
|
||||
return Task.FromResult(Elements);
|
||||
}
|
||||
|
||||
public async Task Delete() => await BaseContainer.Delete();
|
||||
public async Task<IContainer> Clone()
|
||||
public async Task Delete(bool hardDelete = false) => await BaseContainer.Delete();
|
||||
public async Task<IContainer> CloneAsync()
|
||||
{
|
||||
return new VirtualContainer(
|
||||
await BaseContainer.Clone(),
|
||||
await BaseContainer.CloneAsync(),
|
||||
_containerTransformators,
|
||||
_elementTransformators,
|
||||
IsPermanent,
|
||||
@@ -164,5 +178,19 @@ namespace FileTime.Core.Models
|
||||
}
|
||||
|
||||
public async Task Rename(string newName) => await BaseContainer.Rename(newName);
|
||||
public async Task<bool> CanOpenAsync() => await BaseContainer.CanOpenAsync();
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
BaseContainer.Destroy();
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
BaseContainer.Unload();
|
||||
}
|
||||
public async Task<ContainerEscapeResult> HandleEscape() => await BaseContainer.HandleEscape();
|
||||
|
||||
public async Task RunWithLoading(Func<CancellationToken, Task> func, CancellationToken token = default) => await BaseContainer.RunWithLoading(func, token);
|
||||
}
|
||||
}
|
||||
12
src/Core/FileTime.Core/Persistence/PersistenceSettings.cs
Normal file
12
src/Core/FileTime.Core/Persistence/PersistenceSettings.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace FileTime.Core.Persistence
|
||||
{
|
||||
public class PersistenceSettings
|
||||
{
|
||||
public PersistenceSettings(string rootAppDataPath)
|
||||
{
|
||||
RootAppDataPath = rootAppDataPath;
|
||||
}
|
||||
|
||||
public string RootAppDataPath { get; }
|
||||
}
|
||||
}
|
||||
180
src/Core/FileTime.Core/Providers/AbstractContainer.cs
Normal file
180
src/Core/FileTime.Core/Providers/AbstractContainer.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Providers
|
||||
{
|
||||
public abstract class AbstractContainer<TProvider> : IContainer where TProvider : class, IContentProvider
|
||||
{
|
||||
private readonly IContainer? _parent;
|
||||
private readonly List<Exception> _exceptions = new();
|
||||
private IReadOnlyList<IContainer>? _containers;
|
||||
private IReadOnlyList<IItem>? _items;
|
||||
private IReadOnlyList<IElement>? _elements;
|
||||
|
||||
public IReadOnlyList<Exception> Exceptions { get; }
|
||||
|
||||
public bool IsLoaded { get; protected set; }
|
||||
|
||||
public bool SupportsDirectoryLevelSoftDelete { get; protected set; }
|
||||
|
||||
public AsyncEventHandler Refreshed { get; protected set; } = new();
|
||||
public AsyncEventHandler<bool> LoadingChanged { get; protected set; } = new();
|
||||
|
||||
public string Name { get; protected set; }
|
||||
public virtual string DisplayName { get; protected set; }
|
||||
|
||||
public string? FullName { get; protected set; }
|
||||
|
||||
public string? NativePath { get; protected set; }
|
||||
|
||||
public virtual bool IsHidden { get; protected set; }
|
||||
|
||||
public bool IsDestroyed { get; protected set; }
|
||||
|
||||
public virtual SupportsDelete CanDelete { get; protected set; }
|
||||
|
||||
public virtual bool CanRename { get; protected set; }
|
||||
|
||||
public TProvider Provider { get; }
|
||||
|
||||
IContentProvider IItem.Provider => Provider;
|
||||
|
||||
public abstract bool IsExists { get; }
|
||||
|
||||
public virtual bool AllowRecursiveDeletion { get; protected set; }
|
||||
|
||||
public bool Loading { get; protected set; }
|
||||
|
||||
public bool CanHandleEscape { get; protected set; }
|
||||
|
||||
protected AbstractContainer(TProvider provider, IContainer parent, string name) : this(name)
|
||||
{
|
||||
_parent = parent;
|
||||
Provider = provider;
|
||||
FullName = parent.FullName == null ? name : parent.FullName + Constants.SeparatorChar + name;
|
||||
}
|
||||
|
||||
protected AbstractContainer(string name, string? fullName = null) : this(name)
|
||||
{
|
||||
Provider = this is TProvider provider ? provider : throw new ArgumentException($"This constructor is for {nameof(IContentProvider)}s only");
|
||||
FullName = fullName;
|
||||
}
|
||||
|
||||
private AbstractContainer(string name)
|
||||
{
|
||||
DisplayName = Name = name;
|
||||
Exceptions = _exceptions.AsReadOnly();
|
||||
Provider = null!;
|
||||
AllowRecursiveDeletion = true;
|
||||
}
|
||||
|
||||
public virtual Task<bool> CanOpenAsync() => Task.FromResult(_exceptions.Count == 0);
|
||||
|
||||
public abstract Task<IContainer> CloneAsync();
|
||||
|
||||
public abstract Task<IContainer> CreateContainerAsync(string name);
|
||||
|
||||
public abstract Task<IElement> CreateElementAsync(string name);
|
||||
|
||||
public abstract Task Delete(bool hardDelete = false);
|
||||
|
||||
public virtual void Destroy()
|
||||
{
|
||||
_items = null;
|
||||
_containers = null;
|
||||
_elements = null;
|
||||
IsLoaded = false;
|
||||
IsDestroyed = true;
|
||||
Refreshed = new AsyncEventHandler();
|
||||
}
|
||||
|
||||
public virtual async Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default)
|
||||
{
|
||||
if (_containers == null) await RefreshAsync(token);
|
||||
return _containers;
|
||||
}
|
||||
|
||||
public virtual async Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default)
|
||||
{
|
||||
if (_elements == null) await RefreshAsync(token);
|
||||
return _elements;
|
||||
}
|
||||
|
||||
public virtual async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||
{
|
||||
if (_items == null) await RefreshAsync(token);
|
||||
return _items;
|
||||
}
|
||||
|
||||
public async Task RunWithLoading(Func<CancellationToken, Task> func, CancellationToken token = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Loading = true;
|
||||
await LoadingChanged.InvokeAsync(this, Loading, token);
|
||||
await func(token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Loading = false;
|
||||
await LoadingChanged.InvokeAsync(this, Loading, token);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IContainer? GetParent() => _parent;
|
||||
|
||||
public virtual async Task<bool> IsExistsAsync(string name)
|
||||
{
|
||||
var items = await GetItems();
|
||||
return items?.Any(i => i.Name == name) ?? false;
|
||||
}
|
||||
|
||||
public virtual async Task RefreshAsync(CancellationToken token = default)
|
||||
{
|
||||
_exceptions.Clear();
|
||||
var containers = new List<IContainer>();
|
||||
var elements = new List<IElement>();
|
||||
foreach (var item in await RefreshItems(token))
|
||||
{
|
||||
if (item is IContainer container)
|
||||
{
|
||||
containers.Add(container);
|
||||
}
|
||||
else if (item is IElement element)
|
||||
{
|
||||
elements.Add(element);
|
||||
}
|
||||
}
|
||||
|
||||
if (_items != null)
|
||||
{
|
||||
foreach (var item in _items)
|
||||
{
|
||||
item.Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
_containers = containers.OrderBy(c => c.Name).ToList().AsReadOnly();
|
||||
_elements = elements.OrderBy(e => e.Name).ToList().AsReadOnly();
|
||||
_items = _containers.Cast<IItem>().Concat(_elements).ToList().AsReadOnly();
|
||||
IsLoaded = true;
|
||||
if (Refreshed != null) await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
}
|
||||
|
||||
public abstract Task<IEnumerable<IItem>> RefreshItems(CancellationToken token = default);
|
||||
|
||||
public abstract Task Rename(string newName);
|
||||
|
||||
public virtual void Unload()
|
||||
{
|
||||
_items = null;
|
||||
_containers = null;
|
||||
_elements = null;
|
||||
IsLoaded = false;
|
||||
}
|
||||
|
||||
protected void AddException(Exception e) => _exceptions.Add(e);
|
||||
|
||||
public virtual Task<ContainerEscapeResult> HandleEscape() => Task.FromResult(new ContainerEscapeResult(false));
|
||||
}
|
||||
}
|
||||
58
src/Core/FileTime.Core/Providers/AbstractElement.cs
Normal file
58
src/Core/FileTime.Core/Providers/AbstractElement.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Providers
|
||||
{
|
||||
public abstract class AbstractElement<TProvider> : IElement where TProvider : class, IContentProvider
|
||||
{
|
||||
private readonly IContainer _parent;
|
||||
public bool IsSpecial { get; protected set; }
|
||||
|
||||
public string Name { get; protected set; }
|
||||
|
||||
public string DisplayName { get; protected set; }
|
||||
|
||||
public string? FullName { get; protected set; }
|
||||
|
||||
public string? NativePath { get; protected set; }
|
||||
|
||||
public bool IsHidden { get; protected set; }
|
||||
|
||||
public bool IsDestroyed { get; protected set; }
|
||||
|
||||
public virtual bool IsExists { get; protected set; }
|
||||
|
||||
public virtual SupportsDelete CanDelete { get; protected set; }
|
||||
|
||||
public virtual bool CanRename { get; protected set; }
|
||||
|
||||
public TProvider Provider { get; }
|
||||
|
||||
IContentProvider IItem.Provider => Provider;
|
||||
|
||||
protected AbstractElement(TProvider provider, IContainer parent, string name)
|
||||
{
|
||||
_parent = parent;
|
||||
Provider = provider;
|
||||
DisplayName = Name = name;
|
||||
FullName = parent.FullName + Constants.SeparatorChar + name;
|
||||
IsExists = true;
|
||||
}
|
||||
|
||||
public abstract Task Delete(bool hardDelete = false);
|
||||
|
||||
public virtual void Destroy() { }
|
||||
|
||||
public abstract Task<string> GetContent(CancellationToken token = default);
|
||||
|
||||
public abstract Task<IContentReader> GetContentReaderAsync();
|
||||
|
||||
public abstract Task<IContentWriter> GetContentWriterAsync();
|
||||
|
||||
public abstract Task<long?> GetElementSize(CancellationToken token = default);
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
public abstract string? GetPrimaryAttributeText();
|
||||
|
||||
public abstract Task Rename(string newName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace FileTime.Core.Providers.ContainerProperty
|
||||
{
|
||||
public interface IHaveAttributes
|
||||
{
|
||||
string Attributes { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace FileTime.Core.Providers.ContainerProperty
|
||||
{
|
||||
public interface IHaveCreatedAt
|
||||
{
|
||||
DateTime? CreatedAt { get; }
|
||||
}
|
||||
}
|
||||
146
src/Core/FileTime.Core/Providers/ContentProviderBase.cs
Normal file
146
src/Core/FileTime.Core/Providers/ContentProviderBase.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Providers
|
||||
{
|
||||
public abstract class ContentProviderBase<T> : AbstractContainer<T>, IContentProvider
|
||||
where T : class, IContentProvider
|
||||
{
|
||||
private readonly object _initializationGuard = new();
|
||||
protected IReadOnlyList<IContainer> _rootContainers;
|
||||
private bool _initialized;
|
||||
private bool _initializing;
|
||||
private IContainer? _parent;
|
||||
protected List<IContainer>? RootContainers { get; private set; }
|
||||
public override bool IsExists => true;
|
||||
|
||||
public virtual bool SupportsContentStreams { get; }
|
||||
|
||||
public virtual string Protocol { get; }
|
||||
|
||||
protected ContentProviderBase(
|
||||
string name,
|
||||
string protocol,
|
||||
bool supportsContentStreams,
|
||||
string? fullName = null)
|
||||
: base(name, fullName)
|
||||
{
|
||||
Protocol = protocol;
|
||||
SupportsContentStreams = supportsContentStreams;
|
||||
RootContainers = new List<IContainer>();
|
||||
_rootContainers = RootContainers.AsReadOnly();
|
||||
AllowRecursiveDeletion = false;
|
||||
|
||||
CanRename = false;
|
||||
CanDelete = SupportsDelete.False;
|
||||
}
|
||||
|
||||
public virtual Task<bool> CanHandlePath(string path) => Task.FromResult(path.StartsWith(Protocol));
|
||||
public override IContainer? GetParent() => _parent;
|
||||
|
||||
public void SetParent(IContainer parent) => _parent = parent;
|
||||
|
||||
protected async Task AddRootContainer(IContainer newRootContainer)
|
||||
{
|
||||
RootContainers =
|
||||
(await GetContainers())?.Append(newRootContainer).OrderBy(c => c.Name).ToList()
|
||||
?? new List<IContainer>() { newRootContainer };
|
||||
|
||||
_rootContainers = RootContainers.AsReadOnly();
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
protected async Task AddRootContainers(IEnumerable<IContainer> newRootContainers)
|
||||
{
|
||||
RootContainers =
|
||||
(await GetContainers())?.Concat(newRootContainers).OrderBy(c => c.Name).ToList()
|
||||
?? new List<IContainer>(newRootContainers);
|
||||
|
||||
_rootContainers = RootContainers.AsReadOnly();
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
protected async Task SetRootContainers(IEnumerable<IContainer> newRootContainers)
|
||||
{
|
||||
RootContainers = newRootContainers.OrderBy(c => c.Name).ToList();
|
||||
_rootContainers = RootContainers.AsReadOnly();
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
protected void ClearRootContainers()
|
||||
{
|
||||
RootContainers = new List<IContainer>();
|
||||
_rootContainers = RootContainers.AsReadOnly();
|
||||
}
|
||||
|
||||
public override async Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default)
|
||||
{
|
||||
await InitIfNeeded();
|
||||
return RootContainers;
|
||||
}
|
||||
|
||||
public override async Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default)
|
||||
{
|
||||
await InitIfNeeded();
|
||||
return new List<IElement>();
|
||||
}
|
||||
|
||||
public override async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||
{
|
||||
await InitIfNeeded();
|
||||
return RootContainers;
|
||||
}
|
||||
|
||||
private async Task InitIfNeeded()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
lock (_initializationGuard)
|
||||
{
|
||||
if (_initialized) return;
|
||||
if (!_initializing)
|
||||
{
|
||||
_initializing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
await Task.Delay(1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_initialized) return;
|
||||
await Init();
|
||||
_initialized = true;
|
||||
IsLoaded = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_initializationGuard)
|
||||
{
|
||||
_initializing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Task Init()
|
||||
{
|
||||
if (RootContainers == null) ClearRootContainers();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<IItem>> RefreshItems(CancellationToken token = default) { throw new NotImplementedException($"{nameof(RefreshItems)} should not be called in {nameof(ContentProviderBase<T>)}."); }
|
||||
|
||||
public override Task<bool> IsExistsAsync(string name) => Task.FromResult(RootContainers?.Any(i => i.Name == name) ?? false);
|
||||
|
||||
public override async Task RefreshAsync(CancellationToken token = default) => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
public override Task<IContainer> CloneAsync() => Task.FromResult((IContainer)this);
|
||||
public override Task Delete(bool hardDelete = false) => throw new NotSupportedException();
|
||||
public override Task Rename(string newName) => throw new NotSupportedException();
|
||||
public override Task<bool> CanOpenAsync() => Task.FromResult(true);
|
||||
|
||||
public override void Unload() { }
|
||||
|
||||
public override void Destroy() { }
|
||||
}
|
||||
}
|
||||
76
src/Core/FileTime.Core/Providers/ContentProviderStream.cs
Normal file
76
src/Core/FileTime.Core/Providers/ContentProviderStream.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
namespace FileTime.Core.Providers
|
||||
{
|
||||
public class ContentProviderStream : Stream
|
||||
{
|
||||
private readonly IContentReader? _contentReader;
|
||||
private readonly IContentWriter? _contentWriter;
|
||||
public override bool CanRead => _contentReader != null;
|
||||
|
||||
public override bool CanSeek => _contentReader != null;
|
||||
|
||||
public override bool CanWrite => _contentWriter != null;
|
||||
|
||||
public override long Length => throw new NotImplementedException();
|
||||
|
||||
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||
|
||||
public ContentProviderStream(IContentReader contentReader)
|
||||
{
|
||||
_contentReader = contentReader;
|
||||
}
|
||||
|
||||
public ContentProviderStream(IContentWriter contentWriter)
|
||||
{
|
||||
_contentWriter = contentWriter;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
if (_contentWriter == null) throw new NotSupportedException();
|
||||
Task.Run(async () => await _contentWriter.FlushAsync()).Wait();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_contentReader == null) throw new IOException("This stream is not readable");
|
||||
var dataTask = Task.Run(async () => await _contentReader.ReadBytesAsync(count, offset));
|
||||
dataTask.Wait();
|
||||
var data = dataTask.Result;
|
||||
|
||||
if (data.Length > count) throw new Exception("More bytes has been read than requested");
|
||||
Array.Copy(data, buffer, data.Length);
|
||||
return data.Length;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
if (_contentReader == null) throw new NotSupportedException();
|
||||
|
||||
var newPosition = origin switch
|
||||
{
|
||||
SeekOrigin.Begin => offset,
|
||||
SeekOrigin.Current => _contentReader.Position ?? 0 + offset,
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
_contentReader.SetPosition(newPosition);
|
||||
return newPosition;
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_contentWriter == null) throw new NotSupportedException();
|
||||
var data = buffer;
|
||||
if (buffer.Length != count)
|
||||
{
|
||||
data = new byte[count];
|
||||
Array.Copy(buffer, data, count);
|
||||
}
|
||||
Task.Run(async () => await _contentWriter.WriteBytesAsync(data, offset)).Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,10 @@ namespace FileTime.Core.Providers
|
||||
{
|
||||
public interface IContentProvider : IContainer
|
||||
{
|
||||
Task<IReadOnlyList<IContainer>> GetRootContainers(CancellationToken token = default);
|
||||
bool SupportsContentStreams { get; }
|
||||
string Protocol { get; }
|
||||
|
||||
bool CanHandlePath(string path);
|
||||
Task<bool> CanHandlePath(string path);
|
||||
|
||||
void SetParent(IContainer container);
|
||||
}
|
||||
|
||||
11
src/Core/FileTime.Core/Providers/IContentReader.cs
Normal file
11
src/Core/FileTime.Core/Providers/IContentReader.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace FileTime.Core.Providers
|
||||
{
|
||||
public interface IContentReader : IDisposable
|
||||
{
|
||||
int PreferredBufferSize { get; }
|
||||
long? Position { get; }
|
||||
|
||||
Task<byte[]> ReadBytesAsync(int bufferSize, int? offset = null);
|
||||
void SetPosition(long position);
|
||||
}
|
||||
}
|
||||
10
src/Core/FileTime.Core/Providers/IContentWriter.cs
Normal file
10
src/Core/FileTime.Core/Providers/IContentWriter.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace FileTime.Core.Providers
|
||||
{
|
||||
public interface IContentWriter : IDisposable
|
||||
{
|
||||
int PreferredBufferSize { get; }
|
||||
|
||||
Task WriteBytesAsync(byte[] data, int? index = null);
|
||||
Task FlushAsync();
|
||||
}
|
||||
}
|
||||
59
src/Core/FileTime.Core/Providers/LazyLoadingContainer.cs
Normal file
59
src/Core/FileTime.Core/Providers/LazyLoadingContainer.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Providers
|
||||
{
|
||||
public abstract class LazyLoadingContainer<TProvider, TContainer, TElement> : AbstractContainer<TProvider>
|
||||
where TProvider : class, IContentProvider
|
||||
where TContainer : class, IContainer
|
||||
where TElement : class, IElement
|
||||
{
|
||||
protected List<TContainer> Containers { get; }
|
||||
private IReadOnlyList<IItem> _items;
|
||||
protected List<TElement> Elements { get; }
|
||||
|
||||
private readonly IReadOnlyList<IContainer> _containersReadOnly;
|
||||
private readonly IReadOnlyList<IElement> _elementsReadOnly;
|
||||
|
||||
protected LazyLoadingContainer(TProvider provider, IContainer parent, string name) : base(provider, parent, name)
|
||||
{
|
||||
Containers = new List<TContainer>();
|
||||
Elements = new List<TElement>();
|
||||
|
||||
_containersReadOnly = Containers.AsReadOnly();
|
||||
_elementsReadOnly = Elements.AsReadOnly();
|
||||
_items = Containers.Cast<IItem>().Concat(Elements).ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
public virtual async Task AddContainerAsync(TContainer container)
|
||||
{
|
||||
Containers.Add(container);
|
||||
await UpdateChildren();
|
||||
}
|
||||
|
||||
public virtual async Task AddElementAsync(TElement element)
|
||||
{
|
||||
Elements.Add(element);
|
||||
await UpdateChildren();
|
||||
}
|
||||
|
||||
private async Task UpdateChildren()
|
||||
{
|
||||
_items = Containers.Cast<IItem>().Concat(Elements).ToList().AsReadOnly();
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
public override async Task RefreshAsync(CancellationToken token = default)
|
||||
{
|
||||
if (Refreshed != null) await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<IItem>> RefreshItems(CancellationToken token = default) => Task.FromResult(Enumerable.Empty<IItem>());
|
||||
|
||||
public override Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default) => Task.FromResult((IReadOnlyList<IContainer>?)_containersReadOnly);
|
||||
|
||||
public override Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult((IReadOnlyList<IElement>?)_elementsReadOnly);
|
||||
|
||||
public override Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default) => Task.FromResult((IReadOnlyList<IItem>?)_items);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
|
||||
using System;
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
@@ -14,9 +12,11 @@ namespace FileTime.Core.Providers
|
||||
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
public string Name => null;
|
||||
public string DisplayName => null;
|
||||
#pragma warning restore CS8603 // Possible null reference return.
|
||||
|
||||
public string? FullName => null;
|
||||
public string? NativePath => null;
|
||||
|
||||
public bool IsHidden => false;
|
||||
public bool IsLoaded => true;
|
||||
@@ -24,11 +24,22 @@ namespace FileTime.Core.Providers
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
public IContentProvider Provider => null;
|
||||
#pragma warning restore CS8603 // Possible null reference return.
|
||||
public bool CanDelete => false;
|
||||
public SupportsDelete CanDelete => SupportsDelete.False;
|
||||
public bool CanRename => false;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
|
||||
public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly();
|
||||
|
||||
public bool SupportsDirectoryLevelSoftDelete => false;
|
||||
|
||||
public bool IsDestroyed => false;
|
||||
public bool IsExists => true;
|
||||
public bool AllowRecursiveDeletion => false;
|
||||
public bool Loading => false;
|
||||
public bool CanHandleEscape => false;
|
||||
public AsyncEventHandler<bool> LoadingChanged { get; protected set; } = new();
|
||||
|
||||
public TopContainer(IEnumerable<IContentProvider> contentProviders)
|
||||
{
|
||||
_contentProviders = new List<IContentProvider>(contentProviders);
|
||||
@@ -41,26 +52,38 @@ namespace FileTime.Core.Providers
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IContainer> CreateContainer(string name) => throw new NotImplementedException();
|
||||
public Task<IContainer> CreateContainerAsync(string name) => throw new NotImplementedException();
|
||||
|
||||
public Task<IElement> CreateElement(string name) => throw new NotImplementedException();
|
||||
public Task<IElement> CreateElementAsync(string name) => throw new NotImplementedException();
|
||||
|
||||
public Task Delete() => throw new NotImplementedException();
|
||||
public Task Delete(bool hardDelete = false) => throw new NotImplementedException();
|
||||
|
||||
public Task<IItem?> GetByPath(string path) => throw new NotImplementedException();
|
||||
public Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false) => throw new NotImplementedException();
|
||||
|
||||
public IContainer? GetParent() => null;
|
||||
|
||||
public Task<bool> IsExists(string name) => throw new NotImplementedException();
|
||||
public Task<bool> IsExistsAsync(string name) => throw new NotImplementedException();
|
||||
|
||||
public async Task Refresh() => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
public async Task RefreshAsync(CancellationToken token = default) => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
|
||||
public Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default) => Task.FromResult(_items);
|
||||
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default) => Task.FromResult(_containers);
|
||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult(_elements);
|
||||
|
||||
public Task<IContainer> Clone() => Task.FromResult((IContainer)this);
|
||||
public Task<IContainer> CloneAsync() => Task.FromResult((IContainer)this);
|
||||
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
|
||||
public Task<bool> CanOpenAsync() => Task.FromResult(true);
|
||||
|
||||
public void Destroy() { }
|
||||
|
||||
public void Unload() { }
|
||||
public Task<ContainerEscapeResult> HandleEscape() => Task.FromResult(new ContainerEscapeResult(false));
|
||||
|
||||
public async Task RunWithLoading(Func<CancellationToken, Task> func, CancellationToken token = default)
|
||||
{
|
||||
await func(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/Core/FileTime.Core/Search/ChildSearchContainer.cs
Normal file
40
src/Core/FileTime.Core/Search/ChildSearchContainer.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Search
|
||||
{
|
||||
public class ChildSearchContainer : AbstractContainer<IContentProvider>
|
||||
{
|
||||
public ChildSearchContainer(SearchContainer searchContainer, IContentProvider provider, IContainer baseContainer, string name, string displayName, List<ItemNamePart> searchDisplayName) : base(provider, baseContainer.GetParent()!, name)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
SearchContainer = searchContainer;
|
||||
SearchDisplayName = searchDisplayName;
|
||||
BaseContainer = baseContainer;
|
||||
}
|
||||
|
||||
public override bool IsExists => true;
|
||||
|
||||
public SearchContainer SearchContainer { get; }
|
||||
public List<ItemNamePart> SearchDisplayName { get; }
|
||||
public IContainer BaseContainer { get; }
|
||||
|
||||
public override async Task RefreshAsync(CancellationToken token = default)
|
||||
{
|
||||
if (Refreshed != null) await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
}
|
||||
|
||||
public override Task<IContainer> CloneAsync() => throw new NotImplementedException();
|
||||
|
||||
public override Task<IContainer> CreateContainerAsync(string name) => throw new NotSupportedException();
|
||||
|
||||
public override Task<IElement> CreateElementAsync(string name) => throw new NotSupportedException();
|
||||
|
||||
public override Task Delete(bool hardDelete = false) => throw new NotSupportedException();
|
||||
|
||||
public override Task<IEnumerable<IItem>> RefreshItems(CancellationToken token = default) => throw new NotImplementedException();
|
||||
|
||||
public override Task Rename(string newName) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
36
src/Core/FileTime.Core/Search/ChildSearchElement.cs
Normal file
36
src/Core/FileTime.Core/Search/ChildSearchElement.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Search
|
||||
{
|
||||
public class ChildSearchElement : AbstractElement<IContentProvider>
|
||||
{
|
||||
public IElement BaseElement { get; }
|
||||
|
||||
public ChildSearchElement(SearchContainer searchContainer, IContentProvider provider, IContainer parent, IElement baseElement, string name, List<ItemNamePart> searchDisplayName) : base(provider, parent, name)
|
||||
{
|
||||
DisplayName = baseElement.DisplayName;
|
||||
NativePath = FullName;
|
||||
SearchContainer = searchContainer;
|
||||
SearchDisplayName = searchDisplayName;
|
||||
BaseElement = baseElement;
|
||||
}
|
||||
|
||||
public SearchContainer SearchContainer { get; }
|
||||
public List<ItemNamePart> SearchDisplayName { get; }
|
||||
|
||||
public override Task Delete(bool hardDelete = false) => throw new NotSupportedException();
|
||||
|
||||
public override Task<string> GetContent(CancellationToken token = default) => throw new NotSupportedException();
|
||||
|
||||
public override Task<IContentReader> GetContentReaderAsync() => throw new NotSupportedException();
|
||||
|
||||
public override Task<IContentWriter> GetContentWriterAsync() => throw new NotSupportedException();
|
||||
|
||||
public override async Task<long?> GetElementSize(CancellationToken token = default) => await BaseElement.GetElementSize(token);
|
||||
|
||||
public override string? GetPrimaryAttributeText() => BaseElement.GetPrimaryAttributeText();
|
||||
|
||||
public override Task Rename(string newName) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
17
src/Core/FileTime.Core/Search/NameRegexSearchTask.cs
Normal file
17
src/Core/FileTime.Core/Search/NameRegexSearchTask.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Search
|
||||
{
|
||||
public class NameRegexSearchTask : SearchTaskBase
|
||||
{
|
||||
private readonly Regex _nameRegex;
|
||||
|
||||
public NameRegexSearchTask(string namePattern, IContainer searchBaseContainer) : base(searchBaseContainer)
|
||||
{
|
||||
_nameRegex = new Regex(namePattern);
|
||||
}
|
||||
|
||||
protected override Task<bool> IsItemMatch(IItem item) => Task.FromResult(_nameRegex.IsMatch(item.Name));
|
||||
}
|
||||
}
|
||||
21
src/Core/FileTime.Core/Search/NameSearchTask.cs
Normal file
21
src/Core/FileTime.Core/Search/NameSearchTask.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Services;
|
||||
|
||||
namespace FileTime.Core.Search
|
||||
{
|
||||
public class NameSearchTask : SearchTaskBase
|
||||
{
|
||||
private readonly string _name;
|
||||
private readonly ItemNameConverterService _itemNameConverterService;
|
||||
|
||||
public NameSearchTask(string name, IContainer searchBaseContainer, ItemNameConverterService itemNameConverterService) : base(searchBaseContainer)
|
||||
{
|
||||
_name = name;
|
||||
_itemNameConverterService = itemNameConverterService;
|
||||
}
|
||||
|
||||
protected override Task<bool> IsItemMatch(IItem item) => Task.FromResult(item.Name.Contains(_name));
|
||||
|
||||
public override List<ItemNamePart> GetDisplayName(IItem item) => _itemNameConverterService.GetDisplayName(item.DisplayName, _name);
|
||||
}
|
||||
}
|
||||
102
src/Core/FileTime.Core/Search/SearchContainer.cs
Normal file
102
src/Core/FileTime.Core/Search/SearchContainer.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Search
|
||||
{
|
||||
public class SearchContainer : AbstractContainer<IContentProvider>
|
||||
{
|
||||
private int _childContainerCounter;
|
||||
private int _childElementCounter;
|
||||
private readonly List<IContainer> _containers;
|
||||
private IReadOnlyList<IItem> _items;
|
||||
private readonly List<IElement> _elements;
|
||||
|
||||
private readonly IReadOnlyList<IContainer> _containersReadOnly;
|
||||
private readonly IReadOnlyList<IElement> _elementsReadOnly;
|
||||
public SearchTaskBase SearchTaskBase { get; }
|
||||
|
||||
public IContainer SearchBaseContainer { get; }
|
||||
public override bool IsExists => throw new NotImplementedException();
|
||||
|
||||
public SearchContainer(IContainer searchBaseContainer, SearchTaskBase searchTaskBase) : base(searchBaseContainer.Provider, searchBaseContainer.GetParent()!, searchBaseContainer.Name)
|
||||
{
|
||||
SearchBaseContainer = searchBaseContainer;
|
||||
SearchTaskBase = searchTaskBase;
|
||||
_containers = new List<IContainer>();
|
||||
_elements = new List<IElement>();
|
||||
|
||||
_containersReadOnly = _containers.AsReadOnly();
|
||||
_elementsReadOnly = _elements.AsReadOnly();
|
||||
_items = _containers.Cast<IItem>().Concat(_elements).ToList().AsReadOnly();
|
||||
|
||||
CanHandleEscape = true;
|
||||
CanDelete = SupportsDelete.False;
|
||||
}
|
||||
|
||||
public async Task RunWithLazyLoading(Func<CancellationToken, Task> func, CancellationToken token = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Loading = true;
|
||||
await LoadingChanged.InvokeAsync(this, Loading, token);
|
||||
await func(token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Loading = false;
|
||||
await LoadingChanged.InvokeAsync(this, Loading, token);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddContainer(IContainer container)
|
||||
{
|
||||
var childContainer = new ChildSearchContainer(this, Provider, container, "container" + _childContainerCounter++, container.DisplayName, SearchTaskBase.GetDisplayName(container));
|
||||
_containers.Add(childContainer);
|
||||
await UpdateChildren();
|
||||
}
|
||||
|
||||
public async Task AddElement(IElement element)
|
||||
{
|
||||
var childElement = new ChildSearchElement(this, Provider, element.GetParent()!, element, "element" + _childElementCounter++, SearchTaskBase.GetDisplayName(element));
|
||||
_elements.Add(childElement);
|
||||
await UpdateChildren();
|
||||
}
|
||||
|
||||
private async Task UpdateChildren()
|
||||
{
|
||||
_items = _containers.Cast<IItem>().Concat(_elements).ToList().AsReadOnly();
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
public override async Task RefreshAsync(CancellationToken token = default)
|
||||
{
|
||||
if (Refreshed != null) await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
}
|
||||
|
||||
public override Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default) => Task.FromResult((IReadOnlyList<IContainer>?)_containersReadOnly);
|
||||
|
||||
public override Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult((IReadOnlyList<IElement>?)_elementsReadOnly);
|
||||
|
||||
public override Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default) => Task.FromResult((IReadOnlyList<IItem>?)_items);
|
||||
|
||||
public override Task<IContainer> CloneAsync() => Task.FromResult((IContainer)this);
|
||||
|
||||
public override Task<IContainer> CreateContainerAsync(string name) => throw new NotSupportedException();
|
||||
|
||||
public override Task<IElement> CreateElementAsync(string name) => throw new NotSupportedException();
|
||||
|
||||
public override Task Delete(bool hardDelete = false) => throw new NotSupportedException();
|
||||
|
||||
public override Task<IEnumerable<IItem>> RefreshItems(CancellationToken token = default) => throw new NotImplementedException();
|
||||
|
||||
public override Task Rename(string newName) => throw new NotSupportedException();
|
||||
|
||||
public override Task<ContainerEscapeResult> HandleEscape()
|
||||
{
|
||||
if (SearchTaskBase.Cancel()) return Task.FromResult(new ContainerEscapeResult(true));
|
||||
|
||||
return Task.FromResult(new ContainerEscapeResult(SearchBaseContainer));
|
||||
}
|
||||
}
|
||||
}
|
||||
127
src/Core/FileTime.Core/Search/SearchTaskBase.cs
Normal file
127
src/Core/FileTime.Core/Search/SearchTaskBase.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Search
|
||||
{
|
||||
public abstract class SearchTaskBase
|
||||
{
|
||||
private readonly object _searchGuard = new();
|
||||
private readonly IContainer _baseContainer;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
public SearchContainer TargetContainer { get; }
|
||||
public bool Searching { get; private set; }
|
||||
|
||||
protected SearchTaskBase(IContainer searchBaseContainer)
|
||||
{
|
||||
TargetContainer = new SearchContainer(searchBaseContainer, this);
|
||||
_baseContainer = searchBaseContainer;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
lock (_searchGuard)
|
||||
{
|
||||
if (Searching) return;
|
||||
Searching = true;
|
||||
try
|
||||
{
|
||||
_cancellationTokenSource?.Cancel();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
new Thread(BootstrapSearch).Start();
|
||||
|
||||
void BootstrapSearch()
|
||||
{
|
||||
try
|
||||
{
|
||||
Task.Run(Search).Wait();
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_searchGuard)
|
||||
{
|
||||
Searching = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Search()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (_cancellationTokenSource = new CancellationTokenSource())
|
||||
{
|
||||
await TargetContainer.RunWithLazyLoading(async (token) => await TraverseTree(_baseContainer, token), _cancellationTokenSource.Token);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TraverseTree(IContainer container, CancellationToken token = default)
|
||||
{
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
await container.RefreshAsync(token);
|
||||
if (await IsItemMatch(container))
|
||||
{
|
||||
await AddContainer(container);
|
||||
}
|
||||
var childElements = await container.GetElements(token);
|
||||
var childContainers = await container.GetContainers(token);
|
||||
|
||||
if (childElements != null)
|
||||
{
|
||||
await foreach (var childElement in
|
||||
childElements
|
||||
.ToAsyncEnumerable()
|
||||
.WhereAwait(async e => await IsItemMatch(e))
|
||||
)
|
||||
{
|
||||
if (token.IsCancellationRequested) return;
|
||||
await AddElement(childElement);
|
||||
}
|
||||
}
|
||||
|
||||
if (childContainers != null)
|
||||
{
|
||||
foreach (var childContainer in childContainers)
|
||||
{
|
||||
await TraverseTree(childContainer, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddContainer(IContainer container)
|
||||
{
|
||||
await TargetContainer.AddContainer(container);
|
||||
}
|
||||
|
||||
private async Task AddElement(IElement element)
|
||||
{
|
||||
await TargetContainer.AddElement(element);
|
||||
}
|
||||
|
||||
protected abstract Task<bool> IsItemMatch(IItem item);
|
||||
|
||||
public virtual List<ItemNamePart> GetDisplayName(IItem item)
|
||||
{
|
||||
return new List<ItemNamePart>()
|
||||
{
|
||||
new ItemNamePart(item.Name)
|
||||
};
|
||||
}
|
||||
|
||||
public bool Cancel()
|
||||
{
|
||||
if (!Searching || _cancellationTokenSource == null) return false;
|
||||
_cancellationTokenSource?.Cancel();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/Core/FileTime.Core/Services/ItemNameConverterService.cs
Normal file
56
src/Core/FileTime.Core/Services/ItemNameConverterService.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Services
|
||||
{
|
||||
public class ItemNameConverterService
|
||||
{
|
||||
public List<ItemNamePart> GetDisplayName(string name, string? searchText)
|
||||
{
|
||||
var nameParts = new List<ItemNamePart>();
|
||||
searchText = searchText?.ToLower();
|
||||
|
||||
if (!string.IsNullOrEmpty(searchText))
|
||||
{
|
||||
var nameLeft = name;
|
||||
|
||||
while (nameLeft.ToLower().IndexOf(searchText, StringComparison.Ordinal) is int rapidTextStart && rapidTextStart != -1)
|
||||
{
|
||||
var before = rapidTextStart > 0 ? nameLeft.Substring(0, rapidTextStart) : null;
|
||||
var rapidTravel = nameLeft.Substring(rapidTextStart, searchText.Length);
|
||||
|
||||
nameLeft = nameLeft.Substring(rapidTextStart + searchText.Length);
|
||||
|
||||
if (before != null)
|
||||
{
|
||||
nameParts.Add(new ItemNamePart(before));
|
||||
}
|
||||
|
||||
nameParts.Add(new ItemNamePart(rapidTravel, true));
|
||||
}
|
||||
|
||||
if (nameLeft.Length > 0)
|
||||
{
|
||||
nameParts.Add(new ItemNamePart(nameLeft));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nameParts.Add(new ItemNamePart(name));
|
||||
}
|
||||
return nameParts;
|
||||
}
|
||||
|
||||
public string GetFileName(string fullName)
|
||||
{
|
||||
var parts = fullName.Split('.');
|
||||
var fileName = string.Join('.', parts[..^1]);
|
||||
return string.IsNullOrEmpty(fileName) ? fullName : fileName;
|
||||
}
|
||||
|
||||
public string GetFileExtension(string fullName)
|
||||
{
|
||||
var parts = fullName.Split('.');
|
||||
return parts.Length == 1 || (parts.Length == 2 && string.IsNullOrEmpty(parts[0])) ? "" : parts[^1];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace FileTime.Core.StateManagement
|
||||
{
|
||||
public class ElementCreationStates
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
14
src/Core/FileTime.Core/Timeline/CommandRunner.cs
Normal file
14
src/Core/FileTime.Core/Timeline/CommandRunner.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class CommandRunner
|
||||
{
|
||||
public Thread Thread { get; }
|
||||
public CommandTimeState Command { get; }
|
||||
|
||||
public CommandRunner(Thread thread, CommandTimeState command)
|
||||
{
|
||||
Thread = thread;
|
||||
Command = command;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading.Tasks;
|
||||
using FileTime.Core.Command;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
@@ -12,7 +13,7 @@ namespace FileTime.Core.Timeline
|
||||
public CommandTimeState(ICommand command, PointInTime? startTime)
|
||||
{
|
||||
Command = command;
|
||||
UpdateState(startTime).Wait();
|
||||
Task.Run(async () => await UpdateState(startTime)).Wait();
|
||||
}
|
||||
|
||||
public async Task UpdateState(PointInTime? startPoint)
|
||||
|
||||
@@ -5,14 +5,12 @@ namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class Difference
|
||||
{
|
||||
public DifferenceItemType Type { get; }
|
||||
public string Name { get; }
|
||||
public AbsolutePath AbsolutePath { get; }
|
||||
public DifferenceActionType Action { get; }
|
||||
|
||||
public Difference(DifferenceItemType type, DifferenceActionType action, AbsolutePath absolutePath)
|
||||
public Difference(DifferenceActionType action, AbsolutePath absolutePath)
|
||||
{
|
||||
Type = type;
|
||||
AbsolutePath = absolutePath;
|
||||
Action = action;
|
||||
|
||||
@@ -22,9 +20,8 @@ namespace FileTime.Core.Timeline
|
||||
public Difference WithVirtualContentProvider(IContentProvider? virtualContentProvider)
|
||||
{
|
||||
return new Difference(
|
||||
Type,
|
||||
Action,
|
||||
new AbsolutePath(AbsolutePath.ContentProvider, AbsolutePath.Path, virtualContentProvider)
|
||||
new AbsolutePath(AbsolutePath.ContentProvider, AbsolutePath.Path, AbsolutePath.Type, virtualContentProvider)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public enum DifferenceItemType
|
||||
{
|
||||
Container,
|
||||
Element,
|
||||
Unknown
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,12 @@ namespace FileTime.Core.Timeline
|
||||
public class ReadOnlyParallelCommands
|
||||
{
|
||||
public IReadOnlyList<ReadOnlyCommandTimeState> Commands { get; }
|
||||
public ushort Id { get; }
|
||||
|
||||
public ReadOnlyParallelCommands(ParallelCommands parallelCommands)
|
||||
{
|
||||
Commands = parallelCommands.Commands.Select(c => new ReadOnlyCommandTimeState(c)).ToList().AsReadOnly();
|
||||
Id = parallelCommands.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,38 +14,52 @@ namespace FileTime.Core.Timeline
|
||||
public AsyncEventHandler Refreshed { get; } = new AsyncEventHandler();
|
||||
|
||||
public string Name { get; }
|
||||
public string DisplayName { get; }
|
||||
|
||||
public string? FullName { get; }
|
||||
|
||||
public bool IsHidden => false;
|
||||
|
||||
public bool CanDelete => true;
|
||||
public SupportsDelete CanDelete => SupportsDelete.True;
|
||||
|
||||
public bool CanRename => true;
|
||||
|
||||
public IContentProvider Provider { get; }
|
||||
public IContentProvider VirtualProvider { get; }
|
||||
public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly();
|
||||
|
||||
public bool SupportsDirectoryLevelSoftDelete => false;
|
||||
|
||||
public bool IsDestroyed { get; private set; }
|
||||
|
||||
//FIXME: currently this can be different of the real items NativePath, should be fixed
|
||||
public string? NativePath => FullName;
|
||||
public bool IsExists => true;
|
||||
public bool AllowRecursiveDeletion => true;
|
||||
public bool Loading => false;
|
||||
public bool CanHandleEscape => false;
|
||||
public AsyncEventHandler<bool> LoadingChanged { get; protected set; } = new();
|
||||
|
||||
public TimeContainer(string name, IContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider, PointInTime pointInTime)
|
||||
{
|
||||
_parent = parent;
|
||||
_pointInTime = pointInTime;
|
||||
|
||||
Name = name;
|
||||
DisplayName = Name = name;
|
||||
Provider = contentProvider;
|
||||
VirtualProvider = virtualContentProvider;
|
||||
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
|
||||
}
|
||||
|
||||
public async Task<IContainer> Clone() => new TimeContainer(Name, await _parent!.Clone(), Provider, VirtualProvider, _pointInTime);
|
||||
public async Task<IContainer> CloneAsync() => new TimeContainer(Name, await _parent!.CloneAsync(), Provider, VirtualProvider, _pointInTime);
|
||||
|
||||
public Task<IContainer> CreateContainer(string name) => Task.FromResult((IContainer)new TimeContainer(name, this, Provider, VirtualProvider, _pointInTime));
|
||||
public Task<IContainer> CreateContainerAsync(string name) => Task.FromResult((IContainer)new TimeContainer(name, this, Provider, VirtualProvider, _pointInTime));
|
||||
|
||||
public Task<IElement> CreateElement(string name) => Task.FromResult((IElement)new TimeElement(name, this, Provider, VirtualProvider));
|
||||
public Task<IElement> CreateElementAsync(string name) => Task.FromResult((IElement)new TimeElement(name, this, Provider, VirtualProvider));
|
||||
|
||||
public Task Delete() => Task.CompletedTask;
|
||||
public Task Delete(bool hardDelete = false) => Task.CompletedTask;
|
||||
|
||||
public async Task<IItem?> GetByPath(string path)
|
||||
public async Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false)
|
||||
{
|
||||
var paths = path.Split(Constants.SeparatorChar);
|
||||
|
||||
@@ -58,7 +72,7 @@ namespace FileTime.Core.Timeline
|
||||
|
||||
if (item is IContainer container)
|
||||
{
|
||||
return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)));
|
||||
return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)), acceptDeepestMatch);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -69,7 +83,7 @@ namespace FileTime.Core.Timeline
|
||||
(IReadOnlyList<IContainer>?)_pointInTime
|
||||
.Differences
|
||||
.Where(d =>
|
||||
d.Type == DifferenceItemType.Container
|
||||
d.AbsolutePath.Type == AbsolutePathType.Container
|
||||
&& GetParentPath(d.AbsolutePath.Path) == FullName)
|
||||
.Select(MapContainer)
|
||||
.ToList()
|
||||
@@ -81,7 +95,7 @@ namespace FileTime.Core.Timeline
|
||||
(IReadOnlyList<IElement>?)_pointInTime
|
||||
.Differences
|
||||
.Where(d =>
|
||||
d.Type == DifferenceItemType.Element
|
||||
d.AbsolutePath.Type == AbsolutePathType.Element
|
||||
&& GetParentPath(d.AbsolutePath.Path) == FullName)
|
||||
.Select(MapElement)
|
||||
.ToList()
|
||||
@@ -98,9 +112,9 @@ namespace FileTime.Core.Timeline
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
|
||||
public async Task<bool> IsExists(string name) => (await GetItems())?.Any(i => i.Name == name) ?? false;
|
||||
public async Task<bool> IsExistsAsync(string name) => (await GetItems())?.Any(i => i.Name == name) ?? false;
|
||||
|
||||
public async Task Refresh() => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
public async Task RefreshAsync(CancellationToken token = default) => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
|
||||
public Task Rename(string newName) => Task.CompletedTask;
|
||||
|
||||
@@ -108,14 +122,24 @@ namespace FileTime.Core.Timeline
|
||||
|
||||
private IContainer MapContainer(Difference containerDiff)
|
||||
{
|
||||
if (containerDiff.Type != DifferenceItemType.Container) throw new ArgumentException($"{nameof(containerDiff)}'s {nameof(Difference.Type)} property is not {DifferenceItemType.Container}.");
|
||||
if (containerDiff.AbsolutePath.Type != AbsolutePathType.Container) throw new ArgumentException($"{nameof(containerDiff)}'s {nameof(AbsolutePath.Type)} property is not {AbsolutePathType.Container}.");
|
||||
return new TimeContainer(containerDiff.Name, this, Provider, containerDiff.AbsolutePath.VirtualContentProvider ?? containerDiff.AbsolutePath.ContentProvider, _pointInTime);
|
||||
}
|
||||
|
||||
private IElement MapElement(Difference elementDiff)
|
||||
{
|
||||
if (elementDiff.Type != DifferenceItemType.Container) throw new ArgumentException($"{elementDiff}'s {nameof(Difference.Type)} property is not {DifferenceItemType.Element}.");
|
||||
if (elementDiff.AbsolutePath.Type != AbsolutePathType.Element) throw new ArgumentException($"{elementDiff}'s {nameof(AbsolutePath.Type)} property is not {AbsolutePathType.Element}.");
|
||||
return new TimeElement(elementDiff.Name, this, Provider, elementDiff.AbsolutePath.VirtualContentProvider ?? elementDiff.AbsolutePath.ContentProvider);
|
||||
}
|
||||
public Task<bool> CanOpenAsync() => Task.FromResult(true);
|
||||
|
||||
public void Destroy() => IsDestroyed = true;
|
||||
public void Unload() { }
|
||||
public Task<ContainerEscapeResult> HandleEscape() => Task.FromResult(new ContainerEscapeResult(false));
|
||||
|
||||
public async Task RunWithLoading(Func<CancellationToken, Task> func, CancellationToken token = default)
|
||||
{
|
||||
await func(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace FileTime.Core.Timeline
|
||||
{
|
||||
_parent = parent;
|
||||
|
||||
Name = name;
|
||||
DisplayName = Name = name;
|
||||
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
|
||||
Provider = contentProvider;
|
||||
VirtualProvider = virtualContentProvider;
|
||||
@@ -19,24 +19,40 @@ namespace FileTime.Core.Timeline
|
||||
public bool IsSpecial => false;
|
||||
|
||||
public string Name { get; }
|
||||
public string DisplayName { get; }
|
||||
|
||||
public string? FullName { get; }
|
||||
|
||||
//FIXME: currently this can be different of the real items NativePath, should be fixed
|
||||
public string? NativePath => FullName;
|
||||
|
||||
public bool IsHidden => false;
|
||||
|
||||
public bool CanDelete => true;
|
||||
public SupportsDelete CanDelete => SupportsDelete.True;
|
||||
|
||||
public bool CanRename => true;
|
||||
|
||||
public IContentProvider Provider { get; }
|
||||
public IContentProvider VirtualProvider { get; }
|
||||
|
||||
public Task Delete() => Task.CompletedTask;
|
||||
public bool IsDestroyed { get; private set; }
|
||||
public bool IsExists => true;
|
||||
|
||||
public Task Delete(bool hardDelete = false) => Task.CompletedTask;
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
|
||||
public string GetPrimaryAttributeText() => "";
|
||||
public string? GetPrimaryAttributeText() => null;
|
||||
|
||||
public Task Rename(string newName) => Task.CompletedTask;
|
||||
|
||||
public Task<string> GetContent(CancellationToken token = default) => Task.FromResult("");
|
||||
public Task<long?> GetElementSize(CancellationToken token = default) => Task.FromResult((long?)-1L);
|
||||
|
||||
public void Destroy() => IsDestroyed = true;
|
||||
|
||||
public Task<IContentReader> GetContentReaderAsync() => throw new NotSupportedException();
|
||||
|
||||
public Task<IContentWriter> GetContentWriterAsync() => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -1,89 +1,30 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class TimeProvider : IContentProvider
|
||||
public class TimeProvider : ContentProviderBase<TimeProvider>
|
||||
{
|
||||
public bool IsLoaded => true;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
|
||||
public string Name => "time";
|
||||
|
||||
public string? FullName => null;
|
||||
|
||||
public bool IsHidden => false;
|
||||
|
||||
public bool CanDelete => false;
|
||||
|
||||
public bool CanRename => false;
|
||||
|
||||
public IContentProvider Provider => this;
|
||||
|
||||
private readonly PointInTime _pointInTime;
|
||||
|
||||
public TimeProvider(PointInTime pointInTime)
|
||||
public TimeProvider(PointInTime pointInTime) : base("time", "time2://", false)
|
||||
{
|
||||
_pointInTime = pointInTime;
|
||||
}
|
||||
|
||||
public bool CanHandlePath(string path)
|
||||
public override Task<bool> CanHandlePath(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IContainer> Clone() => Task.FromResult((IContainer)this);
|
||||
|
||||
public Task<IContainer> CreateContainer(string name)
|
||||
public override Task<IContainer> CreateContainerAsync(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IElement> CreateElement(string name)
|
||||
public override Task<IElement> CreateElementAsync(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task Delete() => throw new NotSupportedException();
|
||||
|
||||
public Task<IItem?> GetByPath(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IContainer? GetParent() => null;
|
||||
|
||||
public Task<IReadOnlyList<IContainer>> GetRootContainers(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<bool> IsExists(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task Refresh() => Task.CompletedTask;
|
||||
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
|
||||
public void SetParent(IContainer container) { }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Threading.Tasks;
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
@@ -9,11 +11,14 @@ namespace FileTime.Core.Timeline
|
||||
private readonly CommandExecutor _commandExecutor;
|
||||
private readonly List<ParallelCommands> _commandsToRun = new();
|
||||
private readonly object _guard = new();
|
||||
private readonly ILogger<TimeRunner> _logger;
|
||||
|
||||
private bool _resourceIsInUse;
|
||||
private readonly List<Thread> _commandRunners = new();
|
||||
private readonly List<CommandRunner> _commandRunners = new();
|
||||
private bool _enableRunning = true;
|
||||
|
||||
private IReadOnlyList<ReadOnlyParallelCommands> _parallelCommands = new List<ReadOnlyParallelCommands>();
|
||||
|
||||
public bool EnableRunning
|
||||
{
|
||||
get
|
||||
@@ -29,18 +34,25 @@ namespace FileTime.Core.Timeline
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<ReadOnlyParallelCommands> ParallelCommands { get; private set; } = new List<ReadOnlyParallelCommands>().AsReadOnly();
|
||||
public async Task<IReadOnlyList<ReadOnlyParallelCommands>> GetParallelCommandsAsync()
|
||||
{
|
||||
IReadOnlyList<ReadOnlyParallelCommands> parallelCommands = new List<ReadOnlyParallelCommands>();
|
||||
await RunWithLockAsync(() => parallelCommands = _parallelCommands);
|
||||
|
||||
return parallelCommands;
|
||||
}
|
||||
|
||||
public AsyncEventHandler<AbsolutePath> RefreshContainer { get; } = new AsyncEventHandler<AbsolutePath>();
|
||||
|
||||
public event EventHandler? CommandsChanged;
|
||||
public AsyncEventHandler<IReadOnlyList<ReadOnlyParallelCommands>> CommandsChangedAsync { get; } = new();
|
||||
|
||||
public TimeRunner(CommandExecutor commandExecutor)
|
||||
public TimeRunner(CommandExecutor commandExecutor, ILogger<TimeRunner> logger)
|
||||
{
|
||||
_commandExecutor = commandExecutor;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task AddCommand(ICommand command, ParallelCommands? batch = null, bool toNewBatch = false)
|
||||
public async Task AddCommand(ICommand command, int? batchId = null, bool toNewBatch = false)
|
||||
{
|
||||
await RunWithLockAsync(async () =>
|
||||
{
|
||||
@@ -56,9 +68,9 @@ namespace FileTime.Core.Timeline
|
||||
batchToAdd = new ParallelCommands(_commandsToRun.Last().Result);
|
||||
_commandsToRun.Add(batchToAdd);
|
||||
}
|
||||
else if (batch != null && _commandsToRun.Contains(batch))
|
||||
else if (batchId != null && _commandsToRun.Find(b => b.Id == batchId) is ParallelCommands parallelCommands)
|
||||
{
|
||||
batchToAdd = batch;
|
||||
batchToAdd = parallelCommands;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -72,20 +84,14 @@ namespace FileTime.Core.Timeline
|
||||
{
|
||||
StartCommandRunner();
|
||||
}
|
||||
});
|
||||
|
||||
await UpdateReadOnlyCommands();
|
||||
});
|
||||
}
|
||||
|
||||
public async Task TryStartCommandRunner()
|
||||
{
|
||||
await RunWithLockAsync(() =>
|
||||
{
|
||||
if (_commandRunners.Count == 0 && _commandsToRun.Count > 0)
|
||||
{
|
||||
StartCommandRunner();
|
||||
}
|
||||
});
|
||||
await RunWithLockAsync(() => StartCommandRunner());
|
||||
}
|
||||
|
||||
private void StartCommandRunner()
|
||||
@@ -98,13 +104,21 @@ namespace FileTime.Core.Timeline
|
||||
|
||||
private void RunCommands()
|
||||
{
|
||||
while (_commandsToRun.Count > 0 && _commandsToRun[0].Commands.Count == 0) _commandsToRun.RemoveAt(0);
|
||||
|
||||
if (_commandsToRun.Count > 0)
|
||||
{
|
||||
foreach (var command in _commandsToRun[0].Commands)
|
||||
{
|
||||
if (command.CanRun == CanCommandRun.True || (command.CanRun == CanCommandRun.Forceable && command.ForceRun))
|
||||
if (_commandRunners.Find(r => r.Command == command) != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (command.CanRun == CanCommandRun.True || (command.CanRun == CanCommandRun.Forceable && command.ForceRun))
|
||||
{
|
||||
_logger.LogDebug("Starting command: {0}", command.Command.DisplayLabel);
|
||||
var thread = new Thread(new ParameterizedThreadStart(RunCommand));
|
||||
_commandRunners.Add(new CommandRunner(thread, command));
|
||||
thread.Start(command);
|
||||
}
|
||||
else
|
||||
@@ -123,29 +137,44 @@ namespace FileTime.Core.Timeline
|
||||
if (arg is CommandTimeState commandToRun2)
|
||||
{
|
||||
commandToRun = commandToRun2;
|
||||
_commandExecutor.ExecuteCommandAsync(commandToRun.Command, this).Wait();
|
||||
Task.Run(async () => await _commandExecutor.ExecuteCommandAsync(commandToRun.Command, this)).Wait();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(
|
||||
e,
|
||||
"Error while running command: {CommandType} ({Command}) {Error}.",
|
||||
commandToRun?.Command.GetType().Name,
|
||||
commandToRun?.Command.DisplayLabel,
|
||||
e.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisposeCommandThread(Thread.CurrentThread, commandToRun).Wait();
|
||||
Task.Run(async () => await DisposeCommandThread(commandToRun)).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DisposeCommandThread(Thread thread, CommandTimeState? command)
|
||||
private async Task DisposeCommandThread(CommandTimeState? command)
|
||||
{
|
||||
await RunWithLockAsync(() =>
|
||||
await RunWithLockAsync(async () =>
|
||||
{
|
||||
if (command != null)
|
||||
{
|
||||
_logger.LogDebug("Command finished running: {Command}", command.Command.DisplayLabel);
|
||||
_commandsToRun[0].Remove(command);
|
||||
if (_commandsToRun[0].Commands.Count == 0)
|
||||
{
|
||||
_logger.LogDebug("Removing empty command array. {RemainingBatchNumber} batch left.", _commandsToRun.Count - 1);
|
||||
_commandsToRun.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
_commandRunners.Remove(thread);
|
||||
});
|
||||
var currentCommandRunner = _commandRunners.Find(r => r.Command == command);
|
||||
if (currentCommandRunner != null) _commandRunners.Remove(currentCommandRunner);
|
||||
await UpdateReadOnlyCommands();
|
||||
|
||||
await TryStartCommandRunner();
|
||||
StartCommandRunner();
|
||||
});
|
||||
}
|
||||
|
||||
public async Task Refresh()
|
||||
@@ -153,29 +182,27 @@ namespace FileTime.Core.Timeline
|
||||
await RunWithLockAsync(async () =>
|
||||
{
|
||||
await RefreshCommands(PointInTime.CreateEmpty());
|
||||
});
|
||||
await UpdateReadOnlyCommands();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private async Task RefreshCommands(PointInTime? fullStartTime = null)
|
||||
{
|
||||
var curretnTime = fullStartTime ?? _commandsToRun[0].Result;
|
||||
var currentTime = fullStartTime ?? _commandsToRun[0].Result;
|
||||
var startIndex = fullStartTime == null ? 1 : 0;
|
||||
|
||||
for (var i = startIndex; i < _commandsToRun.Count; i++)
|
||||
{
|
||||
curretnTime = await _commandsToRun[i].RefreshResult(curretnTime);
|
||||
currentTime = await _commandsToRun[i].RefreshResult(currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateReadOnlyCommands()
|
||||
{
|
||||
await RunWithLockAsync(() =>
|
||||
{
|
||||
ParallelCommands = _commandsToRun.ConvertAll(c => new ReadOnlyParallelCommands(c)).AsReadOnly();
|
||||
});
|
||||
CommandsChanged?.Invoke(this, EventArgs.Empty);
|
||||
var wait = _commandsToRun.Count == 1;
|
||||
if (wait) await Task.Delay(100);
|
||||
_parallelCommands = _commandsToRun.ConvertAll(c => new ReadOnlyParallelCommands(c)).AsReadOnly();
|
||||
await CommandsChangedAsync.InvokeAsync(this, _parallelCommands);
|
||||
}
|
||||
|
||||
private async Task RunWithLockAsync(Action action)
|
||||
@@ -212,6 +239,6 @@ namespace FileTime.Core.Timeline
|
||||
}
|
||||
}
|
||||
|
||||
private void RunWithLock(Action action) => RunWithLockAsync(action).Wait();
|
||||
private void RunWithLock(Action action) => Task.Run(async () => await RunWithLockAsync(action)).Wait();
|
||||
}
|
||||
}
|
||||
148
src/Core/InitableService/Initable.cs
Normal file
148
src/Core/InitableService/Initable.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
// Autogenerated
|
||||
namespace InitableService;
|
||||
|
||||
public interface IInitable
|
||||
{
|
||||
void Init();
|
||||
}
|
||||
public interface IInitable<T1>
|
||||
{
|
||||
void Init(T1 obj1);
|
||||
}
|
||||
public interface IInitable<T1, T2>
|
||||
{
|
||||
void Init(T1 obj1, T2 obj2);
|
||||
}
|
||||
public interface IInitable<T1, T2, T3>
|
||||
{
|
||||
void Init(T1 obj1, T2 obj2, T3 obj3);
|
||||
}
|
||||
public interface IInitable<T1, T2, T3, T4>
|
||||
{
|
||||
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4);
|
||||
}
|
||||
public interface IInitable<T1, T2, T3, T4, T5>
|
||||
{
|
||||
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5);
|
||||
}
|
||||
public interface IInitable<T1, T2, T3, T4, T5, T6>
|
||||
{
|
||||
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6);
|
||||
}
|
||||
public interface IInitable<T1, T2, T3, T4, T5, T6, T7>
|
||||
{
|
||||
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7);
|
||||
}
|
||||
public interface IInitable<T1, T2, T3, T4, T5, T6, T7, T8>
|
||||
{
|
||||
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8);
|
||||
}
|
||||
public interface IInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9>
|
||||
{
|
||||
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9);
|
||||
}
|
||||
public interface IInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
|
||||
{
|
||||
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10);
|
||||
}
|
||||
public interface IInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
|
||||
{
|
||||
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11);
|
||||
}
|
||||
public interface IInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
|
||||
{
|
||||
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11, T12 obj12);
|
||||
}
|
||||
public interface IInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
|
||||
{
|
||||
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11, T12 obj12, T13 obj13);
|
||||
}
|
||||
public interface IInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
|
||||
{
|
||||
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11, T12 obj12, T13 obj13, T14 obj14);
|
||||
}
|
||||
public interface IInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
|
||||
{
|
||||
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11, T12 obj12, T13 obj13, T14 obj14, T15 obj15);
|
||||
}
|
||||
public interface IAsyncInitable
|
||||
{
|
||||
Task InitAsync();
|
||||
}
|
||||
|
||||
public interface IAsyncInitable<T1>
|
||||
{
|
||||
Task InitAsync(T1 obj1);
|
||||
}
|
||||
|
||||
public interface IAsyncInitable<T1, T2>
|
||||
{
|
||||
Task InitAsync(T1 obj1, T2 obj2);
|
||||
}
|
||||
|
||||
public interface IAsyncInitable<T1, T2, T3>
|
||||
{
|
||||
Task InitAsync(T1 obj1, T2 obj2, T3 obj3);
|
||||
}
|
||||
|
||||
public interface IAsyncInitable<T1, T2, T3, T4>
|
||||
{
|
||||
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4);
|
||||
}
|
||||
|
||||
public interface IAsyncInitable<T1, T2, T3, T4, T5>
|
||||
{
|
||||
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5);
|
||||
}
|
||||
|
||||
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6>
|
||||
{
|
||||
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6);
|
||||
}
|
||||
|
||||
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7>
|
||||
{
|
||||
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7);
|
||||
}
|
||||
|
||||
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7, T8>
|
||||
{
|
||||
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8);
|
||||
}
|
||||
|
||||
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9>
|
||||
{
|
||||
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9);
|
||||
}
|
||||
|
||||
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
|
||||
{
|
||||
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10);
|
||||
}
|
||||
|
||||
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
|
||||
{
|
||||
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11);
|
||||
}
|
||||
|
||||
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
|
||||
{
|
||||
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11, T12 obj12);
|
||||
}
|
||||
|
||||
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
|
||||
{
|
||||
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11, T12 obj12, T13 obj13);
|
||||
}
|
||||
|
||||
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
|
||||
{
|
||||
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11, T12 obj12, T13 obj13, T14 obj14);
|
||||
}
|
||||
|
||||
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
|
||||
{
|
||||
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11, T12 obj12, T13 obj13, T14 obj14, T15 obj15);
|
||||
}
|
||||
|
||||
|
||||
1193
src/Core/InitableService/InitableExtensions.cs
Normal file
1193
src/Core/InitableService/InitableExtensions.cs
Normal file
File diff suppressed because it is too large
Load Diff
13
src/Core/InitableService/InitableService.csproj
Normal file
13
src/Core/InitableService/InitableService.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
180
src/Core/InitableService/generateExtensions.csx
Normal file
180
src/Core/InitableService/generateExtensions.csx
Normal file
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env dotnet-script
|
||||
|
||||
{
|
||||
const string header =
|
||||
@"// Autogenerated
|
||||
using InitableService;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
public static class InitableExtensions
|
||||
{
|
||||
";
|
||||
|
||||
const string footer = "}";
|
||||
using var initableWriter = File.CreateText("InitableExtensions.cs");
|
||||
initableWriter.WriteLine(header);
|
||||
const int generateForParams = 15;
|
||||
|
||||
for (var i = 0; i <= generateForParams; i++)
|
||||
{
|
||||
WriteInitableExtension(initableWriter, false, i);
|
||||
initableWriter.WriteLine();
|
||||
}
|
||||
|
||||
for (var i = 0; i <= generateForParams; i++)
|
||||
{
|
||||
WriteInitableExtension(initableWriter, true, i);
|
||||
initableWriter.WriteLine();
|
||||
}
|
||||
|
||||
initableWriter.WriteLine(footer);
|
||||
|
||||
for (var i = 0; i <= generateForParams; i++)
|
||||
{
|
||||
WriteInitableResolver(initableWriter, false, i);
|
||||
initableWriter.WriteLine();
|
||||
}
|
||||
|
||||
for (var i = 0; i <= generateForParams; i++)
|
||||
{
|
||||
WriteInitableResolver(initableWriter, true, i);
|
||||
initableWriter.WriteLine();
|
||||
}
|
||||
|
||||
initableWriter.Flush();
|
||||
}
|
||||
|
||||
void WriteInitableExtension(StreamWriter initableWriter, bool isAsync, int i)
|
||||
{
|
||||
initableWriter.Write($"\tpublic static {(isAsync ? "Async" : "")}Resolver");
|
||||
if (i > 0)
|
||||
{
|
||||
initableWriter.Write("<");
|
||||
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => "T" + c)));
|
||||
initableWriter.Write(">");
|
||||
}
|
||||
initableWriter.Write($" Get{(isAsync ? "Async" : "")}InitableResolver");
|
||||
if (i > 0)
|
||||
{
|
||||
initableWriter.Write("<");
|
||||
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => "T" + c)));
|
||||
initableWriter.Write(">");
|
||||
}
|
||||
|
||||
initableWriter.Write("(");
|
||||
var thisParameter = "this IServiceProvider serviceProvider";
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => $"T{c} obj{c}").Prepend(thisParameter)));
|
||||
}
|
||||
else
|
||||
{
|
||||
initableWriter.Write(thisParameter);
|
||||
}
|
||||
|
||||
initableWriter.WriteLine(")");
|
||||
initableWriter.WriteLine("\t{");
|
||||
initableWriter.Write($"\t\treturn new {(isAsync ? "Async" : "")}Resolver");
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
initableWriter.Write("<");
|
||||
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => "T" + c)));
|
||||
initableWriter.Write(">");
|
||||
}
|
||||
|
||||
initableWriter.Write("(");
|
||||
if (i > 0)
|
||||
{
|
||||
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => $"obj{c}").Prepend("serviceProvider")));
|
||||
}
|
||||
else
|
||||
{
|
||||
initableWriter.Write("serviceProvider");
|
||||
}
|
||||
initableWriter.WriteLine(");");
|
||||
|
||||
initableWriter.WriteLine("\t}");
|
||||
}
|
||||
|
||||
void WriteInitableResolver(StreamWriter initableWriter, bool isAsync, int i)
|
||||
{
|
||||
initableWriter.Write($"public class {(isAsync ? "Async" : "")}Resolver");
|
||||
if (i > 0)
|
||||
{
|
||||
initableWriter.Write("<");
|
||||
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => "T" + c)));
|
||||
initableWriter.Write(">");
|
||||
}
|
||||
initableWriter.WriteLine();
|
||||
initableWriter.WriteLine("{");
|
||||
initableWriter.WriteLine("\tIServiceProvider _serviceProvider;");
|
||||
if (i > 0)
|
||||
{
|
||||
for (var j = 1; j <= i; j++)
|
||||
{
|
||||
initableWriter.WriteLine($"\tprivate T{j} _obj{j};");
|
||||
}
|
||||
}
|
||||
|
||||
initableWriter.Write($"\tpublic {(isAsync ? "Async" : "")}Resolver(");
|
||||
if (i > 0)
|
||||
{
|
||||
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => $"T{c} obj{c}").Prepend("IServiceProvider serviceProvider")));
|
||||
}
|
||||
else
|
||||
{
|
||||
initableWriter.Write("IServiceProvider serviceProvider");
|
||||
}
|
||||
|
||||
initableWriter.WriteLine(")");
|
||||
initableWriter.WriteLine("\t{");
|
||||
initableWriter.WriteLine("\t\t_serviceProvider = serviceProvider;");
|
||||
if (i > 0)
|
||||
{
|
||||
for (var j = 1; j <= i; j++)
|
||||
{
|
||||
initableWriter.WriteLine($"\t\t_obj{j} = obj{j};");
|
||||
}
|
||||
}
|
||||
initableWriter.WriteLine("\t}");
|
||||
initableWriter.WriteLine("");
|
||||
|
||||
var returnType = isAsync ? "async Task<TResult>" : "TResult";
|
||||
initableWriter.WriteLine($"\tpublic {returnType} GetService{(isAsync ? "Async" : "")}<TResult>()");
|
||||
|
||||
|
||||
|
||||
initableWriter.Write($"\t\twhere TResult : class, I{(isAsync ? "Async" : "")}Initable");
|
||||
if (i > 0)
|
||||
{
|
||||
initableWriter.Write("<");
|
||||
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => "T" + c)));
|
||||
initableWriter.Write(">");
|
||||
}
|
||||
initableWriter.WriteLine("");
|
||||
initableWriter.WriteLine("\t{");
|
||||
initableWriter.WriteLine("\t\tvar initableService = _serviceProvider.GetService<TResult>()!;");
|
||||
if (isAsync)
|
||||
{
|
||||
initableWriter.Write("\t\tawait initableService.InitAsync");
|
||||
}
|
||||
else
|
||||
{
|
||||
initableWriter.Write("\t\tinitableService.Init");
|
||||
}
|
||||
|
||||
initableWriter.Write("(");
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => $"_obj{c}")));
|
||||
}
|
||||
initableWriter.WriteLine(");");
|
||||
initableWriter.WriteLine("\t\treturn initableService;");
|
||||
|
||||
initableWriter.WriteLine("\t}");
|
||||
initableWriter.WriteLine("}");
|
||||
}
|
||||
66
src/Core/InitableService/generateInterfaces.csx
Normal file
66
src/Core/InitableService/generateInterfaces.csx
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env dotnet-script
|
||||
|
||||
{
|
||||
const string header =
|
||||
@"// Autogenerated
|
||||
namespace InitableService;
|
||||
";
|
||||
|
||||
const string footer = "";
|
||||
using var initableWriter = File.CreateText("Initable.cs");
|
||||
initableWriter.WriteLine(header);
|
||||
const int generateForParams = 15;
|
||||
|
||||
for (var i = 0; i <= generateForParams; i++)
|
||||
{
|
||||
WriteInitable(initableWriter, false, i);
|
||||
}
|
||||
|
||||
for (var i = 0; i <= generateForParams; i++)
|
||||
{
|
||||
WriteInitable(initableWriter, true, i);
|
||||
initableWriter.WriteLine();
|
||||
}
|
||||
|
||||
initableWriter.WriteLine(footer);
|
||||
initableWriter.Flush();
|
||||
}
|
||||
|
||||
void WriteInitable(StreamWriter initableWriter, bool isAsync, int i)
|
||||
{
|
||||
initableWriter.Write($"public interface I{(isAsync ? "Async" : "")}Initable");
|
||||
if (i > 0)
|
||||
{
|
||||
initableWriter.Write("<");
|
||||
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => "T" + c)));
|
||||
initableWriter.Write(">");
|
||||
}
|
||||
|
||||
initableWriter.WriteLine();
|
||||
initableWriter.WriteLine("{");
|
||||
|
||||
if (isAsync)
|
||||
{
|
||||
initableWriter.Write("\tTask InitAsync");
|
||||
}
|
||||
else
|
||||
{
|
||||
initableWriter.Write("\tvoid Init");
|
||||
}
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
/*initableWriter.Write("<");
|
||||
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => "T" + c)));
|
||||
initableWriter.Write(">");*/
|
||||
initableWriter.Write("(");
|
||||
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => $"T{c} obj{c}")));
|
||||
initableWriter.Write(")");
|
||||
}
|
||||
else
|
||||
{
|
||||
initableWriter.Write("()");
|
||||
}
|
||||
initableWriter.WriteLine(";");
|
||||
initableWriter.WriteLine("}");
|
||||
}
|
||||
232
src/FileTime.sln
232
src/FileTime.sln
@@ -31,32 +31,21 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Providers.Smb", "P
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GuiApp", "GuiApp", "{890275FF-943A-4D07-83BA-14E5C52D7846}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FileTime.Uno", "FileTime.Uno", "{7CF1A80E-709A-4CB0-8434-7B2F0A4BC497}"
|
||||
EndProject
|
||||
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "FileTime.Uno.Shared", "GuiApp\FileTime.Uno\FileTime.Uno.Shared\FileTime.Uno.Shared.shproj", "{6279C845-92F8-4333-AB99-3D213163593C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Uno.Skia.Gtk", "GuiApp\FileTime.Uno\FileTime.Uno.Skia.Gtk\FileTime.Uno.Skia.Gtk.csproj", "{C56E7DA4-4F15-490E-AE9F-3893238C56D1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Uno.Skia.Linux.FrameBuffer", "GuiApp\FileTime.Uno\FileTime.Uno.Skia.Linux.FrameBuffer\FileTime.Uno.Skia.Linux.FrameBuffer.csproj", "{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Uno.Wasm", "GuiApp\FileTime.Uno\FileTime.Uno.Wasm\FileTime.Uno.Wasm.csproj", "{A7E1383B-E334-4CC2-AF34-D413213847B6}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Uno.Windows.Desktop", "GuiApp\FileTime.Uno\FileTime.Uno.Windows.Desktop\FileTime.Uno.Windows.Desktop.csproj", "{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}"
|
||||
EndProject
|
||||
Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "FileTime.Uno.Windows.Package", "GuiApp\FileTime.Uno\FileTime.Uno.Windows.Package\FileTime.Uno.Windows.Package.wapproj", "{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncEvent", "Core\AsyncEvent\AsyncEvent.csproj", "{9BDAC126-200F-4056-8D35-36EC059B40F3}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Avalonia", "GuiApp\FileTime.Avalonia\FileTime.Avalonia.csproj", "{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{0D2B4BAA-0399-459C-B022-41DB7F408225}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Tools.Compression", "Tools\FileTime.Tools.Compression\FileTime.Tools.Compression.csproj", "{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Providers.Sftp", "Providers\FileTime.Providers.Sftp\FileTime.Providers.Sftp.csproj", "{0E650206-801D-4E8D-95BA-4565B32092E1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InitableService", "Core\InitableService\InitableService.csproj", "{B1520189-8646-4DE8-B5C9-46AE04B4D01C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Providers.Favorites", "Providers\FileTime.Providers.Favorites\FileTime.Providers.Favorites.csproj", "{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SharedMSBuildProjectFiles) = preSolution
|
||||
GuiApp\FileTime.Uno\FileTime.Uno.Shared\FileTime.Uno.Shared.projitems*{02acef48-3be8-43e5-9358-a914bcbea7ca}*SharedItemsImports = 5
|
||||
GuiApp\FileTime.Uno\FileTime.Uno.Shared\FileTime.Uno.Shared.projitems*{6279c845-92f8-4333-ab99-3d213163593c}*SharedItemsImports = 13
|
||||
GuiApp\FileTime.Uno\FileTime.Uno.Shared\FileTime.Uno.Shared.projitems*{a7e1383b-e334-4cc2-af34-d413213847b6}*SharedItemsImports = 5
|
||||
GuiApp\FileTime.Uno\FileTime.Uno.Shared\FileTime.Uno.Shared.projitems*{c56e7da4-4f15-490e-ae9f-3893238c56d1}*SharedItemsImports = 5
|
||||
GuiApp\FileTime.Uno\FileTime.Uno.Shared\FileTime.Uno.Shared.projitems*{ccb064f4-8cae-4a88-bca7-d7d9041c6e1b}*SharedItemsImports = 5
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|ARM = Debug|ARM
|
||||
@@ -250,116 +239,6 @@ Global
|
||||
{35963404-39F6-4CDA-A0FE-A91036FA8DAC}.Release|x64.Build.0 = Release|Any CPU
|
||||
{35963404-39F6-4CDA-A0FE-A91036FA8DAC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{35963404-39F6-4CDA-A0FE-A91036FA8DAC}.Release|x86.Build.0 = Release|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Release|x64.Build.0 = Release|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1}.Release|x86.Build.0 = Release|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Release|x64.Build.0 = Release|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B}.Release|x86.Build.0 = Release|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6}.Release|x86.Build.0 = Release|Any CPU
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Debug|ARM.ActiveCfg = Debug|x64
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Debug|ARM.Build.0 = Debug|x64
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Debug|ARM64.ActiveCfg = Debug|arm64
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Debug|ARM64.Build.0 = Debug|arm64
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Debug|x64.Build.0 = Debug|x64
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Debug|x86.Build.0 = Debug|x86
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Release|Any CPU.Build.0 = Release|x64
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Release|ARM.ActiveCfg = Release|x64
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Release|ARM.Build.0 = Release|x64
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Release|ARM64.ActiveCfg = Release|arm64
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Release|ARM64.Build.0 = Release|arm64
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Release|x64.ActiveCfg = Release|x64
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Release|x64.Build.0 = Release|x64
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Release|x86.ActiveCfg = Release|x86
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA}.Release|x86.Build.0 = Release|x86
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Debug|Any CPU.Deploy.0 = Debug|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Debug|ARM.ActiveCfg = Debug|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Debug|ARM.Build.0 = Debug|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Debug|ARM.Deploy.0 = Debug|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Debug|ARM64.ActiveCfg = Debug|arm64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Debug|ARM64.Build.0 = Debug|arm64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Debug|ARM64.Deploy.0 = Debug|arm64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Debug|x64.Build.0 = Debug|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Debug|x86.Build.0 = Debug|x86
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Debug|x86.Deploy.0 = Debug|x86
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|Any CPU.Build.0 = Release|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|Any CPU.Deploy.0 = Release|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|ARM.ActiveCfg = Release|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|ARM.Build.0 = Release|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|ARM.Deploy.0 = Release|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|ARM64.ActiveCfg = Release|arm64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|ARM64.Build.0 = Release|arm64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|ARM64.Deploy.0 = Release|arm64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|x64.ActiveCfg = Release|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|x64.Build.0 = Release|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|x64.Deploy.0 = Release|x64
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|x86.ActiveCfg = Release|x86
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|x86.Build.0 = Release|x86
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|x86.Deploy.0 = Release|x86
|
||||
{9BDAC126-200F-4056-8D35-36EC059B40F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9BDAC126-200F-4056-8D35-36EC059B40F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9BDAC126-200F-4056-8D35-36EC059B40F3}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
@@ -400,6 +279,86 @@ Global
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Release|x64.Build.0 = Release|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Release|x86.Build.0 = Release|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Release|x86.Build.0 = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|x64.Build.0 = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|x86.Build.0 = Release|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Release|x86.Build.0 = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -414,15 +373,12 @@ Global
|
||||
{BEA824B0-7684-44FF-95BF-A75E92A36C9F} = {D4C7E692-53C0-4423-9944-E25FE3D51BA2}
|
||||
{F46D6CE5-4811-45B8-9CD4-3C993318A2E6} = {D4C7E692-53C0-4423-9944-E25FE3D51BA2}
|
||||
{35963404-39F6-4CDA-A0FE-A91036FA8DAC} = {517D96CE-A956-4638-A93D-465D34DE22B1}
|
||||
{7CF1A80E-709A-4CB0-8434-7B2F0A4BC497} = {890275FF-943A-4D07-83BA-14E5C52D7846}
|
||||
{6279C845-92F8-4333-AB99-3D213163593C} = {7CF1A80E-709A-4CB0-8434-7B2F0A4BC497}
|
||||
{C56E7DA4-4F15-490E-AE9F-3893238C56D1} = {7CF1A80E-709A-4CB0-8434-7B2F0A4BC497}
|
||||
{CCB064F4-8CAE-4A88-BCA7-D7D9041C6E1B} = {7CF1A80E-709A-4CB0-8434-7B2F0A4BC497}
|
||||
{A7E1383B-E334-4CC2-AF34-D413213847B6} = {7CF1A80E-709A-4CB0-8434-7B2F0A4BC497}
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA} = {7CF1A80E-709A-4CB0-8434-7B2F0A4BC497}
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1} = {7CF1A80E-709A-4CB0-8434-7B2F0A4BC497}
|
||||
{9BDAC126-200F-4056-8D35-36EC059B40F3} = {38B1B927-4201-4B7A-87EE-737B8C6D4090}
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7} = {890275FF-943A-4D07-83BA-14E5C52D7846}
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966} = {0D2B4BAA-0399-459C-B022-41DB7F408225}
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1} = {517D96CE-A956-4638-A93D-465D34DE22B1}
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C} = {38B1B927-4201-4B7A-87EE-737B8C6D4090}
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A} = {517D96CE-A956-4638-A93D-465D34DE22B1}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {8D679DCE-AC84-4A91-BFED-8F8D8E1D8183}
|
||||
|
||||
BIN
src/GuiApp/Assets/filetime.ico
Normal file
BIN
src/GuiApp/Assets/filetime.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
12
src/GuiApp/Assets/filetime.svg
Normal file
12
src/GuiApp/Assets/filetime.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#FFD767" fill-rule="nonzero"/>
|
||||
<line x1="4" y1="9" x2="7" y2="9" stroke="#444444" stroke-width="2"/>
|
||||
<line x1="8" y1="9" x2="14" y2="9" stroke="#444444" stroke-width="2"/>
|
||||
<line x1="15" y1="9" x2="20" y2="9" stroke="#444444" stroke-width="2"/>
|
||||
|
||||
<line x1="8" y1="13" x2="14" y2="13" stroke="#444444" stroke-width="2"/>
|
||||
<line x1="15" y1="13" x2="20" y2="13" stroke="#444444" stroke-width="2"/>
|
||||
|
||||
<line x1="8" y1="17" x2="14" y2="17" stroke="#444444" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 794 B |
BIN
src/GuiApp/Assets/filetime.xcf
Normal file
BIN
src/GuiApp/Assets/filetime.xcf
Normal file
Binary file not shown.
BIN
src/GuiApp/Assets/filetime2.xcf
Normal file
BIN
src/GuiApp/Assets/filetime2.xcf
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user