Compare commits

63 Commits

Author SHA1 Message Date
5a99a800ac Nuget updates, bugfixes 2022-03-08 20:12:20 +01:00
c3a7d56bd4 Bump version to 0.0.8 2022-02-28 22:53:47 +01:00
899363b2b0 Favorites 2022-02-28 22:53:06 +01:00
c8748644d2 Loading refactor, SizeContainer&SmbServer delete 2022-02-23 23:49:22 +01:00
cef8e5de18 History 2022-02-23 22:52:03 +01:00
4952c68ce9 Bump version to 0.0.7 2022-02-23 13:59:56 +01:00
697339768f Recursive container size scan 2022-02-23 13:58:12 +01:00
5734126c31 Search improvements, bugfix 2022-02-22 14:37:14 +01:00
92f8e828b5 Bump version to 0.0.6 2022-02-22 13:23:17 +01:00
2ccd887d87 Search by name/regex 2022-02-22 13:20:21 +01:00
087bc906cf Fixes 2022-02-19 23:41:00 +01:00
6eb02a1a08 Simplify fix time waiting 2022-02-19 23:30:06 +01:00
382d158d7e Display children immediately on container change 2022-02-19 23:25:06 +01:00
cd04e992f1 ContentProvider refactor 2022-02-19 23:00:37 +01:00
13a021c030 Bump version 2022-02-18 23:29:09 +01:00
5a88911ca7 Command refactor, fixes, improvements 2022-02-18 23:28:33 +01:00
502f80313a Remove Type from Differece, CopyCommand refactor 2022-02-18 17:15:11 +01:00
83b86f2b50 AbstractFolder refactor 2022-02-18 16:31:13 +01:00
5072d828e6 Sftp, fullname+nativepath refactor 2022-02-17 23:24:41 +01:00
15dc956064 Bump version 2022-02-17 14:41:05 +01:00
ef5c3a9caf Compression, fixes 2022-02-17 14:40:38 +01:00
f110a8442b Remove get hash 2022-02-16 21:16:49 +01:00
d1ceb95884 Hash functions 2022-02-16 21:03:34 +01:00
27a974e3e2 Bump version 2022-02-16 12:17:25 +01:00
f9a40d5a56 Nuget update 2022-02-16 12:17:03 +01:00
6f2a3d0dc4 Smb write, delete, fixes, error handling 2022-02-16 12:14:19 +01:00
f809f0a640 StreamCopyCommandHandler, Async rename 2022-02-16 00:13:10 +01:00
89d891918c Better error messages 2022-02-15 21:26:13 +01:00
20d26340b0 UI improvements 2022-02-15 20:13:54 +01:00
45defb4021 NativePath, Editor, ToastMessageSink 2022-02-15 19:47:38 +01:00
b11d009195 Item name "..." 2022-02-14 12:07:17 +01:00
9661155cdd Fixes, UI improvements
child container, preview, etc
2022-02-13 21:08:57 +01:00
7019918496 Copy status, fixes 2022-02-13 16:49:59 +01:00
afb72ae491 ContainerViewModel unsubscribe fix 2022-02-13 16:49:38 +01:00
48926e0f17 File name, extension fix 2022-02-13 13:42:48 +01:00
ec03067af1 Smb save servers, auth 2022-02-10 11:24:04 +01:00
70180b9f12 MainPageViewModel refactor, keybindings from config 2022-02-08 17:40:41 +01:00
dda636fd9c Remove Unload on chahnge location/selected item 2022-02-08 11:30:35 +01:00
552362c6b3 Dispose -> Destroy, windows icon color fix 2022-02-08 11:26:35 +01:00
755793c85c Optimalization, file name&extension 2022-02-08 09:53:20 +01:00
2b5ab06b57 Type based attributes 2022-02-07 17:56:00 +01:00
b6d57869b1 Timeline commands 2022-02-07 13:03:01 +01:00
7489e0eb1c Timeline command visual navigation 2022-02-06 22:16:53 +01:00
dfe44cfb16 Key equality check bug fix 2022-02-06 22:02:53 +01:00
c061f658aa Optimization, UI improvements, Run command 2022-02-05 00:04:27 +01:00
bca940372a Logging, Configuration 2022-02-04 15:11:08 +01:00
f9c675eb7c Linux tabs restore 2022-02-04 01:07:55 +01:00
2e43cca9fd Linux improvements 2022-02-04 00:49:25 +01:00
96cba48f04 GUI build. launch 2022-02-04 00:48:30 +01:00
2e832f72d2 Using CancellationToken 2022-02-04 00:17:03 +01:00
3f8309dc0f Material icon metadata generator 2022-02-04 00:16:45 +01:00
478d161b8a UI improvements 2022-02-04 00:16:24 +01:00
2ff1aa366e Modern UI, Loading screen CanRunMessages 2022-02-03 16:49:45 +01:00
1319d0bb98 Enable timerunner 2022-02-03 13:09:00 +01:00
3da246b3ca Icon, Tab state persistence 2022-02-03 13:08:35 +01:00
1ea1012703 Global json, windows publish task 2022-02-03 09:06:46 +01:00
ee0c6f7a0b Help, key bindings 2022-02-02 21:50:10 +01:00
f40c84a123 Command status 2022-02-02 18:59:37 +01:00
d795ddcd11 Exception text, Icons 2022-02-02 13:29:51 +01:00
3e553dd448 IconProvider, container exceptions, refactor 2022-02-02 11:02:37 +01:00
c4bf417ba3 Linux context menu provider 2022-02-01 14:20:10 +01:00
bcf0a32e0a Remove Uno project 2022-02-01 13:44:02 +01:00
f92884d1f6 Merge branch 'feature/avalonia' into main 2022-02-01 13:41:09 +01:00
2682 changed files with 14257 additions and 10851 deletions

51
.vscode/launch.json vendored
View File

@@ -8,55 +8,28 @@
"name": ".NET Core Launch (console)", "name": ".NET Core Launch (console)",
"type": "coreclr", "type": "coreclr",
"request": "launch", "request": "launch",
"preLaunchTask": "build", "preLaunchTask": "build cli",
"program": "${workspaceFolder}/src/ConsoleApp/FileTime.ConsoleUI/bin/Debug/net6.0/FileTime.ConsoleUI.dll", "program": "${workspaceFolder}/src/ConsoleApp/FileTime.ConsoleUI/bin/Debug/net6.0/FileTime.ConsoleUI.dll",
"args": [], "args": [],
"cwd": "${workspaceFolder}/src/ConsoleApp/FileTime.ConsoleUI", "cwd": "${workspaceFolder}/src/ConsoleApp/FileTime.ConsoleUI",
"console": "internalConsole", "console": "internalConsole",
"stopAtEntry": false "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", "name": ".NET Core Attach",
"type": "coreclr", "type": "coreclr",
"request": "attach" "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
View File

