Top navigation, search delete

This commit is contained in:
2023-07-28 17:10:50 +02:00
parent ee42e38e45
commit ed6864e127
27 changed files with 320 additions and 149 deletions

View File

@@ -15,32 +15,17 @@ public class TabPersistenceService : ITabPersistenceService
private readonly IAppState _appState; private readonly IAppState _appState;
private readonly ILogger<TabPersistenceService> _logger; private readonly ILogger<TabPersistenceService> _logger;
private class PersistenceRoot //TODO: make this a configuration maybe?
private readonly List<string> _contentProvidersNotToRestore = new()
{ {
public TabStates? TabStates { get; set; } "search"
} };
private class TabStates private record PersistenceRoot(TabStates? TabStates);
{
public List<TabState>? Tabs { get; set; }
public int? ActiveTabNumber { get; set; }
}
private class TabState private record TabStates(List<TabState>? Tabs, int? ActiveTabNumber);
{
public string? Path { get; set; }
public int Number { get; set; }
public TabState() private record TabState(string? Path, int Number);
{
}
public TabState(FullName path, int number)
{
Path = path.Path;
Number = number;
}
}
private readonly string _settingsPath; private readonly string _settingsPath;
private readonly JsonSerializerOptions _jsonOptions; private readonly JsonSerializerOptions _jsonOptions;
@@ -149,7 +134,7 @@ public class TabPersistenceService : ITabPersistenceService
} }
catch catch
{ {
path = path.GetParent(); path = path?.GetParent();
if (path == null) if (path == null)
{ {
throw new Exception($"Could not find an initializable path along {tab.Path}"); throw new Exception($"Could not find an initializable path along {tab.Path}");
@@ -159,6 +144,8 @@ public class TabPersistenceService : ITabPersistenceService
if (container == null) continue; if (container == null) continue;
if (_contentProvidersNotToRestore.Contains(container.Provider.Name)) continue;
var tabToLoad = await _serviceProvider.GetAsyncInitableResolver(container) var tabToLoad = await _serviceProvider.GetAsyncInitableResolver(container)
.GetRequiredServiceAsync<ITab>(); .GetRequiredServiceAsync<ITab>();
var tabViewModel = _serviceProvider.GetInitableResolver(tabToLoad, tab.Number) var tabViewModel = _serviceProvider.GetInitableResolver(tabToLoad, tab.Number)
@@ -181,7 +168,15 @@ public class TabPersistenceService : ITabPersistenceService
if (_appState.Tabs.Count == 0) return false; if (_appState.Tabs.Count == 0) return false;
var tabToActivate = _appState.Tabs.FirstOrDefault(t => t.TabNumber == tabStates.ActiveTabNumber); var optimalTabs = _appState
.Tabs
.TakeWhile(t => t.TabNumber <= tabStates.ActiveTabNumber)
.Reverse();
var suboptimalTabs = _appState
.Tabs
.SkipWhile(t => t.TabNumber <= tabStates.ActiveTabNumber);
var tabToActivate = optimalTabs.Concat(suboptimalTabs).FirstOrDefault();
if (tabToActivate is not null) _appState.SetSelectedTab(tabToActivate); if (tabToActivate is not null) _appState.SetSelectedTab(tabToActivate);
return true; return true;
@@ -189,10 +184,8 @@ public class TabPersistenceService : ITabPersistenceService
public void SaveStates(CancellationToken token = default) public void SaveStates(CancellationToken token = default)
{ {
var state = new PersistenceRoot var state = new PersistenceRoot(SerializeTabStates());
{
TabStates = SerializeTabStates()
};
var settingsDirectory = new DirectoryInfo(string.Join(Path.DirectorySeparatorChar, var settingsDirectory = new DirectoryInfo(string.Join(Path.DirectorySeparatorChar,
_settingsPath.Split(Path.DirectorySeparatorChar)[0..^1])); _settingsPath.Split(Path.DirectorySeparatorChar)[0..^1]));
if (!settingsDirectory.Exists) settingsDirectory.Create(); if (!settingsDirectory.Exists) settingsDirectory.Create();
@@ -207,14 +200,13 @@ public class TabPersistenceService : ITabPersistenceService
{ {
var currentLocation = tab.CurrentLocation.Value; var currentLocation = tab.CurrentLocation.Value;
if (currentLocation is null) continue; if (currentLocation is null) continue;
tabStates.Add(new TabState(currentLocation.FullName!, tab.TabNumber)); tabStates.Add(new TabState(currentLocation.FullName!.Path, tab.TabNumber));
} }
return new TabStates return new TabStates(
{ tabStates,
Tabs = tabStates, _appState.CurrentSelectedTab?.TabNumber
ActiveTabNumber = _appState.CurrentSelectedTab?.TabNumber );
};
} }
public async Task InitAsync() public async Task InitAsync()

View File

@@ -10,7 +10,6 @@ using FileTime.App.Core.UserCommand;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.Core.Command; using FileTime.Core.Command;
using FileTime.Core.Command.CreateContainer; using FileTime.Core.Command.CreateContainer;
using FileTime.Core.Command.CreateElement;
using FileTime.Core.Command.Move; using FileTime.Core.Command.Move;
using FileTime.Core.Extensions; using FileTime.Core.Extensions;
using FileTime.Core.Interactions; using FileTime.Core.Interactions;

View File

@@ -15,7 +15,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
{ {
private readonly ISystemClipboardService _systemClipboardService; private readonly ISystemClipboardService _systemClipboardService;
private readonly IUserCommunicationService _userCommunicationService; private readonly IUserCommunicationService _userCommunicationService;
private readonly ISearchManager _searchManager; private readonly ISearchContentProvider _searchContentProvider;
private readonly IItemNameConverterService _itemNameConverterService; private readonly IItemNameConverterService _itemNameConverterService;
private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITimelessContentProvider _timelessContentProvider;
private readonly IUserCommandHandlerService _userCommandHandlerService; private readonly IUserCommandHandlerService _userCommandHandlerService;
@@ -28,7 +28,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
IAppState appState, IAppState appState,
ISystemClipboardService systemClipboardService, ISystemClipboardService systemClipboardService,
IUserCommunicationService userCommunicationService, IUserCommunicationService userCommunicationService,
ISearchManager searchManager, ISearchContentProvider searchContentProvider,
IItemNameConverterService itemNameConverterService, IItemNameConverterService itemNameConverterService,
ITimelessContentProvider timelessContentProvider, ITimelessContentProvider timelessContentProvider,
IUserCommandHandlerService userCommandHandlerService, IUserCommandHandlerService userCommandHandlerService,
@@ -36,7 +36,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
{ {
_systemClipboardService = systemClipboardService; _systemClipboardService = systemClipboardService;
_userCommunicationService = userCommunicationService; _userCommunicationService = userCommunicationService;
_searchManager = searchManager; _searchContentProvider = searchContentProvider;
_itemNameConverterService = itemNameConverterService; _itemNameConverterService = itemNameConverterService;
_timelessContentProvider = timelessContentProvider; _timelessContentProvider = timelessContentProvider;
_userCommandHandlerService = userCommandHandlerService; _userCommandHandlerService = userCommandHandlerService;
@@ -113,7 +113,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
_ => throw new ArgumentOutOfRangeException() _ => throw new ArgumentOutOfRangeException()
}; };
var searchTask = await _searchManager.StartSearchAsync(searchMatcher, _currentLocation.Value); var searchTask = await _searchContentProvider.StartSearchAsync(searchMatcher, _currentLocation.Value);
var openContainerCommand = new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, searchTask.SearchContainer)); var openContainerCommand = new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, searchTask.SearchContainer));
await _userCommandHandlerService.HandleCommandAsync(openContainerCommand); await _userCommandHandlerService.HandleCommandAsync(openContainerCommand);
} }

