Skip to content

Feature: Added file operation support for FTP #13362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -366,12 +366,17 @@ await DialogDisplayHelper.ShowDialogAsync(
if (fsSourceFolder.Result is IPasswordProtectedItem ppis)
ppis.PasswordRequestedCallback = UIFilesystemHelpers.RequestPassword;

// Moving folders using Storage API can result in data loss, copy instead
//var fsResultMove = await FilesystemTasks.Wrap(() => MoveDirectoryAsync((BaseStorageFolder)fsSourceFolder, (BaseStorageFolder)fsDestinationFolder, fsSourceFolder.Result.Name, collision.Convert(), true));
var fsResultMove = new FilesystemResult<BaseStorageFolder>(null, FileSystemStatusCode.Generic);
var srcFolder = (BaseStorageFolder)fsSourceFolder;
var fsResultMove = await FilesystemTasks.Wrap(() => srcFolder.MoveAsync(fsDestinationFolder.Result, collision).AsTask());

if (await DialogDisplayHelper.ShowDialogAsync("ErrorDialogThisActionCannotBeDone".GetLocalizedResource(), "ErrorDialogUnsupportedMoveOperation".GetLocalizedResource(), "OK", "Cancel".GetLocalizedResource()))
fsResultMove = await FilesystemTasks.Wrap(() => CloneDirectoryAsync((BaseStorageFolder)fsSourceFolder, (BaseStorageFolder)fsDestinationFolder, fsSourceFolder.Result.Name, collision.Convert()));
if (!fsResultMove) // Use generic move folder operation (move folder items one by one)
{
// Moving folders using Storage API can result in data loss, copy instead
//var fsResultMove = await FilesystemTasks.Wrap(() => MoveDirectoryAsync((BaseStorageFolder)fsSourceFolder, (BaseStorageFolder)fsDestinationFolder, fsSourceFolder.Result.Name, collision.Convert(), true));

if (await DialogDisplayHelper.ShowDialogAsync("ErrorDialogThisActionCannotBeDone".GetLocalizedResource(), "ErrorDialogUnsupportedMoveOperation".GetLocalizedResource(), "OK", "Cancel".GetLocalizedResource()))
fsResultMove = await FilesystemTasks.Wrap(() => CloneDirectoryAsync((BaseStorageFolder)fsSourceFolder, (BaseStorageFolder)fsDestinationFolder, fsSourceFolder.Result.Name, collision.Convert()));
}

if (fsSourceFolder.Result is IPasswordProtectedItem ppiu)
ppiu.PasswordRequestedCallback = null;
Expand Down Expand Up @@ -453,6 +458,14 @@ await DialogDisplayHelper.ShowDialogAsync(
return null;
}

bool sourceInCurrentFolder = PathNormalization.TrimPath(_associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath) ==
PathNormalization.GetParentDir(source.Path);
if (fsProgress.Status == FileSystemStatusCode.Success && sourceInCurrentFolder)
{
await _associatedInstance.FilesystemViewModel.RemoveFileOrFolderAsync(source.Path);
await _associatedInstance.FilesystemViewModel.ApplyFilesAndFoldersChangesAsync();
}

var pathWithType = movedItem.FromStorageItem(destination, source.ItemType);