@@ -2,7 +2,7 @@
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{ {
"label": "build", "label": "build cli",
"command": "dotnet", "command": "dotnet",
"type": "process", "type": "process",
"args": [ "args": [
@@ -14,7 +14,19 @@
"problemMatcher": "$msCompile" "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", "command": "dotnet",
"type": "process", "type": "process",
"args": [ "args": [
@@ -26,7 +38,7 @@
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
}, },
{ {
"label": "publish singlefile linux", "label": "publish singlefile linux cli",
"command": "dotnet", "command": "dotnet",
"type": "process", "type": "process",
"args": [ "args": [
@@ -43,7 +55,7 @@
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
}, },
{ {
"label": "publish singlefile windows", "label": "publish singlefile windows cli",
"command": "dotnet", "command": "dotnet",
"type": "process", "type": "process",
"args": [ "args": [
@@ -59,6 +71,18 @@
], ],
"problemMatcher": "$msCompile" "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", "label": "watch",
"command": "dotnet", "command": "dotnet",
@@ -71,42 +95,6 @@
"/consoleloggerparameters:NoSummary" "/consoleloggerparameters:NoSummary"
], ],
"problemMatcher": "$msCompile" "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"
},
] ]
} }

View File

@@ -1,26 +1,24 @@
using FileTime.Core.Command; using FileTime.Core.Command;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Providers;
namespace FileTime.App.Core.Clipboard namespace FileTime.App.Core.Clipboard
{ {
public class Clipboard : IClipboard public class Clipboard : IClipboard
{ {
private readonly List<AbsolutePath> _content; private List<AbsolutePath> _content;
public IReadOnlyList<AbsolutePath> Content { get; } public IReadOnlyList<AbsolutePath> Content { get; private set; }
public Type? CommandType { get; private set; } public Type? CommandType { get; private set; }
public Clipboard() public Clipboard()
{ {
_content = new List<AbsolutePath>(); ResetContent();
Content = _content.AsReadOnly();
} }
public void AddContent(AbsolutePath absolutePath) public void AddContent(AbsolutePath absolutePath)
{ {
foreach (var content in _content) foreach (var content in _content)
{ {
if (content.IsEqual(absolutePath)) return; if (content.Equals(absolutePath)) return;
} }
_content.Add(new AbsolutePath(absolutePath)); _content.Add(new AbsolutePath(absolutePath));
@@ -30,7 +28,7 @@ namespace FileTime.App.Core.Clipboard
{ {
for (var i = 0; i < _content.Count; i++) for (var i = 0; i < _content.Count; i++)
{ {
if (_content[i].IsEqual(absolutePath)) if (_content[i].Equals(absolutePath))
{ {
_content.RemoveAt(i--); _content.RemoveAt(i--);
} }
@@ -39,7 +37,7 @@ namespace FileTime.App.Core.Clipboard
public void Clear() public void Clear()
{ {
_content.Clear(); ResetContent();
CommandType = null; CommandType = null;
} }
@@ -47,5 +45,11 @@ namespace FileTime.App.Core.Clipboard
{ {
CommandType = typeof(T); CommandType = typeof(T);
} }
private void ResetContent()
{
_content = new List<AbsolutePath>();
Content = _content.AsReadOnly();
}
} }
} }

View File

@@ -2,34 +2,65 @@ namespace FileTime.App.Core.Command
{ {
public enum Commands public enum Commands
{ {
None,
AutoRefresh,
ChangeTimelineMode,
CloseTab, CloseTab,
Compress,
Copy, Copy,
CopyHash,
CopyPath,
CreateContainer, CreateContainer,
CreateElement, CreateElement,
Cut, Cut,
Edit,
EnterRapidTravel, EnterRapidTravel,
FindByName,
FindByNameRegex,
GoToHome, GoToHome,
GoToPath,
GoToProvider, GoToProvider,
GoToRoot, GoToRoot,
GoUp, GoUp,
Delete, HardDelete,
Mark,
MoveCursorDown, MoveCursorDown,
MoveCursorDownPage, MoveCursorDownPage,
MoveCursorUp, MoveCursorUp,
MoveCursorUpPage, MoveCursorUpPage,
MoveToBottom,
MoveToFirst, MoveToFirst,
MoveToLast, MoveToLast,
MoveToTop, NextTimelineBlock,
NextTimelineCommand,
Open, Open,
OpenInFileBrowser,
OpenOrRun, OpenOrRun,
PasteMerge, PasteMerge,
PasteOverwrite, PasteOverwrite,
PasteSkip, PasteSkip,
Select, PinFavorite,
ToggleHidden, PreviousTimelineBlock,
Rename, PreviousTimelineCommand,
Dummy,
Refresh, Refresh,
Rename,
RunCommand,
ScanContainerSize,
ShowAllShotcut,
SoftDelete,
SwitchToLastTab,
SwitchToTab1,
SwitchToTab2,
SwitchToTab3,
SwitchToTab4,
SwitchToTab5,
SwitchToTab6,
SwitchToTab7,
SwitchToTab8,
TimelinePause,
TimelineRefresh,
TimelineStart,
ToggleAdvancedIcons,
ToggleHidden,
} }
} }

View File

@@ -0,0 +1,9 @@
using FileTime.Core.Models;
namespace FileTime.App.Core.Models
{
public interface IHaveContainer
{
IContainer? Container { get; }
}
}

View File

@@ -35,7 +35,7 @@ namespace FileTime.App.Core.Tab
foreach (var content in _markedItems[container]) foreach (var content in _markedItems[container])
{ {
if (content.IsEqual(path)) return; if (content.Equals(path)) return;
} }
var tabItem = new AbsolutePath(path); var tabItem = new AbsolutePath(path);
@@ -50,7 +50,7 @@ namespace FileTime.App.Core.Tab
var markedItems = _markedItems[container]; var markedItems = _markedItems[container];
for (var i = 0; i < markedItems.Count; i++) for (var i = 0; i < markedItems.Count; i++)
{ {
if (markedItems[i].IsEqual(path)) if (markedItems[i].Equals(path))
{ {
await ItemUnmarked.InvokeAsync(this, markedItems[i]); await ItemUnmarked.InvokeAsync(this, markedItems[i]);
markedItems.RemoveAt(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) public bool ContainsMarkedItem(IContainer container, AbsolutePath path)
{ {
if (!_markedItems.ContainsKey(container)) return false; if (!_markedItems.ContainsKey(container)) return false;
@@ -83,7 +101,7 @@ namespace FileTime.App.Core.Tab
: new List<AbsolutePath>().AsReadOnly(); : new List<AbsolutePath>().AsReadOnly();
} }
public async Task MakrCurrentItem() public async Task MarkCurrentItem()
{ {
var currentLocation = await Tab!.GetCurrentLocation(); var currentLocation = await Tab!.GetCurrentLocation();
if (currentLocation != null) if (currentLocation != null)

View File

@@ -1,9 +1,13 @@
using FileTime.App.Core.Clipboard; using FileTime.App.Core.Clipboard;
using FileTime.Core.Command; using FileTime.Core.Command;
using FileTime.Core.CommandHandlers;
using FileTime.Core.ContainerSizeScanner;
using FileTime.Core.Providers; using FileTime.Core.Providers;
using FileTime.Core.StateManagement; using FileTime.Core.Services;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
using FileTime.Providers.Favorites;
using FileTime.Providers.Local; using FileTime.Providers.Local;
using FileTime.Providers.Sftp;
using FileTime.Providers.Smb; using FileTime.Providers.Smb;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -18,12 +22,31 @@ namespace FileTime.App.Core
return serviceCollection return serviceCollection
.AddSingleton<IClipboard, Clipboard.Clipboard>() .AddSingleton<IClipboard, Clipboard.Clipboard>()
.AddSingleton<TopContainer>() .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<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;
} }
} }
} }

View File

@@ -9,6 +9,9 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
</ItemGroup> </ItemGroup>
<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="..\FileTime.App.Core\FileTime.App.Core.csproj" />
<ProjectReference Include="..\..\Providers\FileTime.Providers.Local\FileTime.Providers.Local.csproj" /> <ProjectReference Include="..\..\Providers\FileTime.Providers.Local\FileTime.Providers.Local.csproj" />
<ProjectReference Include="..\..\Providers\FileTime.Providers.Smb\FileTime.Providers.Smb.csproj" /> <ProjectReference Include="..\..\Providers\FileTime.Providers.Smb\FileTime.Providers.Smb.csproj" />

View File

@@ -67,7 +67,7 @@ namespace FileTime.ConsoleUI.App
{ {
if (_selectedTab != null) if (_selectedTab != null)
{ {
await _tabStates[_selectedTab].MakrCurrentItem(); await _tabStates[_selectedTab].MarkCurrentItem();
} }
} }
@@ -148,13 +148,13 @@ namespace FileTime.ConsoleUI.App
if (!string.IsNullOrWhiteSpace(newContainerName)) if (!string.IsNullOrWhiteSpace(newContainerName))
{ {
await currentLocation.CreateContainer(newContainerName); await currentLocation.CreateContainerAsync(newContainerName);
} }
} }
async Task Validator(string newPath) async Task Validator(string newPath)
{ {
if (await currentLocation.IsExists(newPath)) if (await currentLocation.IsExistsAsync(newPath))
{ {
_coloredConsoleRenderer.ForegroundColor = _styles.ErrorColor; _coloredConsoleRenderer.ForegroundColor = _styles.ErrorColor;
} }
@@ -169,8 +169,16 @@ namespace FileTime.ConsoleUI.App
{ {
IList<AbsolutePath>? itemsToDelete = null; IList<AbsolutePath>? itemsToDelete = null;
var currentSelectedItems = (await _tabStates[_selectedTab!].GetCurrentMarkedItems()).Select(p => p.Resolve()).ToList(); var currentSelectedItems = new List<IItem>();
var currentSelectedItem = await _selectedTab?.GetCurrentSelectedItem(); 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) if (currentSelectedItems.Count > 0)
{ {
var delete = true; var delete = true;

View File

@@ -90,7 +90,7 @@ namespace FileTime.ConsoleUI.App
new CommandBinding("open", Commands.Open, new[] { new ConsoleKeyInfo('→', ConsoleKey.RightArrow, false, false, false) }, Open), new CommandBinding("open", Commands.Open, new[] { new ConsoleKeyInfo('→', ConsoleKey.RightArrow, false, false, false) }, Open),
new CommandBinding( new CommandBinding(
"go to top", "go to top",
Commands.MoveToTop, Commands.MoveToFirst,
new[] new[]
{ {
new ConsoleKeyInfo('g', ConsoleKey.G, false, false, false), new ConsoleKeyInfo('g', ConsoleKey.G, false, false, false),
@@ -99,7 +99,7 @@ namespace FileTime.ConsoleUI.App
MoveCursorToTop), MoveCursorToTop),
new CommandBinding( new CommandBinding(
"go to bottom", "go to bottom",
Commands.MoveToBottom, Commands.MoveToLast,
new[] new[]
{ {
new ConsoleKeyInfo('G', ConsoleKey.G, true, false, false) new ConsoleKeyInfo('G', ConsoleKey.G, true, false, false)
@@ -114,7 +114,7 @@ namespace FileTime.ConsoleUI.App
new ConsoleKeyInfo('h', ConsoleKey.H, false, false, false) new ConsoleKeyInfo('h', ConsoleKey.H, false, false, false)
}, },
ToggleHidden), 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( new CommandBinding(
"copy", "copy",
Commands.Copy, Commands.Copy,

View File

@@ -24,7 +24,7 @@ namespace FileTime.ConsoleUI.App.UI
foreach (var input in fields) foreach (var input in fields)
{ {
_application.MoveToIOLine(); _application.MoveToIOLine();
_coloredConsoleRenderer.Write(input.Text + ": "); _coloredConsoleRenderer.Write(input.Label + ": ");
results.Add(await _consoleReader.ReadText(placeHolder: input.InputType == InputType.Password ? '*' : null)); results.Add(await _consoleReader.ReadText(placeHolder: input.InputType == InputType.Password ? '*' : null));
} }

View File

@@ -43,7 +43,7 @@ namespace FileTime.ConsoleUI.App.UI
if (paneState == null) throw new Exception($"{nameof(paneState)} can not be null"); if (paneState == null) throw new Exception($"{nameof(paneState)} can not be null");
Tab = pane; Tab = pane;
Tab.CurrentLocationChanged.Add((_, _) => { _currentDisplayStartY = 0; return Task.CompletedTask; }); Tab.CurrentLocationChanged.Add((_, _, _) => { _currentDisplayStartY = 0; return Task.CompletedTask; });
TabState = paneState; TabState = paneState;
} }
@@ -70,7 +70,7 @@ namespace FileTime.ConsoleUI.App.UI
if (currentLocation.GetParent() is var parentContainer && parentContainer is not null) if (currentLocation.GetParent() is var parentContainer && parentContainer is not null)
{ {
await parentContainer.Refresh(); await parentContainer.RefreshAsync();
await PrintColumn( await PrintColumn(
currentVirtualContainer != null currentVirtualContainer != null
@@ -96,7 +96,7 @@ namespace FileTime.ConsoleUI.App.UI
if (token.IsCancellationRequested) return; if (token.IsCancellationRequested) return;
await currentLocation.Refresh(); await currentLocation.RefreshAsync();
await CheckAndSetCurrentDisplayStartY(); await CheckAndSetCurrentDisplayStartY();
await PrintColumn( await PrintColumn(
@@ -112,7 +112,7 @@ namespace FileTime.ConsoleUI.App.UI
if (currentSelectedItem is IContainer selectedContainer) if (currentSelectedItem is IContainer selectedContainer)
{ {
await selectedContainer.Refresh(); await selectedContainer.RefreshAsync();
selectedContainer = currentVirtualContainer != null selectedContainer = currentVirtualContainer != null
? currentVirtualContainer.CloneVirtualChainFor(selectedContainer, v => v.IsTransitive) ? currentVirtualContainer.CloneVirtualChainFor(selectedContainer, v => v.IsTransitive)

View File

@@ -8,7 +8,7 @@
<ProjectReference Include="..\..\AppCommon\FileTime.App.DependencyInjection\FileTime.App.DependencyInjection.csproj" /> <ProjectReference Include="..\..\AppCommon\FileTime.App.DependencyInjection\FileTime.App.DependencyInjection.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>

View File

@@ -2,55 +2,68 @@
{ {
public class AsyncEventHandler<TSender, TArg> public class AsyncEventHandler<TSender, TArg>
{ {
private readonly List<Func<TSender, TArg, Task>> _handlers; private readonly object _guard = new();
private readonly Action<Func<TSender, TArg, Task>> _add; private readonly List<Func<TSender, TArg, CancellationToken, Task>> _handlers;
private readonly Action<Func<TSender, TArg, Task>> _remove; 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(); Handlers = _handlers.AsReadOnly();
_add = add ?? AddInternal; _add = add ?? AddInternal;
_remove = remove ?? RemoveInternal; _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); _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); _remove.Invoke(handler);
} }
}
private void AddInternal(Func<TSender, TArg, Task> handler) private void AddInternal(Func<TSender, TArg, CancellationToken, Task> handler)
{ {
_handlers.Add(handler); _handlers.Add(handler);
} }
private void RemoveInternal(Func<TSender, TArg, Task> handler) private void RemoveInternal(Func<TSender, TArg, CancellationToken, Task> handler)
{ {
_handlers.Remove(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); obj.Add(handler);
return obj; 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); obj.Remove(handler);
return obj; return obj;
@@ -58,11 +71,11 @@
} }
public class AsyncEventHandler<TArg> : AsyncEventHandler<object?, TArg> 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 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) { }
} }
} }

View File

@@ -0,0 +1,9 @@
namespace FileTime.Core.Command
{
public enum CanHandleCommand
{
CanHandle,
Fallback,
CanNotHandle
}
}

View 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);
}
}
}

View File

@@ -1,14 +1,19 @@
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
using Microsoft.Extensions.Logging;
namespace FileTime.Core.Command namespace FileTime.Core.Command
{ {
public class CommandExecutor public class CommandExecutor
{ {
private readonly List<ICommandHandler> _commandHandlers; 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(); _commandHandlers = commandHandlers.ToList();
_logger = logger;
} }
public async Task ExecuteCommandAsync(ICommand command, TimeRunner timeRunner) public async Task ExecuteCommandAsync(ICommand command, TimeRunner timeRunner)
@@ -19,7 +24,15 @@ namespace FileTime.Core.Command
} }
else 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);
}
} }
} }
} }

View 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;
}
}
}

