diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..d7694ae --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "mcp__serena__list_dir", + "Bash(dotnet build:*)" + ] + } +} diff --git a/src/AStar.Dev.File.App/AStar.Dev.File.App.csproj b/src/AStar.Dev.File.App/AStar.Dev.File.App.csproj index a0590be..ee69c06 100644 --- a/src/AStar.Dev.File.App/AStar.Dev.File.App.csproj +++ b/src/AStar.Dev.File.App/AStar.Dev.File.App.csproj @@ -13,6 +13,10 @@ + + + + @@ -29,7 +33,8 @@ All - + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AStar.Dev.File.App/FodyWeavers.xml b/src/AStar.Dev.File.App/FodyWeavers.xml new file mode 100644 index 0000000..63fc148 --- /dev/null +++ b/src/AStar.Dev.File.App/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/AStar.Dev.File.App/Program.cs b/src/AStar.Dev.File.App/Program.cs index a83f283..9f6a371 100644 --- a/src/AStar.Dev.File.App/Program.cs +++ b/src/AStar.Dev.File.App/Program.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.ReactiveUI; using System; namespace AStar.Dev.File.App; @@ -17,5 +18,6 @@ public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() .WithInterFont() - .LogToTrace(); + .LogToTrace() + .UseReactiveUI(); } diff --git a/src/AStar.Dev.File.App/ViewModels/DeletePendingViewModel.cs b/src/AStar.Dev.File.App/ViewModels/DeletePendingViewModel.cs index e509385..f56060c 100644 --- a/src/AStar.Dev.File.App/ViewModels/DeletePendingViewModel.cs +++ b/src/AStar.Dev.File.App/ViewModels/DeletePendingViewModel.cs @@ -1,34 +1,35 @@ using AStar.Dev.File.App.Data; using AStar.Dev.File.App.Services; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; using Microsoft.EntityFrameworkCore; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; using System; using System.Collections.ObjectModel; using System.Linq; +using System.Reactive; using System.Threading.Tasks; namespace AStar.Dev.File.App.ViewModels; -public partial class DeletePendingViewModel : ViewModelBase +public class DeletePendingViewModel : ViewModelBase { private readonly IDbContextFactory _dbContextFactory; private readonly IFileDeleteService _fileDeleteService; private readonly IFileViewerService _fileViewerService; - [ObservableProperty] - private bool _isDeleting; - - [ObservableProperty] - private int _pendingDeleteCount; - - [ObservableProperty] - private string _statusMessage = string.Empty; + [Reactive] public bool IsDeleting { get; set; } + [Reactive] public int PendingDeleteCount { get; set; } + [Reactive] public string StatusMessage { get; set; } = string.Empty; public ObservableCollection PendingDeleteFiles { get; } = []; public event Action? ViewFileRequested; + public ReactiveCommand TogglePendingDeleteCommand { get; } + public ReactiveCommand DeleteAllCommand { get; } + public ReactiveCommand ClearMarkingsCommand { get; } + public ReactiveCommand ViewFileCommand { get; } + public DeletePendingViewModel( IDbContextFactory dbContextFactory, IFileDeleteService fileDeleteService, @@ -39,10 +40,18 @@ public DeletePendingViewModel( _fileViewerService = fileViewerService; _fileViewerService.FileViewRequested += item => ViewFileRequested?.Invoke(item); + TogglePendingDeleteCommand = ReactiveCommand.CreateFromTask(TogglePendingDelete); + + var canDeleteAll = this.WhenAnyValue(x => x.IsDeleting, x => x.PendingDeleteCount, + (deleting, count) => !deleting && count > 0); + DeleteAllCommand = ReactiveCommand.CreateFromTask(DeleteAll, canDeleteAll); + + ClearMarkingsCommand = ReactiveCommand.CreateFromTask(ClearMarkings); + ViewFileCommand = ReactiveCommand.CreateFromTask(ViewFile); + _ = LoadPendingFilesAsync(); } - [RelayCommand] private async Task TogglePendingDelete(ScannedFileDisplayItem? item) { if (item is null) return; @@ -58,7 +67,6 @@ private async Task TogglePendingDelete(ScannedFileDisplayItem? item) await LoadPendingFilesAsync(); } - [RelayCommand(CanExecute = nameof(CanDeleteAll))] private async Task DeleteAll() { if (PendingDeleteFiles.Count == 0) @@ -95,9 +103,6 @@ private async Task DeleteAll() } } - private bool CanDeleteAll() => !IsDeleting && PendingDeleteFiles.Count > 0; - - [RelayCommand] private async Task ClearMarkings() { if (PendingDeleteFiles.Count == 0) @@ -116,7 +121,6 @@ private async Task ClearMarkings() await LoadPendingFilesAsync(); } - [RelayCommand] private async Task ViewFile(ScannedFileDisplayItem? item) { await _fileViewerService.ViewFileAsync(item); @@ -137,7 +141,6 @@ private async Task LoadPendingFilesAsync() files.ForEach(file => PendingDeleteFiles.Add(new ScannedFileDisplayItem(file))); PendingDeleteCount = PendingDeleteFiles.Count; - DeleteAllCommand.NotifyCanExecuteChanged(); } catch (Exception ex) { diff --git a/src/AStar.Dev.File.App/ViewModels/MainWindowViewModel.cs b/src/AStar.Dev.File.App/ViewModels/MainWindowViewModel.cs index 6decac8..ccac9d4 100644 --- a/src/AStar.Dev.File.App/ViewModels/MainWindowViewModel.cs +++ b/src/AStar.Dev.File.App/ViewModels/MainWindowViewModel.cs @@ -1,349 +1,337 @@ -using AStar.Dev.File.App.Data; -using AStar.Dev.File.App.Models; -using AStar.Dev.File.App.Services; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using Microsoft.EntityFrameworkCore; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace AStar.Dev.File.App.ViewModels; - -public partial class MainWindowViewModel : ViewModelBase -{ - private readonly IFileScannerService _fileScannerService; - private readonly IFolderPickerService _folderPickerService; - private readonly IFileViewerService _fileViewerService; - private readonly IDbContextFactory _dbContextFactory; - private CancellationTokenSource? _cts; - private bool _suppressPageReload; - - public static IReadOnlyList PageSizes { get; } = [25, 50, 75, 100, 125, 150, 175, 200]; - - [ObservableProperty] - [NotifyPropertyChangedFor(nameof(CanScan))] - [NotifyCanExecuteChangedFor(nameof(StartScanCommand))] - [NotifyCanExecuteChangedFor(nameof(LoadFromDatabaseCommand))] - private string _selectedFolderPath = string.Empty; - - [ObservableProperty] - [NotifyPropertyChangedFor(nameof(CanScan))] - [NotifyCanExecuteChangedFor(nameof(StartScanCommand))] - [NotifyCanExecuteChangedFor(nameof(SelectFolderCommand))] - [NotifyCanExecuteChangedFor(nameof(CancelCommand))] - private bool _isScanning; - - [ObservableProperty] - private string _currentScanFolder = string.Empty; - - [ObservableProperty] - private int _totalFilesProcessed; - - [ObservableProperty] - [NotifyPropertyChangedFor(nameof(TotalPages), nameof(PagingInfo))] - [NotifyCanExecuteChangedFor(nameof(FirstPageCommand))] - [NotifyCanExecuteChangedFor(nameof(PreviousPageCommand))] - [NotifyCanExecuteChangedFor(nameof(NextPageCommand))] - [NotifyCanExecuteChangedFor(nameof(LastPageCommand))] - private int _pageSize = 50; - - [ObservableProperty] - [NotifyPropertyChangedFor(nameof(PagingInfo))] - [NotifyCanExecuteChangedFor(nameof(FirstPageCommand))] - [NotifyCanExecuteChangedFor(nameof(PreviousPageCommand))] - [NotifyCanExecuteChangedFor(nameof(NextPageCommand))] - [NotifyCanExecuteChangedFor(nameof(LastPageCommand))] - private int _currentPage = 1; - - [ObservableProperty] - [NotifyPropertyChangedFor(nameof(TotalPages), nameof(PagingInfo))] - [NotifyCanExecuteChangedFor(nameof(NextPageCommand))] - [NotifyCanExecuteChangedFor(nameof(LastPageCommand))] - private int _totalFileCount; - - private bool _showDuplicatesOnly; - - public bool ShowDuplicatesOnly - { - get => _showDuplicatesOnly; - set - { - if (SetProperty(ref _showDuplicatesOnly, value)) - { - _suppressPageReload = true; - CurrentPage = 1; - _suppressPageReload = false; - _ = LoadScannedFilesAsync(); - } - } - } - - [RelayCommand] - private void ToggleDuplicatesOnly() - { - ShowDuplicatesOnly = !ShowDuplicatesOnly; - } - - public int TotalPages => TotalFileCount == 0 ? 1 : (int)Math.Ceiling((double)TotalFileCount / PageSize); - - public string PagingInfo => $"PAGE {CurrentPage} OF {TotalPages} [{TotalFileCount} FILES]"; - - public bool CanScan => !IsScanning && !string.IsNullOrWhiteSpace(SelectedFolderPath); - - public ObservableCollection StatusMessages { get; } = []; - public ObservableCollection ScannedFiles { get; } = []; - - public event Action? ViewFileRequested; - public event Action? OpenDeleteWindowRequested; - - public MainWindowViewModel( - IFileScannerService fileScannerService, - IFolderPickerService folderPickerService, - IFileViewerService fileViewerService, - IDbContextFactory dbContextFactory) - { - _fileScannerService = fileScannerService; - _folderPickerService = folderPickerService; - _fileViewerService = fileViewerService; +using AStar.Dev.File.App.Data; +using AStar.Dev.File.App.Models; +using AStar.Dev.File.App.Services; +using Microsoft.EntityFrameworkCore; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AStar.Dev.File.App.ViewModels; + +public class MainWindowViewModel : ViewModelBase +{ + private readonly IFileScannerService _fileScannerService; + private readonly IFolderPickerService _folderPickerService; + private readonly IFileViewerService _fileViewerService; + private readonly IDbContextFactory _dbContextFactory; + private CancellationTokenSource? _cts; + private bool _suppressPageReload; + + public static IReadOnlyList PageSizes { get; } = [25, 50, 75, 100, 125, 150, 175, 200]; + + [Reactive] public string SelectedFolderPath { get; set; } = string.Empty; + [Reactive] public bool IsScanning { get; set; } + [Reactive] public string CurrentScanFolder { get; set; } = string.Empty; + [Reactive] public int TotalFilesProcessed { get; set; } + [Reactive] public int PageSize { get; set; } = 50; + [Reactive] public int CurrentPage { get; set; } = 1; + [Reactive] public int TotalFileCount { get; set; } + [Reactive] public bool ShowDuplicatesOnly { get; set; } + + private readonly ObservableAsPropertyHelper _totalPages; + public int TotalPages => _totalPages.Value; + + private readonly ObservableAsPropertyHelper _pagingInfo; + public string PagingInfo => _pagingInfo.Value; + + public ObservableCollection StatusMessages { get; } = []; + public ObservableCollection ScannedFiles { get; } = []; + + public event Action? ViewFileRequested; + public event Action? OpenDeleteWindowRequested; + + public ReactiveCommand SelectFolderCommand { get; } + public ReactiveCommand StartScanCommand { get; } + public ReactiveCommand CancelCommand { get; } + public ReactiveCommand LoadFromDatabaseCommand { get; } + public ReactiveCommand OpenDeleteWindowCommand { get; } + public ReactiveCommand ToggleDuplicatesOnlyCommand { get; } + public ReactiveCommand TogglePendingDeleteCommand { get; } + public ReactiveCommand ViewFileCommand { get; } + public ReactiveCommand FirstPageCommand { get; } + public ReactiveCommand PreviousPageCommand { get; } + public ReactiveCommand NextPageCommand { get; } + public ReactiveCommand LastPageCommand { get; } + + public MainWindowViewModel( + IFileScannerService fileScannerService, + IFolderPickerService folderPickerService, + IFileViewerService fileViewerService, + IDbContextFactory dbContextFactory) + { + _fileScannerService = fileScannerService; + _folderPickerService = folderPickerService; + _fileViewerService = fileViewerService; _dbContextFactory = dbContextFactory; - // Subscribe to file viewer service events + // Computed properties + _totalPages = this.WhenAnyValue(x => x.TotalFileCount, x => x.PageSize, + (count, size) => count == 0 ? 1 : (int)Math.Ceiling((double)count / size)) + .ToProperty(this, x => x.TotalPages, initialValue: 1); + + _pagingInfo = this.WhenAnyValue(x => x.CurrentPage, x => x.TotalFileCount, x => x.PageSize, + (page, count, size) => + { + int total = count == 0 ? 1 : (int)Math.Ceiling((double)count / size); + return $"PAGE {page} OF {total} [{count} FILES]"; + }) + .ToProperty(this, x => x.PagingInfo, initialValue: "PAGE 1 OF 1 [0 FILES]"); + + // Commands + var canSelectFolder = this.WhenAnyValue(x => x.IsScanning, scanning => !scanning); + SelectFolderCommand = ReactiveCommand.CreateFromTask(SelectFolder, canSelectFolder); + + var canScan = this.WhenAnyValue(x => x.IsScanning, x => x.SelectedFolderPath, + (scanning, path) => !scanning && !string.IsNullOrWhiteSpace(path)); + StartScanCommand = ReactiveCommand.CreateFromTask(StartScan, canScan); + + var canCancel = this.WhenAnyValue(x => x.IsScanning); + CancelCommand = ReactiveCommand.Create(Cancel, canCancel); + + var canLoad = this.WhenAnyValue(x => x.SelectedFolderPath, path => !string.IsNullOrWhiteSpace(path)); + LoadFromDatabaseCommand = ReactiveCommand.CreateFromTask(LoadFromDatabase, canLoad); + + OpenDeleteWindowCommand = ReactiveCommand.Create(() => OpenDeleteWindowRequested?.Invoke()); + ToggleDuplicatesOnlyCommand = ReactiveCommand.Create(() => { ShowDuplicatesOnly = !ShowDuplicatesOnly; }); + + TogglePendingDeleteCommand = ReactiveCommand.CreateFromTask(TogglePendingDelete); + ViewFileCommand = ReactiveCommand.CreateFromTask(ViewFile); + + var canGoFirst = this.WhenAnyValue(x => x.CurrentPage, page => page > 1); + FirstPageCommand = ReactiveCommand.Create(() => { CurrentPage = 1; }, canGoFirst); + + var canGoPrevious = this.WhenAnyValue(x => x.CurrentPage, page => page > 1); + PreviousPageCommand = ReactiveCommand.Create(() => { CurrentPage--; }, canGoPrevious); + + var canGoNext = this.WhenAnyValue(x => x.CurrentPage, x => x.TotalPages, + (page, total) => page < total); + NextPageCommand = ReactiveCommand.Create(() => { CurrentPage++; }, canGoNext); + + var canGoLast = this.WhenAnyValue(x => x.CurrentPage, x => x.TotalPages, + (page, total) => page < total); + LastPageCommand = ReactiveCommand.Create(() => { CurrentPage = TotalPages; }, canGoLast); + + // Side-effect subscriptions (replacing partial void OnXChanged hooks) + this.WhenAnyValue(x => x.CurrentPage) + .Skip(1) + .Subscribe(__ => + { + if (!_suppressPageReload) + _ = LoadScannedFilesAsync(); + }); + + this.WhenAnyValue(x => x.PageSize) + .Skip(1) + .Subscribe(__ => + { + _suppressPageReload = true; + CurrentPage = 1; + _suppressPageReload = false; + _ = LoadScannedFilesAsync(); + }); + + this.WhenAnyValue(x => x.ShowDuplicatesOnly) + .Skip(1) + .Subscribe(__ => + { + _suppressPageReload = true; + CurrentPage = 1; + _suppressPageReload = false; + _ = LoadScannedFilesAsync(); + }); + _fileViewerService.FileViewRequested += item => ViewFileRequested?.Invoke(item); - _ = InitializeAsync(); - } - - private async Task InitializeAsync() - { - await LoadSelectedFolderPathAsync(); - } - - private async Task LoadSelectedFolderPathAsync() - { - await using var db = await _dbContextFactory.CreateDbContextAsync(); - var setting = await db.AppSettings.FirstOrDefaultAsync(s => s.Key == "SelectedFolderPath"); - - if (setting is not null && !string.IsNullOrWhiteSpace(setting.Value) && Directory.Exists(setting.Value)) - { - SelectedFolderPath = setting.Value; - } - else - { - var defaultPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)); - SelectedFolderPath = defaultPath; - } - } - - [RelayCommand(CanExecute = nameof(CanSelectFolder))] - private async Task SelectFolder() - { - var path = await _folderPickerService.OpenFolderPickerAsync(); - if (!string.IsNullOrEmpty(path)) - { - SelectedFolderPath = path; - await SaveSelectedFolderPathAsync(path); - } - } - - private async Task SaveSelectedFolderPathAsync(string path) - { - await using var db = await _dbContextFactory.CreateDbContextAsync(); - var setting = await db.AppSettings.FirstOrDefaultAsync(s => s.Key == "SelectedFolderPath"); - - if (setting is not null) - { - setting.Value = path; - } - else - { - db.AppSettings.Add(new Models.AppSetting { Key = "SelectedFolderPath", Value = path }); - } - - await db.SaveChangesAsync(); - } - - private bool CanSelectFolder() => !IsScanning; - - [RelayCommand(CanExecute = nameof(CanLoadFromDatabase))] - private async Task LoadFromDatabase() - { - _suppressPageReload = true; - CurrentPage = 1; - _suppressPageReload = false; - await LoadScannedFilesAsync(); - } - - private bool CanLoadFromDatabase() => !string.IsNullOrWhiteSpace(SelectedFolderPath); - - [RelayCommand] - private void OpenDeleteWindow() - { - OpenDeleteWindowRequested?.Invoke(); - } - - [RelayCommand(CanExecute = nameof(CanScan))] - private async Task StartScan() - { - if (string.IsNullOrWhiteSpace(SelectedFolderPath) || IsScanning) - return; - - IsScanning = true; - StatusMessages.Clear(); - ScannedFiles.Clear(); - TotalFilesProcessed = 0; - TotalFileCount = 0; - CurrentScanFolder = string.Empty; - _suppressPageReload = true; - CurrentPage = 1; - _suppressPageReload = false; - - _cts = new CancellationTokenSource(); - - var progress = new Progress(update => - { - CurrentScanFolder = update.CurrentFolder; - TotalFilesProcessed = update.TotalFilesProcessed; - if (!string.IsNullOrEmpty(update.StatusMessage)) - StatusMessages.Add(update.StatusMessage); - }); - - try - { - await Task.Run(() => _fileScannerService.ScanAsync(SelectedFolderPath, progress, _cts.Token), _cts.Token); - await LoadScannedFilesAsync(); - } - catch (OperationCanceledException) - { - var time = DateTime.Now.ToString("HH:mm:ss"); - StatusMessages.Add($"[{time}] [CANCELLED] Scan cancelled by user."); - } - finally - { - IsScanning = false; - _cts.Dispose(); - _cts = null; - } - } - - [RelayCommand] - private async Task TogglePendingDelete(ScannedFileDisplayItem? item) - { - if (item is null) return; - - await using var db = await _dbContextFactory.CreateDbContextAsync(); - var file = await db.ScannedFiles.FindAsync(item.Id); - if (file is not null) - { - file.PendingDelete = !file.PendingDelete; - await db.SaveChangesAsync(); - item.PendingDelete = file.PendingDelete; - } - } - - [RelayCommand] - private async Task ViewFile(ScannedFileDisplayItem? item) - { - await _fileViewerService.ViewFileAsync(item); - } - - [RelayCommand(CanExecute = nameof(IsScanning))] - private void Cancel() - { - _cts?.Cancel(); - } - - [RelayCommand(CanExecute = nameof(CanGoFirst))] - private void FirstPage() { CurrentPage = 1; } - private bool CanGoFirst() => CurrentPage > 1; - - [RelayCommand(CanExecute = nameof(CanGoPrevious))] - private void PreviousPage() { CurrentPage--; } - private bool CanGoPrevious() => CurrentPage > 1; - - [RelayCommand(CanExecute = nameof(CanGoNext))] - private void NextPage() { CurrentPage++; } - private bool CanGoNext() => CurrentPage < TotalPages; - - [RelayCommand(CanExecute = nameof(CanGoLast))] - private void LastPage() { CurrentPage = TotalPages; } - private bool CanGoLast() => CurrentPage < TotalPages; - - partial void OnCurrentPageChanged(int value) - { - if (!_suppressPageReload) - _ = LoadScannedFilesAsync(); - } - - partial void OnPageSizeChanged(int value) - { - _suppressPageReload = true; - CurrentPage = 1; - _suppressPageReload = false; - _ = LoadScannedFilesAsync(); - } - - private async Task LoadScannedFilesAsync() - { - try - { - if (string.IsNullOrWhiteSpace(SelectedFolderPath)) return; - - var prefix = SelectedFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; - - await using var db = await _dbContextFactory.CreateDbContextAsync(); - - var baseQuery = db.ScannedFiles.Where(f => f.FullPath.StartsWith(prefix)); - - IQueryable query; - if (ShowDuplicatesOnly) - { - var duplicateSizeSubquery = baseQuery - .GroupBy(f => f.SizeInBytes) - .Where(g => g.Count() > 1) - .Select(g => g.Key); - - query = baseQuery - .Where(f => duplicateSizeSubquery.Contains(f.SizeInBytes)) - .OrderBy(f => f.SizeInBytes) - .ThenBy(f => f.FolderPath) - .ThenBy(f => f.FileName); - } - else - { - query = baseQuery - .OrderBy(f => f.FolderPath) - .ThenBy(f => f.FileName); - } - - TotalFileCount = await query.CountAsync(); - - ClampCurrentPage(); - - var files = await query - .Skip((CurrentPage - 1) * PageSize) - .Take(PageSize) - .ToListAsync(); - - ScannedFiles.Clear(); - files.ForEach(file => ScannedFiles.Add(new ScannedFileDisplayItem(file))); - } - catch (Exception ex) - { - StatusMessages.Add($"Error loading files: {ex.Message}"); + _ = InitializeAsync(); + } + + private async Task InitializeAsync() + { + await LoadSelectedFolderPathAsync(); + } + + private async Task LoadSelectedFolderPathAsync() + { + await using var db = await _dbContextFactory.CreateDbContextAsync(); + var setting = await db.AppSettings.FirstOrDefaultAsync(s => s.Key == "SelectedFolderPath"); + + if (setting is not null && !string.IsNullOrWhiteSpace(setting.Value) && Directory.Exists(setting.Value)) + { + SelectedFolderPath = setting.Value; + } + else + { + SelectedFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)); + } + } + + private async Task SelectFolder() + { + var path = await _folderPickerService.OpenFolderPickerAsync(); + if (!string.IsNullOrEmpty(path)) + { + SelectedFolderPath = path; + await SaveSelectedFolderPathAsync(path); + } + } + + private async Task SaveSelectedFolderPathAsync(string path) + { + await using var db = await _dbContextFactory.CreateDbContextAsync(); + var setting = await db.AppSettings.FirstOrDefaultAsync(s => s.Key == "SelectedFolderPath"); + + if (setting is not null) + { + setting.Value = path; + } + else + { + db.AppSettings.Add(new AppSetting { Key = "SelectedFolderPath", Value = path }); + } + + await db.SaveChangesAsync(); + } + + private async Task LoadFromDatabase() + { + _suppressPageReload = true; + CurrentPage = 1; + _suppressPageReload = false; + await LoadScannedFilesAsync(); + } + + private async Task StartScan() + { + if (string.IsNullOrWhiteSpace(SelectedFolderPath) || IsScanning) + return; + + IsScanning = true; + StatusMessages.Clear(); + ScannedFiles.Clear(); + TotalFilesProcessed = 0; + TotalFileCount = 0; + CurrentScanFolder = string.Empty; + _suppressPageReload = true; + CurrentPage = 1; + _suppressPageReload = false; + + _cts = new CancellationTokenSource(); + + var progress = new Progress(update => + { + CurrentScanFolder = update.CurrentFolder; + TotalFilesProcessed = update.TotalFilesProcessed; + if (!string.IsNullOrEmpty(update.StatusMessage)) + StatusMessages.Add(update.StatusMessage); + }); + + try + { + await Task.Run(() => _fileScannerService.ScanAsync(SelectedFolderPath, progress, _cts.Token), _cts.Token); + await LoadScannedFilesAsync(); + } + catch (OperationCanceledException) + { + var time = DateTime.Now.ToString("HH:mm:ss"); + StatusMessages.Add($"[{time}] [CANCELLED] Scan cancelled by user."); + } + finally + { + IsScanning = false; + _cts.Dispose(); + _cts = null; + } + } + + private async Task TogglePendingDelete(ScannedFileDisplayItem? item) + { + if (item is null) return; + + await using var db = await _dbContextFactory.CreateDbContextAsync(); + var file = await db.ScannedFiles.FindAsync(item.Id); + if (file is not null) + { + file.PendingDelete = !file.PendingDelete; + await db.SaveChangesAsync(); + item.PendingDelete = file.PendingDelete; } - } - - private void ClampCurrentPage() - { - if (CurrentPage <= TotalPages) return; - - _suppressPageReload = true; - CurrentPage = TotalPages; - _suppressPageReload = false; - } -} + } + + private async Task ViewFile(ScannedFileDisplayItem? item) + { + await _fileViewerService.ViewFileAsync(item); + } + + private void Cancel() + { + _cts?.Cancel(); + } + + private async Task LoadScannedFilesAsync() + { + try + { + if (string.IsNullOrWhiteSpace(SelectedFolderPath)) return; + + var prefix = SelectedFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; + + await using var db = await _dbContextFactory.CreateDbContextAsync(); + + var baseQuery = db.ScannedFiles.Where(f => f.FullPath.StartsWith(prefix)); + + IQueryable query; + if (ShowDuplicatesOnly) + { + var duplicateSizeSubquery = baseQuery + .GroupBy(f => f.SizeInBytes) + .Where(g => g.Count() > 1) + .Select(g => g.Key); + + query = baseQuery + .Where(f => duplicateSizeSubquery.Contains(f.SizeInBytes)) + .OrderBy(f => f.SizeInBytes) + .ThenBy(f => f.FolderPath) + .ThenBy(f => f.FileName); + } + else + { + query = baseQuery + .OrderBy(f => f.FolderPath) + .ThenBy(f => f.FileName); + } + + TotalFileCount = await query.CountAsync(); + + ClampCurrentPage(); + + var files = await query + .Skip((CurrentPage - 1) * PageSize) + .Take(PageSize) + .ToListAsync(); + + ScannedFiles.Clear(); + files.ForEach(file => ScannedFiles.Add(new ScannedFileDisplayItem(file))); + } + catch (Exception ex) + { + StatusMessages.Add($"Error loading files: {ex.Message}"); + } + } + + private void ClampCurrentPage() + { + if (CurrentPage <= TotalPages) return; + + _suppressPageReload = true; + CurrentPage = TotalPages; + _suppressPageReload = false; + } +} diff --git a/src/AStar.Dev.File.App/ViewModels/ScannedFileDisplayItem.cs b/src/AStar.Dev.File.App/ViewModels/ScannedFileDisplayItem.cs index 617b565..259b0a2 100644 --- a/src/AStar.Dev.File.App/ViewModels/ScannedFileDisplayItem.cs +++ b/src/AStar.Dev.File.App/ViewModels/ScannedFileDisplayItem.cs @@ -1,11 +1,11 @@ using AStar.Dev.File.App.Models; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; using System.IO; namespace AStar.Dev.File.App.ViewModels; -using System.ComponentModel; - -public class ScannedFileDisplayItem : INotifyPropertyChanged +public class ScannedFileDisplayItem : ReactiveObject { public int Id { get; } public string FullPath { get; } @@ -19,19 +19,7 @@ public class ScannedFileDisplayItem : INotifyPropertyChanged public string LastModified { get; } public string LastViewed { get; } - private bool _pendingDelete; - public bool PendingDelete - { - get => _pendingDelete; - set - { - if (_pendingDelete != value) - { - _pendingDelete = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PendingDelete))); - } - } - } + [Reactive] public bool PendingDelete { get; set; } public ScannedFileDisplayItem(ScannedFile file) { @@ -48,11 +36,9 @@ public ScannedFileDisplayItem(ScannedFile file) LastViewed = file.LastViewed.HasValue ? file.LastViewed.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss") : "—"; - _pendingDelete = file.PendingDelete; + PendingDelete = file.PendingDelete; } - public event PropertyChangedEventHandler? PropertyChanged; - public static string FormatSize(long bytes) { if (bytes >= 1_073_741_824L) diff --git a/src/AStar.Dev.File.App/ViewModels/ViewModelBase.cs b/src/AStar.Dev.File.App/ViewModels/ViewModelBase.cs index 54056cf..7782c2e 100644 --- a/src/AStar.Dev.File.App/ViewModels/ViewModelBase.cs +++ b/src/AStar.Dev.File.App/ViewModels/ViewModelBase.cs @@ -1,7 +1,7 @@ -using CommunityToolkit.Mvvm.ComponentModel; - -namespace AStar.Dev.File.App.ViewModels; - -public abstract class ViewModelBase : ObservableObject -{ -} +using ReactiveUI; + +namespace AStar.Dev.File.App.ViewModels; + +public abstract class ViewModelBase : ReactiveObject +{ +} diff --git a/tests/AStar.Dev.File.App.Tests.Unit/obj/AStar.Dev.File.App.Tests.Unit.csproj.nuget.dgspec.json b/tests/AStar.Dev.File.App.Tests.Unit/obj/AStar.Dev.File.App.Tests.Unit.csproj.nuget.dgspec.json index 493b7a5..29e906d 100644 --- a/tests/AStar.Dev.File.App.Tests.Unit/obj/AStar.Dev.File.App.Tests.Unit.csproj.nuget.dgspec.json +++ b/tests/AStar.Dev.File.App.Tests.Unit/obj/AStar.Dev.File.App.Tests.Unit.csproj.nuget.dgspec.json @@ -65,13 +65,13 @@ "target": "Package", "version": "[11.3.13, )" }, - "Avalonia.Themes.Fluent": { + "Avalonia.ReactiveUI": { "target": "Package", - "version": "[11.3.13, )" + "version": "[11.3.9, )" }, - "CommunityToolkit.Mvvm": { + "Avalonia.Themes.Fluent": { "target": "Package", - "version": "[8.4.2, )" + "version": "[11.3.13, )" }, "Microsoft.EntityFrameworkCore.Design": { "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", @@ -91,6 +91,10 @@ "target": "Package", "version": "[10.0.5, )" }, + "ReactiveUI.Fody": { + "target": "Package", + "version": "[19.5.41, )" + }, "Serilog": { "target": "Package", "version": "[4.3.1, )" diff --git a/tests/AStar.Dev.File.App.Tests.Unit/obj/AStar.Dev.File.App.Tests.Unit.csproj.nuget.g.targets b/tests/AStar.Dev.File.App.Tests.Unit/obj/AStar.Dev.File.App.Tests.Unit.csproj.nuget.g.targets index df41d60..b3e7286 100644 --- a/tests/AStar.Dev.File.App.Tests.Unit/obj/AStar.Dev.File.App.Tests.Unit.csproj.nuget.g.targets +++ b/tests/AStar.Dev.File.App.Tests.Unit/obj/AStar.Dev.File.App.Tests.Unit.csproj.nuget.g.targets @@ -12,7 +12,6 @@ - diff --git a/tests/AStar.Dev.File.App.Tests.Unit/obj/project.assets.json b/tests/AStar.Dev.File.App.Tests.Unit/obj/project.assets.json index 6fecc84..15347b4 100644 --- a/tests/AStar.Dev.File.App.Tests.Unit/obj/project.assets.json +++ b/tests/AStar.Dev.File.App.Tests.Unit/obj/project.assets.json @@ -233,6 +233,24 @@ } } }, + "Avalonia.ReactiveUI/11.3.9": { + "type": "package", + "dependencies": { + "Avalonia": "11.3.9", + "ReactiveUI": "20.1.1", + "System.Reactive": "6.0.1" + }, + "compile": { + "lib/net8.0/Avalonia.ReactiveUI.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Avalonia.ReactiveUI.dll": { + "related": ".xml" + } + } + }, "Avalonia.Remote.Protocol/11.3.13": { "type": "package", "compile": { @@ -357,35 +375,35 @@ } } }, - "CommunityToolkit.Mvvm/8.4.2": { + "DiffEngine/11.3.0": { "type": "package", + "dependencies": { + "EmptyFiles": "4.4.0", + "System.Management": "6.0.1" + }, "compile": { - "lib/net8.0/CommunityToolkit.Mvvm.dll": { + "lib/net7.0/DiffEngine.dll": { "related": ".xml" } }, "runtime": { - "lib/net8.0/CommunityToolkit.Mvvm.dll": { + "lib/net7.0/DiffEngine.dll": { "related": ".xml" } - }, - "build": { - "buildTransitive/CommunityToolkit.Mvvm.targets": {} } }, - "DiffEngine/11.3.0": { + "DynamicData/8.4.1": { "type": "package", "dependencies": { - "EmptyFiles": "4.4.0", - "System.Management": "6.0.1" + "System.Reactive": "6.0.0" }, "compile": { - "lib/net7.0/DiffEngine.dll": { + "lib/net8.0/DynamicData.dll": { "related": ".xml" } }, "runtime": { - "lib/net7.0/DiffEngine.dll": { + "lib/net8.0/DynamicData.dll": { "related": ".xml" } } @@ -406,6 +424,12 @@ "buildTransitive/EmptyFiles.targets": {} } }, + "Fody/6.8.0": { + "type": "package", + "build": { + "build/_._": {} + } + }, "HarfBuzzSharp/8.3.1.1": { "type": "package", "dependencies": { @@ -1125,6 +1149,44 @@ } } }, + "ReactiveUI/20.1.1": { + "type": "package", + "dependencies": { + "DynamicData": "8.4.1", + "Splat": "15.1.1", + "System.ComponentModel.Annotations": "5.0.0" + }, + "compile": { + "lib/net8.0/ReactiveUI.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/ReactiveUI.dll": { + "related": ".xml" + } + } + }, + "ReactiveUI.Fody/19.5.41": { + "type": "package", + "dependencies": { + "Fody": "6.8.0", + "ReactiveUI": "19.5.41" + }, + "compile": { + "lib/net8.0/ReactiveUI.Fody.Helpers.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/ReactiveUI.Fody.Helpers.dll": { + "related": ".xml" + } + }, + "build": { + "build/_._": {} + } + }, "Serilog/4.3.1": { "type": "package", "compile": { @@ -1308,6 +1370,19 @@ } } }, + "Splat/15.1.1": { + "type": "package", + "compile": { + "lib/net8.0/Splat.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Splat.dll": { + "related": ".xml" + } + } + }, "SQLitePCLRaw.bundle_e_sqlite3/2.1.11": { "type": "package", "dependencies": { @@ -1464,6 +1539,19 @@ "buildTransitive/netcoreapp3.1/_._": {} } }, + "System.ComponentModel.Annotations/5.0.0": { + "type": "package", + "compile": { + "ref/netstandard2.1/System.ComponentModel.Annotations.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.1/System.ComponentModel.Annotations.dll": { + "related": ".xml" + } + } + }, "System.Diagnostics.EventLog/6.0.0": { "type": "package", "compile": { @@ -1515,6 +1603,22 @@ } } }, + "System.Reactive/6.0.1": { + "type": "package", + "compile": { + "lib/net6.0/System.Reactive.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/System.Reactive.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, "Tmds.DBus.Protocol/0.21.2": { "type": "package", "compile": { @@ -1650,11 +1754,12 @@ "Avalonia.Desktop": "11.3.13", "Avalonia.Diagnostics": "11.3.13", "Avalonia.Fonts.Inter": "11.3.13", + "Avalonia.ReactiveUI": "11.3.9", "Avalonia.Themes.Fluent": "11.3.13", - "CommunityToolkit.Mvvm": "8.4.2", "Microsoft.EntityFrameworkCore.Sqlite": "10.0.5", "Microsoft.Extensions.DependencyInjection": "10.0.5", "Microsoft.Extensions.Logging": "10.0.5", + "ReactiveUI.Fody": "19.5.41", "Serilog": "4.3.1", "Serilog.Extensions.Logging": "10.0.0", "Serilog.Sinks.Console": "6.1.1", @@ -2002,6 +2107,24 @@ "runtimes/osx/native/libAvaloniaNative.dylib" ] }, + "Avalonia.ReactiveUI/11.3.9": { + "sha512": "Butgdd/wqpZ9QFHAaoOC8qOMTryYoIPeCXrXrV6qf5At4lCEkqcmAe1pRmdqpgeA4e4t19tjbXMElKj28jmhPg==", + "type": "package", + "path": "avalonia.reactiveui/11.3.9", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "avalonia.reactiveui.11.3.9.nupkg.sha512", + "avalonia.reactiveui.nuspec", + "lib/net6.0/Avalonia.ReactiveUI.dll", + "lib/net6.0/Avalonia.ReactiveUI.xml", + "lib/net8.0/Avalonia.ReactiveUI.dll", + "lib/net8.0/Avalonia.ReactiveUI.xml", + "lib/netstandard2.0/Avalonia.ReactiveUI.dll", + "lib/netstandard2.0/Avalonia.ReactiveUI.xml" + ] + }, "Avalonia.Remote.Protocol/11.3.13": { "sha512": "ATDouTcCn484dUxmvzmyuOfNpwWxOKTz8LbBNnHpMZFCtyzZ0b4gvfZXh/d5szyRIF+kaTPK97LMQ5UH8ZQNhg==", "type": "package", @@ -2140,46 +2263,6 @@ "readme.txt" ] }, - "CommunityToolkit.Mvvm/8.4.2": { - "sha512": "WadCzGEc2U+3e20avRLng4qNtt4zoOGWrdUISqJWrHe3/FSnrYjuM5Sb4yQb09LhkBXrrI4Zt3dLKgRMbItsrg==", - "type": "package", - "path": "communitytoolkit.mvvm/8.4.2", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "Icon.png", - "License.md", - "ThirdPartyNotices.txt", - "analyzers/dotnet/roslyn4.0/cs/CommunityToolkit.Mvvm.CodeFixers.dll", - "analyzers/dotnet/roslyn4.0/cs/CommunityToolkit.Mvvm.SourceGenerators.dll", - "analyzers/dotnet/roslyn4.12/cs/CommunityToolkit.Mvvm.CodeFixers.dll", - "analyzers/dotnet/roslyn4.12/cs/CommunityToolkit.Mvvm.SourceGenerators.dll", - "analyzers/dotnet/roslyn4.3/cs/CommunityToolkit.Mvvm.CodeFixers.dll", - "analyzers/dotnet/roslyn4.3/cs/CommunityToolkit.Mvvm.SourceGenerators.dll", - "analyzers/dotnet/roslyn5.0/cs/CommunityToolkit.Mvvm.CodeFixers.dll", - "analyzers/dotnet/roslyn5.0/cs/CommunityToolkit.Mvvm.SourceGenerators.dll", - "build/CommunityToolkit.Mvvm.FeatureSwitches.targets", - "build/CommunityToolkit.Mvvm.SourceGenerators.targets", - "build/CommunityToolkit.Mvvm.Windows.targets", - "build/CommunityToolkit.Mvvm.WindowsSdk.targets", - "build/CommunityToolkit.Mvvm.targets", - "buildTransitive/CommunityToolkit.Mvvm.FeatureSwitches.targets", - "buildTransitive/CommunityToolkit.Mvvm.SourceGenerators.targets", - "buildTransitive/CommunityToolkit.Mvvm.Windows.targets", - "buildTransitive/CommunityToolkit.Mvvm.WindowsSdk.targets", - "buildTransitive/CommunityToolkit.Mvvm.targets", - "communitytoolkit.mvvm.8.4.2.nupkg.sha512", - "communitytoolkit.mvvm.nuspec", - "lib/net8.0-windows10.0.17763/CommunityToolkit.Mvvm.dll", - "lib/net8.0-windows10.0.17763/CommunityToolkit.Mvvm.xml", - "lib/net8.0/CommunityToolkit.Mvvm.dll", - "lib/net8.0/CommunityToolkit.Mvvm.xml", - "lib/netstandard2.0/CommunityToolkit.Mvvm.dll", - "lib/netstandard2.0/CommunityToolkit.Mvvm.xml", - "lib/netstandard2.1/CommunityToolkit.Mvvm.dll", - "lib/netstandard2.1/CommunityToolkit.Mvvm.xml" - ] - }, "DiffEngine/11.3.0": { "sha512": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==", "type": "package", @@ -2214,6 +2297,30 @@ "lib/netstandard2.1/DiffEngine.xml" ] }, + "DynamicData/8.4.1": { + "sha512": "Mn1+fU/jqxgONEJq8KLQPGWEi7g/hUVTbjZyn4QM0sWWDAVOHPO9WjXWORSykwdfg/6S3GM15qsfz+2EvO+QAQ==", + "type": "package", + "path": "dynamicdata/8.4.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE/LICENSE", + "README.md", + "dynamicdata.8.4.1.nupkg.sha512", + "dynamicdata.nuspec", + "lib/net462/DynamicData.dll", + "lib/net462/DynamicData.xml", + "lib/net6.0/DynamicData.dll", + "lib/net6.0/DynamicData.xml", + "lib/net7.0/DynamicData.dll", + "lib/net7.0/DynamicData.xml", + "lib/net8.0/DynamicData.dll", + "lib/net8.0/DynamicData.xml", + "lib/netstandard2.0/DynamicData.dll", + "lib/netstandard2.0/DynamicData.xml", + "logo.png" + ] + }, "EmptyFiles/4.4.0": { "sha512": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw==", "type": "package", @@ -2282,6 +2389,39 @@ "lib/netstandard2.1/EmptyFiles.xml" ] }, + "Fody/6.8.0": { + "sha512": "hfZ/f8Mezt8aTkgv9nsvFdYoQ809/AqwsJlOGOPYIfBcG2aAIG3v3ex9d8ZqQuFYyMoucjRg4eKy3VleeGodKQ==", + "type": "package", + "path": "fody/6.8.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "License.txt", + "build/Fody.targets", + "fody.6.8.0.nupkg.sha512", + "fody.nuspec", + "netclassictask/Fody.dll", + "netclassictask/FodyCommon.dll", + "netclassictask/FodyHelpers.dll", + "netclassictask/FodyIsolated.dll", + "netclassictask/Mono.Cecil.Pdb.dll", + "netclassictask/Mono.Cecil.Pdb.pdb", + "netclassictask/Mono.Cecil.Rocks.dll", + "netclassictask/Mono.Cecil.Rocks.pdb", + "netclassictask/Mono.Cecil.dll", + "netclassictask/Mono.Cecil.pdb", + "netstandardtask/Fody.dll", + "netstandardtask/FodyCommon.dll", + "netstandardtask/FodyHelpers.dll", + "netstandardtask/FodyIsolated.dll", + "netstandardtask/Mono.Cecil.Pdb.dll", + "netstandardtask/Mono.Cecil.Pdb.pdb", + "netstandardtask/Mono.Cecil.Rocks.dll", + "netstandardtask/Mono.Cecil.Rocks.pdb", + "netstandardtask/Mono.Cecil.dll", + "netstandardtask/Mono.Cecil.pdb" + ] + }, "HarfBuzzSharp/8.3.1.1": { "sha512": "tLZN66oe/uiRPTZfrCU4i8ScVGwqHNh5MHrXj0yVf4l7Mz0FhTGnQ71RGySROTmdognAs0JtluHkL41pIabWuQ==", "type": "package", @@ -3289,6 +3429,85 @@ "nsubstitute.nuspec" ] }, + "ReactiveUI/20.1.1": { + "sha512": "9hNPknWjijnaSWs6auypoXqUptPZcRpUypF+cf1zD50fgW+SEoQda502N3fVZ2eWPcaiUad+z6GaLwOWmUVHNw==", + "type": "package", + "path": "reactiveui/20.1.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE/LICENSE", + "README.md", + "lib/net462/ReactiveUI.dll", + "lib/net462/ReactiveUI.xml", + "lib/net472/ReactiveUI.dll", + "lib/net472/ReactiveUI.xml", + "lib/net6.0-windows10.0.17763/ReactiveUI.dll", + "lib/net6.0-windows10.0.17763/ReactiveUI.xml", + "lib/net6.0-windows10.0.19041/ReactiveUI.dll", + "lib/net6.0-windows10.0.19041/ReactiveUI.xml", + "lib/net6.0/ReactiveUI.dll", + "lib/net6.0/ReactiveUI.xml", + "lib/net8.0-android34.0/ReactiveUI.dll", + "lib/net8.0-android34.0/ReactiveUI.xml", + "lib/net8.0-ios17.2/ReactiveUI.dll", + "lib/net8.0-ios17.2/ReactiveUI.xml", + "lib/net8.0-maccatalyst17.2/ReactiveUI.dll", + "lib/net8.0-maccatalyst17.2/ReactiveUI.xml", + "lib/net8.0-macos14.2/ReactiveUI.dll", + "lib/net8.0-macos14.2/ReactiveUI.xml", + "lib/net8.0-tvos17.2/ReactiveUI.dll", + "lib/net8.0-tvos17.2/ReactiveUI.xml", + "lib/net8.0-windows10.0.17763/ReactiveUI.dll", + "lib/net8.0-windows10.0.17763/ReactiveUI.xml", + "lib/net8.0-windows10.0.19041/ReactiveUI.dll", + "lib/net8.0-windows10.0.19041/ReactiveUI.xml", + "lib/net8.0/ReactiveUI.dll", + "lib/net8.0/ReactiveUI.xml", + "lib/netstandard2.0/ReactiveUI.dll", + "lib/netstandard2.0/ReactiveUI.xml", + "logo.png", + "reactiveui.20.1.1.nupkg.sha512", + "reactiveui.nuspec" + ] + }, + "ReactiveUI.Fody/19.5.41": { + "sha512": "bh6OH1WsSHf/ryUVaPnI6LWl1+lrCvq4L/AoHpzPXCKxtR4p0ogPVONnkq0jYgSQ55x7YNCVkthBKeb6uUSbpg==", + "type": "package", + "path": "reactiveui.fody/19.5.41", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE/LICENSE", + "README.md", + "build/ReactiveUI.Fody.props", + "lib/monoandroid13.0/ReactiveUI.Fody.Helpers.dll", + "lib/monoandroid13.0/ReactiveUI.Fody.Helpers.pdb", + "lib/monoandroid13.0/ReactiveUI.Fody.Helpers.xml", + "lib/net462/ReactiveUI.Fody.Helpers.dll", + "lib/net462/ReactiveUI.Fody.Helpers.xml", + "lib/net472/ReactiveUI.Fody.Helpers.dll", + "lib/net472/ReactiveUI.Fody.Helpers.xml", + "lib/net6.0/ReactiveUI.Fody.Helpers.dll", + "lib/net6.0/ReactiveUI.Fody.Helpers.xml", + "lib/net7.0/ReactiveUI.Fody.Helpers.dll", + "lib/net7.0/ReactiveUI.Fody.Helpers.xml", + "lib/net8.0/ReactiveUI.Fody.Helpers.dll", + "lib/net8.0/ReactiveUI.Fody.Helpers.xml", + "lib/netstandard2.0/ReactiveUI.Fody.Helpers.dll", + "lib/netstandard2.0/ReactiveUI.Fody.Helpers.xml", + "lib/xamarinios10/ReactiveUI.Fody.Helpers.dll", + "lib/xamarinios10/ReactiveUI.Fody.Helpers.xml", + "lib/xamarinmac20/ReactiveUI.Fody.Helpers.dll", + "lib/xamarinmac20/ReactiveUI.Fody.Helpers.xml", + "lib/xamarintvos10/ReactiveUI.Fody.Helpers.dll", + "lib/xamarintvos10/ReactiveUI.Fody.Helpers.xml", + "logo.png", + "reactiveui.fody.19.5.41.nupkg.sha512", + "reactiveui.fody.nuspec", + "weaver/ReactiveUI.Fody.dll" + ] + }, "Serilog/4.3.1": { "sha512": "savYe7h5yRlkqBVOwP8cIRDOdqKiPmYCU4W87JH38sBmcKD5EBoXvQIw6bNEvZ/pTe1gsiye3VFCzBsoppGkXQ==", "type": "package", @@ -3588,6 +3807,24 @@ "skiasharp.nativeassets.win32.nuspec" ] }, + "Splat/15.1.1": { + "sha512": "RHDTdF90FwVbRia2cmuIzkiVoETqnXSB2dDBBi/I35HWXqv4OKGqoMcfcd6obMvO2OmmY5PjU1M62K8LkJafAA==", + "type": "package", + "path": "splat/15.1.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE/LICENSE", + "lib/net6.0/Splat.dll", + "lib/net6.0/Splat.xml", + "lib/net8.0/Splat.dll", + "lib/net8.0/Splat.xml", + "lib/netstandard2.0/Splat.dll", + "lib/netstandard2.0/Splat.xml", + "splat.15.1.1.nupkg.sha512", + "splat.nuspec" + ] + }, "SQLitePCLRaw.bundle_e_sqlite3/2.1.11": { "sha512": "DC4nA7yWnf4UZdgJDF+9Mus4/cb0Y3Sfgi3gDnAoKNAIBwzkskNAbNbyu+u4atT0ruVlZNJfwZmwiEwE5oz9LQ==", "type": "package", @@ -3705,6 +3942,96 @@ "useSharedDesignerContext.txt" ] }, + "System.ComponentModel.Annotations/5.0.0": { + "sha512": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg==", + "type": "package", + "path": "system.componentmodel.annotations/5.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net461/System.ComponentModel.Annotations.dll", + "lib/netcore50/System.ComponentModel.Annotations.dll", + "lib/netstandard1.4/System.ComponentModel.Annotations.dll", + "lib/netstandard2.0/System.ComponentModel.Annotations.dll", + "lib/netstandard2.1/System.ComponentModel.Annotations.dll", + "lib/netstandard2.1/System.ComponentModel.Annotations.xml", + "lib/portable-net45+win8/_._", + "lib/win8/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net461/System.ComponentModel.Annotations.dll", + "ref/net461/System.ComponentModel.Annotations.xml", + "ref/netcore50/System.ComponentModel.Annotations.dll", + "ref/netcore50/System.ComponentModel.Annotations.xml", + "ref/netcore50/de/System.ComponentModel.Annotations.xml", + "ref/netcore50/es/System.ComponentModel.Annotations.xml", + "ref/netcore50/fr/System.ComponentModel.Annotations.xml", + "ref/netcore50/it/System.ComponentModel.Annotations.xml", + "ref/netcore50/ja/System.ComponentModel.Annotations.xml", + "ref/netcore50/ko/System.ComponentModel.Annotations.xml", + "ref/netcore50/ru/System.ComponentModel.Annotations.xml", + "ref/netcore50/zh-hans/System.ComponentModel.Annotations.xml", + "ref/netcore50/zh-hant/System.ComponentModel.Annotations.xml", + "ref/netstandard1.1/System.ComponentModel.Annotations.dll", + "ref/netstandard1.1/System.ComponentModel.Annotations.xml", + "ref/netstandard1.1/de/System.ComponentModel.Annotations.xml", + "ref/netstandard1.1/es/System.ComponentModel.Annotations.xml", + "ref/netstandard1.1/fr/System.ComponentModel.Annotations.xml", + "ref/netstandard1.1/it/System.ComponentModel.Annotations.xml", + "ref/netstandard1.1/ja/System.ComponentModel.Annotations.xml", + "ref/netstandard1.1/ko/System.ComponentModel.Annotations.xml", + "ref/netstandard1.1/ru/System.ComponentModel.Annotations.xml", + "ref/netstandard1.1/zh-hans/System.ComponentModel.Annotations.xml", + "ref/netstandard1.1/zh-hant/System.ComponentModel.Annotations.xml", + "ref/netstandard1.3/System.ComponentModel.Annotations.dll", + "ref/netstandard1.3/System.ComponentModel.Annotations.xml", + "ref/netstandard1.3/de/System.ComponentModel.Annotations.xml", + "ref/netstandard1.3/es/System.ComponentModel.Annotations.xml", + "ref/netstandard1.3/fr/System.ComponentModel.Annotations.xml", + "ref/netstandard1.3/it/System.ComponentModel.Annotations.xml", + "ref/netstandard1.3/ja/System.ComponentModel.Annotations.xml", + "ref/netstandard1.3/ko/System.ComponentModel.Annotations.xml", + "ref/netstandard1.3/ru/System.ComponentModel.Annotations.xml", + "ref/netstandard1.3/zh-hans/System.ComponentModel.Annotations.xml", + "ref/netstandard1.3/zh-hant/System.ComponentModel.Annotations.xml", + "ref/netstandard1.4/System.ComponentModel.Annotations.dll", + "ref/netstandard1.4/System.ComponentModel.Annotations.xml", + "ref/netstandard1.4/de/System.ComponentModel.Annotations.xml", + "ref/netstandard1.4/es/System.ComponentModel.Annotations.xml", + "ref/netstandard1.4/fr/System.ComponentModel.Annotations.xml", + "ref/netstandard1.4/it/System.ComponentModel.Annotations.xml", + "ref/netstandard1.4/ja/System.ComponentModel.Annotations.xml", + "ref/netstandard1.4/ko/System.ComponentModel.Annotations.xml", + "ref/netstandard1.4/ru/System.ComponentModel.Annotations.xml", + "ref/netstandard1.4/zh-hans/System.ComponentModel.Annotations.xml", + "ref/netstandard1.4/zh-hant/System.ComponentModel.Annotations.xml", + "ref/netstandard2.0/System.ComponentModel.Annotations.dll", + "ref/netstandard2.0/System.ComponentModel.Annotations.xml", + "ref/netstandard2.1/System.ComponentModel.Annotations.dll", + "ref/netstandard2.1/System.ComponentModel.Annotations.xml", + "ref/portable-net45+win8/_._", + "ref/win8/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "system.componentmodel.annotations.5.0.0.nupkg.sha512", + "system.componentmodel.annotations.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, "System.Diagnostics.EventLog/6.0.0": { "sha512": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==", "type": "package", @@ -3764,6 +4091,34 @@ "useSharedDesignerContext.txt" ] }, + "System.Reactive/6.0.1": { + "sha512": "rHaWtKDwCi9qJ3ObKo8LHPMuuwv33YbmQi7TcUK1C264V3MFnOr5Im7QgCTdLniztP3GJyeiSg5x8NqYJFqRmg==", + "type": "package", + "path": "system.reactive/6.0.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/net6.0-windows10.0.19041/_._", + "build/net6.0/_._", + "buildTransitive/net6.0-windows10.0.19041/_._", + "buildTransitive/net6.0/_._", + "icon.png", + "lib/net472/System.Reactive.dll", + "lib/net472/System.Reactive.xml", + "lib/net6.0-windows10.0.19041/System.Reactive.dll", + "lib/net6.0-windows10.0.19041/System.Reactive.xml", + "lib/net6.0/System.Reactive.dll", + "lib/net6.0/System.Reactive.xml", + "lib/netstandard2.0/System.Reactive.dll", + "lib/netstandard2.0/System.Reactive.xml", + "lib/uap10.0.18362/System.Reactive.dll", + "lib/uap10.0.18362/System.Reactive.pri", + "lib/uap10.0.18362/System.Reactive.xml", + "readme.md", + "system.reactive.6.0.1.nupkg.sha512", + "system.reactive.nuspec" + ] + }, "Tmds.DBus.Protocol/0.21.2": { "sha512": "ScSMrUrrw8px4kK1Glh0fZv/HQUlg1078bNXNPfRPKQ3WbRzV9HpsydYEOgSoMK5LWICMf2bMwIFH0pGjxjcMA==", "type": "package", diff --git a/tests/AStar.Dev.File.App.Tests.Unit/obj/project.nuget.cache b/tests/AStar.Dev.File.App.Tests.Unit/obj/project.nuget.cache index 52daf34..6eddc42 100644 --- a/tests/AStar.Dev.File.App.Tests.Unit/obj/project.nuget.cache +++ b/tests/AStar.Dev.File.App.Tests.Unit/obj/project.nuget.cache @@ -1,6 +1,6 @@ { "version": 2, - "dgSpecHash": "h9CpUWNnkPE=", + "dgSpecHash": "svp4DAH3jrI=", "success": true, "projectFilePath": "/home/jbarden/repos/astar-dev-file-app/tests/AStar.Dev.File.App.Tests.Unit/AStar.Dev.File.App.Tests.Unit.csproj", "expectedPackageFiles": [ @@ -14,6 +14,7 @@ "/home/jbarden/.nuget/packages/avalonia.fonts.inter/11.3.13/avalonia.fonts.inter.11.3.13.nupkg.sha512", "/home/jbarden/.nuget/packages/avalonia.freedesktop/11.3.13/avalonia.freedesktop.11.3.13.nupkg.sha512", "/home/jbarden/.nuget/packages/avalonia.native/11.3.13/avalonia.native.11.3.13.nupkg.sha512", + "/home/jbarden/.nuget/packages/avalonia.reactiveui/11.3.9/avalonia.reactiveui.11.3.9.nupkg.sha512", "/home/jbarden/.nuget/packages/avalonia.remote.protocol/11.3.13/avalonia.remote.protocol.11.3.13.nupkg.sha512", "/home/jbarden/.nuget/packages/avalonia.skia/11.3.13/avalonia.skia.11.3.13.nupkg.sha512", "/home/jbarden/.nuget/packages/avalonia.themes.fluent/11.3.13/avalonia.themes.fluent.11.3.13.nupkg.sha512", @@ -21,9 +22,10 @@ "/home/jbarden/.nuget/packages/avalonia.win32/11.3.13/avalonia.win32.11.3.13.nupkg.sha512", "/home/jbarden/.nuget/packages/avalonia.x11/11.3.13/avalonia.x11.11.3.13.nupkg.sha512", "/home/jbarden/.nuget/packages/castle.core/5.1.1/castle.core.5.1.1.nupkg.sha512", - "/home/jbarden/.nuget/packages/communitytoolkit.mvvm/8.4.2/communitytoolkit.mvvm.8.4.2.nupkg.sha512", "/home/jbarden/.nuget/packages/diffengine/11.3.0/diffengine.11.3.0.nupkg.sha512", + "/home/jbarden/.nuget/packages/dynamicdata/8.4.1/dynamicdata.8.4.1.nupkg.sha512", "/home/jbarden/.nuget/packages/emptyfiles/4.4.0/emptyfiles.4.4.0.nupkg.sha512", + "/home/jbarden/.nuget/packages/fody/6.8.0/fody.6.8.0.nupkg.sha512", "/home/jbarden/.nuget/packages/harfbuzzsharp/8.3.1.1/harfbuzzsharp.8.3.1.1.nupkg.sha512", "/home/jbarden/.nuget/packages/harfbuzzsharp.nativeassets.linux/8.3.1.1/harfbuzzsharp.nativeassets.linux.8.3.1.1.nupkg.sha512", "/home/jbarden/.nuget/packages/harfbuzzsharp.nativeassets.macos/8.3.1.1/harfbuzzsharp.nativeassets.macos.8.3.1.1.nupkg.sha512", @@ -56,6 +58,8 @@ "/home/jbarden/.nuget/packages/microsoft.testing.platform.msbuild/2.0.2/microsoft.testing.platform.msbuild.2.0.2.nupkg.sha512", "/home/jbarden/.nuget/packages/microsoft.win32.registry/5.0.0/microsoft.win32.registry.5.0.0.nupkg.sha512", "/home/jbarden/.nuget/packages/nsubstitute/5.3.0/nsubstitute.5.3.0.nupkg.sha512", + "/home/jbarden/.nuget/packages/reactiveui/20.1.1/reactiveui.20.1.1.nupkg.sha512", + "/home/jbarden/.nuget/packages/reactiveui.fody/19.5.41/reactiveui.fody.19.5.41.nupkg.sha512", "/home/jbarden/.nuget/packages/serilog/4.3.1/serilog.4.3.1.nupkg.sha512", "/home/jbarden/.nuget/packages/serilog.extensions.logging/10.0.0/serilog.extensions.logging.10.0.0.nupkg.sha512", "/home/jbarden/.nuget/packages/serilog.sinks.console/6.1.1/serilog.sinks.console.6.1.1.nupkg.sha512", @@ -66,13 +70,16 @@ "/home/jbarden/.nuget/packages/skiasharp.nativeassets.macos/2.88.9/skiasharp.nativeassets.macos.2.88.9.nupkg.sha512", "/home/jbarden/.nuget/packages/skiasharp.nativeassets.webassembly/2.88.9/skiasharp.nativeassets.webassembly.2.88.9.nupkg.sha512", "/home/jbarden/.nuget/packages/skiasharp.nativeassets.win32/2.88.9/skiasharp.nativeassets.win32.2.88.9.nupkg.sha512", + "/home/jbarden/.nuget/packages/splat/15.1.1/splat.15.1.1.nupkg.sha512", "/home/jbarden/.nuget/packages/sqlitepclraw.bundle_e_sqlite3/2.1.11/sqlitepclraw.bundle_e_sqlite3.2.1.11.nupkg.sha512", "/home/jbarden/.nuget/packages/sqlitepclraw.core/2.1.11/sqlitepclraw.core.2.1.11.nupkg.sha512", "/home/jbarden/.nuget/packages/sqlitepclraw.lib.e_sqlite3/2.1.11/sqlitepclraw.lib.e_sqlite3.2.1.11.nupkg.sha512", "/home/jbarden/.nuget/packages/sqlitepclraw.provider.e_sqlite3/2.1.11/sqlitepclraw.provider.e_sqlite3.2.1.11.nupkg.sha512", "/home/jbarden/.nuget/packages/system.codedom/6.0.0/system.codedom.6.0.0.nupkg.sha512", + "/home/jbarden/.nuget/packages/system.componentmodel.annotations/5.0.0/system.componentmodel.annotations.5.0.0.nupkg.sha512", "/home/jbarden/.nuget/packages/system.diagnostics.eventlog/6.0.0/system.diagnostics.eventlog.6.0.0.nupkg.sha512", "/home/jbarden/.nuget/packages/system.management/6.0.1/system.management.6.0.1.nupkg.sha512", + "/home/jbarden/.nuget/packages/system.reactive/6.0.1/system.reactive.6.0.1.nupkg.sha512", "/home/jbarden/.nuget/packages/tmds.dbus.protocol/0.21.2/tmds.dbus.protocol.0.21.2.nupkg.sha512", "/home/jbarden/.nuget/packages/xunit.analyzers/1.27.0/xunit.analyzers.1.27.0.nupkg.sha512", "/home/jbarden/.nuget/packages/xunit.v3.assert/3.2.2/xunit.v3.assert.3.2.2.nupkg.sha512",