diff --git a/src/Files.App.Storage/FtpStorage/FtpStorable.cs b/src/Files.App.Storage/FtpStorage/FtpStorable.cs index 8fdb51c2180f..75ec533953c9 100644 --- a/src/Files.App.Storage/FtpStorage/FtpStorable.cs +++ b/src/Files.App.Storage/FtpStorage/FtpStorable.cs @@ -1,37 +1,43 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. +using Files.Core.Storage; using Files.Core.Storage.LocatableStorage; -using Files.Shared.Helpers; +using Files.Core.Storage.NestedStorage; using FluentFTP; using System.Threading; using System.Threading.Tasks; namespace Files.App.Storage.FtpStorage { - public abstract class FtpStorable : ILocatableStorable + public abstract class FtpStorable : ILocatableStorable, INestedStorable { - private string? _computedId; - /// - public string Path { get; protected set; } + public virtual string Path { get; protected set; } /// - public string Name { get; protected set; } + public virtual string Name { get; protected set; } /// - public virtual string Id => _computedId ??= ChecksumHelpers.CalculateChecksumForPath(Path); + public virtual string Id { get; } + + /// + /// Gets the parent folder of the storable, if any. + /// + protected virtual IFolder? Parent { get; } - protected internal FtpStorable(string path, string name) + protected internal FtpStorable(string path, string name, IFolder? parent) { Path = FtpHelpers.GetFtpPath(path); Name = name; + Id = Path; + Parent = parent; } /// - public virtual Task GetParentAsync(CancellationToken cancellationToken = default) + public Task GetParentAsync(CancellationToken cancellationToken = default) { - return Task.FromResult(null); + return Task.FromResult(Parent); } protected AsyncFtpClient GetFtpClient() diff --git a/src/Files.App.Storage/FtpStorage/FtpStorageFile.cs b/src/Files.App.Storage/FtpStorage/FtpStorageFile.cs index 19db8d6ffa98..55ed99f60083 100644 --- a/src/Files.App.Storage/FtpStorage/FtpStorageFile.cs +++ b/src/Files.App.Storage/FtpStorage/FtpStorageFile.cs @@ -1,8 +1,10 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. +using Files.Core.Storage; using Files.Core.Storage.LocatableStorage; using Files.Core.Storage.ModifiableStorage; +using Files.Core.Storage.NestedStorage; using System; using System.IO; using System.Threading; @@ -10,10 +12,10 @@ namespace Files.App.Storage.FtpStorage { - public sealed class FtpStorageFile : FtpStorable, IModifiableFile, ILocatableFile + public sealed class FtpStorageFile : FtpStorable, IModifiableFile, ILocatableFile, INestedFile { - public FtpStorageFile(string path, string name) - : base(path, name) + public FtpStorageFile(string path, string name, IFolder? parent) + : base(path, name, parent) { } @@ -24,17 +26,11 @@ public async Task OpenStreamAsync(FileAccess access, CancellationToken c await ftpClient.EnsureConnectedAsync(cancellationToken); if (access.HasFlag(FileAccess.Write)) - { return await ftpClient.OpenWrite(Path, token: cancellationToken); - } else if (access.HasFlag(FileAccess.Read)) - { return await ftpClient.OpenRead(Path, token: cancellationToken); - } else - { throw new ArgumentException($"Invalid {nameof(access)} flag."); - } } } } diff --git a/src/Files.App.Storage/FtpStorage/FtpStorageFolder.cs b/src/Files.App.Storage/FtpStorage/FtpStorageFolder.cs index 3fa1e49902d3..448251578250 100644 --- a/src/Files.App.Storage/FtpStorage/FtpStorageFolder.cs +++ b/src/Files.App.Storage/FtpStorage/FtpStorageFolder.cs @@ -2,10 +2,13 @@ // Licensed under the MIT License. See the LICENSE. using Files.Core.Storage; +using Files.Core.Storage.DirectStorage; using Files.Core.Storage.Enums; +using Files.Core.Storage.ExtendableStorage; using Files.Core.Storage.Extensions; using Files.Core.Storage.LocatableStorage; using Files.Core.Storage.ModifiableStorage; +using Files.Core.Storage.NestedStorage; using Files.Shared.Helpers; using FluentFTP; using System; @@ -17,15 +20,15 @@ namespace Files.App.Storage.FtpStorage { - public sealed class FtpStorageFolder : FtpStorable, ILocatableFolder, IModifiableFolder + public sealed class FtpStorageFolder : FtpStorable, ILocatableFolder, IModifiableFolder, IFolderExtended, INestedFolder, IDirectCopy, IDirectMove { - public FtpStorageFolder(string path, string name) - : base(path, name) + public FtpStorageFolder(string path, string name, IFolder? parent) + : base(path, name, parent) { } /// - public async Task GetFileAsync(string fileName, CancellationToken cancellationToken = default) + public async Task GetFileAsync(string fileName, CancellationToken cancellationToken = default) { using var ftpClient = GetFtpClient(); await ftpClient.EnsureConnectedAsync(cancellationToken); @@ -36,11 +39,11 @@ public async Task GetFileAsync(string fileName, CancellationToken cancell if (item is null || item.Type != FtpObjectType.File) throw new FileNotFoundException(); - return new FtpStorageFile(path, item.Name); + return new FtpStorageFile(path, item.Name, this); } /// - public async Task GetFolderAsync(string folderName, CancellationToken cancellationToken = default) + public async Task GetFolderAsync(string folderName, CancellationToken cancellationToken = default) { using var ftpClient = GetFtpClient(); await ftpClient.EnsureConnectedAsync(cancellationToken); @@ -51,11 +54,11 @@ public async Task GetFolderAsync(string folderName, CancellationToken c if (item is null || item.Type != FtpObjectType.Directory) throw new DirectoryNotFoundException(); - return new FtpStorageFolder(path, item.Name); + return new FtpStorageFolder(path, item.Name, this); } /// - public async IAsyncEnumerable GetItemsAsync(StorableKind kind = StorableKind.All, [EnumeratorCancellation] CancellationToken cancellationToken = default) + public async IAsyncEnumerable GetItemsAsync(StorableKind kind = StorableKind.All, [EnumeratorCancellation] CancellationToken cancellationToken = default) { using var ftpClient = GetFtpClient(); await ftpClient.EnsureConnectedAsync(cancellationToken); @@ -65,7 +68,7 @@ public async IAsyncEnumerable GetItemsAsync(StorableKind kind = Stora foreach (var item in await ftpClient.GetListing(Path, cancellationToken)) { if (item.Type == FtpObjectType.File) - yield return new FtpStorageFile(item.FullName, item.Name); + yield return new FtpStorageFile(item.FullName, item.Name, this); } } else if (kind == StorableKind.Folders) @@ -73,7 +76,7 @@ public async IAsyncEnumerable GetItemsAsync(StorableKind kind = Stora foreach (var item in await ftpClient.GetListing(Path, cancellationToken)) { if (item.Type == FtpObjectType.Directory) - yield return new FtpStorageFolder(item.FullName, item.Name); + yield return new FtpStorageFolder(item.FullName, item.Name, this); } } else @@ -81,16 +84,16 @@ public async IAsyncEnumerable GetItemsAsync(StorableKind kind = Stora foreach (var item in await ftpClient.GetListing(Path, cancellationToken)) { if (item.Type == FtpObjectType.File) - yield return new FtpStorageFile(item.FullName, item.Name); + yield return new FtpStorageFile(item.FullName, item.Name, this); if (item.Type == FtpObjectType.Directory) - yield return new FtpStorageFolder(item.FullName, item.Name); + yield return new FtpStorageFolder(item.FullName, item.Name, this); } } } /// - public async Task DeleteAsync(IStorable item, bool permanently = false, CancellationToken cancellationToken = default) + public async Task DeleteAsync(INestedStorable item, bool permanently = false, CancellationToken cancellationToken = default) { using var ftpClient = GetFtpClient(); await ftpClient.EnsureConnectedAsync(cancellationToken); @@ -110,11 +113,11 @@ public async Task DeleteAsync(IStorable item, bool permanently = false, Cancella } /// - public async Task CreateCopyOfAsync(IStorable itemToCopy, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default) + public async Task CreateCopyOfAsync(INestedStorable itemToCopy, bool overwrite = default, CancellationToken cancellationToken = default) { if (itemToCopy is IFile sourceFile) { - var copiedFile = await CreateFileAsync(itemToCopy.Name, collisionOption, cancellationToken); + var copiedFile = await CreateFileAsync(itemToCopy.Name, overwrite, cancellationToken); await sourceFile.CopyContentsToAsync(copiedFile, cancellationToken); return copiedFile; @@ -126,41 +129,34 @@ public async Task CreateCopyOfAsync(IStorable itemToCopy, CreationCol } /// - public async Task MoveFromAsync(IStorable itemToMove, IModifiableFolder source, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default) + public async Task MoveFromAsync(INestedStorable itemToMove, IModifiableFolder source, bool overwrite = default, CancellationToken cancellationToken = default) { using var ftpClient = GetFtpClient(); await ftpClient.EnsureConnectedAsync(cancellationToken); - var newItem = await CreateCopyOfAsync(itemToMove, collisionOption, cancellationToken); + var newItem = await CreateCopyOfAsync(itemToMove, overwrite, cancellationToken); await source.DeleteAsync(itemToMove, true, cancellationToken); return newItem; } /// - public async Task CreateFileAsync(string desiredName, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default) + public async Task CreateFileAsync(string desiredName, bool overwrite = default, CancellationToken cancellationToken = default) { using var ftpClient = GetFtpClient(); await ftpClient.EnsureConnectedAsync(cancellationToken); var newPath = $"{Path}/{desiredName}"; - if (await ftpClient.FileExists(newPath, cancellationToken)) - { - if (collisionOption == CreationCollisionOption.FailIfExists) - throw new IOException("File already exists."); - - if (collisionOption == CreationCollisionOption.OpenIfExists) - return new FtpStorageFile(newPath, desiredName); - } + if (overwrite && await ftpClient.FileExists(newPath, cancellationToken)) + throw new IOException("File already exists."); using var stream = new MemoryStream(); - var replaceExisting = collisionOption == CreationCollisionOption.ReplaceExisting; - var result = await ftpClient.UploadStream(stream, newPath, replaceExisting ? FtpRemoteExists.Overwrite : FtpRemoteExists.Skip, token: cancellationToken); + var result = await ftpClient.UploadStream(stream, newPath, overwrite ? FtpRemoteExists.Overwrite : FtpRemoteExists.Skip, token: cancellationToken); if (result == FtpStatus.Success) { // Success - return new FtpStorageFile(newPath, desiredName); + return new FtpStorageFile(newPath, desiredName, this); } else if (result == FtpStatus.Skipped) { @@ -175,27 +171,20 @@ public async Task CreateFileAsync(string desiredName, CreationCollisionOp } /// - public async Task CreateFolderAsync(string desiredName, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default) + public async Task CreateFolderAsync(string desiredName, bool overwrite = default, CancellationToken cancellationToken = default) { using var ftpClient = GetFtpClient(); await ftpClient.EnsureConnectedAsync(cancellationToken); var newPath = $"{Path}/{desiredName}"; - if (await ftpClient.DirectoryExists(newPath, cancellationToken)) - { - if (collisionOption == CreationCollisionOption.FailIfExists) - throw new IOException("Directory already exists."); - - if (collisionOption == CreationCollisionOption.OpenIfExists) - return new FtpStorageFolder(newPath, desiredName); - } + if (overwrite && await ftpClient.DirectoryExists(newPath, cancellationToken)) + throw new IOException("Directory already exists."); - var replaceExisting = collisionOption == CreationCollisionOption.ReplaceExisting; - var isSuccessful = await ftpClient.CreateDirectory(newPath, replaceExisting, cancellationToken); + var isSuccessful = await ftpClient.CreateDirectory(newPath, overwrite, cancellationToken); if (!isSuccessful) throw new IOException("Directory was not successfully created."); - return new FtpStorageFolder(newPath, desiredName); + return new FtpStorageFolder(newPath, desiredName, this); } } } diff --git a/src/Files.App.Storage/FtpStorage/FtpStorageService.cs b/src/Files.App.Storage/FtpStorage/FtpStorageService.cs index 9ae28d928cce..f468fde30c81 100644 --- a/src/Files.App.Storage/FtpStorage/FtpStorageService.cs +++ b/src/Files.App.Storage/FtpStorage/FtpStorageService.cs @@ -4,70 +4,41 @@ using Files.Core.Storage; using Files.Core.Storage.LocatableStorage; using FluentFTP; -using System; using System.IO; using System.Threading; using System.Threading.Tasks; namespace Files.App.Storage.FtpStorage { + /// public sealed class FtpStorageService : IFtpStorageService { - public Task IsAccessibleAsync(CancellationToken cancellationToken = default) + /// + public async Task GetFolderAsync(string id, CancellationToken cancellationToken = default) { - return Task.FromResult(true); // TODO: Check if FTP is available - } - - public async Task FileExistsAsync(string path, CancellationToken cancellationToken = default) - { - try - { - _ = await GetFileFromPathAsync(path, cancellationToken); - return true; - } - catch (Exception) - { - return false; - } - } - - public async Task DirectoryExistsAsync(string path, CancellationToken cancellationToken = default) - { - try - { - _ = await GetFolderFromPathAsync(path, cancellationToken); - return true; - } - catch (Exception) - { - return false; - } - } - - public async Task GetFolderFromPathAsync(string path, CancellationToken cancellationToken = default) - { - using var ftpClient = FtpHelpers.GetFtpClient(path); + using var ftpClient = FtpHelpers.GetFtpClient(id); await ftpClient.EnsureConnectedAsync(cancellationToken); - var ftpPath = FtpHelpers.GetFtpPath(path); + var ftpPath = FtpHelpers.GetFtpPath(id); var item = await ftpClient.GetObjectInfo(ftpPath, token: cancellationToken); if (item is null || item.Type != FtpObjectType.Directory) throw new DirectoryNotFoundException("Directory was not found from path."); - return new FtpStorageFolder(ftpPath, item.Name); + return new FtpStorageFolder(ftpPath, item.Name, null); } - public async Task GetFileFromPathAsync(string path, CancellationToken cancellationToken = default) + /// + public async Task GetFileAsync(string id, CancellationToken cancellationToken = default) { - using var ftpClient = FtpHelpers.GetFtpClient(path); + using var ftpClient = FtpHelpers.GetFtpClient(id); await ftpClient.EnsureConnectedAsync(cancellationToken); - var ftpPath = FtpHelpers.GetFtpPath(path); + var ftpPath = FtpHelpers.GetFtpPath(id); var item = await ftpClient.GetObjectInfo(ftpPath, token: cancellationToken); if (item is null || item.Type != FtpObjectType.File) throw new FileNotFoundException("File was not found from path."); - return new FtpStorageFile(ftpPath, item.Name); + return new FtpStorageFile(ftpPath, item.Name, null); } } } diff --git a/src/Files.App.Storage/NativeStorage/NativeFile.cs b/src/Files.App.Storage/NativeStorage/NativeFile.cs index cb530030caed..a22e9b6eab57 100644 --- a/src/Files.App.Storage/NativeStorage/NativeFile.cs +++ b/src/Files.App.Storage/NativeStorage/NativeFile.cs @@ -5,6 +5,7 @@ using Files.Core.Storage.ExtendableStorage; using Files.Core.Storage.LocatableStorage; using Files.Core.Storage.ModifiableStorage; +using Files.Core.Storage.NestedStorage; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -12,21 +13,26 @@ namespace Files.App.Storage.NativeStorage { /// - public sealed class NativeFile : NativeStorable, ILocatableFile, IModifiableFile, IFileExtended + public class NativeFile : NativeStorable, ILocatableFile, IModifiableFile, IFileExtended, INestedFile { + public NativeFile(FileInfo fileInfo) + : base(fileInfo) + { + } + public NativeFile(string path) - : base(path) + : this(new FileInfo(path)) { } /// - public Task OpenStreamAsync(FileAccess access, CancellationToken cancellationToken = default) + public virtual Task OpenStreamAsync(FileAccess access, CancellationToken cancellationToken = default) { return OpenStreamAsync(access, FileShare.None, cancellationToken); } /// - public Task OpenStreamAsync(FileAccess access, FileShare share = FileShare.None, CancellationToken cancellationToken = default) + public virtual Task OpenStreamAsync(FileAccess access, FileShare share = FileShare.None, CancellationToken cancellationToken = default) { var stream = File.Open(Path, FileMode.Open, access, share); return Task.FromResult(stream); diff --git a/src/Files.App.Storage/NativeStorage/NativeFolder.cs b/src/Files.App.Storage/NativeStorage/NativeFolder.cs index 834d45529b35..fedea9a05bfb 100644 --- a/src/Files.App.Storage/NativeStorage/NativeFolder.cs +++ b/src/Files.App.Storage/NativeStorage/NativeFolder.cs @@ -2,11 +2,14 @@ // Licensed under the MIT License. See the LICENSE. using Files.Core.Storage; +using Files.Core.Storage.DirectStorage; using Files.Core.Storage.Enums; +using Files.Core.Storage.ExtendableStorage; using Files.Core.Storage.Extensions; using Files.Core.Storage.LocatableStorage; using Files.Core.Storage.ModifiableStorage; using Files.Core.Storage.MutableStorage; +using Files.Core.Storage.NestedStorage; using System; using System.Collections.Generic; using System.IO; @@ -16,65 +19,69 @@ namespace Files.App.Storage.NativeStorage { - /// - public sealed class NativeFolder : NativeStorable, ILocatableFolder, IModifiableFolder, IMutableFolder + /// + public class NativeFolder : NativeStorable, ILocatableFolder, IModifiableFolder, IMutableFolder, IFolderExtended, INestedFolder, IDirectCopy, IDirectMove { - public NativeFolder(string path) - : base(path) - { - } - - /// - public Task GetFileAsync(string fileName, CancellationToken cancellationToken = default) - { - var path = System.IO.Path.Combine(Path, fileName); - - if (!File.Exists(path)) - throw new FileNotFoundException(); - - return Task.FromResult(new NativeFile(path)); - } - - /// - public Task GetFolderAsync(string folderName, CancellationToken cancellationToken = default) - { - var path = System.IO.Path.Combine(Path, folderName); - - if (!Directory.Exists(path)) - throw new FileNotFoundException(); - - return Task.FromResult(new NativeFolder(path)); - } - - /// - public async IAsyncEnumerable GetItemsAsync(StorableKind kind = StorableKind.All, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - if (kind == StorableKind.Files) - { - foreach (var item in Directory.EnumerateFiles(Path)) - yield return new NativeFile(item); - } - else if (kind == StorableKind.Folders) - { - foreach (var item in Directory.EnumerateDirectories(Path)) - yield return new NativeFolder(item); - } - else - { - foreach (var item in Directory.EnumerateFileSystemEntries(Path)) - { - if (File.Exists(item)) - yield return new NativeFile(item); - else - yield return new NativeFolder(item); - } - } - - await Task.CompletedTask; - } - - /// - public Task DeleteAsync(IStorable item, bool permanently = false, CancellationToken cancellationToken = default) + public NativeFolder(DirectoryInfo directoryInfo) + : base(directoryInfo) + { + } + + public NativeFolder(string path) + : this(new DirectoryInfo(path)) + { + } + + /// + public virtual Task GetFileAsync(string fileName, CancellationToken cancellationToken = default) + { + var path = System.IO.Path.Combine(Path, fileName); + + if (!File.Exists(path)) + throw new FileNotFoundException(); + + return Task.FromResult(new NativeFile(path)); + } + + /// + public virtual Task GetFolderAsync(string folderName, CancellationToken cancellationToken = default) + { + var path = System.IO.Path.Combine(Path, folderName); + if (!Directory.Exists(path)) + throw new FileNotFoundException(); + + return Task.FromResult(new NativeFolder(path)); + } + + /// + public virtual async IAsyncEnumerable GetItemsAsync(StorableKind kind = StorableKind.All, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (kind == StorableKind.Files) + { + foreach (var item in Directory.EnumerateFiles(Path)) + yield return new NativeFile(item); + } + else if (kind == StorableKind.Folders) + { + foreach (var item in Directory.EnumerateDirectories(Path)) + yield return new NativeFolder(item); + } + else + { + foreach (var item in Directory.EnumerateFileSystemEntries(Path)) + { + if (File.Exists(item)) + yield return new NativeFile(item); + else + yield return new NativeFolder(item); + } + } + + await Task.CompletedTask; + } + + /// + public virtual Task DeleteAsync(INestedStorable item, bool permanently = false, CancellationToken cancellationToken = default) { _ = permanently; @@ -92,112 +99,86 @@ public Task DeleteAsync(IStorable item, bool permanently = false, CancellationTo return Task.CompletedTask; } - /// - public async Task CreateCopyOfAsync(IStorable itemToCopy, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default) - { - var overwrite = collisionOption == CreationCollisionOption.ReplaceExisting; - - if (itemToCopy is IFile sourceFile) - { - if (itemToCopy is ILocatableFile sourceLocatableFile) - { - var newPath = System.IO.Path.Combine(Path, itemToCopy.Name); - File.Copy(sourceLocatableFile.Path, newPath, overwrite); - - return new NativeFile(newPath); - } - - var copiedFile = await CreateFileAsync(itemToCopy.Name, collisionOption, cancellationToken); - await sourceFile.CopyContentsToAsync(copiedFile, cancellationToken); - - return copiedFile; - } - else if (itemToCopy is IFolder sourceFolder) - { - // TODO: Implement folder copy - throw new NotSupportedException(); - } - - throw new ArgumentException($"Could not copy type {itemToCopy.GetType()}"); - } - - /// - public async Task MoveFromAsync(IStorable itemToMove, IModifiableFolder source, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default) - { - var overwrite = collisionOption == CreationCollisionOption.ReplaceExisting; - - if (itemToMove is IFile sourceFile) - { - if (itemToMove is ILocatableFile sourceLocatableFile) - { - var newPath = System.IO.Path.Combine(Path, itemToMove.Name); - File.Move(sourceLocatableFile.Path, newPath, overwrite); - - return new NativeFile(newPath); - } - - var copiedFile = await CreateFileAsync(itemToMove.Name, collisionOption, cancellationToken); - await sourceFile.CopyContentsToAsync(copiedFile, cancellationToken); - await source.DeleteAsync(itemToMove, true, cancellationToken); - - return copiedFile; - } - else if (itemToMove is IFolder sourceFolder) - { - throw new NotImplementedException(); - } - - throw new ArgumentException($"Could not move type {itemToMove.GetType()}"); - } - - /// - public async Task CreateFileAsync(string desiredName, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default) - { - var path = System.IO.Path.Combine(Path, desiredName); - if (File.Exists(path)) - { - switch (collisionOption) - { - case CreationCollisionOption.GenerateUniqueName: - return await CreateFileAsync($"{System.IO.Path.GetFileNameWithoutExtension(desiredName)} (1){System.IO.Path.GetExtension(desiredName)}", collisionOption, cancellationToken); - - case CreationCollisionOption.OpenIfExists: - return new NativeFile(path); - - case CreationCollisionOption.FailIfExists: - throw new IOException("File already exists with the same name."); - } - } - - await File.Create(path).DisposeAsync(); - return new NativeFile(path); - } - - /// - public Task CreateFolderAsync(string desiredName, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default) - { - var path = System.IO.Path.Combine(Path, desiredName); - if (Directory.Exists(path)) - { - switch (collisionOption) - { - case CreationCollisionOption.GenerateUniqueName: - return CreateFolderAsync($"{desiredName} (1)", collisionOption, cancellationToken); - - case CreationCollisionOption.OpenIfExists: - return Task.FromResult(new NativeFolder(path)); - - case CreationCollisionOption.FailIfExists: - throw new IOException("Folder already exists with the same name."); - } - } - - _ = Directory.CreateDirectory(path); - return Task.FromResult(new NativeFolder(path)); - } - - /// - public Task GetFolderWatcherAsync(CancellationToken cancellationToken = default) + /// + public virtual async Task CreateCopyOfAsync(INestedStorable itemToCopy, bool overwrite = default, CancellationToken cancellationToken = default) + { + if (itemToCopy is IFile sourceFile) + { + if (itemToCopy is ILocatableFile sourceLocatableFile) + { + var newPath = System.IO.Path.Combine(Path, itemToCopy.Name); + File.Copy(sourceLocatableFile.Path, newPath, overwrite); + + return new NativeFile(newPath); + } + + var copiedFile = await CreateFileAsync(itemToCopy.Name, overwrite, cancellationToken); + await sourceFile.CopyContentsToAsync(copiedFile, cancellationToken); + + return copiedFile; + } + else if (itemToCopy is IFolder sourceFolder) + { + // TODO: Implement folder copy + _ = sourceFolder; + throw new NotSupportedException(); + } + + throw new ArgumentException($"Could not copy type {itemToCopy.GetType()}"); + } + + /// + public virtual async Task MoveFromAsync(INestedStorable itemToMove, IModifiableFolder source, bool overwrite = default, CancellationToken cancellationToken = default) + { + if (itemToMove is IFile sourceFile) + { + if (itemToMove is ILocatableFile sourceLocatableFile) + { + var newPath = System.IO.Path.Combine(Path, itemToMove.Name); + File.Move(sourceLocatableFile.Path, newPath, overwrite); + + return new NativeFile(newPath); + } + else + { + var copiedFile = await CreateFileAsync(itemToMove.Name, overwrite, cancellationToken); + await sourceFile.CopyContentsToAsync(copiedFile, cancellationToken); + await source.DeleteAsync(itemToMove, true, cancellationToken); + + return copiedFile; + } + } + else if (itemToMove is IFolder sourceFolder) + { + throw new NotImplementedException(); + } + + throw new ArgumentException($"Could not move type {itemToMove.GetType()}"); + } + + /// + public virtual async Task CreateFileAsync(string desiredName, bool overwrite = default, CancellationToken cancellationToken = default) + { + var path = System.IO.Path.Combine(Path, desiredName); + if (overwrite || !File.Exists(path)) + await File.Create(path).DisposeAsync(); + + return new NativeFile(path); + } + + /// + public virtual Task CreateFolderAsync(string desiredName, bool overwrite = default, CancellationToken cancellationToken = default) + { + var path = System.IO.Path.Combine(Path, desiredName); + if (overwrite) + Directory.Delete(path, true); + + _ = Directory.CreateDirectory(path); + return Task.FromResult(new NativeFolder(path)); + } + + /// + public Task GetFolderWatcherAsync(CancellationToken cancellationToken = default) { return Task.FromResult(new NativeFolderWatcher(this)); } diff --git a/src/Files.App.Storage/NativeStorage/NativeStorable.cs b/src/Files.App.Storage/NativeStorage/NativeStorable.cs index 9d36ce8c3e3e..1a8fbcc3ca7a 100644 --- a/src/Files.App.Storage/NativeStorage/NativeStorable.cs +++ b/src/Files.App.Storage/NativeStorage/NativeStorable.cs @@ -3,40 +3,62 @@ using Files.Core.Storage; using Files.Core.Storage.LocatableStorage; -using Files.Shared.Helpers; +using Files.Core.Storage.NestedStorage; +using System.IO; using System.Threading; using System.Threading.Tasks; namespace Files.App.Storage.NativeStorage { /// - public abstract class NativeStorable : ILocatableStorable + public abstract class NativeStorable : ILocatableStorable, INestedStorable + where TStorage : FileSystemInfo { - private string? _computedId; + protected readonly TStorage storage; /// - public string Path { get; protected set; } + public virtual string Path { get; protected set; } /// - public string Name { get; protected set; } + public virtual string Name { get; protected set; } /// - public virtual string Id => _computedId ??= ChecksumHelpers.CalculateChecksumForPath(Path); + public virtual string Id { get; } - protected NativeStorable(string path) + protected NativeStorable(TStorage storage) { - Path = path; - Name = System.IO.Path.GetFileName(path); + this.storage = storage; + Path = storage.FullName; + Name = storage.Name; + Id = storage.FullName; } /// - public virtual Task GetParentAsync(CancellationToken cancellationToken = default) + public virtual Task GetParentAsync(CancellationToken cancellationToken = default) { - var parentPath = System.IO.Path.GetDirectoryName(Path); - if (string.IsNullOrEmpty(parentPath)) - return Task.FromResult(null); + var parent = Directory.GetParent(Path); + if (parent is null) + return Task.FromResult(null); - return Task.FromResult(new NativeFolder(parentPath)); + return Task.FromResult(new NativeFolder(parent)); + } + + /// + /// Formats a given . + /// + /// The path to format. + /// A formatted path. + protected static string FormatPath(string path) + { + path = path.Replace("file:///", string.Empty); + + if ('/' != System.IO.Path.DirectorySeparatorChar) + return path.Replace('/', System.IO.Path.DirectorySeparatorChar); + + if ('\\' != System.IO.Path.DirectorySeparatorChar) + return path.Replace('\\', System.IO.Path.DirectorySeparatorChar); + + return path; } } } diff --git a/src/Files.App.Storage/NativeStorage/NativeStorageService.cs b/src/Files.App.Storage/NativeStorage/NativeStorageService.cs index 3b206414b28a..7e2326325dce 100644 --- a/src/Files.App.Storage/NativeStorage/NativeStorageService.cs +++ b/src/Files.App.Storage/NativeStorage/NativeStorageService.cs @@ -13,41 +13,21 @@ namespace Files.App.Storage.NativeStorage public sealed class NativeStorageService : IStorageService { /// - public Task IsAccessibleAsync(CancellationToken cancellationToken = default) + public Task GetFileAsync(string id, CancellationToken cancellationToken = default) { - return Task.FromResult(true); - } - - /// - public Task FileExistsAsync(string path, CancellationToken cancellationToken = default) - { - var fileExists = File.Exists(path); - return Task.FromResult(fileExists); - } - - /// - public Task DirectoryExistsAsync(string path, CancellationToken cancellationToken = default) - { - var directoryExists = Directory.Exists(path); - return Task.FromResult(directoryExists); - } - - /// - public Task GetFolderFromPathAsync(string path, CancellationToken cancellationToken = default) - { - if (!Directory.Exists(path)) - throw new DirectoryNotFoundException($"Directory for '{path}' was not found."); + if (!File.Exists(id)) + throw new FileNotFoundException(); - return Task.FromResult(new NativeFolder(path)); + return Task.FromResult(new NativeFile(id)); } /// - public Task GetFileFromPathAsync(string path, CancellationToken cancellationToken = default) + public Task GetFolderAsync(string id, CancellationToken cancellationToken = default) { - if (!File.Exists(path)) - throw new FileNotFoundException($"File for '{path}' was not found."); + if (!Directory.Exists(id)) + throw new DirectoryNotFoundException(); - return Task.FromResult(new NativeFile(path)); + return Task.FromResult(new NativeFolder(id)); } } } diff --git a/src/Files.App.Storage/WindowsStorage/WindowsStorable.cs b/src/Files.App.Storage/WindowsStorage/WindowsStorable.cs index d6309af73e4a..64a60577746b 100644 --- a/src/Files.App.Storage/WindowsStorage/WindowsStorable.cs +++ b/src/Files.App.Storage/WindowsStorage/WindowsStorable.cs @@ -3,6 +3,7 @@ using Files.Core.Storage; using Files.Core.Storage.LocatableStorage; +using Files.Core.Storage.NestedStorage; using Files.Shared.Helpers; using System.Threading; using System.Threading.Tasks; @@ -11,7 +12,7 @@ namespace Files.App.Storage.WindowsStorage { /// - public abstract class WindowsStorable : ILocatableStorable + public abstract class WindowsStorable : ILocatableStorable, INestedStorable where TStorage : class, IStorageItem { private string? _computedId; @@ -34,6 +35,6 @@ protected internal WindowsStorable(TStorage storage) } /// - public abstract Task GetParentAsync(CancellationToken cancellationToken = default); + public abstract Task GetParentAsync(CancellationToken cancellationToken = default); } } diff --git a/src/Files.App.Storage/WindowsStorage/WindowsStorageFile.cs b/src/Files.App.Storage/WindowsStorage/WindowsStorageFile.cs index d821965be987..a4ed6be48fbd 100644 --- a/src/Files.App.Storage/WindowsStorage/WindowsStorageFile.cs +++ b/src/Files.App.Storage/WindowsStorage/WindowsStorageFile.cs @@ -5,6 +5,7 @@ using Files.Core.Storage.ExtendableStorage; using Files.Core.Storage.LocatableStorage; using Files.Core.Storage.ModifiableStorage; +using Files.Core.Storage.NestedStorage; using System; using System.IO; using System.Threading; @@ -14,7 +15,7 @@ namespace Files.App.Storage.WindowsStorage { /// - public sealed class WindowsStorageFile : WindowsStorable, ILocatableFile, IModifiableFile, IFileExtended + public sealed class WindowsStorageFile : WindowsStorable, ILocatableFile, IModifiableFile, IFileExtended, INestedFile { public WindowsStorageFile(StorageFile storage) : base(storage) @@ -40,7 +41,7 @@ public async Task OpenStreamAsync(FileAccess access, FileShare share = F } /// - public override async Task GetParentAsync(CancellationToken cancellationToken = default) + public override async Task GetParentAsync(CancellationToken cancellationToken = default) { var parentFolderTask = storage.GetParentAsync().AsTask(cancellationToken); var parentFolder = await parentFolderTask; diff --git a/src/Files.App.Storage/WindowsStorage/WindowsStorageFolder.cs b/src/Files.App.Storage/WindowsStorage/WindowsStorageFolder.cs index bb0befc45298..9f217b79b0ad 100644 --- a/src/Files.App.Storage/WindowsStorage/WindowsStorageFolder.cs +++ b/src/Files.App.Storage/WindowsStorage/WindowsStorageFolder.cs @@ -11,94 +11,106 @@ using System.Threading; using System.Threading.Tasks; using Windows.Storage; -using CreationCollisionOption = Files.Core.Storage.Enums.CreationCollisionOption; +using Files.Core.Storage.DirectStorage; +using Files.Core.Storage.ExtendableStorage; +using Files.Core.Storage.NestedStorage; namespace Files.App.Storage.WindowsStorage { /// - public sealed class WindowsStorageFolder : WindowsStorable, ILocatableFolder, IModifiableFolder + public sealed class WindowsStorageFolder : WindowsStorable, ILocatableFolder, IFolderExtended, INestedFolder, IDirectCopy, IDirectMove { + // TODO: Implement IMutableFolder + public WindowsStorageFolder(StorageFolder storage) : base(storage) { } /// - public async Task GetFileAsync(string fileName, CancellationToken cancellationToken = default) + public async Task GetFileAsync(string fileName, CancellationToken cancellationToken = default) { - var fileTask = storage.GetFileAsync(fileName).AsTask(cancellationToken); - var file = await fileTask; - + var file = await storage.GetFileAsync(fileName).AsTask(cancellationToken); return new WindowsStorageFile(file); } /// - public async Task GetFolderAsync(string folderName, CancellationToken cancellationToken = default) + public async Task GetFolderAsync(string folderName, CancellationToken cancellationToken = default) { - var folderTask = storage.GetFolderAsync(folderName).AsTask(cancellationToken); - var folder = await folderTask; - + var folder = await storage.GetFolderAsync(folderName).AsTask(cancellationToken); return new WindowsStorageFolder(folder); } /// - public async IAsyncEnumerable GetItemsAsync(StorableKind kind = StorableKind.All, [EnumeratorCancellation] CancellationToken cancellationToken = default) + public async IAsyncEnumerable GetItemsAsync(StorableKind kind = StorableKind.All, [EnumeratorCancellation] CancellationToken cancellationToken = default) { - if (kind == StorableKind.Files) + switch (kind) { - var files = await storage.GetFilesAsync().AsTask(cancellationToken); - foreach (var item in files) + case StorableKind.Files: { - yield return new WindowsStorageFile(item); + var files = await storage.GetFilesAsync().AsTask(cancellationToken); + foreach (var item in files) + { + yield return new WindowsStorageFile(item); + } + + break; } - } - else if (kind == StorableKind.Folders) - { - var folders = await storage.GetFoldersAsync().AsTask(cancellationToken); - foreach (var item in folders) + + case StorableKind.Folders: { - yield return new WindowsStorageFolder(item); + var folders = await storage.GetFoldersAsync().AsTask(cancellationToken); + foreach (var item in folders) + { + yield return new WindowsStorageFolder(item); + } + + break; } - } - else - { - var items = await storage.GetItemsAsync().AsTask(cancellationToken); - foreach (var item in items) + + case StorableKind.All: { - if (item is StorageFile storageFile) - yield return new WindowsStorageFile(storageFile); + var items = await storage.GetItemsAsync().AsTask(cancellationToken); + foreach (var item in items) + { + if (item is StorageFile storageFile) + yield return new WindowsStorageFile(storageFile); + + if (item is StorageFolder storageFolder) + yield return new WindowsStorageFolder(storageFolder); + } - if (item is StorageFolder storageFolder) - yield return new WindowsStorageFolder(storageFolder); + break; } + + default: + yield break; } } /// - public async Task DeleteAsync(IStorable item, bool permanently = false, CancellationToken cancellationToken = default) + public Task DeleteAsync(INestedStorable item, bool permanently = default, CancellationToken cancellationToken = default) { - if (item is WindowsStorable storageFile) - { - await storageFile.storage.DeleteAsync(GetWindowsStorageDeleteOption(permanently)).AsTask(cancellationToken); - } - else if (item is WindowsStorable storageFolder) + return item switch { - await storageFolder.storage.DeleteAsync(GetWindowsStorageDeleteOption(permanently)).AsTask(cancellationToken); - } - else - { - throw new NotImplementedException(); - } + WindowsStorable storageFile => storageFile.storage + .DeleteAsync(GetWindowsStorageDeleteOption(permanently)) + .AsTask(cancellationToken), + + WindowsStorable storageFolder => storageFolder.storage + .DeleteAsync(GetWindowsStorageDeleteOption(permanently)) + .AsTask(cancellationToken), + + _ => throw new NotImplementedException() + }; } /// - public async Task CreateCopyOfAsync(IStorable itemToCopy, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default) + public async Task CreateCopyOfAsync(INestedStorable itemToCopy, bool overwrite = default, CancellationToken cancellationToken = default) { - if (itemToCopy is WindowsStorable storageFile) + if (itemToCopy is WindowsStorable sourceFile) { - var copiedFileTask = storageFile.storage.CopyAsync(storage, itemToCopy.Name, GetWindowsNameCollisionOption(collisionOption)).AsTask(cancellationToken); - var copiedFile = await copiedFileTask; - + var copiedFile = await sourceFile.storage.CopyAsync(storage, itemToCopy.Name, GetWindowsNameCollisionOption(overwrite)).AsTask(cancellationToken); return new WindowsStorageFile(copiedFile); } @@ -106,72 +118,51 @@ public async Task CreateCopyOfAsync(IStorable itemToCopy, CreationCol } /// - public async Task MoveFromAsync(IStorable itemToMove, IModifiableFolder source, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default) + public async Task MoveFromAsync(INestedStorable itemToMove, IModifiableFolder source, bool overwrite = default, CancellationToken cancellationToken = default) { - if (itemToMove is WindowsStorable storageFile) + if (itemToMove is WindowsStorable sourceFile) { - await storageFile.storage.MoveAsync(storage, itemToMove.Name, GetWindowsNameCollisionOption(collisionOption)).AsTask(cancellationToken); - return new WindowsStorageFile(storageFile.storage); + await sourceFile.storage.MoveAsync(storage, itemToMove.Name, GetWindowsNameCollisionOption(overwrite)).AsTask(cancellationToken); + return new WindowsStorageFile(sourceFile.storage); } throw new ArgumentException($"Could not copy type {itemToMove.GetType()}"); } /// - public async Task CreateFileAsync(string desiredName, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default) + public async Task CreateFileAsync(string desiredName, bool overwrite = default, CancellationToken cancellationToken = default) { - var fileTask = storage.CreateFileAsync(desiredName, GetWindowsCreationCollisionOption(collisionOption)).AsTask(cancellationToken); - var file = await fileTask; - + var file = await storage.CreateFileAsync(desiredName, GetWindowsCreationCollisionOption(overwrite)).AsTask(cancellationToken); return new WindowsStorageFile(file); } /// - public async Task CreateFolderAsync(string desiredName, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default) + public async Task CreateFolderAsync(string desiredName, bool overwrite = default, CancellationToken cancellationToken = default) { - var folderTask = storage.CreateFolderAsync(desiredName, GetWindowsCreationCollisionOption(collisionOption)).AsTask(cancellationToken); - var folder = await folderTask; - + var folder = await storage.CreateFolderAsync(desiredName, GetWindowsCreationCollisionOption(overwrite)).AsTask(cancellationToken); return new WindowsStorageFolder(folder); } /// - public override async Task GetParentAsync(CancellationToken cancellationToken = default) + public override async Task GetParentAsync(CancellationToken cancellationToken = default) { - var parentFolderTask = storage.GetParentAsync().AsTask(cancellationToken); - var parentFolder = await parentFolderTask; - + var parentFolder = await storage.GetParentAsync().AsTask(cancellationToken); return new WindowsStorageFolder(parentFolder); } - private static StorageDeleteOption GetWindowsStorageDeleteOption(bool deletePermanentlyFlag) + private static StorageDeleteOption GetWindowsStorageDeleteOption(bool permanently) { - return deletePermanentlyFlag ? StorageDeleteOption.PermanentDelete : StorageDeleteOption.Default; + return permanently ? StorageDeleteOption.PermanentDelete : StorageDeleteOption.Default; } - private static Windows.Storage.NameCollisionOption GetWindowsNameCollisionOption( - CreationCollisionOption options) + private static NameCollisionOption GetWindowsNameCollisionOption(bool overwrite) { - return options switch - { - CreationCollisionOption.GenerateUniqueName => Windows.Storage.NameCollisionOption.GenerateUniqueName, - CreationCollisionOption.ReplaceExisting => Windows.Storage.NameCollisionOption.ReplaceExisting, - CreationCollisionOption.FailIfExists => Windows.Storage.NameCollisionOption.FailIfExists, - _ => throw new ArgumentOutOfRangeException(nameof(options)) - }; + return overwrite ? NameCollisionOption.ReplaceExisting : NameCollisionOption.GenerateUniqueName; } - private static Windows.Storage.CreationCollisionOption GetWindowsCreationCollisionOption( - CreationCollisionOption options) + private static CreationCollisionOption GetWindowsCreationCollisionOption(bool overwrite) { - return options switch - { - CreationCollisionOption.GenerateUniqueName => Windows.Storage.CreationCollisionOption.GenerateUniqueName, - CreationCollisionOption.ReplaceExisting => Windows.Storage.CreationCollisionOption.ReplaceExisting, - CreationCollisionOption.OpenIfExists => Windows.Storage.CreationCollisionOption.OpenIfExists, - CreationCollisionOption.FailIfExists => Windows.Storage.CreationCollisionOption.FailIfExists, - _ => throw new ArgumentOutOfRangeException(nameof(options)) - }; + return overwrite ? CreationCollisionOption.ReplaceExisting : CreationCollisionOption.OpenIfExists; } } } diff --git a/src/Files.App.Storage/WindowsStorage/WindowsStorageService.cs b/src/Files.App.Storage/WindowsStorage/WindowsStorageService.cs index ef84ab20b802..f1ae043ba3d1 100644 --- a/src/Files.App.Storage/WindowsStorage/WindowsStorageService.cs +++ b/src/Files.App.Storage/WindowsStorage/WindowsStorageService.cs @@ -11,54 +11,20 @@ namespace Files.App.Storage.WindowsStorage { /// - public sealed class WindowsStorageService : IStorageService + internal sealed class WindowsStorageService : IStorageService { /// - public Task IsAccessibleAsync(CancellationToken cancellationToken = default) + public async Task GetFileAsync(string id, CancellationToken cancellationToken = default) { - return Task.FromResult(true); - } - - /// - public async Task FileExistsAsync(string path, CancellationToken cancellationToken = default) - { - try - { - _ = await GetFileFromPathAsync(path, cancellationToken); - return true; - } - catch (Exception) - { - return false; - } - } - - /// - public async Task DirectoryExistsAsync(string path, CancellationToken cancellationToken = default) - { - try - { - _ = await GetFolderFromPathAsync(path, cancellationToken); - return true; - } - catch (Exception) - { - return false; - } + var file = await StorageFile.GetFileFromPathAsync(id).AsTask(cancellationToken); + return new WindowsStorageFile(file); } /// - public async Task GetFolderFromPathAsync(string path, CancellationToken cancellationToken = default) + public async Task GetFolderAsync(string id, CancellationToken cancellationToken = default) { - var folder = await StorageFolder.GetFolderFromPathAsync(path).AsTask(cancellationToken); + var folder = await StorageFolder.GetFolderFromPathAsync(id).AsTask(cancellationToken); return new WindowsStorageFolder(folder); } - - /// - public async Task GetFileFromPathAsync(string path, CancellationToken cancellationToken = default) - { - var file = await StorageFile.GetFileFromPathAsync(path).AsTask(cancellationToken); - return new WindowsStorageFile(file); - } } } diff --git a/src/Files.App/Actions/Content/Selection/ToggleSelectAction.cs b/src/Files.App/Actions/Content/Selection/ToggleSelectAction.cs index debcccad239e..e31df5c99333 100644 --- a/src/Files.App/Actions/Content/Selection/ToggleSelectAction.cs +++ b/src/Files.App/Actions/Content/Selection/ToggleSelectAction.cs @@ -30,7 +30,7 @@ public Task ExecuteAsync() private static SelectorItem? GetFocusedElement() { - return FocusManager.GetFocusedElement(App.Window.Content.XamlRoot) as SelectorItem; + return FocusManager.GetFocusedElement(MainWindow.Instance.Content.XamlRoot) as SelectorItem; } } } diff --git a/src/Files.App/Actions/Global/EnterCompactOverlayAction.cs b/src/Files.App/Actions/Global/EnterCompactOverlayAction.cs index 53549a4b9f92..da0c35257317 100644 --- a/src/Files.App/Actions/Global/EnterCompactOverlayAction.cs +++ b/src/Files.App/Actions/Global/EnterCompactOverlayAction.cs @@ -34,9 +34,9 @@ public EnterCompactOverlayAction() public Task ExecuteAsync() { - var window = App.GetAppWindow(App.Window); - window.SetPresenter(AppWindowPresenterKind.CompactOverlay); - window.Resize(new SizeInt32(400, 350)); + var appWindow = MainWindow.Instance.AppWindow; + appWindow.SetPresenter(AppWindowPresenterKind.CompactOverlay); + appWindow.Resize(new SizeInt32(400, 350)); return Task.CompletedTask; } diff --git a/src/Files.App/Actions/Global/ExitCompactOverlayAction.cs b/src/Files.App/Actions/Global/ExitCompactOverlayAction.cs index 2f9d40798ea7..94abd73aef72 100644 --- a/src/Files.App/Actions/Global/ExitCompactOverlayAction.cs +++ b/src/Files.App/Actions/Global/ExitCompactOverlayAction.cs @@ -33,8 +33,8 @@ public ExitCompactOverlayAction() public Task ExecuteAsync() { - var window = App.GetAppWindow(App.Window); - window.SetPresenter(AppWindowPresenterKind.Overlapped); + var appWindow = MainWindow.Instance.AppWindow; + appWindow.SetPresenter(AppWindowPresenterKind.Overlapped); return Task.CompletedTask; } diff --git a/src/Files.App/Actions/Global/ToggleCompactOverlayAction.cs b/src/Files.App/Actions/Global/ToggleCompactOverlayAction.cs index b3761896a0d7..135e10495bf4 100644 --- a/src/Files.App/Actions/Global/ToggleCompactOverlayAction.cs +++ b/src/Files.App/Actions/Global/ToggleCompactOverlayAction.cs @@ -31,16 +31,16 @@ public ToggleCompactOverlayAction() public Task ExecuteAsync() { - var window = App.GetAppWindow(App.Window); + var appWindow = MainWindow.Instance.AppWindow; if (windowContext.IsCompactOverlay) { - window.SetPresenter(AppWindowPresenterKind.Overlapped); + appWindow.SetPresenter(AppWindowPresenterKind.Overlapped); } else { - window.SetPresenter(AppWindowPresenterKind.CompactOverlay); - window.Resize(new SizeInt32(400, 350)); + appWindow.SetPresenter(AppWindowPresenterKind.CompactOverlay); + appWindow.Resize(new SizeInt32(400, 350)); } return Task.CompletedTask; diff --git a/src/Files.App/Actions/Global/ToggleFullScreenAction.cs b/src/Files.App/Actions/Global/ToggleFullScreenAction.cs index b8dacab21d18..937d7888bc4a 100644 --- a/src/Files.App/Actions/Global/ToggleFullScreenAction.cs +++ b/src/Files.App/Actions/Global/ToggleFullScreenAction.cs @@ -20,22 +20,19 @@ public bool IsOn { get { - var window = App.GetAppWindow(App.Window); - - return window.Presenter.Kind is AppWindowPresenterKind.FullScreen; + var appWindow = MainWindow.Instance.AppWindow; + return appWindow.Presenter.Kind is AppWindowPresenterKind.FullScreen; } } public Task ExecuteAsync() { - var window = App.GetAppWindow(App.Window); - - var newKind = window.Presenter.Kind is AppWindowPresenterKind.FullScreen + var appWindow = MainWindow.Instance.AppWindow; + var newKind = appWindow.Presenter.Kind is AppWindowPresenterKind.FullScreen ? AppWindowPresenterKind.Overlapped : AppWindowPresenterKind.FullScreen; - window.SetPresenter(newKind); - + appWindow.SetPresenter(newKind); return Task.CompletedTask; } } diff --git a/src/Files.App/Actions/Open/OpenTerminalAction.cs b/src/Files.App/Actions/Open/OpenTerminalAction.cs index d4ed04601266..b68d5c746ad8 100644 --- a/src/Files.App/Actions/Open/OpenTerminalAction.cs +++ b/src/Files.App/Actions/Open/OpenTerminalAction.cs @@ -37,7 +37,7 @@ public Task ExecuteAsync() var terminalStartInfo = GetProcessStartInfo(); if (terminalStartInfo is not null) { - App.Window.DispatcherQueue.TryEnqueue(() => + MainWindow.Instance.DispatcherQueue.TryEnqueue(() => { try { diff --git a/src/Files.App/App.xaml.cs b/src/Files.App/App.xaml.cs index 2fe19b26128d..615f1dfe7847 100644 --- a/src/Files.App/App.xaml.cs +++ b/src/Files.App/App.xaml.cs @@ -1,8 +1,6 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using CommunityToolkit.Mvvm.DependencyInjection; -using CommunityToolkit.WinUI; using CommunityToolkit.WinUI.Helpers; using CommunityToolkit.WinUI.Notifications; using Files.App.Commands; @@ -20,32 +18,21 @@ using Files.App.Storage.FtpStorage; using Files.App.Storage.NativeStorage; using Files.App.UserControls.MultitaskingControl; -using Files.App.ViewModels; using Files.App.ViewModels.Settings; -using Files.App.Views; using Files.Core.Services.SizeProvider; using Files.Core.Storage; using Files.Shared; using Files.Shared.Cloud; -using Files.Shared.Extensions; using Files.Shared.Services; using Files.Shared.Services.DateTimeFormatter; -using Microsoft.AppCenter; -using Microsoft.AppCenter.Analytics; -using Microsoft.AppCenter.Crashes; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.Windows.AppLifecycle; -using System; -using System.Diagnostics; using System.IO; -using System.Linq; using System.Text; -using System.Threading.Tasks; using Windows.ApplicationModel; using Windows.ApplicationModel.DataTransfer; using Windows.Storage; @@ -56,11 +43,13 @@ namespace Files.App { public partial class App : Application { + private IHost? _host; + private static bool ShowErrorNotification = false; - private IHost host { get; set; } public static string OutputPath { get; set; } public static CommandBarFlyout? LastOpenedFlyout { get; set; } - public static StorageHistoryWrapper HistoryWrapper = new StorageHistoryWrapper(); + + public static StorageHistoryWrapper HistoryWrapper { get; } = new(); public static AppModel AppModel { get; private set; } public static RecentItems RecentItemsManager { get; private set; } public static QuickAccessManager QuickAccessManager { get; private set; } @@ -70,38 +59,25 @@ public partial class App : Application public static FileTagsManager FileTagsManager { get; private set; } public static ILogger Logger { get; private set; } - public static SecondaryTileHelper SecondaryTileHelper { get; private set; } = new SecondaryTileHelper(); - - public static string AppVersion = $"{Package.Current.Id.Version.Major}.{Package.Current.Id.Version.Minor}.{Package.Current.Id.Version.Build}.{Package.Current.Id.Version.Revision}"; - public static string LogoPath; - public static AppEnvironment AppEnv; + public static SecondaryTileHelper SecondaryTileHelper { get; private set; } = new(); /// - /// Initializes the singleton application object. This is the first line of authored code + /// Initializes the singleton application object. This is the first line of authored code /// executed, and as such is the logical equivalent of main() or WinMain(). /// public App() { - UnhandledException += OnUnhandledException; - TaskScheduler.UnobservedTaskException += OnUnobservedException; InitializeComponent(); - - (AppEnv, LogoPath) = EnvHelpers.GetAppEnvironmentAndLogo(); + EnsureEarlyApp(); } - private static void EnsureSettingsAndConfigurationAreBootstrapped() + private void EnsureEarlyApp() { - RecentItemsManager ??= new RecentItems(); - AppModel ??= new AppModel(); - LibraryManager ??= new LibraryManager(); - CloudDrivesManager ??= new CloudDrivesManager(); - WSLDistroManager ??= new WSLDistroManager(); - FileTagsManager ??= new FileTagsManager(); - QuickAccessManager ??= new QuickAccessManager(); - } + // Configure exception handlers + UnhandledException += App_UnhandledException; + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; - private static Task StartAppCenter() - { #if STORE || STABLE || PREVIEW try { @@ -114,83 +90,27 @@ private static Task StartAppCenter() App.Logger.LogWarning(ex, "AppCenter could not be started."); } #endif - return Task.CompletedTask; - } - - private static async Task InitializeAppComponentsAsync() - { - var userSettingsService = Ioc.Default.GetRequiredService(); - var addItemService = Ioc.Default.GetRequiredService(); - var generalSettingsService = userSettingsService.GeneralSettingsService; - - // Start off a list of tasks we need to run before we can continue startup - await Task.Run(async () => - { - await Task.WhenAll( - StartAppCenter(), - OptionalTask(CloudDrivesManager.UpdateDrivesAsync(), generalSettingsService.ShowCloudDrivesSection), - LibraryManager.UpdateLibrariesAsync(), - OptionalTask(WSLDistroManager.UpdateDrivesAsync(), generalSettingsService.ShowWslSection), - OptionalTask(FileTagsManager.UpdateFileTagsAsync(), generalSettingsService.ShowFileTagsSection), - QuickAccessManager.InitializeAsync() - ); - - await Task.WhenAll( - JumpListHelper.InitializeUpdatesAsync(), - addItemService.GetNewEntriesAsync(), - ContextMenu.WarmUpQueryContextMenuAsync() - ); - - FileTagsHelper.UpdateTagsDb(); - }); - - // Check for required updates - var updateService = Ioc.Default.GetRequiredService(); - await updateService.CheckForUpdates(); - await updateService.DownloadMandatoryUpdates(); - await updateService.CheckAndUpdateFilesLauncherAsync(); - await updateService.CheckLatestReleaseNotesAsync(); - - static async Task OptionalTask(Task task, bool condition) - { - if (condition) - await task; - } } - /// - /// Invoked when the application is launched normally by the end user. Other entry points - /// will be used such as when the application is launched to open a specific file. - /// - /// Details about the launch request and process. - protected override void OnLaunched(LaunchActivatedEventArgs e) + private IHost ConfigureHost() { - var activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs(); - - var logPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, "debug.log"); - //start tracking app usage - if (activatedEventArgs.Data is Windows.ApplicationModel.Activation.IActivatedEventArgs iaea) - SystemInformation.Instance.TrackAppUse(iaea); - - // Initialize MainWindow here - EnsureWindowIsInitialized(); - host = Host.CreateDefaultBuilder() - .UseEnvironment(AppEnv.ToString()) - .ConfigureLogging(builder => + return Host.CreateDefaultBuilder() + .UseEnvironment(ApplicationService.AppEnvironment.ToString()) + .ConfigureLogging(builder => builder - .AddProvider(new FileLoggerProvider(logPath)) - .SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Information) + .AddProvider(new FileLoggerProvider(Path.Combine(ApplicationData.Current.LocalFolder.Path, "debug.log"))) + .SetMinimumLevel(LogLevel.Information) ) - .ConfigureServices(services => + .ConfigureServices(services => services .AddSingleton() - .AddSingleton((sp) => new AppearanceSettingsService((sp.GetService() as UserSettingsService).GetSharingContext())) - .AddSingleton((sp) => new GeneralSettingsService((sp.GetService() as UserSettingsService).GetSharingContext())) - .AddSingleton((sp) => new FoldersSettingsService((sp.GetService() as UserSettingsService).GetSharingContext())) - .AddSingleton((sp) => new ApplicationSettingsService((sp.GetService() as UserSettingsService).GetSharingContext())) - .AddSingleton((sp) => new PreviewPaneSettingsService((sp.GetService() as UserSettingsService).GetSharingContext())) - .AddSingleton((sp) => new LayoutSettingsService((sp.GetService() as UserSettingsService).GetSharingContext())) - .AddSingleton((sp) => new AppSettingsService((sp.GetService() as UserSettingsService).GetSharingContext())) + .AddSingleton(sp => new AppearanceSettingsService((sp.GetService() as UserSettingsService).GetSharingContext())) + .AddSingleton(sp => new GeneralSettingsService((sp.GetService() as UserSettingsService).GetSharingContext())) + .AddSingleton(sp => new FoldersSettingsService((sp.GetService() as UserSettingsService).GetSharingContext())) + .AddSingleton(sp => new ApplicationSettingsService((sp.GetService() as UserSettingsService).GetSharingContext())) + .AddSingleton(sp => new PreviewPaneSettingsService((sp.GetService() as UserSettingsService).GetSharingContext())) + .AddSingleton(sp => new LayoutSettingsService((sp.GetService() as UserSettingsService).GetSharingContext())) + .AddSingleton(sp => new AppSettingsService((sp.GetService() as UserSettingsService).GetSharingContext())) .AddSingleton() .AddSingleton() .AddSingleton() @@ -205,6 +125,7 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() #if UWP .AddSingleton() #else @@ -237,44 +158,122 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) .AddSingleton() ) .Build(); + } - Logger = host.Services.GetRequiredService>(); - App.Logger.LogInformation($"App launched. Launch args type: {activatedEventArgs.Data.GetType().Name}"); + private static async Task InitializeAppComponentsAsync() + { + var userSettingsService = Ioc.Default.GetRequiredService(); + var addItemService = Ioc.Default.GetRequiredService(); + var generalSettingsService = userSettingsService.GeneralSettingsService; - Ioc.Default.ConfigureServices(host.Services); + // Start off a list of tasks we need to run before we can continue startup + await Task.Run(async () => + { + await Task.WhenAll( + OptionalTask(CloudDrivesManager.UpdateDrivesAsync(), generalSettingsService.ShowCloudDrivesSection), + LibraryManager.UpdateLibrariesAsync(), + OptionalTask(WSLDistroManager.UpdateDrivesAsync(), generalSettingsService.ShowWslSection), + OptionalTask(FileTagsManager.UpdateFileTagsAsync(), generalSettingsService.ShowFileTagsSection), + QuickAccessManager.InitializeAsync() + ); + + await Task.WhenAll( + JumpListHelper.InitializeUpdatesAsync(), + addItemService.GetNewEntriesAsync(), + ContextMenu.WarmUpQueryContextMenuAsync() + ); + + FileTagsHelper.UpdateTagsDb(); + }); + + // Check for required updates + var updateService = Ioc.Default.GetRequiredService(); + await updateService.CheckForUpdates(); + await updateService.DownloadMandatoryUpdates(); + await updateService.CheckAndUpdateFilesLauncherAsync(); + await updateService.CheckLatestReleaseNotesAsync(); + + static async Task OptionalTask(Task task, bool condition) + { + if (condition) + await task; + } + } + + /// + /// Invoked when the application is launched normally by the end user. Other entry points + /// will be used such as when the application is launched to open a specific file. + /// + /// Details about the launch request and process. + protected override void OnLaunched(LaunchActivatedEventArgs e) + { + // Get AppActivationArguments + var appActivationArguments = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs(); + + // Start tracking app usage + if (appActivationArguments.Data is Windows.ApplicationModel.Activation.IActivatedEventArgs activationEventArgs) + SystemInformation.Instance.TrackAppUse(activationEventArgs); + + // Configure Host and IoC + _host = ConfigureHost(); + Ioc.Default.ConfigureServices(_host.Services); EnsureSettingsAndConfigurationAreBootstrapped(); + // Initialize and activate MainWindow + EnsureSuperEarlyWindow(); + + // TODO(s) + Logger = Ioc.Default.GetRequiredService>(); + Logger.LogInformation($"App launched. Launch args type: {appActivationArguments.Data.GetType().Name}"); + _ = InitializeAppComponentsAsync().ContinueWith(t => Logger.LogWarning(t.Exception, "Error during InitializeAppComponentsAsync()"), TaskContinuationOptions.OnlyOnFaulted); + _ = MainWindow.Instance.InitializeApplication(appActivationArguments.Data); + } - _ = Window.InitializeApplication(activatedEventArgs.Data); + private static void EnsureSettingsAndConfigurationAreBootstrapped() + { + // TODO(s): Remove initialization here and move out all classes (visible below) out of App.xaml.cs + + RecentItemsManager ??= new RecentItems(); + AppModel ??= new AppModel(); + LibraryManager ??= new LibraryManager(); + CloudDrivesManager ??= new CloudDrivesManager(); + WSLDistroManager ??= new WSLDistroManager(); + FileTagsManager ??= new FileTagsManager(); + QuickAccessManager ??= new QuickAccessManager(); } - private void EnsureWindowIsInitialized() + private void EnsureSuperEarlyWindow() { - Window = new MainWindow(); - Window.Activated += Window_Activated; - Window.Closed += Window_Closed; - WindowHandle = WinRT.Interop.WindowNative.GetWindowHandle(Window); + // Get the MainWindow instance + var window = MainWindow.Instance; + + // Hook events for the window + window.Activated += Window_Activated; + window.Closed += Window_Closed; + + // Attempt to activate it + window.Activate(); } private void Window_Activated(object sender, WindowActivatedEventArgs args) { - if (args.WindowActivationState == WindowActivationState.CodeActivated || - args.WindowActivationState == WindowActivationState.PointerActivated) - { - ShowErrorNotification = true; - ApplicationData.Current.LocalSettings.Values["INSTANCE_ACTIVE"] = -Process.GetCurrentProcess().Id; - } + // TODO(s): Is this code still needed? + if (args.WindowActivationState is not (WindowActivationState.CodeActivated or WindowActivationState.PointerActivated)) + return; + + ShowErrorNotification = true; + ApplicationData.Current.LocalSettings.Values["INSTANCE_ACTIVE"] = -Process.GetCurrentProcess().Id; } public void OnActivated(AppActivationArguments activatedEventArgs) { - App.Logger.LogInformation($"App activated. Activated args type: {activatedEventArgs.Data.GetType().Name}"); + Logger.LogInformation($"App activated. Activated args type: {activatedEventArgs.Data.GetType().Name}"); var data = activatedEventArgs.Data; // InitializeApplication accesses UI, needs to be called on UI thread - _ = Window.DispatcherQueue.EnqueueOrInvokeAsync(() => Window.InitializeApplication(data)); + _ = MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => MainWindow.Instance.InitializeApplication(data)); } /// @@ -359,24 +358,29 @@ public static void SaveSessionTabs() .ToList(); } + #region Exception Handlers + /// /// Occurs when an exception is not handled on the UI thread. /// /// /// - private static void OnUnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) + private static void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) => AppUnhandledException(e.Exception, true); + private void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e) + => AppUnhandledException(e.ExceptionObject as Exception, false); + /// /// Occurs when an exception is not handled on a background thread. /// i.e. A task is fired and forgotten Task.Run(() => {...}) /// /// /// - private static void OnUnobservedException(object sender, UnobservedTaskExceptionEventArgs e) + private static void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e) => AppUnhandledException(e.Exception, false); - private static void AppUnhandledException(Exception ex, bool shouldShowNotification) + private static void AppUnhandledException(Exception? ex, bool shouldShowNotification) { StringBuilder formattedException = new StringBuilder() { Capacity = 200 }; @@ -480,7 +484,7 @@ private static void AppUnhandledException(Exception ex, bool shouldShowNotificat userSettingsService.AppSettingsService.RestoreTabsOnStartup = true; userSettingsService.GeneralSettingsService.LastCrashedTabList = lastSessionTabList; - Window.DispatcherQueue.EnqueueOrInvokeAsync(async () => + MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () => { await Launcher.LaunchUriAsync(new Uri("files-uwp:")); }).Wait(1000); @@ -488,22 +492,6 @@ private static void AppUnhandledException(Exception ex, bool shouldShowNotificat Process.GetCurrentProcess().Kill(); } - public static void CloseApp() - => Window.Close(); - - public static AppWindow GetAppWindow(Window w) - { - var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(w); - - Microsoft.UI.WindowId windowId = - Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd); - - return - AppWindow.GetFromWindowId(windowId); - } - - public static MainWindow Window { get; set; } = null!; - - public static IntPtr WindowHandle { get; private set; } + #endregion } } diff --git a/src/Files.App/Contexts/Window/WindowContext.cs b/src/Files.App/Contexts/Window/WindowContext.cs index 208c5db61655..6c6847307c3e 100644 --- a/src/Files.App/Contexts/Window/WindowContext.cs +++ b/src/Files.App/Contexts/Window/WindowContext.cs @@ -13,7 +13,7 @@ internal class WindowContext : ObservableObject, IWindowContext public WindowContext() { - App.Window.PresenterChanged += Window_PresenterChanged; + MainWindow.Instance.PresenterChanged += Window_PresenterChanged; } private void Window_PresenterChanged(object? sender, AppWindowPresenter e) diff --git a/src/Files.App/Data/Items/DriveItem.cs b/src/Files.App/Data/Items/DriveItem.cs index 4b635fd44b22..0421a456f5be 100644 --- a/src/Files.App/Data/Items/DriveItem.cs +++ b/src/Files.App/Data/Items/DriveItem.cs @@ -1,11 +1,11 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using ByteSizeLib; using Files.App.Storage.WindowsStorage; using Files.Core.Storage; using Files.Core.Storage.Enums; using Files.Core.Storage.LocatableStorage; +using Files.Core.Storage.NestedStorage; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media.Imaging; using Windows.Storage; @@ -178,7 +178,7 @@ public static async Task CreateFromPropertiesAsync(StorageFolder root item.DeviceID = deviceId; item.Root = root; - _ = App.Window.DispatcherQueue.EnqueueOrInvokeAsync(item.UpdatePropertiesAsync); + _ = MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(item.UpdatePropertiesAsync); return item; } @@ -268,25 +268,25 @@ private string GetSizeString() MaxSpace.ToSizeString()); } - public Task GetFileAsync(string fileName, CancellationToken cancellationToken = default) + public Task GetFileAsync(string fileName, CancellationToken cancellationToken = default) { var folder = new WindowsStorageFolder(Root); return folder.GetFileAsync(fileName, cancellationToken); } - public Task GetFolderAsync(string folderName, CancellationToken cancellationToken = default) + public Task GetFolderAsync(string folderName, CancellationToken cancellationToken = default) { var folder = new WindowsStorageFolder(Root); return folder.GetFolderAsync(folderName, cancellationToken); } - public IAsyncEnumerable GetItemsAsync(StorableKind kind = StorableKind.All, CancellationToken cancellationToken = default) + public IAsyncEnumerable GetItemsAsync(StorableKind kind = StorableKind.All, CancellationToken cancellationToken = default) { var folder = new WindowsStorageFolder(Root); return folder.GetItemsAsync(kind, cancellationToken); } - public Task GetParentAsync(CancellationToken cancellationToken = default) + public Task GetParentAsync(CancellationToken cancellationToken = default) { var folder = new WindowsStorageFolder(Root); return folder.GetParentAsync(cancellationToken); diff --git a/src/Files.App/Data/Items/LocationItem.cs b/src/Files.App/Data/Items/LocationItem.cs index 1256ebfb4b0f..e8bb1cce6524 100644 --- a/src/Files.App/Data/Items/LocationItem.cs +++ b/src/Files.App/Data/Items/LocationItem.cs @@ -92,7 +92,7 @@ public ulong SpaceUsed { SetProperty(ref spaceUsed, value); - App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => OnPropertyChanged(nameof(ToolTipText))); + MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => OnPropertyChanged(nameof(ToolTipText))); } } diff --git a/src/Files.App/Data/Models/AppModel.cs b/src/Files.App/Data/Models/AppModel.cs index da251030e220..07ca46926daa 100644 --- a/src/Files.App/Data/Models/AppModel.cs +++ b/src/Files.App/Data/Models/AppModel.cs @@ -1,7 +1,6 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using Files.App.Views; using Microsoft.UI.Xaml.Controls; using Windows.ApplicationModel.DataTransfer; @@ -9,11 +8,8 @@ namespace Files.App.Data.Models { public class AppModel : ObservableObject { - private IFoldersSettingsService FoldersSettings; - public AppModel() { - FoldersSettings = Ioc.Default.GetRequiredService().FoldersSettingsService; Clipboard.ContentChanged += Clipboard_ContentChanged; } @@ -46,7 +42,7 @@ public int TabStripSelectedIndex if (value < MainPageViewModel.AppInstances.Count) { - Frame rootFrame = (Frame)App.Window.Content; + Frame rootFrame = (Frame)MainWindow.Instance.Content; var mainView = (MainPage)rootFrame.Content; mainView.ViewModel.SelectedTabItem = MainPageViewModel.AppInstances[value]; } diff --git a/src/Files.App/Data/Models/SidebarPinnedModel.cs b/src/Files.App/Data/Models/SidebarPinnedModel.cs index 257d3e79adf1..15e8f26c2a55 100644 --- a/src/Files.App/Data/Models/SidebarPinnedModel.cs +++ b/src/Files.App/Data/Models/SidebarPinnedModel.cs @@ -109,7 +109,7 @@ public async Task CreateLocationItemFromPathAsync(string path) locationItem.IconData = iconData; if (locationItem.IconData is not null) - locationItem.Icon = await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => locationItem.IconData.ToBitmapAsync()); + locationItem.Icon = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => locationItem.IconData.ToBitmapAsync()); } if (locationItem.IconData is null) @@ -117,12 +117,12 @@ public async Task CreateLocationItemFromPathAsync(string path) locationItem.IconData = await FileThumbnailHelper.LoadIconWithoutOverlayAsync(path, 48u); if (locationItem.IconData is not null) - locationItem.Icon = await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => locationItem.IconData.ToBitmapAsync()); + locationItem.Icon = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => locationItem.IconData.ToBitmapAsync()); } } else { - locationItem.Icon = await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => UIHelpers.GetSidebarIconResource(Constants.ImageRes.Folder)); + locationItem.Icon = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => UIHelpers.GetSidebarIconResource(Constants.ImageRes.Folder)); locationItem.IsInvalid = true; Debug.WriteLine($"Pinned item was invalid {res.ErrorCode}, item: {path}"); } diff --git a/src/Files.App/Dialogs/CreateArchiveDialog.xaml.cs b/src/Files.App/Dialogs/CreateArchiveDialog.xaml.cs index 6b0e256fc777..132bd386442c 100644 --- a/src/Files.App/Dialogs/CreateArchiveDialog.xaml.cs +++ b/src/Files.App/Dialogs/CreateArchiveDialog.xaml.cs @@ -62,7 +62,7 @@ public CreateArchiveDialog() private static ContentDialog SetContentDialogRoot(ContentDialog contentDialog) { if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8)) - contentDialog.XamlRoot = App.Window.Content.XamlRoot; // WinUi3 + contentDialog.XamlRoot = MainWindow.Instance.Content.XamlRoot; // WinUi3 return contentDialog; } diff --git a/src/Files.App/Dialogs/FilesystemOperationDialog.xaml.cs b/src/Files.App/Dialogs/FilesystemOperationDialog.xaml.cs index 000800738d72..a7bb94deb52b 100644 --- a/src/Files.App/Dialogs/FilesystemOperationDialog.xaml.cs +++ b/src/Files.App/Dialogs/FilesystemOperationDialog.xaml.cs @@ -36,7 +36,7 @@ public FilesystemOperationDialog() { InitializeComponent(); - App.Window.SizeChanged += Current_SizeChanged; + MainWindow.Instance.SizeChanged += Current_SizeChanged; } public new async Task ShowAsync() => (DialogResult)await SetContentDialogRoot(this).ShowAsync(); @@ -46,7 +46,7 @@ private ContentDialog SetContentDialogRoot(ContentDialog contentDialog) { if (Windows.Foundation.Metadata.ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8)) { - contentDialog.XamlRoot = App.Window.Content.XamlRoot; + contentDialog.XamlRoot = MainWindow.Instance.Content.XamlRoot; } return contentDialog; } @@ -59,7 +59,7 @@ private void Current_SizeChanged(object sender, WindowSizeChangedEventArgs e) private void UpdateDialogLayout() { if (ViewModel.FileSystemDialogMode.ConflictsExist) - ContainerGrid.Width = App.Window.Bounds.Width <= 700 ? App.Window.Bounds.Width - 50 : 650; + ContainerGrid.Width = MainWindow.Instance.Bounds.Width <= 700 ? MainWindow.Instance.Bounds.Width - 50 : 650; } protected override void OnApplyTemplate() @@ -87,7 +87,7 @@ private void RootDialog_Closing(ContentDialog sender, ContentDialogClosingEventA if (args.Result == ContentDialogResult.Primary) ViewModel.SaveConflictResolveOption(); - App.Window.SizeChanged -= Current_SizeChanged; + MainWindow.Instance.SizeChanged -= Current_SizeChanged; ViewModel.CancelCts(); } diff --git a/src/Files.App/Dialogs/SettingsDialog.xaml.cs b/src/Files.App/Dialogs/SettingsDialog.xaml.cs index d66be9a6e900..6ad9e8aaa0e8 100644 --- a/src/Files.App/Dialogs/SettingsDialog.xaml.cs +++ b/src/Files.App/Dialogs/SettingsDialog.xaml.cs @@ -16,13 +16,13 @@ public sealed partial class SettingsDialog : ContentDialog, IDialog (FrameworkElement)App.Window.Content; + => (FrameworkElement)MainWindow.Instance.Content; public SettingsDialog() { InitializeComponent(); - App.Window.SizeChanged += Current_SizeChanged; + MainWindow.Instance.SizeChanged += Current_SizeChanged; UpdateDialogLayout(); } @@ -36,9 +36,9 @@ private void Current_SizeChanged(object sender, WindowSizeChangedEventArgs e) private void UpdateDialogLayout() { - ContainerGrid.Height = App.Window.Bounds.Height <= 760 ? App.Window.Bounds.Height - 70 : 690; - ContainerGrid.Width = App.Window.Bounds.Width <= 1100 ? App.Window.Bounds.Width : 1100; - MainSettingsNavigationView.PaneDisplayMode = App.Window.Bounds.Width < 700 ? NavigationViewPaneDisplayMode.LeftCompact : NavigationViewPaneDisplayMode.Left; + ContainerGrid.Height = MainWindow.Instance.Bounds.Height <= 760 ? MainWindow.Instance.Bounds.Height - 70 : 690; + ContainerGrid.Width = MainWindow.Instance.Bounds.Width <= 1100 ? MainWindow.Instance.Bounds.Width : 1100; + MainSettingsNavigationView.PaneDisplayMode = MainWindow.Instance.Bounds.Width < 700 ? NavigationViewPaneDisplayMode.LeftCompact : NavigationViewPaneDisplayMode.Left; } private void MainSettingsNavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) @@ -60,7 +60,7 @@ private void MainSettingsNavigationView_SelectionChanged(NavigationView sender, private void ContentDialog_Closing(ContentDialog sender, ContentDialogClosingEventArgs args) { - App.Window.SizeChanged -= Current_SizeChanged; + MainWindow.Instance.SizeChanged -= Current_SizeChanged; } private void CloseSettingsDialogButton_Click(object sender, RoutedEventArgs e) diff --git a/src/Files.App/Helpers/Dialog/DialogDisplayHelper.cs b/src/Files.App/Helpers/Dialog/DialogDisplayHelper.cs index 29489d9f66eb..1b3c47893858 100644 --- a/src/Files.App/Helpers/Dialog/DialogDisplayHelper.cs +++ b/src/Files.App/Helpers/Dialog/DialogDisplayHelper.cs @@ -49,7 +49,7 @@ public static async Task ShowDialogAsync(DynamicDialog dial { try { - if (App.Window.Content is Frame rootFrame) + if (MainWindow.Instance.Content is Frame rootFrame) { await dialog.ShowAsync(); return dialog.DynamicResult; diff --git a/src/Files.App/Helpers/Environment/EnvHelpers.cs b/src/Files.App/Helpers/Environment/EnvHelpers.cs deleted file mode 100644 index 8ee9f44b5b9b..000000000000 --- a/src/Files.App/Helpers/Environment/EnvHelpers.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2023 Files Community -// Licensed under the MIT License. See the LICENSE. - -namespace Files.App.Helpers -{ - public static class EnvHelpers - { - - public static (AppEnvironment, string) GetAppEnvironmentAndLogo() - { - - - var env = -#if STORE - AppEnvironment.Store; -#elif PREVIEW - AppEnvironment.Preview; -#elif STABLE - AppEnvironment.Stable; -#else - AppEnvironment.Dev; -#endif - - var path = env switch - { - AppEnvironment.Dev => Constants.AssetPaths.DevLogo, - AppEnvironment.Preview => Constants.AssetPaths.PreviewLogo, - _ => Constants.AssetPaths.StableLogo, - }; - - return (env, path); - } - } -} diff --git a/src/Files.App/Helpers/Interop/NativeWinApiHelper.cs b/src/Files.App/Helpers/Interop/NativeWinApiHelper.cs index 0b93a9c378a0..adf5214b6114 100644 --- a/src/Files.App/Helpers/Interop/NativeWinApiHelper.cs +++ b/src/Files.App/Helpers/Interop/NativeWinApiHelper.cs @@ -3,10 +3,8 @@ using Files.App.Utils.Shell; using Microsoft.Extensions.Logging; -using System; using System.Runtime.InteropServices; using System.Text; -using System.Threading.Tasks; using Windows.Foundation.Metadata; using Windows.System; @@ -258,17 +256,6 @@ public static bool IsHasThreadAccessPropertyPresent } } - // https://www.travelneil.com/wndproc-in-uwp.html - [ComImport, System.Runtime.InteropServices.Guid("45D64A29-A63E-4CB6-B498-5781D298CB4F")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface ICoreWindowInterop - { - IntPtr WindowHandle { get; } - bool MessageHandled { get; } - } - - public static IntPtr CoreWindowHandle => App.WindowHandle; - public static Task GetFileAssociationAsync(string filePath) => Win32API.GetFileAssociationAsync(filePath, true); } diff --git a/src/Files.App/Helpers/MenuFlyout/ShellContextMenuHelper.cs b/src/Files.App/Helpers/MenuFlyout/ShellContextMenuHelper.cs index c93f1cd51995..0c2ec5425091 100644 --- a/src/Files.App/Helpers/MenuFlyout/ShellContextMenuHelper.cs +++ b/src/Files.App/Helpers/MenuFlyout/ShellContextMenuHelper.cs @@ -276,7 +276,7 @@ public static async Task LoadShellMenuItems( var (_, secondaryElements) = ItemModelListToContextFlyoutHelper.GetAppBarItemsFromModel(shellMenuItems); if (secondaryElements.Any()) { - var openedPopups = Microsoft.UI.Xaml.Media.VisualTreeHelper.GetOpenPopups(App.Window); + var openedPopups = Microsoft.UI.Xaml.Media.VisualTreeHelper.GetOpenPopups(MainWindow.Instance); var secondaryMenu = openedPopups.FirstOrDefault(popup => popup.Name == "OverflowPopup"); var itemsControl = secondaryMenu?.Child.FindDescendant(); diff --git a/src/Files.App/Helpers/Navigation/NavigationHelpers.cs b/src/Files.App/Helpers/Navigation/NavigationHelpers.cs index 170f74e7d4ad..04e5c0a30420 100644 --- a/src/Files.App/Helpers/Navigation/NavigationHelpers.cs +++ b/src/Files.App/Helpers/Navigation/NavigationHelpers.cs @@ -431,7 +431,7 @@ private static async Task OpenFile(string path, IShellPage ass // WINUI3 private static LauncherOptions InitializeWithWindow(LauncherOptions obj) { - WinRT.Interop.InitializeWithWindow.Initialize(obj, App.WindowHandle); + WinRT.Interop.InitializeWithWindow.Initialize(obj, MainWindow.Instance.WindowHandle); return obj; } diff --git a/src/Files.App/Helpers/ShareItemHelpers.cs b/src/Files.App/Helpers/ShareItemHelpers.cs index 165913cd14ad..98390fa5027e 100644 --- a/src/Files.App/Helpers/ShareItemHelpers.cs +++ b/src/Files.App/Helpers/ShareItemHelpers.cs @@ -23,12 +23,12 @@ public static bool IsItemShareable(ListedItem item) public static void ShareItems(IEnumerable itemsToShare) { var interop = DataTransferManager.As(); - IntPtr result = interop.GetForWindow(App.WindowHandle, InteropHelpers.DataTransferManagerInteropIID); + IntPtr result = interop.GetForWindow(MainWindow.Instance.WindowHandle, InteropHelpers.DataTransferManagerInteropIID); var manager = WinRT.MarshalInterface.FromAbi(result); manager.DataRequested += new TypedEventHandler(Manager_DataRequested); - interop.ShowShareUIForWindow(App.WindowHandle); + interop.ShowShareUIForWindow(MainWindow.Instance.WindowHandle); async void Manager_DataRequested(DataTransferManager sender, DataRequestedEventArgs args) { diff --git a/src/Files.App/Helpers/Storage/FilePropertiesHelpers.cs b/src/Files.App/Helpers/Storage/FilePropertiesHelpers.cs index 98c5ace67217..71d5ad72ec93 100644 --- a/src/Files.App/Helpers/Storage/FilePropertiesHelpers.cs +++ b/src/Files.App/Helpers/Storage/FilePropertiesHelpers.cs @@ -5,12 +5,9 @@ using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Animation; using Microsoft.Windows.ApplicationModel.Resources; using System.Collections.Concurrent; -using System.IO; -using Windows.ApplicationModel; using Windows.Graphics; namespace Files.App.Helpers @@ -26,12 +23,6 @@ public static class FilePropertiesHelpers public static readonly bool FlowDirectionSettingIsRightToLeft = new ResourceManager().CreateResourceContext().QualifierValues["LayoutDirection"] == "RTL"; - /// - /// App logo location to use as window popup icon and title bar icon - /// - public static string LogoPath - => Path.Combine(Package.Current.InstalledLocation.Path, App.LogoPath); - /// /// Get window handle (hWnd) of the given properties window instance /// @@ -93,6 +84,8 @@ public static void OpenPropertiesWindow(IShellPage associatedInstance) /// Associated main window instance public static void OpenPropertiesWindow(object item, IShellPage associatedInstance) { + var applicationService = Ioc.Default.GetRequiredService(); + if (item is null) return; @@ -123,7 +116,7 @@ public static void OpenPropertiesWindow(object item, IShellPage associatedInstan appWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent; appWindow.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent; - appWindow.SetIcon(LogoPath); + appWindow.SetIcon(applicationService.AppIcoPath); frame.Navigate( typeof(Views.Properties.MainPropertiesPage), diff --git a/src/Files.App/Helpers/UI/SecondaryTileHelper.cs b/src/Files.App/Helpers/UI/SecondaryTileHelper.cs index b84dbe0247d0..f56ff259e114 100644 --- a/src/Files.App/Helpers/UI/SecondaryTileHelper.cs +++ b/src/Files.App/Helpers/UI/SecondaryTileHelper.cs @@ -63,7 +63,7 @@ public async Task TryPinFolderAsync(string path, string name) } private SecondaryTile InitializeWithWindow(SecondaryTile obj) { - WinRT.Interop.InitializeWithWindow.Initialize(obj, App.WindowHandle); + WinRT.Interop.InitializeWithWindow.Initialize(obj, MainWindow.Instance.WindowHandle); return obj; } diff --git a/src/Files.App/Helpers/UI/ThemeHelper.cs b/src/Files.App/Helpers/UI/ThemeHelper.cs index 24bf6bc6b513..202026e4a2d9 100644 --- a/src/Files.App/Helpers/UI/ThemeHelper.cs +++ b/src/Files.App/Helpers/UI/ThemeHelper.cs @@ -1,10 +1,6 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using CommunityToolkit.Mvvm.DependencyInjection; -using CommunityToolkit.WinUI; -using Files.App.Extensions; -using Files.App.ViewModels; using Microsoft.UI; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; @@ -49,11 +45,11 @@ public static ElementTheme RootTheme public static void Initialize() { // Save reference as this might be null when the user is in another app - currentApplicationWindow = App.Window; + currentApplicationWindow = MainWindow.Instance; // Set TitleBar background color if (currentApplicationWindow is not null) - titleBar = App.GetAppWindow(currentApplicationWindow)?.TitleBar; + titleBar = MainWindow.Instance.AppWindow.TitleBar; // Apply the desired theme based on what is set in the application settings ApplyTheme(); @@ -68,14 +64,13 @@ private static async void UiSettings_ColorValuesChanged(UISettings sender, objec // Make sure we have a reference to our window so we dispatch a UI change if (currentApplicationWindow is null) { - currentApplicationWindow = App.Window; + currentApplicationWindow = MainWindow.Instance; if (currentApplicationWindow is null) return; } - if (titleBar is null) - titleBar = App.GetAppWindow(currentApplicationWindow)?.TitleBar; + titleBar ??= MainWindow.Instance.AppWindow.TitleBar; // Dispatch on UI thread so that we have a current appbar to access and change await currentApplicationWindow.DispatcherQueue.EnqueueOrInvokeAsync(ApplyTheme); @@ -85,7 +80,7 @@ private static void ApplyTheme() { var rootTheme = RootTheme; - if (App.Window.Content is FrameworkElement rootElement) + if (MainWindow.Instance.Content is FrameworkElement rootElement) rootElement.RequestedTheme = rootTheme; if (titleBar is not null) diff --git a/src/Files.App/Helpers/UI/UIHelpers.cs b/src/Files.App/Helpers/UI/UIHelpers.cs index e83833d9219a..62fc05f63925 100644 --- a/src/Files.App/Helpers/UI/UIHelpers.cs +++ b/src/Files.App/Helpers/UI/UIHelpers.cs @@ -123,14 +123,14 @@ private static ContentDialog SetContentDialogRoot(ContentDialog contentDialog) { if (Windows.Foundation.Metadata.ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8)) { - contentDialog.XamlRoot = App.Window.Content.XamlRoot; + contentDialog.XamlRoot = MainWindow.Instance.Content.XamlRoot; } return contentDialog; } public static void CloseAllDialogs() { - var openedDialogs = VisualTreeHelper.GetOpenPopups(App.Window); + var openedDialogs = VisualTreeHelper.GetOpenPopups(MainWindow.Instance); foreach (var item in openedDialogs) { diff --git a/src/Files.App/Interacts/BaseLayoutCommandImplementationModel.cs b/src/Files.App/Interacts/BaseLayoutCommandImplementationModel.cs index e254b866f6ce..9ecd20778002 100644 --- a/src/Files.App/Interacts/BaseLayoutCommandImplementationModel.cs +++ b/src/Files.App/Interacts/BaseLayoutCommandImplementationModel.cs @@ -62,7 +62,7 @@ public virtual async Task OpenDirectoryInNewTab(RoutedEventArgs e) { foreach (ListedItem listedItem in SlimContentPage.SelectedItems) { - await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(async () => + await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () => { await mainPageViewModel.AddNewTabByPathAsync(typeof(PaneHolderPage), (listedItem as ShortcutItem)?.TargetPath ?? listedItem.ItemPath); }, diff --git a/src/Files.App/MainWindow.xaml.cs b/src/Files.App/MainWindow.xaml.cs index 6dfc1310d3b5..05bd29028ebc 100644 --- a/src/Files.App/MainWindow.xaml.cs +++ b/src/Files.App/MainWindow.xaml.cs @@ -18,24 +18,33 @@ namespace Files.App { public sealed partial class MainWindow : WindowEx { + private static MainWindow? _Instance; + public static MainWindow Instance => _Instance ??= new(); + + public IntPtr WindowHandle { get; } + private MainPageViewModel mainPageViewModel; - public MainWindow() - { - InitializeComponent(); + private IApplicationService ApplicationService { get; } = Ioc.Default.GetRequiredService(); - PersistenceId = "FilesMainWindow"; + private MainWindow() + { + WindowHandle = this.GetWindowHandle(); + InitializeComponent(); EnsureEarlyWindow(); } private void EnsureEarlyWindow() { + // Set PersistenceId + PersistenceId = "FilesMainWindow"; + // Set title AppWindow.Title = "Files"; // Set logo - AppWindow.SetIcon(Path.Combine(Package.Current.InstalledLocation.Path, App.LogoPath)); + AppWindow.SetIcon(Path.Combine(Package.Current.InstalledLocation.Path, ApplicationService.AppIcoPath)); // Extend title bar AppWindow.TitleBar.ExtendsContentIntoTitleBar = true; @@ -160,7 +169,7 @@ private Frame EnsureWindowIsInitialized() // NOTE: // Do not repeat app initialization when the Window already has content, // just ensure that the window is active - if (!(App.Window.Content is Frame rootFrame)) + if (!(MainWindow.Instance.Content is Frame rootFrame)) { // Set system backdrop this.SystemBackdrop = new AppSystemBackdrop(); @@ -171,7 +180,7 @@ private Frame EnsureWindowIsInitialized() rootFrame.NavigationFailed += OnNavigationFailed; // Place the frame in the current Window - App.Window.Content = rootFrame; + MainWindow.Instance.Content = rootFrame; } return rootFrame; diff --git a/src/Files.App/Program.cs b/src/Files.App/Program.cs index 9ed6d5a7a813..5c40f054606a 100644 --- a/src/Files.App/Program.cs +++ b/src/Files.App/Program.cs @@ -1,8 +1,6 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using Files.App.Utils.Shell; -using Files.Core.Helpers; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.Windows.AppLifecycle; diff --git a/src/Files.App/Services/ApplicationService.cs b/src/Files.App/Services/ApplicationService.cs new file mode 100644 index 000000000000..b9148741640e --- /dev/null +++ b/src/Files.App/Services/ApplicationService.cs @@ -0,0 +1,38 @@ +using Windows.ApplicationModel; + +namespace Files.App.Services +{ + /// + internal sealed class ApplicationService : IApplicationService + { + // Workaround to help improve code clarity + internal static readonly AppEnvironment AppEnvironment = +#if STORE + AppEnvironment.Store; +#elif PREVIEW + AppEnvironment.Preview; +#elif STABLE + AppEnvironment.Stable; +#else + AppEnvironment.Dev; +#endif + + /// + public AppEnvironment Environment { get; } = AppEnvironment; + + /// + public Version AppVersion { get; } = new( + Package.Current.Id.Version.Major, + Package.Current.Id.Version.Minor, + Package.Current.Id.Version.Build, + Package.Current.Id.Version.Revision); + + /// + public string AppIcoPath { get; } = AppEnvironment switch + { + AppEnvironment.Dev => Constants.AssetPaths.DevLogo, + AppEnvironment.Preview => Constants.AssetPaths.PreviewLogo, + _ => Constants.AssetPaths.StableLogo + }; + } +} diff --git a/src/Files.App/Services/DialogService.cs b/src/Files.App/Services/DialogService.cs index a1bbadfbb75a..7dc577fdb613 100644 --- a/src/Files.App/Services/DialogService.cs +++ b/src/Files.App/Services/DialogService.cs @@ -50,7 +50,7 @@ public IDialog GetDialog(TViewModel viewModel) dialog.ViewModel = viewModel; if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8)) - contentDialog.XamlRoot = App.Window.Content.XamlRoot; + contentDialog.XamlRoot = MainWindow.Instance.Content.XamlRoot; return dialog; } diff --git a/src/Files.App/Services/FileExplorerService.cs b/src/Files.App/Services/FileExplorerService.cs index b371b730b901..913dff5372e1 100644 --- a/src/Files.App/Services/FileExplorerService.cs +++ b/src/Files.App/Services/FileExplorerService.cs @@ -43,7 +43,7 @@ public Task OpenInFileExplorerAsync(ILocatableFolder folder, CancellationToken c // WINUI3 private FileOpenPicker InitializeWithWindow(FileOpenPicker obj) { - WinRT.Interop.InitializeWithWindow.Initialize(obj, App.WindowHandle); + WinRT.Interop.InitializeWithWindow.Initialize(obj, MainWindow.Instance.WindowHandle); return obj; } @@ -63,7 +63,7 @@ private FileOpenPicker InitializeWithWindow(FileOpenPicker obj) // WINUI3 private FolderPicker InitializeWithWindow(FolderPicker obj) { - WinRT.Interop.InitializeWithWindow.Initialize(obj, App.WindowHandle); + WinRT.Interop.InitializeWithWindow.Initialize(obj, MainWindow.Instance.WindowHandle); return obj; } diff --git a/src/Files.App/Services/FileTagsService.cs b/src/Files.App/Services/FileTagsService.cs index 7b09fa491d4e..a0cc589a86ba 100644 --- a/src/Files.App/Services/FileTagsService.cs +++ b/src/Files.App/Services/FileTagsService.cs @@ -13,7 +13,7 @@ internal sealed class FileTagsService : IFileTagsService { private IStorageService StorageService { get; } = Ioc.Default.GetRequiredService(); - private readonly IFileTagsSettingsService FileTagsSettingsService = Ioc.Default.GetRequiredService(); + private IFileTagsSettingsService FileTagsSettingsService { get; } = Ioc.Default.GetRequiredService(); /// public Task IsSupportedAsync() @@ -45,7 +45,7 @@ public async IAsyncEnumerable GetItemsForTagAsync(string tagUid if (!item.Tags.Contains(tagUid)) continue; - var storable = await StorageService.TryGetStorableFromPathAsync(item.FilePath, cancellationToken); + var storable = await StorageService.TryGetStorableAsync(item.FilePath, cancellationToken); if (storable is null) continue; @@ -60,7 +60,7 @@ public async IAsyncEnumerable GetAllFileTagsAsync([EnumeratorCa { foreach (var item in FileTagsHelper.GetDbInstance().GetAll()) { - var storable = await StorageService.TryGetStorableFromPathAsync(item.FilePath, cancellationToken); + var storable = await StorageService.TryGetStorableAsync(item.FilePath, cancellationToken); if (storable is null) continue; diff --git a/src/Files.App/Services/NetworkDrivesService.cs b/src/Files.App/Services/NetworkDrivesService.cs index 954f3b6ed1ac..c4bd426f9aa7 100644 --- a/src/Files.App/Services/NetworkDrivesService.cs +++ b/src/Files.App/Services/NetworkDrivesService.cs @@ -66,7 +66,7 @@ public async IAsyncEnumerable GetDrivesAsync() public Task OpenMapNetworkDriveDialogAsync() { - var handle = NativeWinApiHelper.CoreWindowHandle.ToInt64(); + var handle = MainWindow.Instance.WindowHandle.ToInt64(); return NetworkDrivesAPI.OpenMapNetworkDriveDialog(handle); } } diff --git a/src/Files.App/Services/RemovableDrivesService.cs b/src/Files.App/Services/RemovableDrivesService.cs index e5442bd5689d..6bd33c17e1fa 100644 --- a/src/Files.App/Services/RemovableDrivesService.cs +++ b/src/Files.App/Services/RemovableDrivesService.cs @@ -57,7 +57,7 @@ public async Task UpdateDrivePropertiesAsync(ILocatableFolder drive) var rootModified = await FilesystemTasks.Wrap(() => StorageFolder.GetFolderFromPathAsync(drive.Path).AsTask()); if (rootModified && drive is DriveItem matchingDriveEjected) { - _ = App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => + _ = MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => { matchingDriveEjected.Root = rootModified.Result; matchingDriveEjected.Text = rootModified.Result.DisplayName; diff --git a/src/Files.App/Services/Settings/FileTagsSettingsService.cs b/src/Files.App/Services/Settings/FileTagsSettingsService.cs index 992467643471..98ee8aa1be1d 100644 --- a/src/Files.App/Services/Settings/FileTagsSettingsService.cs +++ b/src/Files.App/Services/Settings/FileTagsSettingsService.cs @@ -9,10 +9,7 @@ using Files.Core.Services.Settings; using Files.Core.ViewModels.FileTags; using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using Windows.Storage; namespace Files.App.Services.Settings @@ -90,11 +87,6 @@ public IEnumerable GetTagsByName(string tagName) return FileTagList.Where(x => x.Name.Equals(tagName, StringComparison.OrdinalIgnoreCase)); } - public IEnumerable SearchTagsByName(string tagName) - { - return FileTagList.Where(x => x.Name.StartsWith(tagName, StringComparison.OrdinalIgnoreCase)); - } - public void CreateNewTag(string newTagName, string color) { var newTag = new TagViewModel( diff --git a/src/Files.App/Services/UpdateService.cs b/src/Files.App/Services/UpdateService.cs index b18fb3ca8d11..3df30a1e206d 100644 --- a/src/Files.App/Services/UpdateService.cs +++ b/src/Files.App/Services/UpdateService.cs @@ -118,7 +118,7 @@ private async Task GetUpdatePackages() { _storeContext ??= await Task.Run(StoreContext.GetDefault); - InitializeWithWindow.Initialize(_storeContext, App.WindowHandle); + InitializeWithWindow.Initialize(_storeContext, MainWindow.Instance.WindowHandle); var updateList = await _storeContext.GetAppAndOptionalStorePackageUpdatesAsync(); _updatePackages = updateList?.ToList(); diff --git a/src/Files.App/UserControls/MultitaskingControl/BaseMultitaskingControl.cs b/src/Files.App/UserControls/MultitaskingControl/BaseMultitaskingControl.cs index 2cc0a680e82b..c86c938f495b 100644 --- a/src/Files.App/UserControls/MultitaskingControl/BaseMultitaskingControl.cs +++ b/src/Files.App/UserControls/MultitaskingControl/BaseMultitaskingControl.cs @@ -147,7 +147,7 @@ public void CloseTab(TabItem tabItem) { if (Items.Count == 1) { - App.CloseApp(); + MainWindow.Instance.Close(); } else if (Items.Count > 1) { diff --git a/src/Files.App/UserControls/MultitaskingControl/HorizontalMultitaskingControl.xaml.cs b/src/Files.App/UserControls/MultitaskingControl/HorizontalMultitaskingControl.xaml.cs index 0ea02b4e4a99..3f873e4c772a 100644 --- a/src/Files.App/UserControls/MultitaskingControl/HorizontalMultitaskingControl.xaml.cs +++ b/src/Files.App/UserControls/MultitaskingControl/HorizontalMultitaskingControl.xaml.cs @@ -1,16 +1,11 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.WinUI.UI; using Files.App.Commands; -using Files.App.Extensions; -using Files.App.Helpers; -using Files.App.ViewModels; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Shapes; -using System; using Windows.ApplicationModel.DataTransfer; using Windows.Storage; @@ -33,8 +28,8 @@ public HorizontalMultitaskingControl() tabHoverTimer.Interval = TimeSpan.FromMilliseconds(500); tabHoverTimer.Tick += TabHoverSelected; - var appWindowTitleBar = App.GetAppWindow(App.Window).TitleBar; - double rightPaddingColumnWidth = FilePropertiesHelpers.FlowDirectionSettingIsRightToLeft ? appWindowTitleBar.LeftInset : appWindowTitleBar.RightInset; + var appWindow = MainWindow.Instance.AppWindow; + double rightPaddingColumnWidth = FilePropertiesHelpers.FlowDirectionSettingIsRightToLeft ? appWindow.TitleBar.LeftInset : appWindow.TitleBar.RightInset; RightPaddingColumn.Width = new GridLength(rightPaddingColumnWidth >= 0 ? rightPaddingColumnWidth : 0); } diff --git a/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs b/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs index ae10e1145bda..f8c9fa481b05 100644 --- a/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs @@ -52,7 +52,7 @@ public async Task LoadCardThumbnailAsync() // Thumbnail data is valid, set the item icon if (thumbnailData is not null && thumbnailData.Length > 0) - Thumbnail = await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => thumbnailData.ToBitmapAsync(Constants.Widgets.WidgetIconSize)); + Thumbnail = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => thumbnailData.ToBitmapAsync(Constants.Widgets.WidgetIconSize)); } public int CompareTo(DriveCardItem? other) => Item.Path.CompareTo(other?.Item?.Path); diff --git a/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs b/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs index 5ef981e6a46f..1ca835fe7cff 100644 --- a/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs @@ -100,7 +100,7 @@ public async Task LoadCardThumbnailAsync() } if (thumbnailData is not null && thumbnailData.Length > 0) { - Thumbnail = await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => thumbnailData.ToBitmapAsync(Constants.Widgets.WidgetIconSize)); + Thumbnail = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => thumbnailData.ToBitmapAsync(Constants.Widgets.WidgetIconSize)); } } } diff --git a/src/Files.App/Utils/Cloud/CloudDrivesManager.cs b/src/Files.App/Utils/Cloud/CloudDrivesManager.cs index c25edca9c208..7953e17fa479 100644 --- a/src/Files.App/Utils/Cloud/CloudDrivesManager.cs +++ b/src/Files.App/Utils/Cloud/CloudDrivesManager.cs @@ -49,7 +49,7 @@ public async Task UpdateDrivesAsync() { cloudProviderItem.Root = await StorageFolder.GetFolderFromPathAsync(cloudProviderItem.Path); - _ = App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => cloudProviderItem.UpdatePropertiesAsync()); + _ = MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => cloudProviderItem.UpdatePropertiesAsync()); } catch (Exception ex) { @@ -69,7 +69,7 @@ public async Task UpdateDrivesAsync() { cloudProviderItem.IconData = iconData; - await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(async () + await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () => cloudProviderItem.Icon = await iconData.ToBitmapAsync()); } diff --git a/src/Files.App/Utils/FilesystemOperations/Helpers/FilesystemHelpers.cs b/src/Files.App/Utils/FilesystemOperations/Helpers/FilesystemHelpers.cs index 27ab99ae6e05..b15bb17e6217 100644 --- a/src/Files.App/Utils/FilesystemOperations/Helpers/FilesystemHelpers.cs +++ b/src/Files.App/Utils/FilesystemOperations/Helpers/FilesystemHelpers.cs @@ -1,11 +1,8 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using Files.App.Utils.FilesystemHistory; -using Files.App.Utils.StorageItems; -using Files.Core.Services; -using Files.Core.ViewModels.Dialogs.FileSystemDialog; using Files.Core.Storage; +using Files.Core.Storage.Extensions; using Files.Shared.Services; using Microsoft.Extensions.Logging; using System.IO; @@ -673,7 +670,7 @@ public static bool IsValidForFilename(string name) // Same item names in both directories if (StorageHelpers.Exists(item.dest) || (FtpHelpers.IsFtpPath(item.dest) && - await Ioc.Default.GetRequiredService().FileExistsAsync(item.dest))) + await Ioc.Default.GetRequiredService().TryGetFileAsync(item.dest) is not null)) { (incomingItems[item.index] as FileSystemDialogConflictItemViewModel)!.ConflictResolveOption = FileNameConflictResolveOptionType.GenerateNewName; conflictingItems.Add(incomingItems.ElementAt(item.index)); diff --git a/src/Files.App/Utils/FilesystemOperations/ShellFilesystemOperations.cs b/src/Files.App/Utils/FilesystemOperations/ShellFilesystemOperations.cs index 32c48535f509..f1024886ee6b 100644 --- a/src/Files.App/Utils/FilesystemOperations/ShellFilesystemOperations.cs +++ b/src/Files.App/Utils/FilesystemOperations/ShellFilesystemOperations.cs @@ -70,7 +70,7 @@ public async Task CopyItemsAsync(IList so if (sourceRename.Any()) { - var resultItem = await FileOperationsHelpers.CopyItemAsync(sourceRename.Select(s => s.Path).ToArray(), destinationRename.ToArray(), false, NativeWinApiHelper.CoreWindowHandle.ToInt64(), progress, operationID); + var resultItem = await FileOperationsHelpers.CopyItemAsync(sourceRename.Select(s => s.Path).ToArray(), destinationRename.ToArray(), false, MainWindow.Instance.WindowHandle.ToInt64(), progress, operationID); result &= (FilesystemResult)resultItem.Item1; @@ -79,7 +79,7 @@ public async Task CopyItemsAsync(IList so if (sourceReplace.Any()) { - var resultItem = await FileOperationsHelpers.CopyItemAsync(sourceReplace.Select(s => s.Path).ToArray(), destinationReplace.ToArray(), true, NativeWinApiHelper.CoreWindowHandle.ToInt64(), progress, operationID); + var resultItem = await FileOperationsHelpers.CopyItemAsync(sourceReplace.Select(s => s.Path).ToArray(), destinationReplace.ToArray(), true, MainWindow.Instance.WindowHandle.ToInt64(), progress, operationID); result &= (FilesystemResult)resultItem.Item1; @@ -358,7 +358,7 @@ public async Task DeleteItemsAsync(IList var operationID = Guid.NewGuid().ToString(); using var r = cancellationToken.Register(CancelOperation, operationID, false); - var (success, response) = await FileOperationsHelpers.DeleteItemAsync(deleteFilePaths.ToArray(), permanently, NativeWinApiHelper.CoreWindowHandle.ToInt64(), progress, operationID); + var (success, response) = await FileOperationsHelpers.DeleteItemAsync(deleteFilePaths.ToArray(), permanently, MainWindow.Instance.WindowHandle.ToInt64(), progress, operationID); var result = (FilesystemResult)success; var deleteResult = new ShellOperationResult(); @@ -474,7 +474,7 @@ public async Task MoveItemsAsync(IList so if (sourceRename.Any()) { - var (status, response) = await FileOperationsHelpers.MoveItemAsync(sourceRename.Select(s => s.Path).ToArray(), destinationRename.ToArray(), false, NativeWinApiHelper.CoreWindowHandle.ToInt64(), progress, operationID); + var (status, response) = await FileOperationsHelpers.MoveItemAsync(sourceRename.Select(s => s.Path).ToArray(), destinationRename.ToArray(), false, MainWindow.Instance.WindowHandle.ToInt64(), progress, operationID); result &= (FilesystemResult)status; moveResult.Items.AddRange(response?.Final ?? Enumerable.Empty()); @@ -482,7 +482,7 @@ public async Task MoveItemsAsync(IList so if (sourceReplace.Any()) { - var (status, response) = await FileOperationsHelpers.MoveItemAsync(sourceReplace.Select(s => s.Path).ToArray(), destinationReplace.ToArray(), true, NativeWinApiHelper.CoreWindowHandle.ToInt64(), progress, operationID); + var (status, response) = await FileOperationsHelpers.MoveItemAsync(sourceReplace.Select(s => s.Path).ToArray(), destinationReplace.ToArray(), true, MainWindow.Instance.WindowHandle.ToInt64(), progress, operationID); result &= (FilesystemResult)status; moveResult.Items.AddRange(response?.Final ?? Enumerable.Empty()); @@ -709,7 +709,7 @@ public async Task RestoreItemsFromTrashAsync(IList s.Path).ToArray(), destination.ToArray(), false, NativeWinApiHelper.CoreWindowHandle.ToInt64(), progress, operationID); + var (status, response) = await FileOperationsHelpers.MoveItemAsync(source.Select(s => s.Path).ToArray(), destination.ToArray(), false, MainWindow.Instance.WindowHandle.ToInt64(), progress, operationID); var result = (FilesystemResult)status; moveResult.Items.AddRange(response?.Final ?? Enumerable.Empty()); diff --git a/src/Files.App/Utils/Git/GitHelpers.cs b/src/Files.App/Utils/Git/GitHelpers.cs index 2a446bcda42e..4f99dce93d14 100644 --- a/src/Files.App/Utils/Git/GitHelpers.cs +++ b/src/Files.App/Utils/Git/GitHelpers.cs @@ -30,6 +30,8 @@ internal static class GitHelpers private static readonly IDialogService _dialogService = Ioc.Default.GetRequiredService(); + private static readonly IApplicationService _applicationService = Ioc.Default.GetRequiredService(); + private static readonly FetchOptions _fetchOptions = new() { Prune = true @@ -37,8 +39,7 @@ internal static class GitHelpers private static readonly PullOptions _pullOptions = new(); - private static readonly string _clientId = - EnvHelpers.GetAppEnvironmentAndLogo().Item1 is AppEnvironment.Store or AppEnvironment.Stable or AppEnvironment.Preview + private static readonly string _clientId = _applicationService.Environment is AppEnvironment.Store or AppEnvironment.Stable or AppEnvironment.Preview ? CLIENT_ID_SECRET : string.Empty; @@ -266,7 +267,7 @@ public static void FetchOrigin(string? repositoryPath) _logger.LogWarning(ex.Message); } - App.Window.DispatcherQueue.TryEnqueue(() => + MainWindow.Instance.DispatcherQueue.TryEnqueue(() => { IsExecutingGitAction = false; GitFetchCompleted?.Invoke(null, EventArgs.Empty); diff --git a/src/Files.App/Utils/QuickAccessManager.cs b/src/Files.App/Utils/QuickAccessManager.cs index ad983579026b..7bbe19ffc5a8 100644 --- a/src/Files.App/Utils/QuickAccessManager.cs +++ b/src/Files.App/Utils/QuickAccessManager.cs @@ -1,15 +1,9 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using CommunityToolkit.Mvvm.DependencyInjection; -using CommunityToolkit.WinUI.Helpers; -using Files.App.Data.Models; -using Files.App.Helpers; using Files.App.Services; using Files.App.UserControls.Widgets; -using System; using System.IO; -using System.Threading.Tasks; namespace Files.App.Utils { @@ -21,14 +15,15 @@ public sealed class QuickAccessManager public EventHandler? UpdateQuickAccessWidget; - public IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); + public IQuickAccessService QuickAccessService; public SidebarPinnedModel Model; public QuickAccessManager() { + QuickAccessService = Ioc.Default.GetRequiredService(); Model = new(); Initialize(); - } + } public void Initialize() { @@ -50,8 +45,8 @@ public async Task InitializeAsync() { PinnedItemsModified += Model.LoadAsync; - if (!Model.FavoriteItems.Contains(Constants.UserEnvironmentPaths.RecycleBinPath) && SystemInformation.Instance.IsFirstRun) - await QuickAccessService.PinToSidebar(Constants.UserEnvironmentPaths.RecycleBinPath); + //if (!Model.FavoriteItems.Contains(Constants.UserEnvironmentPaths.RecycleBinPath) && SystemInformation.Instance.IsFirstRun) + // await QuickAccessService.PinToSidebar(Constants.UserEnvironmentPaths.RecycleBinPath); await Model.LoadAsync(); } diff --git a/src/Files.App/Utils/Search/FolderSearch.cs b/src/Files.App/Utils/Search/FolderSearch.cs index 08b50413bc34..ab9843846031 100644 --- a/src/Files.App/Utils/Search/FolderSearch.cs +++ b/src/Files.App/Utils/Search/FolderSearch.cs @@ -404,7 +404,7 @@ private ListedItem GetListedItemAsync(string itemPath, WIN32_FIND_DATA findData) { if (t.IsCompletedSuccessfully && t.Result is not null) { - _ = FilesystemTasks.Wrap(() => App.Window.DispatcherQueue.EnqueueOrInvokeAsync(async () => + _ = FilesystemTasks.Wrap(() => MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () => { listedItem.FileImage = await t.Result.ToBitmapAsync(); }, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low)); diff --git a/src/Files.App/ViewModels/Dialogs/CreateShortcutDialogViewModel.cs b/src/Files.App/ViewModels/Dialogs/CreateShortcutDialogViewModel.cs index f0416e7192b6..efafa2cc96f4 100644 --- a/src/Files.App/ViewModels/Dialogs/CreateShortcutDialogViewModel.cs +++ b/src/Files.App/ViewModels/Dialogs/CreateShortcutDialogViewModel.cs @@ -100,7 +100,7 @@ private async Task SelectDestination() private FolderPicker InitializeWithWindow(FolderPicker obj) { - WinRT.Interop.InitializeWithWindow.Initialize(obj, App.WindowHandle); + WinRT.Interop.InitializeWithWindow.Initialize(obj, MainWindow.Instance.WindowHandle); return obj; } diff --git a/src/Files.App/ViewModels/Dialogs/DecompressArchiveDialogViewModel.cs b/src/Files.App/ViewModels/Dialogs/DecompressArchiveDialogViewModel.cs index 50b43acf1ac9..4b8a8ba03bb4 100644 --- a/src/Files.App/ViewModels/Dialogs/DecompressArchiveDialogViewModel.cs +++ b/src/Files.App/ViewModels/Dialogs/DecompressArchiveDialogViewModel.cs @@ -71,7 +71,7 @@ private async Task SelectDestination() // WINUI3 private FolderPicker InitializeWithWindow(FolderPicker obj) { - WinRT.Interop.InitializeWithWindow.Initialize(obj, App.WindowHandle); + WinRT.Interop.InitializeWithWindow.Initialize(obj, MainWindow.Instance.WindowHandle); return obj; } diff --git a/src/Files.App/ViewModels/MainPageViewModel.cs b/src/Files.App/ViewModels/MainPageViewModel.cs index 12b1dfed9ee4..86626e68b187 100644 --- a/src/Files.App/ViewModels/MainPageViewModel.cs +++ b/src/Files.App/ViewModels/MainPageViewModel.cs @@ -160,7 +160,7 @@ public async Task UpdateInstanceProperties(object navigationArg) windowTitle = $"{windowTitle} ({AppInstances.Count})"; if (navigationArg == SelectedTabItem?.TabItemArguments?.NavigationArg) - App.GetAppWindow(App.Window).Title = $"{windowTitle} - Files"; + MainWindow.Instance.AppWindow.Title = $"{windowTitle} - Files"; } public async Task UpdateTabInfo(TabItem tabItem, object navigationArg) diff --git a/src/Files.App/ViewModels/Previews/BasePreviewModel.cs b/src/Files.App/ViewModels/Previews/BasePreviewModel.cs index bc804e38dc72..e908434e1dfc 100644 --- a/src/Files.App/ViewModels/Previews/BasePreviewModel.cs +++ b/src/Files.App/ViewModels/Previews/BasePreviewModel.cs @@ -90,11 +90,11 @@ public async virtual Task> LoadPreviewAndDetailsAsync() iconData ??= await FileThumbnailHelper.LoadIconWithoutOverlayAsync(Item.ItemPath, 256); if (iconData is not null) { - await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(async () => FileImage = await iconData.ToBitmapAsync()); + await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () => FileImage = await iconData.ToBitmapAsync()); } else { - FileImage ??= await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => new BitmapImage()); + FileImage ??= await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => new BitmapImage()); } return new List(); diff --git a/src/Files.App/ViewModels/Previews/ImagePreviewViewModel.cs b/src/Files.App/ViewModels/Previews/ImagePreviewViewModel.cs index 7bfaeadbfaba..7759b44c3d27 100644 --- a/src/Files.App/ViewModels/Previews/ImagePreviewViewModel.cs +++ b/src/Files.App/ViewModels/Previews/ImagePreviewViewModel.cs @@ -32,7 +32,7 @@ public override async Task> LoadPreviewAndDetailsAsync() { using IRandomAccessStream stream = await Item.ItemFile.OpenAsync(FileAccessMode.Read); - await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(async () => + await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () => { BitmapImage bitmap = new(); await bitmap.SetSourceAsync(stream); diff --git a/src/Files.App/ViewModels/Previews/PDFPreviewViewModel.cs b/src/Files.App/ViewModels/Previews/PDFPreviewViewModel.cs index c23b08f97a41..6aa749c07df6 100644 --- a/src/Files.App/ViewModels/Previews/PDFPreviewViewModel.cs +++ b/src/Files.App/ViewModels/Previews/PDFPreviewViewModel.cs @@ -88,7 +88,7 @@ private async Task LoadPagesAsync(PdfDocument pdf) BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream); using SoftwareBitmap sw = await decoder.GetSoftwareBitmapAsync(); - await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(async () => + await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () => { BitmapImage src = new(); PageViewModel pageData = new() diff --git a/src/Files.App/ViewModels/Properties/CustomizationViewModel.cs b/src/Files.App/ViewModels/Properties/CustomizationViewModel.cs index a04de662b473..c46d3c599764 100644 --- a/src/Files.App/ViewModels/Properties/CustomizationViewModel.cs +++ b/src/Files.App/ViewModels/Properties/CustomizationViewModel.cs @@ -125,7 +125,7 @@ public async Task UpdateIcon() if (!result) return false; - await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => + await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => { _appInstance?.FilesystemViewModel?.RefreshItems(null); }); diff --git a/src/Files.App/ViewModels/Properties/HashesViewModel.cs b/src/Files.App/ViewModels/Properties/HashesViewModel.cs index a24c959ca1a5..b965a4eb86e1 100644 --- a/src/Files.App/ViewModels/Properties/HashesViewModel.cs +++ b/src/Files.App/ViewModels/Properties/HashesViewModel.cs @@ -84,7 +84,7 @@ private void ToggleIsEnabled(string? algorithm) { hashInfoItem.IsCalculating = true; - App.Window.DispatcherQueue.EnqueueOrInvokeAsync(async () => + MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () => { try { diff --git a/src/Files.App/ViewModels/Properties/Items/FileProperties.cs b/src/Files.App/ViewModels/Properties/Items/FileProperties.cs index 82a70bfe14ca..cbc83be4ffa0 100644 --- a/src/Files.App/ViewModels/Properties/Items/FileProperties.cs +++ b/src/Files.App/ViewModels/Properties/Items/FileProperties.cs @@ -78,7 +78,7 @@ public override void GetBaseProperties() } else { - await App.Window.DispatcherQueue.EnqueueOrInvokeAsync( + await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync( () => NavigationHelpers.OpenPathInNewTab(Path.GetDirectoryName(ViewModel.ShortcutItemPath))); } }, diff --git a/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs b/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs index 30850ae8dddf..ed0859014d8e 100644 --- a/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs +++ b/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs @@ -61,7 +61,7 @@ public override void GetBaseProperties() ViewModel.ShortcutItemArgumentsVisibility = false; ViewModel.ShortcutItemOpenLinkCommand = new RelayCommand(async () => { - await App.Window.DispatcherQueue.EnqueueOrInvokeAsync( + await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync( () => NavigationHelpers.OpenPathInNewTab(Path.GetDirectoryName(Environment.ExpandEnvironmentVariables(ViewModel.ShortcutItemPath)))); }, () => diff --git a/src/Files.App/ViewModels/Properties/SecurityAdvancedViewModel.cs b/src/Files.App/ViewModels/Properties/SecurityAdvancedViewModel.cs index 3ef475538864..8d4ae7deac36 100644 --- a/src/Files.App/ViewModels/Properties/SecurityAdvancedViewModel.cs +++ b/src/Files.App/ViewModels/Properties/SecurityAdvancedViewModel.cs @@ -202,7 +202,7 @@ private async Task ExecuteChangeOwnerCommand() if (string.IsNullOrEmpty(sid)) return; - await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => + await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => { // Set owner FileSecurityHelpers.SetOwner(_path, sid); @@ -219,7 +219,7 @@ private async Task ExecuteAddAccessControlEntryCommand() if (string.IsNullOrEmpty(sid)) return; - await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => + await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => { // Run Win32API var win32Result = FileSecurityHelpers.AddAccessControlEntry(_path, sid); @@ -235,7 +235,7 @@ private async Task ExecuteRemoveAccessControlEntryCommand() if (SelectedAccessControlEntry is null) return; - await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => + await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => { // Get index of the ACE var index = AccessControlList.AccessControlEntries.IndexOf(SelectedAccessControlEntry); diff --git a/src/Files.App/ViewModels/Properties/SecurityViewModel.cs b/src/Files.App/ViewModels/Properties/SecurityViewModel.cs index 54bfdc3891ff..a3ac248f4e0b 100644 --- a/src/Files.App/ViewModels/Properties/SecurityViewModel.cs +++ b/src/Files.App/ViewModels/Properties/SecurityViewModel.cs @@ -117,7 +117,7 @@ private async Task ExecuteAddAccessControlEntryCommand() if (string.IsNullOrEmpty(sid)) return; - await App.Window.DispatcherQueue.EnqueueAsync(() => + await MainWindow.Instance.DispatcherQueue.EnqueueAsync(() => { // Run Win32API var win32Result = FileSecurityHelpers.AddAccessControlEntry(_path, sid); @@ -130,7 +130,7 @@ await App.Window.DispatcherQueue.EnqueueAsync(() => private async Task ExecuteRemoveAccessControlEntryCommand() { - await App.Window.DispatcherQueue.EnqueueAsync(() => + await MainWindow.Instance.DispatcherQueue.EnqueueAsync(() => { // Get index of the ACE var index = AccessControlList.AccessControlEntries.IndexOf(SelectedAccessControlEntry); diff --git a/src/Files.App/ViewModels/Settings/AdvancedViewModel.cs b/src/Files.App/ViewModels/Settings/AdvancedViewModel.cs index 708a52ee05ec..5059e6541178 100644 --- a/src/Files.App/ViewModels/Settings/AdvancedViewModel.cs +++ b/src/Files.App/ViewModels/Settings/AdvancedViewModel.cs @@ -199,9 +199,11 @@ private async Task ImportSettings() private async Task ExportSettings() { + var applicationService = Ioc.Default.GetRequiredService(); + FileSavePicker filePicker = InitializeWithWindow(new FileSavePicker()); filePicker.FileTypeChoices.Add("Zip File", new[] { ".zip" }); - filePicker.SuggestedFileName = $"Files_{App.AppVersion}"; + filePicker.SuggestedFileName = $"Files_{applicationService.AppVersion}"; StorageFile file = await filePicker.PickSaveFileAsync(); if (file is not null) @@ -270,14 +272,14 @@ public bool IsSetAsOpenFileDialog private FileSavePicker InitializeWithWindow(FileSavePicker obj) { - WinRT.Interop.InitializeWithWindow.Initialize(obj, App.WindowHandle); + WinRT.Interop.InitializeWithWindow.Initialize(obj, MainWindow.Instance.WindowHandle); return obj; } private FileOpenPicker InitializeWithWindow(FileOpenPicker obj) { - WinRT.Interop.InitializeWithWindow.Initialize(obj, App.WindowHandle); + WinRT.Interop.InitializeWithWindow.Initialize(obj, MainWindow.Instance.WindowHandle); return obj; } diff --git a/src/Files.App/ViewModels/Settings/GeneralViewModel.cs b/src/Files.App/ViewModels/Settings/GeneralViewModel.cs index 1ec657e60805..755acf11d3af 100644 --- a/src/Files.App/ViewModels/Settings/GeneralViewModel.cs +++ b/src/Files.App/ViewModels/Settings/GeneralViewModel.cs @@ -334,7 +334,7 @@ private async Task ChangePage() // WINUI3 private FolderPicker InitializeWithWindow(FolderPicker obj) { - WinRT.Interop.InitializeWithWindow.Initialize(obj, App.WindowHandle); + WinRT.Interop.InitializeWithWindow.Initialize(obj, MainWindow.Instance.WindowHandle); return obj; } diff --git a/src/Files.App/ViewModels/UserControls/ToolbarViewModel.cs b/src/Files.App/ViewModels/UserControls/ToolbarViewModel.cs index 30f5688b7b95..68e2282197d5 100644 --- a/src/Files.App/ViewModels/UserControls/ToolbarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/ToolbarViewModel.cs @@ -477,7 +477,7 @@ public async Task PathBoxItem_Tapped(object sender, TappedRoutedEventArgs e) if (pointerRoutedEventArgs is not null) { - await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(async () => + await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () => { await mainPageViewModel.AddNewTabByPathAsync(typeof(PaneHolderPage), itemTappedPath); }, DispatcherQueuePriority.Low); @@ -516,7 +516,7 @@ public void UpdateAdditionalActions() OnPropertyChanged(nameof(HasAdditionalAction)); } - private AddressToolbar? AddressToolbar => (App.Window.Content as Frame)?.FindDescendant(); + private AddressToolbar? AddressToolbar => (MainWindow.Instance.Content as Frame)?.FindDescendant(); public void CloseSearchBox() { diff --git a/src/Files.App/ViewModels/Widgets/FileTagsItemViewModel.cs b/src/Files.App/ViewModels/Widgets/FileTagsItemViewModel.cs index 0d5ab72b1d9c..5199b196e0b8 100644 --- a/src/Files.App/ViewModels/Widgets/FileTagsItemViewModel.cs +++ b/src/Files.App/ViewModels/Widgets/FileTagsItemViewModel.cs @@ -1,15 +1,15 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using Files.Core.Storage.Extensions; -using Files.Core.Storage.LocatableStorage; using Files.App.UserControls.Widgets; +using Files.Core.Storage; +using Files.Core.Storage.Extensions; namespace Files.App.ViewModels.Widgets { public sealed partial class FileTagsItemViewModel : WidgetCardItem { - private readonly ILocatableStorable _associatedStorable; + private readonly IStorable _associatedStorable; // A workaround for lack of MVVM-compliant navigation support. // This workaround must be kept until further refactor of navigation code is completed. @@ -28,14 +28,14 @@ public override string Path set => SetProperty(ref path, value); } - public bool IsFolder => _associatedStorable is ILocatableFolder; + public bool IsFolder => _associatedStorable is IFolder; - public FileTagsItemViewModel(ILocatableStorable associatedStorable, Func openAction, IImageModel? icon) + public FileTagsItemViewModel(IStorable associatedStorable, Func openAction, IImageModel? icon) { _associatedStorable = associatedStorable; _openAction = openAction; _Icon = icon; - _Name = PathHelpers.FormatName(associatedStorable.Path); + _Name = PathHelpers.FormatName(associatedStorable.Id); Path = associatedStorable.TryGetPath(); Item = this; } @@ -43,7 +43,7 @@ public FileTagsItemViewModel(ILocatableStorable associatedStorable, Func (App.Window.Content as Frame)?.FindDescendant(); + => (MainWindow.Instance.Content as Frame)?.FindDescendant(); private CollectionViewSource collectionViewSource = new() { @@ -733,7 +733,7 @@ private async Task AddShellMenuItemsAsync(List s var overflowItems = ItemModelListToContextFlyoutHelper.GetMenuFlyoutItemsFromModel(overflowShellMenuItems); var mainItems = ItemModelListToContextFlyoutHelper.GetAppBarButtonsFromModelIgnorePrimary(mainShellMenuItems); - var openedPopups = Microsoft.UI.Xaml.Media.VisualTreeHelper.GetOpenPopups(App.Window); + var openedPopups = Microsoft.UI.Xaml.Media.VisualTreeHelper.GetOpenPopups(MainWindow.Instance); var secondaryMenu = openedPopups.FirstOrDefault(popup => popup.Name == "OverflowPopup"); var itemsControl = secondaryMenu?.Child.FindDescendant(); @@ -741,11 +741,11 @@ private async Task AddShellMenuItemsAsync(List s { contextMenuFlyout.SetValue(ContextMenuExtensions.ItemsControlProperty, itemsControl); - var ttv = secondaryMenu.TransformToVisual(App.Window.Content); + var ttv = secondaryMenu.TransformToVisual(MainWindow.Instance.Content); var cMenuPos = ttv.TransformPoint(new Point(0, 0)); var requiredHeight = contextMenuFlyout.SecondaryCommands.Concat(mainItems).Where(x => x is not AppBarSeparator).Count() * Constants.UI.ContextMenuSecondaryItemsHeight; - var availableHeight = App.Window.Bounds.Height - cMenuPos.Y - Constants.UI.ContextMenuPrimaryItemsHeight; + var availableHeight = MainWindow.Instance.Bounds.Height - cMenuPos.Y - Constants.UI.ContextMenuPrimaryItemsHeight; // Set menu max height to current height (Avoid menu repositioning) if (requiredHeight > availableHeight) @@ -1390,7 +1390,7 @@ protected void UpdatePreviewPaneSelection(List? value) // Check if the preview pane is open before updating the model if (PreviewPaneViewModel.IsEnabled) { - var isPaneEnabled = ((App.Window.Content as Frame)?.Content as MainPage)?.ShouldPreviewPaneBeActive ?? false; + var isPaneEnabled = ((MainWindow.Instance.Content as Frame)?.Content as MainPage)?.ShouldPreviewPaneBeActive ?? false; if (isPaneEnabled) _ = PreviewPaneViewModel.UpdateSelectedItemPreview(); } diff --git a/src/Files.App/Views/MainPage.xaml.cs b/src/Files.App/Views/MainPage.xaml.cs index b46943b4c0bc..768dac88d10b 100644 --- a/src/Files.App/Views/MainPage.xaml.cs +++ b/src/Files.App/Views/MainPage.xaml.cs @@ -82,7 +82,7 @@ private async Task PromptForReview() try { var storeContext = StoreContext.GetDefault(); - InitializeWithWindow.Initialize(storeContext, App.WindowHandle); + InitializeWithWindow.Initialize(storeContext, MainWindow.Instance.WindowHandle); var storeRateAndReviewResult = await storeContext.RequestRateAndReviewAppAsync(); App.Logger.LogInformation($"STORE: review request status: {storeRateAndReviewResult.Status}"); @@ -97,7 +97,7 @@ private async Task PromptForReview() private ContentDialog SetContentDialogRoot(ContentDialog contentDialog) { if (Windows.Foundation.Metadata.ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8)) - contentDialog.XamlRoot = App.Window.Content.XamlRoot; + contentDialog.XamlRoot = MainWindow.Instance.Content.XamlRoot; return contentDialog; } @@ -127,7 +127,7 @@ private void HorizontalMultitaskingControl_Loaded(object sender, RoutedEventArgs private void SetRectDragRegion() { DragZoneHelper.SetDragZones( - App.Window, + MainWindow.Instance, dragZoneLeftIndent: (int)(TabControl.ActualWidth + TabControl.Margin.Left - TabControl.DragArea.ActualWidth)); } @@ -494,7 +494,7 @@ public bool ShouldPreviewPaneBeDisplayed { var isHomePage = !(SidebarAdaptiveViewModel.PaneHolder?.ActivePane?.InstanceViewModel?.IsPageTypeNotHome ?? false); var isMultiPane = SidebarAdaptiveViewModel.PaneHolder?.IsMultiPaneActive ?? false; - var isBigEnough = App.Window.Bounds.Width > 450 && App.Window.Bounds.Height > 450 || RootGrid.ActualWidth > 700 && App.Window.Bounds.Height > 360; + var isBigEnough = MainWindow.Instance.Bounds.Width > 450 && MainWindow.Instance.Bounds.Height > 450 || RootGrid.ActualWidth > 700 && MainWindow.Instance.Bounds.Height > 360; var isEnabled = (!isHomePage || isMultiPane) && isBigEnough; return isEnabled; diff --git a/src/Files.App/Views/PaneHolderPage.xaml.cs b/src/Files.App/Views/PaneHolderPage.xaml.cs index 5156163eb822..52867dbd0d96 100644 --- a/src/Files.App/Views/PaneHolderPage.xaml.cs +++ b/src/Files.App/Views/PaneHolderPage.xaml.cs @@ -46,7 +46,7 @@ public TabItemArguments TabItemArguments } } - private bool _WindowIsCompact = App.Window.Bounds.Width <= 750; + private bool _WindowIsCompact = MainWindow.Instance.Bounds.Width <= 750; public bool WindowIsCompact { get => _WindowIsCompact; @@ -78,7 +78,7 @@ public bool IsMultiPaneActive => IsRightPaneVisible; public bool IsMultiPaneEnabled - => !(App.Window.Bounds.Width <= 750); + => !(MainWindow.Instance.Bounds.Width <= 750); private NavigationParams _NavParamsLeft; public NavigationParams NavParamsLeft @@ -199,7 +199,7 @@ public PaneHolderPage() UserSettingsService = Ioc.Default.GetRequiredService(); - App.Window.SizeChanged += Current_SizeChanged; + MainWindow.Instance.SizeChanged += Current_SizeChanged; ActivePane = PaneLeft; IsRightPaneVisible = IsMultiPaneEnabled && UserSettingsService.GeneralSettingsService.AlwaysOpenDualPaneInNewTab; @@ -208,7 +208,7 @@ public PaneHolderPage() private void Current_SizeChanged(object sender, WindowSizeChangedEventArgs e) { - WindowIsCompact = App.Window.Bounds.Width <= 750; + WindowIsCompact = MainWindow.Instance.Bounds.Width <= 750; } protected override void OnNavigatedTo(NavigationEventArgs eventArgs) @@ -355,7 +355,7 @@ private async void Pane_GotFocus(object sender, RoutedEventArgs e) public void Dispose() { - App.Window.SizeChanged -= Current_SizeChanged; + MainWindow.Instance.SizeChanged -= Current_SizeChanged; PaneLeft?.Dispose(); PaneRight?.Dispose(); PaneResizer.DoubleTapped -= PaneResizer_OnDoubleTapped; diff --git a/src/Files.App/Views/Properties/GeneralPage.xaml.cs b/src/Files.App/Views/Properties/GeneralPage.xaml.cs index a8aafc908071..d1ee198bb351 100644 --- a/src/Files.App/Views/Properties/GeneralPage.xaml.cs +++ b/src/Files.App/Views/Properties/GeneralPage.xaml.cs @@ -86,7 +86,7 @@ bool SaveDrive(DriveItem drive) newName = letterRegex.Replace(newName, string.Empty); // Remove "(C:)" from the new label Win32API.SetVolumeLabel(drive.Path, newName); - _ = App.Window.DispatcherQueue.EnqueueOrInvokeAsync(async () => + _ = MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () => { await drive.UpdateLabelAsync(); await fsVM.SetWorkingDirectoryAsync(drive.Path); @@ -107,7 +107,7 @@ async Task SaveLibraryAsync(LibraryItem library) if (renamed is ReturnResult.Success) { var newPath = Path.Combine(Path.GetDirectoryName(library.ItemPath)!, newName); - _ = App.Window.DispatcherQueue.EnqueueOrInvokeAsync(async () => + _ = MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () => { await fsVM.SetWorkingDirectoryAsync(newPath); }); @@ -125,7 +125,7 @@ async Task SaveCombinedAsync(IList fileOrFolders) { foreach (var fileOrFolder in fileOrFolders) { - await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => + await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => UIFilesystemHelpers.SetHiddenAttributeItem(fileOrFolder, ViewModel.IsHidden, itemMM) ); } @@ -139,7 +139,7 @@ async Task SaveBaseAsync(ListedItem item) var itemMM = AppInstance?.SlimContentPage?.ItemManipulationModel; if (itemMM is not null) // null on homepage { - await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => + await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => UIFilesystemHelpers.SetHiddenAttributeItem(item, ViewModel.IsHidden, itemMM) ); } @@ -147,7 +147,7 @@ await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => if (!GetNewName(out var newName)) return true; - return await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => + return await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => UIFilesystemHelpers.RenameFileItemAsync(item, ViewModel.ItemName, AppInstance, false) ); } diff --git a/src/Files.App/Views/Properties/LibraryPage.xaml.cs b/src/Files.App/Views/Properties/LibraryPage.xaml.cs index 92dba43938df..b18a5f2bb7ae 100644 --- a/src/Files.App/Views/Properties/LibraryPage.xaml.cs +++ b/src/Files.App/Views/Properties/LibraryPage.xaml.cs @@ -119,7 +119,7 @@ private async Task AddLocation() // WINUI3 private FolderPicker InitializeWithWindow(FolderPicker obj) { - WinRT.Interop.InitializeWithWindow.Initialize(obj, App.WindowHandle); + WinRT.Interop.InitializeWithWindow.Initialize(obj, MainWindow.Instance.WindowHandle); return obj; } diff --git a/src/Files.App/Views/Properties/ShortcutPage.xaml.cs b/src/Files.App/Views/Properties/ShortcutPage.xaml.cs index c3dde60d92ad..d826b712cead 100644 --- a/src/Files.App/Views/Properties/ShortcutPage.xaml.cs +++ b/src/Files.App/Views/Properties/ShortcutPage.xaml.cs @@ -29,7 +29,7 @@ public override async Task SaveChangesAsync() if (shortcutItem is null) return true; - await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => + await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => UIFilesystemHelpers.UpdateShortcutItemProperties(shortcutItem, ViewModel.ShortcutItemPath, ViewModel.ShortcutItemArguments, diff --git a/src/Files.App/Views/Shells/BaseShellPage.cs b/src/Files.App/Views/Shells/BaseShellPage.cs index dc37947b3cb6..d69894115e68 100644 --- a/src/Files.App/Views/Shells/BaseShellPage.cs +++ b/src/Files.App/Views/Shells/BaseShellPage.cs @@ -690,7 +690,7 @@ protected void SelectSidebarItemFromPath(Type incomingSourcePageType = null) protected void SetLoadingIndicatorForTabs(bool isLoading) { - var multitaskingControls = ((App.Window.Content as Frame).Content as MainPage).ViewModel.MultitaskingControls; + var multitaskingControls = ((MainWindow.Instance.Content as Frame).Content as MainPage).ViewModel.MultitaskingControls; foreach (var x in multitaskingControls) x.SetLoadingIndicatorStatus(x.Items.FirstOrDefault(x => x.Control.TabItemContent == PaneHolder), isLoading); @@ -700,7 +700,7 @@ protected void SetLoadingIndicatorForTabs(bool isLoading) protected static ContentDialog SetContentDialogRoot(ContentDialog contentDialog) { if (Windows.Foundation.Metadata.ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8)) - contentDialog.XamlRoot = App.Window.Content.XamlRoot; + contentDialog.XamlRoot = MainWindow.Instance.Content.XamlRoot; return contentDialog; } diff --git a/src/Files.Core.Storage/DirectStorage/IDirectCopy.cs b/src/Files.Core.Storage/DirectStorage/IDirectCopy.cs new file mode 100644 index 000000000000..b03bf7974834 --- /dev/null +++ b/src/Files.Core.Storage/DirectStorage/IDirectCopy.cs @@ -0,0 +1,18 @@ +using Files.Core.Storage.ModifiableStorage; +using Files.Core.Storage.NestedStorage; +using System.Threading; +using System.Threading.Tasks; + +namespace Files.Core.Storage.DirectStorage +{ + /// + /// Provides direct copy operation of storage objects. + /// + public interface IDirectCopy : IModifiableFolder + { + /// + /// Creates a copy of the provided storable item in this folder. + /// + Task CreateCopyOfAsync(INestedStorable itemToCopy, bool overwrite = default, CancellationToken cancellationToken = default); + } +} diff --git a/src/Files.Core.Storage/DirectStorage/IDirectMove.cs b/src/Files.Core.Storage/DirectStorage/IDirectMove.cs new file mode 100644 index 000000000000..074391439215 --- /dev/null +++ b/src/Files.Core.Storage/DirectStorage/IDirectMove.cs @@ -0,0 +1,18 @@ +using Files.Core.Storage.ModifiableStorage; +using Files.Core.Storage.NestedStorage; +using System.Threading; +using System.Threading.Tasks; + +namespace Files.Core.Storage.DirectStorage +{ + /// + /// Provides direct move operation of storage objects. + /// + public interface IDirectMove : IModifiableFolder + { + /// + /// Moves a storable item out of the provided folder, and into this folder. Returns the new item that resides in this folder. + /// + Task MoveFromAsync(INestedStorable itemToMove, IModifiableFolder source, bool overwrite = default, CancellationToken cancellationToken = default); + } +} diff --git a/src/Files.Core.Storage/Enums/CreationCollisionOption.cs b/src/Files.Core.Storage/Enums/CreationCollisionOption.cs deleted file mode 100644 index 835e89236f79..000000000000 --- a/src/Files.Core.Storage/Enums/CreationCollisionOption.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2023 Files Community -// Licensed under the MIT License. See the LICENSE. - -namespace Files.Core.Storage.Enums -{ - public enum CreationCollisionOption : byte - { - GenerateUniqueName = 0, - ReplaceExisting = 1, - OpenIfExists = 2, - FailIfExists = 3, - } -} diff --git a/src/Files.Core.Storage/Enums/NameCollisionOption.cs b/src/Files.Core.Storage/Enums/NameCollisionOption.cs deleted file mode 100644 index e349fc257be7..000000000000 --- a/src/Files.Core.Storage/Enums/NameCollisionOption.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2023 Files Community -// Licensed under the MIT License. See the LICENSE. - -namespace Files.Core.Storage.Enums -{ - public enum NameCollisionOption : byte - { - GenerateUniqueName = 0, - ReplaceExisting = 1, - FailIfExists = 2 - } -} diff --git a/src/Files.Core.Storage/ExtendableStorage/IFolderExtended.cs b/src/Files.Core.Storage/ExtendableStorage/IFolderExtended.cs new file mode 100644 index 000000000000..05f06ed27caf --- /dev/null +++ b/src/Files.Core.Storage/ExtendableStorage/IFolderExtended.cs @@ -0,0 +1,28 @@ +using Files.Core.Storage.NestedStorage; +using System.Threading; +using System.Threading.Tasks; + +namespace Files.Core.Storage.ExtendableStorage +{ + /// + /// Extends existing interface with additional functionality. + /// + public interface IFolderExtended : IFolder + { + /// + /// Gets a file in the current directory by name. + /// + /// The name of the file. + /// A that cancels this action. + /// A that represents the asynchronous operation. Value is , otherwise an exception is thrown. + Task GetFileAsync(string fileName, CancellationToken cancellationToken = default); + + /// + /// Gets a folder in the current directory by name. + /// + /// The name of the folder. + /// A that cancels this action. + /// A that represents the asynchronous operation. If folder is found and access is granted, returns otherwise null. + Task GetFolderAsync(string folderName, CancellationToken cancellationToken = default); + } +} diff --git a/src/Files.Core.Storage/Extensions/StorageExtensions.File.cs b/src/Files.Core.Storage/Extensions/StorageExtensions.File.cs index 574c053dc809..4c95fb23d484 100644 --- a/src/Files.Core.Storage/Extensions/StorageExtensions.File.cs +++ b/src/Files.Core.Storage/Extensions/StorageExtensions.File.cs @@ -11,6 +11,16 @@ namespace Files.Core.Storage.Extensions { public static partial class StorageExtensions { + /// + public static async Task OpenStreamAsync(this IFile file, FileAccess access, FileShare share = FileShare.None, CancellationToken cancellationToken = default) + { + if (file is IFileExtended fileExtended) + return await fileExtended.OpenStreamAsync(access, share, cancellationToken); + + // TODO: Check if the file inherits from ILockableStorable and ensure a disposable handle to it via Stream bridge + return await file.OpenStreamAsync(access, cancellationToken); + } + /// If successful, returns a , otherwise null. /// public static async Task TryOpenStreamAsync(this IFile file, FileAccess access, CancellationToken cancellationToken = default) @@ -31,11 +41,7 @@ public static partial class StorageExtensions { try { - if (file is IFileExtended fileExtended) - return await fileExtended.OpenStreamAsync(access, share, cancellationToken); - - // TODO: Check if the file inherits from ILockableStorable and ensure a disposable handle to it via Stream bridge - return await file.OpenStreamAsync(access, cancellationToken); + return await OpenStreamAsync(file, access, share, cancellationToken); } catch (Exception) { diff --git a/src/Files.Core.Storage/Extensions/StorageExtensions.Folder.cs b/src/Files.Core.Storage/Extensions/StorageExtensions.Folder.cs new file mode 100644 index 000000000000..5264d3b3929e --- /dev/null +++ b/src/Files.Core.Storage/Extensions/StorageExtensions.Folder.cs @@ -0,0 +1,126 @@ +using Files.Core.Storage.Enums; +using Files.Core.Storage.ExtendableStorage; +using Files.Core.Storage.ModifiableStorage; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Files.Core.Storage.Extensions +{ + public static partial class StorageExtensions + { + #region Without Result + + /// If file was found, returns the requested , otherwise null. + /// + public static async Task TryGetFileAsync(this IFolder folder, string fileName, CancellationToken cancellationToken = default) + { + try + { + if (folder is IFolderExtended folderExtended) + return await folderExtended.GetFileAsync(fileName, cancellationToken); + + await foreach (var item in folder.GetFilesAsync(cancellationToken)) + { + if (item.Name == fileName) + return item; + } + + return null; + } + catch (Exception) + { + return null; + } + } + + /// If folder was found, returns the requested , otherwise null. + /// + public static async Task TryGetFolderAsync(this IFolder folder, string folderName, CancellationToken cancellationToken = default) + { + try + { + if (folder is IFolderExtended folderExtended) + return await folderExtended.GetFolderAsync(folderName, cancellationToken); + + await foreach (var item in folder.GetFoldersAsync(cancellationToken)) + { + if (item.Name == folderName) + return item; + } + + return null; + } + catch (Exception) + { + return null; + } + } + + /// If file was created, returns the requested , otherwise null. + /// + public static async Task TryCreateFileAsync(this IModifiableFolder folder, string desiredName, bool overwrite = default, CancellationToken cancellationToken = default) + { + try + { + return await folder.CreateFileAsync(desiredName, overwrite, cancellationToken); + } + catch (Exception) + { + return null; + } + } + + /// If folder was created, returns the requested , otherwise null. + /// + public static async Task TryCreateFolderAsync(this IModifiableFolder folder, string desiredName, bool overwrite = default, CancellationToken cancellationToken = default) + { + try + { + return await folder.CreateFolderAsync(desiredName, overwrite, cancellationToken); + } + catch (Exception) + { + return null; + } + } + + #endregion + + #region Other + + /// + /// Gets all files contained within . + /// + /// The folder to enumerate. + /// A that cancels this action. + /// Returns an async operation represented by of type of files in the directory. + public static async IAsyncEnumerable GetFilesAsync(this IFolder folder, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await foreach (var item in folder.GetItemsAsync(StorableKind.Files, cancellationToken)) + { + if (item is IFile fileItem) + yield return fileItem; + } + } + + /// + /// Gets all folders contained within . + /// + /// The folder to enumerate. + /// A that cancels this action. + /// Returns an async operation represented by of type of folders in the directory. + public static async IAsyncEnumerable GetFoldersAsync(this IFolder folder, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await foreach (var item in folder.GetItemsAsync(StorableKind.Files, cancellationToken)) + { + if (item is IFolder folderItem) + yield return folderItem; + } + } + + #endregion + } +} diff --git a/src/Files.Core.Storage/Extensions/StorageExtensions.Service.cs b/src/Files.Core.Storage/Extensions/StorageExtensions.Service.cs index 7fb9730bf96d..f968f3b0951b 100644 --- a/src/Files.Core.Storage/Extensions/StorageExtensions.Service.cs +++ b/src/Files.Core.Storage/Extensions/StorageExtensions.Service.cs @@ -11,42 +11,29 @@ namespace Files.Core.Storage.Extensions public static partial class StorageExtensions { /// - /// Checks whether the directory exists at a given path and retrieves the folder, otherwise retrieves the file. + /// Checks whether the directory exists at a given path and tries to retrieve the folder, otherwise tries to retrieve the file. /// /// The service. - /// Path to get the storable at. + /// The unique ID of the storable to retrieve. /// A that cancels this action. - /// A that represents the asynchronous operation. Value is that represents the item. - public static async Task GetStorableFromPathAsync(this IStorageService storageService, string path, CancellationToken cancellationToken = default) + /// A that represents the asynchronous operation. If successful, value is that represents the item, otherwise null. + public static async Task TryGetStorableAsync(this IStorageService storageService, string id, CancellationToken cancellationToken = default) { - if (await storageService.DirectoryExistsAsync(path, cancellationToken)) - { - return await storageService.GetFolderFromPathAsync(path, cancellationToken); - } - else - { - return await storageService.GetFileFromPathAsync(path, cancellationToken); - } + return (IStorable?)await storageService.TryGetFolderAsync(id, cancellationToken) ?? await storageService.TryGetFileAsync(id, cancellationToken); } /// - /// Checks whether the directory exists at a given path and tries to retrieve the folder, otherwise tries to retrieve the file. + /// Tries to retrieve a folder associated with . /// /// The service. - /// Path to get the storable at. + /// The unique ID of the folder to retrieve. /// A that cancels this action. - /// A that represents the asynchronous operation. If successful, value is that represents the item, otherwise null. - public static async Task TryGetStorableFromPathAsync(this IStorageService storageService, string path, CancellationToken cancellationToken = default) - { - return (ILocatableStorable?)await storageService.TryGetFolderFromPathAsync(path, cancellationToken) ?? await storageService.TryGetFileFromPathAsync(path, cancellationToken); - } - - /// - public static async Task TryGetFolderFromPathAsync(this IStorageService storageService, string path, CancellationToken cancellationToken = default) + /// A that represents the asynchronous operation. If successful, value is that represents the folder, otherwise null. + public static async Task TryGetFolderAsync(this IStorageService storageService, string id, CancellationToken cancellationToken = default) { try { - return await storageService.GetFolderFromPathAsync(path, cancellationToken); + return await storageService.GetFolderAsync(id, cancellationToken); } catch (Exception) { @@ -54,12 +41,18 @@ public static async Task GetStorableFromPathAsync(this IStor } } - /// - public static async Task TryGetFileFromPathAsync(this IStorageService storageService, string path, CancellationToken cancellationToken = default) + /// + /// Tries to retrieve a file associated with . + /// + /// The service. + /// The unique ID of the file to retrieve. + /// A that cancels this action. + /// A that represents the asynchronous operation. If successful, value is that represents the file, otherwise null. + public static async Task TryGetFileAsync(this IStorageService storageService, string id, CancellationToken cancellationToken = default) { try { - return await storageService.GetFileFromPathAsync(path, cancellationToken); + return await storageService.GetFileAsync(id, cancellationToken); } catch (Exception) { diff --git a/src/Files.Core.Storage/Files.Core.Storage.csproj b/src/Files.Core.Storage/Files.Core.Storage.csproj index 2f251f813488..224fd2108c99 100644 --- a/src/Files.Core.Storage/Files.Core.Storage.csproj +++ b/src/Files.Core.Storage/Files.Core.Storage.csproj @@ -10,4 +10,9 @@ win10-x86;win10-x64;win10-arm64 + + + + + diff --git a/src/Files.Core.Storage/IFolder.cs b/src/Files.Core.Storage/IFolder.cs index dd7ccb251d49..59d34fb26829 100644 --- a/src/Files.Core.Storage/IFolder.cs +++ b/src/Files.Core.Storage/IFolder.cs @@ -2,9 +2,9 @@ // Licensed under the MIT License. See the LICENSE. using Files.Core.Storage.Enums; +using Files.Core.Storage.NestedStorage; using System.Collections.Generic; using System.Threading; -using System.Threading.Tasks; namespace Files.Core.Storage { @@ -13,28 +13,12 @@ namespace Files.Core.Storage /// public interface IFolder : IStorable { - /// - /// Gets a file in the current directory by name. - /// - /// The name of the file. - /// A that cancels this action. - /// A that represents the asynchronous operation. If file is found and access is granted, returns otherwise null. - Task GetFileAsync(string fileName, CancellationToken cancellationToken = default); - - /// - /// Gets a folder in the current directory by name. - /// - /// The name of the folder. - /// A that cancels this action. - /// A that represents the asynchronous operation. If folder is found and access is granted, returns otherwise null. - Task GetFolderAsync(string folderName, CancellationToken cancellationToken = default); - /// /// Gets all items of this directory. /// /// The type of items to enumerate. /// A that cancels this action. /// Returns an async operation represented by of type of items in the directory. - IAsyncEnumerable GetItemsAsync(StorableKind kind = StorableKind.All, CancellationToken cancellationToken = default); + IAsyncEnumerable GetItemsAsync(StorableKind kind = StorableKind.All, CancellationToken cancellationToken = default); } } diff --git a/src/Files.Core.Storage/IStorageService.cs b/src/Files.Core.Storage/IStorageService.cs index d0d04b70fe0a..7e782c46bbbc 100644 --- a/src/Files.Core.Storage/IStorageService.cs +++ b/src/Files.Core.Storage/IStorageService.cs @@ -13,42 +13,19 @@ namespace Files.Core.Storage public interface IStorageService { /// - /// Checks if access to the file system is granted. Additionally, tries to obtain the permission to access rights to the file system. + /// Gets the file with associated . /// + /// The unique ID of the file to retrieve. /// A that cancels this action. - /// A that represents the asynchronous operation. If access is granted returns true, otherwise false. - Task IsAccessibleAsync(CancellationToken cancellationToken = default); + /// A that represents the asynchronous operation. If file is found and access is granted, returns , otherwise throws an exception. + Task GetFileAsync(string id, CancellationToken cancellationToken = default); /// - /// Check if file exists at specified . + /// Gets the folder with associated . /// - /// The path to the file. + /// The unique ID of the folder to retrieve. /// A that cancels this action. - /// A that represents the asynchronous operation. If file exists, returns true otherwise false. - Task FileExistsAsync(string path, CancellationToken cancellationToken = default); - - /// - /// Check if directory exists at specified . - /// - /// The path to the directory. - /// A that cancels this action. - /// A that represents the asynchronous operation. If the directory exists, returns true otherwise false. - Task DirectoryExistsAsync(string path, CancellationToken cancellationToken = default); - - /// - /// Gets the folder at the specified . - /// - /// The path to the folder. - /// A that cancels this action. - /// A that represents the asynchronous operation. Value is , otherwise an exception is thrown. - Task GetFolderFromPathAsync(string path, CancellationToken cancellationToken = default); - - /// - /// Gets the file at the specified . - /// - /// The path to the file. - /// A that cancels this action. - /// A that represents the asynchronous operation. Value is , otherwise an exception is thrown. - Task GetFileFromPathAsync(string path, CancellationToken cancellationToken = default); + /// A that represents the asynchronous operation. If folder is found and access is granted, returns , otherwise throws an exception. + Task GetFolderAsync(string id, CancellationToken cancellationToken = default); } } diff --git a/src/Files.Core.Storage/LocatableStorage/ILocatableStorable.cs b/src/Files.Core.Storage/LocatableStorage/ILocatableStorable.cs index 24d999f44979..cbd751260abe 100644 --- a/src/Files.Core.Storage/LocatableStorage/ILocatableStorable.cs +++ b/src/Files.Core.Storage/LocatableStorage/ILocatableStorable.cs @@ -1,9 +1,6 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using System.Threading; -using System.Threading.Tasks; - namespace Files.Core.Storage.LocatableStorage { /// @@ -15,10 +12,5 @@ public interface ILocatableStorable : IStorable /// Gets the path where the item resides. /// string Path { get; } - - /// - /// Gets the containing folder for this item, if any. - /// - Task GetParentAsync(CancellationToken cancellationToken = default); } } diff --git a/src/Files.Core.Storage/ModifiableStorage/IModifiableFolder.cs b/src/Files.Core.Storage/ModifiableStorage/IModifiableFolder.cs index ac45e33a119f..34815790255a 100644 --- a/src/Files.Core.Storage/ModifiableStorage/IModifiableFolder.cs +++ b/src/Files.Core.Storage/ModifiableStorage/IModifiableFolder.cs @@ -1,7 +1,7 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using Files.Core.Storage.Enums; +using Files.Core.Storage.NestedStorage; using System.Threading; using System.Threading.Tasks; @@ -15,26 +15,16 @@ public interface IModifiableFolder : IFolder, IModifiableStorable /// /// Deletes the provided storable item from this folder. /// - Task DeleteAsync(IStorable item, bool permanently = false, CancellationToken cancellationToken = default); - - /// - /// Creates a copy of the provided storable item in this folder. - /// - Task CreateCopyOfAsync(IStorable itemToCopy, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default); - - /// - /// Moves a storable item out of the provided folder, and into this folder. Returns the new item that resides in this folder. - /// - Task MoveFromAsync(IStorable itemToMove, IModifiableFolder source, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default); + Task DeleteAsync(INestedStorable item, bool permanently = default, CancellationToken cancellationToken = default); /// /// Creates a new file with the desired name inside this folder. /// - Task CreateFileAsync(string desiredName, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default); + Task CreateFileAsync(string desiredName, bool overwrite = default, CancellationToken cancellationToken = default); /// /// Creates a new folder with the desired name inside this folder. /// - Task CreateFolderAsync(string desiredName, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default); + Task CreateFolderAsync(string desiredName, bool overwrite = default, CancellationToken cancellationToken = default); } } diff --git a/src/Files.Core.Storage/NestedStorage/INestedFile.cs b/src/Files.Core.Storage/NestedStorage/INestedFile.cs new file mode 100644 index 000000000000..ab588728f88b --- /dev/null +++ b/src/Files.Core.Storage/NestedStorage/INestedFile.cs @@ -0,0 +1,9 @@ +namespace Files.Core.Storage.NestedStorage +{ + /// + /// Represents a file that resides within a traversable folder structure. + /// + public interface INestedFile : IFile, INestedStorable + { + } +} diff --git a/src/Files.Core.Storage/NestedStorage/INestedFolder.cs b/src/Files.Core.Storage/NestedStorage/INestedFolder.cs new file mode 100644 index 000000000000..2b098bf178bc --- /dev/null +++ b/src/Files.Core.Storage/NestedStorage/INestedFolder.cs @@ -0,0 +1,9 @@ +namespace Files.Core.Storage.NestedStorage +{ + /// + /// Represents a folder that resides within a traversable folder structure. + /// + public interface INestedFolder : IFolder, INestedStorable + { + } +} diff --git a/src/Files.Core.Storage/NestedStorage/INestedStorable.cs b/src/Files.Core.Storage/NestedStorage/INestedStorable.cs new file mode 100644 index 000000000000..9bda622de1cc --- /dev/null +++ b/src/Files.Core.Storage/NestedStorage/INestedStorable.cs @@ -0,0 +1,16 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Files.Core.Storage.NestedStorage +{ + /// + /// Represents a storable resource that resides within a traversable folder structure. + /// + public interface INestedStorable : IStorable + { + /// + /// Gets the containing folder for this item, if any. + /// + Task GetParentAsync(CancellationToken cancellationToken = default); + } +} diff --git a/src/Files.Core/Data/AppModels/BaseDatabaseModel.cs b/src/Files.Core/Data/AppModels/BaseDatabaseModel.cs new file mode 100644 index 000000000000..468aa9fbfa95 --- /dev/null +++ b/src/Files.Core/Data/AppModels/BaseDatabaseModel.cs @@ -0,0 +1,42 @@ +using Files.Shared.Utils; +using System.Collections.Concurrent; +using System.IO; + +namespace Files.Core.Data.AppModels +{ + /// + /// Represents a dictionary-based database model. + /// + public abstract class BaseDatabaseModel : IDatabaseModel + { + protected readonly IAsyncSerializer serializer; + protected readonly SemaphoreSlim storageSemaphore; + protected readonly ConcurrentDictionary settingsCache; + + protected BaseDatabaseModel(IAsyncSerializer serializer) + { + this.serializer = serializer; + storageSemaphore = new(1, 1); + settingsCache = new(); + } + + /// + public abstract TValue? GetValue(string key, Func? defaultValue = null); + + /// + public abstract bool SetValue(string key, TValue? value); + + /// + public abstract Task LoadAsync(CancellationToken cancellationToken = default); + + /// + public abstract Task SaveAsync(CancellationToken cancellationToken = default); + + /// + public virtual void Dispose() + { + storageSemaphore.Dispose(); + settingsCache.Clear(); + } + } +} diff --git a/src/Files.Core/Data/AppModels/SingleFileDatabaseModel.cs b/src/Files.Core/Data/AppModels/SingleFileDatabaseModel.cs new file mode 100644 index 000000000000..cadbf664310f --- /dev/null +++ b/src/Files.Core/Data/AppModels/SingleFileDatabaseModel.cs @@ -0,0 +1,135 @@ +using Files.Core.Storage; +using Files.Core.Storage.Extensions; +using Files.Core.Storage.ModifiableStorage; +using Files.Shared.Extensions; +using Files.Shared.Utils; +using System.IO; + +namespace Files.Core.Data.AppModels +{ + /// + public sealed class SingleFileDatabaseModel : BaseDatabaseModel + { + private readonly string _fileName; + private readonly IModifiableFolder _settingsFolder; + private IFile? _databaseFile; + + public SingleFileDatabaseModel(string fileName, IModifiableFolder settingsFolder, IAsyncSerializer serializer) + : base(serializer) + { + _fileName = fileName; + _settingsFolder = settingsFolder; + } + + /// + public override TValue? GetValue(string key, Func? defaultValue = null) + where TValue : default + { + if (settingsCache.TryGetValue(key, out var value)) + return value.GetValue() ?? (defaultValue is not null ? defaultValue() : default); + + var fallback = defaultValue is not null ? defaultValue() : default; + settingsCache[key] = new NonSerializedData(fallback); + + return fallback; + } + + /// + public override bool SetValue(string key, TValue? value) + where TValue : default + { + settingsCache[key] = new NonSerializedData(value); + return true; + } + + /// + public override async Task LoadAsync(CancellationToken cancellationToken = default) + { + try + { + await storageSemaphore.WaitAsync(cancellationToken); + await EnsureSettingsFileAsync(cancellationToken); + + _ = _databaseFile ?? throw new InvalidOperationException("The database file was not properly initialized."); + + await using var stream = await _databaseFile!.OpenStreamAsync(FileAccess.Read, FileShare.Read, cancellationToken); + var settings = await serializer.DeserializeAsync(stream, cancellationToken); + + // Reset the cache + settingsCache.Clear(); + + if (settings is null) // No settings saved, set cache to empty and return + return; + + foreach (DictionaryEntry item in settings) + { + if (item.Key is not string key) + continue; + + if (item.Value is ISerializedModel serializedData) + settingsCache[key] = serializedData; + else + settingsCache[key] = new NonSerializedData(item.Value); + } + } + finally + { + _ = storageSemaphore.Release(); + } + } + + /// + public override async Task SaveAsync(CancellationToken cancellationToken = default) + { + try + { + await storageSemaphore.WaitAsync(cancellationToken); + await EnsureSettingsFileAsync(cancellationToken); + + _ = _databaseFile ?? throw new InvalidOperationException("The database file was not properly initialized."); + + await using var dataStream = await _databaseFile.OpenStreamAsync(FileAccess.ReadWrite, FileShare.Read, cancellationToken); + await using var settingsStream = await serializer.SerializeAsync(settingsCache, cancellationToken); + + // Overwrite existing content + dataStream.Position = 0L; + dataStream.SetLength(0L); + + // Copy contents + settingsStream.Position = 0L; + await settingsStream.CopyToAsync(dataStream, cancellationToken); + + return true; + } + finally + { + _ = storageSemaphore.Release(); + } + } + + private async Task EnsureSettingsFileAsync(CancellationToken cancellationToken) + { + if (_databaseFile is null) + _databaseFile = await _settingsFolder.TryCreateFileAsync(_fileName, false, cancellationToken); + + return _databaseFile is not null; + } + + /// + private sealed class NonSerializedData : ISerializedModel + { + private readonly object? _value; + + public NonSerializedData(object? value) + { + _value = value; + } + + /// + public T? GetValue() + { + return _value.TryCast(); + } + } + } +} diff --git a/src/Files.Core/Data/Models/IDatabaseModel.cs b/src/Files.Core/Data/Models/IDatabaseModel.cs new file mode 100644 index 000000000000..de51f75b7f62 --- /dev/null +++ b/src/Files.Core/Data/Models/IDatabaseModel.cs @@ -0,0 +1,29 @@ +using Files.Shared.Utils; + +namespace Files.Core.Data.Models +{ + /// + /// Represents a database to store data identified by . + /// + /// The key to identify data with. + public interface IDatabaseModel : IPersistable, IDisposable + { + /// + /// Gets a value from the database. + /// + /// The type of value. + /// The name of the value to get. + /// Retrieves the default value. If is null, returns the default value of . + /// A value from the database. The value is determined by the availability in the storage or by the . + TValue? GetValue(TKey key, Func? defaultValue = null); + + /// + /// Sets a value in the database. + /// + /// The type of value. + /// The name of the value to set. + /// The value to be stored. + /// If the value has been updated in the database, returns true otherwise false. + bool SetValue(TKey key, TValue? value); + } +} diff --git a/src/Files.Core/Data/Models/ISerializedModel.cs b/src/Files.Core/Data/Models/ISerializedModel.cs new file mode 100644 index 000000000000..3fc3730e1d36 --- /dev/null +++ b/src/Files.Core/Data/Models/ISerializedModel.cs @@ -0,0 +1,15 @@ +namespace Files.Core.Data.Models +{ + /// + /// Represents a model that holds serialized data. + /// + public interface ISerializedModel + { + /// + /// Retrieves requested of the serialized data. + /// + /// The type of data to retrieve. + /// Deserialized data of type . If the value cannot be deserialized, returns default. + T? GetValue(); + } +} diff --git a/src/Files.Core/Data/Models/TaggedItemModel.cs b/src/Files.Core/Data/Models/TaggedItemModel.cs index ce46e600e4ad..aeb2f999c011 100644 --- a/src/Files.Core/Data/Models/TaggedItemModel.cs +++ b/src/Files.Core/Data/Models/TaggedItemModel.cs @@ -1,7 +1,7 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using Files.Core.Storage.LocatableStorage; +using Files.Core.Storage; namespace Files.Core.Data.Models { @@ -10,5 +10,5 @@ namespace Files.Core.Data.Models /// /// Tag UIDs that the item is tagged with. /// The item that contains the tags. - public sealed record class TaggedItemModel(string[] TagUids, ILocatableStorable Storable); + public sealed record class TaggedItemModel(string[] TagUids, IStorable Storable); } diff --git a/src/Files.Core/Services/IApplicationService.cs b/src/Files.Core/Services/IApplicationService.cs new file mode 100644 index 000000000000..fcc6d45421ad --- /dev/null +++ b/src/Files.Core/Services/IApplicationService.cs @@ -0,0 +1,24 @@ +namespace Files.Core.Services +{ + /// + /// A service that interacts with common app-related APIs. + /// + public interface IApplicationService + { + /// + /// Gets the application build environment. + /// + AppEnvironment Environment { get; } + + /// + /// Gets the version of the app. + /// + Version AppVersion { get; } + + /// + /// Gets the path at which the App Logo is located. + /// + [Obsolete("This is a bad way of accessing the logo. Use something more abstract instead, and ideally move it out of this interface.")] + string AppIcoPath { get; } + } +} diff --git a/src/Files.Core/Services/Settings/IFileTagsSettingsService.cs b/src/Files.Core/Services/Settings/IFileTagsSettingsService.cs index a424bb9b0e17..d7be5c268a56 100644 --- a/src/Files.Core/Services/Settings/IFileTagsSettingsService.cs +++ b/src/Files.Core/Services/Settings/IFileTagsSettingsService.cs @@ -2,8 +2,6 @@ // Licensed under the MIT License. See the LICENSE. using Files.Core.ViewModels.FileTags; -using System; -using System.Collections.Generic; namespace Files.Core.Services.Settings { @@ -21,8 +19,6 @@ public interface IFileTagsSettingsService : IBaseSettingsService IEnumerable GetTagsByName(string tagName); - IEnumerable SearchTagsByName(string tagName); - void CreateNewTag(string newTagName, string color); void EditTag(string uid, string name, string color); diff --git a/src/Files.Core/ViewModels/Widgets/FileTagsWidget/FileTagsItemViewModel.cs b/src/Files.Core/ViewModels/Widgets/FileTagsWidget/FileTagsItemViewModel.cs index 008431d8043c..0a4058a68690 100644 --- a/src/Files.Core/ViewModels/Widgets/FileTagsWidget/FileTagsItemViewModel.cs +++ b/src/Files.Core/ViewModels/Widgets/FileTagsWidget/FileTagsItemViewModel.cs @@ -1,14 +1,14 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. +using Files.Core.Storage; using Files.Core.Storage.Extensions; -using Files.Core.Storage.LocatableStorage; namespace Files.Core.ViewModels.Widgets.FileTagsWidget { public sealed partial class FileTagsItemViewModel : ObservableObject { - private readonly ILocatableStorable _associatedStorable; + private readonly IStorable _associatedStorable; // A workaround for lack of MVVM-compliant navigation support. // This workaround must be kept until further refactor of navigation code is completed @@ -35,19 +35,19 @@ public string? Path set => SetProperty(ref _Path, value); } - public FileTagsItemViewModel(ILocatableStorable associatedStorable, Func openAction, IImageModel? icon) + public FileTagsItemViewModel(IStorable associatedStorable, Func openAction, IImageModel? icon) { _associatedStorable = associatedStorable; _openAction = openAction; _Icon = icon; - _Name = PathHelpers.FormatName(associatedStorable.Path); + _Name = PathHelpers.FormatName(associatedStorable.Id); _Path = associatedStorable.TryGetPath(); } [RelayCommand] private Task ClickAsync(CancellationToken cancellationToken) { - return _openAction(_associatedStorable.Path); + return _openAction(_associatedStorable.Id); } } } diff --git a/src/Files.Sdk.Storage/DirectStorage/IDirectCopy.cs b/src/Files.Sdk.Storage/DirectStorage/IDirectCopy.cs new file mode 100644 index 000000000000..ba86c6f34764 --- /dev/null +++ b/src/Files.Sdk.Storage/DirectStorage/IDirectCopy.cs @@ -0,0 +1,18 @@ +using Files.Sdk.Storage.ModifiableStorage; +using Files.Sdk.Storage.NestedStorage; +using System.Threading; +using System.Threading.Tasks; + +namespace Files.Sdk.Storage.DirectStorage +{ + /// + /// Provides direct copy operation of storage objects. + /// + public interface IDirectCopy : IModifiableFolder + { + /// + /// Creates a copy of the provided storable item in this folder. + /// + Task CreateCopyOfAsync(INestedStorable itemToCopy, bool overwrite = default, CancellationToken cancellationToken = default); + } +} diff --git a/src/Files.Sdk.Storage/DirectStorage/IDirectMove.cs b/src/Files.Sdk.Storage/DirectStorage/IDirectMove.cs new file mode 100644 index 000000000000..7a21edcc9b30 --- /dev/null +++ b/src/Files.Sdk.Storage/DirectStorage/IDirectMove.cs @@ -0,0 +1,18 @@ +using Files.Sdk.Storage.ModifiableStorage; +using Files.Sdk.Storage.NestedStorage; +using System.Threading; +using System.Threading.Tasks; + +namespace Files.Sdk.Storage.DirectStorage +{ + /// + /// Provides direct move operation of storage objects. + /// + public interface IDirectMove : IModifiableFolder + { + /// + /// Moves a storable item out of the provided folder, and into this folder. Returns the new item that resides in this folder. + /// + Task MoveFromAsync(INestedStorable itemToMove, IModifiableFolder source, bool overwrite = default, CancellationToken cancellationToken = default); + } +} diff --git a/src/Files.Sdk.Storage/NestedStorage/INestedFile.cs b/src/Files.Sdk.Storage/NestedStorage/INestedFile.cs new file mode 100644 index 000000000000..b1a262ffcdb4 --- /dev/null +++ b/src/Files.Sdk.Storage/NestedStorage/INestedFile.cs @@ -0,0 +1,9 @@ +namespace Files.Sdk.Storage.NestedStorage +{ + /// + /// Represents a file that resides within a traversable folder structure. + /// + public interface INestedFile : IFile, INestedStorable + { + } +} diff --git a/src/Files.Sdk.Storage/NestedStorage/INestedFolder.cs b/src/Files.Sdk.Storage/NestedStorage/INestedFolder.cs new file mode 100644 index 000000000000..d73c723adb37 --- /dev/null +++ b/src/Files.Sdk.Storage/NestedStorage/INestedFolder.cs @@ -0,0 +1,9 @@ +namespace Files.Sdk.Storage.NestedStorage +{ + /// + /// Represents a folder that resides within a traversable folder structure. + /// + public interface INestedFolder : IFolder, INestedStorable + { + } +} diff --git a/src/Files.Sdk.Storage/NestedStorage/INestedStorable.cs b/src/Files.Sdk.Storage/NestedStorage/INestedStorable.cs new file mode 100644 index 000000000000..3463db7d4bd6 --- /dev/null +++ b/src/Files.Sdk.Storage/NestedStorage/INestedStorable.cs @@ -0,0 +1,16 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Files.Sdk.Storage.NestedStorage +{ + /// + /// Represents a storable resource that resides within a traversable folder structure. + /// + public interface INestedStorable : IStorable + { + /// + /// Gets the containing folder for this item, if any. + /// + Task GetParentAsync(CancellationToken cancellationToken = default); + } +} diff --git a/src/Files.Shared/Extensions/GenericExtensions.cs b/src/Files.Shared/Extensions/GenericExtensions.cs new file mode 100644 index 000000000000..f251dd4d6e23 --- /dev/null +++ b/src/Files.Shared/Extensions/GenericExtensions.cs @@ -0,0 +1,17 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Files.Shared.Extensions +{ + public static class GenericExtensions + { + [return: NotNullIfNotNull(nameof(defaultValue))] + public static TOut? TryCast(this object? value, Func? defaultValue = null) + { + if (value is TOut outValue) + return outValue; + + return defaultValue is not null ? defaultValue() : default; + } + } +} diff --git a/src/Files.Shared/Extensions/SerializationExtensions.cs b/src/Files.Shared/Extensions/SerializationExtensions.cs new file mode 100644 index 000000000000..6dcb4def7126 --- /dev/null +++ b/src/Files.Shared/Extensions/SerializationExtensions.cs @@ -0,0 +1,25 @@ +using Files.Shared.Utils; +using System.Threading; +using System.Threading.Tasks; + +namespace Files.Shared.Extensions +{ + public static class SerializationExtensions + { + public static async Task SerializeAsync( + this IAsyncSerializer serializer, + TData? data, + CancellationToken cancellationToken = default) + { + return await serializer.SerializeAsync(data, typeof(TData), cancellationToken); + } + + public static async Task DeserializeAsync( + this IAsyncSerializer serializer, + TSerialized serialized, + CancellationToken cancellationToken = default) + { + return (TData?)await serializer.DeserializeAsync(serialized, typeof(TData), cancellationToken); + } + } +} diff --git a/src/Files.Shared/Utils/IAsyncSerializer.cs b/src/Files.Shared/Utils/IAsyncSerializer.cs new file mode 100644 index 000000000000..17a449cb0509 --- /dev/null +++ b/src/Files.Shared/Utils/IAsyncSerializer.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Files.Shared.Utils +{ + /// + /// Provides data serialization abstractions for data. + /// + /// The type of data serialized to. + public interface IAsyncSerializer + { + /// + /// Serializes into . + /// + /// The data to serialize. + /// The type of data to serialize. + /// A that cancels this action. + /// A that represents the asynchronous operation. Value is of transformed . + Task SerializeAsync(object? data, Type dataType, CancellationToken cancellationToken = default); + + /// + /// Deserializes into type. + /// + /// The data to deserialize. + /// The type to deserialize into. + /// A that cancels this action. + /// A that represents the asynchronous operation. Value is of type of transformed . + Task DeserializeAsync(TSerialized serialized, Type dataType, CancellationToken cancellationToken = default); + } +} diff --git a/src/Files.Shared/Utils/IPersistable.cs b/src/Files.Shared/Utils/IPersistable.cs new file mode 100644 index 000000000000..9d23895dfa9a --- /dev/null +++ b/src/Files.Shared/Utils/IPersistable.cs @@ -0,0 +1,25 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Files.Shared.Utils +{ + /// + /// Allows for data to be saved and loaded from a persistence store. + /// + public interface IPersistable + { + /// + /// Asynchronously loads persisted data into memory. + /// + /// A that cancels this action. + /// A that represents the asynchronous operation. + Task LoadAsync(CancellationToken cancellationToken = default); + + /// + /// Asynchronously saves data stored in memory. + /// + /// A that cancels this action. + /// A that represents the asynchronous operation + Task SaveAsync(CancellationToken cancellationToken = default); + } +}