return new StorageHistory(FileOperationType.Move, source, pathWithType);
Expand Down Expand Up @@ -713,7 +726,6 @@ public async Task<IStorageHistory> RestoreFromTrashAsync(IStorageItemWithPath so
if (fsResult)
{
// Moving folders using Storage API can result in data loss, copy instead

//fsResult = await FilesystemTasks.Wrap(() => MoveDirectoryAsync(sourceFolder.Result, destinationFolder.Result, Path.GetFileName(destination), CreationCollisionOption.FailIfExists, true));

if (await DialogDisplayHelper.ShowDialogAsync("ErrorDialogThisActionCannotBeDone".GetLocalizedResource(), "ErrorDialogUnsupportedMoveOperation".GetLocalizedResource(), "OK", "Cancel".GetLocalizedResource()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ IAsyncOperation<StorageFolder> IStorageFolder.CreateFolderAsync(string desiredNa
=> await (await CreateFolderAsync(desiredName, options)).ToStorageFolderAsync());
}

public abstract IAsyncOperation<BaseStorageFolder> MoveAsync(IStorageFolder destinationFolder);

public abstract IAsyncOperation<BaseStorageFolder> MoveAsync(IStorageFolder destinationFolder, NameCollisionOption option);

public abstract IAsyncAction RenameAsync(string desiredName);

public abstract IAsyncAction RenameAsync(string desiredName, NameCollisionOption option);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public interface IBaseStorageFolder : IStorageItem2, IStorageFolder, IStorageFol

new IAsyncOperation<BaseStorageFolder> CreateFolderAsync(string desiredName, CreationCollisionOption options);

IAsyncOperation<BaseStorageFolder> MoveAsync(IStorageFolder destinationFolder);
IAsyncOperation<BaseStorageFolder> MoveAsync(IStorageFolder destinationFolder, NameCollisionOption option);

new BaseStorageItemQueryResult CreateItemQueryWithOptions(QueryOptions queryOptions);

new BaseStorageFileQueryResult CreateFileQueryWithOptions(QueryOptions queryOptions);
Expand Down
33 changes: 29 additions & 4 deletions src/Files.App/Utils/Storage/StorageItems/FtpStorageFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,34 @@ public override IAsyncOperation<BaseStorageFile> CopyAsync(IStorageFolder destin
}, ((IPasswordProtectedItem)this).RetryWithCredentials));
}

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 MoveAsync(IStorageFolder destinationFolder)
=> MoveAsync(destinationFolder, Name, NameCollisionOption.FailIfExists);
public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName)
=> MoveAsync(destinationFolder, desiredNewName, NameCollisionOption.FailIfExists);
public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option)
{
return AsyncInfo.Run((cancellationToken) => SafetyExtensions.Wrap(async () =>
{
using var ftpClient = GetFtpClient();
if (!await ftpClient.EnsureConnectedAsync())
throw new IOException($"Failed to connect to FTP server.");

BaseStorageFolder destFolder = destinationFolder.AsBaseStorageFolder();

if (destFolder is FtpStorageFolder ftpFolder)
{
string destName = $"{ftpFolder.FtpPath}/{Name}";
FtpRemoteExists ftpRemoteExists = option is NameCollisionOption.ReplaceExisting ? FtpRemoteExists.Overwrite : FtpRemoteExists.Skip;

bool isSuccessful = await ftpClient.MoveFile(FtpPath, destName, ftpRemoteExists, cancellationToken);
if (!isSuccessful)
throw new IOException($"Failed to move file from {Path} to {destFolder}.");
}
else
throw new NotSupportedException();
}, ((IPasswordProtectedItem)this).RetryWithCredentials));
}


public override IAsyncAction CopyAndReplaceAsync(IStorageFile fileToReplace) => throw new NotSupportedException();
public override IAsyncAction MoveAndReplaceAsync(IStorageFile fileToReplace) => throw new NotSupportedException();
Expand Down Expand Up @@ -236,7 +261,7 @@ private AsyncFtpClient GetFtpClient()
{
string host = FtpHelpers.GetFtpHost(Path);
ushort port = FtpHelpers.GetFtpPort(Path);
var credentials = Credentials is not null ?
var credentials = Credentials is not null ?
new NetworkCredential(Credentials.UserName, Credentials.SecurePassword) :
FtpManager.Credentials.Get(host, FtpManager.Anonymous);

Expand Down
30 changes: 30 additions & 0 deletions src/Files.App/Utils/Storage/StorageItems/FtpStorageFolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,36 @@ public override IAsyncOperation<BaseStorageFolder> CreateFolderAsync(string desi
}, ((IPasswordProtectedItem)this).RetryWithCredentials));
}

