From 7bdd9806e606b56e5357e662e087629480d45e40 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 17 Feb 2021 14:31:49 +0800 Subject: [PATCH 1/4] Initial support to anonymous ftp enumeration --- Files.Package/Package.appxmanifest | 1 + Files/Files.csproj | 4 + .../FtpStorageEnumerator.cs | 91 +++++++++++++++++++ Files/Interacts/Interaction.cs | 12 ++- Files/ViewModels/ItemViewModel.cs | 49 +++++++++- Files/Views/ModernShellPage.xaml.cs | 9 ++ 6 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 Files/Filesystem/StorageEnumerators/FtpStorageEnumerator.cs diff --git a/Files.Package/Package.appxmanifest b/Files.Package/Package.appxmanifest index aff263813d1a..ce3bc7f3b9b2 100644 --- a/Files.Package/Package.appxmanifest +++ b/Files.Package/Package.appxmanifest @@ -69,6 +69,7 @@ + diff --git a/Files/Files.csproj b/Files/Files.csproj index 8993f2d3e6e7..4ad1c469db01 100644 --- a/Files/Files.csproj +++ b/Files/Files.csproj @@ -180,6 +180,7 @@ + @@ -933,6 +934,9 @@ 2.0.0 + + 33.0.3 + 2.5.2 diff --git a/Files/Filesystem/StorageEnumerators/FtpStorageEnumerator.cs b/Files/Filesystem/StorageEnumerators/FtpStorageEnumerator.cs new file mode 100644 index 000000000000..8ac13493f330 --- /dev/null +++ b/Files/Filesystem/StorageEnumerators/FtpStorageEnumerator.cs @@ -0,0 +1,91 @@ +using ByteSizeLib; +using Files.Extensions; +using FluentFTP; +using Microsoft.Toolkit.Uwp.Extensions; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Windows.Storage; + +namespace Files.Filesystem.StorageEnumerators +{ + public static class FtpStorageEnumerator + { + private static DateTimeOffset ToDateTimeOffset(this DateTime dateTime) + { + var utc = dateTime.ToUniversalTime(); + if (utc <= DateTimeOffset.MinValue.UtcDateTime) + { + return DateTimeOffset.MinValue; + } + if (utc >= DateTimeOffset.MaxValue.UtcDateTime) + { + return DateTimeOffset.MaxValue; + } + return new DateTimeOffset(dateTime); + } + + public static async Task> ListEntries( + string address, + string path, + string returnformat, + IFtpClient ftpClient, + CancellationToken cancellationToken + ) + { + var tempList = new List(); + + if (!ftpClient.IsConnected) + { + await ftpClient.AutoConnectAsync(cancellationToken); + } + + var items = await ftpClient.GetListingAsync(path, FtpListOption.Auto, cancellationToken); + + foreach (var i in items) + { + string itemType; + string itemFileExtension = null; + + if (i.Type == FtpFileSystemObjectType.Directory) + { + itemType = "FileFolderListItem".GetLocalized(); + } + else + { + itemType = "ItemTypeFile".GetLocalized(); + if (i.Name.Contains('.')) + { + itemFileExtension = Path.GetExtension(i.Name); + itemType = itemFileExtension.Trim('.') + " " + itemType; + } + } + + var item = new ListedItem(null, returnformat) + { + PrimaryItemAttribute = i.Type == FtpFileSystemObjectType.Directory ? StorageItemTypes.Folder : StorageItemTypes.File, + FileExtension = itemFileExtension, + LoadFolderGlyph = i.Type == FtpFileSystemObjectType.Directory, + LoadUnknownTypeGlyph = i.Type != FtpFileSystemObjectType.Directory, + ItemName = i.Name, + IsHiddenItem = false, + Opacity = 1, + ItemDateCreatedReal = i.Created.ToDateTimeOffset(), + ItemDateModifiedReal = i.Modified.ToDateTimeOffset(), + ItemType = itemType, + FileSizeBytes = i.Size, + FileSize = ByteSize.FromBytes(i.Size).ToBinaryString().ConvertSizeAbbreviation(), + ItemPath = address + i.FullName, + ItemPropertiesInitialized = true + }; + + tempList.Add(item); + } + + return tempList; + } + } +} diff --git a/Files/Interacts/Interaction.cs b/Files/Interacts/Interaction.cs index b9e99a3f98ef..bc4898ab3f31 100644 --- a/Files/Interacts/Interaction.cs +++ b/Files/Interacts/Interaction.cs @@ -479,10 +479,20 @@ public async Task OpenPath(string path, FilesystemItemType? itemType = nul } var mostRecentlyUsed = Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList; + var isFtp = path.StartsWith("ftp://", StringComparison.OrdinalIgnoreCase) || path.StartsWith("ftps://", StringComparison.OrdinalIgnoreCase); if (itemType == FilesystemItemType.Directory) // OpenDirectory { - if (isShortcutItem) + if (isFtp) + { + AssociatedInstance.NavigationToolbar.PathControlDisplayText = path; + AssociatedInstance.ContentFrame.Navigate(AssociatedInstance.InstanceViewModel.FolderSettings.GetLayoutType(path), new NavigationArguments() + { + NavPathParam = path, + AssociatedTabInstance = AssociatedInstance + }, new SuppressNavigationTransitionInfo()); + } + else if (isShortcutItem) { if (string.IsNullOrEmpty(shortcutTargetPath)) { diff --git a/Files/ViewModels/ItemViewModel.cs b/Files/ViewModels/ItemViewModel.cs index 5c1e624ceb87..353b3d78e67d 100644 --- a/Files/ViewModels/ItemViewModel.cs +++ b/Files/ViewModels/ItemViewModel.cs @@ -6,6 +6,7 @@ using Files.Filesystem.StorageEnumerators; using Files.Helpers; using Files.Helpers.FileListCache; +using FluentFTP; using Microsoft.Toolkit.Uwp.Extensions; using Microsoft.Toolkit.Uwp.Helpers; using Microsoft.Toolkit.Uwp.UI; @@ -47,6 +48,7 @@ public class ItemViewModel : INotifyPropertyChanged, IDisposable private readonly SemaphoreSlim operationSemaphore = new SemaphoreSlim(1, 1); private IntPtr hWatchDir; private IAsyncAction aWatcherAction; + private IFtpClient ftpClient; // files and folders list for manipulating private List filesAndFolders; @@ -938,9 +940,11 @@ await CoreApplication.MainView.ExecuteOnUIThreadAsync(() => AssociatedInstance.NavigationToolbar.CanGoBack = AssociatedInstance.ContentFrame.CanGoBack; AssociatedInstance.NavigationToolbar.CanGoForward = AssociatedInstance.ContentFrame.CanGoForward; + var isFtp = path.StartsWith("ftp://", StringComparison.OrdinalIgnoreCase) || path.StartsWith("ftps://", StringComparison.OrdinalIgnoreCase); + List cacheResult = null; - if (useCache) + if (useCache && !isFtp) { cacheResult = await Task.Run(async () => { @@ -982,6 +986,10 @@ await CoreApplication.MainView.ExecuteOnUIThreadAsync(() => // Recycle bin is special as files are enumerated by the fulltrust process await EnumerateItemsFromSpecialFolderAsync(path); } + else if (isFtp) + { + await EnumerateItemsFromFtpAsync(path); + } else { var sourcePageType = AssociatedInstance.ContentFrame.SourcePageType; @@ -1107,6 +1115,45 @@ public void CloseWatcher() } } + public async Task EnumerateItemsFromFtpAsync(string path) + { + await Task.Run(async () => + { + try + { + string returnformat = Enum.Parse(ApplicationData.Current.LocalSettings.Values[Constants.LocalSettings.DateTimeFormat].ToString()) == TimeStyle.Application ? "D" : "g"; + + var index = path.IndexOf("://") + 3; + var count = path.IndexOf("/", index); + + var address = count == -1 ? path : path.Substring(0, count); + var host = count == -1 ? path.Substring(index) : path.Substring(index, count - index); + var ftpPath = count == -1 ? "/" : path.Substring(count); + + if (ftpClient is null || ftpClient.Host != host) + { + if (ftpClient != null) + { + await ftpClient.DisconnectAsync(); + ftpClient.Dispose(); + } + + ftpClient = new FtpClient(address); + } + + var result = await FtpStorageEnumerator.ListEntries(address, ftpPath, returnformat, ftpClient, addFilesCTS.Token); + filesAndFolders.AddRange(result); + } + catch + { + // ignored + } + }); + + await OrderFilesAndFoldersAsync(); + await ApplyFilesAndFoldersChangesAsync(); + } + public async Task EnumerateItemsFromSpecialFolderAsync(string path) { ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings; diff --git a/Files/Views/ModernShellPage.xaml.cs b/Files/Views/ModernShellPage.xaml.cs index ae4cdf373c9b..2772cfa0ea03 100644 --- a/Files/Views/ModernShellPage.xaml.cs +++ b/Files/Views/ModernShellPage.xaml.cs @@ -657,6 +657,15 @@ public async void CheckPathInput(ItemViewModel instance, string currentInput, st }, new SuppressNavigationTransitionInfo()); } + else if (currentInput.StartsWith("ftp://", StringComparison.OrdinalIgnoreCase) || currentInput.StartsWith("ftps://", StringComparison.OrdinalIgnoreCase)) + { + ContentFrame.Navigate(InstanceViewModel.FolderSettings.GetLayoutType(currentInput), + new NavigationArguments() + { + NavPathParam = currentInput, + AssociatedTabInstance = this + }); // navigate to folder + } else { currentInput = StorageFileExtensions.GetPathWithoutEnvironmentVariable(currentInput); From 004a61d9c166626292c1834d0344d1ea5ce29052 Mon Sep 17 00:00:00 2001 From: Steven He Date: Fri, 19 Feb 2021 15:57:06 +0800 Subject: [PATCH 2/4] FtpStorageItem --- Files/Files.csproj | 2 + .../FtpFilesystemOperaions.cs | 55 +++++++++++ .../StorageFileHelpers/FtpStorageItem.cs | 91 +++++++++++++++++++ Files/MultilingualResources/Files.de-DE.xlf | 2 +- Files/Strings/de-DE/Resources.resw | 60 ++++++++++++ 5 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 Files/Filesystem/FilesystemOperations/FtpFilesystemOperaions.cs create mode 100644 Files/Filesystem/StorageFileHelpers/FtpStorageItem.cs diff --git a/Files/Files.csproj b/Files/Files.csproj index 16037a19662b..b552fa69db9d 100644 --- a/Files/Files.csproj +++ b/Files/Files.csproj @@ -178,6 +178,7 @@ + @@ -186,6 +187,7 @@ + diff --git a/Files/Filesystem/FilesystemOperations/FtpFilesystemOperaions.cs b/Files/Filesystem/FilesystemOperations/FtpFilesystemOperaions.cs new file mode 100644 index 000000000000..91875d815b92 --- /dev/null +++ b/Files/Filesystem/FilesystemOperations/FtpFilesystemOperaions.cs @@ -0,0 +1,55 @@ +using Files.Enums; +using Files.Filesystem.FilesystemHistory; +using Files.Filesystem.StorageFileHelpers; +using FluentFTP; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Windows.Storage; + +namespace Files.Filesystem.FilesystemOperations +{ + public class FtpFilesystemOperaions : IFilesystemOperations + { + public IFtpClient FtpClient { get; set; } + + private async Task EnsureConnected() + { + if (FtpClient is null) + { + return false; + } + + await FtpClient.AutoConnectAsync(); + return true; + } + + public async Task CopyAsync(IStorageItem source, string destination, IProgress progress, IProgress errorCode, CancellationToken cancellationToken) + { + if (!(source is FtpStorageItem item) || !await EnsureConnected()) + { + return null; + } + + throw new NotImplementedException(); + } + public async Task CopyAsync(IStorageItemWithPath source, string destination, IProgress progress, IProgress errorCode, CancellationToken cancellationToken) => throw new NotImplementedException(); + public async Task CreateAsync(IStorageItemWithPath source, IProgress errorCode, CancellationToken cancellationToken) => throw new NotImplementedException(); + public async Task DeleteAsync(IStorageItem source, IProgress progress, IProgress errorCode, bool permanently, CancellationToken cancellationToken) => throw new NotImplementedException(); + public async Task DeleteAsync(IStorageItemWithPath source, IProgress progress, IProgress errorCode, bool permanently, CancellationToken cancellationToken) => throw new NotImplementedException(); + + public void Dispose() + { + + } + + public async Task MoveAsync(IStorageItem source, string destination, IProgress progress, IProgress errorCode, CancellationToken cancellationToken) => throw new NotImplementedException(); + public async Task MoveAsync(IStorageItemWithPath source, string destination, IProgress progress, IProgress errorCode, CancellationToken cancellationToken) => throw new NotImplementedException(); + public async Task RenameAsync(IStorageItem source, string newName, NameCollisionOption collision, IProgress errorCode, CancellationToken cancellationToken) => throw new NotImplementedException(); + public async Task RenameAsync(IStorageItemWithPath source, string newName, NameCollisionOption collision, IProgress errorCode, CancellationToken cancellationToken) => throw new NotImplementedException(); + public async Task RestoreFromTrashAsync(IStorageItemWithPath source, string destination, IProgress progress, IProgress errorCode, CancellationToken cancellationToken) => throw new NotImplementedException(); + } +} diff --git a/Files/Filesystem/StorageFileHelpers/FtpStorageItem.cs b/Files/Filesystem/StorageFileHelpers/FtpStorageItem.cs new file mode 100644 index 000000000000..cb0f8e8f8f5c --- /dev/null +++ b/Files/Filesystem/StorageFileHelpers/FtpStorageItem.cs @@ -0,0 +1,91 @@ +using FluentFTP; +using System; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.Storage; +using Windows.Storage.FileProperties; +using Windows.System.Threading; + +namespace Files.Filesystem.StorageFileHelpers +{ + public class FtpStorageItem : IStorageItem + { + public FtpStorageItem(IFtpClient ftpClient, string name, string path, FileAttributes attributes, DateTimeOffset dateCreated) + { + FtpClient = ftpClient; + Name = name; + Path = path; + Attributes = attributes; + DateCreated = dateCreated; + } + + public IFtpClient FtpClient { get; set; } + + private async Task EnsureConnected() + { + if (FtpClient is null) + { + return false; + } + + await FtpClient.AutoConnectAsync(); + return true; + } + + public IAsyncAction RenameAsync(string desiredName) + { + return ThreadPool.RunAsync(async (x) => + { + if (!await EnsureConnected()) + { + return; + } + + await FtpClient.RenameAsync(Path, string.Join("/", Path.GetFtpDirectoryName(), desiredName)); + }); + } + + public IAsyncAction RenameAsync(string desiredName, NameCollisionOption option) => throw new NotImplementedException(); + public IAsyncAction DeleteAsync() + { + return ThreadPool.RunAsync(async (x) => + { + if (!await EnsureConnected()) + { + return; + } + if ((Attributes & FileAttributes.Directory) != 0) + { + await FtpClient.DeleteDirectoryAsync(Path); + } + else + { + await FtpClient.DeleteFileAsync(Path); + } + }); + } + + public IAsyncAction DeleteAsync(StorageDeleteOption option) => throw new NotImplementedException(); + public IAsyncOperation GetBasicPropertiesAsync() => throw new NotImplementedException(); + public bool IsOfType(StorageItemTypes type) + { + switch (type) + { + case StorageItemTypes.File: + return (Attributes & FileAttributes.Directory) == 0; + case StorageItemTypes.Folder: + return (Attributes & FileAttributes.Directory) != 0; + default: + return false; + } + } + + public FileAttributes Attributes { get; private set; } + + public DateTimeOffset DateCreated { get; private set; } + + public string Name { get; private set; } + + public string Path { get; private set; } + } +} diff --git a/Files/MultilingualResources/Files.de-DE.xlf b/Files/MultilingualResources/Files.de-DE.xlf index 4c6cdb861b00..9e59f712685e 100644 --- a/Files/MultilingualResources/Files.de-DE.xlf +++ b/Files/MultilingualResources/Files.de-DE.xlf @@ -2367,4 +2367,4 @@ - + \ No newline at end of file diff --git a/Files/Strings/de-DE/Resources.resw b/Files/Strings/de-DE/Resources.resw index 9298ea110e15..083c1f6773fe 100644 --- a/Files/Strings/de-DE/Resources.resw +++ b/Files/Strings/de-DE/Resources.resw @@ -1710,4 +1710,64 @@ Mehrfachauswahl aktivieren (derzeitig nur für Kachel- und Gridansicht) + + Wenn du die Dateierweitung veränderts, wird die Datei möglicherweise unbrauchbar. Bist du sicher das du sie ändern möchtest? + + + CD-Laufwerk + + + Cloudlaufwerk + + + Fixed Disk Drive + + + Diskettenlauferk + + + Netzwerklaufwerk + + + Nicht hinzugefügtes Laufwerk + + + RAM Disk Laufwerk + + + Entfernbares Speichermedium + + + Unbekannt + + + Virtuelles Laufwerk + + + Erstelldatum + + + Erstelldatum + + + Mehr Optionen... + + + Netwerklaufwerk hinzufügen + + + Verbindung trennen + + + Anzahl an vorzeitigen Zwischenspeichervorgängen (Eine kleinerer Wert ist für Festplatten empfohlen + + + Vorzeitiges Zwischenspeichern benutzen (Einträge in Unterverzeichnissen werden beim Navigieren vorgeladen) + + + Mehr Gruppenoptionen + + + Mehr Laufwerkoptionen + \ No newline at end of file From 437fdaabcbae4542e948a4fcb450c2abf0812f32 Mon Sep 17 00:00:00 2001 From: Steven He Date: Wed, 24 Feb 2021 21:36:30 +0800 Subject: [PATCH 3/4] some ftp work --- Files/Extensions/TaskExtensions.cs | 21 --- Files/Files.csproj | 1 - .../FtpFilesystemOperaions.cs | 133 ++++++++++++++---- .../StorageFileHelpers/FtpStorageItem.cs | 27 +++- Files/ViewModels/ItemViewModel.cs | 4 +- 5 files changed, 132 insertions(+), 54 deletions(-) delete mode 100644 Files/Extensions/TaskExtensions.cs diff --git a/Files/Extensions/TaskExtensions.cs b/Files/Extensions/TaskExtensions.cs deleted file mode 100644 index 75d5dbd507e1..000000000000 --- a/Files/Extensions/TaskExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Threading.Tasks; - -namespace Files.Extensions -{ - internal static class TaskExtensions - { -#pragma warning disable RCS1175 // Unused this parameter. -#pragma warning disable IDE0060 // Remove unused parameter - /// - /// This function is to explicitly state that we know that we're running task without awaiting. - /// This makes Visual Studio drop the warning, but the programmer intent is still clearly stated. - /// - /// - internal static void Forget(this Task task) - { - // do nothing, just forget about the task - } -#pragma warning restore IDE0060 // Remove unused parameter -#pragma warning restore RCS1175 // Unused this parameter. - } -} diff --git a/Files/Files.csproj b/Files/Files.csproj index b552fa69db9d..2054644e59fe 100644 --- a/Files/Files.csproj +++ b/Files/Files.csproj @@ -177,7 +177,6 @@ - diff --git a/Files/Filesystem/FilesystemOperations/FtpFilesystemOperaions.cs b/Files/Filesystem/FilesystemOperations/FtpFilesystemOperaions.cs index 91875d815b92..2771884bbd59 100644 --- a/Files/Filesystem/FilesystemOperations/FtpFilesystemOperaions.cs +++ b/Files/Filesystem/FilesystemOperations/FtpFilesystemOperaions.cs @@ -3,53 +3,132 @@ using Files.Filesystem.StorageFileHelpers; using FluentFTP; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.IO; using System.Threading; using System.Threading.Tasks; using Windows.Storage; -namespace Files.Filesystem.FilesystemOperations +namespace Files.Filesystem { public class FtpFilesystemOperaions : IFilesystemOperations { - public IFtpClient FtpClient { get; set; } - - private async Task EnsureConnected() + public async Task CopyAsync(IStorageItem source, string destination, IProgress progress, IProgress errorCode, CancellationToken cancellationToken) { - if (FtpClient is null) + await Task.Run(async () => { - return false; - } + if (!(source is FtpStorageItem item) || !await item.EnsureConnected()) + { + return; + } + + var ftpProgress = new Progress(); + + errorCode.Report(FileSystemStatusCode.InProgress); + + ftpProgress.ProgressChanged += (_, args) => + { + progress.Report(Convert.ToSingle(args.Progress)); + }; + + try + { + + if (item.IsOfType(StorageItemTypes.File)) + { + using var fileStream = new FileStream(destination, FileMode.Create); + await item.FtpClient.DownloadAsync(fileStream, item.Path, 0, ftpProgress, cancellationToken); + errorCode.Report(FileSystemStatusCode.Success); + return; + } + else + { + await item.FtpClient.DownloadDirectoryAsync(destination, item.Path, FtpFolderSyncMode.Update, FtpLocalExists.Append, FtpVerify.None, null, ftpProgress, cancellationToken); + } + } + catch (UnauthorizedAccessException) + { + errorCode.Report(FileSystemStatusCode.Unauthorized); + } + catch + { + errorCode.Report(FileSystemStatusCode.Generic); + } + }); - await FtpClient.AutoConnectAsync(); - return true; + return null; } - public async Task CopyAsync(IStorageItem source, string destination, IProgress progress, IProgress errorCode, CancellationToken cancellationToken) + public Task CopyAsync(IStorageItemWithPath source, string destination, IProgress progress, IProgress errorCode, CancellationToken cancellationToken) + { + return CopyAsync(source.Item, destination, progress, errorCode, cancellationToken); + } + + public Task CreateAsync(IStorageItemWithPath source, IProgress errorCode, CancellationToken cancellationToken) => throw new NotImplementedException(); + public async Task DeleteAsync(IStorageItem source, IProgress progress, IProgress errorCode, bool permanently, CancellationToken cancellationToken) { - if (!(source is FtpStorageItem item) || !await EnsureConnected()) + await Task.Run(async () => { - return null; - } + errorCode.Report(FileSystemStatusCode.InProgress); + progress.Report(0); + try + { + await source.DeleteAsync(); + progress.Report(100); + errorCode.Report(FileSystemStatusCode.Success); + } + catch + { + errorCode.Report(FileSystemStatusCode.Generic); + } + }); - throw new NotImplementedException(); + return null; + } + public Task DeleteAsync(IStorageItemWithPath source, IProgress progress, IProgress errorCode, bool permanently, CancellationToken cancellationToken) + { + return DeleteAsync(source.Item, progress, errorCode, permanently, cancellationToken); } - public async Task CopyAsync(IStorageItemWithPath source, string destination, IProgress progress, IProgress errorCode, CancellationToken cancellationToken) => throw new NotImplementedException(); - public async Task CreateAsync(IStorageItemWithPath source, IProgress errorCode, CancellationToken cancellationToken) => throw new NotImplementedException(); - public async Task DeleteAsync(IStorageItem source, IProgress progress, IProgress errorCode, bool permanently, CancellationToken cancellationToken) => throw new NotImplementedException(); - public async Task DeleteAsync(IStorageItemWithPath source, IProgress progress, IProgress errorCode, bool permanently, CancellationToken cancellationToken) => throw new NotImplementedException(); - public void Dispose() + public void Dispose() { } + + public async Task MoveAsync(IStorageItem source, string destination, IProgress progress, IProgress errorCode, CancellationToken cancellationToken) + { + await CopyAsync(source, destination, progress, errorCode, cancellationToken); + await DeleteAsync(source, progress, errorCode, true, cancellationToken); + + return null; + } + public Task MoveAsync(IStorageItemWithPath source, string destination, IProgress progress, IProgress errorCode, CancellationToken cancellationToken) + { + return MoveAsync(source.Item, destination, progress, errorCode, cancellationToken); + } + public async Task RenameAsync(IStorageItem source, string newName, NameCollisionOption collision, IProgress errorCode, CancellationToken cancellationToken) { + await Task.Run(async () => + { + errorCode.Report(FileSystemStatusCode.InProgress); ; + try + { + await source.RenameAsync(newName, collision); + errorCode.Report(FileSystemStatusCode.Success); + } + catch + { + errorCode.Report(FileSystemStatusCode.Generic); + } + }); + + return null; + } + public Task RenameAsync(IStorageItemWithPath source, string newName, NameCollisionOption collision, IProgress errorCode, CancellationToken cancellationToken) + { + return RenameAsync(source.Item, newName, collision, errorCode, cancellationToken); } - public async Task MoveAsync(IStorageItem source, string destination, IProgress progress, IProgress errorCode, CancellationToken cancellationToken) => throw new NotImplementedException(); - public async Task MoveAsync(IStorageItemWithPath source, string destination, IProgress progress, IProgress errorCode, CancellationToken cancellationToken) => throw new NotImplementedException(); - public async Task RenameAsync(IStorageItem source, string newName, NameCollisionOption collision, IProgress errorCode, CancellationToken cancellationToken) => throw new NotImplementedException(); - public async Task RenameAsync(IStorageItemWithPath source, string newName, NameCollisionOption collision, IProgress errorCode, CancellationToken cancellationToken) => throw new NotImplementedException(); - public async Task RestoreFromTrashAsync(IStorageItemWithPath source, string destination, IProgress progress, IProgress errorCode, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task RestoreFromTrashAsync(IStorageItemWithPath source, string destination, IProgress progress, IProgress errorCode, CancellationToken cancellationToken) + { + throw new NotSupportedException(); + } } } diff --git a/Files/Filesystem/StorageFileHelpers/FtpStorageItem.cs b/Files/Filesystem/StorageFileHelpers/FtpStorageItem.cs index cb0f8e8f8f5c..95c977da0585 100644 --- a/Files/Filesystem/StorageFileHelpers/FtpStorageItem.cs +++ b/Files/Filesystem/StorageFileHelpers/FtpStorageItem.cs @@ -8,7 +8,7 @@ namespace Files.Filesystem.StorageFileHelpers { - public class FtpStorageItem : IStorageItem + public class FtpStorageItem : IStorageItem, IStorageItemWithPath { public FtpStorageItem(IFtpClient ftpClient, string name, string path, FileAttributes attributes, DateTimeOffset dateCreated) { @@ -21,7 +21,7 @@ public FtpStorageItem(IFtpClient ftpClient, string name, string path, FileAttrib public IFtpClient FtpClient { get; set; } - private async Task EnsureConnected() + public async Task EnsureConnected() { if (FtpClient is null) { @@ -86,6 +86,27 @@ public bool IsOfType(StorageItemTypes type) public string Name { get; private set; } - public string Path { get; private set; } + public string Path { get; set; } + + public FilesystemItemType ItemType + { + get + { + if (IsOfType(StorageItemTypes.File)) + { + return FilesystemItemType.File; + } + else + { + return FilesystemItemType.Directory; + } + } + } + + public IStorageItem Item + { + get => this; + set => throw new InvalidOperationException(); + } } } diff --git a/Files/ViewModels/ItemViewModel.cs b/Files/ViewModels/ItemViewModel.cs index dde8cb218dbf..34bdff274b8c 100644 --- a/Files/ViewModels/ItemViewModel.cs +++ b/Files/ViewModels/ItemViewModel.cs @@ -1009,7 +1009,7 @@ await CoreApplication.MainView.ExecuteOnUIThreadAsync(() => // run background tasks to iterate through folders and cache all of them preemptively var folders = filesAndFolders.Where(e => e.PrimaryItemAttribute == StorageItemTypes.Folder); var currentStorageFolderSnapshot = currentStorageFolder; - Task.Run(async () => + _ = Task.Run(async () => { try { @@ -1035,7 +1035,7 @@ await folders.AsyncParallelForEach(async (folder) => // ignore exception. This is fine, it's only a caching that can fail NLog.LogManager.GetCurrentClassLogger().Error(ex, ex.Message); } - }).Forget(); + }); } } From f0d48564b07a4b895fd79903fdeea92044c56e5f Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 18 Mar 2021 15:19:12 +0800 Subject: [PATCH 4/4] WIP: IStorageItem --- Files.Package/Package.appxmanifest | 1 - Files/Files.csproj | 2 +- .../FilesystemOperations.cs | 105 ++++++----- .../FtpFilesystemOperaions.cs | 6 +- .../IFilesystemOperations.cs | 12 +- Files/Filesystem/ListedItem.cs | 8 +- .../FtpStorageEnumerator.cs | 5 +- .../StorageFileHelpers/StorageFileWithPath.cs | 21 ++- Files/Helpers/StorageItemHelpers.cs | 178 +++++++++--------- Files/Interacts/Interaction.cs | 42 +++-- Files/ViewModels/ItemViewModel.cs | 28 ++- Files/ViewModels/Previews/BasePreviewModel.cs | 10 +- .../Previews/HtmlPreviewViewModel.cs | 5 +- .../Previews/ImagePreviewViewModel.cs | 29 +-- .../Previews/MarkdownPreviewViewModel.cs | 9 +- .../Previews/MediaPreviewViewModel.cs | 6 +- .../Previews/PDFPreviewViewModel.cs | 26 ++- .../Previews/RichTextPreviewViewModel.cs | 6 +- .../Previews/TextPreviewViewModel.cs | 38 ++-- Files/Views/ModernShellPage.xaml.cs | 2 +- 20 files changed, 315 insertions(+), 224 deletions(-) diff --git a/Files.Package/Package.appxmanifest b/Files.Package/Package.appxmanifest index cb3e8b55b8bb..4f2c507eb0f8 100644 --- a/Files.Package/Package.appxmanifest +++ b/Files.Package/Package.appxmanifest @@ -69,6 +69,5 @@ - diff --git a/Files/Files.csproj b/Files/Files.csproj index d8c170b4d97d..d5b21d09e1f2 100644 --- a/Files/Files.csproj +++ b/Files/Files.csproj @@ -39,7 +39,7 @@ false prompt true - 8.0 + latest true false diff --git a/Files/Filesystem/FilesystemOperations/FilesystemOperations.cs b/Files/Filesystem/FilesystemOperations/FilesystemOperations.cs index 4a57e82eaa56..9f698bf074c9 100644 --- a/Files/Filesystem/FilesystemOperations/FilesystemOperations.cs +++ b/Files/Filesystem/FilesystemOperations/FilesystemOperations.cs @@ -48,7 +48,7 @@ public FilesystemOperations(IShellPage associatedInstance) #region IFilesystemOperations - public async Task CreateAsync(IStorageItemWithPath source, IProgress errorCode, CancellationToken cancellationToken) + public async Task CreateAsync(IStorageItem source, IProgress errorCode, CancellationToken cancellationToken) { try { @@ -112,7 +112,7 @@ public async Task CopyAsync(IStorageItem source, cancellationToken); } - public async Task CopyAsync(IStorageItemWithPath source, + public async Task CopyAsync(IStorageItem source, string destination, IProgress progress, IProgress errorCode, @@ -224,57 +224,66 @@ await DialogDisplayHelper.ShowDialogAsync( } else if (source.ItemType == FilesystemItemType.File) { - var fsResult = (FilesystemResult)await Task.Run(() => NativeFileOperationsHelper.CopyFileFromApp(source.Path, destination, true)); - - if (!fsResult) + FilesystemResult fsResult; + if (IsFtp(source.Path)) { - Debug.WriteLine(System.Runtime.InteropServices.Marshal.GetLastWin32Error()); - - FilesystemResult destinationResult = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(Path.GetDirectoryName(destination)); - var sourceResult = await source.ToStorageItemResult(associatedInstance); - fsResult = sourceResult.ErrorCode | destinationResult.ErrorCode; + var ftpOperation = new FtpFilesystemOperaions(); + return await ftpOperation.CopyAsync(source, destination, progress, errorCode, cancellationToken); + } + else + { + fsResult = (FilesystemResult)await Task.Run(() => NativeFileOperationsHelper.CopyFileFromApp(source.Path, destination, true)); - if (fsResult) + if (!fsResult) { - var file = (StorageFile)sourceResult; - var fsResultCopy = await FilesystemTasks.Wrap(() => file.CopyAsync(destinationResult.Result, Path.GetFileName(file.Name), NameCollisionOption.FailIfExists).AsTask()); - if (fsResultCopy == FileSystemStatusCode.AlreadyExists) + Debug.WriteLine(System.Runtime.InteropServices.Marshal.GetLastWin32Error()); + + FilesystemResult destinationResult = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(Path.GetDirectoryName(destination)); + var sourceResult = await source.ToStorageItemResult(associatedInstance); + fsResult = sourceResult.ErrorCode | destinationResult.ErrorCode; + + if (fsResult) { - var ItemAlreadyExistsDialog = new ContentDialog() + var file = (StorageFile)sourceResult; + var fsResultCopy = await FilesystemTasks.Wrap(() => file.CopyAsync(destinationResult.Result, Path.GetFileName(file.Name), NameCollisionOption.FailIfExists).AsTask()); + if (fsResultCopy == FileSystemStatusCode.AlreadyExists) { - Title = "ItemAlreadyExistsDialogTitle".GetLocalized(), - Content = "ItemAlreadyExistsDialogContent".GetLocalized(), - PrimaryButtonText = "ItemAlreadyExistsDialogPrimaryButtonText".GetLocalized(), - SecondaryButtonText = "ItemAlreadyExistsDialogSecondaryButtonText".GetLocalized(), - CloseButtonText = "ItemAlreadyExistsDialogCloseButtonText".GetLocalized() - }; + var ItemAlreadyExistsDialog = new ContentDialog() + { + Title = "ItemAlreadyExistsDialogTitle".GetLocalized(), + Content = "ItemAlreadyExistsDialogContent".GetLocalized(), + PrimaryButtonText = "ItemAlreadyExistsDialogPrimaryButtonText".GetLocalized(), + SecondaryButtonText = "ItemAlreadyExistsDialogSecondaryButtonText".GetLocalized(), + CloseButtonText = "ItemAlreadyExistsDialogCloseButtonText".GetLocalized() + }; - if (Interacts.Interaction.IsAnyContentDialogOpen()) - { - // Only a single ContentDialog can be open at any time. - return null; - } - ContentDialogResult result = await ItemAlreadyExistsDialog.ShowAsync(); + if (Interacts.Interaction.IsAnyContentDialogOpen()) + { + // Only a single ContentDialog can be open at any time. + return null; + } + ContentDialogResult result = await ItemAlreadyExistsDialog.ShowAsync(); - if (result == ContentDialogResult.Primary) - { - fsResultCopy = await FilesystemTasks.Wrap(() => file.CopyAsync(destinationResult.Result, Path.GetFileName(file.Name), NameCollisionOption.GenerateUniqueName).AsTask()); - } - else if (result == ContentDialogResult.Secondary) - { - fsResultCopy = await FilesystemTasks.Wrap(() => file.CopyAsync(destinationResult.Result, Path.GetFileName(file.Name), NameCollisionOption.ReplaceExisting).AsTask()); - return null; // Cannot undo overwrite operation + if (result == ContentDialogResult.Primary) + { + fsResultCopy = await FilesystemTasks.Wrap(() => file.CopyAsync(destinationResult.Result, Path.GetFileName(file.Name), NameCollisionOption.GenerateUniqueName).AsTask()); + } + else if (result == ContentDialogResult.Secondary) + { + fsResultCopy = await FilesystemTasks.Wrap(() => file.CopyAsync(destinationResult.Result, Path.GetFileName(file.Name), NameCollisionOption.ReplaceExisting).AsTask()); + return null; // Cannot undo overwrite operation + } + else + { + return null; + } } - else + if (fsResultCopy) { - return null; + copiedItem = fsResultCopy.Result; } + fsResult = fsResultCopy; } - if (fsResultCopy) - { - copiedItem = fsResultCopy.Result; - } - fsResult = fsResultCopy; } } errorCode?.Report(fsResult.ErrorCode); @@ -320,7 +329,7 @@ public async Task MoveAsync(IStorageItem source, cancellationToken); } - public async Task MoveAsync(IStorageItemWithPath source, + public async Task MoveAsync(IStorageItem source, string destination, IProgress progress, IProgress errorCode, @@ -541,7 +550,7 @@ public async Task DeleteAsync(IStorageItem source, cancellationToken); } - public async Task DeleteAsync(IStorageItemWithPath source, + public async Task DeleteAsync(IStorageItem source, IProgress progress, IProgress errorCode, bool permanently, @@ -661,7 +670,7 @@ public async Task RenameAsync(IStorageItem source, return await RenameAsync(StorageItemHelpers.FromStorageItem(source), newName, collision, errorCode, cancellationToken); } - public async Task RenameAsync(IStorageItemWithPath source, + public async Task RenameAsync(IStorageItem source, string newName, NameCollisionOption collision, IProgress errorCode, @@ -760,7 +769,7 @@ public async Task RenameAsync(IStorageItemWithPath source, return null; } - public async Task RestoreFromTrashAsync(IStorageItemWithPath source, + public async Task RestoreFromTrashAsync(IStorageItem source, string destination, IProgress progress, IProgress errorCode, @@ -846,6 +855,10 @@ await associatedInstance.FilesystemViewModel.GetFileFromPathAsync(iFilePath) #endregion IFilesystemOperations #region Helpers + private static bool IsFtp(string path) + { + return path.StartsWith("ftp://", StringComparison.OrdinalIgnoreCase) || path.StartsWith("ftps://", StringComparison.OrdinalIgnoreCase); + } private async static Task CloneDirectoryAsync(IStorageFolder sourceFolder, IStorageFolder destinationFolder, string sourceRootName, CreationCollisionOption collision = CreationCollisionOption.FailIfExists) { diff --git a/Files/Filesystem/FilesystemOperations/FtpFilesystemOperaions.cs b/Files/Filesystem/FilesystemOperations/FtpFilesystemOperaions.cs index 2771884bbd59..ddc64f9f5151 100644 --- a/Files/Filesystem/FilesystemOperations/FtpFilesystemOperaions.cs +++ b/Files/Filesystem/FilesystemOperations/FtpFilesystemOperaions.cs @@ -16,7 +16,7 @@ public async Task CopyAsync(IStorageItem source, string destina { await Task.Run(async () => { - if (!(source is FtpStorageItem item) || !await item.EnsureConnected()) + if (!(source is StorageFile item)) { return; } @@ -36,13 +36,13 @@ await Task.Run(async () => if (item.IsOfType(StorageItemTypes.File)) { using var fileStream = new FileStream(destination, FileMode.Create); - await item.FtpClient.DownloadAsync(fileStream, item.Path, 0, ftpProgress, cancellationToken); + await item.CopyAsync(await StorageFolder.GetFolderFromPathAsync(destination)); errorCode.Report(FileSystemStatusCode.Success); return; } else { - await item.FtpClient.DownloadDirectoryAsync(destination, item.Path, FtpFolderSyncMode.Update, FtpLocalExists.Append, FtpVerify.None, null, ftpProgress, cancellationToken); + throw new NotImplementedException(); } } catch (UnauthorizedAccessException) diff --git a/Files/Filesystem/FilesystemOperations/IFilesystemOperations.cs b/Files/Filesystem/FilesystemOperations/IFilesystemOperations.cs index 0c1f0cfb7759..80c4ea4b6ae1 100644 --- a/Files/Filesystem/FilesystemOperations/IFilesystemOperations.cs +++ b/Files/Filesystem/FilesystemOperations/IFilesystemOperations.cs @@ -29,7 +29,7 @@ public interface IFilesystemOperations : IDisposable ///
/// Destination: null /// - Task CreateAsync(IStorageItemWithPath source, IProgress errorCode, CancellationToken cancellationToken); + Task CreateAsync(IStorageItem source, IProgress errorCode, CancellationToken cancellationToken); /// /// Copies to fullPath @@ -65,7 +65,7 @@ Task CopyAsync(IStorageItem source, ///
/// Destination: The item fullPath (as ) the was copied /// - Task CopyAsync(IStorageItemWithPath source, + Task CopyAsync(IStorageItem source, string destination, IProgress progress, IProgress errorCode, @@ -105,7 +105,7 @@ Task MoveAsync(IStorageItem source, ///
/// Destination: The item fullPath (as ) the was moved /// - Task MoveAsync(IStorageItemWithPath source, + Task MoveAsync(IStorageItem source, string destination, IProgress progress, IProgress errorCode, @@ -154,7 +154,7 @@ Task DeleteAsync(IStorageItem source, ///
/// If was false, returns path to recycled item /// - Task DeleteAsync(IStorageItemWithPath source, + Task DeleteAsync(IStorageItem source, IProgress progress, IProgress errorCode, bool permanently, @@ -194,7 +194,7 @@ Task RenameAsync(IStorageItem source, ///
/// Destination: The renamed item fullPath (as ) /// - Task RenameAsync(IStorageItemWithPath source, + Task RenameAsync(IStorageItem source, string newName, NameCollisionOption collision, IProgress errorCode, @@ -214,7 +214,7 @@ Task RenameAsync(IStorageItemWithPath source, ///
/// Destination: The item fullPath (as ) the has been restored /// - Task RestoreFromTrashAsync(IStorageItemWithPath source, + Task RestoreFromTrashAsync(IStorageItem source, string destination, IProgress progress, IProgress errorCode, diff --git a/Files/Filesystem/ListedItem.cs b/Files/Filesystem/ListedItem.cs index 1d8da5e7d074..3c8fa1d84378 100644 --- a/Files/Filesystem/ListedItem.cs +++ b/Files/Filesystem/ListedItem.cs @@ -292,12 +292,12 @@ public override string ToString() public bool IsPinned => App.SidebarPinnedController.Model.FavoriteItems.Contains(itemPath); - private StorageFile itemFile; + private IStorageItem storageItem; - public StorageFile ItemFile + public IStorageItem StorageItem { - get => itemFile; - set => SetProperty(ref itemFile, value); + get => storageItem; + set => SetProperty(ref storageItem, value); } } diff --git a/Files/Filesystem/StorageEnumerators/FtpStorageEnumerator.cs b/Files/Filesystem/StorageEnumerators/FtpStorageEnumerator.cs index 8ac13493f330..47d4c2ad78f8 100644 --- a/Files/Filesystem/StorageEnumerators/FtpStorageEnumerator.cs +++ b/Files/Filesystem/StorageEnumerators/FtpStorageEnumerator.cs @@ -1,5 +1,6 @@ using ByteSizeLib; using Files.Extensions; +using Files.Filesystem.StorageFileHelpers; using FluentFTP; using Microsoft.Toolkit.Uwp.Extensions; using System; @@ -79,7 +80,9 @@ CancellationToken cancellationToken FileSizeBytes = i.Size, FileSize = ByteSize.FromBytes(i.Size).ToBinaryString().ConvertSizeAbbreviation(), ItemPath = address + i.FullName, - ItemPropertiesInitialized = true + StorageItem = new FtpStorageItem(ftpClient, i.Name, address + i.FullName, + i.Type == FtpFileSystemObjectType.Directory ? Windows.Storage.FileAttributes.Directory : Windows.Storage.FileAttributes.Normal, + i.Created.ToDateTimeOffset()) }; tempList.Add(item); diff --git a/Files/Filesystem/StorageFileHelpers/StorageFileWithPath.cs b/Files/Filesystem/StorageFileHelpers/StorageFileWithPath.cs index f5397dc1efb3..9c1f8617f3b2 100644 --- a/Files/Filesystem/StorageFileHelpers/StorageFileWithPath.cs +++ b/Files/Filesystem/StorageFileHelpers/StorageFileWithPath.cs @@ -1,8 +1,12 @@ -using Windows.Storage; +using System; +using System.Runtime.Serialization; +using Windows.Foundation; +using Windows.Storage; +using Windows.Storage.FileProperties; namespace Files.Filesystem { - public class StorageFileWithPath : IStorageItemWithPath + public class StorageFileWithPath : IStorageItem { public StorageFile File { @@ -31,5 +35,18 @@ public StorageFileWithPath(StorageFile file, string path) File = file; Path = path; } + + public IAsyncAction RenameAsync(string desiredName) => File.RenameAsync(desiredName); + public IAsyncAction RenameAsync(string desiredName, NameCollisionOption option) => File.RenameAsync(desiredName, option); + public IAsyncAction DeleteAsync() => File.DeleteAsync(); + public IAsyncAction DeleteAsync(StorageDeleteOption option) => File.DeleteAsync(option); + public IAsyncOperation GetBasicPropertiesAsync() => File.GetBasicPropertiesAsync(); + public bool IsOfType(StorageItemTypes type) => File.IsOfType(type); + + public FileAttributes Attributes => File.Attributes; + + public DateTimeOffset DateCreated => File.DateCreated; + + public string Name => File.Name; } } \ No newline at end of file diff --git a/Files/Helpers/StorageItemHelpers.cs b/Files/Helpers/StorageItemHelpers.cs index cabd0177c849..3212f8f9f6e9 100644 --- a/Files/Helpers/StorageItemHelpers.cs +++ b/Files/Helpers/StorageItemHelpers.cs @@ -10,108 +10,108 @@ namespace Files.Helpers ///
public static class StorageItemHelpers { - public static async Task ToStorageItem(this IStorageItemWithPath item, IShellPage associatedInstance = null) - { - return (await item.ToStorageItemResult(associatedInstance)).Result; - } + //public static async Task ToStorageItem(this IStorageItemWithPath item, IShellPage associatedInstance = null) + //{ + // return (await item.ToStorageItemResult(associatedInstance)).Result; + //} - public static async Task ToStorageItem(string path, IShellPage associatedInstance = null) where TOut : IStorageItem - { - FilesystemResult file = null; - FilesystemResult folder = null; + //public static async Task ToStorageItem(string path, IShellPage associatedInstance = null) where TOut : IStorageItem + //{ + // FilesystemResult file = null; + // FilesystemResult folder = null; - if (associatedInstance == null) - { - file = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFileFromPathAsync(path)); + // if (associatedInstance == null) + // { + // file = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFileFromPathAsync(path)); - if (!file) - { - folder = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(path)); - } - } - else - { - file = await associatedInstance?.FilesystemViewModel?.GetFileFromPathAsync(path); + // if (!file) + // { + // folder = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(path)); + // } + // } + // else + // { + // file = await associatedInstance?.FilesystemViewModel?.GetFileFromPathAsync(path); - if (!file) - { - folder = await associatedInstance?.FilesystemViewModel?.GetFolderFromPathAsync(path); - } - } + // if (!file) + // { + // folder = await associatedInstance?.FilesystemViewModel?.GetFolderFromPathAsync(path); + // } + // } - if (file) - { - return (TOut)(IStorageItem)file.Result; - } - else if (folder) - { - return (TOut)(IStorageItem)folder.Result; - } + // if (file) + // { + // return (TOut)(IStorageItem)file.Result; + // } + // else if (folder) + // { + // return (TOut)(IStorageItem)folder.Result; + // } - return default(TOut); - } + // return default(TOut); + //} - public static async Task> ToStorageItemResult(this IStorageItemWithPath item, IShellPage associatedInstance = null) - { - var returnedItem = new FilesystemResult(null, FileSystemStatusCode.Generic); - if (!string.IsNullOrEmpty(item.Path)) - { - 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))); - } - if (returnedItem.Result == null && item.Item != null) - { - returnedItem = new FilesystemResult(item.Item, FileSystemStatusCode.Success); - } - return returnedItem; - } + //public static async Task> ToStorageItemResult(this IStorageItemWithPath item, IShellPage associatedInstance = null) + //{ + // var returnedItem = new FilesystemResult(null, FileSystemStatusCode.Generic); + // if (!string.IsNullOrEmpty(item.Path)) + // { + // 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))); + // } + // if (returnedItem.Result == null && item.Item != null) + // { + // returnedItem = new FilesystemResult(item.Item, FileSystemStatusCode.Success); + // } + // return returnedItem; + //} - public static IStorageItemWithPath FromPathAndType(string customPath, FilesystemItemType? itemType) - { - return (itemType == FilesystemItemType.File) ? - (IStorageItemWithPath)new StorageFileWithPath(null, customPath) : - (IStorageItemWithPath)new StorageFolderWithPath(null, customPath); - } + //public static IStorageItemWithPath FromPathAndType(string customPath, FilesystemItemType? itemType) + //{ + // return (itemType == FilesystemItemType.File) ? + // (IStorageItemWithPath)new StorageFileWithPath(null, customPath) : + // (IStorageItemWithPath)new StorageFolderWithPath(null, customPath); + //} - public static async Task GetTypeFromPath(string path, IShellPage associatedInstance = null) - { - IStorageItem item = await ToStorageItem(path, associatedInstance); + //public static async Task GetTypeFromPath(string path, IShellPage associatedInstance = null) + //{ + // IStorageItem item = await ToStorageItem(path, associatedInstance); - return item == null ? FilesystemItemType.File : (item.IsOfType(StorageItemTypes.Folder) ? FilesystemItemType.Directory : FilesystemItemType.File); - } + // return item == null ? FilesystemItemType.File : (item.IsOfType(StorageItemTypes.Folder) ? FilesystemItemType.Directory : FilesystemItemType.File); + //} - public static async Task Exists(string path, IShellPage associatedInstance = null) - { - IStorageItem item = await ToStorageItem(path, associatedInstance); + //public static async Task Exists(string path, IShellPage associatedInstance = null) + //{ + // IStorageItem item = await ToStorageItem(path, associatedInstance); - return item != null; - } + // return item != null; + //} - public static IStorageItemWithPath FromStorageItem(this IStorageItem item, string customPath = null, FilesystemItemType? itemType = null) - { - if (item == null) - { - return FromPathAndType(customPath, itemType); - } - else if (item.IsOfType(StorageItemTypes.File)) - { - return new StorageFileWithPath(item as StorageFile, 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 null; - } + //public static IStorageItem FromStorageItem(this IStorageItem item, string customPath = null, FilesystemItemType? itemType = null) + //{ + // if (item == null) + // { + // return FromPathAndType(customPath, itemType); + // } + // else if (item.IsOfType(StorageItemTypes.File)) + // { + // return new StorageFileWithPath(item as StorageFile, 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 null; + //} - public static FilesystemResult ToType(FilesystemResult result) where T : class - { - return new FilesystemResult(result.Result as T, result.ErrorCode); - } + //public static FilesystemResult ToType(FilesystemResult result) where T : class + //{ + // return new FilesystemResult(result.Result as T, result.ErrorCode); + //} } } \ No newline at end of file diff --git a/Files/Interacts/Interaction.cs b/Files/Interacts/Interaction.cs index f1b4ef181733..fc86b6065183 100644 --- a/Files/Interacts/Interaction.cs +++ b/Files/Interacts/Interaction.cs @@ -3,6 +3,7 @@ using Files.Dialogs; using Files.Enums; using Files.Filesystem; +using Files.Filesystem.StorageFileHelpers; using Files.Helpers; using Files.ViewModels; using Files.Views; @@ -476,11 +477,12 @@ public async Task OpenPath(string path, FilesystemItemType? itemType = nul if (isFtp) { AssociatedInstance.NavigationToolbar.PathControlDisplayText = path; - AssociatedInstance.ContentFrame.Navigate(AssociatedInstance.InstanceViewModel.FolderSettings.GetLayoutType(path), new NavigationArguments() + AssociatedInstance.NavigateWithArguments(AssociatedInstance.InstanceViewModel.FolderSettings.GetLayoutType(path), new NavigationArguments() { NavPathParam = path, - AssociatedTabInstance = AssociatedInstance - }, new SuppressNavigationTransitionInfo()); + AssociatedTabInstance = AssociatedInstance, + SelectItems = selectItems + }); } else if (isShortcutItem) { @@ -1076,22 +1078,38 @@ public async void CopyItem_ClickAsync(object sender, RoutedEventArgs e) { foreach (ListedItem listedItem in AssociatedInstance.ContentPage.SelectedItems) { - if (listedItem.PrimaryItemAttribute == StorageItemTypes.File) + var isFtp = listedItem.ItemPath.StartsWith("ftp://", StringComparison.OrdinalIgnoreCase) || listedItem.ItemPath.StartsWith("ftps://", StringComparison.OrdinalIgnoreCase); + + if (isFtp) { - copied = await AssociatedInstance.FilesystemViewModel.GetFileFromPathAsync(listedItem.ItemPath) - .OnSuccess(t => items.Add(t)); - if (!copied) + if (listedItem.PrimaryItemAttribute == StorageItemTypes.File) { - break; + items.Add(listedItem.StorageItem); + } + else + { + throw new NotImplementedException(); } } else { - copied = await AssociatedInstance.FilesystemViewModel.GetFolderFromPathAsync(listedItem.ItemPath) - .OnSuccess(t => items.Add(t)); - if (!copied) + if (listedItem.PrimaryItemAttribute == StorageItemTypes.File) { - break; + copied = await AssociatedInstance.FilesystemViewModel.GetFileFromPathAsync(listedItem.ItemPath) + .OnSuccess(t => items.Add(t)); + if (!copied) + { + break; + } + } + else + { + copied = await AssociatedInstance.FilesystemViewModel.GetFolderFromPathAsync(listedItem.ItemPath) + .OnSuccess(t => items.Add(t)); + if (!copied) + { + break; + } } } } diff --git a/Files/ViewModels/ItemViewModel.cs b/Files/ViewModels/ItemViewModel.cs index c57db4274118..821fef8aee4d 100644 --- a/Files/ViewModels/ItemViewModel.cs +++ b/Files/ViewModels/ItemViewModel.cs @@ -18,7 +18,6 @@ using System.ComponentModel; using System.Diagnostics; using System.IO; -using System.IO.Pipes; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; @@ -909,8 +908,7 @@ private async void RapidAddItemsToCollectionAsync(string path, string previousDi } if (path.StartsWith(AppSettings.RecycleBinPath) || - path.StartsWith(AppSettings.NetworkFolderPath) || - path.StartsWith("ftp:")) + path.StartsWith(AppSettings.NetworkFolderPath)) { // Recycle bin and network are enumerated by the fulltrust process PageTypeUpdated?.Invoke(this, new PageTypeUpdatedEventArgs() { IsTypeCloudDrive = false }); @@ -1005,12 +1003,29 @@ public void CloseWatcher() public async Task EnumerateItemsFromFtpAsync(string path) { + string returnformat = Enum.Parse(ApplicationData.Current.LocalSettings.Values[Constants.LocalSettings.DateTimeFormat].ToString()) == TimeStyle.Application ? "D" : "g"; + + CurrentFolder = new ListedItem(null, returnformat) + { + PrimaryItemAttribute = StorageItemTypes.Folder, + ItemPropertiesInitialized = true, + ItemName = Path.GetFileName(path), + ItemDateModifiedReal = DateTimeOffset.Now, // Fake for now + ItemDateCreatedReal = DateTimeOffset.Now, // Fake for now + ItemType = "FileFolderListItem".GetLocalized(), + LoadFolderGlyph = true, + FileImage = null, + LoadFileIcon = false, + ItemPath = path, + LoadUnknownTypeGlyph = false, + FileSize = null, + FileSizeBytes = 0 + }; + await Task.Run(async () => { try { - string returnformat = Enum.Parse(ApplicationData.Current.LocalSettings.Values[Constants.LocalSettings.DateTimeFormat].ToString()) == TimeStyle.Application ? "D" : "g"; - var index = path.IndexOf("://") + 3; var count = path.IndexOf("/", index); @@ -1018,12 +1033,11 @@ await Task.Run(async () => var host = count == -1 ? path.Substring(index) : path.Substring(index, count - index); var ftpPath = count == -1 ? "/" : path.Substring(count); - if (ftpClient is null || ftpClient.Host != host) + if (ftpClient == null || ftpClient.Host != host) { if (ftpClient != null) { await ftpClient.DisconnectAsync(); - ftpClient.Dispose(); } ftpClient = new FtpClient(address); diff --git a/Files/ViewModels/Previews/BasePreviewModel.cs b/Files/ViewModels/Previews/BasePreviewModel.cs index 3e8bb494b08d..4bd45fc6937b 100644 --- a/Files/ViewModels/Previews/BasePreviewModel.cs +++ b/Files/ViewModels/Previews/BasePreviewModel.cs @@ -40,9 +40,9 @@ public async virtual Task> LoadPreviewAndDetails() { Item.FileImage = await IconData.ToBitmapAsync(); } - else + else if (Item.StorageItem is StorageFile item) { - using var icon = await Item.ItemFile.GetThumbnailAsync(ThumbnailMode.SingleItem, 400); + using var icon = await item.GetThumbnailAsync(ThumbnailMode.SingleItem, 400); Item.FileImage ??= new Windows.UI.Xaml.Media.Imaging.BitmapImage(); await Item.FileImage.SetSourceAsync(icon); } @@ -52,12 +52,12 @@ public async virtual Task> LoadPreviewAndDetails() private async Task> GetSystemFileProperties() { - if (Item.IsShortcutItem) + if (Item.IsShortcutItem || !(Item.StorageItem is StorageFile file)) { return null; } - var list = await FileProperty.RetrieveAndInitializePropertiesAsync(Item.ItemFile, Constants.ResourceFilePaths.PreviewPaneDetailsPropertiesJsonPath); + var list = await FileProperty.RetrieveAndInitializePropertiesAsync(file, Constants.ResourceFilePaths.PreviewPaneDetailsPropertiesJsonPath); list.Find(x => x.ID == "address").Value = await FileProperties.GetAddressFromCoordinatesAsync((double?)list.Find(x => x.Property == "System.GPS.LatitudeDecimal").Value, (double?)list.Find(x => x.Property == "System.GPS.LongitudeDecimal").Value); @@ -70,7 +70,7 @@ public virtual async Task LoadAsync() try { var detailsFull = new List(); - Item.ItemFile ??= await StorageFile.GetFileFromPathAsync(Item.ItemPath); + Item.StorageItem ??= await StorageFile.GetFileFromPathAsync(Item.ItemPath); DetailsFromPreview = await LoadPreviewAndDetails(); RaiseLoadedEvent(); var props = await GetSystemFileProperties(); diff --git a/Files/ViewModels/Previews/HtmlPreviewViewModel.cs b/Files/ViewModels/Previews/HtmlPreviewViewModel.cs index b50eb13a8b7e..3877cccd9d3b 100644 --- a/Files/ViewModels/Previews/HtmlPreviewViewModel.cs +++ b/Files/ViewModels/Previews/HtmlPreviewViewModel.cs @@ -32,7 +32,10 @@ public async override Task> LoadPreviewAndDetails() { try { - TextValue = await FileIO.ReadTextAsync(Item.ItemFile); + if (Item.StorageItem is StorageFile file) + { + TextValue = await FileIO.ReadTextAsync(file); + } } catch (Exception e) { diff --git a/Files/ViewModels/Previews/ImagePreviewViewModel.cs b/Files/ViewModels/Previews/ImagePreviewViewModel.cs index c5273b5db435..42819c417ea4 100644 --- a/Files/ViewModels/Previews/ImagePreviewViewModel.cs +++ b/Files/ViewModels/Previews/ImagePreviewViewModel.cs @@ -33,20 +33,23 @@ public override async Task> LoadPreviewAndDetails() { try { - FileRandomAccessStream stream = (FileRandomAccessStream)await Item.ItemFile.OpenAsync(FileAccessMode.Read); - - // svg files require a different type of source - if (!Item.ItemPath.EndsWith(".svg")) - { - var bitmap = new BitmapImage(); - ImageSource = bitmap; - await bitmap.SetSourceAsync(stream); - } - else + if (Item.StorageItem is StorageFile file) { - var bitmap = new SvgImageSource(); - ImageSource = bitmap; - await bitmap.SetSourceAsync(stream); + FileRandomAccessStream stream = (FileRandomAccessStream)await file.OpenAsync(FileAccessMode.Read); + + // svg files require a different type of source + if (!Item.ItemPath.EndsWith(".svg")) + { + var bitmap = new BitmapImage(); + ImageSource = bitmap; + await bitmap.SetSourceAsync(stream); + } + else + { + var bitmap = new SvgImageSource(); + ImageSource = bitmap; + await bitmap.SetSourceAsync(stream); + } } } catch (Exception e) diff --git a/Files/ViewModels/Previews/MarkdownPreviewViewModel.cs b/Files/ViewModels/Previews/MarkdownPreviewViewModel.cs index 2acaa31d47ec..bfe48d69d012 100644 --- a/Files/ViewModels/Previews/MarkdownPreviewViewModel.cs +++ b/Files/ViewModels/Previews/MarkdownPreviewViewModel.cs @@ -30,9 +30,12 @@ public override async Task> LoadPreviewAndDetails() { try { - var text = await FileIO.ReadTextAsync(Item.ItemFile); - var displayText = text.Length < Constants.PreviewPane.TextCharacterLimit ? text : text.Remove(Constants.PreviewPane.TextCharacterLimit); - TextValue = displayText; + if (Item.StorageItem is StorageFile file) + { + var text = await FileIO.ReadTextAsync(file); + var displayText = text.Length < Constants.PreviewPane.TextCharacterLimit ? text : text.Remove(Constants.PreviewPane.TextCharacterLimit); + TextValue = displayText; + } } catch (Exception e) { diff --git a/Files/ViewModels/Previews/MediaPreviewViewModel.cs b/Files/ViewModels/Previews/MediaPreviewViewModel.cs index e1d209ce5e71..59bdee827ef4 100644 --- a/Files/ViewModels/Previews/MediaPreviewViewModel.cs +++ b/Files/ViewModels/Previews/MediaPreviewViewModel.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Windows.Media.Core; +using Windows.Storage; using Windows.UI.Xaml; namespace Files.ViewModels.Previews @@ -30,7 +31,10 @@ public MediaSource Source public override Task> LoadPreviewAndDetails() { - Source = MediaSource.CreateFromStorageFile(Item.ItemFile); + if (Item.StorageItem is StorageFile file) + { + Source = MediaSource.CreateFromStorageFile(file); + } return Task.FromResult(new List()); } diff --git a/Files/ViewModels/Previews/PDFPreviewViewModel.cs b/Files/ViewModels/Previews/PDFPreviewViewModel.cs index 54805d711f44..f5148614d0d6 100644 --- a/Files/ViewModels/Previews/PDFPreviewViewModel.cs +++ b/Files/ViewModels/Previews/PDFPreviewViewModel.cs @@ -5,6 +5,7 @@ using System.Collections.ObjectModel; using System.Threading.Tasks; using Windows.Data.Pdf; +using Windows.Storage; using Windows.Storage.Streams; using Windows.UI.Xaml; using Windows.UI.Xaml.Media.Imaging; @@ -34,18 +35,23 @@ public Visibility LoadingBarVisibility public async override Task> LoadPreviewAndDetails() { - var pdf = await PdfDocument.LoadFromFileAsync(Item.ItemFile); - var details = new List(); - - LoadPagesAsync(pdf); - // Add the number of pages to the details - details.Add(new FileProperty() + if (Item.StorageItem is StorageFile file) { - NameResource = "PropertyPageCount", - Value = pdf.PageCount, - }); + var pdf = await PdfDocument.LoadFromFileAsync(file); + var details = new List(); + + LoadPagesAsync(pdf); + // Add the number of pages to the details + details.Add(new FileProperty() + { + NameResource = "PropertyPageCount", + Value = pdf.PageCount, + }); + + return details; + } - return details; + return new List(); } private async void LoadPagesAsync(PdfDocument pdf) diff --git a/Files/ViewModels/Previews/RichTextPreviewViewModel.cs b/Files/ViewModels/Previews/RichTextPreviewViewModel.cs index 29a95fe3524e..cf2a120c072e 100644 --- a/Files/ViewModels/Previews/RichTextPreviewViewModel.cs +++ b/Files/ViewModels/Previews/RichTextPreviewViewModel.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; +using Windows.Storage; using Windows.Storage.Streams; namespace Files.ViewModels.Previews @@ -24,7 +25,10 @@ public async override Task> LoadPreviewAndDetails() { try { - Stream = await Item.ItemFile.OpenReadAsync(); + if (Item.StorageItem is StorageFile file) + { + Stream = await file.OpenReadAsync(); + } } catch (Exception e) { diff --git a/Files/ViewModels/Previews/TextPreviewViewModel.cs b/Files/ViewModels/Previews/TextPreviewViewModel.cs index 4047fa4ebae2..4dce0bf1bb92 100644 --- a/Files/ViewModels/Previews/TextPreviewViewModel.cs +++ b/Files/ViewModels/Previews/TextPreviewViewModel.cs @@ -44,8 +44,9 @@ public static async Task TryLoadAsTextAsync(ListedItem item) try { - item.ItemFile = await StorageFile.GetFileFromPathAsync(item.ItemPath); - var text = await FileIO.ReadTextAsync(item.ItemFile); + var file = await StorageFile.GetFileFromPathAsync(item.ItemPath); + item.StorageItem = file; + var text = await FileIO.ReadTextAsync(file); // Check if file is binary if (text.Contains("\0\0\0\0")) @@ -74,22 +75,25 @@ public async override Task> LoadPreviewAndDetails() try { - var text = TextValue ?? await FileIO.ReadTextAsync(Item.ItemFile); - - details.Add(new FileProperty() - { - NameResource = "PropertyLineCount", - Value = text.Split("\n").Length, - }); - - details.Add(new FileProperty() + if (Item.StorageItem is StorageFile file) { - NameResource = "PropertyWordCount", - Value = text.Split(new[] { " ", "\n" }, StringSplitOptions.RemoveEmptyEntries).Length, - }); - - var displayText = text.Length < Constants.PreviewPane.TextCharacterLimit ? text : text.Remove(Constants.PreviewPane.TextCharacterLimit); - TextValue = displayText; + var text = TextValue ?? await FileIO.ReadTextAsync(file); + + details.Add(new FileProperty() + { + NameResource = "PropertyLineCount", + Value = text.Split("\n").Length, + }); + + details.Add(new FileProperty() + { + NameResource = "PropertyWordCount", + Value = text.Split(new[] { " ", "\n" }, StringSplitOptions.RemoveEmptyEntries).Length, + }); + + var displayText = text.Length < Constants.PreviewPane.TextCharacterLimit ? text : text.Remove(Constants.PreviewPane.TextCharacterLimit); + TextValue = displayText; + } } catch (Exception e) { diff --git a/Files/Views/ModernShellPage.xaml.cs b/Files/Views/ModernShellPage.xaml.cs index 13b5db59632e..6b1254b7750c 100644 --- a/Files/Views/ModernShellPage.xaml.cs +++ b/Files/Views/ModernShellPage.xaml.cs @@ -561,7 +561,7 @@ public async void CheckPathInput(ItemViewModel instance, string currentInput, st } else if (currentInput.StartsWith("ftp://", StringComparison.OrdinalIgnoreCase) || currentInput.StartsWith("ftps://", StringComparison.OrdinalIgnoreCase)) { - ContentFrame.Navigate(InstanceViewModel.FolderSettings.GetLayoutType(currentInput), + ItemDisplayFrame.Navigate(InstanceViewModel.FolderSettings.GetLayoutType(currentInput), new NavigationArguments() { NavPathParam = currentInput,