View File

@@ -52,7 +52,7 @@ public abstract partial class ItemViewModel : IItemViewModel
var displayName = itemViewModelType switch var displayName = itemViewModelType switch
{ {
ItemViewModelType.Main => _appState.RapidTravelText.Map(s => (IReadOnlyList<ItemNamePart>) _itemNameConverterService.GetDisplayName(item.DisplayName, s)), ItemViewModelType.Main => _appState.RapidTravelText.Map(s => (IReadOnlyList<ItemNamePart>) _itemNameConverterService.GetDisplayName(item.DisplayName, s)),
_ => new DeclarativeProperty<IReadOnlyList<ItemNamePart>>(new List<ItemNamePart> {new (item.DisplayName)}), _ => new DeclarativeProperty<IReadOnlyList<ItemNamePart>>(new List<ItemNamePart> {new(item.DisplayName)}),
}; };
BaseItem = item; BaseItem = item;
@@ -103,6 +103,6 @@ public abstract partial class ItemViewModel : IItemViewModel
var deepestPath = _parentTab.Tab.LastDeepestSelectedPath; var deepestPath = _parentTab.Tab.LastDeepestSelectedPath;
var commonPath = FullName.CreateSafe(PathHelper.GetCommonPath(ownFullName.Path, deepestPath.Path)); var commonPath = FullName.CreateSafe(PathHelper.GetCommonPath(ownFullName.Path, deepestPath.Path));
return commonPath.Path == ownFullName.Path; return commonPath is not null && commonPath.Path == ownFullName.Path;
} }
} }

View File

