diff --git a/Files.Launcher/MessageHandlers/FileOperationsHandler.cs b/Files.Launcher/MessageHandlers/FileOperationsHandler.cs index 7c06ddfa11dd..41a9fa8685d4 100644 --- a/Files.Launcher/MessageHandlers/FileOperationsHandler.cs +++ b/Files.Launcher/MessageHandlers/FileOperationsHandler.cs @@ -47,6 +47,30 @@ private async Task ParseFileOperationAsync(NamedPipeServerStream connection, Dic { switch (message.Get("fileop", "")) { + case "GetFileHandle": + { + var filePath = (string)message["filepath"]; + var readWrite = (bool)message["readwrite"]; + using var hFile = Kernel32.CreateFile(filePath, Kernel32.FileAccess.GENERIC_READ | (readWrite ? Kernel32.FileAccess.GENERIC_WRITE : 0), FileShare.ReadWrite, null, FileMode.Open, FileFlagsAndAttributes.FILE_ATTRIBUTE_NORMAL); + if (hFile.IsInvalid) + { + await Win32API.SendMessageAsync(connection, new ValueSet() { { "Success", false } }, message.Get("RequestID", (string)null)); + return; + } + var processId = (int)(long)message["processid"]; + using var uwpProces = System.Diagnostics.Process.GetProcessById(processId); + if (!Kernel32.DuplicateHandle(Kernel32.GetCurrentProcess(), hFile.DangerousGetHandle(), uwpProces.Handle, out var targetHandle, 0, false, Kernel32.DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS)) + { + await Win32API.SendMessageAsync(connection, new ValueSet() { { "Success", false } }, message.Get("RequestID", (string)null)); + return; + } + await Win32API.SendMessageAsync(connection, new ValueSet() { + { "Success", true }, + { "Handle", targetHandle.ToInt64() } + }, message.Get("RequestID", (string)null)); + } + break; + case "Clipboard": await Win32API.StartSTATask(() => { @@ -158,7 +182,7 @@ await Win32API.StartSTATask(() => shellOperationResult.Items.Add(new ShellOperationItemResult() { Succeeded = e.Result.Succeeded, - Source = e.SourceItem.FileSystemPath, + Source = e.SourceItem.FileSystemPath ?? e.SourceItem.ParsingName, Destination = e.DestItem?.FileSystemPath, HRresult = (int)e.Result }); @@ -224,7 +248,7 @@ await Win32API.StartSTATask(() => shellOperationResult.Items.Add(new ShellOperationItemResult() { Succeeded = e.Result.Succeeded, - Source = e.SourceItem.FileSystemPath, + Source = e.SourceItem.FileSystemPath ?? e.SourceItem.ParsingName, Destination = !string.IsNullOrEmpty(e.Name) ? Path.Combine(Path.GetDirectoryName(e.SourceItem.FileSystemPath), e.Name) : null, HRresult = (int)e.Result }); @@ -288,8 +312,8 @@ await Win32API.StartSTATask(() => shellOperationResult.Items.Add(new ShellOperationItemResult() { Succeeded = e.Result.Succeeded, - Source = e.SourceItem.FileSystemPath, - Destination = e.DestFolder != null && !string.IsNullOrEmpty(e.Name) ? Path.Combine(e.DestFolder.FileSystemPath, e.Name) : null, + Source = e.SourceItem.FileSystemPath ?? e.SourceItem.ParsingName, + Destination = e.DestFolder?.FileSystemPath != null && !string.IsNullOrEmpty(e.Name) ? Path.Combine(e.DestFolder.FileSystemPath, e.Name) : null, HRresult = (int)e.Result }); }; @@ -363,8 +387,8 @@ await Win32API.StartSTATask(() => shellOperationResult.Items.Add(new ShellOperationItemResult() { Succeeded = e.Result.Succeeded, - Source = e.SourceItem.FileSystemPath, - Destination = e.DestFolder != null && !string.IsNullOrEmpty(e.Name) ? Path.Combine(e.DestFolder.FileSystemPath, e.Name) : null, + Source = e.SourceItem.FileSystemPath ?? e.SourceItem.ParsingName, + Destination = e.DestFolder?.FileSystemPath != null && !string.IsNullOrEmpty(e.Name) ? Path.Combine(e.DestFolder.FileSystemPath, e.Name) : null, HRresult = (int)e.Result }); }; @@ -566,8 +590,8 @@ private void UpdateFileTageDb(object sender, ShellFileOperations.ShellFileOpEven { "delete" => e.DestItem?.FileSystemPath, "rename" => (!string.IsNullOrEmpty(e.Name) ? Path.Combine(Path.GetDirectoryName(e.SourceItem.FileSystemPath), e.Name) : null), - "copy" => (e.DestFolder != null && !string.IsNullOrEmpty(e.Name) ? Path.Combine(e.DestFolder.FileSystemPath, e.Name) : null), - _ => (e.DestFolder != null && !string.IsNullOrEmpty(e.Name) ? Path.Combine(e.DestFolder.FileSystemPath, e.Name) : null) + "copy" => (e.DestFolder?.FileSystemPath != null && !string.IsNullOrEmpty(e.Name) ? Path.Combine(e.DestFolder.FileSystemPath, e.Name) : null), + _ => (e.DestFolder?.FileSystemPath != null && !string.IsNullOrEmpty(e.Name) ? Path.Combine(e.DestFolder.FileSystemPath, e.Name) : null) }; if (destination == null) { diff --git a/Files.Launcher/Program.cs b/Files.Launcher/Program.cs index 48409d66390b..8156baedc017 100644 --- a/Files.Launcher/Program.cs +++ b/Files.Launcher/Program.cs @@ -1,5 +1,4 @@ using Files.Common; -using FilesFullTrust.Helpers; using FilesFullTrust.MessageHandlers; using Newtonsoft.Json; using System; @@ -235,7 +234,7 @@ private static async Task ParseArgumentsAsync(Dictionary message { await Win32API.SendMessageAsync(connection, new ValueSet() { { "Success", -1 } }, message.Get("RequestID", (string)null)); } - break; + break; default: foreach (var mh in messageHandlers) diff --git a/Files.Package/Package.appxmanifest b/Files.Package/Package.appxmanifest index 1bd82ab56c0e..9872b02fff5e 100644 --- a/Files.Package/Package.appxmanifest +++ b/Files.Package/Package.appxmanifest @@ -59,6 +59,13 @@ + + + + .zip + + + diff --git a/Files/App.xaml.cs b/Files/App.xaml.cs index 25a1ab61d852..c3b0aadf2eae 100644 --- a/Files/App.xaml.cs +++ b/Files/App.xaml.cs @@ -129,32 +129,16 @@ private void OnLeavingBackground(object sender, LeavingBackgroundEventArgs e) protected override async void OnLaunched(LaunchActivatedEventArgs e) { await logWriter.InitializeAsync("debug.log"); + Logger.Info($"App launched. Prelaunch: {e.PrelaunchActivated}"); + //start tracking app usage SystemInformation.Instance.TrackAppUse(e); - Logger.Info("App launched"); - bool canEnablePrelaunch = ApiInformation.IsMethodPresent("Windows.ApplicationModel.Core.CoreApplication", "EnablePrelaunch"); await EnsureSettingsAndConfigurationAreBootstrapped(); - // Do not repeat app initialization when the Window already has content, - // just ensure that the window is active - if (!(Window.Current.Content is Frame rootFrame)) - { - // Create a Frame to act as the navigation context and navigate to the first page - rootFrame = new Frame(); - rootFrame.CacheSize = 1; - rootFrame.NavigationFailed += OnNavigationFailed; - - if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) - { - //TODO: Load state from previously suspended application - } - - // Place the frame in the current Window - Window.Current.Content = rootFrame; - } + var rootFrame = EnsureWindowIsInitialized(); if (e.PrelaunchActivated == false) { @@ -184,6 +168,60 @@ protected override async void OnLaunched(LaunchActivatedEventArgs e) } } + protected override async void OnFileActivated(FileActivatedEventArgs e) + { + await logWriter.InitializeAsync("debug.log"); + Logger.Info("App activated by file"); + + //start tracking app usage + SystemInformation.Instance.TrackAppUse(e); + + await EnsureSettingsAndConfigurationAreBootstrapped(); + + var rootFrame = EnsureWindowIsInitialized(); + + var index = 0; + if (rootFrame.Content == null) + { + // When the navigation stack isn't restored navigate to the first page, + // configuring the new page by passing required information as a navigation + // parameter + rootFrame.Navigate(typeof(MainPage), e.Files.First().Path, new SuppressNavigationTransitionInfo()); + index = 1; + } + for (; index < e.Files.Count; index++) + { + await MainPageViewModel.AddNewTabByPathAsync(typeof(PaneHolderPage), e.Files[index].Path); + } + + // Ensure the current window is active + Window.Current.Activate(); + Window.Current.CoreWindow.Activated += CoreWindow_Activated; + } + + private Frame EnsureWindowIsInitialized() + { + // Do not repeat app initialization when the Window already has content, + // just ensure that the window is active + if (!(Window.Current.Content is Frame rootFrame)) + { + // Create a Frame to act as the navigation context and navigate to the first page + rootFrame = new Frame(); + rootFrame.CacheSize = 1; + rootFrame.NavigationFailed += OnNavigationFailed; + + //if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) + //{ + // //TODO: Load state from previously suspended application + //} + + // Place the frame in the current Window + Window.Current.Content = rootFrame; + } + + return rootFrame; + } + private void CoreWindow_Activated(CoreWindow sender, WindowActivatedEventArgs args) { if (args.WindowActivationState == CoreWindowActivationState.CodeActivated || @@ -201,20 +239,12 @@ private void CoreWindow_Activated(CoreWindow sender, WindowActivatedEventArgs ar protected override async void OnActivated(IActivatedEventArgs args) { await logWriter.InitializeAsync("debug.log"); - - Logger.Info("App activated"); + Logger.Info($"App activated by {args.Kind.ToString()}"); await EnsureSettingsAndConfigurationAreBootstrapped(); - // Window management - if (!(Window.Current.Content is Frame rootFrame)) - { - rootFrame = new Frame(); - rootFrame.CacheSize = 1; - Window.Current.Content = rootFrame; - } + var rootFrame = EnsureWindowIsInitialized(); - var currentView = SystemNavigationManager.GetForCurrentView(); switch (args.Kind) { case ActivationKind.Protocol: diff --git a/Files/BaseLayout.cs b/Files/BaseLayout.cs index 55cd08aba403..584a80ae5bcf 100644 --- a/Files/BaseLayout.cs +++ b/Files/BaseLayout.cs @@ -433,6 +433,7 @@ protected override async void OnNavigatedTo(NavigationEventArgs eventArgs) ParentShellPageInstance.InstanceViewModel.IsPageTypeRecycleBin = workingDir.StartsWith(App.AppSettings.RecycleBinPath); ParentShellPageInstance.InstanceViewModel.IsPageTypeMtpDevice = workingDir.StartsWith("\\\\?\\"); ParentShellPageInstance.InstanceViewModel.IsPageTypeFtp = FtpHelpers.IsFtpPath(workingDir); + ParentShellPageInstance.InstanceViewModel.IsPageTypeZipFolder = ZipStorageFolder.IsZipPath(workingDir); ParentShellPageInstance.InstanceViewModel.IsPageTypeSearchResults = false; ParentShellPageInstance.NavToolbarViewModel.PathControlDisplayText = navigationArguments.NavPathParam; if (!navigationArguments.IsLayoutSwitch) @@ -453,6 +454,7 @@ protected override async void OnNavigatedTo(NavigationEventArgs eventArgs) ParentShellPageInstance.InstanceViewModel.IsPageTypeRecycleBin = false; ParentShellPageInstance.InstanceViewModel.IsPageTypeFtp = false; ParentShellPageInstance.InstanceViewModel.IsPageTypeMtpDevice = false; + ParentShellPageInstance.InstanceViewModel.IsPageTypeZipFolder = false; ParentShellPageInstance.InstanceViewModel.IsPageTypeSearchResults = true; if (!navigationArguments.IsLayoutSwitch) { @@ -588,7 +590,10 @@ public async void BaseContextFlyout_Opening(object sender, object e) return; } - AddShellItemsToMenu(shellMenuItems, BaseContextMenuFlyout, shiftPressed); + if (!InstanceViewModel.IsPageTypeZipFolder) + { + AddShellItemsToMenu(shellMenuItems, BaseContextMenuFlyout, shiftPressed); + } } } catch (Exception error) @@ -619,7 +624,7 @@ private async Task LoadMenuItemsAsync() secondaryElements.OfType().ForEach(i => i.MinWidth = 250); // Set menu min width secondaryElements.ForEach(i => ItemContextMenuFlyout.SecondaryCommands.Add(i)); - if (AppSettings.AreFileTagsEnabled && !InstanceViewModel.IsPageTypeSearchResults && !InstanceViewModel.IsPageTypeRecycleBin && !InstanceViewModel.IsPageTypeFtp) + if (AppSettings.AreFileTagsEnabled && !InstanceViewModel.IsPageTypeSearchResults && !InstanceViewModel.IsPageTypeRecycleBin && !InstanceViewModel.IsPageTypeFtp && !InstanceViewModel.IsPageTypeZipFolder) { AddFileTagsItemToMenu(ItemContextMenuFlyout); } @@ -630,7 +635,10 @@ private async Task LoadMenuItemsAsync() return; } - AddShellItemsToMenu(shellMenuItems, ItemContextMenuFlyout, shiftPressed); + if (!InstanceViewModel.IsPageTypeZipFolder) + { + AddShellItemsToMenu(shellMenuItems, ItemContextMenuFlyout, shiftPressed); + } } private void AddFileTagsItemToMenu(Microsoft.UI.Xaml.Controls.CommandBarFlyout contextMenu) @@ -771,10 +779,14 @@ protected async void FileList_DragItemsStarting(object sender, DragItemsStarting { if (item.PrimaryItemAttribute == StorageItemTypes.File) { - selectedStorageItems.Add(await new FtpStorageFile(ParentShellPageInstance.FilesystemViewModel, ftpItem).ToStorageFileAsync()); + selectedStorageItems.Add(await new FtpStorageFile(ftpItem).ToStorageFileAsync()); + } + else if (item.PrimaryItemAttribute == StorageItemTypes.Folder) + { + selectedStorageItems.Add(new FtpStorageFolder(ftpItem)); } } - else if (item.PrimaryItemAttribute == StorageItemTypes.File) + else if (item.PrimaryItemAttribute == StorageItemTypes.File || item is ZipItem) { result = await ParentShellPageInstance.FilesystemViewModel.GetFileFromPathAsync(item.ItemPath) .OnSuccess(t => selectedStorageItems.Add(t)); @@ -907,6 +919,12 @@ protected async void Item_DragOver(object sender, DragEventArgs e) e.DragUIOverride.Caption = string.Format("MoveToFolderCaptionText".GetLocalized(), item.ItemName); e.AcceptedOperation = DataPackageOperation.Move; } + else if (draggedItems.Any(x => x.Item is ZipStorageFile || x.Item is ZipStorageFolder) + || ZipStorageFolder.IsZipPath(item.ItemPath)) + { + e.DragUIOverride.Caption = string.Format("CopyToFolderCaptionText".GetLocalized(), item.ItemName); + e.AcceptedOperation = DataPackageOperation.Copy; + } else if (draggedItems.AreItemsInSameDrive(item.ItemPath)) { e.DragUIOverride.Caption = string.Format("MoveToFolderCaptionText".GetLocalized(), item.ItemName); diff --git a/Files/DataModels/FilesystemItemsOperationDataModel.cs b/Files/DataModels/FilesystemItemsOperationDataModel.cs index 7f8e518c1e3d..64a542571ae6 100644 --- a/Files/DataModels/FilesystemItemsOperationDataModel.cs +++ b/Files/DataModels/FilesystemItemsOperationDataModel.cs @@ -66,7 +66,7 @@ public async Task> ToItems(Action updateP // Add conflicting items first foreach (var item in ConflictingItems) { - var iconData = await FileThumbnailHelper.LoadIconWithoutOverlayAsync(item.SourcePath, 64u); + var iconData = await FileThumbnailHelper.LoadIconFromPathAsync(item.SourcePath, 64u, Windows.Storage.FileProperties.ThumbnailMode.ListView); items.Add(new FilesystemOperationItemViewModel(updatePrimaryButtonEnabled, optionGenerateNewName, optionReplaceExisting, optionSkip) { @@ -84,7 +84,7 @@ public async Task> ToItems(Action updateP // Then add non-conflicting items foreach (var item in nonConflictingItems) { - var iconData = await FileThumbnailHelper.LoadIconWithoutOverlayAsync(item.SourcePath, 64u); + var iconData = await FileThumbnailHelper.LoadIconFromPathAsync(item.SourcePath, 64u, Windows.Storage.FileProperties.ThumbnailMode.ListView); items.Add(new FilesystemOperationItemViewModel(updatePrimaryButtonEnabled, optionGenerateNewName, optionReplaceExisting, optionSkip) { diff --git a/Files/DataModels/ShellNewEntry.cs b/Files/DataModels/ShellNewEntry.cs index c73f17d9ccd9..886f2c776497 100644 --- a/Files/DataModels/ShellNewEntry.cs +++ b/Files/DataModels/ShellNewEntry.cs @@ -1,4 +1,6 @@ using Files.Filesystem; +using Files.Filesystem.StorageItems; +using Files.Helpers; using System; using System.IO; using System.Threading.Tasks; @@ -16,19 +18,19 @@ public class ShellNewEntry public byte[] Data { get; set; } public string Template { get; set; } - public async Task> Create(string filePath, IShellPage associatedInstance) + public async Task> Create(string filePath, IShellPage associatedInstance) { - var parentFolder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(Path.GetDirectoryName(filePath)); + var parentFolder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(PathNormalization.GetParentDir(filePath)); if (parentFolder) { return await Create(parentFolder, Path.GetFileName(filePath)); } - return new FilesystemResult(null, parentFolder.ErrorCode); + return new FilesystemResult(null, parentFolder.ErrorCode); } - public async Task> Create(StorageFolder parentFolder, string fileName) + public async Task> Create(BaseStorageFolder parentFolder, string fileName) { - FilesystemResult createdFile = null; + FilesystemResult createdFile = null; if (!fileName.EndsWith(this.Extension)) { fileName += this.Extension; @@ -39,14 +41,19 @@ public async Task> Create(StorageFolder parentFold } else { - createdFile = await FilesystemTasks.Wrap(() => StorageFile.GetFileFromPathAsync(Template).AsTask()) + createdFile = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFileFromPathAsync(Template)) .OnSuccess(t => t.CopyAsync(parentFolder, fileName, NameCollisionOption.GenerateUniqueName).AsTask()); } if (createdFile) { if (this.Data != null) { - await FileIO.WriteBytesAsync(createdFile.Result, this.Data); + //await FileIO.WriteBytesAsync(createdFile.Result, this.Data); // Calls unsupported OpenTransactedWriteAsync + using (var fileStream = await createdFile.Result.OpenStreamForWriteAsync()) + { + await fileStream.WriteAsync(Data, 0, Data.Length); + await fileStream.FlushAsync(); + } } } return createdFile; diff --git a/Files/DataModels/SidebarPinnedModel.cs b/Files/DataModels/SidebarPinnedModel.cs index fa52ad47be81..b15f2b211a09 100644 --- a/Files/DataModels/SidebarPinnedModel.cs +++ b/Files/DataModels/SidebarPinnedModel.cs @@ -291,7 +291,7 @@ public async Task AddItemToSidebarAsync(string path) locationItem.IconData = iconData; locationItem.Icon = await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(() => locationItem.IconData.ToBitmapAsync()); } - else + if (locationItem.IconData == null) { locationItem.IconData = await FileThumbnailHelper.LoadIconWithoutOverlayAsync(path, 24u); if (locationItem.IconData != null) diff --git a/Files/Files.csproj b/Files/Files.csproj index 39c622953dba..17ae03b274be 100644 --- a/Files/Files.csproj +++ b/Files/Files.csproj @@ -178,8 +178,15 @@ + + + + + + + diff --git a/Files/Filesystem/FileTagsHelper.cs b/Files/Filesystem/FileTagsHelper.cs index c4f6e1cb6af0..06bba54800fd 100644 --- a/Files/Filesystem/FileTagsHelper.cs +++ b/Files/Filesystem/FileTagsHelper.cs @@ -1,4 +1,5 @@ using Common; +using Files.Filesystem.StorageItems; using Files.Helpers; using Files.Models.Settings; using Microsoft.Toolkit.Uwp; @@ -50,12 +51,12 @@ public static void WriteFileTag(string filePath, string tag) public static async Task GetFileFRN(IStorageItem item) { - if (item is StorageFolder folderItem) + if (item is BaseStorageFolder folderItem && folderItem.Properties != null) { var extraProperties = await folderItem.Properties.RetrievePropertiesAsync(new string[] { "System.FileFRN" }); return (ulong?)extraProperties["System.FileFRN"]; } - else if (item is StorageFile fileItem) + else if (item is BaseStorageFile fileItem && fileItem.Properties != null) { var extraProperties = await fileItem.Properties.RetrievePropertiesAsync(new string[] { "System.FileFRN" }); return (ulong?)extraProperties["System.FileFRN"]; diff --git a/Files/Filesystem/FilesystemOperations/FilesystemOperations.cs b/Files/Filesystem/FilesystemOperations/FilesystemOperations.cs index 9710eb069379..e6a13a85ca69 100644 --- a/Files/Filesystem/FilesystemOperations/FilesystemOperations.cs +++ b/Files/Filesystem/FilesystemOperations/FilesystemOperations.cs @@ -5,7 +5,6 @@ using Files.Filesystem.StorageItems; using Files.Helpers; using Files.Interacts; -using FluentFTP; using Microsoft.Toolkit.Uwp; using Newtonsoft.Json; using System; @@ -65,7 +64,7 @@ public FilesystemOperations(IShellPage associatedInstance) var newEntryInfo = await RegistryHelper.GetNewContextMenuEntryForType(Path.GetExtension(source.Path)); if (newEntryInfo == null) { - StorageFolder folder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(Path.GetDirectoryName(source.Path)); + BaseStorageFolder folder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(PathNormalization.GetParentDir(source.Path)); item = await folder.CreateFileAsync(Path.GetFileName(source.Path)); } else @@ -78,7 +77,7 @@ public FilesystemOperations(IShellPage associatedInstance) case FilesystemItemType.Directory: { - StorageFolder folder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(Path.GetDirectoryName(source.Path)); + BaseStorageFolder folder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(PathNormalization.GetParentDir(source.Path)); item = await folder.CreateFolderAsync(Path.GetFileName(source.Path)); break; @@ -139,7 +138,7 @@ await DialogDisplayHelper.ShowDialogAsync( if (source.ItemType == FilesystemItemType.Directory) { if (!string.IsNullOrWhiteSpace(source.Path) && - Path.GetDirectoryName(destination).IsSubPathOf(source.Path)) // We check if user tried to copy anything above the source.ItemPath + PathNormalization.GetParentDir(destination).IsSubPathOf(source.Path)) // We check if user tried to copy anything above the source.ItemPath { var destinationName = destination.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries).Last(); var sourceName = source.Path.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries).Last(); @@ -165,16 +164,16 @@ await DialogDisplayHelper.ShowDialogAsync( } return null; } - else if (!FtpHelpers.IsFtpPath(destination) && !FtpHelpers.IsFtpPath(source.Path)) + else { // CopyFileFromApp only works on file not directories var fsSourceFolder = await source.ToStorageItemResult(associatedInstance); - var fsDestinationFolder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(Path.GetDirectoryName(destination)); + var fsDestinationFolder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(PathNormalization.GetParentDir(destination)); var fsResult = (FilesystemResult)(fsSourceFolder.ErrorCode | fsDestinationFolder.ErrorCode); if (fsResult) { - var fsCopyResult = await FilesystemTasks.Wrap(() => CloneDirectoryAsync((StorageFolder)fsSourceFolder, (StorageFolder)fsDestinationFolder, fsSourceFolder.Result.Name, collision.Convert())); + var fsCopyResult = await FilesystemTasks.Wrap(() => CloneDirectoryAsync((BaseStorageFolder)fsSourceFolder, (BaseStorageFolder)fsDestinationFolder, fsSourceFolder.Result.Name, collision.Convert())); if (fsCopyResult == FileSystemStatusCode.AlreadyExists) { @@ -190,7 +189,7 @@ await DialogDisplayHelper.ShowDialogAsync( // The source folder was hidden, apply hidden attribute to destination NativeFileOperationsHelper.SetFileAttribute(fsCopyResult.Result.Path, FileAttributes.Hidden); } - copiedItem = (StorageFolder)fsCopyResult; + copiedItem = (BaseStorageFolder)fsCopyResult; } fsResult = fsCopyResult; } @@ -212,30 +211,8 @@ await DialogDisplayHelper.ShowDialogAsync( return null; } } - else if (FtpHelpers.IsFtpPath(destination) && !FtpHelpers.IsFtpPath(source.Path)) - { - var fsSourceFolder = await source.ToStorageItemResult(associatedInstance); - var ftpDestFolder = await new StorageFolderWithPath(null, destination).ToStorageItemResult(associatedInstance); - var fsCopyResult = await FilesystemTasks.Wrap(() => CloneDirectoryToFtpAsync((StorageFolder)fsSourceFolder, (FtpStorageFolder)ftpDestFolder.Result, collision.Convert())); - - if (fsCopyResult == FileSystemStatusCode.AlreadyExists) - { - errorCode?.Report(FileSystemStatusCode.AlreadyExists); - progress?.Report(100.0f); - return null; - } - - errorCode?.Report(fsCopyResult ? FileSystemStatusCode.Success : FileSystemStatusCode.Generic); - progress?.Report(100.0f); - return null; - } - else - { - errorCode?.Report(FileSystemStatusCode.Generic); - return null; - } } - else if (source.ItemType == FilesystemItemType.File && !string.IsNullOrEmpty(source.Path) && !FtpHelpers.IsFtpPath(destination)) + else if (source.ItemType == FilesystemItemType.File) { var fsResult = (FilesystemResult)await Task.Run(() => NativeFileOperationsHelper.CopyFileFromApp(source.Path, destination, true)); @@ -243,14 +220,14 @@ await DialogDisplayHelper.ShowDialogAsync( { Debug.WriteLine(System.Runtime.InteropServices.Marshal.GetLastWin32Error()); - FilesystemResult destinationResult = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(Path.GetDirectoryName(destination)); + FilesystemResult destinationResult = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(PathNormalization.GetParentDir(destination)); var sourceResult = await source.ToStorageItemResult(associatedInstance); fsResult = sourceResult.ErrorCode | destinationResult.ErrorCode; if (fsResult) { - var file = (StorageFile)sourceResult; - var fsResultCopy = new FilesystemResult(null, FileSystemStatusCode.Generic); + var file = (BaseStorageFile)sourceResult; + var fsResultCopy = new FilesystemResult(null, FileSystemStatusCode.Generic); if (string.IsNullOrEmpty(file.Path) && collision != NameCollisionOption.ReplaceExisting) { // Microsoft bug! When dragging files from .zip, "GenerateUniqueName" option is not respected and the file gets overwritten @@ -293,64 +270,8 @@ await DialogDisplayHelper.ShowDialogAsync( return null; } } - else if (string.IsNullOrEmpty(source.Path) && !FtpHelpers.IsFtpPath(destination)) - { - var fsResult = source.Item is StorageFile file ? await FilesystemTasks.Wrap(async () => - await file.CopyAsync( - await StorageFolder.GetFolderFromPathAsync(Path.GetDirectoryName(destination)), - file.Name, - collision)) : new FilesystemResult(null, FileSystemStatusCode.Generic); - - if (!fsResult) - { - errorCode?.Report(fsResult.ErrorCode); - return null; - } - } - else if (FtpHelpers.IsFtpPath(destination)) - { - var ftpClient = associatedInstance.FilesystemViewModel.GetFtpInstance(); - - if (!await ftpClient.EnsureConnectedAsync()) - { - errorCode?.Report(FileSystemStatusCode.Generic); - return null; - } - - if (source.Item is StorageFile file) - { - void ReportFtpPorgress(object sender, FtpProgress p) - { - progress?.Report((float)p.Progress); - } - - using var stream = await file.OpenStreamForReadAsync(); - - var ftpProgress = new Progress(); - ftpProgress.ProgressChanged += ReportFtpPorgress; - - var result = await ftpClient.UploadAsync(stream, FtpHelpers.GetFtpPath(destination), collision switch - { - NameCollisionOption.ReplaceExisting => FtpRemoteExists.Overwrite, - _ => FtpRemoteExists.Skip, - }, false, ftpProgress, cancellationToken); - - ftpProgress.ProgressChanged -= ReportFtpPorgress; - - if (result != FtpStatus.Success) - { - errorCode?.Report(FileSystemStatusCode.Generic); - return null; - } - } - } - else - { - errorCode?.Report(FileSystemStatusCode.Generic); - return null; - } - if (Path.GetDirectoryName(destination) == associatedInstance.FilesystemViewModel.WorkingDirectory.TrimPath()) + if (PathNormalization.GetParentDir(destination) == associatedInstance.FilesystemViewModel.WorkingDirectory.TrimPath()) { await Windows.ApplicationModel.Core.CoreApplication.MainView.DispatcherQueue.EnqueueAsync(async () => { @@ -435,7 +356,7 @@ await DialogDisplayHelper.ShowDialogAsync( if (source.ItemType == FilesystemItemType.Directory) { if (!string.IsNullOrWhiteSpace(source.Path) && - Path.GetDirectoryName(destination).IsSubPathOf(source.Path)) // We check if user tried to move anything above the source.ItemPath + PathNormalization.GetParentDir(destination).IsSubPathOf(source.Path)) // We check if user tried to move anything above the source.ItemPath { var destinationName = destination.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries).Last(); var sourceName = source.Path.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries).Last(); @@ -470,12 +391,12 @@ await DialogDisplayHelper.ShowDialogAsync( Debug.WriteLine(System.Runtime.InteropServices.Marshal.GetLastWin32Error()); var fsSourceFolder = await source.ToStorageItemResult(associatedInstance); - var fsDestinationFolder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(Path.GetDirectoryName(destination)); + var fsDestinationFolder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(PathNormalization.GetParentDir(destination)); fsResult = fsSourceFolder.ErrorCode | fsDestinationFolder.ErrorCode; if (fsResult) { - var fsResultMove = await FilesystemTasks.Wrap(() => MoveDirectoryAsync((StorageFolder)fsSourceFolder, (StorageFolder)fsDestinationFolder, fsSourceFolder.Result.Name, collision.Convert(), true)); + var fsResultMove = await FilesystemTasks.Wrap(() => MoveDirectoryAsync((BaseStorageFolder)fsSourceFolder, (BaseStorageFolder)fsDestinationFolder, fsSourceFolder.Result.Name, collision.Convert(), true)); if (fsResultMove == FileSystemStatusCode.AlreadyExists) { @@ -491,7 +412,7 @@ await DialogDisplayHelper.ShowDialogAsync( // The source folder was hidden, apply hidden attribute to destination NativeFileOperationsHelper.SetFileAttribute(fsResultMove.Result.Path, FileAttributes.Hidden); } - movedItem = (StorageFolder)fsResultMove; + movedItem = (BaseStorageFolder)fsResultMove; } fsResult = fsResultMove; } @@ -519,13 +440,13 @@ await DialogDisplayHelper.ShowDialogAsync( { Debug.WriteLine(System.Runtime.InteropServices.Marshal.GetLastWin32Error()); - FilesystemResult destinationResult = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(Path.GetDirectoryName(destination)); + FilesystemResult destinationResult = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(PathNormalization.GetParentDir(destination)); var sourceResult = await source.ToStorageItemResult(associatedInstance); fsResult = sourceResult.ErrorCode | destinationResult.ErrorCode; if (fsResult) { - var file = (StorageFile)sourceResult; + var file = (BaseStorageFile)sourceResult; var fsResultMove = await FilesystemTasks.Wrap(() => file.MoveAsync(destinationResult.Result, Path.GetFileName(file.Name), collision).AsTask()); if (fsResultMove == FileSystemStatusCode.AlreadyExists) @@ -557,7 +478,7 @@ await DialogDisplayHelper.ShowDialogAsync( errorCode?.Report(fsResult.ErrorCode); } - if (Path.GetDirectoryName(destination) == associatedInstance.FilesystemViewModel.WorkingDirectory.TrimPath()) + if (PathNormalization.GetParentDir(destination) == associatedInstance.FilesystemViewModel.WorkingDirectory.TrimPath()) { await Windows.ApplicationModel.Core.CoreApplication.MainView.DispatcherQueue.EnqueueAsync(async () => { @@ -605,26 +526,12 @@ public async Task DeleteAsync(IStorageItemWithPath source, CancellationToken cancellationToken) { bool deleteFromRecycleBin = recycleBinHelpers.IsPathUnderRecycleBin(source.Path); - bool deleteFromFtp = FtpHelpers.IsFtpPath(source.Path); FilesystemResult fsResult = FileSystemStatusCode.InProgress; errorCode?.Report(fsResult); progress?.Report(0.0f); - if (deleteFromFtp) - { - fsResult = await source.ToStorageItemResult(associatedInstance).OnSuccess(async (t) => - { - await t.DeleteAsync(); - return t; - }); - - errorCode?.Report(fsResult.ErrorCode); - progress?.Report(100.0f); - return null; - } - if (permanently) { fsResult = (FilesystemResult)NativeFileOperationsHelper.DeleteFileFromApp(source.Path); @@ -648,24 +555,7 @@ public async Task DeleteAsync(IStorageItemWithPath source, if (fsResult == FileSystemStatusCode.Unauthorized) { // Try again with fulltrust process (non admin: for shortcuts and hidden files) - // Not neeeded if called after trying with ShellFilesystemOperations - /*var connection = await AppServiceConnectionHelper.Instance; - if (connection != null) - { - var (status, response) = await connection.SendMessageForResponseAsync(new ValueSet() - { - { "Arguments", "FileOperation" }, - { "fileop", "DeleteItem" }, - { "operationID", Guid.NewGuid().ToString() }, - { "filepath", source.Path }, - { "permanently", permanently }, - { "HWND", NativeWinApiHelper.CoreWindowHandle.ToInt64() } - }); - fsResult = (FilesystemResult)(status == AppServiceResponseStatus.Success - && response.Get("Success", false)); - var shellOpResult = JsonConvert.DeserializeObject(response.Get("Result", "{\"Items\": []}")); - fsResult &= (FilesystemResult)shellOpResult.Items.All(x => x.Succeeded); - }*/ + // not neeeded if called after trying with ShellFilesystemOperations if (!fsResult) { fsResult = await PerformAdminOperation(new ValueSet() @@ -865,8 +755,8 @@ public async Task RestoreFromTrashAsync(IStorageItemWithPath so { if (source.ItemType == FilesystemItemType.Directory) { - FilesystemResult sourceFolder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(source.Path); - FilesystemResult destinationFolder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(Path.GetDirectoryName(destination)); + FilesystemResult sourceFolder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(source.Path); + FilesystemResult destinationFolder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(PathNormalization.GetParentDir(destination)); fsResult = sourceFolder.ErrorCode | destinationFolder.ErrorCode; errorCode?.Report(fsResult); @@ -881,8 +771,8 @@ public async Task RestoreFromTrashAsync(IStorageItemWithPath so } else { - FilesystemResult sourceFile = await associatedInstance.FilesystemViewModel.GetFileFromPathAsync(source.Path); - FilesystemResult destinationFolder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(Path.GetDirectoryName(destination)); + FilesystemResult sourceFile = await associatedInstance.FilesystemViewModel.GetFileFromPathAsync(source.Path); + FilesystemResult destinationFolder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(PathNormalization.GetParentDir(destination)); fsResult = sourceFile.ErrorCode | destinationFolder.ErrorCode; errorCode?.Report(fsResult); @@ -939,17 +829,17 @@ await associatedInstance.FilesystemViewModel.GetFileFromPathAsync(iFilePath) #region Helpers - private async static Task CloneDirectoryAsync(IStorageFolder sourceFolder, IStorageFolder destinationFolder, string sourceRootName, CreationCollisionOption collision = CreationCollisionOption.FailIfExists) + private async static Task CloneDirectoryAsync(BaseStorageFolder sourceFolder, BaseStorageFolder destinationFolder, string sourceRootName, CreationCollisionOption collision = CreationCollisionOption.FailIfExists) { - StorageFolder createdRoot = await destinationFolder.CreateFolderAsync(sourceRootName, collision); + BaseStorageFolder createdRoot = await destinationFolder.CreateFolderAsync(sourceRootName, collision); destinationFolder = createdRoot; - foreach (IStorageFile fileInSourceDir in await sourceFolder.GetFilesAsync()) + foreach (BaseStorageFile fileInSourceDir in await sourceFolder.GetFilesAsync()) { await fileInSourceDir.CopyAsync(destinationFolder, fileInSourceDir.Name, NameCollisionOption.GenerateUniqueName); } - foreach (IStorageFolder folderinSourceDir in await sourceFolder.GetFoldersAsync()) + foreach (BaseStorageFolder folderinSourceDir in await sourceFolder.GetFoldersAsync()) { await CloneDirectoryAsync(folderinSourceDir, destinationFolder, folderinSourceDir.Name); } @@ -957,35 +847,17 @@ private async static Task CloneDirectoryAsync(IStorageFolder sour return createdRoot; } - private async static Task CloneDirectoryToFtpAsync(IStorageFolder sourceFolder, FtpStorageFolder destinationFolder, CreationCollisionOption collision = CreationCollisionOption.FailIfExists) - { - var result = await FilesystemTasks.Wrap(async () => await destinationFolder.CreateFolderAsync(sourceFolder.Name, collision)); - - if (result) - { - foreach (IStorageFile fileInSourceDir in await sourceFolder.GetFilesAsync()) - { - await destinationFolder.UploadFileAsync(fileInSourceDir, fileInSourceDir.Name, NameCollisionOption.FailIfExists); - } - - foreach (IStorageFolder folderinSourceDir in await sourceFolder.GetFoldersAsync()) - { - await CloneDirectoryToFtpAsync(folderinSourceDir, destinationFolder.CloneWithPath($"{destinationFolder.Path}/{sourceFolder.Name}")); - } - } - } - - private static async Task MoveDirectoryAsync(IStorageFolder sourceFolder, IStorageFolder destinationDirectory, string sourceRootName, CreationCollisionOption collision = CreationCollisionOption.FailIfExists, bool deleteSource = false) + private static async Task MoveDirectoryAsync(BaseStorageFolder sourceFolder, BaseStorageFolder destinationDirectory, string sourceRootName, CreationCollisionOption collision = CreationCollisionOption.FailIfExists, bool deleteSource = false) { - StorageFolder createdRoot = await destinationDirectory.CreateFolderAsync(sourceRootName, collision); + BaseStorageFolder createdRoot = await destinationDirectory.CreateFolderAsync(sourceRootName, collision); destinationDirectory = createdRoot; - foreach (StorageFile fileInSourceDir in await sourceFolder.GetFilesAsync()) + foreach (BaseStorageFile fileInSourceDir in await sourceFolder.GetFilesAsync()) { await fileInSourceDir.MoveAsync(destinationDirectory, fileInSourceDir.Name, NameCollisionOption.GenerateUniqueName); } - foreach (StorageFolder folderinSourceDir in await sourceFolder.GetFoldersAsync()) + foreach (BaseStorageFolder folderinSourceDir in await sourceFolder.GetFoldersAsync()) { await MoveDirectoryAsync(folderinSourceDir, destinationDirectory, folderinSourceDir.Name, collision, false); } diff --git a/Files/Filesystem/FilesystemOperations/Helpers/FilesystemHelpers.cs b/Files/Filesystem/FilesystemOperations/Helpers/FilesystemHelpers.cs index 2189744564e1..bf3e3a006ed9 100644 --- a/Files/Filesystem/FilesystemOperations/Helpers/FilesystemHelpers.cs +++ b/Files/Filesystem/FilesystemOperations/Helpers/FilesystemHelpers.cs @@ -610,11 +610,11 @@ public async Task CopyItemsFromClipboard(DataPackageView packageVi { binItems ??= await recycleBinHelpers.EnumerateRecycleBin(); var matchingItem = binItems.FirstOrDefault(x => x.RecyclePath == item.Path); // Get original file name - destinations.Add(Path.Combine(destination, matchingItem?.FileName ?? item.Name)); + destinations.Add(PathNormalization.Combine(destination, matchingItem?.FileName ?? item.Name)); } else { - destinations.Add(Path.Combine(destination, item.Name)); + destinations.Add(PathNormalization.Combine(destination, item.Name)); } } @@ -629,7 +629,7 @@ public async Task CopyItemsFromClipboard(DataPackageView packageVi { var imgSource = await packageView.GetBitmapAsync(); using var imageStream = await imgSource.OpenReadAsync(); - var folder = await StorageFolder.GetFolderFromPathAsync(destination); + var folder = await StorageFileExtensions.DangerousGetFolderFromPathAsync(destination); // Set the name of the file to be the current time and date var file = await folder.CreateFileAsync($"{DateTime.Now:mm-dd-yy-HHmmss}.png", CreationCollisionOption.GenerateUniqueName); @@ -831,11 +831,11 @@ public async Task MoveItemsFromClipboard(DataPackageView packageVi { binItems ??= await recycleBinHelpers.EnumerateRecycleBin(); var matchingItem = binItems.FirstOrDefault(x => x.RecyclePath == item.Path); // Get original file name - destinations.Add(Path.Combine(destination, matchingItem?.FileName ?? item.Name)); + destinations.Add(PathNormalization.Combine(destination, matchingItem?.FileName ?? item.Name)); } else { - destinations.Add(Path.Combine(destination, item.Name)); + destinations.Add(PathNormalization.Combine(destination, item.Name)); } } diff --git a/Files/Filesystem/FolderHelpers.cs b/Files/Filesystem/FolderHelpers.cs index 6d06b2bdcfec..36db943e63f5 100644 --- a/Files/Filesystem/FolderHelpers.cs +++ b/Files/Filesystem/FolderHelpers.cs @@ -1,4 +1,5 @@ -using System; +using Files.Filesystem.StorageItems; +using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; @@ -43,7 +44,7 @@ public static bool CheckFolderForHiddenAttribute(string path) return isHidden; } - public static async Task CheckBitlockerStatusAsync(StorageFolder rootFolder, string path) + public static async Task CheckBitlockerStatusAsync(BaseStorageFolder rootFolder, string path) { if (rootFolder == null || rootFolder.Properties == null) { diff --git a/Files/Filesystem/FtpManager.cs b/Files/Filesystem/FtpManager.cs index 0b7f1f6d5fbb..47b1cea33cbb 100644 --- a/Files/Filesystem/FtpManager.cs +++ b/Files/Filesystem/FtpManager.cs @@ -1,67 +1,12 @@ -using Files.ViewModels; -using FluentFTP; -using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Net; namespace Files.Filesystem { public static class FtpManager { - private readonly static List> _viewModels = new List>(); - private readonly static List _ftpClients = new List(); - private readonly static object _lock = new object(); + public static Dictionary Credentials = new Dictionary(); - public static FtpClient GetFtpInstance(this ItemViewModel instance) - { - lock (_lock) - { - for (var i = _viewModels.Count - 1; i >= 0; i--) - { - if (_viewModels[i].TryGetTarget(out var target) && target is not null) - { - if (target == instance) - { - return _ftpClients[i]; - } - } - else - { - if (!_ftpClients[i].IsDisposed) - { - _ftpClients[i].Dispose(); - } - - _viewModels.RemoveAt(i); - _ftpClients.RemoveAt(i); - } - } - - _viewModels.Add(new WeakReference(instance)); - var client = new FtpClient(); - _ftpClients.Add(client); - - return client; - } - } - - public static void DisposeUnused() - { - lock (_lock) - { - for (var i = _viewModels.Count - 1; i >= 0; i--) - { - if (!_viewModels[i].TryGetTarget(out var target) || target is null) - { - if (!_ftpClients[i].IsDisposed) - { - _ftpClients[i].Dispose(); - } - - _viewModels.RemoveAt(i); - _ftpClients.RemoveAt(i); - } - } - } - } + public static readonly NetworkCredential Anonymous = new NetworkCredential("anonymous", "anonymous"); } } \ No newline at end of file diff --git a/Files/Filesystem/ListedItem.cs b/Files/Filesystem/ListedItem.cs index 615151b3f9e1..19a5be199c40 100644 --- a/Files/Filesystem/ListedItem.cs +++ b/Files/Filesystem/ListedItem.cs @@ -13,6 +13,7 @@ using System.IO; using Windows.Storage; using Windows.UI.Xaml.Media.Imaging; +using Files.Filesystem.StorageItems; namespace Files.Filesystem { @@ -331,13 +332,15 @@ public override string ToString() public bool IsShortcutItem => this is ShortcutItem; public bool IsLibraryItem => this is LibraryItem; public bool IsLinkItem => IsShortcutItem && ((ShortcutItem)this).IsUrl; + public bool IsFtpItem => this is FtpItem; + public bool IsZipItem => this is ZipItem; public virtual bool IsExecutable => Path.GetExtension(ItemPath)?.ToLower() == ".exe"; public bool IsPinned => App.SidebarPinnedController.Model.FavoriteItems.Contains(itemPath); - private StorageFile itemFile; + private BaseStorageFile itemFile; - public StorageFile ItemFile + public BaseStorageFile ItemFile { get => itemFile; set => SetProperty(ref itemFile, value); @@ -392,7 +395,7 @@ public FtpItem(FtpListItem item, string folder, string dateReturnFormat = null) ItemDateModifiedReal = item.RawModified < DateTime.FromFileTimeUtc(0) ? DateTimeOffset.MinValue : item.RawModified; ItemName = item.Name; FileExtension = Path.GetExtension(item.Name); - ItemPath = Path.Combine(folder, item.Name); + ItemPath = PathNormalization.Combine(folder, item.Name); PrimaryItemAttribute = isFile ? StorageItemTypes.File : StorageItemTypes.Folder; ItemPropertiesInitialized = false; @@ -434,6 +437,17 @@ public ShortcutItem() : base() public override bool IsExecutable => Path.GetExtension(TargetPath)?.ToLower() == ".exe"; } + public class ZipItem : ListedItem + { + public ZipItem(string folderRelativeId, string returnFormat) : base(folderRelativeId, returnFormat) + { + } + + // Parameterless constructor for JsonConvert + public ZipItem() : base() + { } + } + public class LibraryItem : ListedItem { public LibraryItem(LibraryLocationItem lib, string returnFormat = null) : base(null, returnFormat) diff --git a/Files/Filesystem/Search/FolderSearch.cs b/Files/Filesystem/Search/FolderSearch.cs index 1b6b0fa2b3e6..3b3a6530cb44 100644 --- a/Files/Filesystem/Search/FolderSearch.cs +++ b/Files/Filesystem/Search/FolderSearch.cs @@ -1,6 +1,7 @@ using ByteSizeLib; using Files.Common; using Files.Extensions; +using Files.Filesystem.StorageItems; using Files.Helpers; using Microsoft.Toolkit.Uwp; using System; @@ -96,7 +97,7 @@ public async Task> SearchAsync() return results; } - private async Task SearchAsync(StorageFolder folder, IList results, CancellationToken token) + private async Task SearchAsync(BaseStorageFolder folder, IList results, CancellationToken token) { //var sampler = new IntervalSampler(500); uint index = 0; @@ -185,8 +186,8 @@ private async Task SearchTagsAsync(string folder, IList results, Can { try { - IStorageItem item = (StorageFile)await GetStorageFileAsync(match.FilePath); - item ??= (StorageFolder)await GetStorageFolderAsync(match.FilePath); + IStorageItem item = (BaseStorageFile)await GetStorageFileAsync(match.FilePath); + item ??= (BaseStorageFolder)await GetStorageFolderAsync(match.FilePath); results.Add(await GetListedItemAsync(item)); } catch (Exception ex) @@ -343,7 +344,7 @@ private ListedItem GetListedItemAsync(string itemPath, WIN32_FIND_DATA findData) } if (listedItem != null && MaxItemCount > 0) // Only load icon for searchbox suggestions { - _ = FileThumbnailHelper.LoadIconWithoutOverlayAsync(listedItem.ItemPath, ThumbnailSize) + _ = FileThumbnailHelper.LoadIconFromPathAsync(listedItem.ItemPath, ThumbnailSize, ThumbnailMode.ListView) .ContinueWith((t) => { if (t.IsCompletedSuccessfully && t.Result != null) @@ -366,10 +367,10 @@ private ListedItem GetListedItemAsync(string itemPath, WIN32_FIND_DATA findData) private async Task GetListedItemAsync(IStorageItem item) { ListedItem listedItem = null; - var props = await item.GetBasicPropertiesAsync(); if (item.IsOfType(StorageItemTypes.Folder)) { - var folder = (StorageFolder)item; + var folder = item.AsBaseStorageFolder(); + var props = await folder.GetBasicPropertiesAsync(); listedItem = new ListedItem(null) { PrimaryItemAttribute = StorageItemTypes.Folder, @@ -384,7 +385,8 @@ private async Task GetListedItemAsync(IStorageItem item) } else if (item.IsOfType(StorageItemTypes.File)) { - var file = (StorageFile)item; + var file = item.AsBaseStorageFile(); + var props = await file.GetBasicPropertiesAsync(); string itemFileExtension = null; string itemType = null; if (file.Name.Contains(".")) @@ -449,10 +451,10 @@ private QueryOptions ToQueryOptions() return query; } - private static async Task> GetStorageFolderAsync(string path) + private static async Task> GetStorageFolderAsync(string path) => await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(path)); - private static async Task> GetStorageFileAsync(string path) + private static async Task> GetStorageFileAsync(string path) => await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFileFromPathAsync(path)); } } \ No newline at end of file diff --git a/Files/Filesystem/StorageEnumerators/UniversalStorageEnumerator.cs b/Files/Filesystem/StorageEnumerators/UniversalStorageEnumerator.cs index 04729aa8378e..6a862b8378c4 100644 --- a/Files/Filesystem/StorageEnumerators/UniversalStorageEnumerator.cs +++ b/Files/Filesystem/StorageEnumerators/UniversalStorageEnumerator.cs @@ -1,5 +1,6 @@ using ByteSizeLib; using Files.Extensions; +using Files.Filesystem.StorageItems; using Files.Helpers; using Files.Views.LayoutModes; using System; @@ -18,7 +19,7 @@ namespace Files.Filesystem.StorageEnumerators public static class UniversalStorageEnumerator { public static async Task> ListEntries( - StorageFolder rootFolder, + BaseStorageFolder rootFolder, StorageFolderWithPath currentStorageFolder, string returnformat, Type sourcePageType, @@ -70,7 +71,7 @@ ex is UnauthorizedAccessException { if (item.IsOfType(StorageItemTypes.Folder)) { - var folder = await AddFolderAsync(item as StorageFolder, currentStorageFolder, returnformat, cancellationToken); + var folder = await AddFolderAsync(item.AsBaseStorageFolder(), currentStorageFolder, returnformat, cancellationToken); if (folder != null) { tempList.Add(folder); @@ -78,8 +79,7 @@ ex is UnauthorizedAccessException } else { - var file = item as StorageFile; - var fileEntry = await AddFileAsync(file, currentStorageFolder, returnformat, true, sourcePageType, cancellationToken); + var fileEntry = await AddFileAsync(item.AsBaseStorageFile(), currentStorageFolder, returnformat, true, sourcePageType, cancellationToken); if (fileEntry != null) { tempList.Add(fileEntry); @@ -107,7 +107,7 @@ ex is UnauthorizedAccessException return tempList; } - private static async Task> EnumerateFileByFile(StorageFolder rootFolder, uint startFrom, uint itemsToIterate) + private static async Task> EnumerateFileByFile(BaseStorageFolder rootFolder, uint startFrom, uint itemsToIterate) { var tempList = new List(); for (var i = startFrom; i < startFrom + itemsToIterate; i++) @@ -138,7 +138,7 @@ ex is UnauthorizedAccessException return tempList; } - private static async Task AddFolderAsync(StorageFolder folder, StorageFolderWithPath currentStorageFolder, string dateReturnFormat, CancellationToken cancellationToken) + private static async Task AddFolderAsync(BaseStorageFolder folder, StorageFolderWithPath currentStorageFolder, string dateReturnFormat, CancellationToken cancellationToken) { var basicProperties = await folder.GetBasicPropertiesAsync(); if (!cancellationToken.IsCancellationRequested) @@ -155,7 +155,7 @@ private static async Task AddFolderAsync(StorageFolder folder, Stora LoadFolderGlyph = true, FileImage = null, LoadFileIcon = false, - ItemPath = string.IsNullOrEmpty(folder.Path) ? Path.Combine(currentStorageFolder.Path, folder.Name) : folder.Path, + ItemPath = string.IsNullOrEmpty(folder.Path) ? PathNormalization.Combine(currentStorageFolder.Path, folder.Name) : folder.Path, LoadUnknownTypeGlyph = false, FileSize = null, FileSizeBytes = 0 @@ -165,7 +165,7 @@ private static async Task AddFolderAsync(StorageFolder folder, Stora } private static async Task AddFileAsync( - StorageFile file, + BaseStorageFile file, StorageFolderWithPath currentStorageFolder, string dateReturnFormat, bool suppressThumbnailLoading, @@ -179,7 +179,7 @@ CancellationToken cancellationToken file.Name : file.DisplayName; var itemModifiedDate = basicProperties.DateModified; var itemCreatedDate = file.DateCreated; - var itemPath = string.IsNullOrEmpty(file.Path) ? Path.Combine(currentStorageFolder.Path, file.Name) : file.Path; + var itemPath = string.IsNullOrEmpty(file.Path) ? PathNormalization.Combine(currentStorageFolder.Path, file.Name) : file.Path; var itemSize = ByteSize.FromBytes(basicProperties.Size).ToBinaryString().ConvertSizeAbbreviation(); var itemSizeBytes = basicProperties.Size; var itemType = file.DisplayType; diff --git a/Files/Filesystem/StorageEnumerators/Win32StorageEnumerator.cs b/Files/Filesystem/StorageEnumerators/Win32StorageEnumerator.cs index 43b7fe944e78..234b8f722e84 100644 --- a/Files/Filesystem/StorageEnumerators/Win32StorageEnumerator.cs +++ b/Files/Filesystem/StorageEnumerators/Win32StorageEnumerator.cs @@ -306,25 +306,50 @@ CancellationToken cancellationToken opacity = Constants.UI.DimItemOpacity; } - return new ListedItem(null, dateReturnFormat) + if (itemFileExtension == ".zip") { - PrimaryItemAttribute = StorageItemTypes.File, - FileExtension = itemFileExtension, - LoadUnknownTypeGlyph = itemEmptyImgVis, - FileImage = null, - LoadFileIcon = itemThumbnailImgVis, - LoadFolderGlyph = itemFolderImgVis, - ItemName = itemName, - IsHiddenItem = isHidden, - Opacity = opacity, - ItemDateModifiedReal = itemModifiedDate, - ItemDateAccessedReal = itemLastAccessDate, - ItemDateCreatedReal = itemCreatedDate, - ItemType = itemType, - ItemPath = itemPath, - FileSize = itemSize, - FileSizeBytes = itemSizeBytes - }; + return new ZipItem(null, dateReturnFormat) + { + PrimaryItemAttribute = StorageItemTypes.Folder, // Treat zip files as folders + FileExtension = itemFileExtension, + LoadUnknownTypeGlyph = itemEmptyImgVis, + FileImage = null, + LoadFileIcon = itemThumbnailImgVis, + LoadFolderGlyph = itemFolderImgVis, + ItemName = itemName, + IsHiddenItem = isHidden, + Opacity = opacity, + ItemDateModifiedReal = itemModifiedDate, + ItemDateAccessedReal = itemLastAccessDate, + ItemDateCreatedReal = itemCreatedDate, + ItemType = itemType, + ItemPath = itemPath, + FileSize = itemSize, + FileSizeBytes = itemSizeBytes + }; + } + else + { + return new ListedItem(null, dateReturnFormat) + { + PrimaryItemAttribute = StorageItemTypes.File, + FileExtension = itemFileExtension, + LoadUnknownTypeGlyph = itemEmptyImgVis, + FileImage = null, + LoadFileIcon = itemThumbnailImgVis, + LoadFolderGlyph = itemFolderImgVis, + ItemName = itemName, + IsHiddenItem = isHidden, + Opacity = opacity, + ItemDateModifiedReal = itemModifiedDate, + ItemDateAccessedReal = itemLastAccessDate, + ItemDateCreatedReal = itemCreatedDate, + ItemType = itemType, + ItemPath = itemPath, + FileSize = itemSize, + FileSizeBytes = itemSizeBytes + }; + } } return null; } diff --git a/Files/Filesystem/StorageFileHelpers/FilesystemResult.cs b/Files/Filesystem/StorageFileHelpers/FilesystemResult.cs index 7ff6a0151fac..c49cabdac15e 100644 --- a/Files/Filesystem/StorageFileHelpers/FilesystemResult.cs +++ b/Files/Filesystem/StorageFileHelpers/FilesystemResult.cs @@ -1,4 +1,5 @@ using Files.Enums; +using Files.Filesystem.StorageItems; using System; using System.IO; using System.Threading.Tasks; @@ -70,7 +71,7 @@ public static FileSystemStatusCode GetErrorCode(Exception ex, Type T = null) } else if (ex is ArgumentException) // Item was invalid { - return (T == typeof(StorageFolder) || T == typeof(StorageFolderWithPath)) ? + return (typeof(IStorageFolder).IsAssignableFrom(T) || T == typeof(StorageFolderWithPath)) ? FileSystemStatusCode.NotAFolder : FileSystemStatusCode.NotAFile; } else if ((uint)ex.HResult == 0x800700B7) diff --git a/Files/Filesystem/StorageFileHelpers/StorageFileExtensions.cs b/Files/Filesystem/StorageFileHelpers/StorageFileExtensions.cs index 37aaa95b0cca..7a80b3bef943 100644 --- a/Files/Filesystem/StorageFileHelpers/StorageFileExtensions.cs +++ b/Files/Filesystem/StorageFileHelpers/StorageFileExtensions.cs @@ -1,6 +1,7 @@ using Files.Common; using Files.DataModels.NavigationControlItems; using Files.Extensions; +using Files.Filesystem.StorageItems; using Files.Helpers; using Files.UserControls; using Files.ViewModels; @@ -80,7 +81,7 @@ public static List GetDirectoryPathComponents(string value) var component = value.Substring(lastIndex, i - lastIndex); var path = value.Substring(0, i + 1); - if (!path.Equals("ftp:/", StringComparison.OrdinalIgnoreCase)) + if (!new[] { "ftp:/", "ftps:/", "ftpes:/" }.Contains(path, StringComparer.OrdinalIgnoreCase)) { pathBoxItems.Add(GetPathItem(component, path)); } @@ -110,7 +111,7 @@ public async static Task DangerousGetFolderWithPathFromPa foreach (var component in currComponents.ExceptBy(prevComponents, c => c.Path)) { folder = await folder.GetFolderAsync(component.Title); - path = Path.Combine(path, folder.Name); + path = PathNormalization.Combine(path, folder.Name); } return new StorageFolderWithPath(folder, path); } @@ -121,7 +122,7 @@ public async static Task DangerousGetFolderWithPathFromPa foreach (var component in currComponents.Skip(1)) { folder = await folder.GetFolderAsync(component.Title); - path = Path.Combine(path, folder.Name); + path = PathNormalization.Combine(path, folder.Name); } return new StorageFolderWithPath(folder, path); } @@ -131,15 +132,15 @@ public async static Task DangerousGetFolderWithPathFromPa { // Relative path var fullPath = Path.GetFullPath(Path.Combine(parentFolder.Path, value)); - return new StorageFolderWithPath(await StorageFolder.GetFolderFromPathAsync(fullPath)); + return new StorageFolderWithPath(await BaseStorageFolder.GetFolderFromPathAsync(fullPath)); } else { - return new StorageFolderWithPath(await StorageFolder.GetFolderFromPathAsync(value)); + return new StorageFolderWithPath(await BaseStorageFolder.GetFolderFromPathAsync(value)); } } - public async static Task DangerousGetFolderFromPathAsync(string value, + public async static Task DangerousGetFolderFromPathAsync(string value, StorageFolderWithPath rootFolder = null, StorageFolderWithPath parentFolder = null) { @@ -162,10 +163,10 @@ public async static Task DangerousGetFileWithPathFromPathAs foreach (var component in currComponents.ExceptBy(prevComponents, c => c.Path).SkipLast(1)) { folder = await folder.GetFolderAsync(component.Title); - path = Path.Combine(path, folder.Name); + path = PathNormalization.Combine(path, folder.Name); } var file = await folder.GetFileAsync(currComponents.Last().Title); - path = Path.Combine(path, file.Name); + path = PathNormalization.Combine(path, file.Name); return new StorageFileWithPath(file, path); } else if (value.IsSubPathOf(rootFolder.Path)) @@ -175,10 +176,10 @@ public async static Task DangerousGetFileWithPathFromPathAs foreach (var component in currComponents.Skip(1).SkipLast(1)) { folder = await folder.GetFolderAsync(component.Title); - path = Path.Combine(path, folder.Name); + path = PathNormalization.Combine(path, folder.Name); } var file = await folder.GetFileAsync(currComponents.Last().Title); - path = Path.Combine(path, file.Name); + path = PathNormalization.Combine(path, file.Name); return new StorageFileWithPath(file, path); } } @@ -187,15 +188,15 @@ public async static Task DangerousGetFileWithPathFromPathAs { // Relative path var fullPath = Path.GetFullPath(Path.Combine(parentFolder.Path, value)); - return new StorageFileWithPath(await StorageFile.GetFileFromPathAsync(fullPath)); + return new StorageFileWithPath(await BaseStorageFile.GetFileFromPathAsync(fullPath)); } else { - return new StorageFileWithPath(await StorageFile.GetFileFromPathAsync(value)); + return new StorageFileWithPath(await BaseStorageFile.GetFileFromPathAsync(value)); } } - public async static Task DangerousGetFileFromPathAsync(string value, + public async static Task DangerousGetFileFromPathAsync(string value, StorageFolderWithPath rootFolder = null, StorageFolderWithPath parentFolder = null) { @@ -205,22 +206,22 @@ public async static Task DangerousGetFileFromPathAsync(string value public async static Task> GetFoldersWithPathAsync(this StorageFolderWithPath parentFolder, uint maxNumberOfItems = uint.MaxValue) { return (await parentFolder.Folder.GetFoldersAsync(CommonFolderQuery.DefaultQuery, 0, maxNumberOfItems)) - .Select(x => new StorageFolderWithPath(x, Path.Combine(parentFolder.Path, x.Name))).ToList(); + .Select(x => new StorageFolderWithPath(x, PathNormalization.Combine(parentFolder.Path, x.Name))).ToList(); } public async static Task> GetFilesWithPathAsync(this StorageFolderWithPath parentFolder, uint maxNumberOfItems = uint.MaxValue) { return (await parentFolder.Folder.GetFilesAsync(CommonFileQuery.DefaultQuery, 0, maxNumberOfItems)) - .Select(x => new StorageFileWithPath(x, Path.Combine(parentFolder.Path, x.Name))).ToList(); + .Select(x => new StorageFileWithPath(x, PathNormalization.Combine(parentFolder.Path, x.Name))).ToList(); } public async static Task> GetFoldersWithPathAsync(this StorageFolderWithPath parentFolder, string nameFilter, uint maxNumberOfItems = uint.MaxValue) { var queryOptions = new QueryOptions(); queryOptions.ApplicationSearchFilter = $"System.FileName:{nameFilter}*"; - StorageFolderQueryResult queryResult = parentFolder.Folder.CreateFolderQueryWithOptions(queryOptions); + BaseStorageFolderQueryResult queryResult = parentFolder.Folder.CreateFolderQueryWithOptions(queryOptions); - return (await queryResult.GetFoldersAsync(0, maxNumberOfItems)).Select(x => new StorageFolderWithPath(x, Path.Combine(parentFolder.Path, x.Name))).ToList(); + return (await queryResult.GetFoldersAsync(0, maxNumberOfItems)).Select(x => new StorageFolderWithPath(x, PathNormalization.Combine(parentFolder.Path, x.Name))).ToList(); } public static string GetPathWithoutEnvironmentVariable(string path) @@ -301,5 +302,72 @@ public static bool AreItemsAlreadyInFolder(this IEnumerable storag { return storageItems.Select(x => x.Path).AreItemsAlreadyInFolder(destinationPath); } + + public static BaseStorageFolder AsBaseStorageFolder(this IStorageItem item) + { + if (item == null) + { + return null; + } + else if (item.IsOfType(StorageItemTypes.Folder)) + { + if (item is StorageFolder folder) + { + return (BaseStorageFolder)folder; + } + else + { + return item as BaseStorageFolder; + } + } + return null; + } + + public static BaseStorageFile AsBaseStorageFile(this IStorageItem item) + { + if (item == null) + { + return null; + } + else if (item.IsOfType(StorageItemTypes.File)) + { + if (item is StorageFile file) + { + return (BaseStorageFile)file; + } + else + { + return item as BaseStorageFile; + } + } + return null; + } + + public static async Task> ToStandardStorageItemsAsync(this IEnumerable items) + { + var newItems = new List(); + foreach (var item in items) + { + try + { + if (item == null) + { + } + else if (item.IsOfType(StorageItemTypes.File)) + { + newItems.Add(await item.AsBaseStorageFile().ToStorageFileAsync()); + } + else if (item.IsOfType(StorageItemTypes.Folder)) + { + newItems.Add(await item.AsBaseStorageFolder().ToStorageFolderAsync()); + } + } + catch (NotSupportedException) + { + // Ignore items that can0t be converted + } + } + return newItems; + } } } \ No newline at end of file diff --git a/Files/Filesystem/StorageFileHelpers/StorageFileWithPath.cs b/Files/Filesystem/StorageFileHelpers/StorageFileWithPath.cs index ece4699b50a7..6920a543ef59 100644 --- a/Files/Filesystem/StorageFileHelpers/StorageFileWithPath.cs +++ b/Files/Filesystem/StorageFileHelpers/StorageFileWithPath.cs @@ -1,14 +1,15 @@ -using Windows.Storage; +using Files.Filesystem.StorageItems; +using Windows.Storage; namespace Files.Filesystem { public class StorageFileWithPath : IStorageItemWithPath { - public StorageFile File + public BaseStorageFile File { get { - return (StorageFile)Item; + return (BaseStorageFile)Item; } set { @@ -21,13 +22,13 @@ public StorageFile File public IStorageItem Item { get; set; } public FilesystemItemType ItemType => FilesystemItemType.File; - public StorageFileWithPath(StorageFile file) + public StorageFileWithPath(BaseStorageFile file) { File = file; Path = File.Path; } - public StorageFileWithPath(StorageFile file, string path) + public StorageFileWithPath(BaseStorageFile file, string path) { File = file; Path = path; diff --git a/Files/Filesystem/StorageFileHelpers/StorageFolderWithPath.cs b/Files/Filesystem/StorageFileHelpers/StorageFolderWithPath.cs index fc1a893fdee7..319e55c4b8ee 100644 --- a/Files/Filesystem/StorageFileHelpers/StorageFolderWithPath.cs +++ b/Files/Filesystem/StorageFileHelpers/StorageFolderWithPath.cs @@ -1,14 +1,16 @@ -using Windows.Storage; +using Files.Filesystem.StorageItems; +using Windows.Storage; +using Windows.Storage.Search; namespace Files.Filesystem { public class StorageFolderWithPath : IStorageItemWithPath { - public StorageFolder Folder + public BaseStorageFolder Folder { get { - return (StorageFolder)Item; + return (BaseStorageFolder)Item; } set { @@ -21,13 +23,13 @@ public StorageFolder Folder public IStorageItem Item { get; set; } public FilesystemItemType ItemType => FilesystemItemType.Directory; - public StorageFolderWithPath(StorageFolder folder) + public StorageFolderWithPath(BaseStorageFolder folder) { Folder = folder; Path = folder.Path; } - public StorageFolderWithPath(StorageFolder folder, string path) + public StorageFolderWithPath(BaseStorageFolder folder, string path) { Folder = folder; Path = path; diff --git a/Files/Filesystem/StorageItems/BaseQueryResults.cs b/Files/Filesystem/StorageItems/BaseQueryResults.cs new file mode 100644 index 000000000000..8e4cf9da5dae --- /dev/null +++ b/Files/Filesystem/StorageItems/BaseQueryResults.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.Storage; +using Windows.Storage.Search; + +namespace Files.Filesystem.StorageItems +{ + public class BaseStorageItemQueryResult + { + public BaseStorageFolder Folder { get; } + public QueryOptions Options { get; } + + public BaseStorageItemQueryResult(BaseStorageFolder folder, QueryOptions options) + { + Folder = folder; + Options = options; + } + + public virtual IAsyncOperation> GetItemsAsync(uint startIndex, uint maxNumberOfItems) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = (await GetItemsAsync()).Skip((int)startIndex).Take((int)Math.Min(maxNumberOfItems, int.MaxValue)); + return items.ToList(); + }); + } + + public virtual IAsyncOperation> GetItemsAsync() + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await Folder.GetItemsAsync(); + var query = string.Join(" ", Options.ApplicationSearchFilter, Options.UserSearchFilter).Trim(); + if (!string.IsNullOrEmpty(query)) + { + var spaceSplit = Regex.Split(query, "(?<=^[^\"]*(?:\"[^\"]*\"[^\"]*)*) (?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"); + foreach (var split in spaceSplit) + { + var colonSplit = split.Split(":"); + if (colonSplit.Length == 2) + { + if (colonSplit[0] == "System.FileName" || colonSplit[0] == "fileName" || colonSplit[0] == "name") + { + items = items.Where(x => Regex.IsMatch(x.Name, colonSplit[1].Replace("\"","").Replace("*", "(.*?)"), RegexOptions.IgnoreCase)).ToList(); + } + } + else + { + items = items.Where(x => Regex.IsMatch(x.Name, split.Replace("\"", "").Replace("*", "(.*?)"), RegexOptions.IgnoreCase)).ToList(); + } + } + } + return items.ToList(); + }); + } + + public virtual StorageItemQueryResult ToStorageItemQueryResult() => null; + } + + public class BaseStorageFileQueryResult + { + public BaseStorageFolder Folder { get; } + public QueryOptions Options { get; } + + public BaseStorageFileQueryResult(BaseStorageFolder folder, QueryOptions options) + { + Folder = folder; + Options = options; + } + + public virtual IAsyncOperation> GetFilesAsync(uint startIndex, uint maxNumberOfItems) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = (await GetFilesAsync()).Skip((int)startIndex).Take((int)Math.Min(maxNumberOfItems, int.MaxValue)); + return items.ToList(); + }); + } + + public virtual IAsyncOperation> GetFilesAsync() + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await Folder.GetFilesAsync(); + var query = string.Join(" ", Options.ApplicationSearchFilter, Options.UserSearchFilter).Trim(); + if (!string.IsNullOrEmpty(query)) + { + var spaceSplit = Regex.Split(query, "(?<=^[^\"]*(?:\"[^\"]*\"[^\"]*)*) (?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"); + foreach (var split in spaceSplit) + { + var colonSplit = split.Split(":"); + if (colonSplit.Length == 2) + { + if (colonSplit[0] == "System.FileName" || colonSplit[0] == "fileName" || colonSplit[0] == "name") + { + items = items.Where(x => Regex.IsMatch(x.Name, colonSplit[1].Replace("\"", "").Replace("*", "(.*?)"), RegexOptions.IgnoreCase)).ToList(); + } + } + else + { + items = items.Where(x => Regex.IsMatch(x.Name, split.Replace("\"", "").Replace("*", "(.*?)"), RegexOptions.IgnoreCase)).ToList(); + } + } + } + return items.ToList(); + }); + } + + public virtual StorageFileQueryResult ToStorageFileQueryResult() => null; + } + + public class BaseStorageFolderQueryResult + { + public BaseStorageFolder Folder { get; } + public QueryOptions Options { get; } + + public BaseStorageFolderQueryResult(BaseStorageFolder folder, QueryOptions options) + { + Folder = folder; + Options = options; + } + + public virtual IAsyncOperation> GetFoldersAsync(uint startIndex, uint maxNumberOfItems) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = (await GetFoldersAsync()).Skip((int)startIndex).Take((int)Math.Min(maxNumberOfItems, int.MaxValue)); + return items.ToList(); + }); + } + + public virtual IAsyncOperation> GetFoldersAsync() + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await Folder.GetFoldersAsync(); + var query = string.Join(" ", Options.ApplicationSearchFilter, Options.UserSearchFilter).Trim(); + if (!string.IsNullOrEmpty(query)) + { + var spaceSplit = Regex.Split(query, "(?<=^[^\"]*(?:\"[^\"]*\"[^\"]*)*) (?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"); + foreach (var split in spaceSplit) + { + var colonSplit = split.Split(":"); + if (colonSplit.Length == 2) + { + if (colonSplit[0] == "System.FileName" || colonSplit[0] == "fileName" || colonSplit[0] == "name") + { + items = items.Where(x => Regex.IsMatch(x.Name, colonSplit[1].Replace("\"", "").Replace("*", "(.*?)"), RegexOptions.IgnoreCase)).ToList(); + } + } + else + { + items = items.Where(x => Regex.IsMatch(x.Name, split.Replace("\"", "").Replace("*", "(.*?)"), RegexOptions.IgnoreCase)).ToList(); + } + } + } + return items.ToList(); + }); + } + + public virtual StorageFolderQueryResult ToStorageFolderQueryResult() => null; + } + + public class SystemStorageItemQueryResult : BaseStorageItemQueryResult + { + private StorageItemQueryResult StorageItemQueryResult { get; } + + public SystemStorageItemQueryResult(StorageItemQueryResult sfqr) : base(sfqr.Folder, sfqr.GetCurrentQueryOptions()) + { + StorageItemQueryResult = sfqr; + } + + public override IAsyncOperation> GetItemsAsync(uint startIndex, uint maxNumberOfItems) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await StorageItemQueryResult.GetItemsAsync(startIndex, maxNumberOfItems); + return items.Select(x => x is StorageFolder ? (IStorageItem)new SystemStorageFolder(x as StorageFolder) : new SystemStorageFile(x as StorageFile)).ToList(); + }); + } + + public override IAsyncOperation> GetItemsAsync() + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await StorageItemQueryResult.GetItemsAsync(); + return items.Select(x => x is StorageFolder ? (IStorageItem)new SystemStorageFolder(x as StorageFolder) : new SystemStorageFile(x as StorageFile)).ToList(); + }); + } + + public override StorageItemQueryResult ToStorageItemQueryResult() => StorageItemQueryResult; + } + + public class SystemStorageFileQueryResult : BaseStorageFileQueryResult + { + private StorageFileQueryResult StorageFileQueryResult { get; } + + public SystemStorageFileQueryResult(StorageFileQueryResult sfqr) : base(sfqr.Folder, sfqr.GetCurrentQueryOptions()) + { + StorageFileQueryResult = sfqr; + } + + public override IAsyncOperation> GetFilesAsync(uint startIndex, uint maxNumberOfItems) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await StorageFileQueryResult.GetFilesAsync(startIndex, maxNumberOfItems); + return items.Select(x => new SystemStorageFile(x)).ToList(); + }); + } + + public override IAsyncOperation> GetFilesAsync() + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await StorageFileQueryResult.GetFilesAsync(); + return items.Select(x => new SystemStorageFile(x)).ToList(); + }); + } + + public override StorageFileQueryResult ToStorageFileQueryResult() => StorageFileQueryResult; + } + + public class SystemStorageFolderQueryResult : BaseStorageFolderQueryResult + { + private StorageFolderQueryResult StorageFolderQueryResult { get; } + + public SystemStorageFolderQueryResult(StorageFolderQueryResult sfqr) : base(sfqr.Folder, sfqr.GetCurrentQueryOptions()) + { + StorageFolderQueryResult = sfqr; + } + + public override IAsyncOperation> GetFoldersAsync(uint startIndex, uint maxNumberOfItems) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await StorageFolderQueryResult.GetFoldersAsync(startIndex, maxNumberOfItems); + return items.Select(x => new SystemStorageFolder(x)).ToList(); + }); + } + + public override IAsyncOperation> GetFoldersAsync() + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await StorageFolderQueryResult.GetFoldersAsync(); + return items.Select(x => new SystemStorageFolder(x)).ToList(); + }); + } + + public override StorageFolderQueryResult ToStorageFolderQueryResult() => StorageFolderQueryResult; + } +} diff --git a/Files/Filesystem/StorageItems/BaseStorageItem.cs b/Files/Filesystem/StorageItems/BaseStorageItem.cs new file mode 100644 index 000000000000..20d91fa6a97a --- /dev/null +++ b/Files/Filesystem/StorageItems/BaseStorageItem.cs @@ -0,0 +1,465 @@ +using Files.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.Foundation.Metadata; +using Windows.Storage; +using Windows.Storage.FileProperties; +using Windows.Storage.Search; +using Windows.Storage.Streams; + +namespace Files.Filesystem.StorageItems +{ + public interface IBaseStorageFolder : IStorageFolder, IStorageItem, IStorageFolderQueryOperations, IStorageItemProperties, IStorageItemProperties2, IStorageItem2, IStorageFolder2, IStorageItemPropertiesWithProvider + { + new IAsyncOperation CreateFileAsync(string desiredName); + new IAsyncOperation CreateFileAsync(string desiredName, CreationCollisionOption options); + new IAsyncOperation CreateFolderAsync(string desiredName); + new IAsyncOperation CreateFolderAsync(string desiredName, CreationCollisionOption options); + new IAsyncOperation GetFileAsync(string name); + new IAsyncOperation GetFolderAsync(string name); + new IAsyncOperation GetItemAsync(string name); + new IAsyncOperation> GetFilesAsync(); + new IAsyncOperation> GetFoldersAsync(); + new IAsyncOperation> GetItemsAsync(); + + new IAsyncOperation> GetFilesAsync(CommonFileQuery query, uint startIndex, uint maxItemsToRetrieve); + new IAsyncOperation> GetFilesAsync(CommonFileQuery query); + new IAsyncOperation> GetFoldersAsync(CommonFolderQuery query, uint startIndex, uint maxItemsToRetrieve); + new IAsyncOperation> GetFoldersAsync(CommonFolderQuery query); + + new BaseStorageFileQueryResult CreateFileQueryWithOptions(QueryOptions queryOptions); + new BaseStorageFolderQueryResult CreateFolderQueryWithOptions(QueryOptions queryOptions); + new BaseStorageItemQueryResult CreateItemQueryWithOptions(QueryOptions queryOptions); + + new IAsyncOperation GetBasicPropertiesAsync(); + new IStorageItemExtraProperties Properties { get; } + + new IAsyncOperation GetParentAsync(); + + IAsyncOperation ToStorageFolderAsync(); + } + + public interface IBaseStorageFile : IStorageFile, IInputStreamReference, IRandomAccessStreamReference, IStorageItem, IStorageItemProperties, IStorageItemProperties2, IStorageItem2, IStorageItemPropertiesWithProvider, IStorageFilePropertiesWithAvailability, IStorageFile2 + { + new IAsyncOperation CopyAsync(IStorageFolder destinationFolder); + new IAsyncOperation CopyAsync(IStorageFolder destinationFolder, string desiredNewName); + new IAsyncOperation CopyAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option); + + new IAsyncOperation GetBasicPropertiesAsync(); + new IStorageItemExtraProperties Properties { get; } + + new IAsyncOperation GetParentAsync(); + + IAsyncOperation ToStorageFileAsync(); + } + + public class BaseStorageItemExtraProperties : IStorageItemExtraProperties + { + public virtual IAsyncOperation> RetrievePropertiesAsync(IEnumerable propertiesToRetrieve) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var props = new Dictionary(); + propertiesToRetrieve.ForEach(x => props[x] = null); + return props; + }); + } + + public virtual IAsyncAction SavePropertiesAsync([HasVariant] IEnumerable> propertiesToSave) + { + return Task.CompletedTask.AsAsyncAction(); + } + + public virtual IAsyncAction SavePropertiesAsync() + { + return Task.CompletedTask.AsAsyncAction(); + } + } + + public class BaseBasicStorageItemExtraProperties : BaseStorageItemExtraProperties + { + private IStorageItem item; + + public BaseBasicStorageItemExtraProperties(IStorageItem item) + { + this.item = item; + } + + public override IAsyncOperation> RetrievePropertiesAsync(IEnumerable propertiesToRetrieve) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var props = new Dictionary(); + propertiesToRetrieve.ForEach(x => props[x] = null); + // Fill common poperties + var ret = item.AsBaseStorageFile()?.GetBasicPropertiesAsync() ?? item.AsBaseStorageFolder()?.GetBasicPropertiesAsync(); + var basicProps = ret != null ? await ret : null; + props["System.ItemPathDisplay"] = item?.Path; + props["System.DateCreated"] = basicProps?.ItemDate; + props["System.DateModified"] = basicProps?.DateModified; + return props; + }); + } + } + + public class BaseBasicProperties : BaseStorageItemExtraProperties + { + public virtual DateTimeOffset DateModified { get => DateTimeOffset.Now; } + public virtual DateTimeOffset ItemDate { get => DateTimeOffset.Now; } + public virtual ulong Size { get => 0; } + } + + public abstract class BaseStorageFolder : IBaseStorageFolder + { + public static implicit operator BaseStorageFolder(StorageFolder value) => new SystemStorageFolder(value); + + public abstract IAsyncOperation CreateFileAsync(string desiredName); + public abstract IAsyncOperation CreateFileAsync(string desiredName, CreationCollisionOption options); + public abstract IAsyncOperation CreateFolderAsync(string desiredName); + public abstract IAsyncOperation CreateFolderAsync(string desiredName, CreationCollisionOption options); + public abstract IAsyncOperation GetFileAsync(string name); + public abstract IAsyncOperation GetFolderAsync(string name); + public abstract IAsyncOperation GetItemAsync(string name); + public abstract IAsyncOperation> GetFilesAsync(); + public abstract IAsyncOperation> GetFoldersAsync(); + public abstract IAsyncOperation> GetItemsAsync(); + public abstract IAsyncOperation ToStorageFolderAsync(); + public abstract IAsyncAction RenameAsync(string desiredName); + public abstract IAsyncAction RenameAsync(string desiredName, NameCollisionOption option); + public abstract IAsyncAction DeleteAsync(); + public abstract IAsyncAction DeleteAsync(StorageDeleteOption option); + public abstract IAsyncOperation GetBasicPropertiesAsync(); + public abstract bool IsOfType(StorageItemTypes type); + + public abstract FileAttributes Attributes { get; } + public abstract DateTimeOffset DateCreated { get; } + public abstract string Name { get; } + public abstract string Path { get; } + + public abstract IAsyncOperation GetIndexedStateAsync(); + public abstract StorageFileQueryResult CreateFileQuery(); + public abstract StorageFileQueryResult CreateFileQuery(CommonFileQuery query); + public abstract BaseStorageFileQueryResult CreateFileQueryWithOptions(QueryOptions queryOptions); + public abstract StorageFolderQueryResult CreateFolderQuery(); + public abstract StorageFolderQueryResult CreateFolderQuery(CommonFolderQuery query); + public abstract BaseStorageFolderQueryResult CreateFolderQueryWithOptions(QueryOptions queryOptions); + public abstract StorageItemQueryResult CreateItemQuery(); + public abstract BaseStorageItemQueryResult CreateItemQueryWithOptions(QueryOptions queryOptions); + public abstract IAsyncOperation> GetFilesAsync(CommonFileQuery query, uint startIndex, uint maxItemsToRetrieve); + public abstract IAsyncOperation> GetFilesAsync(CommonFileQuery query); + public abstract IAsyncOperation> GetFoldersAsync(CommonFolderQuery query, uint startIndex, uint maxItemsToRetrieve); + + public abstract IAsyncOperation> GetFoldersAsync(CommonFolderQuery query); + public abstract IAsyncOperation> GetItemsAsync(uint startIndex, uint maxItemsToRetrieve); + public abstract bool AreQueryOptionsSupported(QueryOptions queryOptions); + public abstract bool IsCommonFolderQuerySupported(CommonFolderQuery query); + public abstract bool IsCommonFileQuerySupported(CommonFileQuery query); + public abstract IAsyncOperation GetThumbnailAsync(ThumbnailMode mode); + public abstract IAsyncOperation GetThumbnailAsync(ThumbnailMode mode, uint requestedSize); + public abstract IAsyncOperation GetThumbnailAsync(ThumbnailMode mode, uint requestedSize, ThumbnailOptions options); + + public abstract string DisplayName { get; } + public abstract string DisplayType { get; } + public abstract string FolderRelativeId { get; } + public abstract IStorageItemExtraProperties Properties { get; } + + IAsyncOperation IStorageFolder.CreateFileAsync(string desiredName) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return await (await CreateFileAsync(desiredName)).ToStorageFileAsync(); + }); + } + + IAsyncOperation IStorageFolder.CreateFileAsync(string desiredName, CreationCollisionOption options) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return await (await CreateFileAsync(desiredName, options)).ToStorageFileAsync(); + }); + } + + IAsyncOperation IStorageFolder.CreateFolderAsync(string desiredName) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return await (await CreateFolderAsync(desiredName)).ToStorageFolderAsync(); + }); + } + + IAsyncOperation IStorageFolder.CreateFolderAsync(string desiredName, CreationCollisionOption options) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return await (await CreateFolderAsync(desiredName, options)).ToStorageFolderAsync(); + }); + } + + IAsyncOperation IStorageFolder.GetFileAsync(string name) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return await (await GetFileAsync(name)).ToStorageFileAsync(); + }); + } + + IAsyncOperation IStorageFolder.GetFolderAsync(string name) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return await (await GetFolderAsync(name)).ToStorageFolderAsync(); + }); + } + + IAsyncOperation> IStorageFolder.GetFilesAsync() + { + return AsyncInfo.Run>(async (cancellationToken) => + { + return await Task.WhenAll((await GetFilesAsync()).Select(x => x.ToStorageFileAsync().AsTask())); + }); + } + + IAsyncOperation> IStorageFolder.GetFoldersAsync() + { + return AsyncInfo.Run>(async (cancellationToken) => + { + return await Task.WhenAll((await GetFoldersAsync()).Select(x => x.ToStorageFolderAsync().AsTask())); + }); + } + + public IAsyncOperation GetScaledImageAsThumbnailAsync(ThumbnailMode mode) + { + return Task.FromResult(null).AsAsyncOperation(); + } + + public IAsyncOperation GetScaledImageAsThumbnailAsync(ThumbnailMode mode, uint requestedSize) + { + return Task.FromResult(null).AsAsyncOperation(); + } + + public IAsyncOperation GetScaledImageAsThumbnailAsync(ThumbnailMode mode, uint requestedSize, ThumbnailOptions options) + { + return Task.FromResult(null).AsAsyncOperation(); + } + + public abstract IAsyncOperation GetParentAsync(); + public abstract bool IsEqual(IStorageItem item); + public abstract IAsyncOperation TryGetItemAsync(string name); + + public StorageProvider Provider => null; + + IAsyncOperation IStorageItem2.GetParentAsync() + { + return AsyncInfo.Run(async (cancellationToken) => + { + return await (await GetParentAsync()).ToStorageFolderAsync(); + }); + } + + public static IAsyncOperation GetFolderFromPathAsync(string path) + { + return AsyncInfo.Run(async (cancellationToken) => + { + BaseStorageFolder folder = null; + folder ??= await ZipStorageFolder.FromPathAsync(path); + folder ??= await FtpStorageFolder.FromPathAsync(path); + folder ??= await SystemStorageFolder.FromPathAsync(path); + return folder; + }); + } + + IAsyncOperation> IStorageFolderQueryOperations.GetFilesAsync(CommonFileQuery query, uint startIndex, uint maxItemsToRetrieve) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + return await Task.WhenAll((await GetFilesAsync(query, startIndex, maxItemsToRetrieve)).Select(x => x.ToStorageFileAsync().AsTask())); + }); + } + + IAsyncOperation> IStorageFolderQueryOperations.GetFilesAsync(CommonFileQuery query) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + return await Task.WhenAll((await GetFilesAsync(query)).Select(x => x.ToStorageFileAsync().AsTask())); + }); + } + + IAsyncOperation> IStorageFolderQueryOperations.GetFoldersAsync(CommonFolderQuery query, uint startIndex, uint maxItemsToRetrieve) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + return await Task.WhenAll((await GetFoldersAsync(query, startIndex, maxItemsToRetrieve)).Select(x => x.ToStorageFolderAsync().AsTask())); + }); + } + + IAsyncOperation> IStorageFolderQueryOperations.GetFoldersAsync(CommonFolderQuery query) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + return await Task.WhenAll((await GetFoldersAsync(query)).Select(x => x.ToStorageFolderAsync().AsTask())); + }); + } + + StorageFileQueryResult IStorageFolderQueryOperations.CreateFileQueryWithOptions(QueryOptions queryOptions) => throw new NotSupportedException(); + StorageFolderQueryResult IStorageFolderQueryOperations.CreateFolderQueryWithOptions(QueryOptions queryOptions) => throw new NotSupportedException(); + StorageItemQueryResult IStorageFolderQueryOperations.CreateItemQueryWithOptions(QueryOptions queryOptions) => throw new NotSupportedException(); + + IAsyncOperation IStorageItem.GetBasicPropertiesAsync() + { + return AsyncInfo.Run(async (cancellationToken) => + { + var item = await ToStorageFolderAsync(); + return await item.GetBasicPropertiesAsync(); + }); + } + + StorageItemContentProperties IStorageItemProperties.Properties + { + get + { + if (this is SystemStorageFolder sysFolder) + { + return sysFolder.Folder.Properties; + } + return null; + } + } + } + + public abstract class BaseStorageFile : IBaseStorageFile + { + public static implicit operator BaseStorageFile(StorageFile value) => new SystemStorageFile(value); + + public abstract IAsyncOperation ToStorageFileAsync(); + public abstract IAsyncOperation OpenAsync(FileAccessMode accessMode); + public abstract IAsyncOperation OpenTransactedWriteAsync(); + public abstract IAsyncOperation CopyAsync(IStorageFolder destinationFolder); + public abstract IAsyncOperation CopyAsync(IStorageFolder destinationFolder, string desiredNewName); + public abstract IAsyncOperation CopyAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option); + public abstract IAsyncAction CopyAndReplaceAsync(IStorageFile fileToReplace); + public abstract IAsyncAction MoveAsync(IStorageFolder destinationFolder); + public abstract IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName); + public abstract IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option); + public abstract IAsyncAction MoveAndReplaceAsync(IStorageFile fileToReplace); + + public abstract string ContentType { get; } + public abstract string FileType { get; } + + public abstract IAsyncAction RenameAsync(string desiredName); + public abstract IAsyncAction RenameAsync(string desiredName, NameCollisionOption option); + public abstract IAsyncAction DeleteAsync(); + public abstract IAsyncAction DeleteAsync(StorageDeleteOption option); + public abstract IAsyncOperation GetBasicPropertiesAsync(); + public abstract bool IsOfType(StorageItemTypes type); + + public abstract FileAttributes Attributes { get; } + public abstract DateTimeOffset DateCreated { get; } + public abstract string Name { get; } + public abstract string Path { get; } + + public abstract IAsyncOperation OpenReadAsync(); + public abstract IAsyncOperation OpenSequentialReadAsync(); + public abstract IAsyncOperation GetThumbnailAsync(ThumbnailMode mode); + public abstract IAsyncOperation GetThumbnailAsync(ThumbnailMode mode, uint requestedSize); + public abstract IAsyncOperation GetThumbnailAsync(ThumbnailMode mode, uint requestedSize, ThumbnailOptions options); + + public abstract string DisplayName { get; } + public abstract string DisplayType { get; } + public abstract string FolderRelativeId { get; } + public abstract IStorageItemExtraProperties Properties { get; } + + IAsyncOperation IStorageFile.CopyAsync(IStorageFolder destinationFolder) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return await (await CopyAsync(destinationFolder)).ToStorageFileAsync(); + }); + } + + IAsyncOperation IStorageFile.CopyAsync(IStorageFolder destinationFolder, string desiredNewName) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return await (await CopyAsync(destinationFolder, desiredNewName)).ToStorageFileAsync(); + }); + } + + IAsyncOperation IStorageFile.CopyAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return await (await CopyAsync(destinationFolder, desiredNewName, option)).ToStorageFileAsync(); + }); + } + + public IAsyncOperation GetScaledImageAsThumbnailAsync(ThumbnailMode mode) + { + return Task.FromResult(null).AsAsyncOperation(); + } + + public IAsyncOperation GetScaledImageAsThumbnailAsync(ThumbnailMode mode, uint requestedSize) + { + return Task.FromResult(null).AsAsyncOperation(); + } + + public IAsyncOperation GetScaledImageAsThumbnailAsync(ThumbnailMode mode, uint requestedSize, ThumbnailOptions options) + { + return Task.FromResult(null).AsAsyncOperation(); + } + + public abstract IAsyncOperation GetParentAsync(); + public abstract bool IsEqual(IStorageItem item); + + public StorageProvider Provider => null; + + public bool IsAvailable => true; + + public abstract IAsyncOperation OpenAsync(FileAccessMode accessMode, StorageOpenOptions options); + public abstract IAsyncOperation OpenTransactedWriteAsync(StorageOpenOptions options); + + IAsyncOperation IStorageItem2.GetParentAsync() + { + return AsyncInfo.Run(async (cancellationToken) => + { + return await (await GetParentAsync()).ToStorageFolderAsync(); + }); + } + + public static IAsyncOperation GetFileFromPathAsync(string path) + { + return AsyncInfo.Run(async (cancellationToken) => + { + BaseStorageFile file = null; + file ??= await ZipStorageFile.FromPathAsync(path); + file ??= await FtpStorageFile.FromPathAsync(path); + file ??= await SystemStorageFile.FromPathAsync(path); + return file; + }); + } + + IAsyncOperation IStorageItem.GetBasicPropertiesAsync() + { + return AsyncInfo.Run(async (cancellationToken) => + { + var item = await ToStorageFileAsync(); + return await item.GetBasicPropertiesAsync(); + }); + } + + StorageItemContentProperties IStorageItemProperties.Properties + { + get + { + if (this is SystemStorageFile sysFile) + { + return sysFile.File.Properties; + } + return null; + } + } + } +} diff --git a/Files/Filesystem/StorageItems/FtpStorageFile.cs b/Files/Filesystem/StorageItems/FtpStorageFile.cs index b8413182218c..86e500309cc2 100644 --- a/Files/Filesystem/StorageItems/FtpStorageFile.cs +++ b/Files/Filesystem/StorageItems/FtpStorageFile.cs @@ -1,5 +1,5 @@ -using Files.Helpers; -using Files.ViewModels; +using Files.Common; +using Files.Helpers; using FluentFTP; using System; using System.IO; @@ -8,37 +8,47 @@ using Windows.Storage; using Windows.Storage.FileProperties; using Windows.Storage.Streams; +using System; +using System.IO; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; +using Microsoft.Toolkit.Uwp; namespace Files.Filesystem.StorageItems { - internal sealed class FtpStorageFile : IStorageFile + public sealed class FtpStorageFile : BaseStorageFile { - private readonly ItemViewModel _viewModel; - - public FtpStorageFile(ItemViewModel viewModel, FtpItem ftpItem) + public FtpStorageFile(FtpItem ftpItem) { - _viewModel = viewModel; DateCreated = ftpItem.ItemDateCreatedReal; Name = ftpItem.ItemName; Path = ftpItem.ItemPath; FtpPath = FtpHelpers.GetFtpPath(ftpItem.ItemPath); - FileType = ftpItem.ItemType; } - public FtpStorageFile(ItemViewModel viewModel, IStorageItemWithPath item) + public FtpStorageFile(string folder, FtpListItem ftpItem) + { + DateCreated = ftpItem.RawCreated < DateTime.FromFileTimeUtc(0) ? DateTimeOffset.MinValue : ftpItem.RawCreated; + Name = ftpItem.Name; + Path = PathNormalization.Combine(folder, ftpItem.Name); + FtpPath = FtpHelpers.GetFtpPath(Path); + } + + public FtpStorageFile(IStorageItemWithPath item) { - _viewModel = viewModel; Name = System.IO.Path.GetFileName(item.Path); Path = item.Path; FtpPath = FtpHelpers.GetFtpPath(item.Path); - FileType = "FTP File"; } private async void FtpDataStreamingHandler(StreamedFileDataRequest request) { try { - var ftpClient = _viewModel.GetFtpInstance(); + using var ftpClient = new FtpClient(); + ftpClient.Host = FtpHelpers.GetFtpHost(Path); + ftpClient.Port = FtpHelpers.GetFtpPort(Path); + ftpClient.Credentials = FtpManager.Credentials.Get(ftpClient.Host, FtpManager.Anonymous); if (!await ftpClient.EnsureConnectedAsync()) { @@ -46,9 +56,11 @@ private async void FtpDataStreamingHandler(StreamedFileDataRequest request) return; } - using var stream = request.AsStreamForWrite(); - await ftpClient.DownloadAsync(stream, FtpPath); - await request.FlushAsync(); + using (var outStream = request.AsStreamForWrite()) + { + await ftpClient.DownloadAsync(outStream, FtpPath); + await outStream.FlushAsync(); + } request.Dispose(); } catch @@ -57,28 +69,32 @@ private async void FtpDataStreamingHandler(StreamedFileDataRequest request) } } - public IAsyncOperation ToStorageFileAsync() + public override IAsyncOperation ToStorageFileAsync() { return StorageFile.CreateStreamedFileAsync(Name, FtpDataStreamingHandler, null); } - public IAsyncAction RenameAsync(string desiredName) + public override IAsyncAction RenameAsync(string desiredName) { return RenameAsync(desiredName, NameCollisionOption.FailIfExists); } - public IAsyncAction RenameAsync(string desiredName, NameCollisionOption option) + public override IAsyncAction RenameAsync(string desiredName, NameCollisionOption option) { return AsyncInfo.Run(async (cancellationToken) => { - var ftpClient = _viewModel.GetFtpInstance(); + using var ftpClient = new FtpClient(); + ftpClient.Host = FtpHelpers.GetFtpHost(Path); + ftpClient.Port = FtpHelpers.GetFtpPort(Path); + ftpClient.Credentials = FtpManager.Credentials.Get(ftpClient.Host, FtpManager.Anonymous); + if (!await ftpClient.EnsureConnectedAsync()) { return; } if (!await ftpClient.MoveFileAsync(FtpPath, - $"{FtpHelpers.GetFtpDirectoryName(FtpPath)}/{desiredName}", + $"{PathNormalization.GetParentDir(FtpPath)}/{desiredName}", option == NameCollisionOption.ReplaceExisting ? FtpRemoteExists.Overwrite : FtpRemoteExists.Skip, cancellationToken)) { @@ -90,11 +106,15 @@ public IAsyncAction RenameAsync(string desiredName, NameCollisionOption option) }); } - public IAsyncAction DeleteAsync() + public override IAsyncAction DeleteAsync() { return AsyncInfo.Run(async (cancellationToken) => { - var ftpClient = _viewModel.GetFtpInstance(); + using var ftpClient = new FtpClient(); + ftpClient.Host = FtpHelpers.GetFtpHost(Path); + ftpClient.Port = FtpHelpers.GetFtpPort(Path); + ftpClient.Credentials = FtpManager.Credentials.Get(ftpClient.Host, FtpManager.Anonymous); + if (!await ftpClient.EnsureConnectedAsync()) { return; @@ -104,59 +124,116 @@ public IAsyncAction DeleteAsync() }); } - public IAsyncAction DeleteAsync(StorageDeleteOption option) + public override IAsyncAction DeleteAsync(StorageDeleteOption option) { return DeleteAsync(); } - public IAsyncOperation GetBasicPropertiesAsync() => throw new NotSupportedException(); + public override IAsyncOperation GetBasicPropertiesAsync() + { + return AsyncInfo.Run(async (cancellationToken) => + { + using var ftpClient = new FtpClient(); + ftpClient.Host = FtpHelpers.GetFtpHost(Path); + ftpClient.Port = FtpHelpers.GetFtpPort(Path); + ftpClient.Credentials = FtpManager.Credentials.Get(ftpClient.Host, FtpManager.Anonymous); - public bool IsOfType(StorageItemTypes type) => type == StorageItemTypes.File; + if (!await ftpClient.EnsureConnectedAsync()) + { + return new BaseBasicProperties(); + } + + var item = await ftpClient.GetObjectInfoAsync(FtpPath); + if (item != null) + { + return new FtpFileBasicProperties(item); + } + return new BaseBasicProperties(); + }); + } - public Windows.Storage.FileAttributes Attributes { get; } = Windows.Storage.FileAttributes.Normal; + public override bool IsOfType(StorageItemTypes type) => type == StorageItemTypes.File; - public DateTimeOffset DateCreated { get; } + public override Windows.Storage.FileAttributes Attributes { get; } = Windows.Storage.FileAttributes.Normal; - public string Name { get; } + public override DateTimeOffset DateCreated { get; } - public string Path { get; } + public override string Name { get; } + + public override string Path { get; } public string FtpPath { get; } - public IAsyncOperation OpenAsync(FileAccessMode accessMode) => throw new NotSupportedException(); + public override IAsyncOperation OpenAsync(FileAccessMode accessMode) + { + return AsyncInfo.Run(async (cancellationToken) => + { + var ftpClient = new FtpClient(); + ftpClient.Host = FtpHelpers.GetFtpHost(Path); + ftpClient.Port = FtpHelpers.GetFtpPort(Path); + ftpClient.Credentials = FtpManager.Credentials.Get(ftpClient.Host, FtpManager.Anonymous); - public IAsyncOperation OpenTransactedWriteAsync() => throw new NotSupportedException(); + if (!await ftpClient.EnsureConnectedAsync()) + { + return null; + } + + if (accessMode == FileAccessMode.Read) + { + var inStream = await ftpClient.OpenReadAsync(FtpPath, cancellationToken); + return new NonSeekableRandomAccessStream(inStream, (ulong)inStream.Length) + { + DisposeCallback = () => ftpClient.Dispose() + }; + } + else + { + return new RandomAccessStreamWithFlushCallback() + { + DisposeCallback = () => ftpClient.Dispose(), + FlushCallback = UploadFile(ftpClient) + }; + } + }); + } + + private Func> UploadFile(FtpClient ftpClient) + { + return (stream) => AsyncInfo.Run(async (cancellationToken) => + { + await ftpClient.UploadAsync(stream.CloneStream().AsStream(), FtpPath, FtpRemoteExists.Overwrite); + return true; + }); + } - public IAsyncOperation CopyAsync(IStorageFolder destinationFolder) + public override IAsyncOperation OpenTransactedWriteAsync() => throw new NotSupportedException(); + + public override IAsyncOperation CopyAsync(IStorageFolder destinationFolder) { return CopyAsync(destinationFolder, Name, NameCollisionOption.FailIfExists); } - public IAsyncOperation CopyAsync(IStorageFolder destinationFolder, string desiredNewName) + public override IAsyncOperation CopyAsync(IStorageFolder destinationFolder, string desiredNewName) { return CopyAsync(destinationFolder, desiredNewName, NameCollisionOption.FailIfExists); } - public IAsyncOperation CopyAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option) + public override IAsyncOperation CopyAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option) { return AsyncInfo.Run(async (cancellationToken) => { - var ftpClient = _viewModel.GetFtpInstance(); + using var ftpClient = new FtpClient(); + ftpClient.Host = FtpHelpers.GetFtpHost(Path); + ftpClient.Port = FtpHelpers.GetFtpPort(Path); + ftpClient.Credentials = FtpManager.Credentials.Get(ftpClient.Host, FtpManager.Anonymous); if (!await ftpClient.EnsureConnectedAsync()) { return null; } - var createOption = option switch - { - NameCollisionOption.FailIfExists => CreationCollisionOption.FailIfExists, - NameCollisionOption.GenerateUniqueName => CreationCollisionOption.GenerateUniqueName, - NameCollisionOption.ReplaceExisting => CreationCollisionOption.ReplaceExisting, - _ => CreationCollisionOption.FailIfExists - }; - - var file = await destinationFolder.CreateFileAsync(desiredNewName, createOption); + BaseStorageFolder destFolder = destinationFolder.AsBaseStorageFolder(); + BaseStorageFile file = await destFolder.CreateFileAsync(desiredNewName, option.Convert()); var stream = await file.OpenStreamForWriteAsync(); if (await ftpClient.DownloadAsync(stream, FtpPath, token: cancellationToken)) @@ -168,22 +245,133 @@ public IAsyncOperation CopyAsync(IStorageFolder destinationFolder, }); } - public IAsyncAction CopyAndReplaceAsync(IStorageFile fileToReplace) => throw new NotSupportedException(); + public override IAsyncAction CopyAndReplaceAsync(IStorageFile fileToReplace) => throw new NotSupportedException(); + public override IAsyncAction MoveAsync(IStorageFolder destinationFolder) => throw new NotSupportedException(); + public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName) => throw new NotSupportedException(); + public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option) => throw new NotSupportedException(); + public override IAsyncAction MoveAndReplaceAsync(IStorageFile fileToReplace) => throw new NotSupportedException(); - public IAsyncAction MoveAsync(IStorageFolder destinationFolder) => throw new NotSupportedException(); + public override string ContentType { get; } = "application/octet-stream"; - public IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName) => throw new NotSupportedException(); + public override string FileType => System.IO.Path.GetExtension(Name); - public IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option) => throw new NotSupportedException(); + public override IAsyncOperation OpenReadAsync() + { + return AsyncInfo.Run(async (cancellationToken) => + { + var ftpClient = new FtpClient(); + ftpClient.Host = FtpHelpers.GetFtpHost(Path); + ftpClient.Port = FtpHelpers.GetFtpPort(Path); + ftpClient.Credentials = FtpManager.Credentials.Get(ftpClient.Host, FtpManager.Anonymous); - public IAsyncAction MoveAndReplaceAsync(IStorageFile fileToReplace) => throw new NotSupportedException(); + if (!await ftpClient.EnsureConnectedAsync()) + { + return null; + } - public string ContentType { get; } = "application/octet-stream"; + var inStream = await ftpClient.OpenReadAsync(FtpPath, cancellationToken); + var nsStream = new NonSeekableRandomAccessStream(inStream, (ulong)inStream.Length) + { + DisposeCallback = () => ftpClient.Dispose() + }; + return new StreamWithContentType(nsStream); + }); + } + + public override IAsyncOperation OpenSequentialReadAsync() + { + return AsyncInfo.Run(async (cancellationToken) => + { + var ftpClient = new FtpClient(); + ftpClient.Host = FtpHelpers.GetFtpHost(Path); + ftpClient.Port = FtpHelpers.GetFtpPort(Path); + ftpClient.Credentials = FtpManager.Credentials.Get(ftpClient.Host, FtpManager.Anonymous); + + if (!await ftpClient.EnsureConnectedAsync()) + { + return null; + } + + var inStream = await ftpClient.OpenReadAsync(FtpPath, cancellationToken); + return new InputStreamWithDisposeCallback(inStream) { DisposeCallback = () => ftpClient.Dispose() }; + }); + } + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode) + { + return Task.FromResult(null).AsAsyncOperation(); + } + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode, uint requestedSize) + { + return Task.FromResult(null).AsAsyncOperation(); + } + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode, uint requestedSize, ThumbnailOptions options) + { + return Task.FromResult(null).AsAsyncOperation(); + } + + public override IAsyncOperation GetParentAsync() + { + throw new NotSupportedException(); + } - public string FileType { get; } + public override bool IsEqual(IStorageItem item) => item?.Path == Path; - public IAsyncOperation OpenReadAsync() => throw new NotSupportedException(); + public override IAsyncOperation OpenAsync(FileAccessMode accessMode, StorageOpenOptions options) => OpenAsync(accessMode); - public IAsyncOperation OpenSequentialReadAsync() => throw new NotSupportedException(); + public override IAsyncOperation OpenTransactedWriteAsync(StorageOpenOptions options) => throw new NotSupportedException(); + + public static IAsyncOperation FromPathAsync(string path) + { + if (FtpHelpers.IsFtpPath(path) && FtpHelpers.VerifyFtpPath(path)) + { + return Task.FromResult(new FtpStorageFile(new StorageFileWithPath(null, path))).AsAsyncOperation(); + } + return Task.FromResult(null).AsAsyncOperation(); + } + + public override string DisplayName => Name; + + public override string DisplayType + { + get + { + var itemType = "ItemTypeFile".GetLocalized(); + if (Name.Contains(".")) + { + itemType = System.IO.Path.GetExtension(Name).Trim('.') + " " + itemType; + } + return itemType; + } + } + + public override string FolderRelativeId => $"0\\{Name}"; + + public override IStorageItemExtraProperties Properties => new BaseBasicStorageItemExtraProperties(this); + + private class FtpFileBasicProperties : BaseBasicProperties + { + public FtpFileBasicProperties(FtpItem item) + { + DateModified = item.ItemDateModifiedReal; + ItemDate = item.ItemDateCreatedReal; + Size = (ulong)item.FileSizeBytes; + } + + public FtpFileBasicProperties(FtpListItem item) + { + DateModified = item.RawModified < DateTime.FromFileTimeUtc(0) ? DateTimeOffset.MinValue : item.RawModified; + ItemDate = item.RawCreated < DateTime.FromFileTimeUtc(0) ? DateTimeOffset.MinValue : item.RawCreated; + Size = (ulong)item.Size; + } + + public override DateTimeOffset DateModified { get; } + + public override DateTimeOffset ItemDate { get; } + + public override ulong Size { get; } + } } } \ No newline at end of file diff --git a/Files/Filesystem/StorageItems/FtpStorageFolder.cs b/Files/Filesystem/StorageItems/FtpStorageFolder.cs index bc34c0f3b7a5..90485d6905a6 100644 --- a/Files/Filesystem/StorageItems/FtpStorageFolder.cs +++ b/Files/Filesystem/StorageItems/FtpStorageFolder.cs @@ -1,5 +1,5 @@ -using Files.Helpers; -using Files.ViewModels; +using Files.Common; +using Files.Helpers; using FluentFTP; using System; using System.Collections.Generic; @@ -8,25 +8,37 @@ using Windows.Foundation; using Windows.Storage; using Windows.Storage.FileProperties; +using System; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Collections.Generic; +using System.IO; +using Windows.Storage.Search; +using System.Threading.Tasks; +using System.Linq; +using Microsoft.Toolkit.Uwp; namespace Files.Filesystem.StorageItems { - internal sealed class FtpStorageFolder : IStorageFolder + public sealed class FtpStorageFolder : BaseStorageFolder { - private readonly ItemViewModel _viewModel; - - public FtpStorageFolder(ItemViewModel viewModel, FtpItem ftpItem) + public FtpStorageFolder(FtpItem ftpItem) { - _viewModel = viewModel; DateCreated = ftpItem.ItemDateCreatedReal; Name = ftpItem.ItemName; Path = ftpItem.ItemPath; FtpPath = FtpHelpers.GetFtpPath(ftpItem.ItemPath); } - public FtpStorageFolder(ItemViewModel viewModel, IStorageItemWithPath item) + public FtpStorageFolder(string folder, FtpListItem ftpItem) + { + DateCreated = ftpItem.RawCreated < DateTime.FromFileTimeUtc(0) ? DateTimeOffset.MinValue : ftpItem.RawCreated; + Name = ftpItem.Name; + Path = PathNormalization.Combine(folder, ftpItem.Name); + FtpPath = FtpHelpers.GetFtpPath(Path); + } + + public FtpStorageFolder(IStorageItemWithPath item) { - _viewModel = viewModel; Name = System.IO.Path.GetFileName(item.Path); Path = item.Path; FtpPath = FtpHelpers.GetFtpPath(item.Path); @@ -34,31 +46,39 @@ public FtpStorageFolder(ItemViewModel viewModel, IStorageItemWithPath item) public FtpStorageFolder CloneWithPath(string path) { - return new FtpStorageFolder(_viewModel, new StorageFolderWithPath(null, path)); + return new FtpStorageFolder(new StorageFolderWithPath(null, path)); } - public IAsyncOperation UploadFileAsync(IStorageFile sourceFile, string desiredNewName, NameCollisionOption option) + public override IAsyncOperation CreateFileAsync(string desiredName) { - return AsyncInfo.Run(async (cancellationToken) => + return CreateFileAsync(desiredName, CreationCollisionOption.FailIfExists); + } + + public override IAsyncOperation CreateFileAsync(string desiredName, CreationCollisionOption options) + { + return AsyncInfo.Run(async (cancellationToken) => { - var ftpClient = _viewModel.GetFtpInstance(); + using var ftpClient = new FtpClient(); + ftpClient.Host = FtpHelpers.GetFtpHost(Path); + ftpClient.Port = FtpHelpers.GetFtpPort(Path); + ftpClient.Credentials = FtpManager.Credentials.Get(ftpClient.Host, FtpManager.Anonymous); if (!await ftpClient.EnsureConnectedAsync()) { return null; } - using var stream = await sourceFile.OpenStreamForReadAsync(); - var result = await ftpClient.UploadAsync(stream, $"{FtpPath}/{desiredNewName}", option == NameCollisionOption.ReplaceExisting ? FtpRemoteExists.Overwrite : FtpRemoteExists.Skip); + using var stream = new MemoryStream(); + var result = await ftpClient.UploadAsync(stream, $"{FtpPath}/{desiredName}", options == CreationCollisionOption.ReplaceExisting ? FtpRemoteExists.Overwrite : FtpRemoteExists.Skip); if (result == FtpStatus.Success) { - return new FtpStorageFile(_viewModel, new StorageFileWithPath(null, $"{Path}/{desiredNewName}")); + return new FtpStorageFile(new StorageFileWithPath(null, $"{Path}/{desiredName}")); } if (result == FtpStatus.Skipped) { - if (option == NameCollisionOption.FailIfExists) + if (options == CreationCollisionOption.FailIfExists) { throw new IOException("File already exists."); } @@ -66,24 +86,24 @@ public IAsyncOperation UploadFileAsync(IStorageFile sourceFile, return null; } - throw new IOException($"Failed to copy file {sourceFile.Path}."); + throw new IOException($"Failed to create file {FtpPath}/{desiredName}."); }); } - public IAsyncOperation CreateFileAsync(string desiredName) => throw new NotSupportedException(); - - public IAsyncOperation CreateFileAsync(string desiredName, CreationCollisionOption options) => throw new NotSupportedException(); - - public IAsyncOperation CreateFolderAsync(string desiredName) + public override IAsyncOperation CreateFolderAsync(string desiredName) { return CreateFolderAsync(desiredName, CreationCollisionOption.FailIfExists); } - public IAsyncOperation CreateFolderAsync(string desiredName, CreationCollisionOption options) + public override IAsyncOperation CreateFolderAsync(string desiredName, CreationCollisionOption options) { - return AsyncInfo.Run(async (cancellationToken) => + return AsyncInfo.Run(async (cancellationToken) => { - var ftpClient = _viewModel.GetFtpInstance(); + using var ftpClient = new FtpClient(); + ftpClient.Host = FtpHelpers.GetFtpHost(Path); + ftpClient.Port = FtpHelpers.GetFtpPort(Path); + ftpClient.Credentials = FtpManager.Credentials.Get(ftpClient.Host, FtpManager.Anonymous); + if (!await ftpClient.EnsureConnectedAsync()) { throw new IOException($"Failed to connect to FTP server."); @@ -91,7 +111,7 @@ public IAsyncOperation CreateFolderAsync(string desiredName, Crea if (ftpClient.DirectoryExists($"{FtpPath}/{desiredName}")) { - return null; + return new FtpStorageFolder(new StorageFileWithPath(null, $"{Path}/{desiredName}")); } if (!await ftpClient.CreateDirectoryAsync($"{FtpPath}/{desiredName}", @@ -101,68 +121,124 @@ public IAsyncOperation CreateFolderAsync(string desiredName, Crea throw new IOException($"Failed to create folder {desiredName}."); } - return null; + return new FtpStorageFolder(new StorageFileWithPath(null, $"{Path}/{desiredName}")); }); } - private StreamedFileDataRequestedHandler FtpDataStreamingHandler(string name) + public override IAsyncOperation GetFileAsync(string name) { - return async request => + return AsyncInfo.Run(async (cancellationToken) => { - try - { - var ftpClient = _viewModel.GetFtpInstance(); + return await GetItemAsync(name) as BaseStorageFile; + }); + } - if (!await ftpClient.EnsureConnectedAsync()) - { - request.FailAndClose(StreamedFileFailureMode.CurrentlyUnavailable); - return; - } + public override IAsyncOperation GetFolderAsync(string name) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return await GetItemAsync(name) as BaseStorageFolder; + }); + } - using var stream = request.AsStreamForWrite(); - await ftpClient.DownloadAsync(stream, $"{Path}/{name}"); - await request.FlushAsync(); - request.Dispose(); + public override IAsyncOperation GetItemAsync(string name) + { + return AsyncInfo.Run(async (cancellationToken) => + { + using var ftpClient = new FtpClient(); + ftpClient.Host = FtpHelpers.GetFtpHost(Path); + ftpClient.Port = FtpHelpers.GetFtpPort(Path); + ftpClient.Credentials = FtpManager.Credentials.Get(ftpClient.Host, FtpManager.Anonymous); + + if (!await ftpClient.EnsureConnectedAsync()) + { + return null; } - catch + + var item = await ftpClient.GetObjectInfoAsync(FtpHelpers.GetFtpPath(PathNormalization.Combine(Path, name))); + if (item != null) { - request.FailAndClose(StreamedFileFailureMode.Incomplete); + if (item.Type == FtpFileSystemObjectType.File) + { + return new FtpStorageFile(Path, item); + } + else if (item.Type == FtpFileSystemObjectType.Directory) + { + return new FtpStorageFolder(Path, item); + } } - }; + return null; + }); } - public IAsyncOperation GetFileAsync(string name) + public override IAsyncOperation> GetFilesAsync() { - return StorageFile.CreateStreamedFileAsync(name, FtpDataStreamingHandler(name), null); + return AsyncInfo.Run>(async (cancellationToken) => + { + return (await GetItemsAsync())?.OfType().ToList(); + }); } - public IAsyncOperation GetFolderAsync(string name) => throw new NotSupportedException(); - - public IAsyncOperation GetItemAsync(string name) => throw new NotSupportedException(); + public override IAsyncOperation> GetFoldersAsync() + { + return AsyncInfo.Run>(async (cancellationToken) => + { + return (await GetItemsAsync())?.OfType().ToList(); + }); + } - public IAsyncOperation> GetFilesAsync() => throw new NotSupportedException(); + public override IAsyncOperation> GetItemsAsync() + { + return AsyncInfo.Run(async (cancellationToken) => + { + using var ftpClient = new FtpClient(); + ftpClient.Host = FtpHelpers.GetFtpHost(Path); + ftpClient.Port = FtpHelpers.GetFtpPort(Path); + ftpClient.Credentials = FtpManager.Credentials.Get(ftpClient.Host, FtpManager.Anonymous); - public IAsyncOperation> GetFoldersAsync() => throw new NotSupportedException(); + if (!await ftpClient.EnsureConnectedAsync()) + { + return null; + } - public IAsyncOperation> GetItemsAsync() => throw new NotSupportedException(); + var items = new List(); + var list = await ftpClient.GetListingAsync(FtpPath); + foreach (var item in list) + { + if (item.Type == FtpFileSystemObjectType.File) + { + items.Add(new FtpStorageFile(Path, item)); + } + else if (item.Type == FtpFileSystemObjectType.Directory) + { + items.Add(new FtpStorageFolder(Path, item)); + } + }; + return (IReadOnlyList)items; + }); + } - public IAsyncAction RenameAsync(string desiredName) + public override IAsyncAction RenameAsync(string desiredName) { return RenameAsync(desiredName, NameCollisionOption.FailIfExists); } - public IAsyncAction RenameAsync(string desiredName, NameCollisionOption option) + public override IAsyncAction RenameAsync(string desiredName, NameCollisionOption option) { return AsyncInfo.Run(async (cancellationToken) => { - var ftpClient = _viewModel.GetFtpInstance(); + using var ftpClient = new FtpClient(); + ftpClient.Host = FtpHelpers.GetFtpHost(Path); + ftpClient.Port = FtpHelpers.GetFtpPort(Path); + ftpClient.Credentials = FtpManager.Credentials.Get(ftpClient.Host, FtpManager.Anonymous); + if (!await ftpClient.EnsureConnectedAsync()) { return; } if (!await ftpClient.MoveDirectoryAsync(FtpPath, - $"{FtpHelpers.GetFtpDirectoryName(FtpPath)}/{desiredName}", + $"{PathNormalization.GetParentDir(FtpPath)}/{desiredName}", option == NameCollisionOption.ReplaceExisting ? FtpRemoteExists.Overwrite : FtpRemoteExists.Skip, cancellationToken)) { @@ -174,11 +250,15 @@ public IAsyncAction RenameAsync(string desiredName, NameCollisionOption option) }); } - public IAsyncAction DeleteAsync() + public override IAsyncAction DeleteAsync() { return AsyncInfo.Run(async (cancellationToken) => { - var ftpClient = _viewModel.GetFtpInstance(); + using var ftpClient = new FtpClient(); + ftpClient.Host = FtpHelpers.GetFtpHost(Path); + ftpClient.Port = FtpHelpers.GetFtpPort(Path); + ftpClient.Credentials = FtpManager.Credentials.Get(ftpClient.Host, FtpManager.Anonymous); + if (!await ftpClient.EnsureConnectedAsync()) { return; @@ -188,23 +268,208 @@ public IAsyncAction DeleteAsync() }); } - public IAsyncAction DeleteAsync(StorageDeleteOption option) + public override IAsyncAction DeleteAsync(StorageDeleteOption option) { return DeleteAsync(); } - public IAsyncOperation GetBasicPropertiesAsync() => throw new NotSupportedException(); + public override IAsyncOperation GetBasicPropertiesAsync() + { + return AsyncInfo.Run(async (cancellationToken) => + { + using var ftpClient = new FtpClient(); + ftpClient.Host = FtpHelpers.GetFtpHost(Path); + ftpClient.Port = FtpHelpers.GetFtpPort(Path); + ftpClient.Credentials = FtpManager.Credentials.Get(ftpClient.Host, FtpManager.Anonymous); + + if (!await ftpClient.EnsureConnectedAsync()) + { + return new BaseBasicProperties(); + } + + var item = await ftpClient.GetObjectInfoAsync(FtpPath); + if (item != null) + { + return new FtpFolderBasicProperties(item); + } + return new BaseBasicProperties(); + }); + } - public bool IsOfType(StorageItemTypes type) => type == StorageItemTypes.Folder; + public override bool IsOfType(StorageItemTypes type) => type == StorageItemTypes.Folder; - public Windows.Storage.FileAttributes Attributes { get; } = Windows.Storage.FileAttributes.Directory; + public override Windows.Storage.FileAttributes Attributes { get; } = Windows.Storage.FileAttributes.Directory; - public DateTimeOffset DateCreated { get; } + public override DateTimeOffset DateCreated { get; } - public string Name { get; } + public override string Name { get; } - public string Path { get; } + public override string Path { get; } public string FtpPath { get; } + + public override IAsyncOperation GetIndexedStateAsync() + { + return Task.FromResult(IndexedState.NotIndexed).AsAsyncOperation(); + } + + public override StorageFileQueryResult CreateFileQuery() => throw new NotSupportedException(); + + public override StorageFileQueryResult CreateFileQuery(CommonFileQuery query) => throw new NotSupportedException(); + + public override BaseStorageFileQueryResult CreateFileQueryWithOptions(QueryOptions queryOptions) + { + return new BaseStorageFileQueryResult(this, queryOptions); + } + + public override StorageFolderQueryResult CreateFolderQuery() => throw new NotSupportedException(); + + public override StorageFolderQueryResult CreateFolderQuery(CommonFolderQuery query) => throw new NotSupportedException(); + + public override BaseStorageFolderQueryResult CreateFolderQueryWithOptions(QueryOptions queryOptions) + { + return new BaseStorageFolderQueryResult(this, queryOptions); + } + + public override StorageItemQueryResult CreateItemQuery() => throw new NotSupportedException(); + + public override BaseStorageItemQueryResult CreateItemQueryWithOptions(QueryOptions queryOptions) + { + return new BaseStorageItemQueryResult(this, queryOptions); + } + + public override IAsyncOperation> GetFilesAsync(CommonFileQuery query, uint startIndex, uint maxItemsToRetrieve) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await GetFilesAsync(); + return items.Skip((int)startIndex).Take((int)maxItemsToRetrieve).ToList(); + }); + } + + public override IAsyncOperation> GetFilesAsync(CommonFileQuery query) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + return await GetFilesAsync(); + }); + } + + public override IAsyncOperation> GetFoldersAsync(CommonFolderQuery query, uint startIndex, uint maxItemsToRetrieve) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await GetFoldersAsync(); + return items.Skip((int)startIndex).Take((int)maxItemsToRetrieve).ToList(); + }); + } + + public override IAsyncOperation> GetFoldersAsync(CommonFolderQuery query) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + return await GetFoldersAsync(); + }); + } + + public override IAsyncOperation> GetItemsAsync(uint startIndex, uint maxItemsToRetrieve) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await GetItemsAsync(); + return items.Skip((int)startIndex).Take((int)maxItemsToRetrieve).ToList(); + }); + } + + public override bool AreQueryOptionsSupported(QueryOptions queryOptions) => false; + + public override bool IsCommonFolderQuerySupported(CommonFolderQuery query) => false; + + public override bool IsCommonFileQuerySupported(CommonFileQuery query) => false; + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode) + { + return Task.FromResult(null).AsAsyncOperation(); + } + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode, uint requestedSize) + { + return Task.FromResult(null).AsAsyncOperation(); + } + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode, uint requestedSize, ThumbnailOptions options) + { + return Task.FromResult(null).AsAsyncOperation(); + } + + public override IAsyncOperation ToStorageFolderAsync() => throw new NotSupportedException(); + + public override IAsyncOperation GetParentAsync() + { + throw new NotSupportedException(); + } + + public override bool IsEqual(IStorageItem item) => item?.Path == Path; + + public override IAsyncOperation TryGetItemAsync(string name) + { + return AsyncInfo.Run(async (cancellationToken) => + { + try + { + return await GetItemAsync(name); + } + catch + { + return null; + } + }); + } + + public static IAsyncOperation FromPathAsync(string path) + { + if (FtpHelpers.IsFtpPath(path) && FtpHelpers.VerifyFtpPath(path)) + { + return Task.FromResult(new FtpStorageFolder(new StorageFolderWithPath(null, path))).AsAsyncOperation(); + } + return Task.FromResult(null).AsAsyncOperation(); + } + + public override string DisplayName => Name; + + public override string DisplayType + { + get + { + return "FileFolderListItem".GetLocalized(); + } + } + + public override string FolderRelativeId => $"0\\{Name}"; + + public override IStorageItemExtraProperties Properties => new BaseBasicStorageItemExtraProperties(this); + + private class FtpFolderBasicProperties : BaseBasicProperties + { + public FtpFolderBasicProperties(FtpItem item) + { + DateModified = item.ItemDateModifiedReal; + ItemDate = item.ItemDateCreatedReal; + Size = (ulong)item.FileSizeBytes; + } + + public FtpFolderBasicProperties(FtpListItem item) + { + DateModified = item.RawModified < DateTime.FromFileTimeUtc(0) ? DateTimeOffset.MinValue : item.RawModified; + ItemDate = item.RawCreated < DateTime.FromFileTimeUtc(0) ? DateTimeOffset.MinValue : item.RawCreated; + Size = (ulong)item.Size; + } + + public override DateTimeOffset DateModified { get; } + + public override DateTimeOffset ItemDate { get; } + + public override ulong Size { get; } + } } } \ No newline at end of file diff --git a/Files/Filesystem/StorageItems/StreamWithContentType.cs b/Files/Filesystem/StorageItems/StreamWithContentType.cs new file mode 100644 index 000000000000..0eb37247eaad --- /dev/null +++ b/Files/Filesystem/StorageItems/StreamWithContentType.cs @@ -0,0 +1,238 @@ +using System; +using System.IO; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.Storage.Streams; + +namespace Files.Filesystem.StorageItems +{ + public class InputStreamWithDisposeCallback : IInputStream + { + private IInputStream stream; + public Action DisposeCallback { get; set; } + + public InputStreamWithDisposeCallback(Stream stream) + { + this.stream = stream.AsInputStream(); + } + + public IAsyncOperationWithProgress ReadAsync(IBuffer buffer, uint count, InputStreamOptions options) + { + return stream.ReadAsync(buffer, count, options); + } + + public void Dispose() + { + stream.Dispose(); + DisposeCallback?.Invoke(); + } + } + + public class RandomAccessStreamWithFlushCallback : IRandomAccessStream + { + private IRandomAccessStream imrac; + private bool isWritten; + public Func> FlushCallback { get; set; } + public Action DisposeCallback { get; set; } + + public RandomAccessStreamWithFlushCallback() + { + this.imrac = new InMemoryRandomAccessStream(); + } + + public IInputStream GetInputStreamAt(ulong position) + { + return imrac.GetInputStreamAt(position); + } + + public IOutputStream GetOutputStreamAt(ulong position) + { + return imrac.GetOutputStreamAt(position); + } + + public void Seek(ulong position) + { + imrac.Seek(position); + } + + public IRandomAccessStream CloneStream() + { + return imrac.CloneStream(); + } + + public bool CanRead => imrac.CanRead; + + public bool CanWrite => imrac.CanWrite; + + public ulong Position => imrac.Position; + + public ulong Size { get => imrac.Size; set => imrac.Size = value; } + + public IAsyncOperationWithProgress ReadAsync(IBuffer buffer, uint count, InputStreamOptions options) + { + return imrac.ReadAsync(buffer, count, options); + } + + public IAsyncOperationWithProgress WriteAsync(IBuffer buffer) + { + return imrac.WriteAsync(buffer); + } + + public IAsyncOperation FlushAsync() + { + if (isWritten) + { + return imrac.FlushAsync(); + } + + isWritten = true; + + return FlushCallback(this) ?? imrac.FlushAsync(); + } + + public void Dispose() + { + imrac.Dispose(); + DisposeCallback?.Invoke(); + } + } + + public class NonSeekableRandomAccessStream : IRandomAccessStream + { + private Stream stream; + private IRandomAccessStream imrac; + private ulong virtualPosition; + private ulong readToByte; + private ulong byteSize; + + public Action DisposeCallback { get; set; } + + public NonSeekableRandomAccessStream(Stream baseStream, ulong size) + { + this.stream = baseStream; + this.imrac = new InMemoryRandomAccessStream(); + this.virtualPosition = 0; + this.readToByte = 0; + this.byteSize = size; + } + + public IInputStream GetInputStreamAt(ulong position) + { + throw new NotSupportedException(); + } + + public IOutputStream GetOutputStreamAt(ulong position) + { + throw new NotSupportedException(); + } + + public void Seek(ulong position) + { + imrac.Size = Math.Max(imrac.Size, position); + this.virtualPosition = position; + } + + public IRandomAccessStream CloneStream() => imrac.CloneStream(); + + public bool CanRead => imrac.CanRead; + + public bool CanWrite => false; + + public ulong Position => virtualPosition; + + public ulong Size + { + get => byteSize; + set => throw new NotSupportedException(); + } + + public IAsyncOperationWithProgress ReadAsync(IBuffer buffer, uint count, InputStreamOptions options) + { + Func, Task> taskProvider = + async (token, progress) => + { + int read; + var tempBuffer = new byte[16384]; + imrac.Seek(readToByte); + while (imrac.Position < virtualPosition + count) + { + read = await stream.ReadAsync(tempBuffer, 0, tempBuffer.Length); + if (read == 0) + { + break; + } + await imrac.WriteAsync(tempBuffer.AsBuffer(0, read)); + } + readToByte = imrac.Position; + + imrac.Seek(virtualPosition); + var res = await imrac.ReadAsync(buffer, count, options); + virtualPosition = imrac.Position; + return res; + }; + + return AsyncInfo.Run(taskProvider); + } + + public IAsyncOperationWithProgress WriteAsync(IBuffer buffer) + { + throw new NotSupportedException(); + } + + public IAsyncOperation FlushAsync() + { + return imrac.FlushAsync(); + } + + public void Dispose() + { + stream.Dispose(); + imrac.Dispose(); + DisposeCallback?.Invoke(); + } + } + + public class StreamWithContentType : IRandomAccessStreamWithContentType + { + private IRandomAccessStream baseStream; + + public StreamWithContentType(IRandomAccessStream stream) + { + baseStream = stream; + } + + public IInputStream GetInputStreamAt(ulong position) => baseStream.GetInputStreamAt(position); + + public IOutputStream GetOutputStreamAt(ulong position) => baseStream.GetOutputStreamAt(position); + + public void Seek(ulong position) => baseStream.Seek(position); + + public IRandomAccessStream CloneStream() => baseStream.CloneStream(); + + public bool CanRead => baseStream.CanRead; + + public bool CanWrite => baseStream.CanWrite; + + public ulong Position => baseStream.Position; + + public ulong Size { get => baseStream.Size; set => baseStream.Size = value; } + + public IAsyncOperationWithProgress ReadAsync(IBuffer buffer, uint count, InputStreamOptions options) + { + return baseStream.ReadAsync(buffer, count, options); + } + + public IAsyncOperationWithProgress WriteAsync(IBuffer buffer) => baseStream.WriteAsync(buffer); + + public IAsyncOperation FlushAsync() => baseStream.FlushAsync(); + + public void Dispose() + { + baseStream.Dispose(); + } + + public string ContentType { get; set; } = "application/octet-stream"; + } +} diff --git a/Files/Filesystem/StorageItems/SystemStorageFile.cs b/Files/Filesystem/StorageItems/SystemStorageFile.cs new file mode 100644 index 000000000000..f30559946da7 --- /dev/null +++ b/Files/Filesystem/StorageItems/SystemStorageFile.cs @@ -0,0 +1,240 @@ +using Files.Helpers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.Foundation.Metadata; +using Windows.Storage; +using Windows.Storage.FileProperties; +using Windows.Storage.Search; +using Windows.Storage.Streams; + +namespace Files.Filesystem.StorageItems +{ + public sealed class SystemStorageFile : BaseStorageFile + { + public StorageFile File { get; } + + public SystemStorageFile(StorageFile file) + { + File = file; + } + + public override IAsyncOperation OpenAsync(FileAccessMode accessMode) + { + return File.OpenAsync(accessMode); + } + + public override IAsyncOperation OpenTransactedWriteAsync() + { + return File.OpenTransactedWriteAsync(); + } + + public override IAsyncOperation CopyAsync(IStorageFolder destinationFolder) + { + return CopyAsync(destinationFolder, Name, NameCollisionOption.FailIfExists); + } + + public override IAsyncOperation CopyAsync(IStorageFolder destinationFolder, string desiredNewName) + { + return CopyAsync(destinationFolder, desiredNewName, NameCollisionOption.FailIfExists); + } + + public override IAsyncOperation CopyAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option) + { + return AsyncInfo.Run(async (cancellationToken) => + { + var destFolder = destinationFolder.AsBaseStorageFolder(); // Avoid calling IStorageFolder method + var destFile = await destFolder.CreateFileAsync(desiredNewName, option.Convert()); + using (var inStream = await this.OpenStreamForReadAsync()) + using (var outStream = await destFile.OpenStreamForWriteAsync()) + { + await inStream.CopyToAsync(outStream); + await outStream.FlushAsync(); + } + return destFile; + }); + } + + public override IAsyncAction CopyAndReplaceAsync(IStorageFile fileToReplace) + { + return AsyncInfo.Run(async (cancellationToken) => + { + using (var inStream = await this.OpenStreamForReadAsync()) + using (var outStream = await fileToReplace.OpenStreamForWriteAsync()) + { + await inStream.CopyToAsync(outStream); + await outStream.FlushAsync(); + } + }); + } + + public override IAsyncAction MoveAsync(IStorageFolder destinationFolder) + { + return File.MoveAsync(destinationFolder); + } + + public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName) + { + return File.MoveAsync(destinationFolder, desiredNewName); + } + + public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option) + { + return File.MoveAsync(destinationFolder, desiredNewName, option); + } + + public override IAsyncAction MoveAndReplaceAsync(IStorageFile fileToReplace) + { + return File.MoveAndReplaceAsync(fileToReplace); + } + + public override string ContentType => File.ContentType; + + public override string FileType => File.FileType; + + public override IAsyncAction RenameAsync(string desiredName) + { + return File.RenameAsync(desiredName); + } + + public override IAsyncAction RenameAsync(string desiredName, NameCollisionOption option) + { + return File.RenameAsync(desiredName, option); + } + + public override IAsyncAction DeleteAsync() + { + return File.DeleteAsync(); + } + + public override IAsyncAction DeleteAsync(StorageDeleteOption option) + { + return File.DeleteAsync(option); + } + + public override IAsyncOperation GetBasicPropertiesAsync() + { + return AsyncInfo.Run(async (cancellationToken) => + { + var basicProps = await File.GetBasicPropertiesAsync(); + return new SystemFileBasicProperties(basicProps); + }); + } + + public override bool IsOfType(StorageItemTypes type) + { + return File.IsOfType(type); + } + + public override Windows.Storage.FileAttributes Attributes => File.Attributes; + + public override DateTimeOffset DateCreated => File.DateCreated; + + public override string Name => File.Name; + + public override string Path => File.Path; + + public override IAsyncOperation OpenReadAsync() + { + return File.OpenReadAsync(); + } + + public override IAsyncOperation OpenSequentialReadAsync() + { + return File.OpenSequentialReadAsync(); + } + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode) + { + return File.GetThumbnailAsync(mode); + } + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode, uint requestedSize) + { + return File.GetThumbnailAsync(mode, requestedSize); + } + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode, uint requestedSize, ThumbnailOptions options) + { + return File.GetThumbnailAsync(mode, requestedSize, options); + } + + public override IAsyncOperation ToStorageFileAsync() + { + return Task.FromResult(File).AsAsyncOperation(); + } + + public override IAsyncOperation GetParentAsync() + { + return AsyncInfo.Run(async (cancellationToken) => + { + return new SystemStorageFolder(await File.GetParentAsync()); + }); + } + + public override bool IsEqual(IStorageItem item) => File.IsEqual(item); + + public override IAsyncOperation OpenAsync(FileAccessMode accessMode, StorageOpenOptions options) => File.OpenAsync(accessMode, options); + + public override IAsyncOperation OpenTransactedWriteAsync(StorageOpenOptions options) => File.OpenTransactedWriteAsync(options); + + public static IAsyncOperation FromPathAsync(string path) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return new SystemStorageFile(await StorageFile.GetFileFromPathAsync(path)); + }); + } + + public override string DisplayName => File.DisplayName; + + public override string DisplayType => File.DisplayType; + + public override string FolderRelativeId => File.FolderRelativeId; + + public override IStorageItemExtraProperties Properties => File.Properties; + + private class SystemFileBasicProperties : BaseBasicProperties + { + private IStorageItemExtraProperties basicProps; + + public SystemFileBasicProperties(IStorageItemExtraProperties basicProps) + { + this.basicProps = basicProps; + } + + public override DateTimeOffset DateModified + { + get => (basicProps as BasicProperties)?.DateModified ?? DateTimeOffset.Now; + } + + public override DateTimeOffset ItemDate + { + get => (basicProps as BasicProperties)?.ItemDate ?? DateTimeOffset.Now; + } + + public override ulong Size + { + get => (basicProps as BasicProperties)?.Size ?? 0; + } + + public override IAsyncOperation> RetrievePropertiesAsync(IEnumerable propertiesToRetrieve) + { + return basicProps.RetrievePropertiesAsync(propertiesToRetrieve); + } + + public override IAsyncAction SavePropertiesAsync([HasVariant] IEnumerable> propertiesToSave) + { + return basicProps.SavePropertiesAsync(propertiesToSave); + } + + public override IAsyncAction SavePropertiesAsync() + { + return basicProps.SavePropertiesAsync(); + } + } + } +} diff --git a/Files/Filesystem/StorageItems/SystemStorageFolder.cs b/Files/Filesystem/StorageItems/SystemStorageFolder.cs new file mode 100644 index 000000000000..ba641afbda29 --- /dev/null +++ b/Files/Filesystem/StorageItems/SystemStorageFolder.cs @@ -0,0 +1,328 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.Foundation.Metadata; +using Windows.Storage; +using Windows.Storage.FileProperties; +using Windows.Storage.Search; + +namespace Files.Filesystem.StorageItems +{ + public sealed class SystemStorageFolder : BaseStorageFolder + { + public StorageFolder Folder { get; } + + public SystemStorageFolder(StorageFolder folder) + { + Folder = folder; + } + + public override IAsyncOperation CreateFileAsync(string desiredName) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return new SystemStorageFile(await Folder.CreateFileAsync(desiredName)); + }); + } + + public override IAsyncOperation CreateFileAsync(string desiredName, CreationCollisionOption options) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return new SystemStorageFile(await Folder.CreateFileAsync(desiredName, options)); + }); + } + + public override IAsyncOperation CreateFolderAsync(string desiredName) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return new SystemStorageFolder(await Folder.CreateFolderAsync(desiredName)); + }); + } + + public override IAsyncOperation CreateFolderAsync(string desiredName, CreationCollisionOption options) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return new SystemStorageFolder(await Folder.CreateFolderAsync(desiredName, options)); + }); + } + + public override IAsyncOperation GetFileAsync(string name) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return new SystemStorageFile(await Folder.GetFileAsync(name)); + }); + } + + public override IAsyncOperation GetFolderAsync(string name) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return new SystemStorageFolder(await Folder.GetFolderAsync(name)); + }); + } + + public override IAsyncOperation GetItemAsync(string name) + { + return Folder.GetItemAsync(name); + } + + public override IAsyncOperation> GetFilesAsync() + { + return AsyncInfo.Run>(async (cancellationToken) => + { + return (await Folder.GetFilesAsync()).Select(item => new SystemStorageFile(item)).ToList(); + }); + } + + public override IAsyncOperation> GetFoldersAsync() + { + return AsyncInfo.Run>(async (cancellationToken) => + { + return (await Folder.GetFoldersAsync()).Select(item => new SystemStorageFolder(item)).ToList(); + }); + } + + public override IAsyncOperation> GetItemsAsync() + { + return Folder.GetItemsAsync(); + } + + public override IAsyncAction RenameAsync(string desiredName) + { + return Folder.RenameAsync(desiredName); + } + + public override IAsyncAction RenameAsync(string desiredName, NameCollisionOption option) + { + return Folder.RenameAsync(desiredName, option); + } + + public override IAsyncAction DeleteAsync() + { + return Folder.DeleteAsync(); + } + + public override IAsyncAction DeleteAsync(StorageDeleteOption option) + { + return Folder.DeleteAsync(option); + } + + public override IAsyncOperation GetBasicPropertiesAsync() + { + return AsyncInfo.Run(async (cancellationToken) => + { + var basicProps = await Folder.GetBasicPropertiesAsync(); + return new SystemFolderBasicProperties(basicProps); + }); + } + + public override bool IsOfType(StorageItemTypes type) + { + return Folder.IsOfType(type); + } + + public override FileAttributes Attributes => Folder.Attributes; + + public override DateTimeOffset DateCreated => Folder.DateCreated; + + public override string Name => Folder.Name; + + public override string Path => Folder.Path; + + public override IAsyncOperation GetIndexedStateAsync() + { + return Folder.GetIndexedStateAsync(); + } + + public override StorageFileQueryResult CreateFileQuery() + { + return Folder.CreateFileQuery(); + } + + public override StorageFileQueryResult CreateFileQuery(CommonFileQuery query) + { + return Folder.CreateFileQuery(query); + } + + public override BaseStorageFileQueryResult CreateFileQueryWithOptions(QueryOptions queryOptions) + { + return new SystemStorageFileQueryResult(Folder.CreateFileQueryWithOptions(queryOptions)); + } + + public override StorageFolderQueryResult CreateFolderQuery() + { + return Folder.CreateFolderQuery(); + } + + public override StorageFolderQueryResult CreateFolderQuery(CommonFolderQuery query) + { + return Folder.CreateFolderQuery(query); + } + + public override BaseStorageFolderQueryResult CreateFolderQueryWithOptions(QueryOptions queryOptions) + { + return new SystemStorageFolderQueryResult(Folder.CreateFolderQueryWithOptions(queryOptions)); + } + + public override StorageItemQueryResult CreateItemQuery() + { + return Folder.CreateItemQuery(); + } + + public override BaseStorageItemQueryResult CreateItemQueryWithOptions(QueryOptions queryOptions) + { + return new SystemStorageItemQueryResult(Folder.CreateItemQueryWithOptions(queryOptions)); + } + + public override IAsyncOperation> GetFilesAsync(CommonFileQuery query, uint startIndex, uint maxItemsToRetrieve) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await Folder.GetFilesAsync(query, startIndex, maxItemsToRetrieve); + return items.Select(x => new SystemStorageFile(x)).ToList(); + }); + } + + public override IAsyncOperation> GetFilesAsync(CommonFileQuery query) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await Folder.GetFilesAsync(query); + return items.Select(x => new SystemStorageFile(x)).ToList(); + }); + } + + public override IAsyncOperation> GetFoldersAsync(CommonFolderQuery query, uint startIndex, uint maxItemsToRetrieve) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await Folder.GetFoldersAsync(query, startIndex, maxItemsToRetrieve); + return items.Select(x => new SystemStorageFolder(x)).ToList(); + }); + } + + public override IAsyncOperation> GetFoldersAsync(CommonFolderQuery query) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await Folder.GetFoldersAsync(query); + return items.Select(x => new SystemStorageFolder(x)).ToList(); + }); + } + + public override IAsyncOperation> GetItemsAsync(uint startIndex, uint maxItemsToRetrieve) + { + return Folder.GetItemsAsync(startIndex, maxItemsToRetrieve); + } + + public override bool AreQueryOptionsSupported(QueryOptions queryOptions) + { + return Folder.AreQueryOptionsSupported(queryOptions); + } + + public override bool IsCommonFolderQuerySupported(CommonFolderQuery query) + { + return Folder.IsCommonFolderQuerySupported(query); + } + + public override bool IsCommonFileQuerySupported(CommonFileQuery query) + { + return Folder.IsCommonFileQuerySupported(query); + } + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode) + { + return Folder.GetThumbnailAsync(mode); + } + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode, uint requestedSize) + { + return Folder.GetThumbnailAsync(mode, requestedSize); + } + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode, uint requestedSize, ThumbnailOptions options) + { + return Folder.GetThumbnailAsync(mode, requestedSize, options); + } + + public override IAsyncOperation ToStorageFolderAsync() + { + return Task.FromResult(Folder).AsAsyncOperation(); + } + + public override IAsyncOperation GetParentAsync() + { + return AsyncInfo.Run(async (cancellationToken) => + { + return new SystemStorageFolder(await Folder.GetParentAsync()); + }); + } + + public override bool IsEqual(IStorageItem item) => Folder.IsEqual(item); + + public override IAsyncOperation TryGetItemAsync(string name) => Folder.TryGetItemAsync(name); + + public static IAsyncOperation FromPathAsync(string path) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return new SystemStorageFolder(await StorageFolder.GetFolderFromPathAsync(path)); + }); + } + + public override string DisplayName => Folder.DisplayName; + + public override string DisplayType => Folder.DisplayType; + + public override string FolderRelativeId => Folder.FolderRelativeId; + + public override IStorageItemExtraProperties Properties => Folder.Properties; + + private class SystemFolderBasicProperties : BaseBasicProperties + { + private IStorageItemExtraProperties basicProps; + + public SystemFolderBasicProperties(IStorageItemExtraProperties basicProps) + { + this.basicProps = basicProps; + } + + public override DateTimeOffset DateModified + { + get => (basicProps as BasicProperties)?.DateModified ?? DateTimeOffset.Now; + } + + public override DateTimeOffset ItemDate + { + get => (basicProps as BasicProperties)?.ItemDate ?? DateTimeOffset.Now; + } + + public override ulong Size + { + get => (basicProps as BasicProperties)?.Size ?? 0; + } + + public override IAsyncOperation> RetrievePropertiesAsync(IEnumerable propertiesToRetrieve) + { + return basicProps.RetrievePropertiesAsync(propertiesToRetrieve); + } + + public override IAsyncAction SavePropertiesAsync([HasVariant] IEnumerable> propertiesToSave) + { + return basicProps.SavePropertiesAsync(propertiesToSave); + } + + public override IAsyncAction SavePropertiesAsync() + { + return basicProps.SavePropertiesAsync(); + } + } + } +} diff --git a/Files/Filesystem/StorageItems/ZipStorageFile.cs b/Files/Filesystem/StorageItems/ZipStorageFile.cs new file mode 100644 index 000000000000..4853722a5819 --- /dev/null +++ b/Files/Filesystem/StorageItems/ZipStorageFile.cs @@ -0,0 +1,479 @@ +using Files.Helpers; +using ICSharpCode.SharpZipLib.Zip; +using Microsoft.Toolkit.Uwp; +using System; +using System.IO; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.Storage; +using Windows.Storage.FileProperties; +using Windows.Storage.Streams; + +namespace Files.Filesystem.StorageItems +{ + public sealed class ZipStorageFile : BaseStorageFile + { + public ZipStorageFile(string path) + { + Name = System.IO.Path.GetFileName(path.TrimEnd('\\', '/')); + Path = path; + ContainerPath = path; + } + + public ZipStorageFile(string path, string containerPath) + { + Name = System.IO.Path.GetFileName(path.TrimEnd('\\', '/')); + Path = path; + ContainerPath = containerPath; + } + + public ZipStorageFile(string path, string containerPath, ZipEntry entry) + { + Name = System.IO.Path.GetFileName(path.TrimEnd('\\', '/')); + Path = path; + ContainerPath = containerPath; + DateCreated = entry.DateTime; + } + + public override IAsyncOperation ToStorageFileAsync() + { + return StorageFile.CreateStreamedFileAsync(Name, ZipDataStreamingHandler(Path), null); + } + + private StreamedFileDataRequestedHandler ZipDataStreamingHandler(string name) + { + return async request => + { + try + { + // If called from here it fails with Access Denied?! + //var hFile = NativeFileOperationsHelper.OpenFileForRead(ContainerPath); + var hFile = await NativeFileOperationsHelper.OpenProtectedFileForRead(ContainerPath); + if (hFile.IsInvalid) + { + request.FailAndClose(StreamedFileFailureMode.CurrentlyUnavailable); + return; + } + using (ZipFile zipFile = new ZipFile(new FileStream(hFile, FileAccess.Read))) + { + zipFile.IsStreamOwner = true; + var znt = new ZipNameTransform(ContainerPath); + var entry = zipFile.GetEntry(znt.TransformFile(name)); + if (entry != null) + { + using (var inStream = zipFile.GetInputStream(entry)) + using (var outStream = request.AsStreamForWrite()) + { + await inStream.CopyToAsync(outStream); + await outStream.FlushAsync(); + } + request.Dispose(); + } + else + { + request.FailAndClose(StreamedFileFailureMode.CurrentlyUnavailable); + } + } + } + catch + { + request.FailAndClose(StreamedFileFailureMode.Failed); + } + }; + } + + public override IAsyncOperation OpenAsync(FileAccessMode accessMode) + { + return AsyncInfo.Run(async (cancellationToken) => + { + bool rw = accessMode == FileAccessMode.ReadWrite; + var hFile = NativeFileOperationsHelper.OpenFileForRead(ContainerPath, rw); + if (hFile.IsInvalid) + { + return null; + } + if (Path == ContainerPath) + { + return new FileStream(hFile, FileAccess.Read).AsRandomAccessStream(); + } + + ZipFile zipFile = new ZipFile(new FileStream(hFile, rw ? FileAccess.ReadWrite : FileAccess.Read)); + zipFile.IsStreamOwner = true; + var znt = new ZipNameTransform(ContainerPath); + var entry = zipFile.GetEntry(znt.TransformFile(Path)); + if (!rw) + { + if (entry != null) + { + return new NonSeekableRandomAccessStream(zipFile.GetInputStream(entry), (ulong)entry.Size) + { + DisposeCallback = () => zipFile.Close() + }; + } + } + else + { + return new RandomAccessStreamWithFlushCallback() + { + DisposeCallback = () => zipFile.Close(), + FlushCallback = WriteZipEntry(zipFile) + }; + } + return null; + }); + } + + private Func> WriteZipEntry(ZipFile zipFile) + { + return (stream) => AsyncInfo.Run((cancellationToken) => Task.Run(() => + { + var hFile = NativeFileOperationsHelper.OpenFileForRead(ContainerPath, true); + if (hFile.IsInvalid) + { + return true; + } + try + { + var znt = new ZipNameTransform(ContainerPath); + var zipDesiredName = znt.TransformFile(Path); + var entry = zipFile.GetEntry(zipDesiredName); + + zipFile.BeginUpdate(new MemoryArchiveStorage(FileUpdateMode.Direct)); + if (entry != null) + { + zipFile.Delete(entry); + } + zipFile.Add(new StreamDataSource(stream), zipDesiredName); + zipFile.CommitUpdate(); + } + catch (Exception ex) + { + App.Logger.Warn(ex, "Error writing zip file"); + } + return true; + })); + } + + public override IAsyncOperation OpenTransactedWriteAsync() => throw new NotSupportedException(); + + public override IAsyncOperation CopyAsync(IStorageFolder destinationFolder) + { + return CopyAsync(destinationFolder, Name, NameCollisionOption.FailIfExists); + } + + public override IAsyncOperation CopyAsync(IStorageFolder destinationFolder, string desiredNewName) + { + return CopyAsync(destinationFolder, desiredNewName, NameCollisionOption.FailIfExists); + } + + public override IAsyncOperation CopyAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option) + { + return AsyncInfo.Run(async (cancellationToken) => + { + var hFile = NativeFileOperationsHelper.OpenFileForRead(ContainerPath); + if (hFile.IsInvalid) + { + return null; + } + using (ZipFile zipFile = new ZipFile(new FileStream(hFile, FileAccess.Read))) + { + zipFile.IsStreamOwner = true; + var znt = new ZipNameTransform(ContainerPath); + var entry = zipFile.GetEntry(znt.TransformFile(Path)); + if (entry != null) + { + var destFolder = destinationFolder.AsBaseStorageFolder(); + var destFile = await destFolder.CreateFileAsync(desiredNewName, option.Convert()); + using (var inStream = zipFile.GetInputStream(entry)) + using (var outStream = await destFile.OpenStreamForWriteAsync()) + { + await inStream.CopyToAsync(outStream); + await outStream.FlushAsync(); + } + return destFile; + } + return null; + } + }); + } + + public override IAsyncAction CopyAndReplaceAsync(IStorageFile fileToReplace) + { + return AsyncInfo.Run(async (cancellationToken) => + { + var hFile = NativeFileOperationsHelper.OpenFileForRead(ContainerPath); + if (hFile.IsInvalid) + { + return; + } + using (ZipFile zipFile = new ZipFile(new FileStream(hFile, FileAccess.Read))) + { + zipFile.IsStreamOwner = true; + var znt = new ZipNameTransform(ContainerPath); + var entry = zipFile.GetEntry(znt.TransformFile(Path)); + if (entry != null) + { + using var hDestFile = fileToReplace.CreateSafeFileHandle(FileAccess.ReadWrite); + using (var inStream = zipFile.GetInputStream(entry)) + using (var outStream = new FileStream(hDestFile, FileAccess.Write)) + { + await inStream.CopyToAsync(outStream); + await outStream.FlushAsync(); + } + } + } + }); + } + + public override IAsyncAction MoveAsync(IStorageFolder destinationFolder) + { + throw new NotSupportedException(); + } + + public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName) + { + throw new NotSupportedException(); + } + + public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option) + { + throw new NotSupportedException(); + } + + public override IAsyncAction MoveAndReplaceAsync(IStorageFile fileToReplace) + { + throw new NotSupportedException(); + } + + public override string ContentType => "application/octet-stream"; + + public override string FileType => System.IO.Path.GetExtension(Name); + + public override IAsyncAction RenameAsync(string desiredName) + { + throw new NotSupportedException(); + } + + public override IAsyncAction RenameAsync(string desiredName, NameCollisionOption option) + { + throw new NotSupportedException(); + } + + public override IAsyncAction DeleteAsync() + { + throw new NotSupportedException(); + } + + public override IAsyncAction DeleteAsync(StorageDeleteOption option) + { + throw new NotSupportedException(); + } + + public override IAsyncOperation GetBasicPropertiesAsync() + { + return Task.FromResult(GetBasicProperties()).AsAsyncOperation(); + } + + private BaseBasicProperties GetBasicProperties() + { + var hFile = NativeFileOperationsHelper.OpenFileForRead(ContainerPath); + if (hFile.IsInvalid) + { + return new BaseBasicProperties(); + } + using (ZipFile zipFile = new ZipFile(new FileStream(hFile, FileAccess.Read))) + { + zipFile.IsStreamOwner = true; + var znt = new ZipNameTransform(ContainerPath); + var entry = zipFile.GetEntry(znt.TransformFile(Path)); + if (entry != null) + { + return new ZipFileBasicProperties(entry); + } + return new BaseBasicProperties(); + } + } + + public override bool IsOfType(StorageItemTypes type) => type == StorageItemTypes.File; + + public override Windows.Storage.FileAttributes Attributes => Windows.Storage.FileAttributes.Normal | Windows.Storage.FileAttributes.ReadOnly; + + public override DateTimeOffset DateCreated { get; } + + public override string Name { get; } + + public override string Path { get; } + + public string ContainerPath { get; private set; } + + public override IAsyncOperation OpenReadAsync() + { + return AsyncInfo.Run(async (cancellationToken) => + { + var hFile = NativeFileOperationsHelper.OpenFileForRead(ContainerPath); + if (hFile.IsInvalid) + { + return null; + } + if (Path == ContainerPath) + { + return new StreamWithContentType(new FileStream(hFile, FileAccess.Read).AsRandomAccessStream()); + } + + ZipFile zipFile = new ZipFile(new FileStream(hFile, FileAccess.Read)); + zipFile.IsStreamOwner = true; + var znt = new ZipNameTransform(ContainerPath); + var entry = zipFile.GetEntry(znt.TransformFile(Path)); + if (entry != null) + { + var nsStream = new NonSeekableRandomAccessStream(zipFile.GetInputStream(entry), (ulong)entry.Size) + { + DisposeCallback = () => zipFile.Close() + }; + return new StreamWithContentType(nsStream); + } + return null; + }); + } + + public override IAsyncOperation OpenSequentialReadAsync() + { + return AsyncInfo.Run(async (cancellationToken) => + { + var hFile = NativeFileOperationsHelper.OpenFileForRead(ContainerPath); + if (hFile.IsInvalid) + { + return null; + } + if (Path == ContainerPath) + { + return new FileStream(hFile, FileAccess.Read).AsInputStream(); + } + + ZipFile zipFile = new ZipFile(new FileStream(hFile, FileAccess.Read)); + zipFile.IsStreamOwner = true; + var znt = new ZipNameTransform(ContainerPath); + var entry = zipFile.GetEntry(znt.TransformFile(Path)); + if (entry != null) + { + return new InputStreamWithDisposeCallback(zipFile.GetInputStream(entry)) { DisposeCallback = () => zipFile.Close() }; + } + return null; + }); + } + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode) + { + return Task.FromResult(null).AsAsyncOperation(); + } + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode, uint requestedSize) + { + return Task.FromResult(null).AsAsyncOperation(); + } + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode, uint requestedSize, ThumbnailOptions options) + { + return Task.FromResult(null).AsAsyncOperation(); + } + + public override IAsyncOperation GetParentAsync() + { + throw new NotSupportedException(); + } + + public override bool IsEqual(IStorageItem item) => item?.Path == Path; + + public override IAsyncOperation OpenAsync(FileAccessMode accessMode, StorageOpenOptions options) => OpenAsync(accessMode); + + public override IAsyncOperation OpenTransactedWriteAsync(StorageOpenOptions options) => throw new NotSupportedException(); + + public static IAsyncOperation FromPathAsync(string path) + { + var marker = path.IndexOf(".zip"); + if (marker != -1) + { + var containerPath = path.Substring(0, marker + ".zip".Length); + if (path == containerPath) + { + return Task.FromResult(null).AsAsyncOperation(); // Root + } + if (CheckAccess(containerPath)) + { + return Task.FromResult(new ZipStorageFile(path, containerPath)).AsAsyncOperation(); + } + } + return Task.FromResult(null).AsAsyncOperation(); + } + + private static bool CheckAccess(string path) + { + try + { + var hFile = NativeFileOperationsHelper.OpenFileForRead(path); + if (hFile.IsInvalid) + { + return false; + } + using (ZipFile zipFile = new ZipFile(new FileStream(hFile, FileAccess.Read))) + { + zipFile.IsStreamOwner = true; + } + return true; + } + catch + { + return false; + } + } + + public override string DisplayName => Name; + + public override string DisplayType + { + get + { + var itemType = "ItemTypeFile".GetLocalized(); + if (Name.Contains(".")) + { + itemType = System.IO.Path.GetExtension(Name).Trim('.') + " " + itemType; + } + return itemType; + } + } + + public override string FolderRelativeId => $"0\\{Name}"; + + public override IStorageItemExtraProperties Properties => new BaseBasicStorageItemExtraProperties(this); + + private class ZipFileBasicProperties : BaseBasicProperties + { + private ZipEntry zipEntry; + + public ZipFileBasicProperties(ZipEntry entry) + { + this.zipEntry = entry; + } + + public override DateTimeOffset DateModified => zipEntry.DateTime; + + public override DateTimeOffset ItemDate => zipEntry.DateTime; + + public override ulong Size => (ulong)zipEntry.Size; + } + + private class StreamDataSource : IStaticDataSource + { + private IRandomAccessStream stream; + + public StreamDataSource(IRandomAccessStream stream) + { + this.stream = stream; + } + + public Stream GetSource() => stream.CloneStream().AsStream(); + } + } +} diff --git a/Files/Filesystem/StorageItems/ZipStorageFolder.cs b/Files/Filesystem/StorageItems/ZipStorageFolder.cs new file mode 100644 index 000000000000..417e63451ca6 --- /dev/null +++ b/Files/Filesystem/StorageItems/ZipStorageFolder.cs @@ -0,0 +1,517 @@ +using Files.Helpers; +using ICSharpCode.SharpZipLib.Zip; +using Microsoft.Toolkit.Uwp; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.Storage; +using Windows.Storage.FileProperties; +using Windows.Storage.Search; + +namespace Files.Filesystem.StorageItems +{ + public sealed class ZipStorageFolder : BaseStorageFolder + { + public ZipStorageFolder(string path, string containerPath) + { + Name = System.IO.Path.GetFileName(path.TrimEnd('\\', '/')); + Path = path; + ContainerPath = containerPath; + } + + public ZipStorageFolder(string path, string containerPath, ZipEntry entry) + { + Name = System.IO.Path.GetFileName(path.TrimEnd('\\', '/')); + Path = path; + ContainerPath = containerPath; + DateCreated = entry.DateTime; + } + + public override IAsyncOperation CreateFileAsync(string desiredName) + { + return CreateFileAsync(desiredName, CreationCollisionOption.FailIfExists); + } + + public override IAsyncOperation CreateFileAsync(string desiredName, CreationCollisionOption options) + { + return AsyncInfo.Run(async (cancellationToken) => + { + var hFile = NativeFileOperationsHelper.OpenFileForRead(ContainerPath, true); + if (hFile.IsInvalid) + { + return null; + } + using (ZipFile zipFile = new ZipFile(new FileStream(hFile, FileAccess.ReadWrite))) + { + zipFile.IsStreamOwner = true; + + var znt = new ZipNameTransform(ContainerPath); + var zipDesiredName = znt.TransformFile(System.IO.Path.Combine(Path, desiredName)); + var entry = zipFile.GetEntry(zipDesiredName); + + zipFile.BeginUpdate(new MemoryArchiveStorage(FileUpdateMode.Direct)); + if (entry != null) + { + if (options != CreationCollisionOption.ReplaceExisting) + { + zipFile.AbortUpdate(); + return null; + } + zipFile.Delete(entry); + } + zipFile.Add(new FileDataSource() { Stream = new MemoryStream() }, zipDesiredName); + zipFile.CommitUpdate(); + + var wnt = new WindowsNameTransform(ContainerPath); + return new ZipStorageFile(wnt.TransformFile(zipDesiredName), ContainerPath); + } + }); + } + + public override IAsyncOperation CreateFolderAsync(string desiredName) + { + return CreateFolderAsync(desiredName, CreationCollisionOption.FailIfExists); + } + + public override IAsyncOperation CreateFolderAsync(string desiredName, CreationCollisionOption options) + { + return AsyncInfo.Run(async (cancellationToken) => + { + var hFile = NativeFileOperationsHelper.OpenFileForRead(ContainerPath, true); + if (hFile.IsInvalid) + { + return null; + } + using (ZipFile zipFile = new ZipFile(new FileStream(hFile, FileAccess.ReadWrite))) + { + zipFile.IsStreamOwner = true; + + var znt = new ZipNameTransform(ContainerPath); + var zipDesiredName = znt.TransformDirectory(System.IO.Path.Combine(Path, desiredName)); + var entry = zipFile.GetEntry(zipDesiredName); + + zipFile.BeginUpdate(new MemoryArchiveStorage(FileUpdateMode.Direct)); + if (entry != null) + { + if (options != CreationCollisionOption.ReplaceExisting) + { + zipFile.AbortUpdate(); + return null; + } + zipFile.Delete(entry); + } + zipFile.AddDirectory(zipDesiredName); + zipFile.CommitUpdate(); + + var wnt = new WindowsNameTransform(ContainerPath); + return new ZipStorageFolder(wnt.TransformFile(zipDesiredName), ContainerPath); + } + }); + } + + public override IAsyncOperation GetFileAsync(string name) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return await GetItemAsync(name) as ZipStorageFile; + }); + } + + public override IAsyncOperation GetFolderAsync(string name) + { + return AsyncInfo.Run(async (cancellationToken) => + { + return await GetItemAsync(name) as ZipStorageFolder; + }); + } + + public override IAsyncOperation GetItemAsync(string name) + { + return AsyncInfo.Run(async (cancellationToken) => + { + var hFile = NativeFileOperationsHelper.OpenFileForRead(ContainerPath); + if (hFile.IsInvalid) + { + return null; + } + using (ZipFile zipFile = new ZipFile(new FileStream(hFile, FileAccess.Read))) + { + zipFile.IsStreamOwner = true; + var entry = zipFile.GetEntry(name); + if (entry != null) + { + var wnt = new WindowsNameTransform(ContainerPath); + if (entry.IsDirectory) + { + return new ZipStorageFolder(wnt.TransformDirectory(entry.Name), ContainerPath, entry); + } + else + { + return new ZipStorageFile(wnt.TransformFile(entry.Name), ContainerPath, entry); + } + } + return null; + } + }); + } + + public override IAsyncOperation> GetFilesAsync() + { + return AsyncInfo.Run>(async (cancellationToken) => + { + return (await GetItemsAsync())?.OfType().ToList(); + }); + } + + public override IAsyncOperation> GetFoldersAsync() + { + return AsyncInfo.Run>(async (cancellationToken) => + { + return (await GetItemsAsync())?.OfType().ToList(); + }); + } + + public override IAsyncOperation> GetItemsAsync() + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var hFile = NativeFileOperationsHelper.OpenFileForRead(ContainerPath); + if (hFile.IsInvalid) + { + return null; + } + using (ZipFile zipFile = new ZipFile(new FileStream(hFile, FileAccess.Read))) + { + zipFile.IsStreamOwner = true; + var wnt = new WindowsNameTransform(ContainerPath); + var items = new List(); + foreach (var entry in zipFile.OfType()) // Returns all items recursively + { + string winPath = entry.IsDirectory ? wnt.TransformDirectory(entry.Name) : wnt.TransformFile(entry.Name); + if (winPath.StartsWith(Path)) // Child of self + { + var split = winPath.Substring(Path.Length).Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); + if (split.Length > 0) + { + if (entry.IsDirectory || split.Length > 1) // Not all folders have a ZipEntry + { + var itemPath = System.IO.Path.Combine(Path, split[0]); + if (!items.Any(x => x.Path == itemPath)) + { + items.Add(new ZipStorageFolder(itemPath, ContainerPath, entry)); + } + } + else + { + items.Add(new ZipStorageFile(winPath, ContainerPath, entry)); + } + } + } + } + return items; + } + }); + } + + public override IAsyncAction RenameAsync(string desiredName) + { + throw new NotSupportedException(); + } + + public override IAsyncAction RenameAsync(string desiredName, NameCollisionOption option) + { + throw new NotSupportedException(); + } + + public override IAsyncAction DeleteAsync() + { + throw new NotSupportedException(); + } + + public override IAsyncAction DeleteAsync(StorageDeleteOption option) + { + throw new NotSupportedException(); + } + + public override IAsyncOperation GetBasicPropertiesAsync() + { + return AsyncInfo.Run(async (cancellationToken) => + { + if (Path == ContainerPath) + { + var zipFile = new SystemStorageFile(await StorageFile.GetFileFromPathAsync(Path)); + return await zipFile.GetBasicPropertiesAsync(); + } + return GetBasicProperties(); + }); + } + + private BaseBasicProperties GetBasicProperties() + { + if (Path == ContainerPath) + { + return new BaseBasicProperties(); + } + var hFile = NativeFileOperationsHelper.OpenFileForRead(ContainerPath); + if (hFile.IsInvalid) + { + return new BaseBasicProperties(); + } + using (ZipFile zipFile = new ZipFile(new FileStream(hFile, FileAccess.Read))) + { + zipFile.IsStreamOwner = true; + var znt = new ZipNameTransform(ContainerPath); + var entry = zipFile.GetEntry(znt.TransformFile(Path)); + if (entry != null) + { + return new ZipFolderBasicProperties(entry); + } + return new BaseBasicProperties(); + } + } + + public override bool IsOfType(StorageItemTypes type) => type == StorageItemTypes.Folder; + + public override Windows.Storage.FileAttributes Attributes => Windows.Storage.FileAttributes.Directory; + + public override DateTimeOffset DateCreated { get; } + + public override string Name { get; } + + public override string Path { get; } + + public string ContainerPath { get; private set; } + + public override IAsyncOperation GetIndexedStateAsync() + { + return Task.FromResult(IndexedState.NotIndexed).AsAsyncOperation(); + } + + public override StorageFileQueryResult CreateFileQuery() => throw new NotSupportedException(); + + public override StorageFileQueryResult CreateFileQuery(CommonFileQuery query) => throw new NotSupportedException(); + + public override BaseStorageFileQueryResult CreateFileQueryWithOptions(QueryOptions queryOptions) + { + return new BaseStorageFileQueryResult(this, queryOptions); + } + + public override StorageFolderQueryResult CreateFolderQuery() => throw new NotSupportedException(); + + public override StorageFolderQueryResult CreateFolderQuery(CommonFolderQuery query) => throw new NotSupportedException(); + + public override BaseStorageFolderQueryResult CreateFolderQueryWithOptions(QueryOptions queryOptions) + { + return new BaseStorageFolderQueryResult(this, queryOptions); + } + + public override StorageItemQueryResult CreateItemQuery() => throw new NotSupportedException(); + + public override BaseStorageItemQueryResult CreateItemQueryWithOptions(QueryOptions queryOptions) + { + return new BaseStorageItemQueryResult(this, queryOptions); + } + + public override IAsyncOperation> GetFilesAsync(CommonFileQuery query, uint startIndex, uint maxItemsToRetrieve) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await GetFilesAsync(); + return items.Skip((int)startIndex).Take((int)maxItemsToRetrieve).ToList(); + }); + } + + public override IAsyncOperation> GetFilesAsync(CommonFileQuery query) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + return await GetFilesAsync(); + }); + } + + public override IAsyncOperation> GetFoldersAsync(CommonFolderQuery query, uint startIndex, uint maxItemsToRetrieve) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await GetFoldersAsync(); + return items.Skip((int)startIndex).Take((int)maxItemsToRetrieve).ToList(); + }); + } + + public override IAsyncOperation> GetFoldersAsync(CommonFolderQuery query) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + return await GetFoldersAsync(); + }); + } + + public override IAsyncOperation> GetItemsAsync(uint startIndex, uint maxItemsToRetrieve) + { + return AsyncInfo.Run>(async (cancellationToken) => + { + var items = await GetItemsAsync(); + return items.Skip((int)startIndex).Take((int)maxItemsToRetrieve).ToList(); + }); + } + + public override bool AreQueryOptionsSupported(QueryOptions queryOptions) => false; + + public override bool IsCommonFolderQuerySupported(CommonFolderQuery query) => false; + + public override bool IsCommonFileQuerySupported(CommonFileQuery query) => false; + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode) + { + return AsyncInfo.Run(async (cancellationToken) => + { + if (Path == ContainerPath) + { + var zipFile = await StorageFile.GetFileFromPathAsync(Path); + return await zipFile.GetThumbnailAsync(mode); + } + return null; + }); + } + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode, uint requestedSize) + { + return AsyncInfo.Run(async (cancellationToken) => + { + if (Path == ContainerPath) + { + var zipFile = await StorageFile.GetFileFromPathAsync(Path); + return await zipFile.GetThumbnailAsync(mode, requestedSize); + } + return null; + }); + } + + public override IAsyncOperation GetThumbnailAsync(ThumbnailMode mode, uint requestedSize, ThumbnailOptions options) + { + return AsyncInfo.Run(async (cancellationToken) => + { + if (Path == ContainerPath) + { + var zipFile = await StorageFile.GetFileFromPathAsync(Path); + return await zipFile.GetThumbnailAsync(mode, requestedSize, options); + } + return null; + }); + } + + public override IAsyncOperation ToStorageFolderAsync() => throw new NotSupportedException(); + + public override IAsyncOperation GetParentAsync() + { + throw new NotSupportedException(); + } + + public override bool IsEqual(IStorageItem item) => item?.Path == Path; + + public override IAsyncOperation TryGetItemAsync(string name) + { + return AsyncInfo.Run(async (cancellationToken) => + { + try + { + return await GetItemAsync(name); + } + catch + { + return null; + } + }); + } + + public static IAsyncOperation FromPathAsync(string path) + { + var marker = path.IndexOf(".zip"); + if (marker != -1) + { + var containerPath = path.Substring(0, marker + ".zip".Length); + if (CheckAccess(containerPath)) + { + return Task.FromResult(new ZipStorageFolder(path, containerPath)).AsAsyncOperation(); + } + } + return Task.FromResult(null).AsAsyncOperation(); + } + + public static bool IsZipPath(string path) + { + var marker = path.IndexOf(".zip"); + if (marker != -1) + { + marker += ".zip".Length; + if (marker == path.Length || path[marker] == '\\') + { + return true; + } + } + return false; + } + + private static bool CheckAccess(string path) + { + try + { + var hFile = NativeFileOperationsHelper.OpenFileForRead(path); + if (hFile.IsInvalid) + { + return false; + } + using (ZipFile zipFile = new ZipFile(new FileStream(hFile, FileAccess.Read))) + { + zipFile.IsStreamOwner = true; + } + return true; + } + catch + { + return false; + } + } + + public override string DisplayName => Name; + + public override string DisplayType + { + get + { + return "FileFolderListItem".GetLocalized(); + } + } + + public override string FolderRelativeId => $"0\\{Name}"; + + public override IStorageItemExtraProperties Properties => new BaseBasicStorageItemExtraProperties(this); + + private class FileDataSource : IStaticDataSource + { + public Stream Stream { get; set; } + + public Stream GetSource() => Stream; + } + + private class ZipFolderBasicProperties : BaseBasicProperties + { + private ZipEntry zipEntry; + + public ZipFolderBasicProperties(ZipEntry entry) + { + this.zipEntry = entry; + } + + public override DateTimeOffset DateModified => zipEntry.DateTime; + + public override DateTimeOffset ItemDate => zipEntry.DateTime; + + public override ulong Size => (ulong)zipEntry.Size; + } + } +} diff --git a/Files/Helpers/ContextFlyoutItemHelper.cs b/Files/Helpers/ContextFlyoutItemHelper.cs index bc3ca6f3c66d..8ec197e05999 100644 --- a/Files/Helpers/ContextFlyoutItemHelper.cs +++ b/Files/Helpers/ContextFlyoutItemHelper.cs @@ -94,6 +94,7 @@ private static bool Check(ContextMenuFlyoutItemViewModel item, CurrentInstanceVi return (item.ShowInRecycleBin || !currentInstanceViewModel.IsPageTypeRecycleBin) && (item.ShowInSearchPage || !currentInstanceViewModel.IsPageTypeSearchResults) && (item.ShowInFtpPage || !currentInstanceViewModel.IsPageTypeFtp) + && (item.ShowInZipPage || !currentInstanceViewModel.IsPageTypeZipFolder) && (!item.SingleItemOnly || selectedItems.Count == 1) && item.ShowItem; } @@ -109,6 +110,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Items = new List() { // Details view @@ -119,6 +121,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Command = currentInstanceViewModel.FolderSettings.ToggleLayoutModeDetailsView, CommandParameter = true, KeyboardAcceleratorTextOverride = "BaseLayoutContextFlyoutDetails/KeyboardAcceleratorTextOverride".GetLocalized(), @@ -132,6 +135,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Command = currentInstanceViewModel.FolderSettings.ToggleLayoutModeTiles, CommandParameter = true, KeyboardAcceleratorTextOverride = "BaseLayoutContextFlyoutTilesView/KeyboardAcceleratorTextOverride".GetLocalized(), @@ -145,6 +149,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Command = currentInstanceViewModel.FolderSettings.ToggleLayoutModeGridViewSmall, CommandParameter = true, KeyboardAcceleratorTextOverride = "BaseLayoutContextFlyoutGridViewSmall/KeyboardAcceleratorTextOverride".GetLocalized(), @@ -158,6 +163,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Command = currentInstanceViewModel.FolderSettings.ToggleLayoutModeGridViewMedium, CommandParameter = true, KeyboardAcceleratorTextOverride = "BaseLayoutContextFlyoutGridViewMedium/KeyboardAcceleratorTextOverride".GetLocalized(), @@ -171,6 +177,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Command = currentInstanceViewModel.FolderSettings.ToggleLayoutModeGridViewLarge, CommandParameter = true, KeyboardAcceleratorTextOverride = "BaseLayoutContextFlyoutGridViewLarge/KeyboardAcceleratorTextOverride".GetLocalized(), @@ -185,6 +192,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Command = currentInstanceViewModel.FolderSettings.ToggleLayoutModeColumnView, CommandParameter = true, KeyboardAcceleratorTextOverride = "BaseLayoutContextFlyoutColumn/KeyboardAcceleratorTextOverride".GetLocalized(), @@ -203,6 +211,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Items = new List() { new ContextMenuFlyoutItemViewModel() @@ -212,6 +221,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Command = new RelayCommand(() => itemViewModel.IsSortedByName = true), ItemType = ItemType.Toggle, }, @@ -223,6 +233,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, ItemType = ItemType.Toggle }, new ContextMenuFlyoutItemViewModel() @@ -233,6 +244,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, ItemType = ItemType.Toggle }, new ContextMenuFlyoutItemViewModel() @@ -243,6 +255,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, ItemType = ItemType.Toggle }, new ContextMenuFlyoutItemViewModel() @@ -253,6 +266,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, ItemType = ItemType.Toggle }, new ContextMenuFlyoutItemViewModel() @@ -297,6 +311,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, }, new ContextMenuFlyoutItemViewModel() { @@ -306,6 +321,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, ItemType = ItemType.Toggle }, new ContextMenuFlyoutItemViewModel() @@ -316,6 +332,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, ItemType = ItemType.Toggle }, } @@ -327,6 +344,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Items = new List() { new ContextMenuFlyoutItemViewModel() @@ -336,6 +354,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Command = currentInstanceViewModel.FolderSettings.ChangeGroupOptionCommand, CommandParameter = GroupOption.None, ItemType = ItemType.Toggle, @@ -347,6 +366,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Command = currentInstanceViewModel.FolderSettings.ChangeGroupOptionCommand, CommandParameter = GroupOption.Name, ItemType = ItemType.Toggle, @@ -358,6 +378,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Command = currentInstanceViewModel.FolderSettings.ChangeGroupOptionCommand, CommandParameter = GroupOption.DateModified, ItemType = ItemType.Toggle, @@ -369,6 +390,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Command = currentInstanceViewModel.FolderSettings.ChangeGroupOptionCommand, CommandParameter = GroupOption.DateCreated, ItemType = ItemType.Toggle, @@ -380,6 +402,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Command = currentInstanceViewModel.FolderSettings.ChangeGroupOptionCommand, CommandParameter = GroupOption.FileType, ItemType = ItemType.Toggle, @@ -391,6 +414,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Command = currentInstanceViewModel.FolderSettings.ChangeGroupOptionCommand, CommandParameter = GroupOption.Size, ItemType = ItemType.Toggle, @@ -444,6 +468,7 @@ public static List GetBaseLayoutMenuItems(Curren ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, Command = commandsViewModel.RefreshCommand, KeyboardAccelerator = new KeyboardAccelerator { @@ -456,6 +481,7 @@ public static List GetBaseLayoutMenuItems(Curren Text = "BaseLayoutContextFlyoutPaste/Text".GetLocalized(), //Glyph = "\uE16D", ShowInFtpPage = true, + ShowInZipPage = true, ColoredIcon = new ColoredIconModel() { BaseLayerGlyph = "\u0023", @@ -480,6 +506,7 @@ public static List GetBaseLayoutMenuItems(Curren { ItemType = ItemType.Separator, ShowInFtpPage = true, + ShowInZipPage = true, }, new ContextMenuFlyoutItemViewModel() { @@ -496,20 +523,24 @@ public static List GetBaseLayoutMenuItems(Curren IsEnabled = false, }, Items = GetNewItemItems(commandsViewModel), + ShowInFtpPage = true, + ShowInZipPage = true, }, new ContextMenuFlyoutItemViewModel() { Text = "BaseLayoutItemContextFlyoutPinToFavorites/Text".GetLocalized(), Glyph = "\uE840", Command = commandsViewModel.PinDirectoryToFavoritesCommand, - ShowItem = !itemViewModel.CurrentFolder.IsPinned & App.AppSettings.ShowFavoritesSection + ShowItem = !itemViewModel.CurrentFolder.IsPinned & App.AppSettings.ShowFavoritesSection, + ShowInFtpPage = true, }, new ContextMenuFlyoutItemViewModel() { Text = "BaseLayoutContextFlyoutUnpinFromFavorites/Text".GetLocalized(), Glyph = "\uE77A", Command = commandsViewModel.UnpinDirectoryFromFavoritesCommand, - ShowItem = itemViewModel.CurrentFolder.IsPinned & App.AppSettings.ShowFavoritesSection + ShowItem = itemViewModel.CurrentFolder.IsPinned & App.AppSettings.ShowFavoritesSection, + ShowInFtpPage = true, }, new ContextMenuFlyoutItemViewModel() { @@ -539,6 +570,7 @@ public static List GetBaseLayoutMenuItems(Curren }, Command = commandsViewModel.ShowFolderPropertiesCommand, ShowInFtpPage = true, + ShowInZipPage = true, }, new ContextMenuFlyoutItemViewModel() { @@ -585,7 +617,8 @@ public static List GetBaseItemMenuItems(BaseLayo Glyph = "\uE8E5", Command = commandsViewModel.OpenItemCommand, ShowInSearchPage = true, - //ShowInFtpPage = true, + ShowInFtpPage = true, + ShowInZipPage = true, ShowItem = selectedItems.Count <= 10, }, new ContextMenuFlyoutItemViewModel() @@ -632,6 +665,7 @@ public static List GetBaseItemMenuItems(BaseLayo SingleItemOnly = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, }, new ContextMenuFlyoutItemViewModel() { @@ -642,6 +676,7 @@ public static List GetBaseItemMenuItems(BaseLayo ShowItem = selectedItems.Count < 5 && selectedItems.All(i => i.PrimaryItemAttribute == Windows.Storage.StorageItemTypes.Folder), ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, }, new ContextMenuFlyoutItemViewModel() { @@ -652,6 +687,7 @@ public static List GetBaseItemMenuItems(BaseLayo ShowInSearchPage = true, ShowInFtpPage = true, ShowOnShift = true, + ShowInZipPage = true, }, new ContextMenuFlyoutItemViewModel() { @@ -698,6 +734,8 @@ public static List GetBaseItemMenuItems(BaseLayo ItemType = ItemType.Separator, ShowInRecycleBin = true, ShowInSearchPage = true, + ShowInFtpPage = true, + ShowInZipPage = true, }, new ContextMenuFlyoutItemViewModel() { @@ -717,6 +755,7 @@ public static List GetBaseItemMenuItems(BaseLayo }, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, }, new ContextMenuFlyoutItemViewModel() { @@ -731,6 +770,7 @@ public static List GetBaseItemMenuItems(BaseLayo ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, IsPrimary = true, KeyboardAccelerator = new KeyboardAccelerator { @@ -751,6 +791,7 @@ public static List GetBaseItemMenuItems(BaseLayo SingleItemOnly = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, }, new ContextMenuFlyoutItemViewModel() { @@ -767,6 +808,7 @@ public static List GetBaseItemMenuItems(BaseLayo SingleItemOnly = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, IsEnabled = App.MainViewModel.IsPasteEnabled, KeyboardAccelerator = new KeyboardAccelerator { @@ -809,6 +851,7 @@ public static List GetBaseItemMenuItems(BaseLayo SingleItemOnly = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, KeyboardAccelerator = new KeyboardAccelerator { Key = Windows.System.VirtualKey.F2, @@ -842,6 +885,7 @@ public static List GetBaseItemMenuItems(BaseLayo ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, KeyboardAccelerator = new KeyboardAccelerator { Key = Windows.System.VirtualKey.Delete, @@ -862,12 +906,13 @@ public static List GetBaseItemMenuItems(BaseLayo ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, + ShowInZipPage = true, }, new ContextMenuFlyoutItemViewModel() { Text = "BaseLayoutItemContextFlyoutExtractionOptions".GetLocalized(), Glyph = "\xF11A", - ShowItem = selectedItems.Count == 1 && selectedItems.First().PrimaryItemAttribute == StorageItemTypes.File && new [] { ".zip", ".msix", ".msixbundle" }.Contains(selectedItems.First().FileExtension, StringComparer.OrdinalIgnoreCase), + ShowItem = selectedItems.Count == 1 && (selectedItems.First().IsZipItem || (selectedItems.First().PrimaryItemAttribute == StorageItemTypes.File && new [] { ".zip", ".msix", ".msixbundle" }.Contains(selectedItems.First().FileExtension, StringComparer.OrdinalIgnoreCase))), ShowInSearchPage = true, GlyphFontFamilyName = "CustomGlyph", Items = new List() @@ -912,16 +957,18 @@ public static List GetBaseItemMenuItems(BaseLayo Text = "BaseLayoutItemContextFlyoutPinToFavorites/Text".GetLocalized(), Glyph = "\uE840", Command = commandsViewModel.SidebarPinItemCommand, - ShowItem = selectedItems.All(x => x.PrimaryItemAttribute == StorageItemTypes.Folder && !x.IsPinned) & App.AppSettings.ShowFavoritesSection, + ShowItem = selectedItems.All(x => x.PrimaryItemAttribute == StorageItemTypes.Folder && !x.IsZipItem && !x.IsPinned) & App.AppSettings.ShowFavoritesSection, ShowInSearchPage = true, + ShowInFtpPage = true, }, new ContextMenuFlyoutItemViewModel() { Text = "BaseLayoutContextFlyoutUnpinFromFavorites/Text".GetLocalized(), Glyph = "\uE77A", Command = commandsViewModel.SidebarUnpinItemCommand, - ShowItem = selectedItems.All(x => x.PrimaryItemAttribute == StorageItemTypes.Folder && x.IsPinned) & App.AppSettings.ShowFavoritesSection, + ShowItem = selectedItems.All(x => x.PrimaryItemAttribute == StorageItemTypes.Folder && !x.IsZipItem && x.IsPinned) & App.AppSettings.ShowFavoritesSection, ShowInSearchPage = true, + ShowInFtpPage = true, }, new ContextMenuFlyoutItemViewModel() { @@ -929,7 +976,7 @@ public static List GetBaseItemMenuItems(BaseLayo Glyph = "\uE840", Command = commandsViewModel.PinItemToStartCommand, ShowOnShift = true, - ShowItem = selectedItems.All(x => x.PrimaryItemAttribute == StorageItemTypes.Folder && !x.IsItemPinnedToStart), + ShowItem = selectedItems.All(x => x.PrimaryItemAttribute == StorageItemTypes.Folder && !x.IsZipItem && !x.IsItemPinnedToStart), ShowInSearchPage = true, ShowInFtpPage = true, SingleItemOnly = true, @@ -940,7 +987,7 @@ public static List GetBaseItemMenuItems(BaseLayo Glyph = "\uE77A", Command = commandsViewModel.UnpinItemFromStartCommand, ShowOnShift = true, - ShowItem = selectedItems.All(x => x.PrimaryItemAttribute == StorageItemTypes.Folder && x.IsItemPinnedToStart), + ShowItem = selectedItems.All(x => x.PrimaryItemAttribute == StorageItemTypes.Folder && !x.IsZipItem && x.IsItemPinnedToStart), ShowInSearchPage = true, ShowInFtpPage = true, SingleItemOnly = true, @@ -972,6 +1019,8 @@ public static List GetNewItemItems(BaseLayoutCom Text = "BaseLayoutContextFlyoutNewFolder/Text".GetLocalized(), Glyph = "\uE8B7", Command = commandsViewModel.CreateNewFolderCommand, + ShowInFtpPage = true, + ShowInZipPage = true, }, new ContextMenuFlyoutItemViewModel() { diff --git a/Files/Helpers/FileThumbnailHelper.cs b/Files/Helpers/FileThumbnailHelper.cs index 6ecf9e3ca301..d93d248bc32e 100644 --- a/Files/Helpers/FileThumbnailHelper.cs +++ b/Files/Helpers/FileThumbnailHelper.cs @@ -87,17 +87,17 @@ public static async Task LoadIconWithoutOverlayAsync(string filePath, ui public static async Task LoadIconFromStorageItemAsync(IStorageItem item, uint thumbnailSize, Windows.Storage.FileProperties.ThumbnailMode thumbnailMode) { - if (item is StorageFile file) + if (item.IsOfType(StorageItemTypes.File)) { - using var thumbnail = await file.GetThumbnailAsync(thumbnailMode, thumbnailSize, Windows.Storage.FileProperties.ThumbnailOptions.ResizeThumbnail); + using var thumbnail = await item.AsBaseStorageFile().GetThumbnailAsync(thumbnailMode, thumbnailSize, Windows.Storage.FileProperties.ThumbnailOptions.ResizeThumbnail); if (thumbnail != null) { return await thumbnail.ToByteArrayAsync(); } } - else if (item is StorageFolder folder) + else if (item.IsOfType(StorageItemTypes.Folder)) { - using var thumbnail = await folder.GetThumbnailAsync(thumbnailMode, thumbnailSize, Windows.Storage.FileProperties.ThumbnailOptions.ResizeThumbnail); + using var thumbnail = await item.AsBaseStorageFolder().GetThumbnailAsync(thumbnailMode, thumbnailSize, Windows.Storage.FileProperties.ThumbnailOptions.ResizeThumbnail); if (thumbnail != null) { return await thumbnail.ToByteArrayAsync(); @@ -110,19 +110,17 @@ public static async Task LoadIconFromPathAsync(string filePath, uint thu { if (!filePath.EndsWith(".lnk") && !filePath.EndsWith(".url")) { - var item = await FilesystemTasks.Wrap(() => DrivesManager.GetRootFromPathAsync(filePath)); - var res = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(filePath, item)); - if (res) + var item = await StorageItemHelpers.ToStorageItem(filePath); + if (item != null) { - return await LoadIconFromStorageItemAsync(res.Result, thumbnailSize, thumbnailMode); + var iconData = await LoadIconFromStorageItemAsync(item, thumbnailSize, thumbnailMode); + if (iconData != null) + { + return iconData; + } } } - var iconData = await LoadIconWithoutOverlayAsync(filePath, thumbnailSize); - if (iconData != null) - { - return iconData; - } - return null; + return await LoadIconWithoutOverlayAsync(filePath, thumbnailSize); } } } \ No newline at end of file diff --git a/Files/Helpers/FtpHelpers.cs b/Files/Helpers/FtpHelpers.cs index 3d8903ce194d..b6a2665d2037 100644 --- a/Files/Helpers/FtpHelpers.cs +++ b/Files/Helpers/FtpHelpers.cs @@ -28,7 +28,8 @@ public static bool IsFtpPath(string path) if (!string.IsNullOrEmpty(path)) { return path.StartsWith("ftp://", StringComparison.OrdinalIgnoreCase) - || path.StartsWith("ftps://", StringComparison.OrdinalIgnoreCase); + || path.StartsWith("ftps://", StringComparison.OrdinalIgnoreCase) + || path.StartsWith("ftpes://", StringComparison.OrdinalIgnoreCase); } return false; } @@ -98,10 +99,5 @@ public static string GetFtpPath(string path) var hostIndex = path.IndexOf("/", schemaIndex); return hostIndex == -1 ? "/" : path.Substring(hostIndex); } - - public static string GetFtpDirectoryName(string path) - { - return System.IO.Path.GetDirectoryName(path).Replace("\\", "/"); - } } } \ No newline at end of file diff --git a/Files/Helpers/ItemListDisplayHelpers/SortingHelper.cs b/Files/Helpers/ItemListDisplayHelpers/SortingHelper.cs index d373bbbed250..3caa3e345cb2 100644 --- a/Files/Helpers/ItemListDisplayHelpers/SortingHelper.cs +++ b/Files/Helpers/ItemListDisplayHelpers/SortingHelper.cs @@ -36,7 +36,7 @@ public static IEnumerable OrderFileList(List filesAndFol // In ascending order, show folders first, then files. // So, we use == StorageItemTypes.File to make the value for a folder equal to 0, and equal to 1 for the rest. - static bool folderThenFileAsync(ListedItem listedItem) => (listedItem.PrimaryItemAttribute == StorageItemTypes.File); + static bool folderThenFileAsync(ListedItem listedItem) => (listedItem.PrimaryItemAttribute == StorageItemTypes.File || listedItem.IsZipItem); IOrderedEnumerable ordered; if (directorySortDirection == SortDirection.Ascending) diff --git a/Files/Helpers/NativeFileOperationsHelper.cs b/Files/Helpers/NativeFileOperationsHelper.cs index d9f77e8523ae..a6593ff185bf 100644 --- a/Files/Helpers/NativeFileOperationsHelper.cs +++ b/Files/Helpers/NativeFileOperationsHelper.cs @@ -1,10 +1,13 @@ -using Microsoft.Win32.SafeHandles; +using Files.Common; +using Microsoft.Win32.SafeHandles; using System; using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Text; using System.Threading; +using System.Threading.Tasks; +using Windows.Foundation.Collections; namespace Files.Helpers { @@ -78,6 +81,12 @@ public static SafeFileHandle CreateFileForWrite(string filePath, bool overwrite GENERIC_WRITE, 0, IntPtr.Zero, overwrite ? CREATE_ALWAYS : OPEN_ALWAYS, (uint)File_Attributes.BackupSemantics, IntPtr.Zero), true); } + public static SafeFileHandle OpenFileForRead(string filePath, bool readWrite = false) + { + return new SafeFileHandle(CreateFileFromApp(filePath, + GENERIC_READ | (readWrite ? GENERIC_WRITE : 0), FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_ALWAYS, (uint)File_Attributes.BackupSemantics, IntPtr.Zero), true); + } + [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] @@ -298,5 +307,26 @@ public static bool WriteBufferToFileWithProgress(string filePath, byte[] buffer, return result; } + + public static async Task OpenProtectedFileForRead(string filePath, bool readWrite = false) + { + var connection = await AppServiceConnectionHelper.Instance; + if (connection != null) + { + var (status, response) = await connection.SendMessageForResponseAsync(new ValueSet() + { + { "Arguments", "FileOperation" }, + { "fileop", "GetFileHandle" }, + { "filepath", filePath }, + { "readwrite", readWrite }, + { "processid", System.Diagnostics.Process.GetCurrentProcess().Id }, + }); + if (status == Windows.ApplicationModel.AppService.AppServiceResponseStatus.Success && response.Get("Success", false)) + { + return new SafeFileHandle(new IntPtr((long)response["Handle"]), true); + } + } + return new SafeFileHandle(new IntPtr(-1), true); + } } } \ No newline at end of file diff --git a/Files/Helpers/NavigationHelpers.cs b/Files/Helpers/NavigationHelpers.cs index 3bdfa8117302..a33e92776c75 100644 --- a/Files/Helpers/NavigationHelpers.cs +++ b/Files/Helpers/NavigationHelpers.cs @@ -1,6 +1,7 @@ using Files.Common; using Files.Enums; using Files.Filesystem; +using Files.Filesystem.StorageItems; using Files.ViewModels; using Files.Views; using Microsoft.Toolkit.Uwp; @@ -290,20 +291,19 @@ private static async Task OpenDirectory(string path, IShellPag else { opened = await associatedInstance.FilesystemViewModel.GetFolderWithPathFromPathAsync(path) - .OnSuccess(childFolder => + .OnSuccess(async (childFolder) => { // Add location to MRU List - var mostRecentlyUsed = Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList; - mostRecentlyUsed.Add(childFolder.Folder, childFolder.Path); + if (childFolder.Folder is SystemStorageFolder) + { + var mostRecentlyUsed = Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList; + mostRecentlyUsed.Add(await childFolder.Folder.ToStorageFolderAsync(), childFolder.Path); + } }); if (!opened) { opened = (FilesystemResult)FolderHelpers.CheckFolderAccessWithWin32(path); } - if (!opened) - { - opened = (FilesystemResult)path.StartsWith("ftp:"); - } if (opened) { if (App.AppSettings.OpenFoldersNewTab) @@ -344,8 +344,11 @@ private static async Task OpenFile(string path, IShellPage ass if (childFile != null) { // Add location to MRU List - var mostRecentlyUsed = Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList; - mostRecentlyUsed.Add(childFile.File, childFile.Path); + if (childFile.File is SystemStorageFile) + { + var mostRecentlyUsed = Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList; + mostRecentlyUsed.Add(await childFile.File.ToStorageFileAsync(), childFile.Path); + } } } await Win32Helpers.InvokeWin32ComponentAsync(shortcutInfo.TargetPath, associatedInstance, $"{args} {shortcutInfo.Arguments}", shortcutInfo.RunAsAdmin, shortcutInfo.WorkingDirectory); @@ -362,8 +365,11 @@ private static async Task OpenFile(string path, IShellPage ass .OnSuccess(async childFile => { // Add location to MRU List - var mostRecentlyUsed = Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList; - mostRecentlyUsed.Add(childFile.File, childFile.Path); + if (childFile.File is SystemStorageFile) + { + var mostRecentlyUsed = Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList; + mostRecentlyUsed.Add(await childFile.File.ToStorageFileAsync(), childFile.Path); + } if (openViaApplicationPicker) { @@ -390,10 +396,10 @@ await connection.SendMessageAsync(new ValueSet() //try using launcher first bool launchSuccess = false; - StorageFileQueryResult fileQueryResult = null; + BaseStorageFileQueryResult fileQueryResult = null; //Get folder to create a file query (to pass to apps like Photos, Movies & TV..., needed to scroll through the folder like what Windows Explorer does) - StorageFolder currentFolder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(System.IO.Path.GetDirectoryName(path)); + BaseStorageFolder currentFolder = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(PathNormalization.GetParentDir(path)); if (currentFolder != null) { @@ -450,15 +456,15 @@ await connection.SendMessageAsync(new ValueSet() break; } - fileQueryResult = currentFolder.CreateFileQueryWithOptions(queryOptions); - - var options = new LauncherOptions + var options = new LauncherOptions(); + if (currentFolder.AreQueryOptionsSupported(queryOptions)) { - NeighboringFilesQuery = fileQueryResult - }; + fileQueryResult = currentFolder.CreateFileQueryWithOptions(queryOptions); + options.NeighboringFilesQuery = fileQueryResult.ToStorageFileQueryResult(); + } // Now launch file with options. - launchSuccess = await Launcher.LaunchFileAsync(childFile.File, options); + launchSuccess = await Launcher.LaunchFileAsync(await childFile.File.ToStorageFileAsync(), options); } if (!launchSuccess) diff --git a/Files/Helpers/PathNormalization.cs b/Files/Helpers/PathNormalization.cs index a3ff923678fe..1b170c563e54 100644 --- a/Files/Helpers/PathNormalization.cs +++ b/Files/Helpers/PathNormalization.cs @@ -68,8 +68,17 @@ public static string GetParentDir(string path) { return string.Empty; } - var index = path.LastIndexOf("\\"); + var index = path.Contains("/") ? path.LastIndexOf("/") : path.LastIndexOf("\\"); return path.Substring(0, index != -1 ? index : path.Length); } + + public static string Combine(string folder, string name) + { + if (string.IsNullOrEmpty(folder)) + { + return name; + } + return folder.Contains("/") ? Path.Combine(folder, name).Replace("\\", "/") : Path.Combine(folder, name); + } } } \ No newline at end of file diff --git a/Files/Helpers/SaveImageToFile.cs b/Files/Helpers/SaveImageToFile.cs index bb43d730cb0b..266f7032b41f 100644 --- a/Files/Helpers/SaveImageToFile.cs +++ b/Files/Helpers/SaveImageToFile.cs @@ -1,4 +1,5 @@ -using System; +using Files.Filesystem.StorageItems; +using System; using System.Threading.Tasks; using Windows.Graphics.Imaging; using Windows.Storage; @@ -15,7 +16,7 @@ internal static class SaveImageToFile /// /// The guid of the image encoder type /// - public static async Task SaveSoftwareBitmapToFile(SoftwareBitmap softwareBitmap, StorageFile outputFile, Guid encoderId) + public static async Task SaveSoftwareBitmapToFile(SoftwareBitmap softwareBitmap, BaseStorageFile outputFile, Guid encoderId) { using IRandomAccessStream stream = await outputFile.OpenAsync(FileAccessMode.ReadWrite); // Create an encoder with the desired format diff --git a/Files/Helpers/StorageItemHelpers.cs b/Files/Helpers/StorageItemHelpers.cs index 8605f8a38eac..20c74b89e157 100644 --- a/Files/Helpers/StorageItemHelpers.cs +++ b/Files/Helpers/StorageItemHelpers.cs @@ -20,8 +20,8 @@ public static async Task ToStorageItem(this IStorageItemWithPath i public static async Task ToStorageItem(string path, IShellPage associatedInstance = null) where TOut : IStorageItem { - FilesystemResult file = null; - FilesystemResult folder = null; + FilesystemResult file = null; + FilesystemResult folder = null; if (path.ToLower().EndsWith(".lnk") || path.ToLower().EndsWith(".url")) { @@ -73,7 +73,8 @@ async Task GetFile() { if (associatedInstance == null) { - file = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFileFromPathAsync(path)); + var rootItem = await FilesystemTasks.Wrap(() => DrivesManager.GetRootFromPathAsync(path)); + file = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFileFromPathAsync(path, rootItem)); } else { @@ -85,7 +86,8 @@ async Task GetFolder() { if (associatedInstance == null) { - folder = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(path)); + var rootItem = await FilesystemTasks.Wrap(() => DrivesManager.GetRootFromPathAsync(path)); + folder = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(path, rootItem)); } else { @@ -103,25 +105,16 @@ public static async Task GetFileSize(this IStorageFile file) public static async Task> ToStorageItemResult(this IStorageItemWithPath item, IShellPage associatedInstance = null) { var returnedItem = new FilesystemResult(null, FileSystemStatusCode.Generic); + var rootItem = associatedInstance == null ? await FilesystemTasks.Wrap(() => DrivesManager.GetRootFromPathAsync(item.Path)) : null; if (!string.IsNullOrEmpty(item.Path)) { - if (FtpHelpers.IsFtpPath(item.Path)) - { - returnedItem = new FilesystemResult((item.ItemType == FilesystemItemType.File) - ? new FtpStorageFile(associatedInstance.FilesystemViewModel, item) - : new FtpStorageFolder(associatedInstance.FilesystemViewModel, item), - FileSystemStatusCode.Success); - } - else - { - returnedItem = (item.ItemType == FilesystemItemType.File) ? - ToType(associatedInstance != null ? - await associatedInstance.FilesystemViewModel.GetFileFromPathAsync(item.Path) : - await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFileFromPathAsync(item.Path))) : - ToType(associatedInstance != null ? - await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(item.Path) : - await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(item.Path))); - } + returnedItem = (item.ItemType == FilesystemItemType.File) ? + ToType(associatedInstance != null ? + await associatedInstance.FilesystemViewModel.GetFileFromPathAsync(item.Path) : + await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFileFromPathAsync(item.Path, rootItem))) : + ToType(associatedInstance != null ? + await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(item.Path) : + await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(item.Path, rootItem))); } if (returnedItem.Result == null && item.Item != null) { @@ -157,11 +150,11 @@ public static IStorageItemWithPath FromStorageItem(this IStorageItem item, strin } else if (item.IsOfType(StorageItemTypes.File)) { - return new StorageFileWithPath(item as StorageFile, string.IsNullOrEmpty(item.Path) ? customPath : item.Path); + return new StorageFileWithPath(item.AsBaseStorageFile(), string.IsNullOrEmpty(item.Path) ? customPath : item.Path); } else if (item.IsOfType(StorageItemTypes.Folder)) { - return new StorageFolderWithPath(item as StorageFolder, string.IsNullOrEmpty(item.Path) ? customPath : item.Path); + return new StorageFolderWithPath(item.AsBaseStorageFolder(), string.IsNullOrEmpty(item.Path) ? customPath : item.Path); } return null; } diff --git a/Files/Helpers/UIFilesystemHelpers.cs b/Files/Helpers/UIFilesystemHelpers.cs index 5fc680a043f2..fe14bb944f0b 100644 --- a/Files/Helpers/UIFilesystemHelpers.cs +++ b/Files/Helpers/UIFilesystemHelpers.cs @@ -26,8 +26,8 @@ public static async void CutItem(IShellPage associatedInstance) }; List items = new List(); FilesystemResult result = (FilesystemResult)false; - bool canFlush = true; + var canFlush = true; if (associatedInstance.SlimContentPage.IsItemSelected) { // First, reset DataGrid Rows that may be in "cut" command mode @@ -35,18 +35,26 @@ public static async void CutItem(IShellPage associatedInstance) foreach (ListedItem listedItem in associatedInstance.SlimContentPage.SelectedItems.ToList()) { - // FTP doesn't support cut, fallback to copy + // FTP don't support cut, fallback to copy + if (listedItem is not FtpItem) + { + // Dim opacities accordingly + listedItem.Opacity = Constants.UI.DimItemOpacity; + } + if (listedItem is FtpItem ftpItem) { canFlush = false; - items.Add(await new FtpStorageFile(associatedInstance.FilesystemViewModel, ftpItem).ToStorageFileAsync()); - continue; + if (listedItem.PrimaryItemAttribute == StorageItemTypes.File) + { + items.Add(await new FtpStorageFile(ftpItem).ToStorageFileAsync()); + } + else if (listedItem.PrimaryItemAttribute == StorageItemTypes.Folder) + { + items.Add(new FtpStorageFolder(ftpItem)); + } } - - // Dim opacities accordingly - listedItem.Opacity = Constants.UI.DimItemOpacity; - - if (listedItem.PrimaryItemAttribute == StorageItemTypes.File) + else if (listedItem.PrimaryItemAttribute == StorageItemTypes.File || listedItem is ZipItem) { result = await associatedInstance.FilesystemViewModel.GetFileFromPathAsync(listedItem.ItemPath) .OnSuccess(t => items.Add(t)); @@ -94,16 +102,20 @@ public static async void CutItem(IShellPage associatedInstance) } } + var onlyStandard = items.All(x => x is StorageFile || x is StorageFolder || x is SystemStorageFile || x is SystemStorageFolder); + if (onlyStandard) + { + items = await items.ToStandardStorageItemsAsync(); + } if (!items.Any()) { return; } - dataPackage.SetStorageItems(items); + dataPackage.SetStorageItems(items, false); try { Clipboard.SetContent(dataPackage); - - if (canFlush) + if (onlyStandard && canFlush) { Clipboard.Flush(); } @@ -124,9 +136,8 @@ public static async Task CopyItem(IShellPage associatedInstance) string copySourcePath = associatedInstance.FilesystemViewModel.WorkingDirectory; FilesystemResult result = (FilesystemResult)false; - - bool canFlush = true; - + + var canFlush = true; if (associatedInstance.SlimContentPage.IsItemSelected) { foreach (ListedItem listedItem in associatedInstance.SlimContentPage.SelectedItems.ToList()) @@ -134,11 +145,16 @@ public static async Task CopyItem(IShellPage associatedInstance) if (listedItem is FtpItem ftpItem) { canFlush = false; - items.Add(await new FtpStorageFile(associatedInstance.FilesystemViewModel, ftpItem).ToStorageFileAsync()); - continue; + if (listedItem.PrimaryItemAttribute == StorageItemTypes.File) + { + items.Add(await new FtpStorageFile(ftpItem).ToStorageFileAsync()); + } + else if (listedItem.PrimaryItemAttribute == StorageItemTypes.Folder) + { + items.Add(new FtpStorageFolder(ftpItem)); + } } - - if (listedItem.PrimaryItemAttribute == StorageItemTypes.File) + else if (listedItem.PrimaryItemAttribute == StorageItemTypes.File || listedItem is ZipItem) { result = await associatedInstance.FilesystemViewModel.GetFileFromPathAsync(listedItem.ItemPath) .OnSuccess(t => items.Add(t)); @@ -176,23 +192,28 @@ await connection.SendMessageAsync(new ValueSet() } } - if (items?.Count > 0) + var onlyStandard = items.All(x => x is StorageFile || x is StorageFolder || x is SystemStorageFile || x is SystemStorageFolder); + if (onlyStandard) { - dataPackage.SetStorageItems(items); - try - { - Clipboard.SetContent(dataPackage); - - if (canFlush) - { - Clipboard.Flush(); - } - } - catch + items = await items.ToStandardStorageItemsAsync(); + } + if (!items.Any()) + { + return; + } + dataPackage.SetStorageItems(items, false); + try + { + Clipboard.SetContent(dataPackage); + if (onlyStandard && canFlush) { - dataPackage = null; + Clipboard.Flush(); } } + catch + { + dataPackage = null; + } } public static async Task PasteItemAsync(string destinationPath, IShellPage associatedInstance) @@ -279,7 +300,7 @@ public static async Task CreateFileFromDialogResultTypeForResult(A created = await FilesystemTasks.Wrap(async () => { return await associatedInstance.FilesystemHelpers.CreateAsync( - StorageItemHelpers.FromPathAndType(System.IO.Path.Combine(folderRes.Result.Path, userInput), FilesystemItemType.Directory), + StorageItemHelpers.FromPathAndType(PathNormalization.Combine(folderRes.Result.Path, userInput), FilesystemItemType.Directory), true); }); break; @@ -289,7 +310,7 @@ public static async Task CreateFileFromDialogResultTypeForResult(A created = await FilesystemTasks.Wrap(async () => { return await associatedInstance.FilesystemHelpers.CreateAsync( - StorageItemHelpers.FromPathAndType(System.IO.Path.Combine(folderRes.Result.Path, userInput + itemInfo?.Extension), FilesystemItemType.File), + StorageItemHelpers.FromPathAndType(PathNormalization.Combine(folderRes.Result.Path, userInput + itemInfo?.Extension), FilesystemItemType.File), true); }); break; diff --git a/Files/Helpers/WallpaperHelpers.cs b/Files/Helpers/WallpaperHelpers.cs index 32798539b404..2ba27602a40b 100644 --- a/Files/Helpers/WallpaperHelpers.cs +++ b/Files/Helpers/WallpaperHelpers.cs @@ -1,5 +1,6 @@ using Files.Enums; using Files.Filesystem; +using Files.Filesystem.StorageItems; using System; using Windows.Storage; using Windows.System.UserProfile; @@ -13,20 +14,20 @@ public static async void SetAsBackground(WallpaperType type, string filePath, IS if (UserProfilePersonalizationSettings.IsSupported()) { // Get the path of the selected file - StorageFile sourceFile = await StorageItemHelpers.ToStorageItem(filePath, associatedInstance); + BaseStorageFile sourceFile = await StorageItemHelpers.ToStorageItem(filePath, associatedInstance); if (sourceFile == null) { return; } // Get the app's local folder to use as the destination folder. - StorageFolder localFolder = ApplicationData.Current.LocalFolder; + BaseStorageFolder localFolder = ApplicationData.Current.LocalFolder; // the file to the destination folder. // Generate unique name if the file already exists. // If the file you are trying to set as the wallpaper has the same name as the current wallpaper, // the system will ignore the request and no-op the operation - StorageFile file = await FilesystemTasks.Wrap(() => sourceFile.CopyAsync(localFolder, sourceFile.Name, NameCollisionOption.GenerateUniqueName).AsTask()); + BaseStorageFile file = await FilesystemTasks.Wrap(() => sourceFile.CopyAsync(localFolder, sourceFile.Name, NameCollisionOption.GenerateUniqueName).AsTask()); if (file == null) { return; @@ -36,12 +37,12 @@ public static async void SetAsBackground(WallpaperType type, string filePath, IS if (type == WallpaperType.Desktop) { // Set the desktop background - await profileSettings.TrySetWallpaperImageAsync(file); + await profileSettings.TrySetWallpaperImageAsync(await file.ToStorageFileAsync()); } else if (type == WallpaperType.LockScreen) { // Set the lockscreen background - await profileSettings.TrySetLockScreenImageAsync(file); + await profileSettings.TrySetLockScreenImageAsync(await file.ToStorageFileAsync()); } } } diff --git a/Files/Helpers/ZipHelpers.cs b/Files/Helpers/ZipHelpers.cs index ab5ac2175ec4..e408fc3f0574 100644 --- a/Files/Helpers/ZipHelpers.cs +++ b/Files/Helpers/ZipHelpers.cs @@ -1,4 +1,5 @@ -using ICSharpCode.SharpZipLib.Zip; +using Files.Filesystem.StorageItems; +using ICSharpCode.SharpZipLib.Zip; using System; using System.Collections.Generic; using System.IO; @@ -11,7 +12,7 @@ namespace Files.Helpers { public static class ZipHelpers { - public static async Task ExtractArchive(StorageFile archive, StorageFolder destinationFolder, IProgress progressDelegate, CancellationToken cancellationToken) + public static async Task ExtractArchive(BaseStorageFile archive, BaseStorageFolder destinationFolder, IProgress progressDelegate, CancellationToken cancellationToken) { using (ZipFile zipFile = new ZipFile(await archive.OpenStreamForReadAsync())) { @@ -79,7 +80,7 @@ public static async Task ExtractArchive(StorageFile archive, StorageFolder desti return; // TODO: handle error } - using (FileStream destinationStream = new FileStream(hFile, FileAccess.ReadWrite)) + using (FileStream destinationStream = new FileStream(hFile, FileAccess.Write)) { int currentBlockSize = 0; diff --git a/Files/Interacts/BaseLayoutCommandImplementationModel.cs b/Files/Interacts/BaseLayoutCommandImplementationModel.cs index 892af32a8248..50e4482ca784 100644 --- a/Files/Interacts/BaseLayoutCommandImplementationModel.cs +++ b/Files/Interacts/BaseLayoutCommandImplementationModel.cs @@ -2,6 +2,7 @@ using Files.Dialogs; using Files.Enums; using Files.Filesystem; +using Files.Filesystem.StorageItems; using Files.Helpers; using Files.ViewModels; using Files.ViewModels.Dialogs; @@ -13,6 +14,7 @@ using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Windows.ApplicationModel.DataTransfer; using Windows.ApplicationModel.DataTransfer.DragDrop; using Windows.Foundation; @@ -378,14 +380,14 @@ async void Manager_DataRequested(DataTransferManager sender, DataRequestedEventA } else if (item.PrimaryItemAttribute == StorageItemTypes.Folder) { - if (await StorageItemHelpers.ToStorageItem(item.ItemPath, associatedInstance) is StorageFolder folder) + if (await StorageItemHelpers.ToStorageItem(item.ItemPath, associatedInstance) is BaseStorageFolder folder) { items.Add(folder); } } else { - if (await StorageItemHelpers.ToStorageItem(item.ItemPath, associatedInstance) is StorageFile file) + if (await StorageItemHelpers.ToStorageItem(item.ItemPath, associatedInstance) is BaseStorageFile file) { items.Add(file); } @@ -410,7 +412,7 @@ async void Manager_DataRequested(DataTransferManager sender, DataRequestedEventA dataRequest.Data.Properties.Description = "ShareDialogMultipleItemsDescription".GetLocalized(); } - dataRequest.Data.SetStorageItems(items); + dataRequest.Data.SetStorageItems(items, false); dataRequestDeferral.Complete(); // TODO: Unhook the event somewhere @@ -515,7 +517,7 @@ public virtual void GridViewSizeIncrease(KeyboardAcceleratorInvokedEventArgs e) } } - public virtual async void DragOver(DragEventArgs e) + public virtual async Task DragOver(DragEventArgs e) { var deferral = e.GetDeferral(); @@ -582,6 +584,12 @@ public virtual async void DragOver(DragEventArgs e) e.DragUIOverride.Caption = string.Format("MoveToFolderCaptionText".GetLocalized(), folderName); e.AcceptedOperation = DataPackageOperation.Move; } + else if (draggedItems.Any(x => x.Item is ZipStorageFile || x.Item is ZipStorageFolder) + || ZipStorageFolder.IsZipPath(pwd)) + { + e.DragUIOverride.Caption = string.Format("CopyToFolderCaptionText".GetLocalized(), folderName); + e.AcceptedOperation = DataPackageOperation.Copy; + } else if (draggedItems.AreItemsInSameDrive(associatedInstance.FilesystemViewModel.WorkingDirectory)) { e.DragUIOverride.Caption = string.Format("MoveToFolderCaptionText".GetLocalized(), folderName); @@ -598,7 +606,7 @@ public virtual async void DragOver(DragEventArgs e) deferral.Complete(); } - public virtual async void Drop(DragEventArgs e) + public virtual async Task Drop(DragEventArgs e) { var deferral = e.GetDeferral(); @@ -628,7 +636,7 @@ public async void CreateFolderWithSelection(RoutedEventArgs e) public async void DecompressArchive() { - StorageFile archive = await StorageItemHelpers.ToStorageItem(associatedInstance.SlimContentPage.SelectedItem.ItemPath); + BaseStorageFile archive = await StorageItemHelpers.ToStorageItem(associatedInstance.SlimContentPage.SelectedItem.ItemPath); if (archive != null) { @@ -655,12 +663,12 @@ public async void DecompressArchive() FileOperationType.Extract, extractCancellation); - StorageFolder destinationFolder = decompressArchiveViewModel.DestinationFolder; + BaseStorageFolder destinationFolder = decompressArchiveViewModel.DestinationFolder; string destinationFolderPath = decompressArchiveViewModel.DestinationFolderPath; if (destinationFolder == null) { - StorageFolder parentFolder = await StorageItemHelpers.ToStorageItem(Path.GetDirectoryName(archive.Path)); + BaseStorageFolder parentFolder = await StorageItemHelpers.ToStorageItem(Path.GetDirectoryName(archive.Path)); destinationFolder = await parentFolder.CreateFolderAsync(Path.GetFileName(destinationFolderPath), CreationCollisionOption.GenerateUniqueName); } @@ -692,8 +700,8 @@ public async void DecompressArchive() public async void DecompressArchiveHere() { - StorageFile archive = await StorageItemHelpers.ToStorageItem(associatedInstance.SlimContentPage.SelectedItem.ItemPath); - StorageFolder currentFolder = await StorageItemHelpers.ToStorageItem(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath); + BaseStorageFile archive = await StorageItemHelpers.ToStorageItem(associatedInstance.SlimContentPage.SelectedItem.ItemPath); + BaseStorageFolder currentFolder = await StorageItemHelpers.ToStorageItem(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath); if (archive != null && currentFolder != null) { @@ -728,9 +736,9 @@ public async void DecompressArchiveHere() public async void DecompressArchiveToChildFolder() { - StorageFile archive = await StorageItemHelpers.ToStorageItem(associatedInstance.SlimContentPage.SelectedItem.ItemPath); - StorageFolder currentFolder = await StorageItemHelpers.ToStorageItem(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath); - StorageFolder destinationFolder = null; + BaseStorageFile archive = await StorageItemHelpers.ToStorageItem(associatedInstance.SlimContentPage.SelectedItem.ItemPath); + BaseStorageFolder currentFolder = await StorageItemHelpers.ToStorageItem(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath); + BaseStorageFolder destinationFolder = null; if (currentFolder != null) { diff --git a/Files/Interacts/BaseLayoutCommandsViewModel.cs b/Files/Interacts/BaseLayoutCommandsViewModel.cs index d26d3d30c35a..bf8716504b35 100644 --- a/Files/Interacts/BaseLayoutCommandsViewModel.cs +++ b/Files/Interacts/BaseLayoutCommandsViewModel.cs @@ -9,72 +9,69 @@ namespace Files.Interacts { public class BaseLayoutCommandsViewModel : IDisposable { - #region Private Members - - private readonly IBaseLayoutCommandImplementationModel commandsModel; - - #endregion Private Members - #region Constructor public BaseLayoutCommandsViewModel(IBaseLayoutCommandImplementationModel commandsModel) { - this.commandsModel = commandsModel; + this.CommandsModel = commandsModel; InitializeCommands(); } #endregion Constructor + + public IBaseLayoutCommandImplementationModel CommandsModel { get; } + #region Command Initialization private void InitializeCommands() { - RenameItemCommand = new RelayCommand(commandsModel.RenameItem); - CreateShortcutCommand = new RelayCommand(commandsModel.CreateShortcut); - SetAsLockscreenBackgroundItemCommand = new RelayCommand(commandsModel.SetAsLockscreenBackgroundItem); - SetAsDesktopBackgroundItemCommand = new RelayCommand(commandsModel.SetAsDesktopBackgroundItem); - RunAsAdminCommand = new RelayCommand(commandsModel.RunAsAdmin); - RunAsAnotherUserCommand = new RelayCommand(commandsModel.RunAsAnotherUser); - SidebarPinItemCommand = new RelayCommand(commandsModel.SidebarPinItem); - SidebarUnpinItemCommand = new RelayCommand(commandsModel.SidebarUnpinItem); - UnpinDirectoryFromFavoritesCommand = new RelayCommand(commandsModel.UnpinDirectoryFromFavorites); - OpenItemCommand = new RelayCommand(commandsModel.OpenItem); - EmptyRecycleBinCommand = new RelayCommand(commandsModel.EmptyRecycleBin); - QuickLookCommand = new RelayCommand(commandsModel.QuickLook); - CopyItemCommand = new RelayCommand(commandsModel.CopyItem); - CutItemCommand = new RelayCommand(commandsModel.CutItem); - RestoreItemCommand = new RelayCommand(commandsModel.RestoreItem); - DeleteItemCommand = new RelayCommand(commandsModel.DeleteItem); - ShowFolderPropertiesCommand = new RelayCommand(commandsModel.ShowFolderProperties); - ShowPropertiesCommand = new RelayCommand(commandsModel.ShowProperties); - OpenFileLocationCommand = new RelayCommand(commandsModel.OpenFileLocation); - OpenParentFolderCommand = new RelayCommand(commandsModel.OpenParentFolder); - OpenItemWithApplicationPickerCommand = new RelayCommand(commandsModel.OpenItemWithApplicationPicker); - OpenDirectoryInNewTabCommand = new RelayCommand(commandsModel.OpenDirectoryInNewTab); - OpenDirectoryInNewPaneCommand = new RelayCommand(commandsModel.OpenDirectoryInNewPane); - OpenInNewWindowItemCommand = new RelayCommand(commandsModel.OpenInNewWindowItem); - CreateNewFolderCommand = new RelayCommand(commandsModel.CreateNewFolder); - CreateNewFileCommand = new RelayCommand(commandsModel.CreateNewFile); - PasteItemsFromClipboardCommand = new RelayCommand(commandsModel.PasteItemsFromClipboard); - CopyPathOfSelectedItemCommand = new RelayCommand(commandsModel.CopyPathOfSelectedItem); - OpenDirectoryInDefaultTerminalCommand = new RelayCommand(commandsModel.OpenDirectoryInDefaultTerminal); - ShareItemCommand = new RelayCommand(commandsModel.ShareItem); - PinDirectoryToFavoritesCommand = new RelayCommand(commandsModel.PinDirectoryToFavorites); - ItemPointerPressedCommand = new RelayCommand(commandsModel.ItemPointerPressed); - UnpinItemFromStartCommand = new RelayCommand(commandsModel.UnpinItemFromStart); - PinItemToStartCommand = new RelayCommand(commandsModel.PinItemToStart); - PointerWheelChangedCommand = new RelayCommand(commandsModel.PointerWheelChanged); - GridViewSizeDecreaseCommand = new RelayCommand(commandsModel.GridViewSizeDecrease); - GridViewSizeIncreaseCommand = new RelayCommand(commandsModel.GridViewSizeIncrease); - DragOverCommand = new RelayCommand(commandsModel.DragOver); - DropCommand = new RelayCommand(commandsModel.Drop); - RefreshCommand = new RelayCommand(commandsModel.RefreshItems); - SearchUnindexedItems = new RelayCommand(commandsModel.SearchUnindexedItems); - CreateFolderWithSelection = new RelayCommand(commandsModel.CreateFolderWithSelection); - DecompressArchiveCommand = new RelayCommand(commandsModel.DecompressArchive); - DecompressArchiveHereCommand = new RelayCommand(commandsModel.DecompressArchiveHere); - DecompressArchiveToChildFolderCommand = new RelayCommand(commandsModel.DecompressArchiveToChildFolder); + RenameItemCommand = new RelayCommand(CommandsModel.RenameItem); + CreateShortcutCommand = new RelayCommand(CommandsModel.CreateShortcut); + SetAsLockscreenBackgroundItemCommand = new RelayCommand(CommandsModel.SetAsLockscreenBackgroundItem); + SetAsDesktopBackgroundItemCommand = new RelayCommand(CommandsModel.SetAsDesktopBackgroundItem); + RunAsAdminCommand = new RelayCommand(CommandsModel.RunAsAdmin); + RunAsAnotherUserCommand = new RelayCommand(CommandsModel.RunAsAnotherUser); + SidebarPinItemCommand = new RelayCommand(CommandsModel.SidebarPinItem); + SidebarUnpinItemCommand = new RelayCommand(CommandsModel.SidebarUnpinItem); + UnpinDirectoryFromFavoritesCommand = new RelayCommand(CommandsModel.UnpinDirectoryFromFavorites); + OpenItemCommand = new RelayCommand(CommandsModel.OpenItem); + EmptyRecycleBinCommand = new RelayCommand(CommandsModel.EmptyRecycleBin); + QuickLookCommand = new RelayCommand(CommandsModel.QuickLook); + CopyItemCommand = new RelayCommand(CommandsModel.CopyItem); + CutItemCommand = new RelayCommand(CommandsModel.CutItem); + RestoreItemCommand = new RelayCommand(CommandsModel.RestoreItem); + DeleteItemCommand = new RelayCommand(CommandsModel.DeleteItem); + ShowFolderPropertiesCommand = new RelayCommand(CommandsModel.ShowFolderProperties); + ShowPropertiesCommand = new RelayCommand(CommandsModel.ShowProperties); + OpenFileLocationCommand = new RelayCommand(CommandsModel.OpenFileLocation); + OpenParentFolderCommand = new RelayCommand(CommandsModel.OpenParentFolder); + OpenItemWithApplicationPickerCommand = new RelayCommand(CommandsModel.OpenItemWithApplicationPicker); + OpenDirectoryInNewTabCommand = new RelayCommand(CommandsModel.OpenDirectoryInNewTab); + OpenDirectoryInNewPaneCommand = new RelayCommand(CommandsModel.OpenDirectoryInNewPane); + OpenInNewWindowItemCommand = new RelayCommand(CommandsModel.OpenInNewWindowItem); + CreateNewFolderCommand = new RelayCommand(CommandsModel.CreateNewFolder); + CreateNewFileCommand = new RelayCommand(CommandsModel.CreateNewFile); + PasteItemsFromClipboardCommand = new RelayCommand(CommandsModel.PasteItemsFromClipboard); + CopyPathOfSelectedItemCommand = new RelayCommand(CommandsModel.CopyPathOfSelectedItem); + OpenDirectoryInDefaultTerminalCommand = new RelayCommand(CommandsModel.OpenDirectoryInDefaultTerminal); + ShareItemCommand = new RelayCommand(CommandsModel.ShareItem); + PinDirectoryToFavoritesCommand = new RelayCommand(CommandsModel.PinDirectoryToFavorites); + ItemPointerPressedCommand = new RelayCommand(CommandsModel.ItemPointerPressed); + UnpinItemFromStartCommand = new RelayCommand(CommandsModel.UnpinItemFromStart); + PinItemToStartCommand = new RelayCommand(CommandsModel.PinItemToStart); + PointerWheelChangedCommand = new RelayCommand(CommandsModel.PointerWheelChanged); + GridViewSizeDecreaseCommand = new RelayCommand(CommandsModel.GridViewSizeDecrease); + GridViewSizeIncreaseCommand = new RelayCommand(CommandsModel.GridViewSizeIncrease); + DragOverCommand = new RelayCommand(e => _ = CommandsModel.DragOver(e)); + DropCommand = new RelayCommand(e => _ = CommandsModel.Drop(e)); + RefreshCommand = new RelayCommand(CommandsModel.RefreshItems); + SearchUnindexedItems = new RelayCommand(CommandsModel.SearchUnindexedItems); + CreateFolderWithSelection = new RelayCommand(CommandsModel.CreateFolderWithSelection); + DecompressArchiveCommand = new RelayCommand(CommandsModel.DecompressArchive); + DecompressArchiveHereCommand = new RelayCommand(CommandsModel.DecompressArchiveHere); + DecompressArchiveToChildFolderCommand = new RelayCommand(CommandsModel.DecompressArchiveToChildFolder); } #endregion Command Initialization @@ -177,7 +174,7 @@ private void InitializeCommands() public void Dispose() { - commandsModel?.Dispose(); + CommandsModel?.Dispose(); } #endregion IDisposable diff --git a/Files/Interacts/IBaseLayoutCommandImplementationModel.cs b/Files/Interacts/IBaseLayoutCommandImplementationModel.cs index 8acc7de8eb83..cd0b885bdbac 100644 --- a/Files/Interacts/IBaseLayoutCommandImplementationModel.cs +++ b/Files/Interacts/IBaseLayoutCommandImplementationModel.cs @@ -1,5 +1,6 @@ using Files.DataModels; using System; +using System.Threading.Tasks; using Windows.UI.Xaml; using Windows.UI.Xaml.Input; @@ -81,9 +82,9 @@ public interface IBaseLayoutCommandImplementationModel : IDisposable void GridViewSizeIncrease(KeyboardAcceleratorInvokedEventArgs e); - void DragOver(DragEventArgs e); + Task DragOver(DragEventArgs e); - void Drop(DragEventArgs e); + Task Drop(DragEventArgs e); void RefreshItems(RoutedEventArgs e); diff --git a/Files/Program.cs b/Files/Program.cs index e33806414ef0..521c89666359 100644 --- a/Files/Program.cs +++ b/Files/Program.cs @@ -60,6 +60,16 @@ private static async Task Main() return; } } + else if(activatedArgs is FileActivatedEventArgs) + { + var activePid = ApplicationData.Current.LocalSettings.Values.Get("INSTANCE_ACTIVE", -1); + var instance = AppInstance.FindOrRegisterInstanceForKey(activePid.ToString()); + if (!instance.IsCurrentInstance) + { + instance.RedirectActivationTo(); + return; + } + } else if (activatedArgs is CommandLineActivatedEventArgs) { var activePid = ApplicationData.Current.LocalSettings.Values.Get("INSTANCE_ACTIVE", -1); diff --git a/Files/UserControls/InnerNavigationToolbar.xaml.cs b/Files/UserControls/InnerNavigationToolbar.xaml.cs index d193aaf25f49..9b24b748e5bb 100644 --- a/Files/UserControls/InnerNavigationToolbar.xaml.cs +++ b/Files/UserControls/InnerNavigationToolbar.xaml.cs @@ -1,4 +1,5 @@ using Files.DataModels; +using Files.Extensions; using Files.Helpers; using Files.ViewModels; using Microsoft.Toolkit.Uwp; @@ -140,6 +141,12 @@ public ICommand SetCompactOverlayCommand private void NewEmptySpace_Opening(object sender, object e) { + if (!ViewModel.InstanceViewModel.CanCreateFileInPage) + { + var shell = NewEmptySpace.Items.Where(x => (x.Tag as string) == "CreateNewFile").Reverse().ToList(); + shell.ForEach(x => NewEmptySpace.Items.Remove(x)); + return; + } if (cachedNewContextMenuEntries == null) { return; diff --git a/Files/UserControls/MultitaskingControl/HorizontalMultitaskingControl.xaml.cs b/Files/UserControls/MultitaskingControl/HorizontalMultitaskingControl.xaml.cs index f7c6be08e0b6..b653d0b1c1e4 100644 --- a/Files/UserControls/MultitaskingControl/HorizontalMultitaskingControl.xaml.cs +++ b/Files/UserControls/MultitaskingControl/HorizontalMultitaskingControl.xaml.cs @@ -52,14 +52,14 @@ private void HorizontalTabView_TabItemsChanged(TabView sender, Windows.Foundatio private async void TabViewItem_Drop(object sender, DragEventArgs e) { - e.AcceptedOperation = await ((sender as TabViewItem).DataContext as TabItem).Control.TabItemContent.TabItemDrop(sender, e); + await ((sender as TabViewItem).DataContext as TabItem).Control.TabItemContent.TabItemDrop(sender, e); HorizontalTabView.CanReorderTabs = true; tabHoverTimer.Stop(); } - private void TabViewItem_DragEnter(object sender, DragEventArgs e) + private async void TabViewItem_DragEnter(object sender, DragEventArgs e) { - e.AcceptedOperation = ((sender as TabViewItem).DataContext as TabItem).Control.TabItemContent.TabItemDragOver(sender, e); + await ((sender as TabViewItem).DataContext as TabItem).Control.TabItemContent.TabItemDragOver(sender, e); if (e.AcceptedOperation != DataPackageOperation.None) { HorizontalTabView.CanReorderTabs = false; diff --git a/Files/UserControls/MultitaskingControl/TabItem/ITabItem.cs b/Files/UserControls/MultitaskingControl/TabItem/ITabItem.cs index dcad55faf27d..ea5c3e10db63 100644 --- a/Files/UserControls/MultitaskingControl/TabItem/ITabItem.cs +++ b/Files/UserControls/MultitaskingControl/TabItem/ITabItem.cs @@ -20,9 +20,9 @@ public interface ITabItemContent public event EventHandler ContentChanged; - public DataPackageOperation TabItemDragOver(object sender, DragEventArgs e); + public Task TabItemDragOver(object sender, DragEventArgs e); - public Task TabItemDrop(object sender, DragEventArgs e); + public Task TabItemDrop(object sender, DragEventArgs e); } public interface ITabItem diff --git a/Files/UserControls/MultitaskingControl/VerticalTabViewControl.xaml.cs b/Files/UserControls/MultitaskingControl/VerticalTabViewControl.xaml.cs index fa4bf0c55fbf..8d2dd30d9039 100644 --- a/Files/UserControls/MultitaskingControl/VerticalTabViewControl.xaml.cs +++ b/Files/UserControls/MultitaskingControl/VerticalTabViewControl.xaml.cs @@ -45,14 +45,14 @@ private void VerticalTabView_TabItemsChanged(TabView sender, Windows.Foundation. private async void TabViewItem_Drop(object sender, DragEventArgs e) { - e.AcceptedOperation = await ((sender as TabViewItem).DataContext as TabItem).Control.TabItemContent.TabItemDrop(sender, e); + await ((sender as TabViewItem).DataContext as TabItem).Control.TabItemContent.TabItemDrop(sender, e); VerticalTabView.CanReorderTabs = true; tabHoverTimer.Stop(); } - private void TabViewItem_DragEnter(object sender, DragEventArgs e) + private async void TabViewItem_DragEnter(object sender, DragEventArgs e) { - e.AcceptedOperation = ((sender as TabViewItem).DataContext as TabItem).Control.TabItemContent.TabItemDragOver(sender, e); + await ((sender as TabViewItem).DataContext as TabItem).Control.TabItemContent.TabItemDragOver(sender, e); if (e.AcceptedOperation != DataPackageOperation.None) { VerticalTabView.CanReorderTabs = false; diff --git a/Files/UserControls/SidebarControl.xaml.cs b/Files/UserControls/SidebarControl.xaml.cs index 7a2c779a0ec6..3cd921c7ede2 100644 --- a/Files/UserControls/SidebarControl.xaml.cs +++ b/Files/UserControls/SidebarControl.xaml.cs @@ -1,6 +1,7 @@ using Files.DataModels; using Files.DataModels.NavigationControlItems; using Files.Filesystem; +using Files.Filesystem.StorageItems; using Files.Helpers; using Files.Helpers.ContextFlyouts; using Files.ViewModels; @@ -508,6 +509,12 @@ private async void NavigationViewLocationItem_DragOver(object sender, DragEventA e.DragUIOverride.Caption = string.Format("MoveToFolderCaptionText".GetLocalized(), locationItem.Text); e.AcceptedOperation = DataPackageOperation.Move; } + else if (storageItems.Any(x => x.Item is ZipStorageFile || x.Item is ZipStorageFolder) + || ZipStorageFolder.IsZipPath(locationItem.Path)) + { + e.DragUIOverride.Caption = string.Format("CopyToFolderCaptionText".GetLocalized(), locationItem.Text); + e.AcceptedOperation = DataPackageOperation.Copy; + } else if (storageItems.AreItemsInSameDrive(locationItem.Path) || locationItem.IsDefaultLocation) { e.AcceptedOperation = DataPackageOperation.Move; diff --git a/Files/UserControls/Widgets/RecentFilesWidget.xaml.cs b/Files/UserControls/Widgets/RecentFilesWidget.xaml.cs index 70c6c2a268d5..eead77e5ded8 100644 --- a/Files/UserControls/Widgets/RecentFilesWidget.xaml.cs +++ b/Files/UserControls/Widgets/RecentFilesWidget.xaml.cs @@ -1,5 +1,6 @@ using Files.Enums; using Files.Filesystem; +using Files.Filesystem.StorageItems; using Files.ViewModels; using Files.ViewModels.Widgets; using Microsoft.Toolkit.Uwp; @@ -125,7 +126,7 @@ private async Task AddItemToRecentListAsync(IStorageItem item, AccessListEntry e // This is only needed to remove files opened from a disconnected android/MTP phone if (string.IsNullOrEmpty(item.Path)) // This indicates that the file was open from an MTP device { - using (var inputStream = await ((StorageFile)item).OpenReadAsync()) + using (var inputStream = await ((BaseStorageFile)item).OpenReadAsync()) using (var classicStream = inputStream.AsStreamForRead()) using (var streamReader = new StreamReader(classicStream)) { @@ -138,7 +139,7 @@ private async Task AddItemToRecentListAsync(IStorageItem item, AccessListEntry e ItemPath = string.IsNullOrEmpty(item.Path) ? entry.Metadata : item.Path; ItemType = StorageItemTypes.File; ItemImage = new BitmapImage(); - StorageFile file = (StorageFile)item; + BaseStorageFile file = (BaseStorageFile)item; using var thumbnail = await file.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode.ListView, 24, Windows.Storage.FileProperties.ThumbnailOptions.UseCurrentScale); if (thumbnail == null) { diff --git a/Files/ViewModels/ContextMenuFlyoutItemViewModel.cs b/Files/ViewModels/ContextMenuFlyoutItemViewModel.cs index bfbde2dc0cc9..a684b45b6f53 100644 --- a/Files/ViewModels/ContextMenuFlyoutItemViewModel.cs +++ b/Files/ViewModels/ContextMenuFlyoutItemViewModel.cs @@ -46,6 +46,11 @@ public class ContextMenuFlyoutItemViewModel /// public bool ShowInFtpPage { get; set; } + /// + /// True if the item is shown in ZIP archive page + /// + public bool ShowInZipPage { get; set; } + public KeyboardAccelerator KeyboardAccelerator { get; set; } public bool IsChecked { get; set; } public bool IsEnabled { get; set; } = true; diff --git a/Files/ViewModels/CurrentInstanceViewModel.cs b/Files/ViewModels/CurrentInstanceViewModel.cs index 35d31be3aae4..804ceb87e4bf 100644 --- a/Files/ViewModels/CurrentInstanceViewModel.cs +++ b/Files/ViewModels/CurrentInstanceViewModel.cs @@ -153,9 +153,27 @@ public bool IsPageTypeCloudDrive } } + private bool isPageTypeZipFolder = false; + + public bool IsPageTypeZipFolder + { + get => isPageTypeZipFolder; + set + { + SetProperty(ref isPageTypeZipFolder, value); + OnPropertyChanged(nameof(IsCreateButtonEnabledInPage)); + OnPropertyChanged(nameof(CanCreateFileInPage)); + OnPropertyChanged(nameof(CanPasteInPage)); + OnPropertyChanged(nameof(CanOpenTerminalInPage)); + OnPropertyChanged(nameof(CanCopyPathInPage)); + OnPropertyChanged(nameof(ShowSearchUnindexedItemsMessage)); + OnPropertyChanged(nameof(CanShareInPage)); + } + } + public bool IsCreateButtonEnabledInPage { - get => !isPageTypeRecycleBin && isPageTypeNotHome && !isPageTypeSearchResults && !isPageTypeFtp; + get => !isPageTypeRecycleBin && isPageTypeNotHome && !isPageTypeSearchResults; } public bool CanCopyPathInPage @@ -165,12 +183,12 @@ public bool CanCopyPathInPage public bool CanCreateFileInPage { - get => !isPageTypeMtpDevice && !isPageTypeRecycleBin && isPageTypeNotHome && !isPageTypeSearchResults && !isPageTypeFtp; + get => !isPageTypeMtpDevice && !isPageTypeRecycleBin && isPageTypeNotHome && !isPageTypeSearchResults && !isPageTypeFtp && !isPageTypeZipFolder; } public bool CanOpenTerminalInPage { - get => !isPageTypeMtpDevice && !isPageTypeRecycleBin && isPageTypeNotHome && !isPageTypeSearchResults && !isPageTypeFtp; + get => !isPageTypeMtpDevice && !isPageTypeRecycleBin && isPageTypeNotHome && !isPageTypeSearchResults && !isPageTypeFtp && !isPageTypeZipFolder; } public bool CanPasteInPage @@ -180,7 +198,7 @@ public bool CanPasteInPage public bool CanShareInPage { - get => !isPageTypeRecycleBin && isPageTypeNotHome && !isPageTypeFtp; + get => !isPageTypeRecycleBin && isPageTypeNotHome && !isPageTypeFtp && !isPageTypeZipFolder; } } } \ No newline at end of file diff --git a/Files/ViewModels/ItemViewModel.cs b/Files/ViewModels/ItemViewModel.cs index 2b3145982474..8b02151d7dd5 100644 --- a/Files/ViewModels/ItemViewModel.cs +++ b/Files/ViewModels/ItemViewModel.cs @@ -6,9 +6,11 @@ using Files.Filesystem.Cloud; using Files.Filesystem.Search; using Files.Filesystem.StorageEnumerators; +using Files.Filesystem.StorageItems; using Files.Helpers; using Files.Helpers.FileListCache; using Files.UserControls; +using FluentFTP; using Microsoft.Toolkit.Mvvm.ComponentModel; using Microsoft.Toolkit.Uwp; using Microsoft.Toolkit.Uwp.UI; @@ -147,12 +149,12 @@ public async Task SetWorkingDirectoryAsync(string value) OnPropertyChanged(nameof(WorkingDirectory)); } - public async Task> GetFolderFromPathAsync(string value) + public async Task> GetFolderFromPathAsync(string value) { return await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(value, workingRoot, currentStorageFolder)); } - public async Task> GetFileFromPathAsync(string value) + public async Task> GetFileFromPathAsync(string value) { return await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFileFromPathAsync(value, workingRoot, currentStorageFolder)); } @@ -730,7 +732,7 @@ public bool IsLoadingItems private async Task LoadItemThumbnail(ListedItem item, uint thumbnailSize = 20, IStorageItem matchingStorageItem = null, bool forceReload = false) { var wasIconLoaded = false; - if (item.IsLibraryItem || item.PrimaryItemAttribute == StorageItemTypes.File) + if (item.IsLibraryItem || item.PrimaryItemAttribute == StorageItemTypes.File || item.IsZipItem) { if (!forceReload && item.CustomIconData != null) { @@ -747,7 +749,7 @@ await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(async () => { if (!item.IsShortcutItem && !item.IsHiddenItem && !FtpHelpers.IsFtpPath(item.ItemPath)) { - var matchingStorageFile = (StorageFile)matchingStorageItem ?? await GetFileFromPathAsync(item.ItemPath); + var matchingStorageFile = (BaseStorageFile)matchingStorageItem ?? await GetFileFromPathAsync(item.ItemPath); if (matchingStorageFile != null) { var mode = thumbnailSize < 80 ? ThumbnailMode.ListView : ThumbnailMode.SingleItem; @@ -820,7 +822,7 @@ await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(async () => { if (!item.IsShortcutItem && !item.IsHiddenItem && !FtpHelpers.IsFtpPath(item.ItemPath)) { - var matchingStorageFolder = (StorageFolder)matchingStorageItem ?? await GetFolderFromPathAsync(item.ItemPath); + var matchingStorageFolder = (BaseStorageFolder)matchingStorageItem ?? await GetFolderFromPathAsync(item.ItemPath); if (matchingStorageFolder != null) { var mode = thumbnailSize < 80 ? ThumbnailMode.ListView : ThumbnailMode.SingleItem; @@ -928,14 +930,14 @@ await Task.Run(async () => try { bool isFileTypeGroupMode = folderSettings.DirectoryGroupOption == GroupOption.FileType; - StorageFile matchingStorageFile = null; + BaseStorageFile matchingStorageFile = null; if (item.Key != null && FilesAndFolders.IsGrouped && FilesAndFolders.GetExtendedGroupHeaderInfo != null) { gp = FilesAndFolders.GroupedCollection.Where(x => x.Model.Key == item.Key).FirstOrDefault(); loadGroupHeaderInfo = !(gp is null) && !gp.Model.Initialized && !(gp.GetExtendedGroupHeaderInfo is null); } - if (item.IsLibraryItem || item.PrimaryItemAttribute == StorageItemTypes.File) + if (item.IsLibraryItem || item.PrimaryItemAttribute == StorageItemTypes.File || item.IsZipItem) { if (!item.IsShortcutItem && !item.IsHiddenItem && !FtpHelpers.IsFtpPath(item.ItemPath)) { @@ -968,7 +970,7 @@ await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(() => { if (!item.IsShortcutItem && !item.IsHiddenItem && !FtpHelpers.IsFtpPath(item.ItemPath)) { - StorageFolder matchingStorageFolder = await GetFolderFromPathAsync(item.ItemPath); + BaseStorageFolder matchingStorageFolder = await GetFolderFromPathAsync(item.ItemPath); if (matchingStorageFolder != null) { await LoadItemThumbnail(item, thumbnailSize, matchingStorageFolder, true); @@ -1045,12 +1047,12 @@ await FilesystemTasks.Wrap(() => CoreApplication.MainView.DispatcherQueue.Enqueu }); } - private async Task GetItemTypeGroupIcon(ListedItem item, StorageFile matchingStorageItem = null) + private async Task GetItemTypeGroupIcon(ListedItem item, BaseStorageFile matchingStorageItem = null) { ImageSource groupImage = null; if (item.PrimaryItemAttribute != StorageItemTypes.Folder) { - var headerIconInfo = await FileThumbnailHelper.LoadIconWithoutOverlayAsync(item.ItemPath, 76); + var headerIconInfo = await FileThumbnailHelper.LoadIconFromPathAsync(item.ItemPath, 76, ThumbnailMode.ListView); if (headerIconInfo != null && !item.IsShortcutItem) { groupImage = await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(() => headerIconInfo.ToBitmapAsync(), Windows.System.DispatcherQueuePriority.Low); @@ -1326,47 +1328,45 @@ await Task.Run(async () => return; } - var client = this.GetFtpInstance(); - var host = FtpHelpers.GetFtpHost(path); - var port = FtpHelpers.GetFtpPort(path); + using var client = new FtpClient(); + client.Host = FtpHelpers.GetFtpHost(path); + client.Port = FtpHelpers.GetFtpPort(path); + client.Credentials = FtpManager.Credentials.Get(client.Host, FtpManager.Anonymous); - if (!client.IsConnected || client.Host != host || port != client.Port) + await Task.Run(async () => { - if (UIHelpers.IsAnyContentDialogOpen()) return; - - if (client.IsConnected) + try { - await client.DisconnectAsync(); - } - - client.Host = host; - client.Port = port; - - var dialog = new CredentialDialog(); + if (!client.IsConnected && await client.AutoConnectAsync() is null) + { + await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(async () => + { + if (UIHelpers.IsAnyContentDialogOpen()) + { + return; + } + var dialog = new CredentialDialog(); - if (await dialog.ShowAsync() == Windows.UI.Xaml.Controls.ContentDialogResult.Primary) - { - var result = await dialog.Result; + if (await dialog.ShowAsync() == Windows.UI.Xaml.Controls.ContentDialogResult.Primary) + { + var result = await dialog.Result; - if (!result.Anonymous) - { - client.Credentials = new NetworkCredential(result.UserName, result.Password); + if (!result.Anonymous) + { + client.Credentials = new NetworkCredential(result.UserName, result.Password); + } + } + else + { + return; + } + }); } - } - else - { - return; - } - } - - await Task.Run(async () => - { - try - { if (!client.IsConnected && await client.AutoConnectAsync() is null) { throw new InvalidOperationException(); } + FtpManager.Credentials[client.Host] = client.Credentials; var sampler = new IntervalSampler(500); var list = await client.GetListingAsync(FtpHelpers.GetFtpPath(path)); @@ -1385,6 +1385,7 @@ await Task.Run(async () => catch { // network issue + FtpManager.Credentials.Remove(client.Host); } }); } @@ -1396,7 +1397,7 @@ public async Task EnumerateItemsFromStandardFolderAsync(string path, Type s bool enumFromStorageFolder = path == App.CloudDrivesManager.Drives.FirstOrDefault(x => x.Text == "Box")?.Path?.TrimEnd('\\'); // Use storage folder for Box Drive (#4629) - StorageFolder rootFolder = null; + BaseStorageFolder rootFolder = null; if (FolderHelpers.CheckFolderAccessWithWin32(path)) { @@ -1449,7 +1450,7 @@ await DialogDisplayHelper.ShowDialogAsync( if (Path.IsPathRooted(path) && Path.GetPathRoot(path) == path) { - rootFolder ??= await FilesystemTasks.Wrap(() => StorageFolder.GetFolderFromPathAsync(path).AsTask()); + rootFolder ??= await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(path)); if (await FolderHelpers.CheckBitlockerStatusAsync(rootFolder, WorkingDirectory)) { if (Connection != null) @@ -1573,7 +1574,7 @@ await DialogDisplayHelper.ShowDialogAsync( } } - private async Task EnumFromStorageFolderAsync(string path, ListedItem currentFolder, StorageFolder rootFolder, StorageFolderWithPath currentStorageFolder, Type sourcePageType, CancellationToken cancellationToken) + private async Task EnumFromStorageFolderAsync(string path, ListedItem currentFolder, BaseStorageFolder rootFolder, StorageFolderWithPath currentStorageFolder, Type sourcePageType, CancellationToken cancellationToken) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); @@ -1605,14 +1606,14 @@ private async Task EnumFromStorageFolderAsync(string path, ListedItem currentFol private async Task CheckCloudDriveSyncStatusAsync(IStorageItem item) { int? syncStatus = null; - if (item is StorageFile) + if (item is BaseStorageFile file && file.Properties != null) { - IDictionary extraProperties = await ((StorageFile)item).Properties.RetrievePropertiesAsync(new string[] { "System.FilePlaceholderStatus" }); + IDictionary extraProperties = await (file.Properties.RetrievePropertiesAsync(new string[] { "System.FilePlaceholderStatus" })); syncStatus = (int?)(uint?)extraProperties["System.FilePlaceholderStatus"]; } - else if (item is StorageFolder) + else if (item is BaseStorageFolder folder && folder.Properties != null) { - IDictionary extraProperties = await ((StorageFolder)item).Properties.RetrievePropertiesAsync(new string[] { "System.FilePlaceholderStatus", "System.FileOfflineAvailabilityStatus" }); + IDictionary extraProperties = await (folder.Properties.RetrievePropertiesAsync(new string[] { "System.FilePlaceholderStatus", "System.FileOfflineAvailabilityStatus" })); syncStatus = (int?)(uint?)extraProperties["System.FileOfflineAvailabilityStatus"]; // If no FileOfflineAvailabilityStatus, check FilePlaceholderStatus syncStatus = syncStatus ?? (int?)(uint?)extraProperties["System.FilePlaceholderStatus"]; @@ -1628,7 +1629,7 @@ private async Task CheckCloudDriveSyncStatusAsync(IStorage private IAsyncOperation> watchedItemsOperation; - private async void WatchForStorageFolderChanges(StorageFolder rootFolder) + private async void WatchForStorageFolderChanges(BaseStorageFolder rootFolder) { if (rootFolder == null) { @@ -1643,9 +1644,12 @@ await Task.Run(() => }; options.SetPropertyPrefetch(PropertyPrefetchOptions.None, null); options.SetThumbnailPrefetch(ThumbnailMode.ListView, 0, ThumbnailOptions.ReturnOnlyIfCached); - itemQueryResult = rootFolder.CreateItemQueryWithOptions(options); - itemQueryResult.ContentsChanged += ItemQueryResult_ContentsChanged; - watchedItemsOperation = itemQueryResult.GetItemsAsync(0, 1); // Just get one item to start getting notifications + if (rootFolder.AreQueryOptionsSupported(options)) + { + itemQueryResult = rootFolder.CreateItemQueryWithOptions(options).ToStorageItemQueryResult(); + itemQueryResult.ContentsChanged += ItemQueryResult_ContentsChanged; + watchedItemsOperation = itemQueryResult.GetItemsAsync(0, 1); // Just get one item to start getting notifications + } }); } diff --git a/Files/ViewModels/MainPageViewModel.cs b/Files/ViewModels/MainPageViewModel.cs index 97be0e6fb44c..e4a510d40ca5 100644 --- a/Files/ViewModels/MainPageViewModel.cs +++ b/Files/ViewModels/MainPageViewModel.cs @@ -1,5 +1,6 @@ using Files.Common; using Files.Filesystem; +using Files.Filesystem.StorageItems; using Files.Helpers; using Files.UserControls.MultitaskingControl; using Files.Views; @@ -296,7 +297,7 @@ public static async Task UpdateTabInfo(TabItem tabItem, object navigationArg) FilesystemResult rootItem = await FilesystemTasks.Wrap(() => DrivesManager.GetRootFromPathAsync(currentPath)); if (rootItem) { - StorageFolder currentFolder = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(currentPath, rootItem)); + BaseStorageFolder currentFolder = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(currentPath, rootItem)); if (currentFolder != null && !string.IsNullOrEmpty(currentFolder.DisplayName)) { tabLocationHeader = currentFolder.DisplayName; diff --git a/Files/ViewModels/NavToolbarViewModel.cs b/Files/ViewModels/NavToolbarViewModel.cs index dfee08e101dd..8742bc16ea03 100644 --- a/Files/ViewModels/NavToolbarViewModel.cs +++ b/Files/ViewModels/NavToolbarViewModel.cs @@ -26,6 +26,9 @@ using Windows.UI.Xaml.Input; using static Files.UserControls.INavigationToolbar; using SearchBox = Files.UserControls.SearchBox; +using Files.Interacts; +using Files.Enums; +using Files.Filesystem.StorageItems; namespace Files.ViewModels { @@ -456,6 +459,13 @@ public async void PathBoxItem_DragOver(object sender, DragEventArgs e) { e.AcceptedOperation = DataPackageOperation.None; } + // copy be default when dragging from zip + else if (storageItems.Any(x => x.Item is ZipStorageFile || x.Item is ZipStorageFolder) + || ZipStorageFolder.IsZipPath(pathBoxItem.Path)) + { + e.DragUIOverride.Caption = string.Format("CopyToFolderCaptionText".GetLocalized(), pathBoxItem.Title); + e.AcceptedOperation = DataPackageOperation.Copy; + } else { e.DragUIOverride.IsCaptionVisible = true; @@ -866,13 +876,13 @@ await DialogDisplayHelper.ShowDialogAsync("InvalidItemDialogTitle".GetLocalized( public async void SetAddressBarSuggestions(AutoSuggestBox sender, IShellPage shellpage, int maxSuggestions = 7) { - if (!string.IsNullOrWhiteSpace(sender.Text)) + if (!string.IsNullOrWhiteSpace(sender.Text) && shellpage.FilesystemViewModel != null) { try { IList suggestions = null; var expandedPath = StorageFileExtensions.GetPathWithoutEnvironmentVariable(sender.Text); - var folderPath = Path.GetDirectoryName(expandedPath) ?? expandedPath; + var folderPath = PathNormalization.GetParentDir(expandedPath) ?? expandedPath; var folder = await shellpage.FilesystemViewModel.GetFolderWithPathFromPathAsync(folderPath); var currPath = await folder.Result.GetFoldersWithPathAsync(Path.GetFileName(expandedPath), (uint)maxSuggestions); if (currPath.Count() >= maxSuggestions) @@ -894,7 +904,7 @@ public async void SetAddressBarSuggestions(AutoSuggestBox sender, IShellPage she subPath.Select(x => new ListedItem(null) { ItemPath = x.Path, - ItemName = Path.Combine(currPath.First().Folder.DisplayName, x.Folder.DisplayName) + ItemName = PathNormalization.Combine(currPath.First().Folder.DisplayName, x.Folder.DisplayName) })).ToList(); } else diff --git a/Files/ViewModels/PreviewPaneViewModel.cs b/Files/ViewModels/PreviewPaneViewModel.cs index 38e8184ca9a0..15fa1f54438d 100644 --- a/Files/ViewModels/PreviewPaneViewModel.cs +++ b/Files/ViewModels/PreviewPaneViewModel.cs @@ -202,7 +202,7 @@ private async Task GetBuiltInPreviewControlAsync(ListedItem item, b private async Task LoadPreviewControlFromExtension(ListedItem item, Extension extension) { UIElement control = null; - var file = await StorageFile.GetFileFromPathAsync(item.ItemPath); + var file = await StorageFileExtensions.DangerousGetFileFromPathAsync(item.ItemPath); string sharingToken = SharedStorageAccessManager.AddFile(file); var result = await extension.Invoke(new ValueSet() { { "token", sharingToken } }); diff --git a/Files/ViewModels/Previews/BasePreviewModel.cs b/Files/ViewModels/Previews/BasePreviewModel.cs index 04537ae2ecce..69ab128ba5ac 100644 --- a/Files/ViewModels/Previews/BasePreviewModel.cs +++ b/Files/ViewModels/Previews/BasePreviewModel.cs @@ -1,9 +1,13 @@ using Files.Filesystem; +using Files.Filesystem.StorageItems; +using Files.Helpers; using Files.ViewModels.Properties; using Microsoft.Toolkit.Mvvm.ComponentModel; using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Windows.Storage; @@ -48,9 +52,16 @@ public virtual void PreviewControlBase_Unloaded(object sender, RoutedEventArgs e /// A list of details public async virtual Task> LoadPreviewAndDetails() { - using var icon = await Item.ItemFile.GetThumbnailAsync(ThumbnailMode.SingleItem, 400); - FileImage ??= new Windows.UI.Xaml.Media.Imaging.BitmapImage(); - await FileImage.SetSourceAsync(icon); + var iconData = await FileThumbnailHelper.LoadIconFromStorageItemAsync(Item.ItemFile, 400, ThumbnailMode.SingleItem); + iconData ??= await FileThumbnailHelper.LoadIconWithoutOverlayAsync(Item.ItemPath, 400); + if (iconData != null) + { + FileImage = await iconData.ToBitmapAsync(); + } + else + { + FileImage ??= new BitmapImage(); + } return new List(); } @@ -96,7 +107,7 @@ private async Task> GetSystemFileProperties() public virtual async Task LoadAsync() { var detailsFull = new List(); - Item.ItemFile ??= await StorageFile.GetFileFromPathAsync(Item.ItemPath); + Item.ItemFile ??= await StorageFileExtensions.DangerousGetFileFromPathAsync(Item.ItemPath); DetailsFromPreview = await LoadPreviewAndDetails(); var props = await GetSystemFileProperties(); @@ -126,5 +137,21 @@ public override Task> LoadPreviewAndDetails() return Task.FromResult(DetailsFromPreview); } } + + public static async Task ReadFileAsText(BaseStorageFile file, int maxLength = 10 * 1024 * 1024) + { + using (var stream = await file.OpenStreamForReadAsync()) + { + var result = new StringBuilder(); + var bytesRead = 0; + do + { + var buffer = new byte[maxLength]; + bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); + result.Append(Encoding.UTF8.GetString(buffer)); + } while (bytesRead > 0 && result.Length <= maxLength); + return result.ToString(); + } + } } } \ No newline at end of file diff --git a/Files/ViewModels/Previews/CodePreviewViewModel.cs b/Files/ViewModels/Previews/CodePreviewViewModel.cs index 1857b5005342..aea418eb5194 100644 --- a/Files/ViewModels/Previews/CodePreviewViewModel.cs +++ b/Files/ViewModels/Previews/CodePreviewViewModel.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; -using Windows.Storage; namespace Files.ViewModels.Previews { @@ -40,7 +39,8 @@ public async override Task> LoadPreviewAndDetails() try { - var text = TextValue ?? await FileIO.ReadTextAsync(Item.ItemFile); + //var text = TextValue ?? await FileIO.ReadTextAsync(Item.ItemFile); + var text = TextValue ?? await ReadFileAsText(Item.ItemFile); CodeLanguage = GetCodeLanguage(Item.FileExtension); details.Add(new FileProperty() diff --git a/Files/ViewModels/Previews/FolderPreviewViewModel.cs b/Files/ViewModels/Previews/FolderPreviewViewModel.cs index 3e5a0ecabe9d..cfd11bc7f73b 100644 --- a/Files/ViewModels/Previews/FolderPreviewViewModel.cs +++ b/Files/ViewModels/Previews/FolderPreviewViewModel.cs @@ -1,5 +1,7 @@ using Files.Enums; using Files.Filesystem; +using Files.Filesystem.StorageItems; +using Files.Helpers; using Files.ViewModels.Properties; using System; using System.Collections.ObjectModel; @@ -12,7 +14,7 @@ namespace Files.ViewModels.Previews { public class FolderPreviewViewModel { - private StorageFolder Folder { get; set; } + private BaseStorageFolder Folder { get; set; } public ListedItem Item { get; set; } @@ -33,11 +35,15 @@ private async Task LoadPreviewAndDetailsAsync() ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings; string returnformat = Enum.Parse(localSettings.Values[Constants.LocalSettings.DateTimeFormat].ToString()) == TimeStyle.Application ? "D" : "g"; - Folder = await StorageFolder.GetFolderFromPathAsync(Item.ItemPath); + Folder = await StorageFileExtensions.DangerousGetFolderFromPathAsync(Item.ItemPath); var items = await Folder.GetItemsAsync(); - using var icon = await Folder.GetThumbnailAsync(ThumbnailMode.SingleItem, 400); - await Thumbnail.SetSourceAsync(icon); + var iconData = await FileThumbnailHelper.LoadIconFromStorageItemAsync(Folder, 400, ThumbnailMode.SingleItem); + iconData ??= await FileThumbnailHelper.LoadIconWithoutOverlayAsync(Item.ItemPath, 400); + if (iconData != null) + { + Thumbnail = await iconData.ToBitmapAsync(); + } var info = await Folder.GetBasicPropertiesAsync(); Item.FileDetails = new ObservableCollection() diff --git a/Files/ViewModels/Previews/HtmlPreviewViewModel.cs b/Files/ViewModels/Previews/HtmlPreviewViewModel.cs index 343b10a49208..a88ec39fcb6b 100644 --- a/Files/ViewModels/Previews/HtmlPreviewViewModel.cs +++ b/Files/ViewModels/Previews/HtmlPreviewViewModel.cs @@ -29,7 +29,7 @@ public string TextValue public async override Task> LoadPreviewAndDetails() { - TextValue = await FileIO.ReadTextAsync(Item.ItemFile); + TextValue = await ReadFileAsText(Item.ItemFile); // await FileIO.ReadTextAsync(Item.ItemFile); return new List(); } } diff --git a/Files/ViewModels/Previews/MarkdownPreviewViewModel.cs b/Files/ViewModels/Previews/MarkdownPreviewViewModel.cs index b7bba65f8bd6..4c21fb9f0c45 100644 --- a/Files/ViewModels/Previews/MarkdownPreviewViewModel.cs +++ b/Files/ViewModels/Previews/MarkdownPreviewViewModel.cs @@ -27,7 +27,7 @@ public string TextValue public override async Task> LoadPreviewAndDetails() { - var text = await FileIO.ReadTextAsync(Item.ItemFile); + var text = await ReadFileAsText(Item.ItemFile); // await FileIO.ReadTextAsync(Item.ItemFile); var displayText = text.Length < Constants.PreviewPane.TextCharacterLimit ? text : text.Remove(Constants.PreviewPane.TextCharacterLimit); TextValue = displayText; diff --git a/Files/ViewModels/Previews/PDFPreviewViewModel.cs b/Files/ViewModels/Previews/PDFPreviewViewModel.cs index 2ba084963d51..92eb585dd480 100644 --- a/Files/ViewModels/Previews/PDFPreviewViewModel.cs +++ b/Files/ViewModels/Previews/PDFPreviewViewModel.cs @@ -36,8 +36,9 @@ public Visibility LoadingBarVisibility public async override Task> LoadPreviewAndDetails() { - var pdf = await PdfDocument.LoadFromFileAsync(Item.ItemFile); - TryLoadPagesAsync(pdf); + var fileStream = await Item.ItemFile.OpenReadAsync(); + var pdf = await PdfDocument.LoadFromStreamAsync(fileStream); + TryLoadPagesAsync(pdf, fileStream); var details = new List { // Add the number of pages to the details @@ -51,7 +52,7 @@ public async override Task> LoadPreviewAndDetails() return details; } - public async void TryLoadPagesAsync(PdfDocument pdf) + public async void TryLoadPagesAsync(PdfDocument pdf, IRandomAccessStream fileStream) { try { @@ -61,6 +62,10 @@ public async void TryLoadPagesAsync(PdfDocument pdf) { Debug.WriteLine(e); } + finally + { + fileStream.Dispose(); + } } private async Task LoadPagesAsync(PdfDocument pdf) diff --git a/Files/ViewModels/Previews/TextPreviewViewModel.cs b/Files/ViewModels/Previews/TextPreviewViewModel.cs index 6413be9b3441..11313d28378c 100644 --- a/Files/ViewModels/Previews/TextPreviewViewModel.cs +++ b/Files/ViewModels/Previews/TextPreviewViewModel.cs @@ -44,8 +44,8 @@ public static async Task TryLoadAsTextAsync(ListedItem item) try { - item.ItemFile = await StorageFile.GetFileFromPathAsync(item.ItemPath); - var text = await FileIO.ReadTextAsync(item.ItemFile); + item.ItemFile = await StorageFileExtensions.DangerousGetFileFromPathAsync(item.ItemPath); + var text = await ReadFileAsText(item.ItemFile); // await FileIO.ReadTextAsync(item.ItemFile); // Check if file is binary if (text.Contains("\0\0\0\0")) @@ -74,7 +74,8 @@ public async override Task> LoadPreviewAndDetails() try { - var text = TextValue ?? await FileIO.ReadTextAsync(Item.ItemFile); + //var text = TextValue ?? await FileIO.ReadTextAsync(Item.ItemFile); + var text = TextValue ?? await ReadFileAsText(Item.ItemFile); details.Add(new FileProperty() { diff --git a/Files/ViewModels/Properties/BaseProperties.cs b/Files/ViewModels/Properties/BaseProperties.cs index cf697e429847..d5846eab1ad1 100644 --- a/Files/ViewModels/Properties/BaseProperties.cs +++ b/Files/ViewModels/Properties/BaseProperties.cs @@ -1,6 +1,7 @@ using ByteSizeLib; using Files.Enums; using Files.Extensions; +using Files.Filesystem.StorageItems; using Microsoft.Toolkit.Uwp; using System; using System.Collections.Generic; @@ -28,7 +29,7 @@ public abstract class BaseProperties public abstract void GetSpecialProperties(); - public async void GetOtherProperties(StorageItemContentProperties properties) + public async void GetOtherProperties(IStorageItemExtraProperties properties) { string dateAccessedProperty = "System.DateAccessed"; List propertiesName = new List(); diff --git a/Files/ViewModels/Properties/DriveProperties.cs b/Files/ViewModels/Properties/DriveProperties.cs index 01a42e34bfd4..f6422a4b63bf 100644 --- a/Files/ViewModels/Properties/DriveProperties.cs +++ b/Files/ViewModels/Properties/DriveProperties.cs @@ -1,5 +1,6 @@ using Files.DataModels.NavigationControlItems; using Files.Filesystem; +using Files.Filesystem.StorageItems; using Files.Helpers; using Microsoft.Toolkit.Uwp; using System; @@ -40,7 +41,7 @@ public async override void GetSpecialProperties() { ViewModel.ItemAttributesVisibility = Visibility.Collapsed; var item = await FilesystemTasks.Wrap(() => DrivesManager.GetRootFromPathAsync(Drive.Path)); - StorageFolder diskRoot = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(Drive.Path, item)); + BaseStorageFolder diskRoot = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(Drive.Path, item)); if (ViewModel.LoadFileIcon) { @@ -54,7 +55,7 @@ public async override void GetSpecialProperties() } } - if (diskRoot == null) + if (diskRoot == null || diskRoot.Properties == null) { ViewModel.LastSeparatorVisibility = Visibility.Collapsed; return; diff --git a/Files/ViewModels/Properties/FileProperties.cs b/Files/ViewModels/Properties/FileProperties.cs index 555db4fdaf2c..0c9b2ff5d10c 100644 --- a/Files/ViewModels/Properties/FileProperties.cs +++ b/Files/ViewModels/Properties/FileProperties.cs @@ -1,6 +1,7 @@ using ByteSizeLib; using Files.Extensions; using Files.Filesystem; +using Files.Filesystem.StorageItems; using Files.Helpers; using Microsoft.Toolkit.Mvvm.Input; using Microsoft.Toolkit.Uwp; @@ -110,7 +111,7 @@ public override async void GetSpecialProperties() ViewModel.ItemSizeVisibility = Visibility.Visible; ViewModel.ItemSize = $"{ByteSize.FromBytes(Item.FileSizeBytes).ToBinaryString().ConvertSizeAbbreviation()} ({ByteSize.FromBytes(Item.FileSizeBytes).Bytes:#,##0} {"ItemSizeBytes".GetLocalized()})"; - var fileIconData = await FileThumbnailHelper.LoadIconWithoutOverlayAsync(Item.ItemPath, 80); + var fileIconData = await FileThumbnailHelper.LoadIconFromPathAsync(Item.ItemPath, 80, Windows.Storage.FileProperties.ThumbnailMode.SingleItem); if (fileIconData != null) { ViewModel.IconData = fileIconData; @@ -130,7 +131,7 @@ public override async void GetSpecialProperties() } } - StorageFile file = await AppInstance.FilesystemViewModel.GetFileFromPathAsync((Item as ShortcutItem)?.TargetPath ?? Item.ItemPath); + BaseStorageFile file = await AppInstance.FilesystemViewModel.GetFileFromPathAsync((Item as ShortcutItem)?.TargetPath ?? Item.ItemPath); if (file == null) { // Could not access file, can't show any other property @@ -143,7 +144,10 @@ public override async void GetSpecialProperties() return; } - GetOtherProperties(file.Properties); + if (file.Properties != null) + { + GetOtherProperties(file.Properties); + } // Get file MD5 hash var hashAlgTypeName = HashAlgorithmNames.Md5; @@ -162,7 +166,7 @@ public override async void GetSpecialProperties() public async void GetSystemFileProperties() { - StorageFile file = await FilesystemTasks.Wrap(() => StorageFile.GetFileFromPathAsync(Item.ItemPath).AsTask()); + BaseStorageFile file = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFileFromPathAsync(Item.ItemPath)); if (file == null) { // Could not access file, can't show any other property @@ -218,7 +222,7 @@ public static async Task GetAddressFromCoordinatesAsync(double? Lat, dou public async Task SyncPropertyChangesAsync() { - StorageFile file = await FilesystemTasks.Wrap(() => StorageFile.GetFileFromPathAsync(Item.ItemPath).AsTask()); + BaseStorageFile file = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFileFromPathAsync(Item.ItemPath)); if (file == null) { // Could not access file, can't save properties @@ -237,7 +241,10 @@ public async Task SyncPropertyChangesAsync() try { - await file.Properties.SavePropertiesAsync(newDict); + if (file.Properties != null) + { + await file.Properties.SavePropertiesAsync(newDict); + } } catch { @@ -260,7 +267,7 @@ public async Task SyncPropertyChangesAsync() public async Task ClearPropertiesAsync() { var failedProperties = new List(); - StorageFile file = await FilesystemTasks.Wrap(() => StorageFile.GetFileFromPathAsync(Item.ItemPath).AsTask()); + BaseStorageFile file = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFileFromPathAsync(Item.ItemPath)); if (file == null) { return; @@ -277,7 +284,10 @@ public async Task ClearPropertiesAsync() try { - await file.Properties.SavePropertiesAsync(newDict); + if (file.Properties != null) + { + await file.Properties.SavePropertiesAsync(newDict); + } } catch { @@ -349,7 +359,7 @@ private async void ViewModel_PropertyChanged(object sender, System.ComponentMode private async Task GetHashForFileAsync(ListedItem fileItem, string nameOfAlg, CancellationToken token, IProgress progress, IShellPage associatedInstance) { HashAlgorithmProvider algorithmProvider = HashAlgorithmProvider.OpenAlgorithm(nameOfAlg); - StorageFile file = await StorageItemHelpers.ToStorageItem((fileItem as ShortcutItem)?.TargetPath ?? fileItem.ItemPath, associatedInstance); + BaseStorageFile file = await StorageItemHelpers.ToStorageItem((fileItem as ShortcutItem)?.TargetPath ?? fileItem.ItemPath, associatedInstance); if (file == null) { return ""; @@ -361,17 +371,26 @@ private async Task GetHashForFileAsync(ListedItem fileItem, string nameO return ""; } - var inputStream = stream.AsInputStream(); - var str = inputStream.AsStreamForRead(); - var cap = (long)(0.5 * str.Length) / 100; uint capacity; - if (cap >= uint.MaxValue) + var inputStream = stream.AsInputStream(); + bool isProgressSupported = false; + + try { - capacity = uint.MaxValue; + var cap = (long)(0.5 * stream.Length) / 100; + if (cap >= uint.MaxValue) + { + capacity = uint.MaxValue; + } + else + { + capacity = Convert.ToUInt32(cap); + } + isProgressSupported = true; } - else + catch (NotSupportedException) { - capacity = Convert.ToUInt32(cap); + capacity = 64 * 1024; } Windows.Storage.Streams.Buffer buffer = new Windows.Storage.Streams.Buffer(capacity); @@ -387,7 +406,7 @@ private async Task GetHashForFileAsync(ListedItem fileItem, string nameO { break; } - progress?.Report((float)str.Position / str.Length * 100.0f); + progress?.Report(isProgressSupported ? (float)stream.Position / stream.Length * 100.0f : 20); } inputStream.Dispose(); stream.Dispose(); diff --git a/Files/ViewModels/Properties/FileProperty.cs b/Files/ViewModels/Properties/FileProperty.cs index 3ab2a5b4a1a6..ac765f239404 100644 --- a/Files/ViewModels/Properties/FileProperty.cs +++ b/Files/ViewModels/Properties/FileProperty.cs @@ -1,4 +1,5 @@ using Files.Converters; +using Files.Filesystem.StorageItems; using Files.Helpers; using Microsoft.Toolkit.Mvvm.ComponentModel; using Microsoft.Toolkit.Uwp; @@ -145,9 +146,9 @@ public void InitializeProperty() /// /// /// - public async Task SaveValueToFile(StorageFile file) + public async Task SaveValueToFile(BaseStorageFile file) { - if (!string.IsNullOrEmpty(Property)) + if (!string.IsNullOrEmpty(Property) || file.Properties == null) { return; } @@ -252,7 +253,7 @@ private object ConvertBack(string value) /// The file whose properties you wish to obtain /// The path to the json file of properties to be loaded /// A list if FileProperties containing their values - public async static Task> RetrieveAndInitializePropertiesAsync(StorageFile file, string path = Constants.ResourceFilePaths.DetailsPagePropertiesJsonPath) + public async static Task> RetrieveAndInitializePropertiesAsync(BaseStorageFile file, string path = Constants.ResourceFilePaths.DetailsPagePropertiesJsonPath) { var propertiesJsonFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(path)); @@ -283,7 +284,10 @@ public async static Task> RetrieveAndInitializePropertiesAsyn object val = null; try { - val = (await file.Properties.RetrievePropertiesAsync(new string[] { prop })).First().Value; + if (file.Properties != null) + { + val = (await file.Properties.RetrievePropertiesAsync(new string[] { prop })).First().Value; + } } catch (ArgumentException e) { @@ -292,7 +296,11 @@ public async static Task> RetrieveAndInitializePropertiesAsyn keyValuePairs.Add(prop, val); } #else - var keyValuePairs = await file.Properties.RetrievePropertiesAsync(propsToGet); + IDictionary keyValuePairs = new Dictionary(); + if (file.Properties != null) + { + keyValuePairs = await file.Properties.RetrievePropertiesAsync(propsToGet); + } #endif foreach (var prop in list) { diff --git a/Files/ViewModels/Properties/FolderProperties.cs b/Files/ViewModels/Properties/FolderProperties.cs index 0196592226ae..1e7fec2a0eed 100644 --- a/Files/ViewModels/Properties/FolderProperties.cs +++ b/Files/ViewModels/Properties/FolderProperties.cs @@ -2,6 +2,7 @@ using Files.Enums; using Files.Extensions; using Files.Filesystem; +using Files.Filesystem.StorageItems; using Files.Helpers; using Microsoft.Toolkit.Mvvm.Input; using Microsoft.Toolkit.Uwp; @@ -79,7 +80,7 @@ public async override void GetSpecialProperties() ViewModel.IsHidden = NativeFileOperationsHelper.HasFileAttribute( Item.ItemPath, System.IO.FileAttributes.Hidden); - var fileIconData = await FileThumbnailHelper.LoadIconWithoutOverlayAsync(Item.ItemPath, 80); + var fileIconData = await FileThumbnailHelper.LoadIconFromPathAsync(Item.ItemPath, 80, Windows.Storage.FileProperties.ThumbnailMode.SingleItem); if (fileIconData != null) { ViewModel.IconData = fileIconData; @@ -100,7 +101,7 @@ public async override void GetSpecialProperties() } } - StorageFolder storageFolder; + BaseStorageFolder storageFolder; try { storageFolder = await AppInstance.FilesystemViewModel.GetFolderFromPathAsync((Item as ShortcutItem)?.TargetPath ?? Item.ItemPath); @@ -117,7 +118,10 @@ public async override void GetSpecialProperties() ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings; string returnformat = Enum.Parse(localSettings.Values[Constants.LocalSettings.DateTimeFormat].ToString()) == TimeStyle.Application ? "D" : "g"; ViewModel.ItemCreatedTimestamp = storageFolder.DateCreated.GetFriendlyDateFromFormat(returnformat); - GetOtherProperties(storageFolder.Properties); + if (storageFolder.Properties != null) + { + GetOtherProperties(storageFolder.Properties); + } GetFolderSize(storageFolder, TokenSource.Token); } else if (Item.ItemPath.Equals(App.AppSettings.RecycleBinPath, StringComparison.OrdinalIgnoreCase)) @@ -162,7 +166,7 @@ public async override void GetSpecialProperties() } } - private async void GetFolderSize(StorageFolder storageFolder, CancellationToken token) + private async void GetFolderSize(BaseStorageFolder storageFolder, CancellationToken token) { if (string.IsNullOrEmpty(storageFolder.Path)) { diff --git a/Files/ViewModels/Properties/LibraryProperties.cs b/Files/ViewModels/Properties/LibraryProperties.cs index 9bf9e7f89f6a..fda8d85eb366 100644 --- a/Files/ViewModels/Properties/LibraryProperties.cs +++ b/Files/ViewModels/Properties/LibraryProperties.cs @@ -2,6 +2,7 @@ using Files.Enums; using Files.Extensions; using Files.Filesystem; +using Files.Filesystem.StorageItems; using Files.Helpers; using Microsoft.Toolkit.Uwp; using System; @@ -67,23 +68,26 @@ public async override void GetSpecialProperties() ViewModel.LoadFileIcon = true; } - StorageFile libraryFile = await AppInstance.FilesystemViewModel.GetFileFromPathAsync(Library.ItemPath); + BaseStorageFile libraryFile = await AppInstance.FilesystemViewModel.GetFileFromPathAsync(Library.ItemPath); if (libraryFile != null) { ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings; string returnformat = Enum.Parse(localSettings.Values[Constants.LocalSettings.DateTimeFormat].ToString()) == TimeStyle.Application ? "D" : "g"; ViewModel.ItemCreatedTimestamp = libraryFile.DateCreated.GetFriendlyDateFromFormat(returnformat); - GetOtherProperties(libraryFile.Properties); + if (libraryFile.Properties != null) + { + GetOtherProperties(libraryFile.Properties); + } } - var storageFolders = new List(); + var storageFolders = new List(); if (Library.Folders != null) { try { foreach (var path in Library.Folders) { - StorageFolder folder = await AppInstance.FilesystemViewModel.GetFolderFromPathAsync(path); + BaseStorageFolder folder = await AppInstance.FilesystemViewModel.GetFolderFromPathAsync(path); if (!string.IsNullOrEmpty(folder.Path)) { storageFolders.Add(folder); @@ -108,7 +112,7 @@ public async override void GetSpecialProperties() } } - private async void GetLibrarySize(List storageFolders, CancellationToken token) + private async void GetLibrarySize(List storageFolders, CancellationToken token) { ViewModel.ItemSizeVisibility = Visibility.Visible; ViewModel.ItemSizeProgressVisibility = Visibility.Visible; diff --git a/Files/ViewModels/Properties/PropertiesTab.cs b/Files/ViewModels/Properties/PropertiesTab.cs index e2bf7cbe989a..803227840391 100644 --- a/Files/ViewModels/Properties/PropertiesTab.cs +++ b/Files/ViewModels/Properties/PropertiesTab.cs @@ -41,7 +41,7 @@ protected override void OnNavigatedTo(NavigationEventArgs e) } else if (np.navParameter is ListedItem item) { - if (item.PrimaryItemAttribute == StorageItemTypes.File) + if (item.PrimaryItemAttribute == StorageItemTypes.File || item.IsZipItem) { BaseProperties = new FileProperties(ViewModel, np.tokenSource, Dispatcher, hashProgress, item, AppInstance); } @@ -66,7 +66,7 @@ protected override void OnNavigatedFrom(NavigationEventArgs e) { if (BaseProperties != null && BaseProperties.TokenSource != null) { - BaseProperties.TokenSource.Cancel(); + //BaseProperties.TokenSource.Cancel(); } base.OnNavigatedFrom(e); diff --git a/Files/ViewModels/SettingsViewModel.cs b/Files/ViewModels/SettingsViewModel.cs index b3957ff56e23..5832dc5fca6e 100644 --- a/Files/ViewModels/SettingsViewModel.cs +++ b/Files/ViewModels/SettingsViewModel.cs @@ -102,6 +102,7 @@ private void TrackAnalytics() Analytics.TrackEvent($"{nameof(ShowLibrarySection)} {ShowLibrarySection}"); Analytics.TrackEvent($"{nameof(ShowBundlesWidget)} {ShowBundlesWidget}"); Analytics.TrackEvent($"{nameof(ListAndSortDirectoriesAlongsideFiles)} {ListAndSortDirectoriesAlongsideFiles}"); + Analytics.TrackEvent($"{nameof(AreFileTagsEnabled)} {AreFileTagsEnabled}"); } public static async void OpenLogLocation() @@ -614,8 +615,6 @@ public AppTheme SelectedTheme #endregion Appearance - - #region Startup /// diff --git a/Files/Views/ColumnShellPage.xaml.cs b/Files/Views/ColumnShellPage.xaml.cs index ecdb0c62b16f..440f766bf837 100644 --- a/Files/Views/ColumnShellPage.xaml.cs +++ b/Files/Views/ColumnShellPage.xaml.cs @@ -868,35 +868,9 @@ private void SetLoadingIndicatorForTabs(bool isLoading) } } - public DataPackageOperation TabItemDragOver(object sender, DragEventArgs e) - { - if (Filesystem.FilesystemHelpers.HasDraggedStorageItems(e.DataView)) - { - if (!InstanceViewModel.IsPageTypeSearchResults) - { - return DataPackageOperation.Move; - } - } - return DataPackageOperation.None; - } + public Task TabItemDragOver(object sender, DragEventArgs e) => SlimContentPage?.CommandsViewModel.CommandsModel.DragOver(e); - public async Task TabItemDrop(object sender, DragEventArgs e) - { - if (Filesystem.FilesystemHelpers.HasDraggedStorageItems(e.DataView)) - { - if (InstanceViewModel.IsPageTypeNotHome && !InstanceViewModel.IsPageTypeSearchResults) - { - await FilesystemHelpers.PerformOperationTypeAsync( - DataPackageOperation.Move, - e.DataView, - FilesystemViewModel.WorkingDirectory, - false, - true); - return DataPackageOperation.Move; - } - } - return DataPackageOperation.None; - } + public Task TabItemDrop(object sender, DragEventArgs e) => SlimContentPage?.CommandsViewModel.CommandsModel.Drop(e); public void NavigateWithArguments(Type sourcePageType, NavigationArguments navArgs) { diff --git a/Files/Views/ModernShellPage.xaml.cs b/Files/Views/ModernShellPage.xaml.cs index 7af96e7b4b3a..a50ce7ad3b28 100644 --- a/Files/Views/ModernShellPage.xaml.cs +++ b/Files/Views/ModernShellPage.xaml.cs @@ -1021,35 +1021,9 @@ private void SetLoadingIndicatorForTabs(bool isLoading) } } - public DataPackageOperation TabItemDragOver(object sender, DragEventArgs e) - { - if (Filesystem.FilesystemHelpers.HasDraggedStorageItems(e.DataView)) - { - if (!InstanceViewModel.IsPageTypeSearchResults) - { - return DataPackageOperation.Move; - } - } - return DataPackageOperation.None; - } + public Task TabItemDragOver(object sender, DragEventArgs e) => SlimContentPage?.CommandsViewModel.CommandsModel.DragOver(e); - public async Task TabItemDrop(object sender, DragEventArgs e) - { - if (Filesystem.FilesystemHelpers.HasDraggedStorageItems(e.DataView)) - { - if (InstanceViewModel.IsPageTypeNotHome && !InstanceViewModel.IsPageTypeSearchResults) - { - await FilesystemHelpers.PerformOperationTypeAsync( - DataPackageOperation.Move, - e.DataView, - FilesystemViewModel.WorkingDirectory, - false, - true); - return DataPackageOperation.Move; - } - } - return DataPackageOperation.None; - } + public Task TabItemDrop(object sender, DragEventArgs e) => SlimContentPage?.CommandsViewModel.CommandsModel.Drop(e); public void NavigateHome() { diff --git a/Files/Views/Pages/PropertiesDetails.xaml.cs b/Files/Views/Pages/PropertiesDetails.xaml.cs index 386570fc44c8..1a89f3558f47 100644 --- a/Files/Views/Pages/PropertiesDetails.xaml.cs +++ b/Files/Views/Pages/PropertiesDetails.xaml.cs @@ -21,10 +21,10 @@ protected override void Properties_Loaded(object sender, RoutedEventArgs e) { base.Properties_Loaded(sender, e); - if (BaseProperties != null) + if (BaseProperties is FileProperties fileProps) { Stopwatch stopwatch = Stopwatch.StartNew(); - (BaseProperties as FileProperties).GetSystemFileProperties(); + fileProps.GetSystemFileProperties(); stopwatch.Stop(); Debug.WriteLine(string.Format("System file properties were obtained in {0} milliseconds", stopwatch.ElapsedMilliseconds)); } @@ -37,7 +37,10 @@ public override async Task SaveChangesAsync(ListedItem item) using DynamicDialog dialog = DynamicDialogFactory.GetFor_PropertySaveErrorDialog(); try { - await (BaseProperties as FileProperties).SyncPropertyChangesAsync(); + if (BaseProperties is FileProperties fileProps) + { + await fileProps.SyncPropertyChangesAsync(); + } return true; } catch @@ -67,7 +70,10 @@ public override async Task SaveChangesAsync(ListedItem item) private async void ClearPropertiesConfirmation_Click(object sender, RoutedEventArgs e) { ClearPropertiesFlyout.Hide(); - await (BaseProperties as FileProperties).ClearPropertiesAsync(); + if (BaseProperties is FileProperties fileProps) + { + await fileProps.ClearPropertiesAsync(); + } } public override void Dispose() diff --git a/Files/Views/PaneHolderPage.xaml.cs b/Files/Views/PaneHolderPage.xaml.cs index f63b902ec1b7..1b7cfda74bc1 100644 --- a/Files/Views/PaneHolderPage.xaml.cs +++ b/Files/Views/PaneHolderPage.xaml.cs @@ -264,19 +264,9 @@ private void Pane_ContentChanged(object sender, TabItemArguments e) }; } - public DataPackageOperation TabItemDragOver(object sender, DragEventArgs e) - { - return ActivePane?.TabItemDragOver(sender, e) ?? DataPackageOperation.None; - } + public Task TabItemDragOver(object sender, DragEventArgs e) => ActivePane?.TabItemDragOver(sender, e) ?? Task.CompletedTask; - public async Task TabItemDrop(object sender, DragEventArgs e) - { - if (ActivePane != null) - { - return await ActivePane.TabItemDrop(sender, e); - } - return DataPackageOperation.None; - } + public Task TabItemDrop(object sender, DragEventArgs e) => ActivePane?.TabItemDrop(sender, e) ?? Task.CompletedTask; public void OpenPathInNewPane(string path) { diff --git a/Files/Views/WidgetsPage.xaml.cs b/Files/Views/WidgetsPage.xaml.cs index be20d1bfa76f..b18cfad5621b 100644 --- a/Files/Views/WidgetsPage.xaml.cs +++ b/Files/Views/WidgetsPage.xaml.cs @@ -211,6 +211,8 @@ protected override async void OnNavigatedTo(NavigationEventArgs eventArgs) AppInstance.InstanceViewModel.IsPageTypeMtpDevice = false; AppInstance.InstanceViewModel.IsPageTypeRecycleBin = false; AppInstance.InstanceViewModel.IsPageTypeCloudDrive = false; + AppInstance.InstanceViewModel.IsPageTypeFtp = false; + AppInstance.InstanceViewModel.IsPageTypeZipFolder = false; AppInstance.NavToolbarViewModel.CanRefresh = false; AppInstance.NavToolbarViewModel.CanGoBack = AppInstance.CanNavigateBackward; AppInstance.NavToolbarViewModel.CanGoForward = AppInstance.CanNavigateForward;