Top navigation, search delete
This commit is contained in:
@@ -15,32 +15,17 @@ public class TabPersistenceService : ITabPersistenceService
|
||||
private readonly IAppState _appState;
|
||||
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
|
||||
{
|
||||
public List<TabState>? Tabs { get; set; }
|
||||
public int? ActiveTabNumber { get; set; }
|
||||
}
|
||||
private record PersistenceRoot(TabStates? TabStates);
|
||||
|
||||
private class TabState
|
||||
{
|
||||
public string? Path { get; set; }
|
||||
public int Number { get; set; }
|
||||
private record TabStates(List<TabState>? Tabs, int? ActiveTabNumber);
|
||||
|
||||
public TabState()
|
||||
{
|
||||
}
|
||||
|
||||
public TabState(FullName path, int number)
|
||||
{
|
||||
Path = path.Path;
|
||||
Number = number;
|
||||
}
|
||||
}
|
||||
private record TabState(string? Path, int Number);
|
||||
|
||||
private readonly string _settingsPath;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
@@ -149,7 +134,7 @@ public class TabPersistenceService : ITabPersistenceService
|
||||
}
|
||||
catch
|
||||
{
|
||||
path = path.GetParent();
|
||||
path = path?.GetParent();
|
||||
if (path == null)
|
||||
{
|
||||
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 (_contentProvidersNotToRestore.Contains(container.Provider.Name)) continue;
|
||||
|
||||
var tabToLoad = await _serviceProvider.GetAsyncInitableResolver(container)
|
||||
.GetRequiredServiceAsync<ITab>();
|
||||
var tabViewModel = _serviceProvider.GetInitableResolver(tabToLoad, tab.Number)
|
||||
@@ -181,7 +168,15 @@ public class TabPersistenceService : ITabPersistenceService
|
||||
|
||||
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);
|
||||
|
||||
return true;
|
||||
@@ -189,10 +184,8 @@ public class TabPersistenceService : ITabPersistenceService
|
||||
|
||||
public void SaveStates(CancellationToken token = default)
|
||||
{
|
||||
var state = new PersistenceRoot
|
||||
{
|
||||
TabStates = SerializeTabStates()
|
||||
};
|
||||
var state = new PersistenceRoot(SerializeTabStates());
|
||||
|
||||
var settingsDirectory = new DirectoryInfo(string.Join(Path.DirectorySeparatorChar,
|
||||
_settingsPath.Split(Path.DirectorySeparatorChar)[0..^1]));
|
||||
if (!settingsDirectory.Exists) settingsDirectory.Create();
|
||||
@@ -207,14 +200,13 @@ public class TabPersistenceService : ITabPersistenceService
|
||||
{
|
||||
var currentLocation = tab.CurrentLocation.Value;
|
||||
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
|
||||
{
|
||||
Tabs = tabStates,
|
||||
ActiveTabNumber = _appState.CurrentSelectedTab?.TabNumber
|
||||
};
|
||||
return new TabStates(
|
||||
tabStates,
|
||||
_appState.CurrentSelectedTab?.TabNumber
|
||||
);
|
||||
}
|
||||
|
||||
public async Task InitAsync()
|
||||
|
||||
@@ -10,7 +10,6 @@ using FileTime.App.Core.UserCommand;
|
||||
using FileTime.App.Core.ViewModels;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Command.CreateContainer;
|
||||
using FileTime.Core.Command.CreateElement;
|
||||
using FileTime.Core.Command.Move;
|
||||
using FileTime.Core.Extensions;
|
||||
using FileTime.Core.Interactions;
|
||||
|
||||
@@ -15,7 +15,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
|
||||
{
|
||||
private readonly ISystemClipboardService _systemClipboardService;
|
||||
private readonly IUserCommunicationService _userCommunicationService;
|
||||
private readonly ISearchManager _searchManager;
|
||||
private readonly ISearchContentProvider _searchContentProvider;
|
||||
private readonly IItemNameConverterService _itemNameConverterService;
|
||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||
private readonly IUserCommandHandlerService _userCommandHandlerService;
|
||||
@@ -28,7 +28,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
|
||||
IAppState appState,
|
||||
ISystemClipboardService systemClipboardService,
|
||||
IUserCommunicationService userCommunicationService,
|
||||
ISearchManager searchManager,
|
||||
ISearchContentProvider searchContentProvider,
|
||||
IItemNameConverterService itemNameConverterService,
|
||||
ITimelessContentProvider timelessContentProvider,
|
||||
IUserCommandHandlerService userCommandHandlerService,
|
||||
@@ -36,7 +36,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
|
||||
{
|
||||
_systemClipboardService = systemClipboardService;
|
||||
_userCommunicationService = userCommunicationService;
|
||||
_searchManager = searchManager;
|
||||
_searchContentProvider = searchContentProvider;
|
||||
_itemNameConverterService = itemNameConverterService;
|
||||
_timelessContentProvider = timelessContentProvider;
|
||||
_userCommandHandlerService = userCommandHandlerService;
|
||||
@@ -113,7 +113,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
|
||||
_ => 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));
|
||||
await _userCommandHandlerService.HandleCommandAsync(openContainerCommand);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public abstract partial class ItemViewModel : IItemViewModel
|
||||
var displayName = itemViewModelType switch
|
||||
{
|
||||
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;
|
||||
@@ -103,6 +103,6 @@ public abstract partial class ItemViewModel : IItemViewModel
|
||||
var deepestPath = _parentTab.Tab.LastDeepestSelectedPath;
|
||||
var commonPath = FullName.CreateSafe(PathHelper.GetCommonPath(ownFullName.Path, deepestPath.Path));
|
||||
|
||||
return commonPath.Path == ownFullName.Path;
|
||||
return commonPath is not null && commonPath.Path == ownFullName.Path;
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,7 @@ public partial class TabViewModel : ITabViewModel
|
||||
|
||||
private static async Task<IItem> MapItemAsync(AbsolutePath item)
|
||||
=> await item.ResolveAsync(forceResolve: true,
|
||||
itemInitializationSettings: new ItemInitializationSettings(true));
|
||||
itemInitializationSettings: new ItemInitializationSettings {SkipChildInitialization = true});
|
||||
|
||||
private IItemViewModel MapItemToViewModel(IItem item, ItemViewModelType type)
|
||||
{
|
||||
|
||||
@@ -31,6 +31,7 @@ public static class DependencyInjection
|
||||
serviceCollection.TryAddSingleton<ICommandRunner, CommandRunner>();
|
||||
serviceCollection.TryAddSingleton<IContentAccessorFactory, ContentAccessorFactory>();
|
||||
serviceCollection.TryAddSingleton<IContentProviderRegistry, ContentProviderRegistry>();
|
||||
serviceCollection.TryAddSingleton<IRootContentProvider, RootContentProvider>();
|
||||
//TODO: check local/remote context
|
||||
serviceCollection.TryAddSingleton<ILocalCommandExecutor, LocalCommandExecutor>();
|
||||
serviceCollection.TryAddSingleton<ICommandSchedulerNotifier, LocalCommandSchedulerNotifier>();
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using FileTime.Core.ContentAccess;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.App.Search;
|
||||
|
||||
public interface ISearchContentProvider : IContentProvider
|
||||
{
|
||||
|
||||
Task<ISearchTask> StartSearchAsync(ISearchMatcher matcher, IContainer searchIn);
|
||||
void RemoveSearch(FullName searchFullName);
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -7,12 +7,14 @@ namespace FileTime.App.Search;
|
||||
|
||||
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 SearchContentProvider(ISearchManager searchManager) : base(ContentProviderName)
|
||||
public SearchContentProvider(ITimelessContentProvider timelessContentProvider)
|
||||
: base(ContentProviderName, timelessContentProvider)
|
||||
{
|
||||
_searchManager = searchManager;
|
||||
_timelessContentProvider = timelessContentProvider;
|
||||
}
|
||||
|
||||
public override Task<IItem> GetItemByNativePathAsync(
|
||||
@@ -21,9 +23,12 @@ public class SearchContentProvider : ContentProviderBase, ISearchContentProvider
|
||||
bool forceResolve = false,
|
||||
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
|
||||
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);
|
||||
}
|
||||
|
||||
public override NativePath GetNativePath(FullName fullName) => new(fullName.Path);
|
||||
public override FullName GetFullName(NativePath nativePath) => new(nativePath.Path);
|
||||
@@ -36,4 +41,34 @@ public class SearchContentProvider : ContentProviderBase, ISearchContentProvider
|
||||
=> Task.FromResult(null as byte[]);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/AppCommon/FileTime.App.Search/SearchItemDeleter.cs
Normal file
13
src/AppCommon/FileTime.App.Search/SearchItemDeleter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ namespace FileTime.App.Search;
|
||||
public class SearchTask : ISearchTask
|
||||
{
|
||||
private readonly IContainer _baseContainer;
|
||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||
private readonly ISearchMatcher _matcher;
|
||||
private readonly Container _container;
|
||||
private readonly ObservableCollection<Exception> _exceptions = new();
|
||||
@@ -22,19 +23,22 @@ public class SearchTask : ISearchTask
|
||||
|
||||
public SearchTask(
|
||||
IContainer baseContainer,
|
||||
IContentProvider contentProvider,
|
||||
ISearchMatcher matcher
|
||||
ISearchContentProvider contentProvider,
|
||||
ITimelessContentProvider timelessContentProvider,
|
||||
ISearchMatcher matcher,
|
||||
AbsolutePath parent
|
||||
)
|
||||
{
|
||||
var randomId = $"{SearchContentProvider.ContentProviderName}/{_searchId++}_{baseContainer.Name}";
|
||||
_baseContainer = baseContainer;
|
||||
_timelessContentProvider = timelessContentProvider;
|
||||
_matcher = matcher;
|
||||
_container = new Container(
|
||||
baseContainer.Name,
|
||||
baseContainer.DisplayName,
|
||||
new FullName(randomId),
|
||||
new NativePath(randomId),
|
||||
null,
|
||||
parent,
|
||||
false,
|
||||
true,
|
||||
null,
|
||||
@@ -85,7 +89,11 @@ public class SearchTask : ISearchTask
|
||||
|
||||
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))
|
||||
{
|
||||
_items.Add(itemPath);
|
||||
|
||||
@@ -10,7 +10,8 @@ public static class Startup
|
||||
{
|
||||
services.TryAddSingleton<ISearchContentProvider, SearchContentProvider>();
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace FileTime.Core.ContentAccess;
|
||||
|
||||
public interface IContentProviderRegistry
|
||||
{
|
||||
IEnumerable<IContentProvider> ContentProviders { get; }
|
||||
ReadOnlyObservableCollection<IContentProvider> ContentProviders { get; }
|
||||
void AddContentProvider(IContentProvider contentProvider);
|
||||
void RemoveContentProvider(IContentProvider contentProvider);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace FileTime.Core.ContentAccess;
|
||||
|
||||
public interface IRootContentProvider : IContentProvider
|
||||
{
|
||||
|
||||
}
|
||||
@@ -4,18 +4,22 @@ public record FullName(string Path)
|
||||
{
|
||||
public FullName? GetParent()
|
||||
{
|
||||
if (Path.Length == 0) return null;
|
||||
var pathParts = Path.TrimEnd(Constants.SeparatorChar).Split(Constants.SeparatorChar);
|
||||
return pathParts.Length switch
|
||||
{
|
||||
> 1 => CreateSafe(string.Join(Constants.SeparatorChar, pathParts.SkipLast(1))),
|
||||
_ => null
|
||||
_ => CreateSafe("")
|
||||
};
|
||||
}
|
||||
|
||||
public static FullName? CreateSafe(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
if (path is null)
|
||||
return null;
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return new FullName("");
|
||||
|
||||
return new(path);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
namespace FileTime.Core.Models;
|
||||
|
||||
public readonly struct ItemInitializationSettings
|
||||
public sealed class ItemInitializationSettings
|
||||
{
|
||||
public readonly bool SkipChildInitialization;
|
||||
|
||||
public ItemInitializationSettings(bool skipChildInitialization)
|
||||
{
|
||||
SkipChildInitialization = skipChildInitialization;
|
||||
}
|
||||
public bool SkipChildInitialization { get; init; }
|
||||
public AbsolutePath? Parent { get; init; }
|
||||
}
|
||||
@@ -52,7 +52,10 @@ public class DeleteCommand : CommandBase, IExecutableCommand
|
||||
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)
|
||||
{
|
||||
await _commandSchedulerNotifier.RefreshContainer(parent);
|
||||
@@ -68,17 +71,25 @@ public class DeleteCommand : CommandBase, IExecutableCommand
|
||||
foreach (var itemToDeleteName in itemsToDelete)
|
||||
{
|
||||
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
|
||||
{
|
||||
try
|
||||
{
|
||||
itemDeleter = _contentAccessorFactory.GetItemDeleter(itemToDelete.Provider);
|
||||
itemDeleters.Add(itemToDelete.Provider.Name, itemDeleter);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (itemDeleter is null) continue;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,5 @@ namespace FileTime.Core.Command.Delete;
|
||||
public class DeleteStrategy : IDeleteStrategy
|
||||
{
|
||||
public async Task DeleteItem(IItem item, IItemDeleter deleter)
|
||||
{
|
||||
await deleter.DeleteAsync(item.Provider, item.FullName!);
|
||||
}
|
||||
=> await deleter.DeleteAsync(item.Provider, item.FullName!);
|
||||
}
|
||||
@@ -32,7 +32,7 @@ public abstract class ContentProviderBase : IContentProvider
|
||||
|
||||
public IContentProvider Provider => this;
|
||||
|
||||
public AbsolutePath? Parent => null;
|
||||
public AbsolutePath? Parent { get; }
|
||||
|
||||
public DateTime? CreatedAt => null;
|
||||
|
||||
@@ -46,14 +46,15 @@ public abstract class ContentProviderBase : IContentProvider
|
||||
public bool AllowRecursiveDeletion => false;
|
||||
|
||||
public AbsolutePathType Type => AbsolutePathType.Container;
|
||||
public PointInTime PointInTime { get; } = PointInTime.Eternal;
|
||||
public PointInTime PointInTime => PointInTime.Eternal;
|
||||
|
||||
public ObservableCollection<Exception> Exceptions { get; } = new();
|
||||
|
||||
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;
|
||||
FullName = FullName.CreateSafe(name);
|
||||
Extensions = new ExtensionCollection();
|
||||
|
||||
@@ -1,21 +1,64 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FileTime.Core.ContentAccess;
|
||||
|
||||
public class ContentProviderRegistry : IContentProviderRegistry
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly Lazy<IList<IContentProvider>> _defaultContentProviders;
|
||||
private readonly List<IContentProvider> _additionalContentProviders = new();
|
||||
private readonly ObservableCollection<IContentProvider> _contentProviders = new();
|
||||
private readonly ReadOnlyObservableCollection<IContentProvider> _contentProvidersReadOnly;
|
||||
private bool _initialized;
|
||||
|
||||
public ContentProviderRegistry(IServiceProvider 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);
|
||||
public void RemoveContentProvider(IContentProvider contentProvider) => _additionalContentProviders.Remove(contentProvider);
|
||||
private void InitializeContentProviderListIfNeeded()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/Core/FileTime.Core.ContentAccess/RootContentProvider.cs
Normal file
87
src/Core/FileTime.Core.ContentAccess/RootContentProvider.cs
Normal 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();
|
||||
}
|
||||
@@ -140,7 +140,7 @@ public class Tab : ITab
|
||||
{
|
||||
var itemNameToSelect = LastDeepestSelectedPath.Path
|
||||
.Split(Constants.SeparatorChar)
|
||||
.Skip(parentPath.Split(Constants.SeparatorChar).Length)
|
||||
.Skip(parentPath == "" ? 0 : parentPath.Split(Constants.SeparatorChar).Length)
|
||||
.FirstOrDefault();
|
||||
|
||||
var itemToSelect = items.FirstOrDefault(i => i.FullName?.GetName() == itemNameToSelect);
|
||||
|
||||
@@ -2,18 +2,24 @@ using System.Reactive.Subjects;
|
||||
using FileTime.Core.ContentAccess;
|
||||
using FileTime.Core.Enums;
|
||||
using FileTime.Core.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FileTime.Core.Timeline;
|
||||
|
||||
public class TimelessContentProvider : ITimelessContentProvider
|
||||
{
|
||||
private readonly IContentProviderRegistry _contentProviderRegistry;
|
||||
private readonly Lazy<IRootContentProvider> _rootContentProvider;
|
||||
|
||||
public BehaviorSubject<PointInTime> CurrentPointInTime { get; } = new(PointInTime.Present);
|
||||
|
||||
public TimelessContentProvider(IContentProviderRegistry contentProviderRegistry)
|
||||
public TimelessContentProvider(
|
||||
IContentProviderRegistry contentProviderRegistry,
|
||||
IServiceProvider serviceProvider
|
||||
)
|
||||
{
|
||||
_contentProviderRegistry = contentProviderRegistry;
|
||||
_rootContentProvider = new Lazy<IRootContentProvider>(serviceProvider.GetRequiredService<IRootContentProvider>);
|
||||
}
|
||||
|
||||
public async Task<IItem> GetItemByFullNameAsync(FullName fullName, PointInTime? pointInTime,
|
||||
@@ -23,6 +29,8 @@ public class TimelessContentProvider : ITimelessContentProvider
|
||||
{
|
||||
//TODO time modifications
|
||||
var contentProviderName = fullName.Path.Split(Constants.SeparatorChar).FirstOrDefault();
|
||||
if (contentProviderName == "") return _rootContentProvider.Value;
|
||||
|
||||
var contentProvider = _contentProviderRegistry.ContentProviders.FirstOrDefault(p => p.Name == contentProviderName);
|
||||
|
||||
if (contentProvider is null)
|
||||
@@ -37,7 +45,7 @@ public class TimelessContentProvider : ITimelessContentProvider
|
||||
{
|
||||
foreach (var contentProvider in _contentProviderRegistry.ContentProviders)
|
||||
{
|
||||
if(!contentProvider.CanHandlePath(nativePath)) continue;
|
||||
if (!contentProvider.CanHandlePath(nativePath)) continue;
|
||||
|
||||
return await contentProvider.GetItemByNativePathAsync(nativePath, pointInTime ?? PointInTime.Present);
|
||||
}
|
||||
@@ -49,7 +57,7 @@ public class TimelessContentProvider : ITimelessContentProvider
|
||||
{
|
||||
foreach (var contentProvider in _contentProviderRegistry.ContentProviders)
|
||||
{
|
||||
if(!contentProvider.CanHandlePath(nativePath)) continue;
|
||||
if (!contentProvider.CanHandlePath(nativePath)) continue;
|
||||
|
||||
return contentProvider.GetFullName(nativePath);
|
||||
}
|
||||
@@ -61,7 +69,7 @@ public class TimelessContentProvider : ITimelessContentProvider
|
||||
{
|
||||
foreach (var contentProvider in _contentProviderRegistry.ContentProviders)
|
||||
{
|
||||
if(!contentProvider.CanHandlePath(fullName)) continue;
|
||||
if (!contentProvider.CanHandlePath(fullName)) continue;
|
||||
|
||||
return contentProvider.GetNativePath(fullName);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||
private readonly bool _isCaseInsensitive;
|
||||
|
||||
public LocalContentProvider(ITimelessContentProvider timelessContentProvider) : base(LocalContentProviderConstants.ContentProviderId)
|
||||
public LocalContentProvider(ITimelessContentProvider timelessContentProvider)
|
||||
: base(LocalContentProviderConstants.ContentProviderId, timelessContentProvider)
|
||||
{
|
||||
_timelessContentProvider = timelessContentProvider;
|
||||
_isCaseInsensitive = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
@@ -66,13 +67,13 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
||||
PointInTime pointInTime,
|
||||
bool forceResolve = false,
|
||||
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
|
||||
ItemInitializationSettings itemInitializationSettings = default)
|
||||
ItemInitializationSettings? itemInitializationSettings = null)
|
||||
{
|
||||
var path = nativePath.Path;
|
||||
Exception? innerException;
|
||||
try
|
||||
{
|
||||
if ((path?.Length ?? 0) == 0)
|
||||
if (path.Length == 0)
|
||||
{
|
||||
return Task.FromResult((IItem) this);
|
||||
}
|
||||
@@ -81,7 +82,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
||||
return Task.FromResult((IItem) DirectoryToContainer(
|
||||
new DirectoryInfo(path!.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar),
|
||||
pointInTime,
|
||||
!itemInitializationSettings.SkipChildInitialization)
|
||||
itemInitializationSettings)
|
||||
);
|
||||
}
|
||||
else if (File.Exists(path))
|
||||
@@ -193,18 +194,21 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
||||
}
|
||||
|
||||
private Container DirectoryToContainer(DirectoryInfo directoryInfo, PointInTime pointInTime,
|
||||
bool initializeChildren = true)
|
||||
ItemInitializationSettings? initializationSettings = null)
|
||||
{
|
||||
initializationSettings ??= new();
|
||||
|
||||
var fullName = GetFullName(directoryInfo.FullName);
|
||||
var parentFullName = fullName.GetParent();
|
||||
var parent =
|
||||
parentFullName is null
|
||||
initializationSettings.Parent
|
||||
?? (parentFullName is null
|
||||
? null
|
||||
: new AbsolutePath(
|
||||
_timelessContentProvider,
|
||||
pointInTime,
|
||||
parentFullName,
|
||||
AbsolutePathType.Container);
|
||||
AbsolutePathType.Container));
|
||||
var exceptions = new ObservableCollection<Exception>();
|
||||
|
||||
var children = new ObservableCollection<AbsolutePath>();
|
||||
@@ -229,7 +233,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
||||
children
|
||||
);
|
||||
|
||||
if (initializeChildren)
|
||||
if (!initializationSettings.SkipChildInitialization)
|
||||
{
|
||||
Task.Run(async () => await LoadChildren(container, directoryInfo, children, pointInTime, exceptions));
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace FileTime.Providers.LocalAdmin;
|
||||
//TODO: this should be a RemoteContentProvider if there will be one
|
||||
public class AdminContentProvider : RemoteContentProvider, IAdminContentProvider
|
||||
{
|
||||
public AdminContentProvider() : base("local", "localAdmin")
|
||||
public AdminContentProvider(ITimelessContentProvider timelessContentProvider) : base(timelessContentProvider, "local", "localAdmin")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,11 @@ namespace FileTime.Providers.Remote;
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user