View 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);
}
}
}

View 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();
}
}

View 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);
}
}
}
}

View 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);
}
}

View 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!);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -1,25 +1,34 @@
using AsyncEvent;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
namespace FileTime.Core.Command namespace FileTime.Core.Command.CreateContainer
{ {
public class CreateContainerCommand : IExecutableCommand public class CreateContainerCommand : IExecutableCommand
{ {
public AbsolutePath Container { get; } public AbsolutePath Container { get; }
public string NewContainerName { 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) public CreateContainerCommand(AbsolutePath container, string newContainerName)
{ {
Container = container; Container = container;
NewContainerName = newContainerName; NewContainerName = newContainerName;
DisplayLabel = $"Create container {newContainerName}";
} }
public async Task Execute(TimeRunner timeRunner) public async Task Execute(TimeRunner timeRunner)
{ {
var possibleContainer = await Container.Resolve(); var possibleContainer = await Container.ResolveAsync();
if (possibleContainer is IContainer container) if (possibleContainer is IContainer container)
{ {
await container.CreateContainer(NewContainerName); await container.CreateContainerAsync(NewContainerName);
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container)); await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
} }
//TODO: else //TODO: else
@@ -29,18 +38,18 @@ namespace FileTime.Core.Command
{ {
var newDifferences = new List<Difference>() 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)); return Task.FromResult(startPoint.WithDifferences(newDifferences));
} }
public async Task<CanCommandRun> CanRun(PointInTime startPoint) 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 == null) return CanCommandRun.Forceable;
if (resolvedContainer is not IContainer container if (resolvedContainer is not IContainer container
|| await container.IsExists(NewContainerName)) || await container.IsExistsAsync(NewContainerName))
{ {
return CanCommandRun.False; return CanCommandRun.False;
} }

View File

@@ -1,25 +1,33 @@
using AsyncEvent;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
namespace FileTime.Core.Command namespace FileTime.Core.Command.CreateElement
{ {
public class CreateElementCommand : IExecutableCommand public class CreateElementCommand : IExecutableCommand
{ {
public AbsolutePath Container { get; } public AbsolutePath Container { get; }
public string NewElementName { 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) public CreateElementCommand(AbsolutePath container, string newElementName)
{ {
Container = container; Container = container;
NewElementName = newElementName; NewElementName = newElementName;
DisplayLabel = $"Create element {newElementName}";
} }
public async Task Execute(TimeRunner timeRunner) public async Task Execute(TimeRunner timeRunner)
{ {
var possibleContainer = await Container.Resolve(); var possibleContainer = await Container.ResolveAsync();
if (possibleContainer is IContainer container) if (possibleContainer is IContainer container)
{ {
await container.CreateElement(NewElementName); await container.CreateElementAsync(NewElementName);
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container)); await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
} }
} }
@@ -28,18 +36,18 @@ namespace FileTime.Core.Command
{ {
var newDifferences = new List<Difference>() 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)); return Task.FromResult(startPoint.WithDifferences(newDifferences));
} }
public async Task<CanCommandRun> CanRun(PointInTime startPoint) public async Task<CanCommandRun> CanRun(PointInTime startPoint)
{ {
var resolvedContainer = Container.Resolve(); var resolvedContainer = await Container.ResolveAsync();
if (resolvedContainer == null) return CanCommandRun.Forceable; if (resolvedContainer == null) return CanCommandRun.Forceable;
if (resolvedContainer is not IContainer container if (resolvedContainer is not IContainer container
|| await container.IsExists(NewElementName)) || await container.IsExistsAsync(NewElementName))
{ {
return CanCommandRun.False; return CanCommandRun.False;
} }

View 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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -1,10 +1,16 @@
using AsyncEvent;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
namespace FileTime.Core.Command namespace FileTime.Core.Command
{ {
public interface ICommand public interface ICommand
{ {
string DisplayLabel { get; }
IReadOnlyList<string> CanRunMessages { get; }
Task<CanCommandRun> CanRun(PointInTime startPoint); Task<CanCommandRun> CanRun(PointInTime startPoint);
Task<PointInTime> SimulateCommand(PointInTime startPoint); Task<PointInTime> SimulateCommand(PointInTime startPoint);
int Progress { get; }
int CurrentProgress { get; }
AsyncEventHandler ProgressChanged { get; }
} }
} }

View File

@@ -1,3 +1,4 @@
using FileTime.Core.Interactions;
using FileTime.Core.Models; using FileTime.Core.Models;
namespace FileTime.Core.Command namespace FileTime.Core.Command
@@ -5,7 +6,10 @@ namespace FileTime.Core.Command
public interface ITransportationCommand : ICommand public interface ITransportationCommand : ICommand
{ {
IList<AbsolutePath> Sources { get; } IList<AbsolutePath> Sources { get; }
IContainer? Target { get; set;} AbsolutePath? Target { get; set; }
TransportMode? TransportMode { get; set; } TransportMode? TransportMode { get; set; }
bool TargetIsContainer { get; }
List<InputElement> Inputs { get; }
List<object>? InputResults { get; set; }
} }
} }

View 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();
}
}
}