public override IAsyncOperation<BaseStorageFolder> MoveAsync(IStorageFolder destinationFolder)
=> MoveAsync(destinationFolder, NameCollisionOption.FailIfExists);
public override IAsyncOperation<BaseStorageFolder> MoveAsync(IStorageFolder destinationFolder, NameCollisionOption option)
{
return AsyncInfo.Run((cancellationToken) => SafetyExtensions.Wrap<BaseStorageFolder>(async () =>
{
using var ftpClient = GetFtpClient();
if (!await ftpClient.EnsureConnectedAsync())
throw new IOException($"Failed to connect to FTP server.");

BaseStorageFolder destFolder = destinationFolder.AsBaseStorageFolder();

if (destFolder is FtpStorageFolder ftpFolder)
{
string destName = $"{ftpFolder.FtpPath}/{Name}";
FtpRemoteExists ftpRemoteExists = option is NameCollisionOption.ReplaceExisting ? FtpRemoteExists.Overwrite : FtpRemoteExists.Skip;

bool isSuccessful = await ftpClient.MoveDirectory(FtpPath, destName, ftpRemoteExists, token: cancellationToken);
if (!isSuccessful)
throw new IOException($"Failed to move folder from {Path} to {destFolder}.");

var folder = new FtpStorageFolder(new StorageFileWithPath(null, destName));
((IPasswordProtectedItem)folder).CopyFrom(this);
return folder;
}
else
throw new NotSupportedException();
}, ((IPasswordProtectedItem)this).RetryWithCredentials));
}

public override IAsyncAction RenameAsync(string desiredName)
=> RenameAsync(desiredName, NameCollisionOption.FailIfExists);
public override IAsyncAction RenameAsync(string desiredName, NameCollisionOption option)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ public override IAsyncOperation<BaseStorageFile> CreateFileAsync(string desiredN
public override IAsyncOperation<BaseStorageFolder> CreateFolderAsync(string desiredName, CreationCollisionOption options)
=> throw new NotSupportedException();

public override IAsyncOperation<BaseStorageFolder> MoveAsync(IStorageFolder destinationFolder) => throw new NotSupportedException();
public override IAsyncOperation<BaseStorageFolder> MoveAsync(IStorageFolder destinationFolder, NameCollisionOption option) => throw new NotSupportedException();

public override IAsyncAction RenameAsync(string desiredName) => throw new NotSupportedException();
public override IAsyncAction RenameAsync(string desiredName, NameCollisionOption option) => throw new NotSupportedException();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ public override IAsyncOperation<BaseStorageFolder> CreateFolderAsync(string desi
public override IAsyncOperation<BaseStorageFolder> CreateFolderAsync(string desiredName, CreationCollisionOption options)
=> AsyncInfo.Run<BaseStorageFolder>(async (cancellationToken) => new SystemStorageFolder(await Folder.CreateFolderAsync(desiredName, options)));

public override IAsyncOperation<BaseStorageFolder> MoveAsync(IStorageFolder destinationFolder) => throw new NotSupportedException();
public override IAsyncOperation<BaseStorageFolder> MoveAsync(IStorageFolder destinationFolder, NameCollisionOption option) => throw new NotSupportedException();

public override IAsyncAction RenameAsync(string desiredName) => Folder.RenameAsync(desiredName);
public override IAsyncAction RenameAsync(string desiredName, NameCollisionOption option) => Folder.RenameAsync(desiredName, option);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ public override IAsyncOperation<BaseStorageFolder> CreateFolderAsync(string desi
=> CreateFolderAsync(desiredName, CreationCollisionOption.FailIfExists);
public override IAsyncOperation<BaseStorageFolder> CreateFolderAsync(string desiredName, CreationCollisionOption options)
=> throw new NotSupportedException();
public override IAsyncOperation<BaseStorageFolder> MoveAsync(IStorageFolder destinationFolder) => throw new NotSupportedException();
public override IAsyncOperation<BaseStorageFolder> MoveAsync(IStorageFolder destinationFolder, NameCollisionOption option) => throw new NotSupportedException();

public override IAsyncAction RenameAsync(string desiredName)
=> RenameAsync(desiredName, NameCollisionOption.FailIfExists);
Expand Down
3 changes: 3 additions & 0 deletions src/Files.App/Utils/Storage/StorageItems/ZipStorageFolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,9 @@ public override IAsyncOperation<BaseStorageFolder> CreateFolderAsync(string desi
}, ((IPasswordProtectedItem)this).RetryWithCredentials));
}

public override IAsyncOperation<BaseStorageFolder> MoveAsync(IStorageFolder destinationFolder) => throw new NotSupportedException();
public override IAsyncOperation<BaseStorageFolder> MoveAsync(IStorageFolder destinationFolder, NameCollisionOption option) => throw new NotSupportedException();

public override IAsyncAction RenameAsync(string desiredName) => RenameAsync(desiredName, NameCollisionOption.FailIfExists);
public override IAsyncAction RenameAsync(string desiredName, NameCollisionOption option)
{
Expand Down