From eef783bd7742c6345ac8cd59984fa2400d7e661e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Tue, 12 Sep 2023 17:25:24 +0200 Subject: [PATCH] Frequency navigation db --- .../ViewModels/CommandPaletteViewModel.cs | 8 +- .../Models/IApplicationSettings.cs | 1 + .../Models/ApplicationSettings.cs | 2 + .../ItemPreview/ElementPreviewViewModel.cs | 2 +- .../FileTime.App.Database.Abstractions.csproj | 10 + .../IDatabaseConnection.cs | 7 + .../IDatabaseContext.cs | 6 + .../IQueryCollection.cs | 11 + .../IQueryable.cs | 14 + .../IQueryableResult.cs | 14 + .../ITransaction.cs | 8 + .../IUpdatable.cs | 8 + .../FileTime.App.Database.csproj | 20 ++ .../LiteDb/DatabaseConnection.cs | 34 ++ .../LiteDb/DatabaseContext.cs | 15 + .../LiteDb/QueryCollection.cs | 20 ++ .../FileTime.App.Database/LiteDb/Queryable.cs | 51 +++ .../LiteDb/QueryableResult.cs | 23 ++ .../LiteDb/Transaction.cs | 25 ++ .../FileTime.App.Database/LiteDb/Updatable.cs | 19 ++ .../FileTime.App.Database/Startup.cs | 14 + .../DependencyInjection.cs | 2 + .../FileTime.App.DependencyInjection.csproj | 1 + .../Services/IFrequencyNavigationService.cs | 2 +- .../FileTime.App.FrequencyNavigation.csproj | 2 + .../Models/ContainerFrequencyData.cs | 12 +- .../Services/FrequencyNavigationRepository.cs | 301 ++++++++++++++++++ .../Services/FrequencyNavigationService.cs | 181 ++++------- .../Startup.cs | 2 +- .../FrequencyNavigationViewModel.cs | 8 +- .../IFuzzyPanelViewModel.cs | 2 +- .../FileTime.App.FuzzyPanel.csproj | 1 + .../FuzzyPanelViewModel.cs | 25 +- src/FileTime.sln | 14 + .../Avalonia/FileTime.GuiApp/Startup.cs | 1 + 35 files changed, 726 insertions(+), 140 deletions(-) create mode 100644 src/AppCommon/FileTime.App.Database.Abstractions/FileTime.App.Database.Abstractions.csproj create mode 100644 src/AppCommon/FileTime.App.Database.Abstractions/IDatabaseConnection.cs create mode 100644 src/AppCommon/FileTime.App.Database.Abstractions/IDatabaseContext.cs create mode 100644 src/AppCommon/FileTime.App.Database.Abstractions/IQueryCollection.cs create mode 100644 src/AppCommon/FileTime.App.Database.Abstractions/IQueryable.cs create mode 100644 src/AppCommon/FileTime.App.Database.Abstractions/IQueryableResult.cs create mode 100644 src/AppCommon/FileTime.App.Database.Abstractions/ITransaction.cs create mode 100644 src/AppCommon/FileTime.App.Database.Abstractions/IUpdatable.cs create mode 100644 src/AppCommon/FileTime.App.Database/FileTime.App.Database.csproj create mode 100644 src/AppCommon/FileTime.App.Database/LiteDb/DatabaseConnection.cs create mode 100644 src/AppCommon/FileTime.App.Database/LiteDb/DatabaseContext.cs create mode 100644 src/AppCommon/FileTime.App.Database/LiteDb/QueryCollection.cs create mode 100644 src/AppCommon/FileTime.App.Database/LiteDb/Queryable.cs create mode 100644 src/AppCommon/FileTime.App.Database/LiteDb/QueryableResult.cs create mode 100644 src/AppCommon/FileTime.App.Database/LiteDb/Transaction.cs create mode 100644 src/AppCommon/FileTime.App.Database/LiteDb/Updatable.cs create mode 100644 src/AppCommon/FileTime.App.Database/Startup.cs create mode 100644 src/AppCommon/FileTime.App.FrequencyNavigation/Services/FrequencyNavigationRepository.cs diff --git a/src/AppCommon/FileTime.App.CommandPalette/ViewModels/CommandPaletteViewModel.cs b/src/AppCommon/FileTime.App.CommandPalette/ViewModels/CommandPaletteViewModel.cs index f065700..3e4c743 100644 --- a/src/AppCommon/FileTime.App.CommandPalette/ViewModels/CommandPaletteViewModel.cs +++ b/src/AppCommon/FileTime.App.CommandPalette/ViewModels/CommandPaletteViewModel.cs @@ -21,7 +21,7 @@ public class CommandPaletteViewModel : FuzzyPanelViewModel logger) + ILogger logger) : base(logger) { _commandPaletteService = commandPaletteService; _identifiableUserCommandService = identifiableUserCommandService; @@ -34,7 +34,11 @@ public class CommandPaletteViewModel : FuzzyPanelViewModel _commandPaletteService.CloseCommandPalette(); - public override void UpdateFilteredMatches() => UpdateFilteredMatchesInternal(); + public override Task UpdateFilteredMatches() + { + UpdateFilteredMatchesInternal(); + return Task.CompletedTask; + } private void UpdateFilteredMatchesInternal() => FilteredMatches = _commandPaletteService diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Models/IApplicationSettings.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Models/IApplicationSettings.cs index dc212ef..9a17887 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/Models/IApplicationSettings.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Models/IApplicationSettings.cs @@ -4,4 +4,5 @@ public interface IApplicationSettings { string AppDataRoot { get; } string EnvironmentName { get; } + string DataFolderName { get; } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Models/ApplicationSettings.cs b/src/AppCommon/FileTime.App.Core/Models/ApplicationSettings.cs index 44f8875..6f4ef39 100644 --- a/src/AppCommon/FileTime.App.Core/Models/ApplicationSettings.cs +++ b/src/AppCommon/FileTime.App.Core/Models/ApplicationSettings.cs @@ -7,6 +7,8 @@ public class ApplicationSettings : IApplicationSettings public string AppDataRoot { get; private set; } = null!; public string EnvironmentName { get; private set; } = null!; + public string DataFolderName { get; } = "data"; + public ApplicationSettings() { #if DEBUG diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/ItemPreview/ElementPreviewViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/ItemPreview/ElementPreviewViewModel.cs index 73251b3..92289f5 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/ItemPreview/ElementPreviewViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/ItemPreview/ElementPreviewViewModel.cs @@ -118,7 +118,7 @@ public partial class ElementPreviewViewModel : IElementPreviewViewModel, IAsyncI } catch (Exception ex) { - TextContent = $"Error while getting content of {element.FullName}. " + ex.ToString(); + TextContent = $"Error while getting content of {element.FullName}. " + ex; } Mode = (TextContent?.Length ?? 0) switch diff --git a/src/AppCommon/FileTime.App.Database.Abstractions/FileTime.App.Database.Abstractions.csproj b/src/AppCommon/FileTime.App.Database.Abstractions/FileTime.App.Database.Abstractions.csproj new file mode 100644 index 0000000..b217350 --- /dev/null +++ b/src/AppCommon/FileTime.App.Database.Abstractions/FileTime.App.Database.Abstractions.csproj @@ -0,0 +1,10 @@ + + + + net7.0 + enable + enable + FileTime.App.Database + + + diff --git a/src/AppCommon/FileTime.App.Database.Abstractions/IDatabaseConnection.cs b/src/AppCommon/FileTime.App.Database.Abstractions/IDatabaseConnection.cs new file mode 100644 index 0000000..e7ee1ab --- /dev/null +++ b/src/AppCommon/FileTime.App.Database.Abstractions/IDatabaseConnection.cs @@ -0,0 +1,7 @@ +namespace FileTime.App.Database; + +public interface IDatabaseConnection : IDisposable +{ + ITransaction BeginTransaction(); + IQueryCollection GetCollection(string collectionName); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Database.Abstractions/IDatabaseContext.cs b/src/AppCommon/FileTime.App.Database.Abstractions/IDatabaseContext.cs new file mode 100644 index 0000000..8ac5e57 --- /dev/null +++ b/src/AppCommon/FileTime.App.Database.Abstractions/IDatabaseContext.cs @@ -0,0 +1,6 @@ +namespace FileTime.App.Database; + +public interface IDatabaseContext +{ + ValueTask GetConnectionAsync(); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Database.Abstractions/IQueryCollection.cs b/src/AppCommon/FileTime.App.Database.Abstractions/IQueryCollection.cs new file mode 100644 index 0000000..d8c4c45 --- /dev/null +++ b/src/AppCommon/FileTime.App.Database.Abstractions/IQueryCollection.cs @@ -0,0 +1,11 @@ +using System.Linq.Expressions; + +namespace FileTime.App.Database; + +public interface IQueryCollection +{ + IQueryable Query(); + bool Exists(Expression> predicate); + T? FirstOrDefault(Expression> predicate); + IEnumerable ToEnumerable(); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Database.Abstractions/IQueryable.cs b/src/AppCommon/FileTime.App.Database.Abstractions/IQueryable.cs new file mode 100644 index 0000000..d59e45f --- /dev/null +++ b/src/AppCommon/FileTime.App.Database.Abstractions/IQueryable.cs @@ -0,0 +1,14 @@ +using System.Linq.Expressions; + +namespace FileTime.App.Database; + +public interface IQueryable : IQueryableResult +{ + IQueryable Where(Expression> predicate); + IQueryable Skip(int skip); + IQueryable Take(int take); + IQueryable Include(Expression> selector); + IQueryableResult Select(Expression> selector); + IQueryable OrderBy(Expression> order); + IQueryable OrderByDescending(Expression> order); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Database.Abstractions/IQueryableResult.cs b/src/AppCommon/FileTime.App.Database.Abstractions/IQueryableResult.cs new file mode 100644 index 0000000..e9590ee --- /dev/null +++ b/src/AppCommon/FileTime.App.Database.Abstractions/IQueryableResult.cs @@ -0,0 +1,14 @@ +namespace FileTime.App.Database; + +public interface IQueryableResult +{ + int Count(); + bool Exists(); + T First(); + T? FirstOrDefault(); + T Single(); + T? SingleOrDefault(); + IEnumerable ToEnumerable(); + List ToList(); + T[] ToArray(); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Database.Abstractions/ITransaction.cs b/src/AppCommon/FileTime.App.Database.Abstractions/ITransaction.cs new file mode 100644 index 0000000..ecb5902 --- /dev/null +++ b/src/AppCommon/FileTime.App.Database.Abstractions/ITransaction.cs @@ -0,0 +1,8 @@ +namespace FileTime.App.Database; + +public interface ITransaction : IDisposable +{ + ValueTask CommitAsync(); + void Rollback(); + IUpdatable GetCollection(string collectionName); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Database.Abstractions/IUpdatable.cs b/src/AppCommon/FileTime.App.Database.Abstractions/IUpdatable.cs new file mode 100644 index 0000000..d95c9fb --- /dev/null +++ b/src/AppCommon/FileTime.App.Database.Abstractions/IUpdatable.cs @@ -0,0 +1,8 @@ +namespace FileTime.App.Database; + +public interface IUpdatable +{ + void Insert(T item); + void Update(T item); + void Delete(int id); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Database/FileTime.App.Database.csproj b/src/AppCommon/FileTime.App.Database/FileTime.App.Database.csproj new file mode 100644 index 0000000..de5a0d7 --- /dev/null +++ b/src/AppCommon/FileTime.App.Database/FileTime.App.Database.csproj @@ -0,0 +1,20 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + + diff --git a/src/AppCommon/FileTime.App.Database/LiteDb/DatabaseConnection.cs b/src/AppCommon/FileTime.App.Database/LiteDb/DatabaseConnection.cs new file mode 100644 index 0000000..8375718 --- /dev/null +++ b/src/AppCommon/FileTime.App.Database/LiteDb/DatabaseConnection.cs @@ -0,0 +1,34 @@ +using FileTime.App.Core.Models; +using LiteDB; + +namespace FileTime.App.Database.LiteDb; + +public class DatabaseConnection : IDatabaseConnection +{ + private readonly ILiteDatabase _liteDb; + + public DatabaseConnection(IApplicationSettings applicationSettings) + { + var dataFolderPath = Path.Combine(applicationSettings.AppDataRoot, applicationSettings.DataFolderName); + if (!Directory.Exists(dataFolderPath)) + { + Directory.CreateDirectory(dataFolderPath); + } + + var databasePath = Path.Combine(dataFolderPath, "FileTime.db"); + _liteDb = new LiteDatabase($"Filename={databasePath};Mode=Shared;"); + } + + public ITransaction BeginTransaction() + { + _liteDb.BeginTrans(); + var database = new Transaction(_liteDb); + + return database; + } + + public IQueryCollection GetCollection(string collectionName) + => new QueryCollection(_liteDb.GetCollection(collectionName)); + + public void Dispose() => _liteDb.Dispose(); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Database/LiteDb/DatabaseContext.cs b/src/AppCommon/FileTime.App.Database/LiteDb/DatabaseContext.cs new file mode 100644 index 0000000..0963ab8 --- /dev/null +++ b/src/AppCommon/FileTime.App.Database/LiteDb/DatabaseContext.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace FileTime.App.Database.LiteDb; + +public class DatabaseContext : IDatabaseContext +{ + private readonly IServiceProvider _serviceProvider; + + public DatabaseContext(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + public ValueTask GetConnectionAsync() + => ValueTask.FromResult((IDatabaseConnection)_serviceProvider.GetRequiredService()); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Database/LiteDb/QueryCollection.cs b/src/AppCommon/FileTime.App.Database/LiteDb/QueryCollection.cs new file mode 100644 index 0000000..c644ad0 --- /dev/null +++ b/src/AppCommon/FileTime.App.Database/LiteDb/QueryCollection.cs @@ -0,0 +1,20 @@ +using System.Linq.Expressions; +using LiteDB; + +namespace FileTime.App.Database.LiteDb; + +public class QueryCollection : IQueryCollection +{ + private readonly ILiteCollection _collection; + + public QueryCollection(ILiteCollection collection) + { + _collection = collection; + } + + public IQueryable Query() => new Queryable(_collection.Query()); + + public bool Exists(Expression> predicate) => _collection.Exists(predicate); + public T? FirstOrDefault(Expression> predicate) => _collection.FindOne(predicate); + public IEnumerable ToEnumerable() => _collection.FindAll(); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Database/LiteDb/Queryable.cs b/src/AppCommon/FileTime.App.Database/LiteDb/Queryable.cs new file mode 100644 index 0000000..25f83ad --- /dev/null +++ b/src/AppCommon/FileTime.App.Database/LiteDb/Queryable.cs @@ -0,0 +1,51 @@ +using System.Linq.Expressions; +using LiteDB; + +namespace FileTime.App.Database.LiteDb; + +public class Queryable : IQueryable +{ + private readonly ILiteQueryable _collection; + private int SkipCount { get; init; } + private int TakeCount { get; init; } + + public Queryable(ILiteQueryable collection) + { + _collection = collection; + } + + public IQueryable Where(Expression> predicate) => new Queryable(_collection.Where(predicate)); + public IQueryable OrderBy(Expression> order) => new Queryable(_collection.OrderBy(order)); + public IQueryable OrderByDescending(Expression> order) => new Queryable(_collection.OrderByDescending(order)); + public IQueryable Skip(int skip) => new Queryable(_collection) {SkipCount = skip}; + public IQueryable Take(int take) => new Queryable(_collection) {TakeCount = take}; + public IQueryable Include(Expression> selector) => new Queryable(_collection.Include(selector)); + + private ILiteQueryableResult ApplySkipAndTake(ILiteQueryableResult collection) + { + if (SkipCount > 0) + { + collection = collection.Skip(SkipCount); + } + + if (TakeCount > 0) + { + collection = collection.Limit(TakeCount); + } + + return collection; + } + + + public IQueryableResult Select(Expression> selector) => new QueryableResult(ApplySkipAndTake(_collection.Select(selector))); + + public int Count() => ApplySkipAndTake(_collection).Count(); + public bool Exists() => ApplySkipAndTake(_collection).Exists(); + public T First() => ApplySkipAndTake(_collection).First(); + public T FirstOrDefault() => ApplySkipAndTake(_collection).FirstOrDefault(); + public T Single() => ApplySkipAndTake(_collection).Single(); + public T SingleOrDefault() => ApplySkipAndTake(_collection).SingleOrDefault(); + public IEnumerable ToEnumerable() => ApplySkipAndTake(_collection).ToEnumerable(); + public List ToList() => ApplySkipAndTake(_collection).ToList(); + public T[] ToArray() => ApplySkipAndTake(_collection).ToArray(); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Database/LiteDb/QueryableResult.cs b/src/AppCommon/FileTime.App.Database/LiteDb/QueryableResult.cs new file mode 100644 index 0000000..8645cf8 --- /dev/null +++ b/src/AppCommon/FileTime.App.Database/LiteDb/QueryableResult.cs @@ -0,0 +1,23 @@ +using LiteDB; + +namespace FileTime.App.Database.LiteDb; + +public class QueryableResult : IQueryableResult +{ + private readonly ILiteQueryableResult _queryableResult; + + public QueryableResult(ILiteQueryableResult queryableResult) + { + _queryableResult = queryableResult; + } + + public int Count() => _queryableResult.Count(); + public bool Exists() => _queryableResult.Exists(); + public T First() => _queryableResult.First(); + public T FirstOrDefault() => _queryableResult.FirstOrDefault(); + public T Single() => _queryableResult.Single(); + public T SingleOrDefault() => _queryableResult.SingleOrDefault(); + public IEnumerable ToEnumerable() => _queryableResult.ToEnumerable(); + public List ToList() => _queryableResult.ToList(); + public T[] ToArray() => _queryableResult.ToArray(); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Database/LiteDb/Transaction.cs b/src/AppCommon/FileTime.App.Database/LiteDb/Transaction.cs new file mode 100644 index 0000000..b69d29c --- /dev/null +++ b/src/AppCommon/FileTime.App.Database/LiteDb/Transaction.cs @@ -0,0 +1,25 @@ +using LiteDB; + +namespace FileTime.App.Database.LiteDb; + +public class Transaction : ITransaction +{ + private readonly ILiteDatabase _liteDatabase; + + public Transaction(ILiteDatabase liteDatabase) + { + _liteDatabase = liteDatabase; + } + + public ValueTask CommitAsync() + { + _liteDatabase.Commit(); + return ValueTask.CompletedTask; + } + + public void Rollback() => _liteDatabase.Rollback(); + + public IUpdatable GetCollection(string collectionName) => new Updatable(_liteDatabase.GetCollection(collectionName)); + + public void Dispose() => _liteDatabase.Dispose(); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Database/LiteDb/Updatable.cs b/src/AppCommon/FileTime.App.Database/LiteDb/Updatable.cs new file mode 100644 index 0000000..2badeed --- /dev/null +++ b/src/AppCommon/FileTime.App.Database/LiteDb/Updatable.cs @@ -0,0 +1,19 @@ +using LiteDB; + +namespace FileTime.App.Database.LiteDb; + +public class Updatable : IUpdatable +{ + private readonly ILiteCollection _collection; + + public Updatable(ILiteCollection collection) + { + _collection = collection; + } + + public void Insert(T item) => _collection.Insert(item); + + public void Update(T item) => _collection.Update(item); + + public void Delete(int id) => _collection.Delete(new BsonValue(id)); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Database/Startup.cs b/src/AppCommon/FileTime.App.Database/Startup.cs new file mode 100644 index 0000000..3486b32 --- /dev/null +++ b/src/AppCommon/FileTime.App.Database/Startup.cs @@ -0,0 +1,14 @@ +using FileTime.App.Database.LiteDb; +using Microsoft.Extensions.DependencyInjection; + +namespace FileTime.App.Database; + +public static class Startup +{ + public static IServiceCollection AddDatabase(this IServiceCollection services) + { + services.AddSingleton(); + services.AddTransient(); + return services; + } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs b/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs index 5859ae6..c018792 100644 --- a/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs +++ b/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs @@ -2,6 +2,7 @@ using FileTime.App.Core; using FileTime.App.Core.Models; using FileTime.App.Core.Services; using FileTime.App.Core.Services.Persistence; +using FileTime.App.Database; using FileTime.Core; using FileTime.Providers.Local; using FileTime.Providers.LocalAdmin; @@ -26,6 +27,7 @@ public static class DependencyInjection return serviceCollection .AddCoreDependencies() + .AddDatabase() .AddAppCoreDependencies(configuration) .AddLocalProviderServices() .AddLocalAdminProviderServices(configuration) diff --git a/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj b/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj index 074eac4..ab446dc 100644 --- a/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj +++ b/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj @@ -22,6 +22,7 @@ + diff --git a/src/AppCommon/FileTime.App.FrequencyNavigation.Abstractions/Services/IFrequencyNavigationService.cs b/src/AppCommon/FileTime.App.FrequencyNavigation.Abstractions/Services/IFrequencyNavigationService.cs index 9d02cd6..3a612e8 100644 --- a/src/AppCommon/FileTime.App.FrequencyNavigation.Abstractions/Services/IFrequencyNavigationService.cs +++ b/src/AppCommon/FileTime.App.FrequencyNavigation.Abstractions/Services/IFrequencyNavigationService.cs @@ -9,5 +9,5 @@ public interface IFrequencyNavigationService IFrequencyNavigationViewModel? CurrentModal { get; } Task OpenNavigationWindow(); void CloseNavigationWindow(); - IList GetMatchingContainers(string searchText); + ValueTask> GetMatchingContainers(string searchText); } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.FrequencyNavigation/FileTime.App.FrequencyNavigation.csproj b/src/AppCommon/FileTime.App.FrequencyNavigation/FileTime.App.FrequencyNavigation.csproj index 11105f6..e50ccc3 100644 --- a/src/AppCommon/FileTime.App.FrequencyNavigation/FileTime.App.FrequencyNavigation.csproj +++ b/src/AppCommon/FileTime.App.FrequencyNavigation/FileTime.App.FrequencyNavigation.csproj @@ -8,7 +8,9 @@ + + diff --git a/src/AppCommon/FileTime.App.FrequencyNavigation/Models/ContainerFrequencyData.cs b/src/AppCommon/FileTime.App.FrequencyNavigation/Models/ContainerFrequencyData.cs index 884d324..9f3dba9 100644 --- a/src/AppCommon/FileTime.App.FrequencyNavigation/Models/ContainerFrequencyData.cs +++ b/src/AppCommon/FileTime.App.FrequencyNavigation/Models/ContainerFrequencyData.cs @@ -2,6 +2,14 @@ namespace FileTime.App.FrequencyNavigation.Models; public class ContainerFrequencyData { - public int Score { get; set; } = 1; - public DateTime LastAccessed { get; set; } = DateTime.Now; + public string Path { get; } + public int Score { get; set; } + public DateTime LastAccessed { get; set; } + + public ContainerFrequencyData(string path, int score, DateTime lastAccessed) + { + Path = path; + Score = score; + LastAccessed = lastAccessed; + } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.FrequencyNavigation/Services/FrequencyNavigationRepository.cs b/src/AppCommon/FileTime.App.FrequencyNavigation/Services/FrequencyNavigationRepository.cs new file mode 100644 index 0000000..460372a --- /dev/null +++ b/src/AppCommon/FileTime.App.FrequencyNavigation/Services/FrequencyNavigationRepository.cs @@ -0,0 +1,301 @@ +using FileTime.App.Database; +using FileTime.App.FrequencyNavigation.Models; +using FileTime.Core.Models; + +namespace FileTime.App.FrequencyNavigation.Services; + +public class FrequencyNavigationRepository +{ + private class ContainerScore + { + public int Id { get; set; } + public required string Path { get; init; } + public required DateTime LastAccessed { get; set; } + public required int Score { get; set; } + } + + private const string CollectionName = "FrequencyNavigation"; + private const double CacheExpirationInSeconds = 60; + private const double MaxPersistIntervalInSeconds = 60; + + private readonly IDatabaseContext _databaseContext; + private readonly SemaphoreSlim _databaseSemaphore = new(1, 1); + private readonly SemaphoreSlim _cacheSemaphore = new(1, 1); + private readonly List _cachedFrequencyData = new(); + private readonly List _extraCachedFrequencyData = new(); + private DateTime _cachedTime; + private DateTime _lastPersistTime = DateTime.Now; + + public FrequencyNavigationRepository(IDatabaseContext databaseContext) + { + _databaseContext = databaseContext; + } + + public async Task IncreaseContainerScoreAsync(string containerNameString) + { + if (!containerNameString.Contains(Constants.SeparatorChar)) return; + + await _cacheSemaphore.WaitAsync(); + try + { + var frequencyData = _extraCachedFrequencyData.FirstOrDefault(d => d.Path == containerNameString); + if (frequencyData is null) + { + _extraCachedFrequencyData.Add(new ContainerFrequencyData(containerNameString, 1, DateTime.Now)); + } + else + { + frequencyData.Score++; + frequencyData.LastAccessed = DateTime.Now; + } + } + finally + { + _cacheSemaphore.Release(); + } + + await TryPersistExtraFrequencyDataAsync(); + } + + private async Task TryPersistExtraFrequencyDataAsync() + { + await _cacheSemaphore.WaitAsync(); + try + { + if ((DateTime.Now - _lastPersistTime).TotalSeconds < MaxPersistIntervalInSeconds) + { + return; + } + + _lastPersistTime = DateTime.Now; + } + finally + { + _cacheSemaphore.Release(); + } + + await PersistExtraFrequencyDataAsync(); + } + + private async Task PersistExtraFrequencyDataAsync(bool skipCacheLock = false) + { + await _databaseSemaphore.WaitAsync(); + if (!skipCacheLock) + { + await _cacheSemaphore.WaitAsync(); + } + + try + { + using var connection = await _databaseContext.GetConnectionAsync(); + using var transaction = connection.BeginTransaction(); + + var queryCollection = connection.GetCollection(CollectionName); + var updateCollection = transaction.GetCollection(CollectionName); + + var extraCachedFrequencyData = _extraCachedFrequencyData.ToList(); + _extraCachedFrequencyData.Clear(); + foreach (var extraFrequencyData in extraCachedFrequencyData) + { + var currentFrequencyData = queryCollection.FirstOrDefault(d => d.Path == extraFrequencyData.Path); + + if (currentFrequencyData is null) + { + updateCollection.Insert(new ContainerScore + { + Path = extraFrequencyData.Path, + LastAccessed = extraFrequencyData.LastAccessed, + Score = extraFrequencyData.Score + }); + } + else + { + currentFrequencyData.Score += extraFrequencyData.Score; + currentFrequencyData.LastAccessed = extraFrequencyData.LastAccessed; + + updateCollection.Update(currentFrequencyData); + } + } + + await transaction.CommitAsync(); + } + finally + { + _databaseSemaphore.Release(); + if (!skipCacheLock) + { + _cacheSemaphore.Release(); + } + } + } + + public async Task GetAgeSum() + { + await _databaseSemaphore.WaitAsync(); + try + { + using var connection = await _databaseContext.GetConnectionAsync(); + var query = connection.GetCollection(CollectionName); + return query.Query().Select(c => c.Score).ToEnumerable().Sum(); + } + finally + { + _databaseSemaphore.Release(); + } + } + + public async Task AgeContainersAsync() + { + await PersistExtraFrequencyDataAsync(); + + await _databaseSemaphore.WaitAsync(); + try + { + using var connection = await _databaseContext.GetConnectionAsync(); + using var transaction = connection.BeginTransaction(); + + var queryCollection = connection.GetCollection(CollectionName); + var updateCollection = transaction.GetCollection(CollectionName); + + var now = DateTime.Now; + foreach (var container in queryCollection.ToEnumerable()) + { + var newScore = (int) Math.Floor(container.Score * 0.9); + if (newScore > 0) + { + container.Score = newScore; + container.LastAccessed = now; + updateCollection.Update(container); + } + else + { + updateCollection.Delete(container.Id); + } + } + + await transaction.CommitAsync(); + } + finally + { + _databaseSemaphore.Release(); + } + } + + public async ValueTask> GetContainersAsync() + { + await _cacheSemaphore.WaitAsync(); + try + { + if ((DateTime.Now - _cachedTime).TotalSeconds > CacheExpirationInSeconds) + { + await PersistExtraFrequencyDataAsync(skipCacheLock: true); + var containerScores = await GetContainersFromDatabaseAsync(); + _cachedFrequencyData.Clear(); + _cachedFrequencyData.AddRange(containerScores); + _cachedTime = DateTime.Now; + + return _cachedFrequencyData.ToArray(); + } + + var frequencyData = new List(_cachedFrequencyData); + + foreach (var extraFrequencyData in _extraCachedFrequencyData) + { + var existingFrequencyData = frequencyData + .FirstOrDefault(f => f.Path == extraFrequencyData.Path); + if (existingFrequencyData is null) + { + frequencyData.Add(extraFrequencyData); + } + else + { + existingFrequencyData.Score += extraFrequencyData.Score; + existingFrequencyData.LastAccessed = extraFrequencyData.LastAccessed; + } + } + + + return frequencyData; + } + finally + { + _cacheSemaphore.Release(); + } + } + + private async Task> GetContainersFromDatabaseAsync() + { + await _databaseSemaphore.WaitAsync(); + try + { + using var connection = await _databaseContext.GetConnectionAsync(); + + var repo = connection.GetCollection(CollectionName); + + return repo + .ToEnumerable() + .Select(c => new ContainerFrequencyData(c.Path, c.Score, c.LastAccessed)) + .ToList(); + } + finally + { + _databaseSemaphore.Release(); + } + } + + public async Task AddContainerAsync(ContainerFrequencyData containerFrequencyData) + { + await _databaseSemaphore.WaitAsync(); + try + { + using var connection = await _databaseContext.GetConnectionAsync(); + using var transaction = connection.BeginTransaction(); + + var repo = transaction.GetCollection(CollectionName); + + repo.Insert(new ContainerScore + { + Path = containerFrequencyData.Path, + LastAccessed = containerFrequencyData.LastAccessed, + Score = containerFrequencyData.Score + }); + + await transaction.CommitAsync(); + } + finally + { + _databaseSemaphore.Release(); + } + } + + public async Task AddContainersAsync(IEnumerable containerFrequencyData) + { + await _databaseSemaphore.WaitAsync(); + try + { + var containerFrequencyDataList = containerFrequencyData.ToList(); + if (containerFrequencyDataList.Count == 0) return; + + using var connection = await _databaseContext.GetConnectionAsync(); + using var transaction = connection.BeginTransaction(); + + var repo = transaction.GetCollection(CollectionName); + + foreach (var frequencyData in containerFrequencyDataList) + { + repo.Insert(new ContainerScore + { + Path = frequencyData.Path, + LastAccessed = frequencyData.LastAccessed, + Score = frequencyData.Score + }); + } + + await transaction.CommitAsync(); + } + finally + { + _databaseSemaphore.Release(); + } + } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.FrequencyNavigation/Services/FrequencyNavigationService.cs b/src/AppCommon/FileTime.App.FrequencyNavigation/Services/FrequencyNavigationService.cs index db3aa47..e857ceb 100644 --- a/src/AppCommon/FileTime.App.FrequencyNavigation/Services/FrequencyNavigationService.cs +++ b/src/AppCommon/FileTime.App.FrequencyNavigation/Services/FrequencyNavigationService.cs @@ -9,35 +9,38 @@ using FileTime.Core.Models.Extensions; using FileTime.Core.Services; using Microsoft.Extensions.Logging; using PropertyChanged.SourceGenerator; +using static System.DeferTools; namespace FileTime.App.FrequencyNavigation.Services; -public partial class FrequencyNavigationService : IFrequencyNavigationService, IStartupHandler, IExitHandler +public partial class FrequencyNavigationService : IFrequencyNavigationService, IStartupHandler { private const int MaxAge = 10_000; + private const int AgingPersistenceAfterSeconds = 60; - private DateTime _lastSave = DateTime.Now; private readonly ILogger _logger; private readonly IModalService _modalService; - private readonly SemaphoreSlim _saveLock = new(1, 1); - private Dictionary _containerScores = new(); + private readonly FrequencyNavigationRepository _frequencyNavigationRepository; private readonly DeclarativeProperty _showWindow = new(false); - private readonly string _dbPath; - private bool _loaded; - - [Notify] IFrequencyNavigationViewModel? _currentModal; + private readonly string _oldDbPath; + private DateTime _lastTryAging = DateTime.Now; + + [Notify] private IFrequencyNavigationViewModel? _currentModal; IDeclarativeProperty IFrequencyNavigationService.ShowWindow => _showWindow; public FrequencyNavigationService( ITabEvents tabEvents, + IModalService modalService, + FrequencyNavigationRepository frequencyNavigationRepository, IApplicationSettings applicationSettings, - ILogger logger, - IModalService modalService) + ILogger logger) { _logger = logger; _modalService = modalService; - _dbPath = Path.Combine(applicationSettings.AppDataRoot, "frequencyNavigationScores.json"); + _frequencyNavigationRepository = frequencyNavigationRepository; tabEvents.LocationChanged += OnTabLocationChanged; + + _oldDbPath = Path.Combine(applicationSettings.AppDataRoot, "frequencyNavigationScores.json"); } async void OnTabLocationChanged(object? sender, TabLocationChanged e) @@ -70,7 +73,6 @@ public partial class FrequencyNavigationService : IFrequencyNavigationService, I private async Task IncreaseContainerScore(IContainer container) { - await _saveLock.WaitAsync(); try { if (container.GetExtension() is not null) return; @@ -78,156 +80,87 @@ public partial class FrequencyNavigationService : IFrequencyNavigationService, I var containerNameString = container.FullName?.Path; if (containerNameString is null) return; - if (_containerScores.ContainsKey(containerNameString)) - { - _containerScores[containerNameString].Score++; - _containerScores[containerNameString].LastAccessed = DateTime.Now; - } - else - { - _containerScores.Add(containerNameString, new ContainerFrequencyData()); - } + await _frequencyNavigationRepository.IncreaseContainerScoreAsync(containerNameString); } catch (Exception e) { _logger.LogError(e, "Error increasing container score"); } - finally - { - _saveLock.Release(); - } - try - { - if (TryAgeContainerScores() || DateTime.Now - _lastSave > TimeSpan.FromMinutes(5)) - { - } - - //TODO: move to if above - await SaveStateAsync(); - } - catch (Exception e) - { - _logger.LogError(e, "Error aging container scores"); - } + await TryAgeContainersAsync(); } - private bool TryAgeContainerScores() + private async Task TryAgeContainersAsync() { - if (_containerScores.Select(c => c.Value.Score).Sum() < MaxAge) - return false; - - AgeContainerScores(); - return true; - } - - private void AgeContainerScores() - { - var now = DateTime.Now; - var itemsToRemove = new List(); - foreach (var container in _containerScores) + if ((DateTime.Now - _lastTryAging).TotalSeconds >= AgingPersistenceAfterSeconds) { - var newScore = (int) Math.Floor(container.Value.Score * 0.9); - if (newScore > 0) - { - container.Value.Score = newScore; - } - else - { - itemsToRemove.Add(container.Key); - } + return; } - foreach (var itemToRemove in itemsToRemove) + if (await _frequencyNavigationRepository.GetAgeSum() < MaxAge) { - _containerScores.Remove(itemToRemove); + _lastTryAging = DateTime.Now; + return; } + + await _frequencyNavigationRepository.AgeContainersAsync(); } - public IList GetMatchingContainers(string searchText) + public async ValueTask> GetMatchingContainers(string searchText) { if (string.IsNullOrWhiteSpace(searchText)) return new List(); - _saveLock.Wait(); - try - { - return _containerScores - .Where(c => - { - var searchTerms = searchText.Split(' '); - return searchTerms.All(s => c.Key.Contains(s, StringComparison.OrdinalIgnoreCase)); - }) - .OrderByDescending(c => GetWeightedScore(c.Value.Score, c.Value.LastAccessed)) - .Select(c => c.Key) - .ToList(); - } - finally - { - _saveLock.Release(); - } + var frequencyData = await _frequencyNavigationRepository.GetContainersAsync(); + var searchTerms = searchText.Split(' '); + + return frequencyData + .Where(c => searchTerms.All(s => c.Path.Contains(s, StringComparison.OrdinalIgnoreCase))) + .OrderByDescending(c => GetWeightedScore(c.Score, c.LastAccessed)) + .Select(c => c.Path) + .ToList(); } - private int GetWeightedScore(int score, DateTime lastAccess) + private static int GetWeightedScore(int score, DateTime lastAccess) { var now = DateTime.Now; var timeSinceLastAccess = now - lastAccess; return timeSinceLastAccess.TotalHours switch { - < 1 => score *= 4, - < 24 => score *= 2, - < 168 => score /= 2, - _ => score /= 4 + < 1 => score * 4, + < 24 => score * 2, + < 168 => score / 2, + _ => score / 4 }; } + // TODO: remove this migration at some time in the future public async Task InitAsync() { - await LoadStateAsync(); - _loaded = true; - } + if (!File.Exists(_oldDbPath)) return; + using var _ = Defer(() => File.Delete(_oldDbPath)); - private async Task LoadStateAsync() - { - if (!File.Exists(_dbPath)) + if ((await _frequencyNavigationRepository.GetContainersAsync()).Any()) return; + + await using var dbStream = File.OpenRead(_oldDbPath); + var containerScores = await JsonSerializer.DeserializeAsync>(dbStream); + if (containerScores is null || containerScores.Count == 0) + { return; + } - try - { - await _saveLock.WaitAsync(); - _logger.LogTrace("Loading frequency navigation state from file '{DbPath}'", _dbPath); - await using var dbStream = File.OpenRead(_dbPath); - var containerScores = await JsonSerializer.DeserializeAsync>(dbStream); - if (containerScores is null) return; + var frequencyData = containerScores + .Select( + c => new ContainerFrequencyData(c.Key, c.Value.Score, c.Value.LastAccessed) + ) + .Where(c => c.Path.Contains(Constants.SeparatorChar)); - _containerScores = containerScores; - } - catch (Exception e) - { - _logger.LogError(e, "Error loading frequency navigation state"); - } - finally - { - _saveLock.Release(); - } + await _frequencyNavigationRepository.AddContainersAsync(frequencyData); } - public async Task ExitAsync(CancellationToken token = default) => await SaveStateAsync(token); - - private async Task SaveStateAsync(CancellationToken token = default) + private class OldContainerFrequencyData { - if(!_loaded) return; - await _saveLock.WaitAsync(token); - try - { - _lastSave = DateTime.Now; - await using var dbStream = File.Create(_dbPath); - await JsonSerializer.SerializeAsync(dbStream, _containerScores); - dbStream.Flush(); - } - finally - { - _saveLock.Release(); - } + public int Score { get; set; } = 1; + public DateTime LastAccessed { get; set; } = DateTime.Now; } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.FrequencyNavigation/Startup.cs b/src/AppCommon/FileTime.App.FrequencyNavigation/Startup.cs index 8330d72..3363473 100644 --- a/src/AppCommon/FileTime.App.FrequencyNavigation/Startup.cs +++ b/src/AppCommon/FileTime.App.FrequencyNavigation/Startup.cs @@ -14,7 +14,7 @@ public static class Startup services.AddSingleton(); services.TryAddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(sp => sp.GetRequiredService()); - services.AddSingleton(sp => sp.GetRequiredService()); + services.TryAddTransient(); return services; } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.FrequencyNavigation/ViewModels/FrequencyNavigationViewModel.cs b/src/AppCommon/FileTime.App.FrequencyNavigation/ViewModels/FrequencyNavigationViewModel.cs index e17720d..6d7f05d 100644 --- a/src/AppCommon/FileTime.App.FrequencyNavigation/ViewModels/FrequencyNavigationViewModel.cs +++ b/src/AppCommon/FileTime.App.FrequencyNavigation/ViewModels/FrequencyNavigationViewModel.cs @@ -6,6 +6,7 @@ using FileTime.App.FuzzyPanel; using FileTime.Core.Models; using FileTime.Core.Timeline; using GeneralInputKey; +using Microsoft.Extensions.Logging; namespace FileTime.App.FrequencyNavigation.ViewModels; @@ -18,7 +19,8 @@ public class FrequencyNavigationViewModel : FuzzyPanelViewModel, IFreque public FrequencyNavigationViewModel( IFrequencyNavigationService frequencyNavigationService, IUserCommandHandlerService userCommandHandlerService, - ITimelessContentProvider timelessContentProvider) + ITimelessContentProvider timelessContentProvider, + ILogger logger) : base(logger) { _frequencyNavigationService = frequencyNavigationService; _userCommandHandlerService = userCommandHandlerService; @@ -64,8 +66,8 @@ public class FrequencyNavigationViewModel : FuzzyPanelViewModel, IFreque return false; } - public override void UpdateFilteredMatches() => - FilteredMatches = new List(_frequencyNavigationService.GetMatchingContainers(SearchText)); + public override async Task UpdateFilteredMatches() + => FilteredMatches = new List(await _frequencyNavigationService.GetMatchingContainers(SearchText)); string IModalViewModel.Name => "FrequencyNavigation"; } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.FuzzyPanel.Abstraction/IFuzzyPanelViewModel.cs b/src/AppCommon/FileTime.App.FuzzyPanel.Abstraction/IFuzzyPanelViewModel.cs index d1a205a..5e6f087 100644 --- a/src/AppCommon/FileTime.App.FuzzyPanel.Abstraction/IFuzzyPanelViewModel.cs +++ b/src/AppCommon/FileTime.App.FuzzyPanel.Abstraction/IFuzzyPanelViewModel.cs @@ -7,6 +7,6 @@ public interface IFuzzyPanelViewModel where TItem : class List FilteredMatches { get; } TItem? SelectedItem { get; } string SearchText { get; set; } - void UpdateFilteredMatches(); + Task UpdateFilteredMatches(); Task HandleKeyDown(GeneralKeyEventArgs keyEventArgs); } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.FuzzyPanel/FileTime.App.FuzzyPanel.csproj b/src/AppCommon/FileTime.App.FuzzyPanel/FileTime.App.FuzzyPanel.csproj index b9296f8..fbdcff0 100644 --- a/src/AppCommon/FileTime.App.FuzzyPanel/FileTime.App.FuzzyPanel.csproj +++ b/src/AppCommon/FileTime.App.FuzzyPanel/FileTime.App.FuzzyPanel.csproj @@ -7,6 +7,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AppCommon/FileTime.App.FuzzyPanel/FuzzyPanelViewModel.cs b/src/AppCommon/FileTime.App.FuzzyPanel/FuzzyPanelViewModel.cs index 81ae21e..9bbf697 100644 --- a/src/AppCommon/FileTime.App.FuzzyPanel/FuzzyPanelViewModel.cs +++ b/src/AppCommon/FileTime.App.FuzzyPanel/FuzzyPanelViewModel.cs @@ -1,21 +1,24 @@ using System.ComponentModel; using DeclarativeProperty; using GeneralInputKey; +using Microsoft.Extensions.Logging; using PropertyChanged.SourceGenerator; namespace FileTime.App.FuzzyPanel; public abstract partial class FuzzyPanelViewModel : IFuzzyPanelViewModel where TItem : class { + private readonly ILogger _logger; private readonly Func _itemEquality; private string _searchText = String.Empty; - [Notify(set: Setter.Protected)] private IDeclarativeProperty _showWindow; - [Notify(set: Setter.Protected)] private List _filteredMatches; + [Notify(set: Setter.Protected)] private IDeclarativeProperty _showWindow = null!; + [Notify(set: Setter.Protected)] private List _filteredMatches = null!; [Notify(set: Setter.Protected)] private TItem? _selectedItem; - protected FuzzyPanelViewModel(Func? itemEquality = null) + protected FuzzyPanelViewModel(ILogger logger, Func? itemEquality = null) { + _logger = logger; _itemEquality = itemEquality ?? ((a, b) => a == b); } @@ -29,7 +32,15 @@ public abstract partial class FuzzyPanelViewModel : IFuzzyPanelViewModel< _searchText = value; OnPropertyChanged(new PropertyChangedEventArgs(nameof(SearchText))); - UpdateFilteredMatches(); + Update(value); + } + } + + private async void Update(string value) + { + try + { + await UpdateFilteredMatches(); if (string.IsNullOrWhiteSpace(value)) { SelectedItem = null; @@ -39,6 +50,10 @@ public abstract partial class FuzzyPanelViewModel : IFuzzyPanelViewModel< UpdateSelectedItem(); } } + catch(Exception e) + { + _logger.LogError(e, "Error while updating filtered matches"); + } } private void UpdateSelectedItem() @@ -50,7 +65,7 @@ public abstract partial class FuzzyPanelViewModel : IFuzzyPanelViewModel< : null; } - public abstract void UpdateFilteredMatches(); + public abstract Task UpdateFilteredMatches(); public virtual Task HandleKeyDown(GeneralKeyEventArgs keyEventArgs) { diff --git a/src/FileTime.sln b/src/FileTime.sln index e395723..5244025 100644 --- a/src/FileTime.sln +++ b/src/FileTime.sln @@ -151,6 +151,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Tools.VirtualDiskS EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Tools.VirtualDiskSources.Abstractions", "Tools\FileTime.Tools.VirtualDiskSources.Abstractions\FileTime.Tools.VirtualDiskSources.Abstractions.csproj", "{53E5B762-B620-4106-B481-31A478A1E14F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.Database", "AppCommon\FileTime.App.Database\FileTime.App.Database.csproj", "{610C9140-4B05-46A2-BFF4-501049EBA25E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.Database.Abstractions", "AppCommon\FileTime.App.Database.Abstractions\FileTime.App.Database.Abstractions.csproj", "{635DC6E5-A762-409E-BBCC-CE1D29F4DDB9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -417,6 +421,14 @@ Global {53E5B762-B620-4106-B481-31A478A1E14F}.Debug|Any CPU.Build.0 = Debug|Any CPU {53E5B762-B620-4106-B481-31A478A1E14F}.Release|Any CPU.ActiveCfg = Release|Any CPU {53E5B762-B620-4106-B481-31A478A1E14F}.Release|Any CPU.Build.0 = Release|Any CPU + {610C9140-4B05-46A2-BFF4-501049EBA25E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {610C9140-4B05-46A2-BFF4-501049EBA25E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {610C9140-4B05-46A2-BFF4-501049EBA25E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {610C9140-4B05-46A2-BFF4-501049EBA25E}.Release|Any CPU.Build.0 = Release|Any CPU + {635DC6E5-A762-409E-BBCC-CE1D29F4DDB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {635DC6E5-A762-409E-BBCC-CE1D29F4DDB9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {635DC6E5-A762-409E-BBCC-CE1D29F4DDB9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {635DC6E5-A762-409E-BBCC-CE1D29F4DDB9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -488,6 +500,8 @@ Global {25AA9F04-EEEE-49C4-870B-CDFF71717687} = {778AAF38-20FF-438C-A9C3-60850C8B5A27} {DBCB10AD-9647-46AB-B9A1-3ACB9BCA46B9} = {8C3CFEFE-78A5-4940-B388-D15FCE02ECE9} {53E5B762-B620-4106-B481-31A478A1E14F} = {8C3CFEFE-78A5-4940-B388-D15FCE02ECE9} + {610C9140-4B05-46A2-BFF4-501049EBA25E} = {A5291117-3001-498B-AC8B-E14F71F72570} + {635DC6E5-A762-409E-BBCC-CE1D29F4DDB9} = {A5291117-3001-498B-AC8B-E14F71F72570} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF} diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Startup.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Startup.cs index 6a747f6..4a7423c 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Startup.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Startup.cs @@ -4,6 +4,7 @@ using Avalonia.Input; using FileTime.App.Core.Configuration; using FileTime.App.Core.Services; using FileTime.App.Core.ViewModels; +using FileTime.App.Database.LiteDb; using FileTime.Core.Interactions; using FileTime.GuiApp.App.CloudDrives; using FileTime.GuiApp.App.Configuration;