View File

@@ -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();
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -1,27 +1,46 @@
using AsyncEvent; using AsyncEvent;
using FileTime.Core.Helper;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Search;
namespace FileTime.Core.Components namespace FileTime.Core.Components
{ {
public class Tab public class Tab
{ {
private IItem? _currentSelectedItem; 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; 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 string? _lastPath;
private bool _currentlySelecting;
private readonly object _guardSetCurrentSelectedItemCTS = new();
private CancellationTokenSource? _setCurrentSelectedItemCTS;
public int CurrentSelectedIndex { get; private set; } public int CurrentSelectedIndex { get; private set; }
public bool AutoRefresh { get; set; }
public IReadOnlyList<AbsolutePath> History { get; }
public AsyncEventHandler CurrentLocationChanged = new(); 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) public async Task Init(IContainer currentPath)
{ {
await SetCurrentLocation(currentPath); await SetCurrentLocation(currentPath);
} }
public Task<IContainer> GetCurrentLocation() public Task<IContainer> GetCurrentLocation(CancellationToken token = default)
{ {
return Task.FromResult(_currentLocation); return Task.FromResult(_currentLocation);
} }
@@ -33,40 +52,95 @@ namespace FileTime.Core.Components
if (_currentLocation != null) if (_currentLocation != null)
{ {
_currentLocation.Refreshed.Remove(HandleCurrentLocationRefresh); _currentLocation.Refreshed.Remove(HandleCurrentLocationRefresh);
if (_currentLocation is SearchContainer searchContainer)
{
searchContainer.SearchTaskBase.Cancel();
}
} }
_currentLocation = value; _currentLocation = value;
await AddHistoryItem(_currentLocation);
await CurrentLocationChanged.InvokeAsync(this, AsyncEventArgs.Empty); await CurrentLocationChanged.InvokeAsync(this, AsyncEventArgs.Empty);
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; var currentLocationItems = await (await GetCurrentLocation()).GetItems();
await SetCurrentSelectedItem(await GetItemByLastPath() ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null)); 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); _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() public Task<IItem?> GetCurrentSelectedItem()
{ {
return Task.FromResult(_currentSelectedItem); 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; IItem? itemToSelect = null;
if (value != null) if (value != null)
{ {
itemToSelect = (await _currentLocation.GetItems())?.FirstOrDefault(i => itemToSelect = (await _currentLocation.GetItems(token))?.FirstOrDefault(i =>
i.FullName == null && value?.FullName == null i.FullName == null && value?.FullName == null
? i.Name == value?.Name ? i.Name == value?.Name
: i.FullName == value?.FullName); : 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; _currentSelectedItem = itemToSelect;
_lastPath = GetCommonPath(_lastPath, itemToSelect?.FullName); _lastPath = PathHelper.GetLongerPath(_lastPath, itemToSelect?.FullName);
CurrentSelectedIndex = await GetItemIndex(itemToSelect);
await CurrentSelectedItemChanged.InvokeAsync(this, AsyncEventArgs.Empty); 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) public async Task<IItem?> GetItemByLastPath(IContainer? container = null)
@@ -75,118 +149,92 @@ namespace FileTime.Core.Components
var containerFullName = container.FullName; var containerFullName = container.FullName;
if (_lastPath == null if (_lastPath == null
|| !container.IsLoaded
|| (containerFullName != null && !_lastPath.StartsWith(containerFullName)) || (containerFullName != null && !_lastPath.StartsWith(containerFullName))
) )
{ {
return null; return null;
} }
var itemNameToSelect = _lastPath var itemNameToSelect = _lastPath
.Split(Constants.SeparatorChar) .Split(Constants.SeparatorChar)
.Skip( .Skip((containerFullName?.Split(Constants.SeparatorChar).Length) ?? 0)
containerFullName == null
? 0
: containerFullName
.Split(Constants.SeparatorChar)
.Count())
.FirstOrDefault(); .FirstOrDefault();
return (await container.GetItems())?.FirstOrDefault(i => i.Name == itemNameToSelect); return (await container.GetItems())?.FirstOrDefault(i => i.Name == itemNameToSelect);
} }
private string GetCommonPath(string? oldPath, string? newPath) private async Task HandleCurrentLocationRefresh(object? sender, AsyncEventArgs e, CancellationToken token = default)
{
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)
{ {
var currentSelectedName = (await GetCurrentSelectedItem())?.FullName ?? (await GetItemByLastPath())?.FullName; 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) 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) 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) 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) 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(); var possibleItemsToSelect = currentLocationItems.Take(CurrentSelectedIndex).Reverse().Skip(skip).ToList();
if (possibleItemsToSelect.Count == 0) possibleItemsToSelect = currentLocationItems.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(); var possibleItemsToSelect = currentLocationItems.Skip(CurrentSelectedIndex + 1 + skip).ToList();
if (possibleItemsToSelect.Count == 0) possibleItemsToSelect = currentLocationItems.Reverse().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; 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) if (await GetCurrentSelectedItem() != null)
{ {
(await GetCurrentLocation())?.Refresh(); if (token.IsCancellationRequested) return;
IItem? newSelectedItem = null; IItem? newSelectedItem = null;
try
{
_currentlySelecting = true;
if (AutoRefresh && currentLocation != null)
{
await currentLocation.RefreshAsync(token);
if (token.IsCancellationRequested) return;
}
foreach (var item in currentPossibleItems) foreach (var item in currentPossibleItems)
{ {
if (currentLocationItems.FirstOrDefault(i => i.Name == item.Name) is var possibleNewSelectedItem if (currentLocationItems.FirstOrDefault(i => i.Name == item.Name) is var possibleNewSelectedItem
@@ -199,14 +247,18 @@ 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 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) if (lastCurrentLocation is VirtualContainer lastCurrentVirtualContainer)
{ {
await SetCurrentLocation(lastCurrentVirtualContainer.CloneVirtualChainFor(parent, v => v.IsPermanent)); await SetCurrentLocation(lastCurrentVirtualContainer.CloneVirtualChainFor(parent, v => v.IsPermanent));
await SetCurrentSelectedItem(lastCurrentVirtualContainer.GetRealContainer()); await SetCurrentSelectedItem(lastCurrentVirtualContainer.GetRealContainer(), true);
} }
else else
{ {
await SetCurrentLocation(parent); await SetCurrentLocation(parent);
var newCurrentLocation = (await (await GetCurrentLocation()).GetItems())?.FirstOrDefault(i => i.Name == lastCurrentLocation.Name); 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() public async Task Open()
{ {
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; if (_currentSelectedItem is ChildSearchElement searchElement && searchElement.GetParent() is IContainer parentContainer)
if (_currentSelectedItem is IContainer childContainer)
{ {
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 else
{ {
await SetCurrentLocation(childContainer); 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); 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; 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++) for (var i = 0; i < currentLocationItems.Count; i++)
{ {
if (currentLocationItems[i] == item) return i; if (currentLocationItems[i] == item) return i;

View File

@@ -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();
}
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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();
}
}

View 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;
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <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> <ItemGroup>
<ProjectReference Include="..\..\AppCommon\FileTime.App.Style\FileTime.App.Style.csproj" /> <ProjectReference Include="..\..\AppCommon\FileTime.App.Style\FileTime.App.Style.csproj" />
<ProjectReference Include="..\AsyncEvent\AsyncEvent.csproj" /> <ProjectReference Include="..\AsyncEvent\AsyncEvent.csproj" />

View 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;
}
}
}

View 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);
}
}
}

View File

@@ -2,15 +2,38 @@ namespace FileTime.Core.Interactions
{ {
public class InputElement public class InputElement
{ {
public string Text { get; } public string Label { get; }
public InputType InputType { get; } public InputType InputType { get; }
public string? DefaultValue { 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; InputType = inputType;
DefaultValue = defaultValue; 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));
}
} }
} }

View File

@@ -2,8 +2,8 @@ namespace FileTime.Core.Interactions
{ {
public enum InputType public enum InputType
{ {
Text, Options,
Password, Password,
Bool Text,
} }
} }

View File

@@ -3,25 +3,28 @@ using FileTime.Core.Timeline;
namespace FileTime.Core.Models namespace FileTime.Core.Models
{ {
public sealed class AbsolutePath public sealed class AbsolutePath : IEquatable<AbsolutePath>
{ {
public IContentProvider ContentProvider { get; } public IContentProvider ContentProvider { get; }
public IContentProvider? VirtualContentProvider { get; } public IContentProvider? VirtualContentProvider { get; }
public string Path { get; } public string Path { get; }
public AbsolutePathType Type { get; }
public AbsolutePath(AbsolutePath from) public AbsolutePath(AbsolutePath from)
{ {
ContentProvider = from.ContentProvider; ContentProvider = from.ContentProvider;
Path = from.Path; Path = from.Path;
VirtualContentProvider = from.VirtualContentProvider; 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; ContentProvider = contentProvider;
Path = path; Path = path;
VirtualContentProvider = virtualContentProvider; VirtualContentProvider = virtualContentProvider;
Type = type;
} }
public AbsolutePath(IItem item) public AbsolutePath(IItem item)
@@ -43,55 +46,82 @@ namespace FileTime.Core.Models
ContentProvider = item.Provider; ContentProvider = item.Provider;
Path = item.FullName!; 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; var contentProvider = parent.Provider;
IContentProvider? virtualContentProvider; var path = parent.FullName! + Constants.SeparatorChar + childName;
string? path;
if (parent is TimeContainer timeContainer) var virtualContentProvider = parent switch
{ {
contentProvider = timeContainer.Provider; TimeContainer timeContainer => timeContainer.VirtualProvider,
virtualContentProvider = timeContainer.VirtualProvider; _ => null
path = timeContainer.FullName! + Constants.SeparatorChar + childName; };
}
else return new AbsolutePath(contentProvider, path, childType, virtualContentProvider);
{
contentProvider = parent.Provider;
path = parent.FullName! + Constants.SeparatorChar + childName;
virtualContentProvider = null;
} }
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?? var result = VirtualContentProvider != null && (await VirtualContentProvider.IsExistsAsync(Path))
return path.ContentProvider == ContentProvider && path.Path == Path;
}
public async Task<IItem?> Resolve()
{
var result = VirtualContentProvider != null && (await VirtualContentProvider.IsExists(Path))
? await VirtualContentProvider.GetByPath(Path) ? await VirtualContentProvider.GetByPath(Path)
: null; : null;
if (ContentProvider == null) return null;
result ??= await ContentProvider.GetByPath(Path); result ??= await ContentProvider.GetByPath(Path);
return result; return result;
} }
public string GetParent() public string GetParentPath()
{ {
var pathParts = Path.Split(Constants.SeparatorChar); 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 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);
} }
} }

