Search WIP
This commit is contained in:
20
src/AppCommon/FileTime.App.Search/FileTime.App.Search.csproj
Normal file
20
src/AppCommon/FileTime.App.Search/FileTime.App.Search.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.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.Search.Abstractions\FileTime.App.Search.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
21
src/AppCommon/FileTime.App.Search/NameContainsMatcher.cs
Normal file
21
src/AppCommon/FileTime.App.Search/NameContainsMatcher.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using FileTime.App.Core.Models;
|
||||
using FileTime.App.Core.Services;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.App.Search;
|
||||
|
||||
public class NameContainsMatcher : ISearchMatcher
|
||||
{
|
||||
private readonly IItemNameConverterService _itemNameConverterService;
|
||||
private readonly string _searchText;
|
||||
|
||||
public NameContainsMatcher(IItemNameConverterService itemNameConverterService, string searchText)
|
||||
{
|
||||
_itemNameConverterService = itemNameConverterService;
|
||||
_searchText = searchText;
|
||||
}
|
||||
|
||||
public Task<bool> IsItemMatchAsync(IItem item) => Task.FromResult(item.Name.Contains(_searchText, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
public List<ItemNamePart> GetDisplayName(IItem item) => _itemNameConverterService.GetDisplayName(item.DisplayName, _searchText);
|
||||
}
|
||||
44
src/AppCommon/FileTime.App.Search/SearchContentProvider.cs
Normal file
44
src/AppCommon/FileTime.App.Search/SearchContentProvider.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using DynamicData;
|
||||
using FileTime.Core.ContentAccess;
|
||||
using FileTime.Core.Enums;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.App.Search;
|
||||
|
||||
public class SearchContentProvider : ISearchContentProvider
|
||||
{
|
||||
public string Name { get; }
|
||||
public string DisplayName { get; }
|
||||
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 Task<IItem> GetItemByFullNameAsync(FullName fullName, PointInTime pointInTime, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, ItemInitializationSettings itemInitializationSettings = default) => throw new NotImplementedException();
|
||||
|
||||
public Task<IItem> GetItemByNativePathAsync(NativePath nativePath, PointInTime pointInTime, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, ItemInitializationSettings itemInitializationSettings = default) => throw new NotImplementedException();
|
||||
|
||||
public NativePath GetNativePath(FullName fullName) => 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();
|
||||
}
|
||||
27
src/AppCommon/FileTime.App.Search/SearchManager.cs
Normal file
27
src/AppCommon/FileTime.App.Search/SearchManager.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.App.Search;
|
||||
|
||||
public class SearchManager : ISearchManager
|
||||
{
|
||||
private readonly ISearchContentProvider _searchContainerProvider;
|
||||
private readonly List<SearchTask> _searchTasks = new();
|
||||
|
||||
public SearchManager(ISearchContentProvider searchContainerProvider)
|
||||
{
|
||||
_searchContainerProvider = searchContainerProvider;
|
||||
}
|
||||
|
||||
public async Task StartSearchAsync(ISearchMatcher matcher, IContainer searchIn)
|
||||
{
|
||||
var searchTask = new SearchTask(
|
||||
searchIn,
|
||||
_searchContainerProvider,
|
||||
matcher
|
||||
);
|
||||
|
||||
_searchTasks.Add(searchTask);
|
||||
|
||||
await searchTask.StartAsync();
|
||||
}
|
||||
}
|
||||
101
src/AppCommon/FileTime.App.Search/SearchTask.cs
Normal file
101
src/AppCommon/FileTime.App.Search/SearchTask.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using FileTime.Core.ContentAccess;
|
||||
using FileTime.Core.Enums;
|
||||
using FileTime.Core.Extensions;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.App.Search;
|
||||
|
||||
public class SearchTask
|
||||
{
|
||||
private readonly IContainer _baseContainer;
|
||||
private readonly ISearchMatcher _matcher;
|
||||
private readonly Container _container;
|
||||
private readonly SourceList<Exception> _exceptions = new();
|
||||
private readonly SourceCache<AbsolutePath, string> _items = new(p => p.Path.Path);
|
||||
private readonly SemaphoreSlim _searchingLock = new(1, 1);
|
||||
private bool _isSearching;
|
||||
|
||||
public SearchTask(
|
||||
IContainer baseContainer,
|
||||
IContentProvider contentProvider,
|
||||
ISearchMatcher matcher
|
||||
)
|
||||
{
|
||||
_baseContainer = baseContainer;
|
||||
_matcher = matcher;
|
||||
_container = new Container(
|
||||
baseContainer.Name,
|
||||
baseContainer.DisplayName,
|
||||
new FullName(""),
|
||||
new NativePath(""),
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
null,
|
||||
SupportsDelete.False,
|
||||
false,
|
||||
null,
|
||||
contentProvider,
|
||||
false,
|
||||
PointInTime.Present,
|
||||
_exceptions.Connect(),
|
||||
new ReadOnlyExtensionCollection(new ExtensionCollection()),
|
||||
Observable.Return(_items.Connect())
|
||||
);
|
||||
}
|
||||
|
||||
public async Task StartAsync()
|
||||
{
|
||||
await _searchingLock.WaitAsync();
|
||||
if (_isSearching) return;
|
||||
_isSearching = true;
|
||||
_searchingLock.Release();
|
||||
|
||||
Task.Run(BootstrapSearch);
|
||||
|
||||
async Task BootstrapSearch()
|
||||
{
|
||||
try
|
||||
{
|
||||
_container.IsLoading.OnNext(true);
|
||||
await TraverseTree(_baseContainer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_container.IsLoading.OnNext(false);
|
||||
|
||||
await _searchingLock.WaitAsync();
|
||||
_isSearching = false;
|
||||
_searchingLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TraverseTree(IContainer container)
|
||||
{
|
||||
var items = (await container.Items.GetItemsAsync())?.ToList();
|
||||
if (items is null) return;
|
||||
|
||||
var childContainers = new List<IContainer>();
|
||||
|
||||
foreach (var itemPath in items)
|
||||
{
|
||||
var item = await itemPath.ResolveAsync();
|
||||
if (await _matcher.IsItemMatchAsync(item))
|
||||
{
|
||||
_items.AddOrUpdate(itemPath);
|
||||
}
|
||||
|
||||
if (item is IContainer childContainer)
|
||||
childContainers.Add(childContainer);
|
||||
}
|
||||
|
||||
foreach (var childContainer in childContainers)
|
||||
{
|
||||
await TraverseTree(childContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/AppCommon/FileTime.App.Search/Startup.cs
Normal file
14
src/AppCommon/FileTime.App.Search/Startup.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FileTime.App.Search;
|
||||
|
||||
public static class Startup
|
||||
{
|
||||
public static IServiceCollection AddSearch(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<ISearchContentProvider, SearchContentProvider>();
|
||||
services.AddSingleton<ISearchManager, SearchManager>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user