From 755793c85c197d3d75bd23d31e5d52f35214eac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Tue, 8 Feb 2022 09:53:20 +0100 Subject: [PATCH] Optimalization, file name&extension --- src/Core/FileTime.Core/Components/Tab.cs | 106 ++++++++++-------- src/Core/FileTime.Core/Models/IContainer.cs | 1 + .../FileTime.Core/Models/VirtualContainer.cs | 5 + .../FileTime.Core/Providers/TopContainer.cs | 2 + .../FileTime.Core/Timeline/TimeContainer.cs | 1 + .../FileTime.Core/Timeline/TimeProvider.cs | 2 + src/GuiApp/FileTime.Avalonia/App.axaml | 2 + .../Application/TabContainer.cs | 32 ++++-- .../Converters/ContextMenuGenerator.cs | 8 +- .../Converters/GetFileExtensionConverter.cs | 25 +++++ ...> ItemViewModelIsAttibuteTypeConverter.cs} | 5 +- .../Services/ItemNameConverterService.cs | 19 +++- .../ViewModels/ContainerViewModel.cs | 51 ++++++--- .../ViewModels/ElementPreviewViewModel.cs | 9 +- .../ViewModels/MainPageViewModel.cs | 23 ++++ .../FileTime.Avalonia/Views/ItemView.axaml | 12 +- .../LocalContentProvider.cs | 16 ++- .../FileTime.Providers.Local/LocalFolder.cs | 10 +- .../SmbContentProvider.cs | 2 + .../FileTime.Providers.Smb/SmbFolder.cs | 13 ++- .../FileTime.Providers.Smb/SmbServer.cs | 2 + .../FileTime.Providers.Smb/SmbShare.cs | 7 ++ 22 files changed, 268 insertions(+), 85 deletions(-) create mode 100644 src/GuiApp/FileTime.Avalonia/Converters/GetFileExtensionConverter.cs rename src/GuiApp/FileTime.Avalonia/Converters/{ItemViewModelToAttibuteTypeConverter.cs => ItemViewModelIsAttibuteTypeConverter.cs} (81%) diff --git a/src/Core/FileTime.Core/Components/Tab.cs b/src/Core/FileTime.Core/Components/Tab.cs index 9c9bf6d..9cbba11 100644 --- a/src/Core/FileTime.Core/Components/Tab.cs +++ b/src/Core/FileTime.Core/Components/Tab.cs @@ -18,6 +18,8 @@ namespace FileTime.Core.Components public int CurrentSelectedIndex { get; private set; } + public bool AutoRefresh { get; set; } + public AsyncEventHandler CurrentLocationChanged = new(); public AsyncEventHandler CurrentSelectedItemChanged = new(); @@ -54,39 +56,47 @@ namespace FileTime.Core.Components return Task.FromResult(_currentSelectedItem); } - public async Task SetCurrentSelectedItem(IItem? value, bool secondary = false) + public async Task SetCurrentSelectedItem(IItem? value, bool secondary = false, CancellationToken token = default) { - if (_currentlySelecting) return; + if (_currentlySelecting) return false; - if (_currentSelectedItem != value) + if (_currentSelectedItem == value) return false; + IItem? itemToSelect = null; + if (value != null) { - IItem? itemToSelect = null; - if (value != null) - { - itemToSelect = (await _currentLocation.GetItems())?.FirstOrDefault(i => - i.FullName == null && value?.FullName == null - ? i.Name == value?.Name - : i.FullName == value?.FullName); - if (itemToSelect == null) throw new IndexOutOfRangeException($"Provided item ({value.FullName ?? "unknwon"}) does not exists in the current container ({_currentLocation.FullName ?? "unknwon"})."); - } + itemToSelect = (await _currentLocation.GetItems(token))?.FirstOrDefault(i => + i.FullName == null && value?.FullName == null + ? i.Name == value?.Name + : i.FullName == value?.FullName); + if (itemToSelect == null) throw new IndexOutOfRangeException($"Provided item ({value.FullName ?? "unknwon"}) does not exists in the current container ({_currentLocation.FullName ?? "unknwon"})."); + } - CancellationToken newToken; - lock (_guardSetCurrentSelectedItemCTS) + CancellationToken newToken; + lock (_guardSetCurrentSelectedItemCTS) + { + if (token.IsCancellationRequested) return false; + _setCurrentSelectedItemCTS?.Cancel(); + if (token.IsCancellationRequested) { - _setCurrentSelectedItemCTS?.Cancel(); _setCurrentSelectedItemCTS = new CancellationTokenSource(); newToken = _setCurrentSelectedItemCTS.Token; } - - _currentSelectedItem = itemToSelect; - _lastPath = GetCommonPath(_lastPath, itemToSelect?.FullName); - - var newCurrentSelectedIndex = await GetItemIndex(itemToSelect, newToken); - if (newToken.IsCancellationRequested) return; - CurrentSelectedIndex = newCurrentSelectedIndex; - - await CurrentSelectedItemChanged.InvokeAsync(this, AsyncEventArgs.Empty, newToken); + else + { + _setCurrentSelectedItemCTS = new CancellationTokenSource(); + newToken = CancellationTokenSource.CreateLinkedTokenSource(_setCurrentSelectedItemCTS.Token, token).Token; + } } + + _currentSelectedItem = itemToSelect; + _lastPath = GetCommonPath(_lastPath, itemToSelect?.FullName); + + var newCurrentSelectedIndex = await GetItemIndex(itemToSelect, CancellationToken.None); + CurrentSelectedIndex = newCurrentSelectedIndex; + + await CurrentSelectedItemChanged.InvokeAsync(this, AsyncEventArgs.Empty, newToken); + + return !newToken.IsCancellationRequested; } public async Task GetItemByLastPath(IContainer? container = null) { @@ -94,7 +104,6 @@ namespace FileTime.Core.Components var containerFullName = container.FullName; if (_lastPath == null - || !container.IsLoaded || (containerFullName != null && !_lastPath.StartsWith(containerFullName)) ) { @@ -154,53 +163,60 @@ namespace FileTime.Core.Components } } - public async Task SelectFirstItem() + public async Task SelectFirstItem(CancellationToken token = default) { - var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; + var currentLocationItems = (await (await GetCurrentLocation(token)).GetItems(token))!; if (currentLocationItems.Count > 0) { - await SetCurrentSelectedItem(currentLocationItems[0]); + await SetCurrentSelectedItem(currentLocationItems[0], token: token); } } - public async Task SelectLastItem() + public async Task SelectLastItem(CancellationToken token = default) { - var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; + var currentLocationItems = (await (await GetCurrentLocation(token)).GetItems(token))!; if (currentLocationItems.Count > 0) { - await SetCurrentSelectedItem(currentLocationItems[currentLocationItems.Count - 1]); + await SetCurrentSelectedItem(currentLocationItems[currentLocationItems.Count - 1], token: token); } } - public async Task SelectPreviousItem(int skip = 0) + public async Task SelectPreviousItem(int skip = 0, CancellationToken token = default) { - var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; + var currentLocationItems = (await (await GetCurrentLocation(token)).GetItems(token))!; var possibleItemsToSelect = currentLocationItems.Take(CurrentSelectedIndex).Reverse().Skip(skip).ToList(); if (possibleItemsToSelect.Count == 0) possibleItemsToSelect = currentLocationItems.ToList(); - await SelectItem(possibleItemsToSelect); + await SelectItem(possibleItemsToSelect, token); } - public async Task SelectNextItem(int skip = 0) + public async Task SelectNextItem(int skip = 0, CancellationToken token = default) { - var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; + var currentLocationItems = (await (await GetCurrentLocation(token)).GetItems(token))!; var possibleItemsToSelect = currentLocationItems.Skip(CurrentSelectedIndex + 1 + skip).ToList(); if (possibleItemsToSelect.Count == 0) possibleItemsToSelect = currentLocationItems.Reverse().ToList(); - await SelectItem(possibleItemsToSelect); + await SelectItem(possibleItemsToSelect, token); } - private async Task SelectItem(IEnumerable currentPossibleItems) + private async Task SelectItem(IEnumerable currentPossibleItems, CancellationToken token = default) { if (!currentPossibleItems.Any()) return; - var currentLocation = await GetCurrentLocation(); - var currentLocationItems = (await currentLocation.GetItems())!; + if (token.IsCancellationRequested) return; + var currentLocation = await GetCurrentLocation(token); + var currentLocationItems = (await currentLocation.GetItems(token))!; if (await GetCurrentSelectedItem() != null) { + if (token.IsCancellationRequested) return; + _currentlySelecting = true; - currentLocation?.RefreshAsync(); + if (AutoRefresh && currentLocation != null) + { + await currentLocation.RefreshAsync(token); + if (token.IsCancellationRequested) return; + } IItem? newSelectedItem = null; foreach (var item in currentPossibleItems) @@ -215,15 +231,15 @@ namespace FileTime.Core.Components 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); } _currentlySelecting = false; - await SetCurrentSelectedItem(newSelectedItem ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null)); + await SetCurrentSelectedItem(newSelectedItem ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null), token: token); } else { - await SetCurrentSelectedItem(currentLocationItems.Count > 0 ? currentLocationItems[0] : null); + await SetCurrentSelectedItem(currentLocationItems.Count > 0 ? currentLocationItems[0] : null, token: token); } } @@ -269,7 +285,7 @@ namespace FileTime.Core.Components await SetCurrentSelectedItem(newCurrentLocation); } - foreach(var lastLocationItem in currentLocationItems.OfType()) + foreach (var lastLocationItem in currentLocationItems.OfType()) { lastLocationItem.Dispose(); } diff --git a/src/Core/FileTime.Core/Models/IContainer.cs b/src/Core/FileTime.Core/Models/IContainer.cs index 36321a9..95ce589 100644 --- a/src/Core/FileTime.Core/Models/IContainer.cs +++ b/src/Core/FileTime.Core/Models/IContainer.cs @@ -18,6 +18,7 @@ namespace FileTime.Core.Models Task Clone(); Task CanOpen(); + void Unload(); bool IsLoaded { get; } bool SupportsDirectoryLevelSoftDelete { get; } diff --git a/src/Core/FileTime.Core/Models/VirtualContainer.cs b/src/Core/FileTime.Core/Models/VirtualContainer.cs index 3efeb44..fd0f62d 100644 --- a/src/Core/FileTime.Core/Models/VirtualContainer.cs +++ b/src/Core/FileTime.Core/Models/VirtualContainer.cs @@ -175,5 +175,10 @@ namespace FileTime.Core.Models { BaseContainer.Dispose(); } + + public void Unload() + { + BaseContainer.Unload(); + } } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Providers/TopContainer.cs b/src/Core/FileTime.Core/Providers/TopContainer.cs index b0f5b5d..4a15dfa 100644 --- a/src/Core/FileTime.Core/Providers/TopContainer.cs +++ b/src/Core/FileTime.Core/Providers/TopContainer.cs @@ -72,5 +72,7 @@ namespace FileTime.Core.Providers public Task CanOpen() => Task.FromResult(true); public void Dispose() { } + + public void Unload() { } } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Timeline/TimeContainer.cs b/src/Core/FileTime.Core/Timeline/TimeContainer.cs index a155a72..93018ad 100644 --- a/src/Core/FileTime.Core/Timeline/TimeContainer.cs +++ b/src/Core/FileTime.Core/Timeline/TimeContainer.cs @@ -125,5 +125,6 @@ namespace FileTime.Core.Timeline public Task CanOpen() => Task.FromResult(true); public void Dispose() => IsDisposed = true; + public void Unload() { } } } \ No newline at end of file diff --git a/src/Core/FileTime.Core/Timeline/TimeProvider.cs b/src/Core/FileTime.Core/Timeline/TimeProvider.cs index 87442e8..a81392e 100644 --- a/src/Core/FileTime.Core/Timeline/TimeProvider.cs +++ b/src/Core/FileTime.Core/Timeline/TimeProvider.cs @@ -94,5 +94,7 @@ namespace FileTime.Core.Timeline public Task CanOpen() => Task.FromResult(true); public void Dispose() { } + + public void Unload() { } } } \ No newline at end of file diff --git a/src/GuiApp/FileTime.Avalonia/App.axaml b/src/GuiApp/FileTime.Avalonia/App.axaml index 732cee5..80eccfb 100644 --- a/src/GuiApp/FileTime.Avalonia/App.axaml +++ b/src/GuiApp/FileTime.Avalonia/App.axaml @@ -132,6 +132,8 @@ + + diff --git a/src/GuiApp/FileTime.Avalonia/Application/TabContainer.cs b/src/GuiApp/FileTime.Avalonia/Application/TabContainer.cs index 458e6cf..6170b0f 100644 --- a/src/GuiApp/FileTime.Avalonia/Application/TabContainer.cs +++ b/src/GuiApp/FileTime.Avalonia/Application/TabContainer.cs @@ -21,6 +21,8 @@ namespace FileTime.Avalonia.Application public partial class TabContainer : INewItemProcessor { private bool _updateFromCode; + private CancellationTokenSource? _moveCancellationTokenSource; + [Property] private TabState _tabState; @@ -70,6 +72,10 @@ namespace FileTime.Avalonia.Application { if (_selectedItem != value) { + if(_selectedItem is ContainerViewModel containerVM) + { + containerVM.Unload(unloadParent: false); + } _selectedItem = value; if (value is ElementViewModel elementViewModel) @@ -83,8 +89,10 @@ namespace FileTime.Avalonia.Application ElementPreview = null; } - await Tab.SetCurrentSelectedItem(SelectedItem?.Item, fromDataBinding); - OnPropertyChanged(nameof(SelectedItem)); + if (await Tab.SetCurrentSelectedItem(SelectedItem?.Item, fromDataBinding)) + { + OnPropertyChanged(nameof(SelectedItem)); + } } } @@ -131,6 +139,7 @@ namespace FileTime.Avalonia.Application private async Task Tab_CurrentLocationChanged(object? sender, AsyncEventArgs e, CancellationToken token = default) { + CurrentLocation.Unload(true); var currentLocation = await Tab.GetCurrentLocation(token); var parent = GenerateParent(currentLocation); CurrentLocation = new ContainerViewModel(this, parent, currentLocation, ItemNameConverterService); @@ -270,6 +279,13 @@ namespace FileTime.Avalonia.Application } } + private CancellationToken CancelAndGenerateNextMovementToken() + { + if(_moveCancellationTokenSource != null) _moveCancellationTokenSource.Cancel(); + _moveCancellationTokenSource = new CancellationTokenSource(); + return _moveCancellationTokenSource.Token; + } + public async Task Open() { if (ChildContainer != null) @@ -285,32 +301,32 @@ namespace FileTime.Avalonia.Application public async Task MoveCursorDown() { - await RunFromCode(async () => await Tab.SelectNextItem()); + await RunFromCode(async () => await Tab.SelectNextItem(token: CancelAndGenerateNextMovementToken())); } public async Task MoveCursorDownPage() { - await RunFromCode(async () => await Tab.SelectNextItem(10)); + await RunFromCode(async () => await Tab.SelectNextItem(10, token: CancelAndGenerateNextMovementToken())); } public async Task MoveCursorUp() { - await RunFromCode(async () => await Tab.SelectPreviousItem()); + await RunFromCode(async () => await Tab.SelectPreviousItem(token: CancelAndGenerateNextMovementToken())); } public async Task MoveCursorUpPage() { - await RunFromCode(async () => await Tab.SelectPreviousItem(10)); + await RunFromCode(async () => await Tab.SelectPreviousItem(10, token: CancelAndGenerateNextMovementToken())); } public async Task MoveCursorToFirst() { - await RunFromCode(Tab.SelectFirstItem); + await RunFromCode(async () => await Tab.SelectFirstItem(token: CancelAndGenerateNextMovementToken())); } public async Task MoveCursorToLast() { - await RunFromCode(Tab.SelectLastItem); + await RunFromCode(async () => await Tab.SelectLastItem(token: CancelAndGenerateNextMovementToken())); } public async Task GotToProvider() diff --git a/src/GuiApp/FileTime.Avalonia/Converters/ContextMenuGenerator.cs b/src/GuiApp/FileTime.Avalonia/Converters/ContextMenuGenerator.cs index b000c52..8a492fe 100644 --- a/src/GuiApp/FileTime.Avalonia/Converters/ContextMenuGenerator.cs +++ b/src/GuiApp/FileTime.Avalonia/Converters/ContextMenuGenerator.cs @@ -10,14 +10,12 @@ namespace FileTime.Avalonia.Converters { public class ContextMenuGenerator : IValueConverter { - private readonly IContextMenuProvider _contextMenuProvider; + private IContextMenuProvider? _contextMenuProvider; - public ContextMenuGenerator() - { - _contextMenuProvider = App.ServiceProvider.GetService() ?? throw new Exception($"No {nameof(IContextMenuProvider)} is registered."); - } public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { + _contextMenuProvider ??= App.ServiceProvider.GetService() ?? throw new Exception($"No {nameof(IContextMenuProvider)} is registered."); + if (value is ContainerViewModel containerViewModel) { return _contextMenuProvider.GetContextMenuForFolder(containerViewModel.Container); diff --git a/src/GuiApp/FileTime.Avalonia/Converters/GetFileExtensionConverter.cs b/src/GuiApp/FileTime.Avalonia/Converters/GetFileExtensionConverter.cs new file mode 100644 index 0000000..c543cee --- /dev/null +++ b/src/GuiApp/FileTime.Avalonia/Converters/GetFileExtensionConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Globalization; +using Avalonia.Data.Converters; +using FileTime.Avalonia.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace FileTime.Avalonia.Converters +{ + public class GetFileExtensionConverter : IValueConverter + { + private ItemNameConverterService? _itemNameConverterService; + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is not string fullName) return value; + _itemNameConverterService ??= App.ServiceProvider.GetService() ?? throw new Exception($"No {nameof(ItemNameConverterService)} is registered.");; + + return _itemNameConverterService.GetFileExtension(fullName); + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/GuiApp/FileTime.Avalonia/Converters/ItemViewModelToAttibuteTypeConverter.cs b/src/GuiApp/FileTime.Avalonia/Converters/ItemViewModelIsAttibuteTypeConverter.cs similarity index 81% rename from src/GuiApp/FileTime.Avalonia/Converters/ItemViewModelToAttibuteTypeConverter.cs rename to src/GuiApp/FileTime.Avalonia/Converters/ItemViewModelIsAttibuteTypeConverter.cs index 890aa18..29590a4 100644 --- a/src/GuiApp/FileTime.Avalonia/Converters/ItemViewModelToAttibuteTypeConverter.cs +++ b/src/GuiApp/FileTime.Avalonia/Converters/ItemViewModelIsAttibuteTypeConverter.cs @@ -9,9 +9,12 @@ namespace FileTime.Avalonia.Converters { public class ItemViewModelIsAttibuteTypeConverter : IValueConverter { + public bool Invert { get; set; } public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - return parameter is AttibuteType targetAttribute && GetAttibuteType(value) == targetAttribute; + var result = parameter is AttibuteType targetAttribute && GetAttibuteType(value) == targetAttribute; + if (Invert && parameter is AttibuteType) result = !result; + return result; } private static AttibuteType? GetAttibuteType(object? value) diff --git a/src/GuiApp/FileTime.Avalonia/Services/ItemNameConverterService.cs b/src/GuiApp/FileTime.Avalonia/Services/ItemNameConverterService.cs index 83c0f6c..01277b4 100644 --- a/src/GuiApp/FileTime.Avalonia/Services/ItemNameConverterService.cs +++ b/src/GuiApp/FileTime.Avalonia/Services/ItemNameConverterService.cs @@ -2,6 +2,7 @@ using FileTime.Avalonia.Application; using FileTime.Avalonia.Models; using FileTime.Avalonia.ViewModels; +using FileTime.Core.Models; using MvvmGen; using System; using System.Collections.Generic; @@ -17,9 +18,10 @@ namespace FileTime.Avalonia.Services var nameParts = new List(); var rapidTravelText = AppState.RapidTravelText.ToLower(); + var name = itemViewModel.Item is IElement ? GetFileName(itemViewModel.Item.Name) : itemViewModel.Item.Name; if (AppState.ViewMode == ViewMode.RapidTravel && rapidTravelText.Length > 0) { - var nameLeft = itemViewModel.Item.Name; + var nameLeft = name; while (nameLeft.ToLower().IndexOf(rapidTravelText, StringComparison.Ordinal) is int rapidTextStart && rapidTextStart != -1) { @@ -43,9 +45,22 @@ namespace FileTime.Avalonia.Services } else { - nameParts.Add(new ItemNamePart(itemViewModel.Item.Name)); + nameParts.Add(new ItemNamePart(name)); } return nameParts; } + + public string GetFileName(string fullName) + { + var parts = fullName.Split('.'); + var fileName = string.Join('.', parts[..^1]); + return fileName == "." ? fullName : fileName; + } + + public string GetFileExtension(string fullName) + { + var parts = fullName.Split('.'); + return parts.Length > 1 ? parts[^1] : ""; + } } } diff --git a/src/GuiApp/FileTime.Avalonia/ViewModels/ContainerViewModel.cs b/src/GuiApp/FileTime.Avalonia/ViewModels/ContainerViewModel.cs index 557ba5b..5218482 100644 --- a/src/GuiApp/FileTime.Avalonia/ViewModels/ContainerViewModel.cs +++ b/src/GuiApp/FileTime.Avalonia/ViewModels/ContainerViewModel.cs @@ -72,7 +72,7 @@ namespace FileTime.Avalonia.ViewModels { get { - if (!_isInitialized) Task.Run(Refresh); + if (!_isInitialized) Task.Run(Refresh).Wait(); return _containers; } set @@ -90,7 +90,7 @@ namespace FileTime.Avalonia.ViewModels { get { - if (!_isInitialized) Task.Run(Refresh); + if (!_isInitialized) Task.Run(Refresh).Wait(); return _elements; } set @@ -107,7 +107,7 @@ namespace FileTime.Avalonia.ViewModels { get { - if (!_isInitialized) Task.Run(Refresh); + if (!_isInitialized) Task.Run(Refresh).Wait(); return _items; } set @@ -139,15 +139,15 @@ namespace FileTime.Avalonia.ViewModels private async Task Container_Refreshed(object? sender, AsyncEventArgs e, CancellationToken token = default) { - await Refresh(false, false, token); + await Refresh(false, false, token: token); } [Obsolete($"Use the parametrizable version of {nameof(Refresh)}.")] private async Task Refresh() { - await Refresh(true); + await Refresh(true, silent: true); } - private async Task Refresh(bool initializeChildren, bool alloweReuse = true, CancellationToken token = default) + private async Task Refresh(bool initializeChildren, bool alloweReuse = true, bool silent = false, CancellationToken token = default) { if (_isRefreshing) return; @@ -169,9 +169,9 @@ namespace FileTime.Avalonia.ViewModels } } - if(await _container.GetElements() is IReadOnlyList elements) + if (await _container.GetElements() is IReadOnlyList elements) { - foreach(var element in elements) + foreach (var element in elements) { var generator = async (IElement e) => { @@ -203,9 +203,18 @@ namespace FileTime.Avalonia.ViewModels containerToRemove?.Dispose(); } - Containers = new ObservableCollection(newContainers); - Elements = new ObservableCollection(newElements); - Items = new ObservableCollection(newContainers.Cast().Concat(newElements)); + if (silent) + { + _containers = new ObservableCollection(newContainers); + _elements = new ObservableCollection(newElements); + _items = new ObservableCollection(newContainers.Cast().Concat(newElements)); + } + else + { + Containers = new ObservableCollection(newContainers); + Elements = new ObservableCollection(newElements); + Items = new ObservableCollection(newContainers.Cast().Concat(newElements)); + } for (var i = 0; i < Items.Count; i++) { @@ -260,19 +269,35 @@ namespace FileTime.Avalonia.ViewModels return await generator(item); } - public void Unload(bool recursive = true) + public void Unload(bool recursive = true, bool unloadParent = true, bool unloadEvents = false) { _isInitialized = false; if (recursive) { foreach (var container in _containers) { - container.Unload(true); + container.Unload(true, false, true); container.Dispose(); container.ChildrenToAdopt.Clear(); } } + if (unloadParent) + { + var parent = Parent; + while (parent != null) + { + var lastParent = parent; + parent = parent.Parent; + lastParent.Unload(); + } + } + + if(unloadEvents) + { + Container.Refreshed.Remove(Container_Refreshed); + } + _containers.Clear(); _elements.Clear(); _items.Clear(); diff --git a/src/GuiApp/FileTime.Avalonia/ViewModels/ElementPreviewViewModel.cs b/src/GuiApp/FileTime.Avalonia/ViewModels/ElementPreviewViewModel.cs index 488de73..c5f14cc 100644 --- a/src/GuiApp/FileTime.Avalonia/ViewModels/ElementPreviewViewModel.cs +++ b/src/GuiApp/FileTime.Avalonia/ViewModels/ElementPreviewViewModel.cs @@ -34,7 +34,14 @@ namespace FileTime.Avalonia.ViewModels } else if (elementSize < MAXTEXTPREVIEWSIZE) { - TextContent = await element.GetContent(); + try + { + TextContent = await element.GetContent(); + } + catch(Exception e) + { + TextContent = $"Error while getting content of {element.FullName}. " + e.ToString(); + } Mode = ElementPreviewMode.Text; } else diff --git a/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs b/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs index 16b69eb..871650e 100644 --- a/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs +++ b/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs @@ -202,7 +202,9 @@ namespace FileTime.Avalonia.ViewModels } places.Add(new PlaceInfo(name, container)); } + } + LocalContentProvider.Unload(); } else { @@ -972,6 +974,22 @@ namespace FileTime.Avalonia.ViewModels } } + private Task ToggleAutoRefresh() + { + var tab = AppState.SelectedTab.TabState.Tab; + tab.AutoRefresh = !tab.AutoRefresh; + + var text = "Auto refresh is: " + (tab.AutoRefresh ? "ON" : "OFF"); + _popupTexts.Add(text); + + Task.Run(async () => + { + await Task.Delay(5000); + await Dispatcher.UIThread.InvokeAsync(() => _popupTexts.Remove(text)); + }); + return Task.CompletedTask; + } + [Command] public async void ProcessInputs() { @@ -1438,6 +1456,11 @@ namespace FileTime.Avalonia.ViewModels FileTime.App.Core.Command.Commands.Dummy, new KeyWithModifiers[] { new KeyWithModifiers(Key.T), new KeyWithModifiers(Key.M) }, ChangeTimelineMode), + new CommandBinding( + "toggle auto refresh", + FileTime.App.Core.Command.Commands.Dummy, + new KeyWithModifiers[] { new KeyWithModifiers(Key.R, shift: true) }, + ToggleAutoRefresh), //TODO REMOVE new CommandBinding( "open in default file browser", diff --git a/src/GuiApp/FileTime.Avalonia/Views/ItemView.axaml b/src/GuiApp/FileTime.Avalonia/Views/ItemView.axaml index c48c4e9..b2a68a8 100644 --- a/src/GuiApp/FileTime.Avalonia/Views/ItemView.axaml +++ b/src/GuiApp/FileTime.Avalonia/Views/ItemView.axaml @@ -21,6 +21,7 @@ Source="{Binding Converter={StaticResource ItemToImageConverter}}" /> @@ -41,13 +42,14 @@ - - - - - + + + + + diff --git a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs index b1eb4c6..b3ca6b2 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs @@ -103,6 +103,20 @@ namespace FileTime.Providers.Local public Task Rename(string newName) => throw new NotSupportedException(); public Task CanOpen() => Task.FromResult(true); - public void Dispose() { } + public void Dispose() + { + foreach (var c in _rootContainers) + { + c.Unload(); + } + } + + public void Unload() + { + foreach (var c in _rootContainers) + { + c.Unload(); + } + } } } \ 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 017b5ad..7cf0c4b 100644 --- a/src/Providers/FileTime.Providers.Local/LocalFolder.cs +++ b/src/Providers/FileTime.Providers.Local/LocalFolder.cs @@ -27,7 +27,7 @@ namespace FileTime.Providers.Local public SupportsDelete CanDelete { get; } public bool CanRename => true; - public AsyncEventHandler Refreshed { get; } = new(); + public AsyncEventHandler Refreshed { get; private set; } = new(); public string Attributes => GetAttributes(); @@ -191,6 +191,14 @@ namespace FileTime.Providers.Local _containers = null; _elements = null; IsDisposed = true; + Refreshed = new AsyncEventHandler(); + } + + public void Unload() + { + _items = null; + _containers = null; + _elements = null; } } } \ 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 f0d48e3..bf69da4 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs @@ -106,5 +106,7 @@ namespace FileTime.Providers.Smb public Task CanOpen() => Task.FromResult(true); public void Dispose() { } + + public void Unload() { } } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Smb/SmbFolder.cs b/src/Providers/FileTime.Providers.Smb/SmbFolder.cs index 51fb6cb..3f748be 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbFolder.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbFolder.cs @@ -119,21 +119,28 @@ namespace FileTime.Providers.Smb public async Task?> GetItems(CancellationToken token = default) { - if (_items == null) await RefreshAsync(); + if (_items == null) await RefreshAsync(token); return _items; } public async Task?> GetContainers(CancellationToken token = default) { - if (_containers == null) await RefreshAsync(); + if (_containers == null) await RefreshAsync(token); return _containers; } public async Task?> GetElements(CancellationToken token = default) { - if (_elements == null) await RefreshAsync(); + if (_elements == null) await RefreshAsync(token); return _elements; } public Task CanOpen() => Task.FromResult(true); public void Dispose() => IsDisposed = true; + + public void Unload() + { + _items = null; + _containers = null; + _elements = null; + } } } \ 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 77cb732..e8c672b 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbServer.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbServer.cs @@ -194,5 +194,7 @@ namespace FileTime.Providers.Smb public Task CanOpen() => Task.FromResult(true); public void Dispose() { } + + public void Unload() { } } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Smb/SmbShare.cs b/src/Providers/FileTime.Providers.Smb/SmbShare.cs index c7008ba..e5a2142 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbShare.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbShare.cs @@ -172,5 +172,12 @@ namespace FileTime.Providers.Smb public Task CanOpen() => Task.FromResult(true); public void Dispose() { } + + public void Unload() + { + _items = null; + _containers = null; + _elements = null; + } } } \ No newline at end of file