Skip to content

Commit e7d3d6d

Browse files
Feature: Display conflicts modal when pasting on FTP locations (#12242)
1 parent 594d747 commit e7d3d6d

File tree

12 files changed

+75
-62
lines changed

12 files changed

+75
-62
lines changed

src/Files.App.Storage/FtpStorage/FtpManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace Files.App.Storage.FtpStorage
88
{
9-
internal static class FtpManager
9+
public static class FtpManager
1010
{
1111
public static readonly Dictionary<string, NetworkCredential> Credentials = new();
1212

src/Files.App.Storage/FtpStorage/FtpStorageService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Files.App.Storage.FtpStorage
1313
{
14-
public sealed class FtpStorageService : IStorageService
14+
public sealed class FtpStorageService : IFtpStorageService
1515
{
1616
public Task<bool> IsAccessibleAsync(CancellationToken cancellationToken = default)
1717
{

src/Files.App/App.xaml.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Files.App.ServicesImplementation.DateTimeFormatter;
1515
using Files.App.ServicesImplementation.Settings;
1616
using Files.App.Shell;
17+
using Files.App.Storage.FtpStorage;
1718
using Files.App.Storage.NativeStorage;
1819
using Files.App.UserControls.MultitaskingControl;
1920
using Files.App.ViewModels;
@@ -206,6 +207,7 @@ protected override void OnLaunched(LaunchActivatedEventArgs e)
206207
#else
207208
.AddSingleton<IStorageService, NativeStorageService>()
208209
#endif
210+
.AddSingleton<IFtpStorageService, FtpStorageService>()
209211
.AddSingleton<IAddItemService, AddItemService>()
210212
#if STABLE || PREVIEW
211213
.AddSingleton<IUpdateService, SideloadUpdateService>()
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.IO;
2+
3+
namespace Files.App.Data.Exceptions
4+
{
5+
public sealed class FileAlreadyExistsException : IOException
6+
{
7+
public string FileName { get; private set; }
8+
9+
public FileAlreadyExistsException(string message, string fileName) : base(message)
10+
{
11+
FileName = fileName;
12+
}
13+
}
14+
}

src/Files.App/Filesystem/FilesystemOperations/Helpers/FilesystemHelpers.cs

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,14 @@
11
// Copyright (c) 2023 Files Community
22
// Licensed under the MIT License. See the LICENSE.
33

4-
using CommunityToolkit.Mvvm.DependencyInjection;
5-
using Files.App.Extensions;
64
using Files.App.Filesystem.FilesystemHistory;
7-
using Files.App.Helpers;
8-
using Files.App.Interacts;
95
using Files.Backend.Services;
10-
using Files.Backend.Services.Settings;
116
using Files.Backend.ViewModels.Dialogs.FileSystemDialog;
12-
using Files.Shared;
13-
using Files.Shared.Enums;
14-
using Files.Shared.Extensions;
7+
using Files.Sdk.Storage;
158
using Files.Shared.Services;
169
using Microsoft.Extensions.Logging;
17-
using System;
18-
using System.Collections.Generic;
19-
using System.Diagnostics;
2010
using System.IO;
21-
using System.Linq;
2211
using System.Runtime.InteropServices;
23-
using System.Threading;
24-
using System.Threading.Tasks;
2512
using Vanara.PInvoke;
2613
using Windows.ApplicationModel.DataTransfer;
2714
using Windows.Graphics.Imaging;
@@ -31,7 +18,7 @@
3118

3219
namespace Files.App.Filesystem
3320
{
34-
public class FilesystemHelpers : IFilesystemHelpers
21+
public sealed class FilesystemHelpers : IFilesystemHelpers
3522
{
3623
#region Private Members
3724

@@ -680,7 +667,10 @@ public static bool IsValidForFilename(string name)
680667
// Assume GenerateNewName when source and destination are the same
681668
if (string.IsNullOrEmpty(item.src.Path) || item.src.Path != item.dest)
682669
{
683-
if (StorageHelpers.Exists(item.dest)) // Same item names in both directories
670+
// Same item names in both directories
671+
if (StorageHelpers.Exists(item.dest) ||
672+
(FtpHelpers.IsFtpPath(item.dest) &&
673+
await Ioc.Default.GetRequiredService<IFtpStorageService>().FileExistsAsync(item.dest)))
684674
{
685675
(incomingItems[item.index] as FileSystemDialogConflictItemViewModel)!.ConflictResolveOption = FileNameConflictResolveOptionType.GenerateNewName;
686676
conflictingItems.Add(incomingItems.ElementAt(item.index));

src/Files.App/Filesystem/FtpManager.cs

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/Files.App/Filesystem/StorageFileHelpers/FilesystemTasks.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
11
// Copyright (c) 2023 Files Community
22
// Licensed under the MIT License. See the LICENSE.
33

4-
using Files.Shared.Enums;
5-
using System;
4+
using Files.App.Data.Exceptions;
65
using System.IO;
76
using System.Runtime.InteropServices;
8-
using System.Threading.Tasks;
97
using Windows.Storage;
108

119
namespace Files.App.Filesystem
1210
{
1311
public static class FilesystemTasks
1412
{
15-
public static FileSystemStatusCode GetErrorCode(Exception ex, Type T = null) => (ex, (uint)ex.HResult) switch
13+
public static FileSystemStatusCode GetErrorCode(Exception ex, Type? T = null) => (ex, (uint)ex.HResult) switch
1614
{
1715
(UnauthorizedAccessException, _) => FileSystemStatusCode.Unauthorized,
1816
(FileNotFoundException, _) => FileSystemStatusCode.NotFound, // Item was deleted
1917
(COMException, _) => FileSystemStatusCode.NotFound, // Item's drive was ejected
2018
(_, 0x8007000F) => FileSystemStatusCode.NotFound, // The system cannot find the drive specified
2119
(PathTooLongException, _) => FileSystemStatusCode.NameTooLong,
20+
(FileAlreadyExistsException, _) => FileSystemStatusCode.AlreadyExists,
2221
(IOException, _) => FileSystemStatusCode.InUse,
2322
(ArgumentException, _) => ToStatusCode(T), // Item was invalid
2423
(_, 0x800700B7) => FileSystemStatusCode.AlreadyExists,

src/Files.App/Filesystem/StorageItems/FtpStorageFile.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
// Copyright (c) 2023 Files Community
22
// Licensed under the MIT License. See the LICENSE.
33

4-
using Files.App.Extensions;
5-
using Files.App.Helpers;
6-
using Files.Shared.Extensions;
4+
using Files.App.Storage.FtpStorage;
75
using FluentFTP;
8-
using System;
96
using System.IO;
107
using System.Runtime.InteropServices.WindowsRuntime;
11-
using System.Threading.Tasks;
128
using Windows.Foundation;
139
using Windows.Storage;
1410
using Windows.Storage.FileProperties;

src/Files.App/Filesystem/StorageItems/FtpStorageFolder.cs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
// Copyright (c) 2023 Files Community
22
// Licensed under the MIT License. See the LICENSE.
33

4-
using Files.App.Extensions;
5-
using Files.App.Helpers;
6-
using Files.Shared.Extensions;
4+
using Files.App.Data.Exceptions;
5+
using Files.App.Storage.FtpStorage;
76
using FluentFTP;
8-
using System;
9-
using System.Collections.Generic;
107
using System.IO;
11-
using System.Linq;
128
using System.Runtime.InteropServices.WindowsRuntime;
13-
using System.Threading.Tasks;
149
using Windows.Foundation;
1510
using Windows.Storage;
1611
using Windows.Storage.FileProperties;
@@ -186,20 +181,31 @@ public override IAsyncOperation<BaseStorageFile> CreateFileAsync(string desiredN
186181

187182
using var stream = new MemoryStream();
188183

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

192-
var result = await ftpClient.UploadStream(stream, remotePath, ftpRemoteExists);
193-
if (result is FtpStatus.Success)
186+
FtpStatus result;
187+
string finalName;
188+
var remotePath = $"{FtpPath}/{desiredName}";
189+
var nameWithoutExt = System.IO.Path.GetFileNameWithoutExtension(desiredName);
190+
var extension = System.IO.Path.GetExtension(desiredName);
191+
ushort attempt = 1;
192+
193+
do
194194
{
195-
return new FtpStorageFile(new StorageFileWithPath(null, $"{Path}/{desiredName}"));
195+
finalName = desiredName;
196+
result = await ftpClient.UploadStream(stream, remotePath, ftpRemoteExists);
197+
desiredName = $"{nameWithoutExt} ({attempt}){extension}";
198+
remotePath = $"{FtpPath}/{desiredName}";
196199
}
200+
while (result is FtpStatus.Skipped && ++attempt < 1024 && options == CreationCollisionOption.GenerateUniqueName);
201+
202+
if (result is FtpStatus.Success)
203+
return new FtpStorageFile(new StorageFileWithPath(null, $"{Path}/{finalName}"));
204+
197205
if (result is FtpStatus.Skipped)
198206
{
199207
if (options is CreationCollisionOption.FailIfExists)
200-
{
201-
throw new IOException("File already exists.");
202-
}
208+
throw new FileAlreadyExistsException("File already exists.", desiredName);
203209

204210
return null;
205211
}

src/Files.App/Helpers/FtpHelpers.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ public static string GetFtpAuthority(string path)
7373
if (hostIndex == -1)
7474
hostIndex = path.Length;
7575

76-
7776
return path.Substring(schemaIndex, hostIndex - schemaIndex);
7877
}
7978

src/Files.App/ViewModels/ItemViewModel.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
// Copyright (c) 2023 Files Community
22
// Licensed under the MIT License. See the LICENSE.
33

4-
using CommunityToolkit.WinUI;
54
using Files.App.Filesystem.Cloud;
65
using Files.App.Filesystem.Search;
76
using Files.App.Filesystem.StorageEnumerators;
87
using Files.App.Filesystem.StorageItems;
98
using Files.App.Helpers.FileListCache;
109
using Files.App.Shell;
10+
using Files.App.Storage.FtpStorage;
1111
using Files.App.UserControls;
1212
using Files.App.ViewModels.Previews;
1313
using Files.Backend.Services;
@@ -40,7 +40,7 @@
4040

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

141141
WorkingDirectory = value;
142142

143-
var pathRoot = FtpHelpers.IsFtpPath(WorkingDirectory)
144-
? WorkingDirectory.Substring(0, FtpHelpers.GetRootIndex(WorkingDirectory))
145-
: Path.GetPathRoot(WorkingDirectory);
143+
string? pathRoot;
144+
if (FtpHelpers.IsFtpPath(WorkingDirectory))
145+
{
146+
var rootIndex = FtpHelpers.GetRootIndex(WorkingDirectory);
147+
pathRoot = rootIndex is -1
148+
? WorkingDirectory
149+
: WorkingDirectory.Substring(0, rootIndex);
150+
}
151+
else
152+
{
153+
pathRoot = Path.GetPathRoot(WorkingDirectory);
154+
}
155+
146156
GitDirectory = pathRoot is null ? null : GitHelpers.GetGitRepositoryPath(WorkingDirectory, pathRoot);
147157
OnPropertyChanged(nameof(WorkingDirectory));
148158
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright (c) 2023 Files Community
2+
// Licensed under the MIT License. See the LICENSE.
3+
4+
namespace Files.Sdk.Storage
5+
{
6+
/// <summary>
7+
/// Provides an abstract layer for accessing an ftp file system
8+
/// </summary>
9+
public interface IFtpStorageService : IStorageService
10+
{
11+
}
12+
}

0 commit comments

Comments
 (0)