Search WIP
This commit is contained in:
@@ -4,6 +4,7 @@ using FileTime.App.Core.ViewModels;
|
|||||||
using FileTime.App.Search;
|
using FileTime.App.Search;
|
||||||
using FileTime.Core.Interactions;
|
using FileTime.Core.Interactions;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
namespace FileTime.App.Core.Services.UserCommandHandler;
|
namespace FileTime.App.Core.Services.UserCommandHandler;
|
||||||
|
|
||||||
@@ -13,6 +14,8 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
private readonly IUserCommunicationService _userCommunicationService;
|
private readonly IUserCommunicationService _userCommunicationService;
|
||||||
private readonly ISearchManager _searchManager;
|
private readonly ISearchManager _searchManager;
|
||||||
private readonly IItemNameConverterService _itemNameConverterService;
|
private readonly IItemNameConverterService _itemNameConverterService;
|
||||||
|
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||||
|
private readonly IUserCommandHandlerService _userCommandHandlerService;
|
||||||
private IContainer? _currentLocation;
|
private IContainer? _currentLocation;
|
||||||
private IItemViewModel? _currentSelectedItem;
|
private IItemViewModel? _currentSelectedItem;
|
||||||
|
|
||||||
@@ -21,12 +24,16 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
ISystemClipboardService systemClipboardService,
|
ISystemClipboardService systemClipboardService,
|
||||||
IUserCommunicationService userCommunicationService,
|
IUserCommunicationService userCommunicationService,
|
||||||
ISearchManager searchManager,
|
ISearchManager searchManager,
|
||||||
IItemNameConverterService itemNameConverterService) : base(appState)
|
IItemNameConverterService itemNameConverterService,
|
||||||
|
ITimelessContentProvider timelessContentProvider,
|
||||||
|
IUserCommandHandlerService userCommandHandlerService) : base(appState)
|
||||||
{
|
{
|
||||||
_systemClipboardService = systemClipboardService;
|
_systemClipboardService = systemClipboardService;
|
||||||
_userCommunicationService = userCommunicationService;
|
_userCommunicationService = userCommunicationService;
|
||||||
_searchManager = searchManager;
|
_searchManager = searchManager;
|
||||||
_itemNameConverterService = itemNameConverterService;
|
_itemNameConverterService = itemNameConverterService;
|
||||||
|
_timelessContentProvider = timelessContentProvider;
|
||||||
|
_userCommandHandlerService = userCommandHandlerService;
|
||||||
SaveCurrentLocation(l => _currentLocation = l);
|
SaveCurrentLocation(l => _currentLocation = l);
|
||||||
SaveCurrentSelectedItem(i => _currentSelectedItem = i);
|
SaveCurrentSelectedItem(i => _currentSelectedItem = i);
|
||||||
|
|
||||||
@@ -40,7 +47,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
|
|
||||||
private async Task Search(SearchCommand searchCommand)
|
private async Task Search(SearchCommand searchCommand)
|
||||||
{
|
{
|
||||||
if(_currentLocation is null) return;
|
if (_currentLocation is null) return;
|
||||||
|
|
||||||
var searchQuery = searchCommand.SearchText;
|
var searchQuery = searchCommand.SearchText;
|
||||||
if (string.IsNullOrEmpty(searchQuery))
|
if (string.IsNullOrEmpty(searchQuery))
|
||||||
@@ -61,7 +68,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO proper error message
|
//TODO proper error message
|
||||||
if(string.IsNullOrWhiteSpace(searchQuery)) return;
|
if (string.IsNullOrWhiteSpace(searchQuery)) return;
|
||||||
|
|
||||||
var searchMatcher = searchCommand.SearchType switch
|
var searchMatcher = searchCommand.SearchType switch
|
||||||
{
|
{
|
||||||
@@ -70,7 +77,9 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
_ => throw new ArgumentOutOfRangeException()
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
};
|
};
|
||||||
|
|
||||||
await _searchManager.StartSearchAsync(searchMatcher, _currentLocation);
|
var searchTask = await _searchManager.StartSearchAsync(searchMatcher, _currentLocation);
|
||||||
|
var openContainerCommand = new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, searchTask.SearchContainer));
|
||||||
|
await _userCommandHandlerService.HandleCommandAsync(openContainerCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CopyNativePath()
|
private async Task CopyNativePath()
|
||||||
|
|||||||
@@ -134,7 +134,6 @@ public partial class TabViewModel : ITabViewModel
|
|||||||
.OfType<IContainerViewModel>()
|
.OfType<IContainerViewModel>()
|
||||||
.Where(c => c?.Container is not null)
|
.Where(c => c?.Container is not null)
|
||||||
.Select(c => c.Container!.Items)
|
.Select(c => c.Container!.Items)
|
||||||
.Switch()
|
|
||||||
.Select(i =>
|
.Select(i =>
|
||||||
i
|
i
|
||||||
?.TransformAsync(MapItem)
|
?.TransformAsync(MapItem)
|
||||||
@@ -165,7 +164,6 @@ public partial class TabViewModel : ITabViewModel
|
|||||||
.Select(p => Observable.FromAsync(async () => (IContainer)await p!.ResolveAsync()))
|
.Select(p => Observable.FromAsync(async () => (IContainer)await p!.ResolveAsync()))
|
||||||
.Switch()
|
.Switch()
|
||||||
.Select(p => p.Items)
|
.Select(p => p.Items)
|
||||||
.Switch()
|
|
||||||
.Select(items =>
|
.Select(items =>
|
||||||
items
|
items
|
||||||
?.TransformAsync(MapItem)
|
?.TransformAsync(MapItem)
|
||||||
|
|||||||
@@ -4,5 +4,6 @@ namespace FileTime.App.Search;
|
|||||||
|
|
||||||
public interface ISearchManager
|
public interface ISearchManager
|
||||||
{
|
{
|
||||||
Task StartSearchAsync(ISearchMatcher matcher, IContainer searchIn);
|
Task<ISearchTask> StartSearchAsync(ISearchMatcher matcher, IContainer searchIn);
|
||||||
|
IReadOnlyList<ISearchTask> SearchTasks { get; }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
|
namespace FileTime.App.Search;
|
||||||
|
|
||||||
|
public interface ISearchTask
|
||||||
|
{
|
||||||
|
IContainer SearchContainer { get; }
|
||||||
|
Task StartAsync();
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
|
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Core\FileTime.Core.ContentAccess\FileTime.Core.ContentAccess.csproj" />
|
||||||
<ProjectReference Include="..\..\Core\FileTime.Core.Models\FileTime.Core.Models.csproj" />
|
<ProjectReference Include="..\..\Core\FileTime.Core.Models\FileTime.Core.Models.csproj" />
|
||||||
<ProjectReference Include="..\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
<ProjectReference Include="..\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
||||||
<ProjectReference Include="..\FileTime.App.Search.Abstractions\FileTime.App.Search.Abstractions.csproj" />
|
<ProjectReference Include="..\FileTime.App.Search.Abstractions\FileTime.App.Search.Abstractions.csproj" />
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using DynamicData;
|
|
||||||
using FileTime.Core.ContentAccess;
|
using FileTime.Core.ContentAccess;
|
||||||
using FileTime.Core.Enums;
|
using FileTime.Core.Enums;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
@@ -6,39 +5,34 @@ using FileTime.Core.Timeline;
|
|||||||
|
|
||||||
namespace FileTime.App.Search;
|
namespace FileTime.App.Search;
|
||||||
|
|
||||||
public class SearchContentProvider : ISearchContentProvider
|
public class SearchContentProvider : ContentProviderBase, ISearchContentProvider
|
||||||
{
|
{
|
||||||
public string Name { get; }
|
private readonly ISearchManager _searchManager;
|
||||||
public string DisplayName { get; }
|
public const string ContentProviderName = "search";
|
||||||
public FullName? FullName { get; }
|
|
||||||
public NativePath? NativePath { get; }
|
|
||||||
public AbsolutePath? Parent { get; }
|
|
||||||
public bool IsHidden { get; }
|
|
||||||
public bool IsExists { get; }
|
|
||||||
public DateTime? CreatedAt { get; }
|
|
||||||
public SupportsDelete CanDelete { get; }
|
|
||||||
public bool CanRename { get; }
|
|
||||||
public IContentProvider Provider { get; }
|
|
||||||
public string? Attributes { get; }
|
|
||||||
public AbsolutePathType Type { get; }
|
|
||||||
public PointInTime PointInTime { get; }
|
|
||||||
public IObservable<IChangeSet<Exception>> Exceptions { get; }
|
|
||||||
public ReadOnlyExtensionCollection Extensions { get; }
|
|
||||||
public IObservable<IObservable<IChangeSet<AbsolutePath, string>>?> Items { get; }
|
|
||||||
public IObservable<bool> IsLoading { get; }
|
|
||||||
public bool AllowRecursiveDeletion { get; }
|
|
||||||
public Task OnEnter() => throw new NotImplementedException();
|
|
||||||
|
|
||||||
public bool SupportsContentStreams { get; }
|
public SearchContentProvider(ISearchManager searchManager) : base(ContentProviderName)
|
||||||
public Task<IItem> GetItemByFullNameAsync(FullName fullName, PointInTime pointInTime, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, ItemInitializationSettings itemInitializationSettings = default) => throw new NotImplementedException();
|
{
|
||||||
|
_searchManager = searchManager;
|
||||||
|
}
|
||||||
|
|
||||||
public Task<IItem> GetItemByNativePathAsync(NativePath nativePath, PointInTime pointInTime, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, ItemInitializationSettings itemInitializationSettings = default) => throw new NotImplementedException();
|
public override Task<IItem> GetItemByNativePathAsync(
|
||||||
|
NativePath nativePath,
|
||||||
|
PointInTime pointInTime,
|
||||||
|
bool forceResolve = false,
|
||||||
|
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
|
||||||
|
ItemInitializationSettings itemInitializationSettings = default
|
||||||
|
) =>
|
||||||
|
Task.FromResult((IItem)_searchManager.SearchTasks
|
||||||
|
.First(searchTask => searchTask.SearchContainer.NativePath == nativePath).SearchContainer);
|
||||||
|
|
||||||
public NativePath GetNativePath(FullName fullName) => throw new NotImplementedException();
|
public override NativePath GetNativePath(FullName fullName) => new(fullName.Path);
|
||||||
|
|
||||||
public Task<byte[]?> GetContentAsync(IElement element, int? maxLength = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
public override Task<byte[]?> GetContentAsync(
|
||||||
|
IElement element,
|
||||||
|
int? maxLength = null,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
=> Task.FromResult(null as byte[]);
|
||||||
|
|
||||||
public bool CanHandlePath(NativePath path) => throw new NotImplementedException();
|
public override bool CanHandlePath(NativePath path) => path.Path.StartsWith(ContentProviderName);
|
||||||
|
|
||||||
public bool CanHandlePath(FullName path) => throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,25 @@
|
|||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace FileTime.App.Search;
|
namespace FileTime.App.Search;
|
||||||
|
|
||||||
public class SearchManager : ISearchManager
|
public class SearchManager : ISearchManager
|
||||||
{
|
{
|
||||||
private readonly ISearchContentProvider _searchContainerProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private ISearchContentProvider? _searchContainerProvider;
|
||||||
private readonly List<SearchTask> _searchTasks = new();
|
private readonly List<SearchTask> _searchTasks = new();
|
||||||
|
|
||||||
public SearchManager(ISearchContentProvider searchContainerProvider)
|
public IReadOnlyList<ISearchTask> SearchTasks { get; }
|
||||||
|
|
||||||
|
public SearchManager(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_searchContainerProvider = searchContainerProvider;
|
_serviceProvider = serviceProvider;
|
||||||
|
SearchTasks = _searchTasks.AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartSearchAsync(ISearchMatcher matcher, IContainer searchIn)
|
public async Task<ISearchTask> StartSearchAsync(ISearchMatcher matcher, IContainer searchIn)
|
||||||
{
|
{
|
||||||
|
_searchContainerProvider ??= _serviceProvider.GetRequiredService<ISearchContentProvider>();
|
||||||
var searchTask = new SearchTask(
|
var searchTask = new SearchTask(
|
||||||
searchIn,
|
searchIn,
|
||||||
_searchContainerProvider,
|
_searchContainerProvider,
|
||||||
@@ -23,5 +29,7 @@ public class SearchManager : ISearchManager
|
|||||||
_searchTasks.Add(searchTask);
|
_searchTasks.Add(searchTask);
|
||||||
|
|
||||||
await searchTask.StartAsync();
|
await searchTask.StartAsync();
|
||||||
|
|
||||||
|
return searchTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ using FileTime.Core.Timeline;
|
|||||||
|
|
||||||
namespace FileTime.App.Search;
|
namespace FileTime.App.Search;
|
||||||
|
|
||||||
public class SearchTask
|
public class SearchTask : ISearchTask
|
||||||
{
|
{
|
||||||
private readonly IContainer _baseContainer;
|
private readonly IContainer _baseContainer;
|
||||||
private readonly ISearchMatcher _matcher;
|
private readonly ISearchMatcher _matcher;
|
||||||
@@ -17,6 +17,9 @@ public class SearchTask
|
|||||||
private readonly SourceCache<AbsolutePath, string> _items = new(p => p.Path.Path);
|
private readonly SourceCache<AbsolutePath, string> _items = new(p => p.Path.Path);
|
||||||
private readonly SemaphoreSlim _searchingLock = new(1, 1);
|
private readonly SemaphoreSlim _searchingLock = new(1, 1);
|
||||||
private bool _isSearching;
|
private bool _isSearching;
|
||||||
|
private static int _searchId = 1;
|
||||||
|
|
||||||
|
public IContainer SearchContainer => _container;
|
||||||
|
|
||||||
public SearchTask(
|
public SearchTask(
|
||||||
IContainer baseContainer,
|
IContainer baseContainer,
|
||||||
@@ -24,13 +27,14 @@ public class SearchTask
|
|||||||
ISearchMatcher matcher
|
ISearchMatcher matcher
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
var randomId = $"{SearchContentProvider.ContentProviderName}/{_searchId++}_{baseContainer.Name}";
|
||||||
_baseContainer = baseContainer;
|
_baseContainer = baseContainer;
|
||||||
_matcher = matcher;
|
_matcher = matcher;
|
||||||
_container = new Container(
|
_container = new Container(
|
||||||
baseContainer.Name,
|
baseContainer.Name,
|
||||||
baseContainer.DisplayName,
|
baseContainer.DisplayName,
|
||||||
new FullName(""),
|
new FullName(randomId),
|
||||||
new NativePath(""),
|
new NativePath(randomId),
|
||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
@@ -43,7 +47,7 @@ public class SearchTask
|
|||||||
PointInTime.Present,
|
PointInTime.Present,
|
||||||
_exceptions.Connect(),
|
_exceptions.Connect(),
|
||||||
new ReadOnlyExtensionCollection(new ExtensionCollection()),
|
new ReadOnlyExtensionCollection(new ExtensionCollection()),
|
||||||
Observable.Return(_items.Connect())
|
_items.Connect().StartWithEmpty()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
using FileTime.Core.ContentAccess;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
|
||||||
namespace FileTime.App.Search;
|
namespace FileTime.App.Search;
|
||||||
|
|
||||||
@@ -6,8 +8,9 @@ public static class Startup
|
|||||||
{
|
{
|
||||||
public static IServiceCollection AddSearch(this IServiceCollection services)
|
public static IServiceCollection AddSearch(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddSingleton<ISearchContentProvider, SearchContentProvider>();
|
services.TryAddSingleton<ISearchContentProvider, SearchContentProvider>();
|
||||||
services.AddSingleton<ISearchManager, SearchManager>();
|
services.AddSingleton<IContentProvider>(sp => sp.GetRequiredService<ISearchContentProvider>());
|
||||||
|
services.TryAddSingleton<ISearchManager, SearchManager>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ public static class DynamicDataExtensions
|
|||||||
{
|
{
|
||||||
private class DisposableContext<TParam, TTaskResult>
|
private class DisposableContext<TParam, TTaskResult>
|
||||||
{
|
{
|
||||||
|
private readonly SemaphoreSlim _semaphore = new(1, 1);
|
||||||
private readonly Func<TParam, TTaskResult> _transformResult;
|
private readonly Func<TParam, TTaskResult> _transformResult;
|
||||||
private readonly TaskCompletionSource<TTaskResult?> _taskCompletionSource;
|
private readonly TaskCompletionSource<TTaskResult?> _taskCompletionSource;
|
||||||
|
private bool _isFinished;
|
||||||
public IDisposable? Disposable { get; set; }
|
public IDisposable? Disposable { get; set; }
|
||||||
|
|
||||||
public DisposableContext(Func<TParam, TTaskResult> transformResult,
|
public DisposableContext(Func<TParam, TTaskResult> transformResult,
|
||||||
@@ -22,6 +24,7 @@ public static class DynamicDataExtensions
|
|||||||
|
|
||||||
public void OnNext(TParam param)
|
public void OnNext(TParam param)
|
||||||
{
|
{
|
||||||
|
if (IsFinished()) return;
|
||||||
Disposable?.Dispose();
|
Disposable?.Dispose();
|
||||||
var result = _transformResult(param);
|
var result = _transformResult(param);
|
||||||
_taskCompletionSource.SetResult(result);
|
_taskCompletionSource.SetResult(result);
|
||||||
@@ -29,15 +32,27 @@ public static class DynamicDataExtensions
|
|||||||
|
|
||||||
public void OnError(Exception ex)
|
public void OnError(Exception ex)
|
||||||
{
|
{
|
||||||
|
if (IsFinished()) return;
|
||||||
Disposable?.Dispose();
|
Disposable?.Dispose();
|
||||||
_taskCompletionSource.SetException(ex);
|
_taskCompletionSource.SetException(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnCompleted()
|
public void OnCompleted()
|
||||||
{
|
{
|
||||||
|
if (IsFinished()) return;
|
||||||
Disposable?.Dispose();
|
Disposable?.Dispose();
|
||||||
_taskCompletionSource.SetResult(default);
|
_taskCompletionSource.SetResult(default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsFinished()
|
||||||
|
{
|
||||||
|
_semaphore.Wait();
|
||||||
|
var finished = _isFinished;
|
||||||
|
_isFinished = true;
|
||||||
|
_semaphore.Release();
|
||||||
|
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<IEnumerable<AbsolutePath>?> GetItemsAsync(
|
public static async Task<IEnumerable<AbsolutePath>?> GetItemsAsync(
|
||||||
@@ -46,12 +61,12 @@ public static class DynamicDataExtensions
|
|||||||
.Select(s =>
|
.Select(s =>
|
||||||
s is null
|
s is null
|
||||||
? new SourceList<AbsolutePath>().Connect().StartWithEmpty().ToCollection()
|
? new SourceList<AbsolutePath>().Connect().StartWithEmpty().ToCollection()
|
||||||
: s.ToCollection())
|
: s.StartWithEmpty().ToCollection())
|
||||||
.Switch());
|
.Switch());
|
||||||
|
|
||||||
public static async Task<IEnumerable<AbsolutePath>?> GetItemsAsync(
|
public static async Task<IEnumerable<AbsolutePath>?> GetItemsAsync(
|
||||||
this IObservable<IChangeSet<AbsolutePath, string>> stream)
|
this IObservable<IChangeSet<AbsolutePath, string>> stream)
|
||||||
=> await GetItemsAsync(stream.ToCollection());
|
=> await GetItemsAsync(stream.StartWithEmpty().ToCollection());
|
||||||
|
|
||||||
public static Task<IEnumerable<AbsolutePath>?> GetItemsAsync(
|
public static Task<IEnumerable<AbsolutePath>?> GetItemsAsync(
|
||||||
this IObservable<IReadOnlyCollection<AbsolutePath>> stream)
|
this IObservable<IReadOnlyCollection<AbsolutePath>> stream)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ namespace FileTime.Core.Models;
|
|||||||
|
|
||||||
public interface IContainer : IItem
|
public interface IContainer : IItem
|
||||||
{
|
{
|
||||||
IObservable<IObservable<IChangeSet<AbsolutePath, string>>?> Items { get; }
|
IObservable<IChangeSet<AbsolutePath, string>> Items { get; }
|
||||||
IObservable<bool> IsLoading { get; }
|
IObservable<bool> IsLoading { get; }
|
||||||
bool AllowRecursiveDeletion { get; }
|
bool AllowRecursiveDeletion { get; }
|
||||||
}
|
}
|
||||||
@@ -10,11 +10,12 @@ namespace FileTime.Core.ContentAccess;
|
|||||||
public abstract class ContentProviderBase : IContentProvider
|
public abstract class ContentProviderBase : IContentProvider
|
||||||
{
|
{
|
||||||
private readonly ReadOnlyExtensionCollection _extensions;
|
private readonly ReadOnlyExtensionCollection _extensions;
|
||||||
|
private readonly IObservable<IChangeSet<AbsolutePath, string>> _items;
|
||||||
|
|
||||||
protected BehaviorSubject<IObservable<IChangeSet<AbsolutePath, string>>?> Items { get; } = new(null);
|
protected SourceCache<AbsolutePath, string> Items { get; } = new(p => p.Path.Path);
|
||||||
protected ExtensionCollection Extensions { get; }
|
protected ExtensionCollection Extensions { get; }
|
||||||
|
|
||||||
IObservable<IObservable<IChangeSet<AbsolutePath, string>>?> IContainer.Items => Items;
|
IObservable<IChangeSet<AbsolutePath, string>> IContainer.Items => _items;
|
||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ public abstract class ContentProviderBase : IContentProvider
|
|||||||
FullName = FullName.CreateSafe(name);
|
FullName = FullName.CreateSafe(name);
|
||||||
Extensions = new ExtensionCollection();
|
Extensions = new ExtensionCollection();
|
||||||
_extensions = Extensions.AsReadOnly();
|
_extensions = Extensions.AsReadOnly();
|
||||||
|
_items = Items.Connect().StartWithEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual Task OnEnter() => Task.CompletedTask;
|
public virtual Task OnEnter() => Task.CompletedTask;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public record Container(
|
|||||||
PointInTime PointInTime,
|
PointInTime PointInTime,
|
||||||
IObservable<IChangeSet<Exception>> Exceptions,
|
IObservable<IChangeSet<Exception>> Exceptions,
|
||||||
ReadOnlyExtensionCollection Extensions,
|
ReadOnlyExtensionCollection Extensions,
|
||||||
IObservable<IObservable<IChangeSet<AbsolutePath, string>>?> Items) : IContainer
|
IObservable<IChangeSet<AbsolutePath, string>> Items) : IContainer
|
||||||
{
|
{
|
||||||
private readonly CancellationTokenSource _loadingCancellationTokenSource = new();
|
private readonly CancellationTokenSource _loadingCancellationTokenSource = new();
|
||||||
public CancellationToken LoadingCancellationToken => _loadingCancellationTokenSource.Token;
|
public CancellationToken LoadingCancellationToken => _loadingCancellationTokenSource.Token;
|
||||||
|
|||||||
@@ -52,8 +52,7 @@ public class Tab : ITab
|
|||||||
CurrentLocation
|
CurrentLocation
|
||||||
.Where(c => c is not null)
|
.Where(c => c is not null)
|
||||||
.Select(c => c!.Items)
|
.Select(c => c!.Items)
|
||||||
.Switch()
|
.Select(items => items.TransformAsync(MapItem)),
|
||||||
.Select(items => items?.TransformAsync(MapItem)),
|
|
||||||
_itemFilters.Connect().StartWithEmpty().ToCollection(),
|
_itemFilters.Connect().StartWithEmpty().ToCollection(),
|
||||||
(items, filters) =>
|
(items, filters) =>
|
||||||
items
|
items
|
||||||
|
|||||||
@@ -14,45 +14,35 @@ namespace FileTime.GuiApp.Services;
|
|||||||
|
|
||||||
public class RootDriveInfoService : IStartupHandler
|
public class RootDriveInfoService : IStartupHandler
|
||||||
{
|
{
|
||||||
private readonly SourceList<DriveInfo> _rootDrives = new();
|
private readonly List<DriveInfo> _rootDrives = new();
|
||||||
|
|
||||||
public RootDriveInfoService(
|
public RootDriveInfoService(
|
||||||
IGuiAppState guiAppState,
|
IGuiAppState guiAppState,
|
||||||
ILocalContentProvider localContentProvider,
|
ILocalContentProvider localContentProvider)
|
||||||
ITimelessContentProvider timelessContentProvider)
|
|
||||||
{
|
{
|
||||||
InitRootDrives();
|
InitRootDrives();
|
||||||
|
|
||||||
var localContentProviderAsList = new SourceCache<AbsolutePath, string>(i => i.Path.Path);
|
var rootDriveInfos = localContentProvider.Items.Transform(
|
||||||
localContentProviderAsList.AddOrUpdate(new AbsolutePath(timelessContentProvider, localContentProvider));
|
i =>
|
||||||
var localContentProviderStream = localContentProviderAsList.Connect();
|
{
|
||||||
|
var rootDrive = _rootDrives.FirstOrDefault(d =>
|
||||||
var rootDriveInfos = Observable.CombineLatest(
|
|
||||||
localContentProvider.Items,
|
|
||||||
_rootDrives.Connect().StartWithEmpty().ToCollection(),
|
|
||||||
(items, drives) =>
|
|
||||||
{
|
{
|
||||||
return items is null
|
var containerPath = localContentProvider.GetNativePath(i.Path).Path;
|
||||||
? Observable.Empty<IChangeSet<(AbsolutePath Path, DriveInfo? Drive), string>>()
|
var drivePath = d.Name.TrimEnd(Path.DirectorySeparatorChar);
|
||||||
: items!
|
return containerPath == drivePath
|
||||||
.Or(new[] { localContentProviderStream })
|
|| (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && containerPath == "/" &&
|
||||||
.Transform(i => (Path: i, Drive: drives.FirstOrDefault(d =>
|
d.Name == "/");
|
||||||
{
|
});
|
||||||
var containerPath = localContentProvider.GetNativePath(i.Path).Path;
|
|
||||||
var drivePath = d.Name.TrimEnd(Path.DirectorySeparatorChar);
|
return (Path: i, Drive: rootDrive);
|
||||||
return containerPath == drivePath
|
}
|
||||||
|| (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && containerPath == "/" &&
|
)
|
||||||
d.Name == "/");
|
.Filter(t => t.Drive is not null)
|
||||||
})))
|
.TransformAsync(async t => (Item: await t.Path.ResolveAsyncSafe(), Drive: t.Drive!))
|
||||||
.Filter(t => t.Drive is not null);
|
.Filter(t => t.Item is IContainer)
|
||||||
}
|
.Transform(t => (Container: (IContainer) t.Item!, t.Drive))
|
||||||
)
|
.Transform(t => new RootDriveInfo(t.Drive, t.Container))
|
||||||
.Switch()
|
.Sort(SortExpressionComparer<RootDriveInfo>.Ascending(d => d.Name));
|
||||||
.TransformAsync(async t => (Item: await t.Path.ResolveAsyncSafe(), Drive: t.Drive!))
|
|
||||||
.Filter(t => t.Item is IContainer)
|
|
||||||
.Transform(t => (Container: (IContainer)t.Item!, t.Drive))
|
|
||||||
.Transform(t => new RootDriveInfo(t.Drive, t.Container))
|
|
||||||
.Sort(SortExpressionComparer<RootDriveInfo>.Ascending(d => d.Name));
|
|
||||||
|
|
||||||
guiAppState.RootDriveInfos = rootDriveInfos.ToBindedCollection();
|
guiAppState.RootDriveInfos = rootDriveInfos.ToBindedCollection();
|
||||||
|
|
||||||
@@ -68,6 +58,7 @@ public class RootDriveInfoService : IStartupHandler
|
|||||||
&& d.DriveFormat != "tracefs"
|
&& d.DriveFormat != "tracefs"
|
||||||
&& !d.RootDirectory.FullName.StartsWith("/snap/"));
|
&& !d.RootDirectory.FullName.StartsWith("/snap/"));
|
||||||
|
|
||||||
|
_rootDrives.Clear();
|
||||||
_rootDrives.AddRange(drives);
|
_rootDrives.AddRange(drives);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Reactive.Subjects;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using FileTime.Core.ContentAccess;
|
using FileTime.Core.ContentAccess;
|
||||||
@@ -13,7 +12,6 @@ namespace FileTime.Providers.Local;
|
|||||||
public sealed partial class LocalContentProvider : ContentProviderBase, ILocalContentProvider
|
public sealed partial class LocalContentProvider : ContentProviderBase, ILocalContentProvider
|
||||||
{
|
{
|
||||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||||
private readonly SourceCache<AbsolutePath, string> _rootDirectories = new(i => i.Path.Path);
|
|
||||||
private readonly bool _isCaseInsensitive;
|
private readonly bool _isCaseInsensitive;
|
||||||
|
|
||||||
public LocalContentProvider(ITimelessContentProvider timelessContentProvider) : base("local")
|
public LocalContentProvider(ITimelessContentProvider timelessContentProvider) : base("local")
|
||||||
@@ -24,8 +22,6 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
SupportsContentStreams = true;
|
SupportsContentStreams = true;
|
||||||
|
|
||||||
RefreshRootDirectories();
|
RefreshRootDirectories();
|
||||||
|
|
||||||
Items.OnNext(_rootDirectories.Connect());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task OnEnter()
|
public override Task OnEnter()
|
||||||
@@ -41,7 +37,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
? new DirectoryInfo("/").GetDirectories()
|
? new DirectoryInfo("/").GetDirectories()
|
||||||
: Environment.GetLogicalDrives().Select(d => new DirectoryInfo(d));
|
: Environment.GetLogicalDrives().Select(d => new DirectoryInfo(d));
|
||||||
|
|
||||||
_rootDirectories.Edit(actions =>
|
Items.Edit(actions =>
|
||||||
{
|
{
|
||||||
actions.Clear();
|
actions.Clear();
|
||||||
actions.AddOrUpdate(rootDirectories.Select(d => DirectoryToAbsolutePath(d, PointInTime.Present)));
|
actions.AddOrUpdate(rootDirectories.Select(d => DirectoryToAbsolutePath(d, PointInTime.Present)));
|
||||||
@@ -50,7 +46,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
|
|
||||||
public override bool CanHandlePath(NativePath path)
|
public override bool CanHandlePath(NativePath path)
|
||||||
{
|
{
|
||||||
var rootDrive = _rootDirectories
|
var rootDrive = Items
|
||||||
.Items
|
.Items
|
||||||
.FirstOrDefault(r =>
|
.FirstOrDefault(r =>
|
||||||
path.Path.StartsWith(
|
path.Path.StartsWith(
|
||||||
@@ -173,7 +169,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
pointInTime,
|
pointInTime,
|
||||||
exceptions.Connect(),
|
exceptions.Connect(),
|
||||||
new ExtensionCollection().AsReadOnly(),
|
new ExtensionCollection().AsReadOnly(),
|
||||||
Observable.Return<IObservable<IChangeSet<AbsolutePath, string>>?>(null)
|
new SourceCache<AbsolutePath, string>(a => a.Path.Path).Connect()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,9 +224,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
pointInTime,
|
pointInTime,
|
||||||
exceptions.Connect(),
|
exceptions.Connect(),
|
||||||
new ExtensionCollection().AsReadOnly(),
|
new ExtensionCollection().AsReadOnly(),
|
||||||
//Observable.FromAsync(async () => await Task.Run(InitChildrenHelper)
|
children.Connect().StartWithEmpty()
|
||||||
//Observable.Return(InitChildren())
|
|
||||||
Observable.Return(children.Connect())
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Task.Run(() => LoadChildren(container, directoryInfo, children, pointInTime, exceptions));
|
Task.Run(() => LoadChildren(container, directoryInfo, children, pointInTime, exceptions));
|
||||||
|
|||||||
Reference in New Issue
Block a user