@@ -198,7 +198,7 @@ public partial class TabViewModel : ITabViewModel
private static async Task<IItem> MapItemAsync(AbsolutePath item) private static async Task<IItem> MapItemAsync(AbsolutePath item)
=> await item.ResolveAsync(forceResolve: true, => await item.ResolveAsync(forceResolve: true,
itemInitializationSettings: new ItemInitializationSettings(true)); itemInitializationSettings: new ItemInitializationSettings {SkipChildInitialization = true});
private IItemViewModel MapItemToViewModel(IItem item, ItemViewModelType type) private IItemViewModel MapItemToViewModel(IItem item, ItemViewModelType type)
{ {

View File

@@ -31,6 +31,7 @@ public static class DependencyInjection
serviceCollection.TryAddSingleton<ICommandRunner, CommandRunner>(); serviceCollection.TryAddSingleton<ICommandRunner, CommandRunner>();
serviceCollection.TryAddSingleton<IContentAccessorFactory, ContentAccessorFactory>(); serviceCollection.TryAddSingleton<IContentAccessorFactory, ContentAccessorFactory>();
serviceCollection.TryAddSingleton<IContentProviderRegistry, ContentProviderRegistry>(); serviceCollection.TryAddSingleton<IContentProviderRegistry, ContentProviderRegistry>();
serviceCollection.TryAddSingleton<IRootContentProvider, RootContentProvider>();
//TODO: check local/remote context //TODO: check local/remote context
serviceCollection.TryAddSingleton<ILocalCommandExecutor, LocalCommandExecutor>(); serviceCollection.TryAddSingleton<ILocalCommandExecutor, LocalCommandExecutor>();
serviceCollection.TryAddSingleton<ICommandSchedulerNotifier, LocalCommandSchedulerNotifier>(); serviceCollection.TryAddSingleton<ICommandSchedulerNotifier, LocalCommandSchedulerNotifier>();

View File

@@ -1,8 +1,10 @@
using FileTime.Core.ContentAccess; using FileTime.Core.ContentAccess;
using FileTime.Core.Models;
namespace FileTime.App.Search; namespace FileTime.App.Search;
public interface ISearchContentProvider : IContentProvider public interface ISearchContentProvider : IContentProvider
{ {
Task<ISearchTask> StartSearchAsync(ISearchMatcher matcher, IContainer searchIn);
void RemoveSearch(FullName searchFullName);
} }

View File

@@ -1,9 +0,0 @@
using FileTime.Core.Models;
namespace FileTime.App.Search;
public interface ISearchManager
{
Task<ISearchTask> StartSearchAsync(ISearchMatcher matcher, IContainer searchIn);
IReadOnlyList<ISearchTask> SearchTasks { get; }
}

View File

@@ -7,12 +7,14 @@ namespace FileTime.App.Search;
public class SearchContentProvider : ContentProviderBase, ISearchContentProvider public class SearchContentProvider : ContentProviderBase, ISearchContentProvider
{ {
private readonly ISearchManager _searchManager; private readonly ITimelessContentProvider _timelessContentProvider;
private readonly List<SearchTask> _searchTasks = new();
public const string ContentProviderName = "search"; public const string ContentProviderName = "search";
public SearchContentProvider(ISearchManager searchManager) : base(ContentProviderName) public SearchContentProvider(ITimelessContentProvider timelessContentProvider)
: base(ContentProviderName, timelessContentProvider)
{ {
_searchManager = searchManager; _timelessContentProvider = timelessContentProvider;
} }
public override Task<IItem> GetItemByNativePathAsync( public override Task<IItem> GetItemByNativePathAsync(
@@ -21,9 +23,12 @@ public class SearchContentProvider : ContentProviderBase, ISearchContentProvider
bool forceResolve = false, bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default ItemInitializationSettings itemInitializationSettings = default
) => )
Task.FromResult((IItem) _searchManager.SearchTasks {
if (nativePath.Path == ContentProviderName) return Task.FromResult((IItem) this);
return Task.FromResult((IItem) _searchTasks
.First(searchTask => searchTask.SearchContainer.NativePath == nativePath).SearchContainer); .First(searchTask => searchTask.SearchContainer.NativePath == nativePath).SearchContainer);
}
public override NativePath GetNativePath(FullName fullName) => new(fullName.Path); public override NativePath GetNativePath(FullName fullName) => new(fullName.Path);
public override FullName GetFullName(NativePath nativePath) => new(nativePath.Path); public override FullName GetFullName(NativePath nativePath) => new(nativePath.Path);
@@ -36,4 +41,34 @@ public class SearchContentProvider : ContentProviderBase, ISearchContentProvider
=> Task.FromResult(null as byte[]); => Task.FromResult(null as byte[]);
public override bool CanHandlePath(NativePath path) => path.Path.StartsWith(ContentProviderName); public override bool CanHandlePath(NativePath path) => path.Path.StartsWith(ContentProviderName);
public async Task<ISearchTask> StartSearchAsync(ISearchMatcher matcher, IContainer searchIn)
{
var searchTask = new SearchTask(
searchIn,
this,
_timelessContentProvider,
matcher,
new AbsolutePath(_timelessContentProvider, this)
);
_searchTasks.Add(searchTask);
await searchTask.StartAsync();
Items.Add(new AbsolutePath(_timelessContentProvider, searchTask.SearchContainer));
return searchTask;
}
public void RemoveSearch(FullName searchFullName)
{
var searchTask = _searchTasks.FirstOrDefault(t => t.SearchContainer.FullName == searchFullName);
if (searchTask is null) return;
_searchTasks.Remove(searchTask);
var searchItem = Items.FirstOrDefault(c => c.Path == searchTask.SearchContainer.FullName);
if (searchItem is not null)
{
Items.Remove(searchItem);
}
}
} }

View File

@@ -0,0 +1,13 @@
using FileTime.Core.ContentAccess;
using FileTime.Core.Models;
namespace FileTime.App.Search;
public class SearchItemDeleter : IItemDeleter<ISearchContentProvider>
{
public Task DeleteAsync(ISearchContentProvider contentProvider, FullName fullName)
{
contentProvider.RemoveSearch(fullName);
return Task.CompletedTask;
}
}

View File

@@ -1,35 +0,0 @@
using FileTime.Core.Models;
using Microsoft.Extensions.DependencyInjection;
namespace FileTime.App.Search;
public class SearchManager : ISearchManager
{
private readonly IServiceProvider _serviceProvider;
private ISearchContentProvider? _searchContainerProvider;
private readonly List<SearchTask> _searchTasks = new();
public IReadOnlyList<ISearchTask> SearchTasks { get; }
public SearchManager(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
SearchTasks = _searchTasks.AsReadOnly();
}
public async Task<ISearchTask> StartSearchAsync(ISearchMatcher matcher, IContainer searchIn)
{
_searchContainerProvider ??= _serviceProvider.GetRequiredService<ISearchContentProvider>();
var searchTask = new SearchTask(
searchIn,
_searchContainerProvider,
matcher
);
_searchTasks.Add(searchTask);
await searchTask.StartAsync();
return searchTask;
}
}

View File

@@ -10,6 +10,7 @@ namespace FileTime.App.Search;
public class SearchTask : ISearchTask public class SearchTask : ISearchTask
{ {
private readonly IContainer _baseContainer; private readonly IContainer _baseContainer;
private readonly ITimelessContentProvider _timelessContentProvider;
private readonly ISearchMatcher _matcher; private readonly ISearchMatcher _matcher;
private readonly Container _container; private readonly Container _container;
private readonly ObservableCollection<Exception> _exceptions = new(); private readonly ObservableCollection<Exception> _exceptions = new();
@@ -22,19 +23,22 @@ public class SearchTask : ISearchTask
public SearchTask( public SearchTask(
IContainer baseContainer, IContainer baseContainer,
IContentProvider contentProvider, ISearchContentProvider contentProvider,
ISearchMatcher matcher ITimelessContentProvider timelessContentProvider,
ISearchMatcher matcher,
AbsolutePath parent
) )
{ {
var randomId = $"{SearchContentProvider.ContentProviderName}/{_searchId++}_{baseContainer.Name}"; var randomId = $"{SearchContentProvider.ContentProviderName}/{_searchId++}_{baseContainer.Name}";
_baseContainer = baseContainer; _baseContainer = baseContainer;
_timelessContentProvider = timelessContentProvider;
_matcher = matcher; _matcher = matcher;
_container = new Container( _container = new Container(
baseContainer.Name, baseContainer.Name,
baseContainer.DisplayName, baseContainer.DisplayName,
new FullName(randomId), new FullName(randomId),
new NativePath(randomId), new NativePath(randomId),
null, parent,
false, false,
true, true,
null, null,
@@ -85,7 +89,11 @@ public class SearchTask : ISearchTask
foreach (var itemPath in items) foreach (var itemPath in items)
{ {
var item = await itemPath.ResolveAsync(); var item = await itemPath.ResolveAsync(
itemInitializationSettings: new ItemInitializationSettings
{
Parent = new AbsolutePath(_timelessContentProvider, _container)
});
if (await _matcher.IsItemMatchAsync(item)) if (await _matcher.IsItemMatchAsync(item))
{ {
_items.Add(itemPath); _items.Add(itemPath);

View File

@@ -10,7 +10,8 @@ public static class Startup
{ {
services.TryAddSingleton<ISearchContentProvider, SearchContentProvider>(); services.TryAddSingleton<ISearchContentProvider, SearchContentProvider>();
services.AddSingleton<IContentProvider>(sp => sp.GetRequiredService<ISearchContentProvider>()); services.AddSingleton<IContentProvider>(sp => sp.GetRequiredService<ISearchContentProvider>());
services.TryAddSingleton<ISearchManager, SearchManager>(); services.TryAddSingleton<IItemDeleter<ISearchContentProvider>, SearchItemDeleter>();
services.TryAddSingleton<IItemDeleter<SearchContentProvider>>(sp => sp.GetRequiredService<IItemDeleter<ISearchContentProvider>>());
return services; return services;
} }

View File

@@ -1,8 +1,10 @@
using System.Collections.ObjectModel;
namespace FileTime.Core.ContentAccess; namespace FileTime.Core.ContentAccess;
public interface IContentProviderRegistry public interface IContentProviderRegistry
{ {
IEnumerable<IContentProvider> ContentProviders { get; } ReadOnlyObservableCollection<IContentProvider> ContentProviders { get; }
void AddContentProvider(IContentProvider contentProvider); void AddContentProvider(IContentProvider contentProvider);
void RemoveContentProvider(IContentProvider contentProvider); void RemoveContentProvider(IContentProvider contentProvider);
} }

View File

@@ -0,0 +1,6 @@
namespace FileTime.Core.ContentAccess;
public interface IRootContentProvider : IContentProvider
{
}

View File

@@ -4,18 +4,22 @@ public record FullName(string Path)
{ {
public FullName? GetParent() public FullName? GetParent()
{ {
if (Path.Length == 0) return null;
var pathParts = Path.TrimEnd(Constants.SeparatorChar).Split(Constants.SeparatorChar); var pathParts = Path.TrimEnd(Constants.SeparatorChar).Split(Constants.SeparatorChar);
return pathParts.Length switch return pathParts.Length switch
{ {
> 1 => CreateSafe(string.Join(Constants.SeparatorChar, pathParts.SkipLast(1))), > 1 => CreateSafe(string.Join(Constants.SeparatorChar, pathParts.SkipLast(1))),
_ => null _ => CreateSafe("")
}; };
} }
public static FullName? CreateSafe(string? path) public static FullName? CreateSafe(string? path)
{ {
if (string.IsNullOrWhiteSpace(path)) if (path is null)
return null; return null;
if (string.IsNullOrWhiteSpace(path))
return new FullName("");
return new(path); return new(path);
} }

View File

@@ -1,11 +1,7 @@
namespace FileTime.Core.Models; namespace FileTime.Core.Models;
public readonly struct ItemInitializationSettings public sealed class ItemInitializationSettings
{ {
public readonly bool SkipChildInitialization; public bool SkipChildInitialization { get; init; }
public AbsolutePath? Parent { get; init; }
public ItemInitializationSettings(bool skipChildInitialization)
{
SkipChildInitialization = skipChildInitialization;
}
} }

View File

@@ -52,7 +52,10 @@ public class DeleteCommand : CommandBase, IExecutableCommand
new DeleteStrategy() new DeleteStrategy()
); );
var parents = ItemsToDelete.Select(i => i.GetParent()).OfType<FullName>().Distinct(); var parents = ItemsToDelete
.Select(i => i.GetParent())
.OfType<FullName>()
.Distinct();
foreach (var parent in parents) foreach (var parent in parents)
{ {
await _commandSchedulerNotifier.RefreshContainer(parent); await _commandSchedulerNotifier.RefreshContainer(parent);
@@ -68,17 +71,25 @@ public class DeleteCommand : CommandBase, IExecutableCommand
foreach (var itemToDeleteName in itemsToDelete) foreach (var itemToDeleteName in itemsToDelete)
{ {
var itemToDelete = await _timelessContentProvider.GetItemByFullNameAsync(itemToDeleteName, currentTime); var itemToDelete = await _timelessContentProvider.GetItemByFullNameAsync(itemToDeleteName, currentTime);
IItemDeleter itemDeleter; IItemDeleter? itemDeleter = null;
if (itemDeleters.ContainsKey(itemToDelete.Provider.Name)) if (itemDeleters.TryGetValue(itemToDelete.Provider.Name, out var deleter))
{ {
itemDeleter = itemDeleters[itemToDelete.Provider.Name]; itemDeleter = deleter;
} }
else else
{
try
{ {
itemDeleter = _contentAccessorFactory.GetItemDeleter(itemToDelete.Provider); itemDeleter = _contentAccessorFactory.GetItemDeleter(itemToDelete.Provider);
itemDeleters.Add(itemToDelete.Provider.Name, itemDeleter); itemDeleters.Add(itemToDelete.Provider.Name, itemDeleter);
} }
catch
{
}
}
if (itemDeleter is null) continue;
if (itemToDelete is IContainer container) if (itemToDelete is IContainer container)
{ {
@@ -95,7 +106,7 @@ public class DeleteCommand : CommandBase, IExecutableCommand
} }
} }
await itemDeleter.DeleteAsync(itemToDelete.Provider, itemToDelete.FullName!); await deleteStrategy.DeleteItem(itemToDelete, itemDeleter);
} }
} }
} }

View File

@@ -6,7 +6,5 @@ namespace FileTime.Core.Command.Delete;
public class DeleteStrategy : IDeleteStrategy public class DeleteStrategy : IDeleteStrategy
{ {
public async Task DeleteItem(IItem item, IItemDeleter deleter) public async Task DeleteItem(IItem item, IItemDeleter deleter)
{ => await deleter.DeleteAsync(item.Provider, item.FullName!);
await deleter.DeleteAsync(item.Provider, item.FullName!);
}
} }

View File

@@ -32,7 +32,7 @@ public abstract class ContentProviderBase : IContentProvider
public IContentProvider Provider => this; public IContentProvider Provider => this;
public AbsolutePath? Parent => null; public AbsolutePath? Parent { get; }
public DateTime? CreatedAt => null; public DateTime? CreatedAt => null;
@@ -46,14 +46,15 @@ public abstract class ContentProviderBase : IContentProvider
public bool AllowRecursiveDeletion => false; public bool AllowRecursiveDeletion => false;
public AbsolutePathType Type => AbsolutePathType.Container; public AbsolutePathType Type => AbsolutePathType.Container;
public PointInTime PointInTime { get; } = PointInTime.Eternal; public PointInTime PointInTime => PointInTime.Eternal;
public ObservableCollection<Exception> Exceptions { get; } = new(); public ObservableCollection<Exception> Exceptions { get; } = new();
ReadOnlyExtensionCollection IItem.Extensions => _extensions; ReadOnlyExtensionCollection IItem.Extensions => _extensions;
protected ContentProviderBase(string name) protected ContentProviderBase(string name, ITimelessContentProvider timelessContentProvider)
{ {
Parent = new AbsolutePath(timelessContentProvider, PointInTime.Eternal, new FullName(""), AbsolutePathType.Container);
DisplayName = Name = name; DisplayName = Name = name;
FullName = FullName.CreateSafe(name); FullName = FullName.CreateSafe(name);
Extensions = new ExtensionCollection(); Extensions = new ExtensionCollection();

View File

@@ -1,21 +1,64 @@
using System.Collections.ObjectModel;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace FileTime.Core.ContentAccess; namespace FileTime.Core.ContentAccess;
public class ContentProviderRegistry : IContentProviderRegistry public class ContentProviderRegistry : IContentProviderRegistry
{ {
private readonly object _lock = new();
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly Lazy<IList<IContentProvider>> _defaultContentProviders; private readonly ObservableCollection<IContentProvider> _contentProviders = new();
private readonly List<IContentProvider> _additionalContentProviders = new(); private readonly ReadOnlyObservableCollection<IContentProvider> _contentProvidersReadOnly;
private bool _initialized;
public ContentProviderRegistry(IServiceProvider serviceProvider) public ContentProviderRegistry(IServiceProvider serviceProvider)
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_defaultContentProviders = new Lazy<IList<IContentProvider>>(() => serviceProvider.GetServices<IContentProvider>().ToList()); _contentProvidersReadOnly = new ReadOnlyObservableCollection<IContentProvider>(_contentProviders);
} }
public IEnumerable<IContentProvider> ContentProviders => _defaultContentProviders.Value.Concat(_additionalContentProviders); public ReadOnlyObservableCollection<IContentProvider> ContentProviders
{
get
{
InitializeContentProviderListIfNeeded();
return _contentProvidersReadOnly;
}
}
public void AddContentProvider(IContentProvider contentProvider) => _additionalContentProviders.Add(contentProvider); private void InitializeContentProviderListIfNeeded()
public void RemoveContentProvider(IContentProvider contentProvider) => _additionalContentProviders.Remove(contentProvider); {
lock (_lock)
{
if (!_initialized)
{
foreach (var contentProvider in _serviceProvider.GetServices<IContentProvider>())
{
_contentProviders.Add(contentProvider);
}
_initialized = true;
}
}
}
public void AddContentProvider(IContentProvider contentProvider)
{
InitializeContentProviderListIfNeeded();
lock (_lock)
{
_contentProviders.Add(contentProvider);
}
}
public void RemoveContentProvider(IContentProvider contentProvider)
{
InitializeContentProviderListIfNeeded();
lock (_lock)
{
_contentProviders.Remove(contentProvider);
}
}
} }

View File

@@ -0,0 +1,87 @@
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using FileTime.Core.Enums;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
using ObservableComputations;
namespace FileTime.Core.ContentAccess;
public class RootContentProvider : IRootContentProvider
{
private readonly ITimelessContentProvider _timelessContentProvider;
public string Name => "root";
public string DisplayName => "Root";
public FullName? FullName => null;
public NativePath? NativePath => null;
public AbsolutePath? Parent => null;
public bool IsHidden => false;
public bool IsExists => true;
public DateTime? CreatedAt => null;
public SupportsDelete CanDelete => SupportsDelete.False;
public bool CanRename => false;
public IContentProvider Provider => this;
public string? Attributes => null;
public AbsolutePathType Type => AbsolutePathType.Container;
public PointInTime PointInTime => PointInTime.Eternal;
public ObservableCollection<Exception> Exceptions { get; } = new();
public ReadOnlyExtensionCollection Extensions { get; } = new(new ExtensionCollection());
public ObservableCollection<AbsolutePath> Items { get; }
public IObservable<bool> IsLoading => Observable.Return(false);
public bool? IsLoaded => true;
public Task WaitForLoaded(CancellationToken token = default) => Task.CompletedTask;
public bool AllowRecursiveDeletion => false;
public Task OnEnter() => Task.CompletedTask;
public bool SupportsContentStreams => false;
public RootContentProvider(
IContentProviderRegistry contentProviderRegistry,
ITimelessContentProvider timelessContentProvider)
{
_timelessContentProvider = timelessContentProvider;
Items = contentProviderRegistry
.ContentProviders
.Selecting<IContentProvider, AbsolutePath>(c =>
new AbsolutePath(timelessContentProvider, c)
);
}
public async Task<IItem> GetItemByFullNameAsync(
FullName fullName,
PointInTime pointInTime,
bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default)
=> await _timelessContentProvider.GetItemByFullNameAsync(
fullName,
pointInTime,
forceResolve,
forceResolvePathType,
itemInitializationSettings);
public async Task<IItem> GetItemByNativePathAsync(
NativePath nativePath,
PointInTime pointInTime,
bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default)
=> await _timelessContentProvider.GetItemByNativePathAsync(
nativePath,
pointInTime
) ?? throw new FileNotFoundException();
public NativePath GetNativePath(FullName fullName) => throw new NotImplementedException();
public FullName GetFullName(NativePath nativePath) => throw new NotImplementedException();
public Task<byte[]?> GetContentAsync(
IElement element,
int? maxLength = null,
CancellationToken cancellationToken = default) => throw new NotImplementedException();
public bool CanHandlePath(NativePath path) => throw new NotImplementedException();
public bool CanHandlePath(FullName path) => throw new NotImplementedException();
}

View File

@@ -140,7 +140,7 @@ public class Tab : ITab
{ {
var itemNameToSelect = LastDeepestSelectedPath.Path var itemNameToSelect = LastDeepestSelectedPath.Path
.Split(Constants.SeparatorChar) .Split(Constants.SeparatorChar)
.Skip(parentPath.Split(Constants.SeparatorChar).Length) .Skip(parentPath == "" ? 0 : parentPath.Split(Constants.SeparatorChar).Length)
.FirstOrDefault(); .FirstOrDefault();
var itemToSelect = items.FirstOrDefault(i => i.FullName?.GetName() == itemNameToSelect); var itemToSelect = items.FirstOrDefault(i => i.FullName?.GetName() == itemNameToSelect);

View File

@@ -2,18 +2,24 @@ using System.Reactive.Subjects;
using FileTime.Core.ContentAccess; using FileTime.Core.ContentAccess;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
using Microsoft.Extensions.DependencyInjection;
namespace FileTime.Core.Timeline; namespace FileTime.Core.Timeline;
public class TimelessContentProvider : ITimelessContentProvider public class TimelessContentProvider : ITimelessContentProvider
{ {
private readonly IContentProviderRegistry _contentProviderRegistry; private readonly IContentProviderRegistry _contentProviderRegistry;
private readonly Lazy<IRootContentProvider> _rootContentProvider;
public BehaviorSubject<PointInTime> CurrentPointInTime { get; } = new(PointInTime.Present); public BehaviorSubject<PointInTime> CurrentPointInTime { get; } = new(PointInTime.Present);
public TimelessContentProvider(IContentProviderRegistry contentProviderRegistry) public TimelessContentProvider(
IContentProviderRegistry contentProviderRegistry,
IServiceProvider serviceProvider
)
{ {
_contentProviderRegistry = contentProviderRegistry; _contentProviderRegistry = contentProviderRegistry;
_rootContentProvider = new Lazy<IRootContentProvider>(serviceProvider.GetRequiredService<IRootContentProvider>);
} }
public async Task<IItem> GetItemByFullNameAsync(FullName fullName, PointInTime? pointInTime, public async Task<IItem> GetItemByFullNameAsync(FullName fullName, PointInTime? pointInTime,
@@ -23,6 +29,8 @@ public class TimelessContentProvider : ITimelessContentProvider
{ {
//TODO time modifications //TODO time modifications
var contentProviderName = fullName.Path.Split(Constants.SeparatorChar).FirstOrDefault(); var contentProviderName = fullName.Path.Split(Constants.SeparatorChar).FirstOrDefault();
if (contentProviderName == "") return _rootContentProvider.Value;
var contentProvider = _contentProviderRegistry.ContentProviders.FirstOrDefault(p => p.Name == contentProviderName); var contentProvider = _contentProviderRegistry.ContentProviders.FirstOrDefault(p => p.Name == contentProviderName);
if (contentProvider is null) if (contentProvider is null)
@@ -37,7 +45,7 @@ public class TimelessContentProvider : ITimelessContentProvider
{ {
foreach (var contentProvider in _contentProviderRegistry.ContentProviders) foreach (var contentProvider in _contentProviderRegistry.ContentProviders)
{ {
if(!contentProvider.CanHandlePath(nativePath)) continue; if (!contentProvider.CanHandlePath(nativePath)) continue;
return await contentProvider.GetItemByNativePathAsync(nativePath, pointInTime ?? PointInTime.Present); return await contentProvider.GetItemByNativePathAsync(nativePath, pointInTime ?? PointInTime.Present);
} }
@@ -49,7 +57,7 @@ public class TimelessContentProvider : ITimelessContentProvider
{ {
foreach (var contentProvider in _contentProviderRegistry.ContentProviders) foreach (var contentProvider in _contentProviderRegistry.ContentProviders)
{ {
if(!contentProvider.CanHandlePath(nativePath)) continue; if (!contentProvider.CanHandlePath(nativePath)) continue;
return contentProvider.GetFullName(nativePath); return contentProvider.GetFullName(nativePath);
} }
@@ -61,7 +69,7 @@ public class TimelessContentProvider : ITimelessContentProvider
{ {
foreach (var contentProvider in _contentProviderRegistry.ContentProviders) foreach (var contentProvider in _contentProviderRegistry.ContentProviders)
{ {
if(!contentProvider.CanHandlePath(fullName)) continue; if (!contentProvider.CanHandlePath(fullName)) continue;
return contentProvider.GetNativePath(fullName); return contentProvider.GetNativePath(fullName);
} }

View File

@@ -14,7 +14,8 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITimelessContentProvider _timelessContentProvider;
private readonly bool _isCaseInsensitive; private readonly bool _isCaseInsensitive;
public LocalContentProvider(ITimelessContentProvider timelessContentProvider) : base(LocalContentProviderConstants.ContentProviderId) public LocalContentProvider(ITimelessContentProvider timelessContentProvider)
: base(LocalContentProviderConstants.ContentProviderId, timelessContentProvider)
{ {
_timelessContentProvider = timelessContentProvider; _timelessContentProvider = timelessContentProvider;
_isCaseInsensitive = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); _isCaseInsensitive = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
@@ -66,13 +67,13 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
PointInTime pointInTime, PointInTime pointInTime,
bool forceResolve = false, bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default) ItemInitializationSettings? itemInitializationSettings = null)
{ {
var path = nativePath.Path; var path = nativePath.Path;
Exception? innerException; Exception? innerException;
try try
{ {
if ((path?.Length ?? 0) == 0) if (path.Length == 0)
{ {
return Task.FromResult((IItem) this); return Task.FromResult((IItem) this);
} }
@@ -81,7 +82,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
return Task.FromResult((IItem) DirectoryToContainer( return Task.FromResult((IItem) DirectoryToContainer(
new DirectoryInfo(path!.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar), new DirectoryInfo(path!.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar),
pointInTime, pointInTime,
!itemInitializationSettings.SkipChildInitialization) itemInitializationSettings)
); );
} }
else if (File.Exists(path)) else if (File.Exists(path))
@@ -193,18 +194,21 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
} }
private Container DirectoryToContainer(DirectoryInfo directoryInfo, PointInTime pointInTime, private Container DirectoryToContainer(DirectoryInfo directoryInfo, PointInTime pointInTime,
bool initializeChildren = true) ItemInitializationSettings? initializationSettings = null)
{ {
initializationSettings ??= new();
var fullName = GetFullName(directoryInfo.FullName); var fullName = GetFullName(directoryInfo.FullName);
var parentFullName = fullName.GetParent(); var parentFullName = fullName.GetParent();
var parent = var parent =
parentFullName is null initializationSettings.Parent
?? (parentFullName is null
? null ? null
: new AbsolutePath( : new AbsolutePath(
_timelessContentProvider, _timelessContentProvider,
pointInTime, pointInTime,
parentFullName, parentFullName,
AbsolutePathType.Container); AbsolutePathType.Container));
var exceptions = new ObservableCollection<Exception>(); var exceptions = new ObservableCollection<Exception>();
var children = new ObservableCollection<AbsolutePath>(); var children = new ObservableCollection<AbsolutePath>();
@@ -229,7 +233,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
children children
); );
if (initializeChildren) if (!initializationSettings.SkipChildInitialization)
{ {
Task.Run(async () => await LoadChildren(container, directoryInfo, children, pointInTime, exceptions)); Task.Run(async () => await LoadChildren(container, directoryInfo, children, pointInTime, exceptions));
} }

View File

@@ -9,7 +9,7 @@ namespace FileTime.Providers.LocalAdmin;
//TODO: this should be a RemoteContentProvider if there will be one //TODO: this should be a RemoteContentProvider if there will be one
public class AdminContentProvider : RemoteContentProvider, IAdminContentProvider public class AdminContentProvider : RemoteContentProvider, IAdminContentProvider
{ {
public AdminContentProvider() : base("local", "localAdmin") public AdminContentProvider(ITimelessContentProvider timelessContentProvider) : base(timelessContentProvider, "local", "localAdmin")
{ {
} }

View File

@@ -7,7 +7,11 @@ namespace FileTime.Providers.Remote;
public class RemoteContentProvider : ContentProviderBase, IRemoteContentProvider public class RemoteContentProvider : ContentProviderBase, IRemoteContentProvider
{ {
public RemoteContentProvider(string remoteName, string name = "remote") : base(name) public RemoteContentProvider(
ITimelessContentProvider timelessContentProvider,
string remoteName,
string name = "remote")
: base(name, timelessContentProvider)
{ {
} }