From 215503a4e3041d5bba64c64a51d3fce1bd36d5db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Thu, 20 Jan 2022 18:16:01 +0100 Subject: [PATCH] Async core refactor --- .../FileTime.App.Core/Tab/TabState.cs | 12 +- .../Color/ColoredConsoleRenderer.cs | 8 +- .../Application.CommandHandlers.cs | 116 +++++++------ .../FileTime.ConsoleUI.App/Application.cs | 33 ++-- .../Command/CommandBinding.cs | 11 +- .../UI/ConsoleInputInterface.cs | 4 +- .../UI/ConsoleReader.cs | 4 +- .../FileTime.ConsoleUI.App/UI/Render.cs | 74 ++++---- .../UI/RenderSynchronizer.cs | 44 +++++ src/ConsoleApp/FileTime.ConsoleUI/Program.cs | 46 +++-- src/Core/AsyncEvent/AsyncEvent.csproj | 9 + src/Core/AsyncEvent/AsyncEventArgs.cs | 7 + src/Core/AsyncEvent/AsyncEventHandler.cs | 68 ++++++++ src/Core/FileTime.Core/Command/CopyCommand.cs | 21 +-- .../FileTime.Core/Command/DeleteCommand.cs | 16 +- .../Command/IExecutableCommand.cs | 2 +- src/Core/FileTime.Core/Components/Tab.cs | 163 ++++++++++++------ src/Core/FileTime.Core/FileTime.Core.csproj | 1 + .../Interactions/IInputInterface.cs | 2 +- src/Core/FileTime.Core/Models/IContainer.cs | 20 ++- src/Core/FileTime.Core/Models/IItem.cs | 2 +- .../FileTime.Core/Models/VirtualContainer.cs | 67 ++++--- .../Providers/IContentProvider.cs | 2 +- .../FileTime.Core/Providers/TopContainer.cs | 40 +++-- src/FileTime.sln | 23 +++ .../LocalContentProvider.cs | 54 ++++-- .../FileTime.Providers.Local/LocalFile.cs | 3 +- .../FileTime.Providers.Local/LocalFolder.cs | 80 ++++----- .../SmbContentProvider.cs | 51 ++++-- .../FileTime.Providers.Smb/SmbFile.cs | 7 +- .../FileTime.Providers.Smb/SmbFolder.cs | 86 ++++----- .../FileTime.Providers.Smb/SmbServer.cs | 64 ++++--- .../FileTime.Providers.Smb/SmbShare.cs | 84 ++++----- 33 files changed, 761 insertions(+), 463 deletions(-) create mode 100644 src/ConsoleApp/FileTime.ConsoleUI.App/UI/RenderSynchronizer.cs create mode 100644 src/Core/AsyncEvent/AsyncEvent.csproj create mode 100644 src/Core/AsyncEvent/AsyncEventArgs.cs create mode 100644 src/Core/AsyncEvent/AsyncEventHandler.cs diff --git a/src/AppCommon/FileTime.App.Core/Tab/TabState.cs b/src/AppCommon/FileTime.App.Core/Tab/TabState.cs index a16f7ae..2d4ca96 100644 --- a/src/AppCommon/FileTime.App.Core/Tab/TabState.cs +++ b/src/AppCommon/FileTime.App.Core/Tab/TabState.cs @@ -65,9 +65,13 @@ namespace FileTime.App.Core.Tab return false; } - public IReadOnlyList GetCurrentSelectedItems() => - SelectedItems.ContainsKey(Tab.CurrentLocation) - ? SelectedItems[Tab.CurrentLocation] - : new List().AsReadOnly(); + public async Task> GetCurrentSelectedItems() + { + var currentLocation = await Tab.GetCurrentLocation(); + + return SelectedItems.ContainsKey(currentLocation) + ? SelectedItems[currentLocation] + : new List().AsReadOnly(); + } } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Style/Color/ColoredConsoleRenderer.cs b/src/AppCommon/FileTime.App.Style/Color/ColoredConsoleRenderer.cs index cd5ee37..3881576 100644 --- a/src/AppCommon/FileTime.App.Style/Color/ColoredConsoleRenderer.cs +++ b/src/AppCommon/FileTime.App.Style/Color/ColoredConsoleRenderer.cs @@ -71,8 +71,12 @@ namespace FileTime.ConsoleUI.App.UI.Color public void Clear() { - Console.SetCursorPosition(0, 0); - Write(new string(' ', Console.WindowHeight * Console.WindowWidth)); + try + { + Console.SetCursorPosition(0, 0); + Write(new string(' ', Console.WindowHeight * Console.WindowWidth)); + } + catch { } } } } \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/Application.CommandHandlers.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/Application.CommandHandlers.cs index 31ac03f..9d6147d 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/Application.CommandHandlers.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/Application.CommandHandlers.cs @@ -8,12 +8,12 @@ namespace FileTime.ConsoleUI.App { private void CloseTab() { - var currentTabIndex = _panes.IndexOf(_selectedTab!); + var currentTabIndex = _tabs.IndexOf(_selectedTab!); RemoveTab(_selectedTab!); - if (_panes.Count > 0) + if (_tabs.Count > 0) { - _selectedTab = _panes[currentTabIndex == 0 ? 0 : currentTabIndex - 1]; + _selectedTab = _tabs[currentTabIndex == 0 ? 0 : currentTabIndex - 1]; } else { @@ -22,34 +22,36 @@ namespace FileTime.ConsoleUI.App } } - private void MoveCursorUp() => _selectedTab!.SelectPreviousItem(); - private void MoveCursorDown() => _selectedTab!.SelectNextItem(); - private void GoUp() => _selectedTab!.GoUp(); - private void Open() => _selectedTab!.Open(); + private async Task MoveCursorUp() => await _selectedTab!.SelectPreviousItem(); + private async Task MoveCursorDown() => await _selectedTab!.SelectNextItem(); + private async Task GoUp() => await _selectedTab!.GoUp(); + private async Task Open() => await _selectedTab!.Open(); - private void MoveCursorUpPage() => _selectedTab!.SelectPreviousItem(_renderers[_selectedTab].PageSize); - private void MoveCursorDownPage() => _selectedTab!.SelectNextItem(_renderers[_selectedTab].PageSize); - private void MoveCursorToTop() => _selectedTab!.SelectFirstItem(); - private void MoveCursorToBottom() => _selectedTab!.SelectLastItem(); + private async Task MoveCursorUpPage() => await _selectedTab!.SelectPreviousItem(_renderers[_selectedTab].PageSize); + private async Task MoveCursorDownPage() => await _selectedTab!.SelectNextItem(_renderers[_selectedTab].PageSize); + private async Task MoveCursorToTop() => await _selectedTab!.SelectFirstItem(); + private async Task MoveCursorToBottom() => await _selectedTab!.SelectLastItem(); - private void ToggleHidden() + private async Task ToggleHidden() { const string hiddenFilterName = "filter_showhiddenelements"; - IContainer containerToOpen = _selectedTab!.CurrentLocation; + var currentLocation = await _selectedTab!.GetCurrentLocation(); - if (_selectedTab.CurrentLocation is VirtualContainer oldVirtualContainer) + IContainer containerToOpen = currentLocation; + + if (currentLocation is VirtualContainer oldVirtualContainer) { containerToOpen = oldVirtualContainer.HasWithName(hiddenFilterName) ? oldVirtualContainer.ExceptWithName(hiddenFilterName) - : GenerateHiddenFilterVirtualContainer(_selectedTab.CurrentLocation); + : GenerateHiddenFilterVirtualContainer(currentLocation); } else { - containerToOpen = GenerateHiddenFilterVirtualContainer(_selectedTab.CurrentLocation); + containerToOpen = GenerateHiddenFilterVirtualContainer(currentLocation); } - _selectedTab.OpenContainer(containerToOpen); + await _selectedTab.OpenContainer(containerToOpen); static VirtualContainer GenerateHiddenFilterVirtualContainer(IContainer container) { @@ -70,39 +72,42 @@ namespace FileTime.ConsoleUI.App } } - public void Select() + public async Task Select() { - if (_selectedTab!.CurrentSelectedItem != null) + var currentLocation = await _selectedTab!.GetCurrentLocation(); + if (currentLocation != null) { - var currentSelectedItem = _selectedTab.CurrentSelectedItem; - if (_paneStates[_selectedTab].ContainsSelectedItem(currentSelectedItem.Provider, _selectedTab.CurrentLocation, currentSelectedItem.FullName!)) + var currentSelectedItem = await _selectedTab.GetCurrentSelectedItem()!; + if (_paneStates[_selectedTab].ContainsSelectedItem(currentSelectedItem.Provider, currentLocation, currentSelectedItem.FullName!)) { - _paneStates[_selectedTab].RemoveSelectedItem(currentSelectedItem.Provider, _selectedTab.CurrentLocation, currentSelectedItem.FullName!); + _paneStates[_selectedTab].RemoveSelectedItem(currentSelectedItem.Provider, currentLocation, currentSelectedItem.FullName!); } else { - _paneStates[_selectedTab].AddSelectedItem(currentSelectedItem.Provider, _selectedTab.CurrentLocation, currentSelectedItem.FullName!); + _paneStates[_selectedTab].AddSelectedItem(currentSelectedItem.Provider, currentLocation, currentSelectedItem.FullName!); } - _selectedTab.SelectNextItem(); + await _selectedTab.SelectNextItem(); } } - public void Copy() + public async Task Copy() { _clipboard.Clear(); _clipboard.SetCommand(); - if (_paneStates[_selectedTab!].GetCurrentSelectedItems().Count > 0) + var currentSelectedItems = await _paneStates[_selectedTab!].GetCurrentSelectedItems(); + if (currentSelectedItems.Count > 0) { - foreach (var selectedItem in _paneStates[_selectedTab!].GetCurrentSelectedItems()) + foreach (var selectedItem in currentSelectedItems) { _clipboard.AddContent(selectedItem.ContentProvider, selectedItem.Path); } } else { - _clipboard.AddContent(_selectedTab!.CurrentSelectedItem!.Provider, _selectedTab.CurrentSelectedItem.FullName!); + var currentSelectedItem = (await _selectedTab!.GetCurrentSelectedItem())!; + _clipboard.AddContent(currentSelectedItem.Provider, currentSelectedItem.FullName!); } } @@ -112,21 +117,21 @@ namespace FileTime.ConsoleUI.App _clipboard.SetCommand(); } - public void PasteMerge() + public async Task PasteMerge() { - Paste(TransportMode.Merge); + await Paste(TransportMode.Merge); } - public void PasteOverwrite() + public async Task PasteOverwrite() { - Paste(TransportMode.Overwrite); + await Paste(TransportMode.Overwrite); } - public void PasteSkip() + public async Task PasteSkip() { - Paste(TransportMode.Skip); + await Paste(TransportMode.Skip); } - private void Paste(TransportMode transportMode) + private async Task Paste(TransportMode transportMode) { if (_clipboard.CommandType != null) { @@ -140,9 +145,10 @@ namespace FileTime.ConsoleUI.App command.Sources.Add(item); } - command.Target = _selectedTab.CurrentLocation is VirtualContainer virtualContainer + var currentLocation = await _selectedTab!.GetCurrentLocation(); + command.Target = currentLocation is VirtualContainer virtualContainer ? virtualContainer.BaseContainer - : _selectedTab.CurrentLocation; + : currentLocation; _commandExecutor.ExecuteCommand(command); @@ -150,24 +156,25 @@ namespace FileTime.ConsoleUI.App } } - private void CreateContainer() + private async Task CreateContainer() { - if (_selectedTab?.CurrentLocation != null) + var currentLocation = await _selectedTab?.GetCurrentLocation(); + if (currentLocation != null) { _coloredConsoleRenderer.ResetColor(); MoveToIOLine(2); _coloredConsoleRenderer.Write("New container name: "); - var newContainerName = _consoleReader.ReadText(validator: Validator); + var newContainerName = await _consoleReader.ReadText(validator: Validator); if (!string.IsNullOrWhiteSpace(newContainerName)) { - _selectedTab.CurrentLocation.CreateContainer(newContainerName); + await currentLocation.CreateContainer(newContainerName); } } - void Validator(string newPath) + async Task Validator(string newPath) { - if (_selectedTab!.CurrentLocation.IsExists(newPath)) + if (await currentLocation.IsExists(newPath)) { _coloredConsoleRenderer.ForegroundColor = _styles.ErrorColor; } @@ -178,30 +185,33 @@ namespace FileTime.ConsoleUI.App } } - private void HardDelete() + private async Task HardDelete() { - IList itemsToDelete = null; + IList? itemsToDelete = null; - if (_paneStates[_selectedTab!].GetCurrentSelectedItems().Count > 0) + var currentSelectedItems = await _paneStates[_selectedTab!].GetCurrentSelectedItems(); + var currentSelectedItem = await _selectedTab?.GetCurrentSelectedItem(); + if (currentSelectedItems.Count > 0) { var delete = true; - if (_paneStates[_selectedTab!].GetCurrentSelectedItems().Count == 1 - && _paneStates[_selectedTab!].GetCurrentSelectedItems()[0] is IContainer container - && container.Items.Count > 0) + //FIXME: check 'is Container' + if (currentSelectedItems.Count == 1 + && currentSelectedItems[0] is IContainer container + && (await container.GetItems())?.Count > 0) { delete = AskForApprove($"The container '{container.Name}' is not empty."); } if (delete) { - itemsToDelete = _paneStates[_selectedTab].GetCurrentSelectedItems().Cast().ToList(); + itemsToDelete = currentSelectedItems.Cast().ToList(); } } - else if (_selectedTab?.CurrentSelectedItem != null) + else if (currentSelectedItem != null) { bool delete = true; - if (_selectedTab?.CurrentSelectedItem is IContainer container && container.Items.Count > 0) + if (currentSelectedItem is IContainer container && (await container.GetItems())?.Count > 0) { delete = AskForApprove($"The container '{container.Name}' is not empty."); } @@ -210,7 +220,7 @@ namespace FileTime.ConsoleUI.App { itemsToDelete = new List() { - new AbsolutePath(_selectedTab.CurrentSelectedItem.Provider, _selectedTab.CurrentSelectedItem.FullName!) + new AbsolutePath(currentSelectedItem.Provider, currentSelectedItem.FullName!) }; } } diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/Application.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/Application.cs index fceed1e..287e016 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/Application.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/Application.cs @@ -15,7 +15,7 @@ namespace FileTime.ConsoleUI.App { public partial class Application { - private readonly List _panes = new(); + private readonly List _tabs = new(); private readonly Dictionary _renderers = new(); private readonly Dictionary _paneStates = new(); private Tab? _selectedTab; @@ -48,29 +48,30 @@ namespace FileTime.ConsoleUI.App InitCommandBindings(); } - public void SetContainer(IContainer currentPath) + public async Task SetContainer(IContainer currentPath) { - _selectedTab = CreateTab(currentPath); + _selectedTab = await CreateTab(currentPath); } - private Tab CreateTab(IContainer container) + private async Task CreateTab(IContainer container) { - var pane = new Tab(container); - _panes.Add(pane); + var tab = new Tab(); + await tab.Init(container); + _tabs.Add(tab); - var paneState = new TabState(pane); - _paneStates.Add(pane, paneState); + var paneState = new TabState(tab); + _paneStates.Add(tab, paneState); var renderer = _serviceProvider.GetService()!; - renderer.Init(pane, paneState); - _renderers.Add(pane, renderer); + renderer.Init(tab, paneState); + _renderers.Add(tab, renderer); - return pane; + return tab; } private void RemoveTab(Tab pane) { - _panes.Remove(pane); + _tabs.Remove(pane); _renderers.Remove(pane); _paneStates.Remove(pane); } @@ -181,15 +182,15 @@ namespace FileTime.ConsoleUI.App _commandBindings.AddRange(commandBindings); } - public void PrintUI() + public async Task PrintUI(CancellationToken token = default) { if (_selectedTab != null) { - _renderers[_selectedTab].PrintUI(); + await _renderers[_selectedTab].PrintUI(token); } } - public bool ProcessKey(ConsoleKeyInfo keyinfo) + public async Task ProcessKey(ConsoleKeyInfo keyinfo) { var key = keyinfo.Key; _previousKeys.Add(keyinfo); @@ -208,7 +209,7 @@ namespace FileTime.ConsoleUI.App } else if (selectedCommandBinding != null) { - selectedCommandBinding.Invoke(); + await selectedCommandBinding.InvokeAsync(); _previousKeys.Clear(); } else diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/Command/CommandBinding.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/Command/CommandBinding.cs index 1eec568..88da993 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/Command/CommandBinding.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/Command/CommandBinding.cs @@ -4,14 +4,14 @@ namespace FileTime.ConsoleUI.App.Command { public class CommandBinding { - private readonly Action _commandHandler; + private readonly Func _commandHandler; public string Name { get; } public ConsoleKeyInfo[] Keys { get; } public Commands Command { get; } - public CommandBinding(string name, Commands command, ConsoleKeyInfo[] keys, Action commandHandler) + public CommandBinding(string name, Commands command, ConsoleKeyInfo[] keys, Func commandHandler) { Name = name; Command = command; @@ -19,6 +19,11 @@ namespace FileTime.ConsoleUI.App.Command _commandHandler = commandHandler; } - public void Invoke() => _commandHandler(); + public CommandBinding(string name, Commands command, ConsoleKeyInfo[] keys, Action commandHandler) + : this(name, command, keys, () => { commandHandler(); return Task.CompletedTask; }) + { + } + + public async Task InvokeAsync() => await _commandHandler(); } } \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/UI/ConsoleInputInterface.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/UI/ConsoleInputInterface.cs index 1bb39ac..300c997 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/UI/ConsoleInputInterface.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/UI/ConsoleInputInterface.cs @@ -15,7 +15,7 @@ namespace FileTime.ConsoleUI.App.UI _coloredConsoleRenderer = coloredConsoleRenderer; _consoleReader = consoleReader; } - public string?[] ReadInputs(IEnumerable fields) + public async Task ReadInputs(IEnumerable fields) { var results = new List(); @@ -26,7 +26,7 @@ namespace FileTime.ConsoleUI.App.UI _application.MoveToIOLine(); _coloredConsoleRenderer.Write(input.Text + ": "); - results.Add(_consoleReader.ReadText(placeHolder: input.InputType == InputType.Password ? '*' : null)); + results.Add(await _consoleReader.ReadText(placeHolder: input.InputType == InputType.Password ? '*' : null)); } return results.ToArray(); diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/UI/ConsoleReader.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/UI/ConsoleReader.cs index 5a8bc5e..52e0ba8 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/UI/ConsoleReader.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/UI/ConsoleReader.cs @@ -10,7 +10,7 @@ namespace FileTime.ConsoleUI.App.UI { _coloredConsoleRenderer = coloredConsoleRenderer; } - public string? ReadText(int? maxLength = null, Action? validator = null, char? placeHolder = null) + public async Task ReadText(int? maxLength = null, Func? validator = null, char? placeHolder = null) { var cursorVisible = false; try @@ -73,7 +73,7 @@ namespace FileTime.ConsoleUI.App.UI position++; } - validator?.Invoke(input); + await validator?.Invoke(input); Console.SetCursorPosition(currentConsoleLeft, currentConsoleTop); _coloredConsoleRenderer.Write($"{{0,-{maxLength}}}", placeHolder == null ? input : new string((char)placeHolder, input.Length)); diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/UI/Render.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/UI/Render.cs index 9f0dbee..2b59d04 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/UI/Render.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/UI/Render.cs @@ -48,33 +48,37 @@ namespace FileTime.ConsoleUI.App.UI TabState = paneState; } - public void PrintUI() + public async Task PrintUI(CancellationToken token = default) { if (Tab != null) { - PrintPrompt(); - PrintTabs(); + await PrintPrompt(token); + await PrintTabs(token); } } - private void PrintTabs() + private async Task PrintTabs(CancellationToken token = default) { var previousColumnWidth = (int)Math.Floor(Console.WindowWidth * 0.15) - 1; var currentColumnWidth = (int)Math.Floor(Console.WindowWidth * 0.4) - 1; var nextColumnWidth = Console.WindowWidth - currentColumnWidth - previousColumnWidth - 2; - var currentVirtualContainer = Tab!.CurrentLocation as VirtualContainer; - if (Tab.CurrentLocation.GetParent() is var parentContainer && parentContainer is not null) + var currentLocation = (await Tab!.GetCurrentLocation())!; + var currentSelectedItem = (await Tab!.GetCurrentSelectedItem())!; + + var currentVirtualContainer = currentLocation as VirtualContainer; + + if (currentLocation.GetParent() is var parentContainer && parentContainer is not null) { - parentContainer.Refresh(); + await parentContainer.Refresh(); - PrintColumn( + await PrintColumn( currentVirtualContainer != null ? currentVirtualContainer.CloneVirtualChainFor(parentContainer, v => v.IsTransitive) : parentContainer, currentVirtualContainer != null ? currentVirtualContainer.GetRealContainer() - : Tab.CurrentLocation, + : currentLocation, PrintMode.Previous, 0, _contentPaddingTop, @@ -90,29 +94,35 @@ namespace FileTime.ConsoleUI.App.UI _contentRowCount); } - Tab.CurrentLocation.Refresh(); + if (token.IsCancellationRequested) return; - CheckAndSetCurrentDisplayStartY(); - PrintColumn( - Tab.CurrentLocation, - Tab.CurrentSelectedItem, + await currentLocation.Refresh(); + + await CheckAndSetCurrentDisplayStartY(); + await PrintColumn( + currentLocation, + currentSelectedItem, PrintMode.Current, previousColumnWidth + 1, _contentPaddingTop, currentColumnWidth, _contentRowCount); - if (Tab.CurrentSelectedItem is IContainer selectedContainer) + if (token.IsCancellationRequested) return; + + if (currentSelectedItem is IContainer selectedContainer) { - selectedContainer.Refresh(); + await selectedContainer.Refresh(); selectedContainer = currentVirtualContainer != null ? currentVirtualContainer.CloneVirtualChainFor(selectedContainer, v => v.IsTransitive) : selectedContainer; - PrintColumn( + var selectedContainerItems = (await selectedContainer.GetItems())!; + + await PrintColumn( selectedContainer, - selectedContainer.Items.Count > 0 ? selectedContainer.Items[0] : null, + selectedContainerItems.Count > 0 ? selectedContainerItems[0] : null, PrintMode.Next, previousColumnWidth + currentColumnWidth + 2, _contentPaddingTop, @@ -129,8 +139,11 @@ namespace FileTime.ConsoleUI.App.UI } } - private void PrintPrompt() + private async Task PrintPrompt(CancellationToken token = default) { + var currentLocation = await Tab!.GetCurrentLocation(); + var currentSelectedItem = await Tab!.GetCurrentSelectedItem(); + Console.SetCursorPosition(0, 0); _coloredRenderer.ResetColor(); _coloredRenderer.ForegroundColor = _appStyle.AccentForeground; @@ -140,24 +153,24 @@ namespace FileTime.ConsoleUI.App.UI _coloredRenderer.Write(' '); _coloredRenderer.ForegroundColor = _appStyle.ContainerForeground; - var path = Tab!.CurrentLocation.FullName + "/"; + var path = currentLocation.FullName + "/"; _coloredRenderer.Write(path); - if (Tab.CurrentSelectedItem?.Name != null) + if (currentSelectedItem?.Name != null) { _coloredRenderer.ResetColor(); - _coloredRenderer.Write($"{{0,-{300 - path.Length}}}", Tab.CurrentSelectedItem.Name); + _coloredRenderer.Write($"{{0,-{300 - path.Length}}}", currentSelectedItem.Name); } } - private void PrintColumn(IContainer currentContainer, IItem? currentItem, PrintMode printMode, int startX, int startY, int elementWidth, int availableRows) + private async Task PrintColumn(IContainer currentContainer, IItem? currentItem, PrintMode printMode, int startX, int startY, int elementWidth, int availableRows, CancellationToken token = default) { - var allItem = currentContainer.Containers.Cast().Concat(currentContainer.Elements).ToList(); + var allItem = (await currentContainer.GetItems())!.ToList(); var printedItemsCount = 0; var currentY = 0; if (allItem.Count > 0) { - var currentIndex = allItem.FindIndex(i => i == currentItem); + var currentIndex = allItem.FindIndex(i => i.Name == currentItem?.Name); var skipElements = printMode switch { @@ -169,7 +182,7 @@ namespace FileTime.ConsoleUI.App.UI var maxTextWidth = elementWidth - ITEMPADDINGLEFT - ITEMPADDINGRIGHT; - var itemsToPrint = currentContainer.Items.Skip(skipElements).Take(availableRows).ToList(); + var itemsToPrint = (await currentContainer.GetItems())!.Skip(skipElements).Take(availableRows).ToList(); printedItemsCount = itemsToPrint.Count; foreach (var item in itemsToPrint) { @@ -182,7 +195,7 @@ namespace FileTime.ConsoleUI.App.UI var container = item as IContainer; var element = item as IElement; - attributePart = container != null ? "" + container.Items.Count : element!.GetPrimaryAttributeText(); + attributePart = container != null ? "" + (await container.GetItems())!.Count : element!.GetPrimaryAttributeText(); IConsoleColor? backgroundColor = null; IConsoleColor? foregroundColor = null; @@ -213,7 +226,7 @@ namespace FileTime.ConsoleUI.App.UI foregroundColor = _appStyle.SelectedItemForeground; } - if (item == currentItem) + if (item.Name == currentItem?.Name) { (backgroundColor, foregroundColor) = (foregroundColor, backgroundColor); } @@ -224,6 +237,7 @@ namespace FileTime.ConsoleUI.App.UI var text = string.Format($"{{0,-{elementWidth}}}", _paddingLeft + (isSelected ? " " : "") + namePart + _paddingRight); text = string.Concat(text.AsSpan(0, text.Length - attributePart.Length - 1), " ", attributePart); + if (token.IsCancellationRequested) return; Console.SetCursorPosition(startX, startY + currentY++); _coloredRenderer.Write(text); @@ -262,7 +276,7 @@ namespace FileTime.ConsoleUI.App.UI } } - private void CheckAndSetCurrentDisplayStartY() + private async Task CheckAndSetCurrentDisplayStartY() { const int padding = 5; @@ -273,7 +287,7 @@ namespace FileTime.ConsoleUI.App.UI } while (Tab.CurrentSelectedIndex > _currentDisplayStartY + _contentRowCount - padding - && _currentDisplayStartY < Tab.CurrentLocation.Items.Count - _contentRowCount) + && _currentDisplayStartY < (await (await Tab.GetCurrentLocation()).GetItems())!.Count - _contentRowCount) { _currentDisplayStartY++; } diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/UI/RenderSynchronizer.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/UI/RenderSynchronizer.cs new file mode 100644 index 0000000..4f5c8c5 --- /dev/null +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/UI/RenderSynchronizer.cs @@ -0,0 +1,44 @@ +namespace FileTime.ConsoleUI.App.UI +{ + public class RenderSynchronizer + { + private readonly object renderLock = new(); + private readonly Application _application; + private bool _needsRender; + + private CancellationTokenSource renderCancellationSource = new(); + + public RenderSynchronizer(Application application) + { + _application = application; + } + public async void Start() + { + while (_application.IsRunning) + { + if (_needsRender) + { + lock (renderLock) + { + _needsRender = false; + renderCancellationSource = new(); + } + + await _application.PrintUI(renderCancellationSource.Token); + } + } + } + + public void NeedsReRender() + { + lock (renderLock) + { + _needsRender = true; + if (renderCancellationSource.Token.CanBeCanceled) + { + renderCancellationSource.Cancel(); + } + } + } + } +} \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI/Program.cs b/src/ConsoleApp/FileTime.ConsoleUI/Program.cs index c911dc8..0b37c69 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI/Program.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI/Program.cs @@ -15,63 +15,83 @@ namespace FileTime.ConsoleUI public class Program { static ILogger? _logger; + static Thread? _renderThread; - public static void Main() + public static async Task Main() { var serviceProvider = CreateServiceProvider(); _logger = serviceProvider.GetService>()!; var coloredConsoleRenderer = serviceProvider.GetService()!; var localContentProvider = serviceProvider.GetService()!; + var renderSynchronizer = serviceProvider.GetService()!; var currentPath = Environment.CurrentDirectory.Replace(Path.DirectorySeparatorChar, Constants.SeparatorChar); _logger.LogInformation("Current directory: '{0}'", currentPath); - var currentPossibleDirectory = localContentProvider.GetByPath(currentPath); + var currentPossibleDirectory = await localContentProvider.GetByPath(currentPath); if (currentPossibleDirectory is IContainer container) { serviceProvider.GetService(); coloredConsoleRenderer.Clear(); - Console.CursorVisible = false; + try + { + Console.CursorVisible = false; + } + catch { } var app = serviceProvider.GetService()!; - app.SetContainer(container); - app.PrintUI(); + await app.SetContainer(container); + renderSynchronizer.NeedsReRender(); + + _renderThread = new Thread(new ThreadStart(renderSynchronizer.Start)); + _renderThread.Start(); while (app.IsRunning) { - if (app.ProcessKey(Console.ReadKey(true))) + if (await app.ProcessKey(Console.ReadKey(true))) { - app.PrintUI(); + renderSynchronizer.NeedsReRender(); } } + renderSynchronizer.NeedsReRender(); + Console.SetCursorPosition(0, Console.WindowHeight - 1); Console.CursorVisible = true; } else { - Console.WriteLine("Current working directory is not a directory???"); + Console.WriteLine("Current working directory is not a directory??? Possible directory's type is: '" + currentPossibleDirectory?.GetType() + "'"); Thread.Sleep(100); } } private static bool IsAnsiColorSupported() { - Console.CursorLeft = 0; - Console.CursorTop = 0; + try + { + Console.CursorLeft = 0; + Console.CursorTop = 0; - Console.Write("\u001b[0ma"); + Console.Write("\u001b[0ma"); - return Console.CursorLeft == 1 && Console.CursorTop == 0; + return Console.CursorLeft == 1 && Console.CursorTop == 0; + } + catch + { + return false; + } } private static ServiceProvider CreateServiceProvider() { return DependencyInjection.RegisterDefaultServices() - .AddLogging(/* (builder) => builder.AddConsole().AddDebug() */) + //.AddLogging() + .AddLogging((builder) => builder.AddConsole().AddDebug()) .AddSingleton() + .AddSingleton() .AddSingleton(new Styles(IsAnsiColorSupported())) .AddSingleton() .AddSingleton() diff --git a/src/Core/AsyncEvent/AsyncEvent.csproj b/src/Core/AsyncEvent/AsyncEvent.csproj new file mode 100644 index 0000000..132c02c --- /dev/null +++ b/src/Core/AsyncEvent/AsyncEvent.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/src/Core/AsyncEvent/AsyncEventArgs.cs b/src/Core/AsyncEvent/AsyncEventArgs.cs new file mode 100644 index 0000000..e7ffa12 --- /dev/null +++ b/src/Core/AsyncEvent/AsyncEventArgs.cs @@ -0,0 +1,7 @@ +namespace AsyncEvent +{ + public class AsyncEventArgs + { + public static AsyncEventArgs Empty = new AsyncEventArgs(); + } +} \ No newline at end of file diff --git a/src/Core/AsyncEvent/AsyncEventHandler.cs b/src/Core/AsyncEvent/AsyncEventHandler.cs new file mode 100644 index 0000000..bb05bd8 --- /dev/null +++ b/src/Core/AsyncEvent/AsyncEventHandler.cs @@ -0,0 +1,68 @@ +namespace AsyncEvent +{ + public class AsyncEventHandler where TArg : AsyncEventArgs + { + private readonly List> _handlers; + private readonly Action> _add; + private readonly Action> _remove; + + public IReadOnlyList> Handlers { get; } + + public AsyncEventHandler(Action>? add = null, Action>? remove = null) + { + _handlers = new List>(); + Handlers = _handlers.AsReadOnly(); + _add = add ?? AddInternal; + _remove = remove ?? RemoveInternal; + } + + public void Add(Func handler) + { + _add.Invoke(handler); + } + + public void Remove(Func handler) + { + _remove.Invoke(handler); + } + + private void AddInternal(Func handler) + { + _handlers.Add(handler); + } + + private void RemoveInternal(Func handler) + { + _handlers.Remove(handler); + } + + public async Task InvokeAsync(TSender sender, TArg args) + { + foreach(var handler in _handlers) + { + await handler(sender, args); + } + } + + public static AsyncEventHandler operator +(AsyncEventHandler obj, Func handler) + { + obj.Add(handler); + return obj; + } + + public static AsyncEventHandler operator -(AsyncEventHandler obj, Func handler) + { + obj.Remove(handler); + return obj; + } + } + public class AsyncEventHandler : AsyncEventHandler where TArg : AsyncEventArgs + { + public AsyncEventHandler(Action>? add = null, Action>? remove = null) : base(add, remove) { } + } + + public class AsyncEventHandler : AsyncEventHandler + { + public AsyncEventHandler(Action>? add = null, Action>? remove = null) : base(add, remove) { } + } +} diff --git a/src/Core/FileTime.Core/Command/CopyCommand.cs b/src/Core/FileTime.Core/Command/CopyCommand.cs index a7c24b8..fc88879 100644 --- a/src/Core/FileTime.Core/Command/CopyCommand.cs +++ b/src/Core/FileTime.Core/Command/CopyCommand.cs @@ -16,38 +16,39 @@ namespace FileTime.Core.Command throw new NotImplementedException(); } - public void Execute(Action copy) + public async Task Execute(Action copy) { - DoCopy(Sources, Target, TransportMode, copy); + await DoCopy(Sources, Target, TransportMode, copy); } - private void DoCopy(IEnumerable sources, IContainer target, TransportMode transportMode, Action copy) + private async Task DoCopy(IEnumerable sources, IContainer target, TransportMode transportMode, Action copy) { foreach (var source in sources) { - var item = source.ContentProvider.GetByPath(source.Path); + var item = await source.ContentProvider.GetByPath(source.Path); if (item is IContainer container) { - var targetContainer = target.Containers.FirstOrDefault(d => d.Name == container.Name) ?? (target.CreateContainer(container.Name)!); + var targetContainer = (await target.GetContainers())?.FirstOrDefault(d => d.Name == container.Name) ?? (await target.CreateContainer(container.Name)!); - var childDirectories = container.Containers.Select(d => new AbsolutePath(item.Provider, d.FullName!)); - var childFiles = container.Elements.Select(f => new AbsolutePath(item.Provider, f.FullName!)); + var childDirectories = (await container.GetContainers())!.Select(d => new AbsolutePath(item.Provider, d.FullName!)); + var childFiles = (await container.GetElements())!.Select(f => new AbsolutePath(item.Provider, f.FullName!)); - DoCopy(childDirectories.Concat(childFiles), targetContainer, transportMode, copy); + await DoCopy(childDirectories.Concat(childFiles), targetContainer, transportMode, copy); } else if (item is IElement element) { var targetName = element.Name; + var targetNameExists = await target.IsExists(targetName); if (transportMode == TransportMode.Merge) { - for (var i = 0; target.IsExists(targetName); i++) + for (var i = 0; targetNameExists; i++) { targetName = element.Name + (i == 0 ? "_" : $"_{i}"); } } - else if (transportMode == TransportMode.Skip && target.IsExists(targetName)) + else if (transportMode == TransportMode.Skip && targetNameExists) { continue; } diff --git a/src/Core/FileTime.Core/Command/DeleteCommand.cs b/src/Core/FileTime.Core/Command/DeleteCommand.cs index 412b3ec..c8ceba1 100644 --- a/src/Core/FileTime.Core/Command/DeleteCommand.cs +++ b/src/Core/FileTime.Core/Command/DeleteCommand.cs @@ -12,29 +12,29 @@ namespace FileTime.Core.Command throw new NotImplementedException(); } - public void Execute() + public async Task Execute() { foreach (var item in ItemsToDelete) { - DoDelete(item.ContentProvider.GetByPath(item.Path)!); + await DoDelete(await item.ContentProvider.GetByPath(item.Path)!); } } - private void DoDelete(IItem item) + private async Task DoDelete(IItem item) { if (item is IContainer container) { - foreach (var child in container.Items) + foreach (var child in await container.GetItems()) { - DoDelete(child); - child.Delete(); + await DoDelete(child); + await child.Delete(); } - item.Delete(); + await item.Delete(); } else if(item is IElement element) { - element.Delete(); + await element.Delete(); } } } diff --git a/src/Core/FileTime.Core/Command/IExecutableCommand.cs b/src/Core/FileTime.Core/Command/IExecutableCommand.cs index e9a74bd..05eef55 100644 --- a/src/Core/FileTime.Core/Command/IExecutableCommand.cs +++ b/src/Core/FileTime.Core/Command/IExecutableCommand.cs @@ -2,6 +2,6 @@ namespace FileTime.Core.Command { public interface IExecutableCommand : ICommand { - void Execute(); + Task Execute(); } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Components/Tab.cs b/src/Core/FileTime.Core/Components/Tab.cs index 45c1678..b0565c9 100644 --- a/src/Core/FileTime.Core/Components/Tab.cs +++ b/src/Core/FileTime.Core/Components/Tab.cs @@ -1,3 +1,4 @@ +using AsyncEvent; using FileTime.Core.Models; namespace FileTime.Core.Components @@ -7,7 +8,7 @@ namespace FileTime.Core.Components private IItem? _currentSelectedItem; private IContainer _currentLocation; - public IContainer CurrentLocation + /* public IContainer CurrentLocation { get => _currentLocation; private set @@ -38,75 +39,122 @@ namespace FileTime.Core.Components CurrentSelectedItemChanged?.Invoke(this, EventArgs.Empty); } } - } + } */ public int CurrentSelectedIndex { get; private set; } public event EventHandler CurrentLocationChanged; public event EventHandler CurrentSelectedItemChanged; - public Tab(IContainer currentPath) + public async Task Init(IContainer currentPath) { - CurrentLocation = currentPath; - CurrentSelectedItem = CurrentLocation.Items.Count > 0 ? CurrentLocation.Items[0] : null; + await SetCurrentLocation(currentPath); } - private void HandleCurrentLocationRefresh(object? sender, EventArgs e) + public Task GetCurrentLocation() { - var currentSelectedName = CurrentSelectedItem?.FullName; + return Task.FromResult(_currentLocation); + } + + public async Task SetCurrentLocation(IContainer value) + { + if (_currentLocation != value) + { + if (_currentLocation != null) + { + _currentLocation.Refreshed.Remove(HandleCurrentLocationRefresh); + } + + _currentLocation = value; + CurrentLocationChanged?.Invoke(this, EventArgs.Empty); + + var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; + await SetCurrentSelectedItem(currentLocationItems.Count > 0 ? currentLocationItems[0] : null); + _currentLocation.Refreshed.Add(HandleCurrentLocationRefresh); + } + } + + public Task GetCurrentSelectedItem() + { + return Task.FromResult(_currentSelectedItem); + } + + public async Task SetCurrentSelectedItem(IItem? value) + { + if (_currentSelectedItem != value) + { + var contains = (await _currentLocation.GetItems())?.Contains(value) ?? false; + if(value != null && !contains) throw new IndexOutOfRangeException("Provided item does not exists in the current container."); + + _currentSelectedItem = value; + CurrentSelectedIndex = await GetItemIndex(value); + CurrentSelectedItemChanged?.Invoke(this, EventArgs.Empty); + } + } + + private async Task HandleCurrentLocationRefresh(object? sender, AsyncEventArgs e) + { + var currentSelectedName = (await GetCurrentSelectedItem())?.FullName; + var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; if (currentSelectedName != null) { - CurrentSelectedItem = CurrentLocation.Items.FirstOrDefault(i => i.FullName == currentSelectedName) ?? _currentLocation.Items.FirstOrDefault(); + await SetCurrentSelectedItem(currentLocationItems.FirstOrDefault(i => i.FullName == currentSelectedName) ?? currentLocationItems.FirstOrDefault()); } - else if (CurrentLocation.Items.Count > 0) + else if (currentLocationItems.Count > 0) { - CurrentSelectedItem = CurrentLocation.Items[0]; + await SetCurrentSelectedItem(currentLocationItems[0]); } } - public void SelectFirstItem() + public async Task SelectFirstItem() { - if (CurrentLocation.Items.Count > 0) + var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; + if (currentLocationItems.Count > 0) { - CurrentSelectedItem = CurrentLocation.Items[0]; + await SetCurrentSelectedItem(currentLocationItems[0]); } } - public void SelectLastItem() + public async Task SelectLastItem() { - if (CurrentLocation.Items.Count > 0) + var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; + if (currentLocationItems.Count > 0) { - CurrentSelectedItem = CurrentLocation.Items[CurrentLocation.Items.Count - 1]; + await SetCurrentSelectedItem(currentLocationItems[currentLocationItems.Count - 1]); } } - public void SelectPreviousItem(int skip = 0) + public async Task SelectPreviousItem(int skip = 0) { - var possibleItemsToSelect = CurrentLocation.Items.Take(CurrentSelectedIndex).Reverse().Skip(skip).ToList(); + var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; + var possibleItemsToSelect = currentLocationItems.Take(CurrentSelectedIndex).Reverse().Skip(skip).ToList(); - if (possibleItemsToSelect.Count == 0) possibleItemsToSelect = CurrentLocation.Items.ToList(); - SelectItem(possibleItemsToSelect); + if (possibleItemsToSelect.Count == 0) possibleItemsToSelect = currentLocationItems.ToList(); + await SelectItem(possibleItemsToSelect); } - public void SelectNextItem(int skip = 0) + public async Task SelectNextItem(int skip = 0) { - var possibleItemsToSelect = CurrentLocation.Items.Skip(CurrentSelectedIndex + 1 + skip).ToList(); + var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; + var possibleItemsToSelect = currentLocationItems.Skip(CurrentSelectedIndex + 1 + skip).ToList(); - if (possibleItemsToSelect.Count == 0) possibleItemsToSelect = CurrentLocation.Items.Reverse().ToList(); - SelectItem(possibleItemsToSelect); + if (possibleItemsToSelect.Count == 0) possibleItemsToSelect = currentLocationItems.Reverse().ToList(); + await SelectItem(possibleItemsToSelect); } - private void SelectItem(IEnumerable currentPossibleItems) + private async Task SelectItem(IEnumerable currentPossibleItems) { if (!currentPossibleItems.Any()) return; - if (CurrentSelectedItem != null) + var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; + + if (await GetCurrentSelectedItem() != null) { - CurrentLocation.Refresh(); + (await GetCurrentLocation())?.Refresh(); IItem? newSelectedItem = null; foreach (var item in currentPossibleItems) { - if (CurrentLocation.Items.FirstOrDefault(i => i.Name == item.Name) is var possibleNewSelectedItem + if (currentLocationItems.FirstOrDefault(i => i.Name == item.Name) is var possibleNewSelectedItem && possibleNewSelectedItem is not null) { newSelectedItem = possibleNewSelectedItem; @@ -114,78 +162,89 @@ namespace FileTime.Core.Components } } - CurrentSelectedItem = newSelectedItem ?? (CurrentLocation.Items.Count > 0 ? CurrentLocation.Items[0] : null); + if(newSelectedItem != null) + { + newSelectedItem = (await (await GetCurrentLocation()).GetItems())?.FirstOrDefault(i => i.Name == newSelectedItem.Name); + } + + await SetCurrentSelectedItem(newSelectedItem ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null)); } else { - CurrentSelectedItem = CurrentLocation.Items.Count > 0 ? CurrentLocation.Items[0] : null; + await SetCurrentSelectedItem(currentLocationItems.Count > 0 ? currentLocationItems[0] : null); } } - public void GoToProvider() + public async Task GoToProvider() { - if (CurrentLocation == null) return; + var currentLocatin = await GetCurrentLocation(); + if (currentLocatin == null) return; - CurrentLocation = CurrentLocation.Provider; + await SetCurrentLocation(currentLocatin.Provider); } - public void GoToRoot() + public async Task GoToRoot() { - if (CurrentLocation == null) return; + var currentLocatin = await GetCurrentLocation(); + if (currentLocatin == null) return; - var root = CurrentLocation; + var root = currentLocatin; while (root!.GetParent() != null) { root = root.GetParent(); } - CurrentLocation = root; + await SetCurrentLocation(root); } - public void GoUp() + public async Task GoUp() { - var lastCurrentLocation = CurrentLocation; - var parent = CurrentLocation.GetParent(); + var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; + var lastCurrentLocation = await GetCurrentLocation(); + var parent = (await GetCurrentLocation()).GetParent(); if (parent is not null) { if (lastCurrentLocation is VirtualContainer lastCurrentVirtualContainer) { - CurrentLocation = lastCurrentVirtualContainer.CloneVirtualChainFor(parent, v => v.IsPermanent); - CurrentSelectedItem = lastCurrentVirtualContainer.GetRealContainer(); + await SetCurrentLocation(lastCurrentVirtualContainer.CloneVirtualChainFor(parent, v => v.IsPermanent)); + await SetCurrentSelectedItem(lastCurrentVirtualContainer.GetRealContainer()); } else { - CurrentLocation = parent; - CurrentSelectedItem = lastCurrentLocation; + await SetCurrentLocation(parent); + var newCurrentLocation = (await (await GetCurrentLocation()).GetItems())?.FirstOrDefault(i => i.Name == lastCurrentLocation.Name); + await SetCurrentSelectedItem(newCurrentLocation); } } } - public void Open() + public async Task Open() { + var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; if (_currentSelectedItem is IContainer childContainer) { - if (CurrentLocation is VirtualContainer currentVirtuakContainer) + if (await GetCurrentLocation() is VirtualContainer currentVirtuakContainer) { - CurrentLocation = currentVirtuakContainer.CloneVirtualChainFor(childContainer, v => v.IsPermanent); + await SetCurrentLocation(currentVirtuakContainer.CloneVirtualChainFor(childContainer, v => v.IsPermanent)); } else { - CurrentLocation = childContainer; + await SetCurrentLocation(childContainer); } } } - public void OpenContainer(IContainer container) => CurrentLocation = container; + public async Task OpenContainer(IContainer container) => await SetCurrentLocation(container); - private int GetItemIndex(IItem? item) + private async Task GetItemIndex(IItem? item) { if (item == null) return -1; + var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; - for (var i = 0; i < CurrentLocation.Items.Count; i++) + for (var i = 0; i < currentLocationItems.Count; i++) { - if (CurrentLocation.Items[i] == item) return i; + if (currentLocationItems[i] == item) return i; } return -1; diff --git a/src/Core/FileTime.Core/FileTime.Core.csproj b/src/Core/FileTime.Core/FileTime.Core.csproj index 9fbaec9..94ede9b 100644 --- a/src/Core/FileTime.Core/FileTime.Core.csproj +++ b/src/Core/FileTime.Core/FileTime.Core.csproj @@ -2,6 +2,7 @@ + diff --git a/src/Core/FileTime.Core/Interactions/IInputInterface.cs b/src/Core/FileTime.Core/Interactions/IInputInterface.cs index 25df636..2d39f74 100644 --- a/src/Core/FileTime.Core/Interactions/IInputInterface.cs +++ b/src/Core/FileTime.Core/Interactions/IInputInterface.cs @@ -2,6 +2,6 @@ namespace FileTime.Core.Interactions { public interface IInputInterface { - string?[] ReadInputs(IEnumerable fields); + Task ReadInputs(IEnumerable fields); } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Models/IContainer.cs b/src/Core/FileTime.Core/Models/IContainer.cs index 59f42cf..32fab49 100644 --- a/src/Core/FileTime.Core/Models/IContainer.cs +++ b/src/Core/FileTime.Core/Models/IContainer.cs @@ -1,19 +1,21 @@ +using AsyncEvent; + namespace FileTime.Core.Models { public interface IContainer : IItem { - IReadOnlyList Items { get; } - IReadOnlyList Containers { get; } - IReadOnlyList Elements { get; } + Task?> GetItems(CancellationToken token = default); + Task?> GetContainers(CancellationToken token = default); + Task?> GetElements(CancellationToken token = default); - void Refresh(); + Task Refresh(); IContainer? GetParent(); - IItem? GetByPath(string path); - IContainer CreateContainer(string name); - IElement CreateElement(string name); + Task GetByPath(string path); + Task CreateContainer(string name); + Task CreateElement(string name); - bool IsExists(string name); + Task IsExists(string name); - event EventHandler? Refreshed; + AsyncEventHandler Refreshed { get; } } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Models/IItem.cs b/src/Core/FileTime.Core/Models/IItem.cs index 6c38bfe..68de20d 100644 --- a/src/Core/FileTime.Core/Models/IItem.cs +++ b/src/Core/FileTime.Core/Models/IItem.cs @@ -8,6 +8,6 @@ namespace FileTime.Core.Models string? FullName { get; } bool IsHidden { get; } IContentProvider Provider { get; } - void Delete(); + Task Delete(); } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Models/VirtualContainer.cs b/src/Core/FileTime.Core/Models/VirtualContainer.cs index 229f4a3..1a727d1 100644 --- a/src/Core/FileTime.Core/Models/VirtualContainer.cs +++ b/src/Core/FileTime.Core/Models/VirtualContainer.cs @@ -1,3 +1,4 @@ +using AsyncEvent; using FileTime.Core.Providers; namespace FileTime.Core.Models @@ -12,11 +13,11 @@ namespace FileTime.Core.Models public bool IsPermanent { get; } public bool IsTransitive { get; } public string? VirtualContainerName { get; } - public IReadOnlyList Items { get; private set; } + public IReadOnlyList? Items { get; private set; } - public IReadOnlyList Containers { get; private set; } + public IReadOnlyList? Containers { get; private set; } - public IReadOnlyList Elements { get; private set; } + public IReadOnlyList? Elements { get; private set; } public string Name => BaseContainer.Name; @@ -26,10 +27,15 @@ namespace FileTime.Core.Models public IContentProvider Provider => BaseContainer.Provider; - public event EventHandler? Refreshed + public AsyncEventHandler Refreshed { get; } + + private void RefreshAddBase(Func handler) { - add => BaseContainer.Refreshed += value; - remove => BaseContainer.Refreshed -= value; + BaseContainer.Refreshed.Add(handler); + } + private void RefreshRemoveBase(Func handler) + { + BaseContainer.Refreshed.Add(handler); } public VirtualContainer( @@ -40,32 +46,40 @@ namespace FileTime.Core.Models bool isTransitive = false, string? virtualContainerName = null) { + Refreshed = new (RefreshAddBase, RefreshRemoveBase); BaseContainer = baseContainer; _containerTransformators = containerTransformators; _elementTransformators = elementTransformators; - InitItems(); IsPermanent = isPermanent; IsTransitive = isTransitive; VirtualContainerName = virtualContainerName; } - private void InitItems() + public async Task Init() { - Containers = _containerTransformators.Aggregate(BaseContainer.Containers.AsEnumerable(), (a, t) => t(a)).ToList().AsReadOnly(); - Elements = _elementTransformators.Aggregate(BaseContainer.Elements.AsEnumerable(), (a, t) => t(a)).ToList().AsReadOnly(); - - Items = Containers.Cast().Concat(Elements).ToList().AsReadOnly(); + await InitItems(); } - public IItem? GetByPath(string path) => BaseContainer.GetByPath(path); + private async Task InitItems() + { + Containers = _containerTransformators.Aggregate((await BaseContainer.GetContainers())?.AsEnumerable(), (a, t) => t(a!))?.ToList()?.AsReadOnly(); + Elements = _elementTransformators.Aggregate((await BaseContainer.GetElements())?.AsEnumerable(), (a, t) => t(a!))?.ToList()?.AsReadOnly(); + + Items = (Elements != null + ? Containers?.Cast().Concat(Elements) + : Containers?.Cast()) + ?.ToList().AsReadOnly(); + } + + public async Task GetByPath(string path) => await BaseContainer.GetByPath(path); public IContainer? GetParent() => BaseContainer.GetParent(); - public void Refresh() + public async Task Refresh() { - BaseContainer.Refresh(); - InitItems(); + await BaseContainer.Refresh(); + await InitItems(); } public IContainer GetRealContainer() => @@ -113,10 +127,23 @@ namespace FileTime.Core.Models : baseContainer; } - public IContainer CreateContainer(string name) => BaseContainer.CreateContainer(name); - public IElement CreateElement(string name) => BaseContainer.CreateElement(name); - public bool IsExists(string name) => BaseContainer.IsExists(name); + public async Task CreateContainer(string name) => await BaseContainer.CreateContainer(name); + public async Task CreateElement(string name) => await BaseContainer.CreateElement(name); + public async Task IsExists(string name) => await BaseContainer.IsExists(name); - public void Delete() => BaseContainer.Delete(); + public Task?> GetItems(CancellationToken token = default) + { + return Task.FromResult(Items); + } + public Task?> GetContainers(CancellationToken token = default) + { + return Task.FromResult(Containers); + } + public Task?> GetElements(CancellationToken token = default) + { + return Task.FromResult(Elements); + } + + public async Task Delete() => await BaseContainer.Delete(); } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Providers/IContentProvider.cs b/src/Core/FileTime.Core/Providers/IContentProvider.cs index 2b0782e..7e8b570 100644 --- a/src/Core/FileTime.Core/Providers/IContentProvider.cs +++ b/src/Core/FileTime.Core/Providers/IContentProvider.cs @@ -4,7 +4,7 @@ namespace FileTime.Core.Providers { public interface IContentProvider : IContainer { - IReadOnlyList RootContainers { get; } + Task> GetRootContainers(CancellationToken token = default); bool CanHandlePath(string path); diff --git a/src/Core/FileTime.Core/Providers/TopContainer.cs b/src/Core/FileTime.Core/Providers/TopContainer.cs index 3f04f9b..f845ca6 100644 --- a/src/Core/FileTime.Core/Providers/TopContainer.cs +++ b/src/Core/FileTime.Core/Providers/TopContainer.cs @@ -1,4 +1,5 @@ +using AsyncEvent; using FileTime.Core.Models; namespace FileTime.Core.Providers @@ -6,12 +7,9 @@ namespace FileTime.Core.Providers public class TopContainer : IContainer { private readonly List _contentProviders; - - public IReadOnlyList Items => Containers; - - public IReadOnlyList Containers { get; } - - public IReadOnlyList Elements { get; } = new List().AsReadOnly(); + private readonly IReadOnlyList? _containers; + private readonly IReadOnlyList? _items; + private readonly IReadOnlyList? _elements = new List().AsReadOnly(); public string Name => null; @@ -21,12 +19,13 @@ namespace FileTime.Core.Providers public IContentProvider Provider => null; - public event EventHandler? Refreshed; + public AsyncEventHandler Refreshed { get; } = new(); public TopContainer(IEnumerable contentProviders) { _contentProviders = new List(contentProviders); - Containers = _contentProviders.AsReadOnly(); + _containers = _contentProviders.AsReadOnly(); + _items = _containers.Cast().ToList().AsReadOnly(); foreach (var contentProvider in contentProviders) { @@ -34,21 +33,34 @@ namespace FileTime.Core.Providers } } - public IContainer CreateContainer(string name) => throw new NotImplementedException(); + public Task CreateContainer(string name) => throw new NotImplementedException(); - public IElement CreateElement(string name) => throw new NotImplementedException(); + public Task CreateElement(string name) => throw new NotImplementedException(); - public void Delete() => throw new NotImplementedException(); + public Task Delete() => throw new NotImplementedException(); - public IItem? GetByPath(string path) => throw new NotImplementedException(); + public Task GetByPath(string path) => throw new NotImplementedException(); public IContainer? GetParent() => null; - public bool IsExists(string name) => throw new NotImplementedException(); + public Task IsExists(string name) => throw new NotImplementedException(); - public void Refresh() + public Task Refresh() { + return Task.CompletedTask; + } + public Task?> GetItems(CancellationToken token = default) + { + return Task.FromResult(_items); + } + public Task?> GetContainers(CancellationToken token = default) + { + return Task.FromResult(_containers); + } + public Task?> GetElements(CancellationToken token = default) + { + return Task.FromResult(_elements); } } } \ No newline at end of file diff --git a/src/FileTime.sln b/src/FileTime.sln index b8d4b2c..c2a8086 100644 --- a/src/FileTime.sln +++ b/src/FileTime.sln @@ -45,6 +45,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Uno.Windows.Deskto EndProject Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "FileTime.Uno.Windows.Package", "GuiApp\FileTime.Uno\FileTime.Uno.Windows.Package\FileTime.Uno.Windows.Package.wapproj", "{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncEvent", "Core\AsyncEvent\AsyncEvent.csproj", "{9BDAC126-200F-4056-8D35-36EC059B40F3}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution GuiApp\FileTime.Uno\FileTime.Uno.Shared\FileTime.Uno.Shared.projitems*{02acef48-3be8-43e5-9358-a914bcbea7ca}*SharedItemsImports = 5 @@ -356,6 +358,26 @@ Global {6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|x86.ActiveCfg = Release|x86 {6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|x86.Build.0 = Release|x86 {6BB770CB-B0C1-4A82-86B1-3C56F4393BE1}.Release|x86.Deploy.0 = Release|x86 + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Debug|ARM.ActiveCfg = Debug|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Debug|ARM.Build.0 = Debug|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Debug|ARM64.Build.0 = Debug|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Debug|x64.ActiveCfg = Debug|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Debug|x64.Build.0 = Debug|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Debug|x86.ActiveCfg = Debug|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Debug|x86.Build.0 = Debug|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Release|Any CPU.Build.0 = Release|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Release|ARM.ActiveCfg = Release|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Release|ARM.Build.0 = Release|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Release|ARM64.ActiveCfg = Release|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Release|ARM64.Build.0 = Release|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Release|x64.ActiveCfg = Release|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Release|x64.Build.0 = Release|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Release|x86.ActiveCfg = Release|Any CPU + {9BDAC126-200F-4056-8D35-36EC059B40F3}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -377,6 +399,7 @@ Global {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} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8D679DCE-AC84-4A91-BFED-8F8D8E1D8183} diff --git a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs index 1a4e399..dc8f7b8 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs @@ -1,4 +1,5 @@ using System.Runtime.InteropServices; +using AsyncEvent; using FileTime.Core.Models; using FileTime.Core.Providers; using Microsoft.Extensions.Logging; @@ -8,15 +9,19 @@ namespace FileTime.Providers.Local public class LocalContentProvider : IContentProvider { private readonly ILogger _logger; - private IContainer _parent = null; + private IContainer? _parent; - public IReadOnlyList RootContainers { get; } + private readonly IReadOnlyList _rootContainers; + private readonly IReadOnlyList? _items; + private readonly IReadOnlyList? _elements = new List().AsReadOnly(); + + /* public IReadOnlyList RootContainers { get; } public IReadOnlyList Items => RootContainers; public IReadOnlyList Containers => RootContainers; - public IReadOnlyList Elements { get; } = new List(); + public IReadOnlyList Elements { get; } = new List(); */ public string Name { get; } = "local"; @@ -25,7 +30,7 @@ namespace FileTime.Providers.Local public IContentProvider Provider => this; - public event EventHandler? Refreshed; + public AsyncEventHandler Refreshed { get; } = new(); public bool IsCaseInsensitive { get; } @@ -41,13 +46,14 @@ namespace FileTime.Providers.Local FullName = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "" : null; - RootContainers = rootDirectories.Select(d => new LocalFolder(d, this, this)).OrderBy(d => d.Name).ToList().AsReadOnly(); + _rootContainers = rootDirectories.Select(d => new LocalFolder(d, this, this)).OrderBy(d => d.Name).ToList().AsReadOnly(); + _items = _rootContainers.Cast().ToList().AsReadOnly(); } - public IItem? GetByPath(string path) + public async Task GetByPath(string path) { var pathParts = (IsCaseInsensitive ? path.ToLower() : path).TrimStart(Constants.SeparatorChar).Split(Constants.SeparatorChar); - var rootContainer = RootContainers.FirstOrDefault(c => NormalizePath(c.Name) == NormalizePath(pathParts[0])); + var rootContainer = _rootContainers.FirstOrDefault(c => NormalizePath(c.Name) == NormalizePath(pathParts[0])); if (rootContainer == null) { @@ -55,27 +61,43 @@ namespace FileTime.Providers.Local return null; } - return rootContainer.GetByPath(string.Join(Constants.SeparatorChar, pathParts.Skip(1))); + return await rootContainer.GetByPath(string.Join(Constants.SeparatorChar, pathParts.Skip(1))); } - public void Refresh() - { - } + public Task Refresh() => Task.CompletedTask; + public IContainer? GetParent() => _parent; - public IContainer CreateContainer(string name) => throw new NotSupportedException(); - public IElement CreateElement(string name) => throw new NotSupportedException(); - public bool IsExists(string name) => Items.Any(i => i.Name == name); + public Task CreateContainer(string name) => throw new NotSupportedException(); + public Task CreateElement(string name) => throw new NotSupportedException(); + public Task IsExists(string name) => Task.FromResult(_rootContainers.Any(i => i.Name == name)); - public void Delete() => throw new NotSupportedException(); + public Task Delete() => throw new NotSupportedException(); internal string NormalizePath(string path) => IsCaseInsensitive ? path.ToLower() : path; - public bool CanHandlePath(string path) => RootContainers.Any(r => path.StartsWith(r.Name)); + public bool CanHandlePath(string path) => _rootContainers.Any(r => path.StartsWith(r.Name)); public void SetParent(IContainer container) { _parent = container; } + public Task?> GetRootContainers(CancellationToken token = default) + { + return Task.FromResult(_rootContainers); + } + + public Task?> GetItems(CancellationToken token = default) + { + return Task.FromResult(_items); + } + public Task?> GetContainers(CancellationToken token = default) + { + return Task.FromResult(_rootContainers); + } + public Task?> GetElements(CancellationToken token = default) + { + return Task.FromResult(_elements); + } } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Local/LocalFile.cs b/src/Providers/FileTime.Providers.Local/LocalFile.cs index 54303a7..69ded4b 100644 --- a/src/Providers/FileTime.Providers.Local/LocalFile.cs +++ b/src/Providers/FileTime.Providers.Local/LocalFile.cs @@ -34,9 +34,10 @@ namespace FileTime.Providers.Local public string GetPrimaryAttributeText() => _file.Length.ToSizeString(); - public void Delete() + public Task Delete() { _file.Delete(); + return Task.CompletedTask; } } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Local/LocalFolder.cs b/src/Providers/FileTime.Providers.Local/LocalFolder.cs index 77cb744..3a7492c 100644 --- a/src/Providers/FileTime.Providers.Local/LocalFolder.cs +++ b/src/Providers/FileTime.Providers.Local/LocalFolder.cs @@ -1,3 +1,4 @@ +using AsyncEvent; using FileTime.Core.Models; using FileTime.Core.Providers; @@ -15,44 +16,11 @@ namespace FileTime.Providers.Local public LocalContentProvider Provider { get; } IContentProvider IItem.Provider => Provider; - public IReadOnlyList Items - { - get - { - if (_items == null) Refresh(); - return _items!; - } - - private set => _items = value; - } - - public IReadOnlyList Containers - { - get - { - if (_containers == null) Refresh(); - return _containers!; - } - - private set => _containers = value; - } - - public IReadOnlyList Elements - { - get - { - if (_elements == null) Refresh(); - return _elements!; - } - - private set => _elements = value; - } - public string Name { get; } public string FullName { get; } - public event EventHandler? Refreshed; + public AsyncEventHandler Refreshed { get; } = new(); public LocalFolder(DirectoryInfo directory, LocalContentProvider contentProvider, IContainer? parent) { @@ -66,7 +34,7 @@ namespace FileTime.Providers.Local public IContainer? GetParent() => _parent; - public void Refresh() + public Task Refresh() { _containers = new List(); _elements = new List(); @@ -79,14 +47,32 @@ namespace FileTime.Providers.Local catch { } _items = _containers.Cast().Concat(_elements).ToList().AsReadOnly(); - Refreshed?.Invoke(this, EventArgs.Empty); + Refreshed?.InvokeAsync(this, AsyncEventArgs.Empty); + + return Task.CompletedTask; } - public IItem? GetByPath(string path) + public async Task?> GetItems(CancellationToken token = default) + { + if (_items == null) await Refresh(); + return _items; + } + public async Task?> GetContainers(CancellationToken token = default) + { + if (_containers == null) await Refresh(); + return _containers; + } + public async Task?> GetElements(CancellationToken token = default) + { + if (_elements == null) await Refresh(); + return _elements; + } + + public async Task GetByPath(string path) { var paths = path.Split(Constants.SeparatorChar); - var item = Items.FirstOrDefault(i => Provider.NormalizePath(i.Name) == Provider.NormalizePath(paths[0])); + var item = (await GetItems())!.FirstOrDefault(i => Provider.NormalizePath(i.Name) == Provider.NormalizePath(paths[0])); if (paths.Length == 1) { @@ -95,29 +81,33 @@ namespace FileTime.Providers.Local if (item is IContainer container) { - return container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1))); + return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1))); } return null; } - public IContainer CreateContainer(string name) + public async Task CreateContainer(string name) { Directory.CreateSubdirectory(name); - Refresh(); + await Refresh(); return _containers!.FirstOrDefault(c => Provider.NormalizePath(c.Name) == Provider.NormalizePath(name))!; } - public IElement CreateElement(string name) + public async Task CreateElement(string name) { using (File.Create(Path.Combine(Directory.FullName, name))) { } - Refresh(); + await Refresh(); return _elements!.FirstOrDefault(e => Provider.NormalizePath(e.Name) == Provider.NormalizePath(name))!; } - public bool IsExists(string name) => Items.Any(i => Provider.NormalizePath(i.Name) == Provider.NormalizePath(name)); + public async Task IsExists(string name) => (await GetItems())?.Any(i => Provider.NormalizePath(i.Name) == Provider.NormalizePath(name)) ?? false; - public void Delete() => Directory.Delete(true); + public Task Delete() + { + Directory.Delete(true); + return Task.CompletedTask; + } } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs b/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs index 863fd6c..baf4bf2 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs @@ -1,3 +1,4 @@ +using AsyncEvent; using FileTime.Core.Interactions; using FileTime.Core.Models; using FileTime.Core.Providers; @@ -7,16 +8,11 @@ namespace FileTime.Providers.Smb public class SmbContentProvider : IContentProvider { private IContainer _parent; - private readonly List _rootContainers; private readonly IInputInterface _inputInterface; - - public IReadOnlyList RootContainers { get; } - - public IReadOnlyList Items => RootContainers; - - public IReadOnlyList Containers => RootContainers; - - public IReadOnlyList Elements { get; } = new List(); + private readonly List _rootContainers; + private readonly IReadOnlyList _rootContainersReadOnly; + private readonly IReadOnlyList? _items; + private readonly IReadOnlyList? _elements = new List().AsReadOnly(); public string Name { get; } = "smb"; @@ -26,16 +22,16 @@ namespace FileTime.Providers.Smb public IContentProvider Provider => this; - public event EventHandler? Refreshed; + public AsyncEventHandler Refreshed { get; } = new(); public SmbContentProvider(IInputInterface inputInterface) { _rootContainers = new List(); - RootContainers = _rootContainers.AsReadOnly(); + _rootContainersReadOnly = _rootContainers.AsReadOnly(); _inputInterface = inputInterface; } - public IContainer CreateContainer(string name) + public async Task CreateContainer(string name) { var fullName = "\\\\" + name; var container = _rootContainers.Find(c => c.Name == name); @@ -46,37 +42,54 @@ namespace FileTime.Providers.Smb _rootContainers.Add(container); } - Refresh(); + await Refresh(); return container; } - public IElement CreateElement(string name) + public Task CreateElement(string name) { throw new NotSupportedException(); } - public void Delete() + public Task Delete() { throw new NotSupportedException(); } - public IItem? GetByPath(string path) + public Task GetByPath(string path) { throw new NotImplementedException(); } public IContainer? GetParent() => _parent; - public bool IsExists(string name) => Items.Any(i => i.Name == name); + public async Task IsExists(string name) => (await GetItems()).Any(i => i.Name == name); - public void Refresh() + public async Task Refresh() { - Refreshed?.Invoke(this, EventArgs.Empty); + await Refreshed?.InvokeAsync(this, AsyncEventArgs.Empty); } public bool CanHandlePath(string path) => path.StartsWith("smb://") || path.StartsWith(@"\\"); public void SetParent(IContainer container) => _parent = container; + public Task?> GetRootContainers(CancellationToken token = default) + { + return Task.FromResult(_rootContainersReadOnly); + } + + public Task?> GetItems(CancellationToken token = default) + { + return Task.FromResult(_items); + } + public Task?> GetContainers(CancellationToken token = default) + { + return Task.FromResult(_rootContainersReadOnly); + } + public Task?> GetElements(CancellationToken token = default) + { + return Task.FromResult(_elements); + } } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Smb/SmbFile.cs b/src/Providers/FileTime.Providers.Smb/SmbFile.cs index 6ef1cfb..b8f0daf 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbFile.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbFile.cs @@ -16,18 +16,15 @@ namespace FileTime.Providers.Smb public IContentProvider Provider { get; } - private readonly Func _getSmbClient; - - public SmbFile(string name, SmbContentProvider provider, IContainer parent, Func getSmbClient) + public SmbFile(string name, SmbContentProvider provider, IContainer parent) { Name = name; FullName = parent.FullName + Constants.SeparatorChar + Name; Provider = provider; - _getSmbClient = getSmbClient; } - public void Delete() + public Task Delete() { throw new NotImplementedException(); } diff --git a/src/Providers/FileTime.Providers.Smb/SmbFolder.cs b/src/Providers/FileTime.Providers.Smb/SmbFolder.cs index ed074c6..ba2471f 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbFolder.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbFolder.cs @@ -1,3 +1,4 @@ +using AsyncEvent; using FileTime.Core.Models; using FileTime.Core.Providers; using SMBLibrary; @@ -10,43 +11,9 @@ namespace FileTime.Providers.Smb private IReadOnlyList? _items; private IReadOnlyList? _containers; private IReadOnlyList? _elements; - private Func _getSmbClient; private readonly SmbShare _smbShare; private readonly IContainer? _parent; - public IReadOnlyList Items - { - get - { - if (_items == null) Refresh(); - return _items!; - } - - private set => _items = value; - } - - public IReadOnlyList Containers - { - get - { - if (_containers == null) Refresh(); - return _containers!; - } - - private set => _containers = value; - } - - public IReadOnlyList Elements - { - get - { - if (_elements == null) Refresh(); - return _elements!; - } - - private set => _elements = value; - } - public string Name { get; } public string? FullName { get; } @@ -56,39 +23,33 @@ namespace FileTime.Providers.Smb public SmbContentProvider Provider { get; } IContentProvider IItem.Provider => Provider; - public event EventHandler? Refreshed; + public AsyncEventHandler Refreshed { get; } = new(); - public SmbFolder(string name, SmbContentProvider contentProvider, SmbShare smbShare, IContainer parent, Func getSmbClient) + public SmbFolder(string name, SmbContentProvider contentProvider, SmbShare smbShare, IContainer parent) { _parent = parent; - _getSmbClient = getSmbClient; + _smbShare = smbShare; Name = name; FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name; Provider = contentProvider; - _smbShare = smbShare; } - public IContainer CreateContainer(string name) + public Task CreateContainer(string name) { throw new NotImplementedException(); } - public IElement CreateElement(string name) + public Task CreateElement(string name) { throw new NotImplementedException(); } - public void Delete() - { - throw new NotImplementedException(); - } - - public IItem? GetByPath(string path) + public async Task GetByPath(string path) { var paths = path.Split(Constants.SeparatorChar); - var item = Items.FirstOrDefault(i => i.Name == paths[0]); + var item = (await GetItems())?.FirstOrDefault(i => i.Name == paths[0]); if (paths.Length == 1) { @@ -97,7 +58,7 @@ namespace FileTime.Providers.Smb if (item is IContainer container) { - return container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1))); + return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1))); } return null; @@ -105,12 +66,17 @@ namespace FileTime.Providers.Smb public IContainer? GetParent() => _parent; - public bool IsExists(string name) + public Task IsExists(string name) { throw new NotImplementedException(); } - public void Refresh() + public Task Delete() + { + throw new NotImplementedException(); + } + + public async Task Refresh() { var containers = new List(); var elements = new List(); @@ -118,7 +84,7 @@ namespace FileTime.Providers.Smb try { var path = FullName![(_smbShare.FullName!.Length + 1)..]; - (containers, elements) = _smbShare.ListFolder(this, _smbShare.Name, path); + (containers, elements) = await _smbShare.ListFolder(this, _smbShare.Name, path); } catch { } @@ -126,7 +92,23 @@ namespace FileTime.Providers.Smb _elements = elements.AsReadOnly(); _items = _containers.Cast().Concat(_elements).ToList().AsReadOnly(); - Refreshed?.Invoke(this, EventArgs.Empty); + await Refreshed?.InvokeAsync(this, AsyncEventArgs.Empty); + } + + public async Task?> GetItems(CancellationToken token = default) + { + if (_items == null) await Refresh(); + return _items; + } + public async Task?> GetContainers(CancellationToken token = default) + { + if (_containers == null) await Refresh(); + return _containers; + } + public async Task?> GetElements(CancellationToken token = default) + { + if (_elements == null) await Refresh(); + return _elements; } } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Smb/SmbServer.cs b/src/Providers/FileTime.Providers.Smb/SmbServer.cs index be199da..9840f40 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbServer.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbServer.cs @@ -1,4 +1,5 @@ using System.Net; +using AsyncEvent; using FileTime.Core.Interactions; using FileTime.Core.Models; using FileTime.Core.Providers; @@ -13,31 +14,11 @@ namespace FileTime.Providers.Smb private string? _password; private IReadOnlyList? _shares; + private IReadOnlyList? _items; + private readonly IReadOnlyList? _elements = new List().AsReadOnly(); private ISMBClient? _client; private readonly IInputInterface _inputInterface; - public IReadOnlyList Items - { - get - { - if (_shares == null) Refresh(); - return _shares!; - } - } - - public IReadOnlyList Containers - { - get - { - if (_shares == null) Refresh(); - return _shares!; - } - - private set => _shares = value; - } - - public IReadOnlyList Elements { get; } = new List().AsReadOnly(); - public string Name { get; } public string? FullName { get; } @@ -48,7 +29,7 @@ namespace FileTime.Providers.Smb IContentProvider IItem.Provider => Provider; - public event EventHandler? Refreshed; + public AsyncEventHandler Refreshed { get; } = new(); public SmbServer(string path, SmbContentProvider contentProvider, IInputInterface inputInterface) { @@ -58,43 +39,60 @@ namespace FileTime.Providers.Smb FullName = Name = path; } - public IContainer CreateContainer(string name) + public async Task?> GetItems(CancellationToken token = default) + { + if (_shares == null) await Refresh(); + return _shares; + } + public async Task?> GetContainers(CancellationToken token = default) + { + if (_shares == null) await Refresh(); + return _shares; + } + public Task?> GetElements(CancellationToken token = default) + { + return Task.FromResult(_elements); + } + + public Task CreateContainer(string name) { throw new NotSupportedException(); } - public IElement CreateElement(string name) + public Task CreateElement(string name) { throw new NotSupportedException(); } - public void Delete() + public Task Delete() { + return Task.CompletedTask; } - public IItem? GetByPath(string path) + public Task GetByPath(string path) { throw new NotImplementedException(); } public IContainer? GetParent() => Provider; - public bool IsExists(string name) + public Task IsExists(string name) { throw new NotImplementedException(); } - public void Refresh() + public async Task Refresh() { - ISMBClient client = GetSmbClient(); + ISMBClient client = await GetSmbClient(); List shares = client.ListShares(out var status); _shares = shares.ConvertAll(s => new SmbShare(s, Provider, this, GetSmbClient)).AsReadOnly(); - Refreshed?.Invoke(this, EventArgs.Empty); + _items = _shares.Cast().ToList().AsReadOnly(); + await Refreshed?.InvokeAsync(this, AsyncEventArgs.Empty); } - private ISMBClient GetSmbClient() + private async Task GetSmbClient() { if (_client == null) { @@ -108,7 +106,7 @@ namespace FileTime.Providers.Smb { if (_username == null && _password == null) { - var inputs = _inputInterface.ReadInputs( + var inputs = await _inputInterface.ReadInputs( new InputElement[] { new InputElement($"Username for '{Name}'", InputType.Text), diff --git a/src/Providers/FileTime.Providers.Smb/SmbShare.cs b/src/Providers/FileTime.Providers.Smb/SmbShare.cs index f50fc3e..dc3b1c3 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbShare.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbShare.cs @@ -1,3 +1,4 @@ +using AsyncEvent; using FileTime.Core.Interactions; using FileTime.Core.Models; using FileTime.Core.Providers; @@ -11,42 +12,9 @@ namespace FileTime.Providers.Smb private IReadOnlyList? _items; private IReadOnlyList? _containers; private IReadOnlyList? _elements; - private Func _getSmbClient; + private Func> _getSmbClient; private readonly IContainer? _parent; - public IReadOnlyList Items - { - get - { - if (_items == null) Refresh(); - return _items!; - } - - private set => _items = value; - } - - public IReadOnlyList Containers - { - get - { - if (_containers == null) Refresh(); - return _containers!; - } - - private set => _containers = value; - } - - public IReadOnlyList Elements - { - get - { - if (_elements == null) Refresh(); - return _elements!; - } - - private set => _elements = value; - } - public string Name { get; } public string? FullName { get; } @@ -56,9 +24,9 @@ namespace FileTime.Providers.Smb public SmbContentProvider Provider { get; } IContentProvider IItem.Provider => Provider; - public event EventHandler? Refreshed; + public AsyncEventHandler Refreshed { get; } = new(); - public SmbShare(string name, SmbContentProvider contentProvider, IContainer parent, Func getSmbClient) + public SmbShare(string name, SmbContentProvider contentProvider, IContainer parent, Func> getSmbClient) { _parent = parent; _getSmbClient = getSmbClient; @@ -68,26 +36,42 @@ namespace FileTime.Providers.Smb Provider = contentProvider; } - public IContainer CreateContainer(string name) + public async Task?> GetItems(CancellationToken token = default) + { + if (_items == null) await Refresh(); + return _items; + } + public async Task?> GetContainers(CancellationToken token = default) + { + if (_containers == null) await Refresh(); + return _containers; + } + public async Task?> GetElements(CancellationToken token = default) + { + if (_elements == null) await Refresh(); + return _elements; + } + + public Task CreateContainer(string name) { throw new NotImplementedException(); } - public IElement CreateElement(string name) + public Task CreateElement(string name) { throw new NotImplementedException(); } - public void Delete() + public Task Delete() { throw new NotImplementedException(); } - public IItem? GetByPath(string path) + public async Task GetByPath(string path) { var paths = path.Split(Constants.SeparatorChar); - var item = Items.FirstOrDefault(i => i.Name == paths[0]); + var item = (await GetItems())?.FirstOrDefault(i => i.Name == paths[0]); if (paths.Length == 1) { @@ -96,7 +80,7 @@ namespace FileTime.Providers.Smb if (item is IContainer container) { - return container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1))); + return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1))); } return null; @@ -104,19 +88,19 @@ namespace FileTime.Providers.Smb public IContainer? GetParent() => _parent; - public bool IsExists(string name) + public Task IsExists(string name) { throw new NotImplementedException(); } - public void Refresh() + public async Task Refresh() { var containers = new List(); var elements = new List(); try { - (containers, elements) = ListFolder(this, Name, string.Empty); + (containers, elements) = await ListFolder(this, Name, string.Empty); } catch { } @@ -124,15 +108,15 @@ namespace FileTime.Providers.Smb _elements = elements.AsReadOnly(); _items = _containers.Cast().Concat(_elements).ToList().AsReadOnly(); - Refreshed?.Invoke(this, EventArgs.Empty); + await Refreshed?.InvokeAsync(this, AsyncEventArgs.Empty); } - public (List containers, List elements) ListFolder(IContainer parent, string shareName, string folderName) + public async Task<(List containers, List elements)> ListFolder(IContainer parent, string shareName, string folderName) { var containers = new List(); var elements = new List(); - var client = _getSmbClient(); + var client = await _getSmbClient(); ISMBFileStore fileStore = client.TreeConnect(shareName, out var status); if (status == NTStatus.STATUS_SUCCESS) { @@ -148,11 +132,11 @@ namespace FileTime.Providers.Smb { if ((fileDirectoryInformation.FileAttributes & SMBLibrary.FileAttributes.Directory) == SMBLibrary.FileAttributes.Directory) { - containers.Add(new SmbFolder(fileDirectoryInformation.FileName, Provider, this, parent, _getSmbClient)); + containers.Add(new SmbFolder(fileDirectoryInformation.FileName, Provider, this, parent)); } else { - elements.Add(new SmbFile(fileDirectoryInformation.FileName, Provider, parent, _getSmbClient)); + elements.Add(new SmbFile(fileDirectoryInformation.FileName, Provider, parent)); } } }