diff --git a/src/Files.App/App.xaml.cs b/src/Files.App/App.xaml.cs index 7e4c0eb63ffe..be348b2c9152 100644 --- a/src/Files.App/App.xaml.cs +++ b/src/Files.App/App.xaml.cs @@ -41,7 +41,7 @@ public static CommandBarFlyout? LastOpenedFlyout public static StorageHistoryWrapper HistoryWrapper { get; private set; } = null!; public static FileTagsManager FileTagsManager { get; private set; } = null!; public static RecentItems RecentItemsManager { get; private set; } = null!; - public static LibraryManager LibraryManager { get; private set; } = null!; + public static IWindowsLibraryService WindowsLibraryService { get; private set; } = null!; public static AppModel AppModel { get; private set; } = null!; public static ILogger Logger { get; private set; } = null!; @@ -116,7 +116,7 @@ async Task ActivateAsync() HistoryWrapper = Ioc.Default.GetRequiredService(); FileTagsManager = Ioc.Default.GetRequiredService(); RecentItemsManager = Ioc.Default.GetRequiredService(); - LibraryManager = Ioc.Default.GetRequiredService(); + WindowsLibraryService = Ioc.Default.GetRequiredService(); Logger = Ioc.Default.GetRequiredService>(); AppModel = Ioc.Default.GetRequiredService(); diff --git a/src/Files.App/Data/Contracts/IWindowsLibraryService.cs b/src/Files.App/Data/Contracts/IWindowsLibraryService.cs new file mode 100644 index 000000000000..1ecf48a0fd3b --- /dev/null +++ b/src/Files.App/Data/Contracts/IWindowsLibraryService.cs @@ -0,0 +1,45 @@ +// Copyright (c) 2024 Files Community +// Licensed under the MIT License. See the LICENSE. + +using System.Collections.Specialized; + +namespace Files.App.Data.Contracts +{ + public interface IWindowsLibraryService + { + event EventHandler? DataChanged; + + IReadOnlyList Libraries { get; } + + /// + /// Get libraries of the current user with the help of the FullTrust process. + /// + /// List of library items + Task> GetLibrariesAsync(); + + Task UpdateLibrariesAsync(); + + /// + /// Update library details. + /// + /// Library file path + /// Update the default save folder or null to keep current + /// Update the library folders or null to keep current + /// Update the library pinned status or null to keep current + /// The new library if successfully updated + Task UpdateLibraryAsync(string libraryPath, string? defaultSaveFolder = null, string[]? folders = null, bool? isPinned = null); + + bool TryGetLibrary(string path, out LibraryLocationItem library); + + /// + /// Create a new library with the specified name. + /// + /// The name of the new library (must be unique) + /// The new library if successfully created + Task CreateNewLibrary(string name); + + (bool result, string reason) CanCreateLibrary(string name); + + bool IsLibraryPath(string path); + } +} diff --git a/src/Files.App/Data/Models/ItemViewModel.cs b/src/Files.App/Data/Models/ItemViewModel.cs index 9e8464e922c4..05c2a4ec3179 100644 --- a/src/Files.App/Data/Models/ItemViewModel.cs +++ b/src/Files.App/Data/Models/ItemViewModel.cs @@ -140,7 +140,7 @@ public async Task SetWorkingDirectoryAsync(string? value) var isLibrary = false; string? name = null; - if (App.LibraryManager.TryGetLibrary(value, out LibraryLocationItem library)) + if (App.WindowsLibraryService.TryGetLibrary(value, out LibraryLocationItem library)) { isLibrary = true; name = library.Text; @@ -1384,7 +1384,7 @@ private async Task RapidAddItemsToCollectionAsync(string path, string? previousD if (path.ToLowerInvariant().EndsWith(ShellLibraryItem.EXTENSION, StringComparison.Ordinal)) { - if (App.LibraryManager.TryGetLibrary(path, out LibraryLocationItem library) && !library.IsEmpty) + if (App.WindowsLibraryService.TryGetLibrary(path, out LibraryLocationItem library) && !library.IsEmpty) { var libItem = new LibraryItem(library); foreach (var folder in library.Folders) diff --git a/src/Files.App/GlobalUsings.cs b/src/Files.App/GlobalUsings.cs index 994cd5a70fdb..a7c9e5d268f6 100644 --- a/src/Files.App/GlobalUsings.cs +++ b/src/Files.App/GlobalUsings.cs @@ -27,7 +27,6 @@ global using global::Files.App.Utils.Cloud; global using global::Files.App.Utils.FileTags; global using global::Files.App.Utils.Git; -global using global::Files.App.Utils.Library; global using global::Files.App.Utils.RecentItem; global using global::Files.App.Utils.RecycleBin; global using global::Files.App.Utils.Serialization; diff --git a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs index 27571fb9c3c5..1df4f95dace4 100644 --- a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs +++ b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs @@ -71,7 +71,7 @@ public static async Task InitializeAppComponentsAsync() // Start off a list of tasks we need to run before we can continue startup await Task.WhenAll( OptionalTaskAsync(CloudDrivesManager.UpdateDrivesAsync(), generalSettingsService.ShowCloudDrivesSection), - App.LibraryManager.UpdateLibrariesAsync(), + App.WindowsLibraryService.UpdateLibrariesAsync(), OptionalTaskAsync(WSLDistroManager.UpdateDrivesAsync(), generalSettingsService.ShowWslSection), OptionalTaskAsync(App.FileTagsManager.UpdateFileTagsAsync(), generalSettingsService.ShowFileTagsSection), App.QuickAccessManager.InitializeAsync() @@ -190,6 +190,7 @@ public static IHost ConfigureHost() .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() // ViewModels .AddSingleton() .AddSingleton() @@ -204,7 +205,6 @@ public static IHost ConfigureHost() .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() .AddSingleton() ).Build(); } diff --git a/src/Files.App/Helpers/Layout/LayoutPreferencesManager.cs b/src/Files.App/Helpers/Layout/LayoutPreferencesManager.cs index a024363c1146..c13550ca8ae5 100644 --- a/src/Files.App/Helpers/Layout/LayoutPreferencesManager.cs +++ b/src/Files.App/Helpers/Layout/LayoutPreferencesManager.cs @@ -561,7 +561,7 @@ public static void SetLayoutPreferencesForPath(string path, LayoutPreferencesIte DirectoryGroupByDateUnit = GroupByDateUnit.Year }; } - else if (LibraryManager.IsLibraryPath(path)) + else if (App.WindowsLibraryService.IsLibraryPath(path)) { // Default for libraries is to group by folder path return new() diff --git a/src/Files.App/Helpers/Navigation/NavigationHelpers.cs b/src/Files.App/Helpers/Navigation/NavigationHelpers.cs index e76430b91f90..660aa0209552 100644 --- a/src/Files.App/Helpers/Navigation/NavigationHelpers.cs +++ b/src/Files.App/Helpers/Navigation/NavigationHelpers.cs @@ -147,7 +147,7 @@ private static async Task UpdateTabInfoAsync(TabBarItem tabItem, object navigati tabLocationHeader = "ThisPC".GetLocalizedResource(); else if (currentPath.Equals(Constants.UserEnvironmentPaths.NetworkFolderPath, StringComparison.OrdinalIgnoreCase)) tabLocationHeader = "SidebarNetworkDrives".GetLocalizedResource(); - else if (App.LibraryManager.TryGetLibrary(currentPath, out LibraryLocationItem library)) + else if (App.WindowsLibraryService.TryGetLibrary(currentPath, out LibraryLocationItem library)) { var libName = System.IO.Path.GetFileNameWithoutExtension(library.Path).GetLocalizedResource(); // If localized string is empty use the library name. @@ -449,7 +449,7 @@ private static async Task OpenLibrary(string path, IShellPage await OpenPath(forceOpenInNewTab, UserSettingsService.FoldersSettingsService.OpenFoldersInNewTab, path, associatedInstance); opened = (FilesystemResult)true; } - else if (App.LibraryManager.TryGetLibrary(path, out LibraryLocationItem library)) + else if (App.WindowsLibraryService.TryGetLibrary(path, out LibraryLocationItem library)) { opened = (FilesystemResult)await library.CheckDefaultSaveFolderAccess(); if (opened) diff --git a/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs b/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs index 4df5ad003698..282b5a0efc17 100644 --- a/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs +++ b/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs @@ -284,7 +284,7 @@ public static async Task CreateFileFromDialogResultTypeAsync(AddItemDialogItemTy if (associatedInstance.SlimContentPage is not null) { currentPath = associatedInstance.FilesystemViewModel.WorkingDirectory; - if (App.LibraryManager.TryGetLibrary(currentPath, out var library) && + if (App.WindowsLibraryService.TryGetLibrary(currentPath, out var library) && !library.IsEmpty && library.Folders.Count == 1) // TODO: handle libraries with multiple folders { @@ -375,7 +375,7 @@ public static async Task CreateShortcutAsync(IShellPage? associatedInstance, IRe { var currentPath = associatedInstance?.FilesystemViewModel.WorkingDirectory; - if (App.LibraryManager.TryGetLibrary(currentPath ?? string.Empty, out var library) && !library.IsEmpty) + if (App.WindowsLibraryService.TryGetLibrary(currentPath ?? string.Empty, out var library) && !library.IsEmpty) currentPath = library.DefaultSaveFolder; foreach (ListedItem selectedItem in selectedItems) @@ -394,7 +394,7 @@ public static async Task CreateShortcutAsync(IShellPage? associatedInstance, IRe public static async Task CreateShortcutFromDialogAsync(IShellPage associatedInstance) { var currentPath = associatedInstance.FilesystemViewModel.WorkingDirectory; - if (App.LibraryManager.TryGetLibrary(currentPath, out var library) && + if (App.WindowsLibraryService.TryGetLibrary(currentPath, out var library) && !library.IsEmpty) { currentPath = library.DefaultSaveFolder; diff --git a/src/Files.App/Services/WindowsJumpListService.cs b/src/Files.App/Services/WindowsJumpListService.cs index 4efa69643498..37c35e6adc55 100644 --- a/src/Files.App/Services/WindowsJumpListService.cs +++ b/src/Files.App/Services/WindowsJumpListService.cs @@ -152,7 +152,7 @@ private void AddFolder(string path, string group, JumpList instance) displayName = "ThisPC".GetLocalizedResource(); else if (path.Equals(Constants.UserEnvironmentPaths.NetworkFolderPath, StringComparison.OrdinalIgnoreCase)) displayName = "SidebarNetworkDrives".GetLocalizedResource(); - else if (App.LibraryManager.TryGetLibrary(path, out LibraryLocationItem library)) + else if (App.WindowsLibraryService.TryGetLibrary(path, out LibraryLocationItem library)) { var libName = Path.GetFileNameWithoutExtension(library.Path); displayName = libName switch diff --git a/src/Files.App/Utils/Library/LibraryManager.cs b/src/Files.App/Services/WindowsLibraryService.cs similarity index 51% rename from src/Files.App/Utils/Library/LibraryManager.cs rename to src/Files.App/Services/WindowsLibraryService.cs index 772c6479d1c3..b635f342b1c2 100644 --- a/src/Files.App/Utils/Library/LibraryManager.cs +++ b/src/Files.App/Services/WindowsLibraryService.cs @@ -1,182 +1,100 @@ -// Copyright (c) 2024 Files Community +// Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. -using Files.App.Dialogs; -using Files.App.ViewModels.Dialogs; using Microsoft.Extensions.Logging; -using Microsoft.UI.Xaml.Controls; using System.Collections.Specialized; using System.IO; using Vanara.PInvoke; using Vanara.Windows.Shell; -using Windows.System; -using Visibility = Microsoft.UI.Xaml.Visibility; -namespace Files.App.Utils.Library +namespace Files.App.Services { - public sealed class LibraryManager : IDisposable + public class WindowsLibraryService : IWindowsLibraryService, IDisposable { - public EventHandler? DataChanged; + private readonly FileSystemWatcher _librariesWatcher; - private FileSystemWatcher librariesWatcher; - private readonly List libraries = []; - private static readonly Lazy lazy = new(() => new LibraryManager()); - - public static LibraryManager Default - => lazy.Value; + public event EventHandler? DataChanged; + private readonly List _Libraries = []; public IReadOnlyList Libraries { get { - lock (libraries) - { - return libraries.ToList().AsReadOnly(); - } + lock (_Libraries) + return _Libraries.ToList().AsReadOnly(); } } - public LibraryManager() - { - InitializeWatcher(); - } - - private void InitializeWatcher() + public WindowsLibraryService() { - if (librariesWatcher is not null) - return; - - librariesWatcher = new FileSystemWatcher + _librariesWatcher = new() { Path = ShellLibraryItem.LibrariesPath, Filter = "*" + ShellLibraryItem.EXTENSION, NotifyFilter = NotifyFilters.Attributes | NotifyFilters.LastWrite | NotifyFilters.FileName, - IncludeSubdirectories = false, + IncludeSubdirectories = true, }; - librariesWatcher.Created += OnLibraryChanged; - librariesWatcher.Changed += OnLibraryChanged; - librariesWatcher.Deleted += OnLibraryChanged; - librariesWatcher.Renamed += OnLibraryRenamed; - - librariesWatcher.EnableRaisingEvents = true; + _librariesWatcher.Created += OnLibraryChanged; + _librariesWatcher.Changed += OnLibraryChanged; + _librariesWatcher.Deleted += OnLibraryChanged; + _librariesWatcher.Renamed += OnLibraryRenamed; } - /// - /// Get libraries of the current user with the help of the FullTrust process. - /// - /// List of library items - public static async Task> ListUserLibraries() + /// + public async Task> GetLibrariesAsync() { var libraries = await Win32Helper.StartSTATask(() => { try { var libraryItems = new List(); + // https://learn.microsoft.com/windows/win32/search/-search-win7-development-scenarios#library-descriptions - var libFiles = Directory.EnumerateFiles(ShellLibraryItem.LibrariesPath, "*" + ShellLibraryItem.EXTENSION); - foreach (var libFile in libFiles) + var shellFiles = Directory.EnumerateFiles(ShellLibraryItem.LibrariesPath, "*" + ShellLibraryItem.EXTENSION); + foreach (var item in shellFiles) { - using var shellItem = new ShellLibraryEx(Shell32.ShellUtil.GetShellItemForPath(libFile), true); - if (shellItem is ShellLibraryEx library) - { - libraryItems.Add(ShellFolderExtensions.GetShellLibraryItem(library, libFile)); - } + using var shellItem = new ShellLibraryEx(Shell32.ShellUtil.GetShellItemForPath(item), true); + + if (shellItem is ShellLibraryEx libraryItem) + libraryItems.Add(ShellFolderExtensions.GetShellLibraryItem(libraryItem, item)); } + return libraryItems; } catch (Exception e) { App.Logger.LogWarning(e, null); + return []; } - - return []; }); - return libraries.Select(lib => new LibraryLocationItem(lib)).ToList(); + return libraries!.Select(lib => new LibraryLocationItem(lib)).ToList(); } + /// public async Task UpdateLibrariesAsync() { - lock (libraries) - { - libraries.Clear(); - } - var libs = await ListUserLibraries(); + lock (_Libraries) + _Libraries.Clear(); + + var libs = await GetLibrariesAsync(); if (libs is not null) { libs.Sort(); - lock (libraries) - { - libraries.AddRange(libs); - } - } - DataChanged?.Invoke(SectionType.Library, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - public bool TryGetLibrary(string path, out LibraryLocationItem library) - { - if (string.IsNullOrWhiteSpace(path) || !path.EndsWith(ShellLibraryItem.EXTENSION, StringComparison.OrdinalIgnoreCase)) - { - library = null; - return false; + lock (_Libraries) + _Libraries.AddRange(libs); } - library = Libraries.FirstOrDefault(l => string.Equals(path, l.Path, StringComparison.OrdinalIgnoreCase)); - return library is not null; - } - - /// - /// Create a new library with the specified name. - /// - /// The name of the new library (must be unique) - /// The new library if successfully created - public async Task CreateNewLibrary(string name) - { - if (string.IsNullOrWhiteSpace(name) || !CanCreateLibrary(name).result) - return false; - var newLib = new LibraryLocationItem(await Win32Helper.StartSTATask(() => - { - try - { - using var library = new ShellLibraryEx(name, Shell32.KNOWNFOLDERID.FOLDERID_Libraries, false); - library.Folders.Add(ShellItem.Open(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments))); // Add default folder so it's not empty - library.Commit(); - library.Reload(); - return Task.FromResult(ShellFolderExtensions.GetShellLibraryItem(library, library.GetDisplayName(ShellItemDisplayString.DesktopAbsoluteParsing))); - } - catch (Exception e) - { - App.Logger.LogWarning(e, null); - } - - return Task.FromResult(null); - })); - - if (newLib is not null) - { - lock (libraries) - { - libraries.Add(newLib); - } - DataChanged?.Invoke(SectionType.Library, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newLib)); - return true; - } - return false; + DataChanged?.Invoke(SectionType.Library, new(NotifyCollectionChangedAction.Reset)); } - /// - /// Update library details. - /// - /// Library file path - /// Update the default save folder or null to keep current - /// Update the library folders or null to keep current - /// Update the library pinned status or null to keep current - /// The new library if successfully updated - public async Task UpdateLibrary(string libraryPath, string defaultSaveFolder = null, string[] folders = null, bool? isPinned = null) + /// + public async Task UpdateLibraryAsync(string libraryPath, string? defaultSaveFolder = null, string[]? folders = null, bool? isPinned = null) { + // Nothing to update if (string.IsNullOrWhiteSpace(libraryPath) || (defaultSaveFolder is null && folders is null && isPinned is null)) - // Nothing to update return null; var item = await Win32Helper.StartSTATask(() => @@ -184,6 +102,7 @@ public async Task UpdateLibrary(string libraryPath, string try { bool updated = false; + using var library = new ShellLibraryEx(Shell32.ShellUtil.GetShellItemForPath(libraryPath), false); if (folders is not null) { @@ -195,34 +114,39 @@ public async Task UpdateLibrary(string libraryPath, string library.Folders.Remove(toRemove); updated = true; } - var foldersToAdd = folders.Distinct(StringComparer.OrdinalIgnoreCase) - .Where(folderPath => !library.Folders.Any(f => string.Equals(folderPath, f.FileSystemPath, StringComparison.OrdinalIgnoreCase))) - .Select(ShellItem.Open); + var foldersToAdd = + folders.Distinct(StringComparer.OrdinalIgnoreCase) + .Where(folderPath => !library.Folders.Any(f => string.Equals(folderPath, f.FileSystemPath, StringComparison.OrdinalIgnoreCase))) + .Select(ShellItem.Open); + foreach (var toAdd in foldersToAdd) { library.Folders.Add(toAdd); updated = true; } + foreach (var toAdd in foldersToAdd) - { toAdd.Dispose(); - } } } + if (defaultSaveFolder is not null) { library.DefaultSaveFolder = ShellItem.Open(defaultSaveFolder); updated = true; } + if (isPinned is not null) { library.PinnedToNavigationPane = isPinned == true; updated = true; } + if (updated) { library.Commit(); - library.Reload(); // Reload folders list + library.Reload(); + return Task.FromResult(ShellFolderExtensions.GetShellLibraryItem(library, libraryPath)); } } @@ -240,139 +164,104 @@ public async Task UpdateLibrary(string libraryPath, string var libItem = Libraries.FirstOrDefault(l => string.Equals(l.Path, libraryPath, StringComparison.OrdinalIgnoreCase)); if (libItem is not null) { - lock (libraries) - { - libraries[libraries.IndexOf(libItem)] = newLib; - } + lock (_Libraries) + _Libraries[_Libraries.IndexOf(libItem)] = newLib; + DataChanged?.Invoke(SectionType.Library, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newLib, libItem)); } + return newLib; } + return null; } + /// + public bool TryGetLibrary(string path, out LibraryLocationItem library) + { + if (string.IsNullOrWhiteSpace(path) || + !path.EndsWith(ShellLibraryItem.EXTENSION, StringComparison.OrdinalIgnoreCase)) + { + library = null; + + return false; + } + + library = Libraries.FirstOrDefault(l => string.Equals(path, l.Path, StringComparison.OrdinalIgnoreCase)); + + return library is not null; + } + + /// + public async Task CreateNewLibrary(string name) + { + if (string.IsNullOrWhiteSpace(name) || !CanCreateLibrary(name).result) + return false; + + var newLib = new LibraryLocationItem(await Win32Helper.StartSTATask(() => + { + try + { + using var library = new ShellLibraryEx(name, Shell32.KNOWNFOLDERID.FOLDERID_Libraries, false); + library.Folders.Add(ShellItem.Open(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments))); // Add default folder so it's not empty + library.Commit(); + library.Reload(); + return Task.FromResult(ShellFolderExtensions.GetShellLibraryItem(library, library.GetDisplayName(ShellItemDisplayString.DesktopAbsoluteParsing))); + } + catch (Exception e) + { + App.Logger.LogWarning(e, null); + } + + return Task.FromResult(null); + })); + + if (newLib is not null) + { + lock (_Libraries) + _Libraries.Add(newLib); + + DataChanged?.Invoke(SectionType.Library, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newLib)); + + return true; + } + + return false; + } + + /// public (bool result, string reason) CanCreateLibrary(string name) { if (string.IsNullOrWhiteSpace(name)) - { return (false, "ErrorInputEmpty".GetLocalizedResource()); - } + if (FilesystemHelpers.ContainsRestrictedCharacters(name)) - { return (false, "ErrorNameInputRestrictedCharacters".GetLocalizedResource()); - } + if (FilesystemHelpers.ContainsRestrictedFileName(name)) - { return (false, "ErrorNameInputRestricted".GetLocalizedResource()); - } + if (Libraries.Any((item) => string.Equals(name, item.Text, StringComparison.OrdinalIgnoreCase) || string.Equals(name, Path.GetFileNameWithoutExtension(item.Path), StringComparison.OrdinalIgnoreCase))) - { return (false, "CreateLibraryErrorAlreadyExists".GetLocalizedResource()); - } - return (true, string.Empty); - } - public static async Task ShowRestoreDefaultLibrariesDialogAsync() - { - var dialog = new DynamicDialog(new DynamicDialogViewModel - { - TitleText = "DialogRestoreLibrariesTitleText".GetLocalizedResource(), - SubtitleText = "DialogRestoreLibrariesSubtitleText".GetLocalizedResource(), - PrimaryButtonText = "Restore".GetLocalizedResource(), - CloseButtonText = "Cancel".GetLocalizedResource(), - PrimaryButtonAction = async (vm, e) => - { - await ContextMenu.InvokeVerb("restorelibraries", ShellLibraryItem.LibrariesPath); - await App.LibraryManager.UpdateLibrariesAsync(); - }, - CloseButtonAction = (vm, e) => vm.HideDialog(), - KeyDownAction = (vm, e) => - { - if (e.Key == VirtualKey.Escape) - { - vm.HideDialog(); - } - }, - DynamicButtons = DynamicDialogButtons.Primary | DynamicDialogButtons.Cancel - }); - await dialog.ShowAsync(); + return (true, string.Empty); } - public static async Task ShowCreateNewLibraryDialogAsync() + public bool IsLibraryPath(string path) { - var inputText = new TextBox - { - PlaceholderText = "FolderWidgetCreateNewLibraryInputPlaceholderText".GetLocalizedResource() - }; - var tipText = new TextBlock - { - Text = string.Empty, - Visibility = Visibility.Collapsed - }; - - var dialog = new DynamicDialog(new DynamicDialogViewModel - { - DisplayControl = new Grid - { - Children = - { - new StackPanel - { - Spacing = 4d, - Children = - { - inputText, - tipText - } - } - } - }, - TitleText = "FolderWidgetCreateNewLibraryDialogTitleText".GetLocalizedResource(), - SubtitleText = "SideBarCreateNewLibrary/Text".GetLocalizedResource(), - PrimaryButtonText = "Create".GetLocalizedResource(), - CloseButtonText = "Cancel".GetLocalizedResource(), - PrimaryButtonAction = async (vm, e) => - { - var (result, reason) = App.LibraryManager.CanCreateLibrary(inputText.Text); - tipText.Text = reason; - tipText.Visibility = result ? Visibility.Collapsed : Visibility.Visible; - if (!result) - { - e.Cancel = true; - return; - } - await App.LibraryManager.CreateNewLibrary(inputText.Text); - }, - CloseButtonAction = (vm, e) => - { - vm.HideDialog(); - }, - KeyDownAction = async (vm, e) => - { - if (e.Key == VirtualKey.Enter) - { - await App.LibraryManager.CreateNewLibrary(inputText.Text); - } - else if (e.Key == VirtualKey.Escape) - { - vm.HideDialog(); - } - }, - DynamicButtons = DynamicDialogButtons.Primary | DynamicDialogButtons.Cancel - }); - await dialog.ShowAsync(); + return !string.IsNullOrEmpty(path) && path.EndsWith(ShellLibraryItem.EXTENSION, StringComparison.OrdinalIgnoreCase); } private void OnLibraryChanged(WatcherChangeTypes changeType, string oldPath, string newPath) { if (newPath is not null && (!newPath.ToLowerInvariant().EndsWith(ShellLibraryItem.EXTENSION, StringComparison.Ordinal) || !File.Exists(newPath))) { - System.Diagnostics.Debug.WriteLine($"Ignored library event: {changeType}, {oldPath} -> {newPath}"); + Debug.WriteLine($"Ignored library event: {changeType}, {oldPath} -> {newPath}"); return; } - System.Diagnostics.Debug.WriteLine($"Library event: {changeType}, {oldPath} -> {newPath}"); + Debug.WriteLine($"Library event: {changeType}, {oldPath} -> {newPath}"); if (!changeType.HasFlag(WatcherChangeTypes.Deleted)) { @@ -387,26 +276,24 @@ private void OnLibraryChanged(WatcherChangeTypes changeType, string oldPath, str string? path = oldPath; if (string.IsNullOrEmpty(oldPath)) - { path = library1?.FullPath; - } + var changedLibrary = Libraries.FirstOrDefault(l => string.Equals(l.Path, path, StringComparison.OrdinalIgnoreCase)); if (changedLibrary is not null) { - lock (libraries) - { - libraries.Remove(changedLibrary); - } + lock (_Libraries) + _Libraries.Remove(changedLibrary); + DataChanged?.Invoke(SectionType.Library, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, changedLibrary)); } + // library is null in case it was deleted if (library is not null && !Libraries.Any(x => x.Path == library1?.FullPath)) { var libItem = new LibraryLocationItem(library1); - lock (libraries) - { - libraries.Add(libItem); - } + lock (_Libraries) + _Libraries.Add(libItem); + DataChanged?.Invoke(SectionType.Library, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, libItem)); } @@ -429,12 +316,13 @@ private void OnLibraryChanged(object sender, FileSystemEventArgs e) } private void OnLibraryRenamed(object sender, RenamedEventArgs e) - => OnLibraryChanged(e.ChangeType, e.OldFullPath, e.FullPath); - - public static bool IsLibraryPath(string path) - => !string.IsNullOrEmpty(path) && path.EndsWith(ShellLibraryItem.EXTENSION, StringComparison.OrdinalIgnoreCase); + { + OnLibraryChanged(e.ChangeType, e.OldFullPath, e.FullPath); + } public void Dispose() - => librariesWatcher?.Dispose(); + { + _librariesWatcher?.Dispose(); + } } -} \ No newline at end of file +} diff --git a/src/Files.App/Utils/Archives/CompressHelper.cs b/src/Files.App/Utils/Archives/CompressHelper.cs index 800e044ffb6e..cb395657c1dc 100644 --- a/src/Files.App/Utils/Archives/CompressHelper.cs +++ b/src/Files.App/Utils/Archives/CompressHelper.cs @@ -51,7 +51,7 @@ public static (string[] Sources, string directory, string fileName) GetCompressD string directory = associatedInstance.FilesystemViewModel.WorkingDirectory.Normalize(); - if (App.LibraryManager.TryGetLibrary(directory, out var library) && !library.IsEmpty) + if (App.WindowsLibraryService.TryGetLibrary(directory, out var library) && !library.IsEmpty) directory = library.DefaultSaveFolder; string fileName = Path.GetFileName(sources.Length is 1 ? sources[0] : directory); diff --git a/src/Files.App/Utils/Storage/Enumerators/UniversalStorageEnumerator.cs b/src/Files.App/Utils/Storage/Enumerators/UniversalStorageEnumerator.cs index f4438f851471..89c7a11fa492 100644 --- a/src/Files.App/Utils/Storage/Enumerators/UniversalStorageEnumerator.cs +++ b/src/Files.App/Utils/Storage/Enumerators/UniversalStorageEnumerator.cs @@ -260,7 +260,7 @@ public static async Task AddFileAsync( return null; // TODO: is this needed to be handled here? - if (App.LibraryManager.TryGetLibrary(file.Path, out LibraryLocationItem library)) + if (App.WindowsLibraryService.TryGetLibrary(file.Path, out LibraryLocationItem library)) { return new LibraryItem(library) { diff --git a/src/Files.App/Utils/Storage/Enumerators/Win32StorageEnumerator.cs b/src/Files.App/Utils/Storage/Enumerators/Win32StorageEnumerator.cs index 442efba22bba..2e75d6a80f89 100644 --- a/src/Files.App/Utils/Storage/Enumerators/Win32StorageEnumerator.cs +++ b/src/Files.App/Utils/Storage/Enumerators/Win32StorageEnumerator.cs @@ -329,7 +329,7 @@ CancellationToken cancellationToken IsUrl = isUrl, }; } - else if (App.LibraryManager.TryGetLibrary(itemPath, out LibraryLocationItem library)) + else if (App.WindowsLibraryService.TryGetLibrary(itemPath, out LibraryLocationItem library)) { return new LibraryItem(library) { diff --git a/src/Files.App/Utils/Storage/Search/FolderSearch.cs b/src/Files.App/Utils/Storage/Search/FolderSearch.cs index 229230dfd4be..45de1c346ccd 100644 --- a/src/Files.App/Utils/Storage/Search/FolderSearch.cs +++ b/src/Files.App/Utils/Storage/Search/FolderSearch.cs @@ -72,7 +72,7 @@ public Task SearchAsync(IList results, CancellationToken token) { try { - if (App.LibraryManager.TryGetLibrary(Folder, out var library)) + if (App.WindowsLibraryService.TryGetLibrary(Folder, out var library)) { return AddItemsForLibraryAsync(library, results, token); } @@ -114,7 +114,7 @@ public async Task> SearchAsync() try { var token = CancellationToken.None; - if (App.LibraryManager.TryGetLibrary(Folder, out var library)) + if (App.WindowsLibraryService.TryGetLibrary(Folder, out var library)) { await AddItemsForLibraryAsync(library, results, token); } diff --git a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs index ac792a8b34b9..deb5d0845f87 100644 --- a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs @@ -19,6 +19,7 @@ using Windows.UI.Core; using Files.Core.Storage; using Files.Core.Storage.Extensions; +using Files.App.Dialogs; namespace Files.App.ViewModels.UserControls { @@ -246,7 +247,7 @@ public SidebarViewModel() Manager_DataChanged(SectionType.FileTag, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); App.QuickAccessManager.Model.DataChanged += Manager_DataChanged; - App.LibraryManager.DataChanged += Manager_DataChanged; + App.WindowsLibraryService.DataChanged += Manager_DataChanged; drivesViewModel.Drives.CollectionChanged += (x, args) => Manager_DataChanged(SectionType.Drives, args); CloudDrivesManager.DataChanged += Manager_DataChanged; NetworkDrivesService.Drives.CollectionChanged += (x, args) => Manager_DataChanged(SectionType.Network, args); @@ -284,7 +285,7 @@ await dispatcherQueue.EnqueueOrInvokeAsync(async () => SectionType.Drives => drivesViewModel.Drives.Cast().ToList().AsReadOnly(), SectionType.Network => NetworkDrivesService.Drives.Cast().ToList().AsReadOnly(), SectionType.WSL => WSLDistroManager.Distros, - SectionType.Library => App.LibraryManager.Libraries, + SectionType.Library => App.WindowsLibraryService.Libraries, SectionType.FileTag => App.FileTagsManager.FileTags, _ => null }; @@ -580,7 +581,7 @@ public async Task UpdateSectionVisibilityAsync(SectionType sectionType, bool sho SectionType.Network when generalSettingsService.ShowNetworkDrivesSection => NetworkDrivesService.UpdateDrivesAsync, SectionType.WSL when generalSettingsService.ShowWslSection => WSLDistroManager.UpdateDrivesAsync, SectionType.FileTag when generalSettingsService.ShowFileTagsSection => App.FileTagsManager.UpdateFileTagsAsync, - SectionType.Library => App.LibraryManager.UpdateLibrariesAsync, + SectionType.Library => App.WindowsLibraryService.UpdateLibrariesAsync, SectionType.Pinned => App.QuickAccessManager.Model.AddAllItemsToSidebarAsync, _ => () => Task.CompletedTask }; @@ -641,7 +642,7 @@ public void Dispose() UserSettingsService.OnSettingChangedEvent -= UserSettingsService_OnSettingChangedEvent; App.QuickAccessManager.Model.DataChanged -= Manager_DataChanged; - App.LibraryManager.DataChanged -= Manager_DataChanged; + App.WindowsLibraryService.DataChanged -= Manager_DataChanged; drivesViewModel.Drives.CollectionChanged -= (x, args) => Manager_DataChanged(SectionType.Drives, args); CloudDrivesManager.DataChanged -= Manager_DataChanged; NetworkDrivesService.Drives.CollectionChanged -= (x, args) => Manager_DataChanged(SectionType.Network, args); @@ -792,9 +793,9 @@ public async void HandleItemInvokedAsync(object item, PointerUpdateKind pointerU shellPage.NavigateToPath(navigationPath, sourcePageType); } - public readonly ICommand CreateLibraryCommand = new AsyncRelayCommand(LibraryManager.ShowCreateNewLibraryDialogAsync); + public readonly ICommand CreateLibraryCommand = new AsyncRelayCommand(ShowCreateNewLibraryDialogAsync); - public readonly ICommand RestoreLibrariesCommand = new AsyncRelayCommand(LibraryManager.ShowRestoreDefaultLibrariesDialogAsync); + public readonly ICommand RestoreLibrariesCommand = new AsyncRelayCommand(ShowRestoreDefaultLibrariesDialogAsync); private ICommand HideSectionCommand { get; } @@ -932,6 +933,97 @@ private void FormatDrive() Win32Helper.OpenFormatDriveDialog(rightClickedItem.Path); } + public static async Task ShowRestoreDefaultLibrariesDialogAsync() + { + var dialog = new DynamicDialog(new DynamicDialogViewModel + { + TitleText = "DialogRestoreLibrariesTitleText".GetLocalizedResource(), + SubtitleText = "DialogRestoreLibrariesSubtitleText".GetLocalizedResource(), + PrimaryButtonText = "Restore".GetLocalizedResource(), + CloseButtonText = "Cancel".GetLocalizedResource(), + PrimaryButtonAction = async (vm, e) => + { + await ContextMenu.InvokeVerb("restorelibraries", ShellLibraryItem.LibrariesPath); + await App.WindowsLibraryService.UpdateLibrariesAsync(); + }, + CloseButtonAction = (vm, e) => vm.HideDialog(), + KeyDownAction = (vm, e) => + { + if (e.Key == VirtualKey.Escape) + { + vm.HideDialog(); + } + }, + DynamicButtons = DynamicDialogButtons.Primary | DynamicDialogButtons.Cancel + }); + await dialog.ShowAsync(); + } + + public static async Task ShowCreateNewLibraryDialogAsync() + { + var inputText = new TextBox + { + PlaceholderText = "FolderWidgetCreateNewLibraryInputPlaceholderText".GetLocalizedResource() + }; + var tipText = new TextBlock + { + Text = string.Empty, + Visibility = Visibility.Collapsed + }; + + var dialog = new DynamicDialog(new DynamicDialogViewModel + { + DisplayControl = new Grid + { + Children = + { + new StackPanel + { + Spacing = 4d, + Children = + { + inputText, + tipText + } + } + } + }, + TitleText = "FolderWidgetCreateNewLibraryDialogTitleText".GetLocalizedResource(), + SubtitleText = "SideBarCreateNewLibrary/Text".GetLocalizedResource(), + PrimaryButtonText = "Create".GetLocalizedResource(), + CloseButtonText = "Cancel".GetLocalizedResource(), + PrimaryButtonAction = async (vm, e) => + { + var (result, reason) = App.WindowsLibraryService.CanCreateLibrary(inputText.Text); + tipText.Text = reason; + tipText.Visibility = result ? Visibility.Collapsed : Visibility.Visible; + if (!result) + { + e.Cancel = true; + return; + } + await App.WindowsLibraryService.CreateNewLibrary(inputText.Text); + }, + CloseButtonAction = (vm, e) => + { + vm.HideDialog(); + }, + KeyDownAction = async (vm, e) => + { + if (e.Key == VirtualKey.Enter) + { + await App.WindowsLibraryService.CreateNewLibrary(inputText.Text); + } + else if (e.Key == VirtualKey.Escape) + { + vm.HideDialog(); + } + }, + DynamicButtons = DynamicDialogButtons.Primary | DynamicDialogButtons.Cancel + }); + await dialog.ShowAsync(); + } + private List GetLocationItemMenuItems(INavigationControlItem item, CommandBarFlyout menu) { var options = item.MenuOptions; diff --git a/src/Files.App/Views/Layouts/BaseLayoutPage.cs b/src/Files.App/Views/Layouts/BaseLayoutPage.cs index 526bbe65182f..34d2b977c4c3 100644 --- a/src/Files.App/Views/Layouts/BaseLayoutPage.cs +++ b/src/Files.App/Views/Layouts/BaseLayoutPage.cs @@ -422,7 +422,7 @@ protected override async void OnNavigatedTo(NavigationEventArgs e) ParentShellPageInstance.InstanceViewModel.IsPageTypeMtpDevice = workingDir.StartsWith("\\\\?\\", StringComparison.Ordinal); ParentShellPageInstance.InstanceViewModel.IsPageTypeFtp = FtpHelpers.IsFtpPath(workingDir); ParentShellPageInstance.InstanceViewModel.IsPageTypeZipFolder = ZipStorageFolder.IsZipPath(workingDir); - ParentShellPageInstance.InstanceViewModel.IsPageTypeLibrary = LibraryManager.IsLibraryPath(workingDir); + ParentShellPageInstance.InstanceViewModel.IsPageTypeLibrary = App.WindowsLibraryService.IsLibraryPath(workingDir); ParentShellPageInstance.InstanceViewModel.IsPageTypeSearchResults = false; ParentShellPageInstance.ToolbarViewModel.PathControlDisplayText = navigationArguments.NavPathParam; @@ -455,12 +455,12 @@ protected override async void OnNavigatedTo(NavigationEventArgs e) ParentShellPageInstance.InstanceViewModel.IsPageTypeMtpDevice = workingDir.StartsWith("\\\\?\\", StringComparison.Ordinal); ParentShellPageInstance.InstanceViewModel.IsPageTypeFtp = FtpHelpers.IsFtpPath(workingDir); ParentShellPageInstance.InstanceViewModel.IsPageTypeZipFolder = ZipStorageFolder.IsZipPath(workingDir); - ParentShellPageInstance.InstanceViewModel.IsPageTypeLibrary = LibraryManager.IsLibraryPath(workingDir); + ParentShellPageInstance.InstanceViewModel.IsPageTypeLibrary = App.WindowsLibraryService.IsLibraryPath(workingDir); ParentShellPageInstance.InstanceViewModel.IsPageTypeSearchResults = true; if (!navigationArguments.IsLayoutSwitch) { - var displayName = App.LibraryManager.TryGetLibrary(navigationArguments.SearchPathParam, out var lib) ? lib.Text : navigationArguments.SearchPathParam; + var displayName = App.WindowsLibraryService.TryGetLibrary(navigationArguments.SearchPathParam, out var lib) ? lib.Text : navigationArguments.SearchPathParam; await ParentShellPageInstance.UpdatePathUIToWorkingDirectoryAsync(null, string.Format("SearchPagePathBoxOverrideText".GetLocalizedResource(), navigationArguments.SearchQuery, displayName)); var searchInstance = new Utils.Storage.FolderSearch { diff --git a/src/Files.App/Views/Layouts/ColumnsLayoutPage.xaml.cs b/src/Files.App/Views/Layouts/ColumnsLayoutPage.xaml.cs index 8b11c25f4f21..e4791e836884 100644 --- a/src/Files.App/Views/Layouts/ColumnsLayoutPage.xaml.cs +++ b/src/Files.App/Views/Layouts/ColumnsLayoutPage.xaml.cs @@ -98,7 +98,7 @@ protected override void OnNavigatedTo(NavigationEventArgs eventArgs) { var rootPathList = App.QuickAccessManager.Model.PinnedFolders.Select(NormalizePath) .Concat(CloudDrivesManager.Drives.Select(x => NormalizePath(x.Path))).ToList() - .Concat(App.LibraryManager.Libraries.Select(x => NormalizePath(x.Path))).ToList(); + .Concat(App.WindowsLibraryService.Libraries.Select(x => NormalizePath(x.Path))).ToList(); rootPathList.Add(NormalizePath(pathRoot)); while (!rootPathList.Contains(NormalizePath(path))) diff --git a/src/Files.App/Views/Properties/GeneralPage.xaml.cs b/src/Files.App/Views/Properties/GeneralPage.xaml.cs index 1238d99e5271..f6788d6d1d5e 100644 --- a/src/Files.App/Views/Properties/GeneralPage.xaml.cs +++ b/src/Files.App/Views/Properties/GeneralPage.xaml.cs @@ -102,7 +102,7 @@ bool SaveDrive(DriveItem drive) async Task SaveLibraryAsync(LibraryItem library) { var fsVM = AppInstance.FilesystemViewModel; - if (!GetNewName(out var newName) || fsVM is null || !App.LibraryManager.CanCreateLibrary(newName).result) + if (!GetNewName(out var newName) || fsVM is null || !App.WindowsLibraryService.CanCreateLibrary(newName).result) return false; newName = $"{newName}{ShellLibraryItem.EXTENSION}"; diff --git a/src/Files.App/Views/Properties/LibraryPage.xaml.cs b/src/Files.App/Views/Properties/LibraryPage.xaml.cs index 3eccdf7d8e9f..f9f31a31bbae 100644 --- a/src/Files.App/Views/Properties/LibraryPage.xaml.cs +++ b/src/Files.App/Views/Properties/LibraryPage.xaml.cs @@ -199,7 +199,7 @@ public override async Task SaveChangesAsync() using var dialog = DynamicDialogFactory.GetFor_PropertySaveErrorDialog(); try { - var library = await Task.Run(() => App.LibraryManager.UpdateLibrary(props.Library.ItemPath, newDefaultSaveFolder, newFolders, newIsPinned)); + var library = await Task.Run(() => App.WindowsLibraryService.UpdateLibraryAsync(props.Library.ItemPath, newDefaultSaveFolder, newFolders, newIsPinned)); if (library is not null) { props.UpdateLibrary(new LibraryItem(library));