Skip to content

Feature: Display conflicts modal when pasting in FTP locations #12242

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
May 3, 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
2 changes: 1 addition & 1 deletion src/Files.App.Storage/FtpStorage/FtpManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Files.App.Storage.FtpStorage
{
internal static class FtpManager
public static class FtpManager
{
public static readonly Dictionary<string, NetworkCredential> Credentials = new();

Expand Down
2 changes: 1 addition & 1 deletion src/Files.App.Storage/FtpStorage/FtpStorageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace Files.App.Storage.FtpStorage
{
public sealed class FtpStorageService : IStorageService
public sealed class FtpStorageService : IFtpStorageService
{
public Task<bool> IsAccessibleAsync(CancellationToken cancellationToken = default)
{
Expand Down
2 changes: 2 additions & 0 deletions src/Files.App/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Files.App.ServicesImplementation.DateTimeFormatter;
using Files.App.ServicesImplementation.Settings;
using Files.App.Shell;
using Files.App.Storage.FtpStorage;
using Files.App.Storage.NativeStorage;
using Files.App.UserControls.MultitaskingControl;
using Files.App.ViewModels;
Expand Down Expand Up @@ -206,6 +207,7 @@ protected override void OnLaunched(LaunchActivatedEventArgs e)
#else
.AddSingleton<IStorageService, NativeStorageService>()
#endif
.AddSingleton<IFtpStorageService, FtpStorageService>()
.AddSingleton<IAddItemService, AddItemService>()
#if STABLE || PREVIEW
.AddSingleton<IUpdateService, SideloadUpdateService>()
Expand Down
14 changes: 14 additions & 0 deletions src/Files.App/Data/Exceptions/FileAlreadyExistsException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.IO;

namespace Files.App.Data.Exceptions
{
public sealed class FileAlreadyExistsException : IOException
{
public string FileName { get; private set; }

public FileAlreadyExistsException(string message, string fileName) : base(message)
{
FileName = fileName;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
// Copyright (c) 2023 Files Community
// Licensed under the MIT License. See the LICENSE.

using CommunityToolkit.Mvvm.DependencyInjection;
using Files.App.Extensions;
using Files.App.Filesystem.FilesystemHistory;
using Files.App.Helpers;
using Files.App.Interacts;
using Files.Backend.Services;
using Files.Backend.Services.Settings;
using Files.Backend.ViewModels.Dialogs.FileSystemDialog;
using Files.Shared;
using Files.Shared.Enums;
using Files.Shared.Extensions;
using Files.Sdk.Storage;
using Files.Shared.Services;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Vanara.PInvoke;
using Windows.ApplicationModel.DataTransfer;
using Windows.Graphics.Imaging;
Expand All @@ -31,7 +18,7 @@

namespace Files.App.Filesystem
{
public class FilesystemHelpers : IFilesystemHelpers
public sealed class FilesystemHelpers : IFilesystemHelpers
{
#region Private Members

Expand Down Expand Up @@ -680,7 +667,10 @@ public static bool IsValidForFilename(string name)
// Assume GenerateNewName when source and destination are the same
if (string.IsNullOrEmpty(item.src.Path) || item.src.Path != item.dest)
{
if (StorageHelpers.Exists(item.dest)) // Same item names in both directories
// Same item names in both directories
if (StorageHelpers.Exists(item.dest) ||
(FtpHelpers.IsFtpPath(item.dest) &&
await Ioc.Default.GetRequiredService<IFtpStorageService>().FileExistsAsync(item.dest)))
{
(incomingItems[item.index] as FileSystemDialogConflictItemViewModel)!.ConflictResolveOption = FileNameConflictResolveOptionType.GenerateNewName;
conflictingItems.Add(incomingItems.ElementAt(item.index));
Expand Down
15 changes: 0 additions & 15 deletions src/Files.App/Filesystem/FtpManager.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
// Copyright (c) 2023 Files Community
// Licensed under the MIT License. See the LICENSE.

using Files.Shared.Enums;
using System;
using Files.App.Data.Exceptions;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Windows.Storage;

namespace Files.App.Filesystem
{
public static class FilesystemTasks
{
public static FileSystemStatusCode GetErrorCode(Exception ex, Type T = null) => (ex, (uint)ex.HResult) switch
public static FileSystemStatusCode GetErrorCode(Exception ex, Type? T = null) => (ex, (uint)ex.HResult) switch
{
(UnauthorizedAccessException, _) => FileSystemStatusCode.Unauthorized,
(FileNotFoundException, _) => FileSystemStatusCode.NotFound, // Item was deleted
(COMException, _) => FileSystemStatusCode.NotFound, // Item's drive was ejected
(_, 0x8007000F) => FileSystemStatusCode.NotFound, // The system cannot find the drive specified
(PathTooLongException, _) => FileSystemStatusCode.NameTooLong,
(FileAlreadyExistsException, _) => FileSystemStatusCode.AlreadyExists,
(IOException, _) => FileSystemStatusCode.InUse,
(ArgumentException, _) => ToStatusCode(T), // Item was invalid
(_, 0x800700B7) => FileSystemStatusCode.AlreadyExists,
Expand Down
6 changes: 1 addition & 5 deletions src/Files.App/Filesystem/StorageItems/FtpStorageFile.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
// Copyright (c) 2023 Files Community
// Licensed under the MIT License. See the LICENSE.

using Files.App.Extensions;
using Files.App.Helpers;
using Files.Shared.Extensions;
using Files.App.Storage.FtpStorage;
using FluentFTP;
using System;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Storage;
using Windows.Storage.FileProperties;
Expand Down
34 changes: 20 additions & 14 deletions src/Files.App/Filesystem/StorageItems/FtpStorageFolder.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
// Copyright (c) 2023 Files Community
// Licensed under the MIT License. See the LICENSE.

using Files.App.Extensions;
using Files.App.Helpers;
using Files.Shared.Extensions;
using Files.App.Data.Exceptions;
using Files.App.Storage.FtpStorage;
using FluentFTP;
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;
Expand Down Expand Up @@ -186,20 +181,31 @@ public override IAsyncOperation<BaseStorageFile> CreateFileAsync(string desiredN

using var stream = new MemoryStream();

string remotePath = $"{FtpPath}/{desiredName}";
var ftpRemoteExists = options is CreationCollisionOption.ReplaceExisting ? FtpRemoteExists.Overwrite : FtpRemoteExists.Skip;

var result = await ftpClient.UploadStream(stream, remotePath, ftpRemoteExists);
if (result is FtpStatus.Success)
FtpStatus result;
string finalName;
var remotePath = $"{FtpPath}/{desiredName}";
var nameWithoutExt = System.IO.Path.GetFileNameWithoutExtension(desiredName);
var extension = System.IO.Path.GetExtension(desiredName);
ushort attempt = 1;

do
{
return new FtpStorageFile(new StorageFileWithPath(null, $"{Path}/{desiredName}"));
finalName = desiredName;
result = await ftpClient.UploadStream(stream, remotePath, ftpRemoteExists);
desiredName = $"{nameWithoutExt} ({attempt}){extension}";
remotePath = $"{FtpPath}/{desiredName}";
}
while (result is FtpStatus.Skipped && ++attempt < 1024 && options == CreationCollisionOption.GenerateUniqueName);

if (result is FtpStatus.Success)
return new FtpStorageFile(new StorageFileWithPath(null, $"{Path}/{finalName}"));

if (result is FtpStatus.Skipped)
{
if (options is CreationCollisionOption.FailIfExists)
{
throw new IOException("File already exists.");
}
throw new FileAlreadyExistsException("File already exists.", desiredName);

return null;
}
Expand Down
1 change: 0 additions & 1 deletion src/Files.App/Helpers/FtpHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ public static string GetFtpAuthority(string path)
if (hostIndex == -1)
hostIndex = path.Length;


return path.Substring(schemaIndex, hostIndex - schemaIndex);
}

Expand Down
20 changes: 15 additions & 5 deletions src/Files.App/ViewModels/ItemViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright (c) 2023 Files Community
// Licensed under the MIT License. See the LICENSE.

using CommunityToolkit.WinUI;
using Files.App.Filesystem.Cloud;
using Files.App.Filesystem.Search;
using Files.App.Filesystem.StorageEnumerators;
using Files.App.Filesystem.StorageItems;
using Files.App.Helpers.FileListCache;
using Files.App.Shell;
using Files.App.Storage.FtpStorage;
using Files.App.UserControls;
using Files.App.ViewModels.Previews;
using Files.Backend.Services;
Expand Down Expand Up @@ -40,7 +40,7 @@

namespace Files.App.ViewModels
{
public class ItemViewModel : ObservableObject, IDisposable
public sealed class ItemViewModel : ObservableObject, IDisposable
{
private readonly SemaphoreSlim enumFolderSemaphore;
private readonly ConcurrentQueue<(uint Action, string FileName)> operationQueue;
Expand Down Expand Up @@ -140,9 +140,19 @@ public async Task SetWorkingDirectoryAsync(string? value)

WorkingDirectory = value;

var pathRoot = FtpHelpers.IsFtpPath(WorkingDirectory)
? WorkingDirectory.Substring(0, FtpHelpers.GetRootIndex(WorkingDirectory))
: Path.GetPathRoot(WorkingDirectory);
string? pathRoot;
if (FtpHelpers.IsFtpPath(WorkingDirectory))
{
var rootIndex = FtpHelpers.GetRootIndex(WorkingDirectory);
pathRoot = rootIndex is -1
? WorkingDirectory
: WorkingDirectory.Substring(0, rootIndex);
}
else
{
pathRoot = Path.GetPathRoot(WorkingDirectory);
}

GitDirectory = pathRoot is null ? null : GitHelpers.GetGitRepositoryPath(WorkingDirectory, pathRoot);
OnPropertyChanged(nameof(WorkingDirectory));
}
Expand Down
12 changes: 12 additions & 0 deletions src/Files.Sdk.Storage/IFtpStorageService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) 2023 Files Community
// Licensed under the MIT License. See the LICENSE.

namespace Files.Sdk.Storage
{
/// <summary>
/// Provides an abstract layer for accessing an ftp file system
/// </summary>
public interface IFtpStorageService : IStorageService
{
}
}