View 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);
}
}
}

View File

@@ -0,0 +1,9 @@
namespace FileTime.Core.Models
{
public enum AbsolutePathType
{
Unknown,
Container,
Element
}
}

View File

@@ -3,5 +3,6 @@ namespace FileTime.Core.Models
public static class Constants public static class Constants
{ {
public const char SeparatorChar = '/'; public const char SeparatorChar = '/';
public const string ContentProviderProtocol = "ctp://";
} }
} }

View 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;
}
}
}

View File

@@ -4,21 +4,57 @@ namespace FileTime.Core.Models
{ {
public interface IContainer : IItem public interface IContainer : IItem
{ {
IReadOnlyList<Exception> Exceptions { get; }
Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default); Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default);
Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default); Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default);
Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default); Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default);
bool AllowRecursiveDeletion { get; }
bool Loading { get; }
bool CanHandleEscape { get; }
Task Refresh(); Task RefreshAsync(CancellationToken token = default);
Task<IItem?> GetByPath(string path); public async Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false)
Task<IContainer> CreateContainer(string name); {
Task<IElement> CreateElement(string name); 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 IsLoaded { get; }
bool SupportsDirectoryLevelSoftDelete { get; }
AsyncEventHandler Refreshed { get; } AsyncEventHandler Refreshed { get; }
AsyncEventHandler<bool> LoadingChanged { get; }
} }
} }

