Search WIP

This commit is contained in:
2023-02-27 08:41:19 +01:00
parent 64d7634b1b
commit a01c3a69b4
16 changed files with 128 additions and 100 deletions

View File

@@ -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()

View File

@@ -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)

View File

@@ -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; }
} }

View File

@@ -0,0 +1,9 @@
using FileTime.Core.Models;
namespace FileTime.App.Search;
public interface ISearchTask
{
IContainer SearchContainer { get; }
Task StartAsync();
}

View File

@@ -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" />

View File

@@ -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();
} }

View File

@@ -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;
} }
} }

View File

@@ -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()
); );
} }

View File

@@ -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;
} }

View File

@@ -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)

View File

@@ -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; }
} }

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -14,43 +14,33 @@ 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 rootDriveInfos = Observable.CombineLatest(
localContentProvider.Items,
_rootDrives.Connect().StartWithEmpty().ToCollection(),
(items, drives) =>
{ {
return items is null var rootDrive = _rootDrives.FirstOrDefault(d =>
? Observable.Empty<IChangeSet<(AbsolutePath Path, DriveInfo? Drive), string>>()
: items!
.Or(new[] { localContentProviderStream })
.Transform(i => (Path: i, Drive: drives.FirstOrDefault(d =>
{ {
var containerPath = localContentProvider.GetNativePath(i.Path).Path; var containerPath = localContentProvider.GetNativePath(i.Path).Path;
var drivePath = d.Name.TrimEnd(Path.DirectorySeparatorChar); var drivePath = d.Name.TrimEnd(Path.DirectorySeparatorChar);
return containerPath == drivePath return containerPath == drivePath
|| (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && containerPath == "/" && || (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && containerPath == "/" &&
d.Name == "/"); d.Name == "/");
}))) });
.Filter(t => t.Drive is not null);
return (Path: i, Drive: rootDrive);
} }
) )
.Switch() .Filter(t => t.Drive is not null)
.TransformAsync(async t => (Item: await t.Path.ResolveAsyncSafe(), Drive: t.Drive!)) .TransformAsync(async t => (Item: await t.Path.ResolveAsyncSafe(), Drive: t.Drive!))
.Filter(t => t.Item is IContainer) .Filter(t => t.Item is IContainer)
.Transform(t => (Container: (IContainer)t.Item!, t.Drive)) .Transform(t => (Container: (IContainer) t.Item!, t.Drive))
.Transform(t => new RootDriveInfo(t.Drive, t.Container)) .Transform(t => new RootDriveInfo(t.Drive, t.Container))
.Sort(SortExpressionComparer<RootDriveInfo>.Ascending(d => d.Name)); .Sort(SortExpressionComparer<RootDriveInfo>.Ascending(d => d.Name));
@@ -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);
} }
} }

View File

@@ -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));