View File

@@ -1,8 +1,15 @@
using FileTime.Core.Providers;
namespace FileTime.Core.Models namespace FileTime.Core.Models
{ {
public interface IElement : IItem public interface IElement : IItem
{ {
bool IsSpecial { get; } 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();
} }
} }

View File

@@ -0,0 +1,8 @@
namespace FileTime.Core.Models
{
public interface IFile : IElement
{
string Attributes { get; }
DateTime CreatedAt { get; }
}
}

View File

@@ -5,12 +5,17 @@ namespace FileTime.Core.Models
public interface IItem public interface IItem
{ {
string Name { get; } string Name { get; }
string DisplayName { get; }
string? FullName { get; } string? FullName { get; }
string? NativePath { get; }
bool IsHidden { get; } bool IsHidden { get; }
bool CanDelete { get; } bool IsDestroyed { get; }
bool IsExists { get; }
SupportsDelete CanDelete { get; }
bool CanRename { get; } bool CanRename { get; }
IContentProvider Provider { get; } IContentProvider Provider { get; }
Task Delete(); void Destroy();
Task Delete(bool hardDelete = false);
Task Rename(string newName); Task Rename(string newName);
IContainer? GetParent(); IContainer? GetParent();
} }

View File

@@ -0,0 +1,7 @@
namespace FileTime.Core.Models
{
public interface IItemWithSize : IItem
{
long? Size { get; }
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.Core.Models
{
public interface ISymlinkElement
{
IItem RealItem { get; }
}
}

View 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;
}
}
}

View File

@@ -0,0 +1,9 @@
namespace FileTime.Core.Models
{
public enum SupportsDelete
{
False,
True,
HardDeleteOnly,
}
}

View File

@@ -20,23 +20,37 @@ namespace FileTime.Core.Models
public IReadOnlyList<IElement>? Elements { get; private set; } public IReadOnlyList<IElement>? Elements { get; private set; }
public string Name => BaseContainer.Name; public string Name => BaseContainer.Name;
public string DisplayName => BaseContainer.DisplayName;
public string? FullName => BaseContainer.FullName; public string? FullName => BaseContainer.FullName;
public string? NativePath => BaseContainer.NativePath;
public bool IsHidden => BaseContainer.IsHidden; public bool IsHidden => BaseContainer.IsHidden;
public bool IsLoaded => BaseContainer.IsLoaded; public bool IsLoaded => BaseContainer.IsLoaded;
public bool CanDelete => BaseContainer.CanDelete; public SupportsDelete CanDelete => BaseContainer.CanDelete;
public bool CanRename => BaseContainer.CanRename; public bool CanRename => BaseContainer.CanRename;
public IContentProvider Provider => BaseContainer.Provider; public IContentProvider Provider => BaseContainer.Provider;
public IReadOnlyList<Exception> Exceptions => BaseContainer.Exceptions;
public bool SupportsDirectoryLevelSoftDelete => BaseContainer.SupportsDirectoryLevelSoftDelete;
public AsyncEventHandler Refreshed { get; } 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); 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); BaseContainer.Refreshed.Add(handler);
} }
@@ -64,10 +78,10 @@ namespace FileTime.Core.Models
await InitItems(); 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(); Containers = _containerTransformators.Aggregate((await BaseContainer.GetContainers(token))?.AsEnumerable(), (a, t) => t(a!))?.ToList()?.AsReadOnly();
Elements = _elementTransformators.Aggregate((await BaseContainer.GetElements())?.AsEnumerable(), (a, t) => t(a!))?.ToList()?.AsReadOnly(); Elements = _elementTransformators.Aggregate((await BaseContainer.GetElements(token))?.AsEnumerable(), (a, t) => t(a!))?.ToList()?.AsReadOnly();
Items = (Elements != null Items = (Elements != null
? Containers?.Cast<IItem>().Concat(Elements) ? Containers?.Cast<IItem>().Concat(Elements)
@@ -75,14 +89,14 @@ namespace FileTime.Core.Models
?.ToList().AsReadOnly(); ?.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 IContainer? GetParent() => BaseContainer.GetParent();
public async Task Refresh() public async Task RefreshAsync(CancellationToken token = default)
{ {
await BaseContainer.Refresh(); await BaseContainer.RefreshAsync(token);
await InitItems(); await InitItems(token);
} }
public IContainer GetRealContainer() => public IContainer GetRealContainer() =>
@@ -133,9 +147,9 @@ namespace FileTime.Core.Models
: baseContainer; : baseContainer;
} }
public async Task<IContainer> CreateContainer(string name) => await BaseContainer.CreateContainer(name); public async Task<IContainer> CreateContainerAsync(string name) => await BaseContainer.CreateContainerAsync(name);
public async Task<IElement> CreateElement(string name) => await BaseContainer.CreateElement(name); public async Task<IElement> CreateElementAsync(string name) => await BaseContainer.CreateElementAsync(name);
public async Task<bool> IsExists(string name) => await BaseContainer.IsExists(name); public async Task<bool> IsExistsAsync(string name) => await BaseContainer.IsExistsAsync(name);
public Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default) public Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
{ {
@@ -150,11 +164,11 @@ namespace FileTime.Core.Models
return Task.FromResult(Elements); return Task.FromResult(Elements);
} }
public async Task Delete() => await BaseContainer.Delete(); public async Task Delete(bool hardDelete = false) => await BaseContainer.Delete();
public async Task<IContainer> Clone() public async Task<IContainer> CloneAsync()
{ {
return new VirtualContainer( return new VirtualContainer(
await BaseContainer.Clone(), await BaseContainer.CloneAsync(),
_containerTransformators, _containerTransformators,
_elementTransformators, _elementTransformators,
IsPermanent, IsPermanent,
@@ -164,5 +178,19 @@ namespace FileTime.Core.Models
} }
public async Task Rename(string newName) => await BaseContainer.Rename(newName); 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);
} }
} }

View File

@@ -0,0 +1,12 @@
namespace FileTime.Core.Persistence
{
public class PersistenceSettings
{
public PersistenceSettings(string rootAppDataPath)
{
RootAppDataPath = rootAppDataPath;
}
public string RootAppDataPath { get; }
}
}

View 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));
}
}

View 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);
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.Core.Providers.ContainerProperty
{
public interface IHaveAttributes
{
string Attributes { get; }
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.Core.Providers.ContainerProperty
{
public interface IHaveCreatedAt
{
DateTime? CreatedAt { get; }
}
}

View 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() { }
}
}

View 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();
}
}
}

View File

@@ -4,9 +4,10 @@ namespace FileTime.Core.Providers
{ {
public interface IContentProvider : IContainer 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); void SetParent(IContainer container);
} }

View 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);
}
}

View 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();
}
}

View 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);
}
}

View File

@@ -1,5 +1,3 @@
using System;
using AsyncEvent; using AsyncEvent;
using FileTime.Core.Models; using FileTime.Core.Models;
@@ -14,9 +12,11 @@ namespace FileTime.Core.Providers
#pragma warning disable CS8603 // Possible null reference return. #pragma warning disable CS8603 // Possible null reference return.
public string Name => null; public string Name => null;
public string DisplayName => null;
#pragma warning restore CS8603 // Possible null reference return. #pragma warning restore CS8603 // Possible null reference return.
public string? FullName => null; public string? FullName => null;
public string? NativePath => null;
public bool IsHidden => false; public bool IsHidden => false;
public bool IsLoaded => true; public bool IsLoaded => true;
@@ -24,11 +24,22 @@ namespace FileTime.Core.Providers
#pragma warning disable CS8603 // Possible null reference return. #pragma warning disable CS8603 // Possible null reference return.
public IContentProvider Provider => null; public IContentProvider Provider => null;
#pragma warning restore CS8603 // Possible null reference return. #pragma warning restore CS8603 // Possible null reference return.
public bool CanDelete => false; public SupportsDelete CanDelete => SupportsDelete.False;
public bool CanRename => false; public bool CanRename => false;
public AsyncEventHandler Refreshed { get; } = new(); 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) public TopContainer(IEnumerable<IContentProvider> contentProviders)
{ {
_contentProviders = new List<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 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<IItem>?> GetItems(CancellationToken token = default) => Task.FromResult(_items);
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default) => Task.FromResult(_containers); public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default) => Task.FromResult(_containers);
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult(_elements); 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 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);
}
} }
} }

View 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();
}
}

View 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();
}
}

View 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));
}
}

View 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);
}
}

View 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));
}
}
}

View 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;
}
}
}

View 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];
}
}
}

View File

@@ -1,7 +0,0 @@
namespace FileTime.Core.StateManagement
{
public class ElementCreationStates
{
}
}

View 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;
}
}
}

View File

@@ -1,3 +1,4 @@
using System.Threading.Tasks;
using FileTime.Core.Command; using FileTime.Core.Command;
namespace FileTime.Core.Timeline namespace FileTime.Core.Timeline
@@ -12,7 +13,7 @@ namespace FileTime.Core.Timeline
public CommandTimeState(ICommand command, PointInTime? startTime) public CommandTimeState(ICommand command, PointInTime? startTime)
{ {
Command = command; Command = command;
UpdateState(startTime).Wait(); Task.Run(async () => await UpdateState(startTime)).Wait();
} }
public async Task UpdateState(PointInTime? startPoint) public async Task UpdateState(PointInTime? startPoint)

View File

@@ -5,14 +5,12 @@ namespace FileTime.Core.Timeline
{ {
public class Difference public class Difference
{ {
public DifferenceItemType Type { get; }
public string Name { get; } public string Name { get; }
public AbsolutePath AbsolutePath { get; } public AbsolutePath AbsolutePath { get; }
public DifferenceActionType Action { get; } public DifferenceActionType Action { get; }
public Difference(DifferenceItemType type, DifferenceActionType action, AbsolutePath absolutePath) public Difference(DifferenceActionType action, AbsolutePath absolutePath)
{ {
Type = type;
AbsolutePath = absolutePath; AbsolutePath = absolutePath;
Action = action; Action = action;
@@ -22,9 +20,8 @@ namespace FileTime.Core.Timeline
public Difference WithVirtualContentProvider(IContentProvider? virtualContentProvider) public Difference WithVirtualContentProvider(IContentProvider? virtualContentProvider)
{ {
return new Difference( return new Difference(
Type,
Action, Action,
new AbsolutePath(AbsolutePath.ContentProvider, AbsolutePath.Path, virtualContentProvider) new AbsolutePath(AbsolutePath.ContentProvider, AbsolutePath.Path, AbsolutePath.Type, virtualContentProvider)
); );
} }
} }

View File

@@ -1,9 +0,0 @@
namespace FileTime.Core.Timeline
{
public enum DifferenceItemType
{
Container,
Element,
Unknown
}
}

View File

@@ -3,9 +3,12 @@ namespace FileTime.Core.Timeline
public class ReadOnlyParallelCommands public class ReadOnlyParallelCommands
{ {
public IReadOnlyList<ReadOnlyCommandTimeState> Commands { get; } public IReadOnlyList<ReadOnlyCommandTimeState> Commands { get; }
public ushort Id { get; }
public ReadOnlyParallelCommands(ParallelCommands parallelCommands) public ReadOnlyParallelCommands(ParallelCommands parallelCommands)
{ {
Commands = parallelCommands.Commands.Select(c => new ReadOnlyCommandTimeState(c)).ToList().AsReadOnly(); Commands = parallelCommands.Commands.Select(c => new ReadOnlyCommandTimeState(c)).ToList().AsReadOnly();
Id = parallelCommands.Id;
} }
} }
} }

View File

@@ -14,38 +14,52 @@ namespace FileTime.Core.Timeline
public AsyncEventHandler Refreshed { get; } = new AsyncEventHandler(); public AsyncEventHandler Refreshed { get; } = new AsyncEventHandler();
public string Name { get; } public string Name { get; }
public string DisplayName { get; }
public string? FullName { get; } public string? FullName { get; }
public bool IsHidden => false; public bool IsHidden => false;
public bool CanDelete => true; public SupportsDelete CanDelete => SupportsDelete.True;
public bool CanRename => true; public bool CanRename => true;
public IContentProvider Provider { get; } public IContentProvider Provider { get; }
public IContentProvider VirtualProvider { 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) public TimeContainer(string name, IContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider, PointInTime pointInTime)
{ {
_parent = parent; _parent = parent;
_pointInTime = pointInTime; _pointInTime = pointInTime;
Name = name; DisplayName = Name = name;
Provider = contentProvider; Provider = contentProvider;
VirtualProvider = virtualContentProvider; VirtualProvider = virtualContentProvider;
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name; 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); var paths = path.Split(Constants.SeparatorChar);
@@ -58,7 +72,7 @@ namespace FileTime.Core.Timeline
if (item is IContainer container) 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; return null;
@@ -69,7 +83,7 @@ namespace FileTime.Core.Timeline
(IReadOnlyList<IContainer>?)_pointInTime (IReadOnlyList<IContainer>?)_pointInTime
.Differences .Differences
.Where(d => .Where(d =>
d.Type == DifferenceItemType.Container d.AbsolutePath.Type == AbsolutePathType.Container
&& GetParentPath(d.AbsolutePath.Path) == FullName) && GetParentPath(d.AbsolutePath.Path) == FullName)
.Select(MapContainer) .Select(MapContainer)
.ToList() .ToList()
@@ -81,7 +95,7 @@ namespace FileTime.Core.Timeline
(IReadOnlyList<IElement>?)_pointInTime (IReadOnlyList<IElement>?)_pointInTime
.Differences .Differences
.Where(d => .Where(d =>
d.Type == DifferenceItemType.Element d.AbsolutePath.Type == AbsolutePathType.Element
&& GetParentPath(d.AbsolutePath.Path) == FullName) && GetParentPath(d.AbsolutePath.Path) == FullName)
.Select(MapElement) .Select(MapElement)
.ToList() .ToList()
@@ -98,9 +112,9 @@ namespace FileTime.Core.Timeline
public IContainer? GetParent() => _parent; 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; public Task Rename(string newName) => Task.CompletedTask;
@@ -108,14 +122,24 @@ namespace FileTime.Core.Timeline
private IContainer MapContainer(Difference containerDiff) 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); return new TimeContainer(containerDiff.Name, this, Provider, containerDiff.AbsolutePath.VirtualContentProvider ?? containerDiff.AbsolutePath.ContentProvider, _pointInTime);
} }
private IElement MapElement(Difference elementDiff) 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); 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);
}
} }
} }

View File

@@ -10,7 +10,7 @@ namespace FileTime.Core.Timeline
{ {
_parent = parent; _parent = parent;
Name = name; DisplayName = Name = name;
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name; FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
Provider = contentProvider; Provider = contentProvider;
VirtualProvider = virtualContentProvider; VirtualProvider = virtualContentProvider;
@@ -19,24 +19,40 @@ namespace FileTime.Core.Timeline
public bool IsSpecial => false; public bool IsSpecial => false;
public string Name { get; } public string Name { get; }
public string DisplayName { get; }
public string? FullName { 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 IsHidden => false;
public bool CanDelete => true; public SupportsDelete CanDelete => SupportsDelete.True;
public bool CanRename => true; public bool CanRename => true;
public IContentProvider Provider { get; } public IContentProvider Provider { get; }
public IContentProvider VirtualProvider { 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 IContainer? GetParent() => _parent;
public string GetPrimaryAttributeText() => ""; public string? GetPrimaryAttributeText() => null;
public Task Rename(string newName) => Task.CompletedTask; 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();
} }
} }

View File

@@ -1,89 +1,30 @@
using AsyncEvent;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Providers; using FileTime.Core.Providers;
namespace FileTime.Core.Timeline 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; private readonly PointInTime _pointInTime;
public TimeProvider(PointInTime pointInTime) public TimeProvider(PointInTime pointInTime) : base("time", "time2://", false)
{ {
_pointInTime = pointInTime; _pointInTime = pointInTime;
} }
public bool CanHandlePath(string path) public override Task<bool> CanHandlePath(string path)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Task<IContainer> Clone() => Task.FromResult((IContainer)this); public override Task<IContainer> CreateContainerAsync(string name)
public Task<IContainer> CreateContainer(string name)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Task<IElement> CreateElement(string name) public override Task<IElement> CreateElementAsync(string name)
{ {
throw new NotImplementedException(); 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) { }
} }
} }

View File

@@ -1,6 +1,8 @@
using System.Threading.Tasks;
using AsyncEvent; using AsyncEvent;
using FileTime.Core.Command; using FileTime.Core.Command;
using FileTime.Core.Models; using FileTime.Core.Models;
using Microsoft.Extensions.Logging;
namespace FileTime.Core.Timeline namespace FileTime.Core.Timeline
{ {
@@ -9,11 +11,14 @@ namespace FileTime.Core.Timeline
private readonly CommandExecutor _commandExecutor; private readonly CommandExecutor _commandExecutor;
private readonly List<ParallelCommands> _commandsToRun = new(); private readonly List<ParallelCommands> _commandsToRun = new();
private readonly object _guard = new(); private readonly object _guard = new();
private readonly ILogger<TimeRunner> _logger;
private bool _resourceIsInUse; private bool _resourceIsInUse;
private readonly List<Thread> _commandRunners = new(); private readonly List<CommandRunner> _commandRunners = new();
private bool _enableRunning = true; private bool _enableRunning = true;
private IReadOnlyList<ReadOnlyParallelCommands> _parallelCommands = new List<ReadOnlyParallelCommands>();
public bool EnableRunning public bool EnableRunning
{ {
get 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 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; _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 () => await RunWithLockAsync(async () =>
{ {
@@ -56,9 +68,9 @@ namespace FileTime.Core.Timeline
batchToAdd = new ParallelCommands(_commandsToRun.Last().Result); batchToAdd = new ParallelCommands(_commandsToRun.Last().Result);
_commandsToRun.Add(batchToAdd); _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 else
{ {
@@ -72,20 +84,14 @@ namespace FileTime.Core.Timeline
{ {
StartCommandRunner(); StartCommandRunner();
} }
});
await UpdateReadOnlyCommands(); await UpdateReadOnlyCommands();
});
} }
public async Task TryStartCommandRunner() public async Task TryStartCommandRunner()
{ {
await RunWithLockAsync(() => await RunWithLockAsync(() => StartCommandRunner());
{
if (_commandRunners.Count == 0 && _commandsToRun.Count > 0)
{
StartCommandRunner();
}
});
} }
private void StartCommandRunner() private void StartCommandRunner()
@@ -98,13 +104,21 @@ namespace FileTime.Core.Timeline
private void RunCommands() private void RunCommands()
{ {
while (_commandsToRun.Count > 0 && _commandsToRun[0].Commands.Count == 0) _commandsToRun.RemoveAt(0);
if (_commandsToRun.Count > 0) if (_commandsToRun.Count > 0)
{ {
foreach (var command in _commandsToRun[0].Commands) 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)); var thread = new Thread(new ParameterizedThreadStart(RunCommand));
_commandRunners.Add(new CommandRunner(thread, command));
thread.Start(command); thread.Start(command);
} }
else else
@@ -123,29 +137,44 @@ namespace FileTime.Core.Timeline
if (arg is CommandTimeState commandToRun2) if (arg is CommandTimeState commandToRun2)
{ {
commandToRun = 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 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) if (command != null)
{ {
_logger.LogDebug("Command finished running: {Command}", command.Command.DisplayLabel);
_commandsToRun[0].Remove(command); _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 UpdateReadOnlyCommands();
StartCommandRunner();
await TryStartCommandRunner(); });
} }
public async Task Refresh() public async Task Refresh()
@@ -153,29 +182,27 @@ namespace FileTime.Core.Timeline
await RunWithLockAsync(async () => await RunWithLockAsync(async () =>
{ {
await RefreshCommands(PointInTime.CreateEmpty()); await RefreshCommands(PointInTime.CreateEmpty());
});
await UpdateReadOnlyCommands(); await UpdateReadOnlyCommands();
});
} }
private async Task RefreshCommands(PointInTime? fullStartTime = null) 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; var startIndex = fullStartTime == null ? 1 : 0;
for (var i = startIndex; i < _commandsToRun.Count; i++) for (var i = startIndex; i < _commandsToRun.Count; i++)
{ {
curretnTime = await _commandsToRun[i].RefreshResult(curretnTime); currentTime = await _commandsToRun[i].RefreshResult(currentTime);
} }
} }
private async Task UpdateReadOnlyCommands() private async Task UpdateReadOnlyCommands()
{ {
await RunWithLockAsync(() => var wait = _commandsToRun.Count == 1;
{ if (wait) await Task.Delay(100);
ParallelCommands = _commandsToRun.ConvertAll(c => new ReadOnlyParallelCommands(c)).AsReadOnly(); _parallelCommands = _commandsToRun.ConvertAll(c => new ReadOnlyParallelCommands(c)).AsReadOnly();
}); await CommandsChangedAsync.InvokeAsync(this, _parallelCommands);
CommandsChanged?.Invoke(this, EventArgs.Empty);
} }
private async Task RunWithLockAsync(Action action) 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();
} }
} }

View 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);
}

File diff suppressed because it is too large Load Diff

View 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>

View 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("}");
}

View 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("}");
}

View File

@@ -31,32 +31,21 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Providers.Smb", "P
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GuiApp", "GuiApp", "{890275FF-943A-4D07-83BA-14E5C52D7846}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GuiApp", "GuiApp", "{890275FF-943A-4D07-83BA-14E5C52D7846}"
EndProject 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}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncEvent", "Core\AsyncEvent\AsyncEvent.csproj", "{9BDAC126-200F-4056-8D35-36EC059B40F3}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Avalonia", "GuiApp\FileTime.Avalonia\FileTime.Avalonia.csproj", "{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Avalonia", "GuiApp\FileTime.Avalonia\FileTime.Avalonia.csproj", "{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}"
EndProject 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 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 GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|ARM = Debug|ARM 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|x64.Build.0 = Release|Any CPU
{35963404-39F6-4CDA-A0FE-A91036FA8DAC}.Release|x86.ActiveCfg = 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 {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.ActiveCfg = Debug|Any CPU
{9BDAC126-200F-4056-8D35-36EC059B40F3}.Debug|Any CPU.Build.0 = 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 {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|x64.Build.0 = Release|Any CPU
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Release|x86.ActiveCfg = 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 {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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -414,15 +373,12 @@ Global
{BEA824B0-7684-44FF-95BF-A75E92A36C9F} = {D4C7E692-53C0-4423-9944-E25FE3D51BA2} {BEA824B0-7684-44FF-95BF-A75E92A36C9F} = {D4C7E692-53C0-4423-9944-E25FE3D51BA2}
{F46D6CE5-4811-45B8-9CD4-3C993318A2E6} = {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} {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} {9BDAC126-200F-4056-8D35-36EC059B40F3} = {38B1B927-4201-4B7A-87EE-737B8C6D4090}
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7} = {890275FF-943A-4D07-83BA-14E5C52D7846} {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 EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8D679DCE-AC84-4A91-BFED-8F8D8E1D8183} SolutionGuid = {8D679DCE-AC84-4A91-BFED-8F8D8E1D8183}

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View 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

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More