Skip to content

Commit 21f1ef2

Browse files
Merge
2 parents 449014f + c93841e commit 21f1ef2

File tree

140 files changed

+2057
-624
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

140 files changed

+2057
-624
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Files.Sdk.Storage.LocatableStorage;
2+
using Files.Shared.Helpers;
23
using FluentFTP;
34
using System.Threading;
45
using System.Threading.Tasks;
@@ -7,22 +8,24 @@ namespace Files.App.Storage.FtpStorage
78
{
89
public abstract class FtpStorable : ILocatableStorable
910
{
11+
private string? _computedId;
12+
1013
/// <inheritdoc/>
1114
public string Path { get; protected set; }
1215

1316
/// <inheritdoc/>
1417
public string Name { get; protected set; }
1518

1619
/// <inheritdoc/>
17-
public string Id { get; protected set; }
20+
public virtual string Id => _computedId ??= ChecksumHelpers.CalculateChecksumForPath(Path);
1821

1922
protected internal FtpStorable(string path, string name)
2023
{
2124
Path = FtpHelpers.GetFtpPath(path);
2225
Name = name;
23-
Id = string.Empty;
2426
}
2527

28+
/// <inheritdoc/>
2629
public virtual Task<ILocatableFolder?> GetParentAsync(CancellationToken cancellationToken = default)
2730
{
2831
return Task.FromResult<ILocatableFolder?>(null);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public FtpStorageFile(string path, string name)
1515
}
1616

1717
/// <inheritdoc/>
18-
public async Task<Stream> OpenStreamAsync(FileAccess access, FileShare share, CancellationToken cancellationToken = default)
18+
public async Task<Stream> OpenStreamAsync(FileAccess access, CancellationToken cancellationToken = default)
1919
{
2020
using var ftpClient = GetFtpClient();
2121
await ftpClient.EnsureConnectedAsync(cancellationToken);
@@ -30,7 +30,7 @@ public async Task<Stream> OpenStreamAsync(FileAccess access, FileShare share, Ca
3030
}
3131
else
3232
{
33-
throw new ArgumentException($"Invalid {nameof(share)} flag.");
33+
throw new ArgumentException($"Invalid {nameof(access)} flag.");
3434
}
3535
}
3636
}

src/Files.App.Storage/FtpStorage/FtpFileSystemService.cs renamed to src/Files.App.Storage/FtpStorage/FtpStorageService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
using Files.Sdk.Storage;
12
using Files.Sdk.Storage.LocatableStorage;
2-
using Files.Sdk.Storage.Services;
33
using FluentFTP;
44
using System;
55
using System.IO;
@@ -8,9 +8,9 @@
88

99
namespace Files.App.Storage.FtpStorage
1010
{
11-
public sealed class FtpFileSystemService : IFileSystemService
11+
public sealed class FtpStorageService : IStorageService
1212
{
13-
public Task<bool> IsFileSystemAccessibleAsync(CancellationToken cancellationToken = default)
13+
public Task<bool> IsAccessibleAsync(CancellationToken cancellationToken = default)
1414
{
1515
return Task.FromResult(true); // TODO: Check if FTP is available
1616
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Files.Sdk.Storage;
2+
using Files.Sdk.Storage.ExtendableStorage;
3+
using Files.Sdk.Storage.LocatableStorage;
4+
using Files.Sdk.Storage.ModifiableStorage;
5+
using System.IO;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
9+
namespace Files.App.Storage.NativeStorage
10+
{
11+
/// <inheritdoc cref="IFile"/>
12+
public sealed class NativeFile : NativeStorable, ILocatableFile, IModifiableFile, IFileExtended
13+
{
14+
public NativeFile(string path)
15+
: base(path)
16+
{
17+
}
18+
19+
/// <inheritdoc/>
20+
public Task<Stream> OpenStreamAsync(FileAccess access, CancellationToken cancellationToken = default)
21+
{
22+
return OpenStreamAsync(access, FileShare.None, cancellationToken);
23+
}
24+
25+
/// <inheritdoc/>
26+
public Task<Stream> OpenStreamAsync(FileAccess access, FileShare share = FileShare.None, CancellationToken cancellationToken = default)
27+
{
28+
var stream = File.Open(Path, FileMode.Open, access, share);
29+
return Task.FromResult<Stream>(stream);
30+
}
31+
}
32+
}
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
using Files.Sdk.Storage;
2+
using Files.Sdk.Storage.Enums;
3+
using Files.Sdk.Storage.Extensions;
4+
using Files.Sdk.Storage.LocatableStorage;
5+
using Files.Sdk.Storage.ModifiableStorage;
6+
using Files.Sdk.Storage.MutableStorage;
7+
using System;
8+
using System.Collections.Generic;
9+
using System.IO;
10+
using System.Runtime.CompilerServices;
11+
using System.Threading;
12+
using System.Threading.Tasks;
13+
14+
namespace Files.App.Storage.NativeStorage
15+
{
16+
/// <inheritdoc cref="IFolder"/>
17+
public sealed class NativeFolder : NativeStorable, ILocatableFolder, IModifiableFolder, IMutableFolder
18+
{
19+
public NativeFolder(string path)
20+
: base(path)
21+
{
22+
}
23+
24+
/// <inheritdoc/>
25+
public Task<IFile> GetFileAsync(string fileName, CancellationToken cancellationToken = default)
26+
{
27+
var path = System.IO.Path.Combine(Path, fileName);
28+
29+
if (!File.Exists(path))
30+
throw new FileNotFoundException();
31+
32+
return Task.FromResult<IFile>(new NativeFile(path));
33+
}
34+
35+
/// <inheritdoc/>
36+
public Task<IFolder> GetFolderAsync(string folderName, CancellationToken cancellationToken = default)
37+
{
38+
var path = System.IO.Path.Combine(Path, folderName);
39+
40+
if (!Directory.Exists(path))
41+
throw new FileNotFoundException();
42+
43+
return Task.FromResult<IFolder>(new NativeFolder(path));
44+
}
45+
46+
/// <inheritdoc/>
47+
public async IAsyncEnumerable<IStorable> GetItemsAsync(StorableKind kind = StorableKind.All, [EnumeratorCancellation] CancellationToken cancellationToken = default)
48+
{
49+
if (kind == StorableKind.Files)
50+
{
51+
foreach (var item in Directory.EnumerateFiles(Path))
52+
yield return new NativeFile(item);
53+
}
54+
else if (kind == StorableKind.Folders)
55+
{
56+
foreach (var item in Directory.EnumerateDirectories(Path))
57+
yield return new NativeFolder(item);
58+
}
59+
else
60+
{
61+
foreach (var item in Directory.EnumerateFileSystemEntries(Path))
62+
{
63+
if (File.Exists(item))
64+
yield return new NativeFile(item);
65+
else
66+
yield return new NativeFolder(item);
67+
}
68+
}
69+
70+
await Task.CompletedTask;
71+
}
72+
73+
/// <inheritdoc/>
74+
public Task DeleteAsync(IStorable item, bool permanently = false, CancellationToken cancellationToken = default)
75+
{
76+
_ = permanently;
77+
78+
if (item is ILocatableFile locatableFile)
79+
{
80+
File.Delete(locatableFile.Path);
81+
}
82+
else if (item is ILocatableFolder locatableFolder)
83+
{
84+
Directory.Delete(locatableFolder.Path, true);
85+
}
86+
else
87+
throw new ArgumentException($"Could not delete {item}.");
88+
89+
return Task.CompletedTask;
90+
}
91+
92+
/// <inheritdoc/>
93+
public async Task<IStorable> CreateCopyOfAsync(IStorable itemToCopy, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default)
94+
{
95+
var overwrite = collisionOption == CreationCollisionOption.ReplaceExisting;
96+
97+
if (itemToCopy is IFile sourceFile)
98+
{
99+
if (itemToCopy is ILocatableFile sourceLocatableFile)
100+
{
101+
var newPath = System.IO.Path.Combine(Path, itemToCopy.Name);
102+
File.Copy(sourceLocatableFile.Path, newPath, overwrite);
103+
104+
return new NativeFile(newPath);
105+
}
106+
107+
var copiedFile = await CreateFileAsync(itemToCopy.Name, collisionOption, cancellationToken);
108+
await sourceFile.CopyContentsToAsync(copiedFile, cancellationToken);
109+
110+
return copiedFile;
111+
}
112+
else if (itemToCopy is IFolder sourceFolder)
113+
{
114+
// TODO: Implement folder copy
115+
throw new NotSupportedException();
116+
}
117+
118+
throw new ArgumentException($"Could not copy type {itemToCopy.GetType()}");
119+
}
120+
121+
/// <inheritdoc/>
122+
public async Task<IStorable> MoveFromAsync(IStorable itemToMove, IModifiableFolder source, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default)
123+
{
124+
var overwrite = collisionOption == CreationCollisionOption.ReplaceExisting;
125+
126+
if (itemToMove is IFile sourceFile)
127+
{
128+
if (itemToMove is ILocatableFile sourceLocatableFile)
129+
{
130+
var newPath = System.IO.Path.Combine(Path, itemToMove.Name);
131+
File.Move(sourceLocatableFile.Path, newPath, overwrite);
132+
133+
return new NativeFile(newPath);
134+
}
135+
136+
var copiedFile = await CreateFileAsync(itemToMove.Name, collisionOption, cancellationToken);
137+
await sourceFile.CopyContentsToAsync(copiedFile, cancellationToken);
138+
await source.DeleteAsync(itemToMove, true, cancellationToken);
139+
140+
return copiedFile;
141+
}
142+
else if (itemToMove is IFolder sourceFolder)
143+
{
144+
throw new NotImplementedException();
145+
}
146+
147+
throw new ArgumentException($"Could not move type {itemToMove.GetType()}");
148+
}
149+
150+
/// <inheritdoc/>
151+
public async Task<IFile> CreateFileAsync(string desiredName, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default)
152+
{
153+
var path = System.IO.Path.Combine(Path, desiredName);
154+
if (File.Exists(path))
155+
{
156+
switch (collisionOption)
157+
{
158+
case CreationCollisionOption.GenerateUniqueName:
159+
return await CreateFileAsync($"{System.IO.Path.GetFileNameWithoutExtension(desiredName)} (1){System.IO.Path.GetExtension(desiredName)}", collisionOption, cancellationToken);
160+
161+
case CreationCollisionOption.OpenIfExists:
162+
return new NativeFile(path);
163+
164+
case CreationCollisionOption.FailIfExists:
165+
throw new IOException("File already exists with the same name.");
166+
}
167+
}
168+
169+
await File.Create(path).DisposeAsync();
170+
return new NativeFile(path);
171+
}
172+
173+
/// <inheritdoc/>
174+
public Task<IFolder> CreateFolderAsync(string desiredName, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default)
175+
{
176+
var path = System.IO.Path.Combine(Path, desiredName);
177+
if (Directory.Exists(path))
178+
{
179+
switch (collisionOption)
180+
{
181+
case CreationCollisionOption.GenerateUniqueName:
182+
return CreateFolderAsync($"{desiredName} (1)", collisionOption, cancellationToken);
183+
184+
case CreationCollisionOption.OpenIfExists:
185+
return Task.FromResult<IFolder>(new NativeFolder(path));
186+
187+
case CreationCollisionOption.FailIfExists:
188+
throw new IOException("Folder already exists with the same name.");
189+
}
190+
}
191+
192+
_ = Directory.CreateDirectory(path);
193+
return Task.FromResult<IFolder>(new NativeFolder(path));
194+
}
195+
196+
/// <inheritdoc/>
197+
public Task<IFolderWatcher> GetFolderWatcherAsync(CancellationToken cancellationToken = default)
198+
{
199+
return Task.FromResult<IFolderWatcher>(new NativeFolderWatcher(this));
200+
}
201+
}
202+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using Files.Sdk.Storage.LocatableStorage;
2+
using Files.Sdk.Storage.MutableStorage;
3+
using System.Collections.Specialized;
4+
using System.IO;
5+
using System.Threading.Tasks;
6+
7+
namespace Files.App.Storage.NativeStorage
8+
{
9+
/// <inheritdoc cref="IFolderWatcher"/>
10+
public sealed class NativeFolderWatcher : IFolderWatcher
11+
{
12+
private FileSystemWatcher? _fileSystemWatcher;
13+
private NotifyCollectionChangedEventHandler? _collectionChanged;
14+
15+
public IMutableFolder Folder { get; }
16+
17+
/// <inheritdoc/>
18+
public event NotifyCollectionChangedEventHandler? CollectionChanged
19+
{
20+
add
21+
{
22+
if (_fileSystemWatcher is not null)
23+
_fileSystemWatcher.EnableRaisingEvents = true;
24+
25+
_collectionChanged += value;
26+
}
27+
remove
28+
{
29+
if (_fileSystemWatcher is not null)
30+
_fileSystemWatcher.EnableRaisingEvents = false;
31+
32+
_collectionChanged -= value;
33+
}
34+
}
35+
36+
public NativeFolderWatcher(IMutableFolder folder)
37+
{
38+
Folder = folder;
39+
SetupWatcher();
40+
}
41+
42+
private void SetupWatcher()
43+
{
44+
if (Folder is ILocatableFolder locatableFolder)
45+
{
46+
_fileSystemWatcher = new(locatableFolder.Path);
47+
_fileSystemWatcher.Created += FileSystemWatcher_Created;
48+
_fileSystemWatcher.Deleted += FileSystemWatcher_Deleted;
49+
_fileSystemWatcher.Renamed += FileSystemWatcher_Renamed;
50+
}
51+
}
52+
53+
private void FileSystemWatcher_Renamed(object sender, RenamedEventArgs e)
54+
{
55+
_collectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Replace, e.FullPath, e.OldFullPath));
56+
}
57+
58+
private void FileSystemWatcher_Deleted(object sender, FileSystemEventArgs e)
59+
{
60+
_collectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Remove, e.FullPath));
61+
}
62+
63+
private void FileSystemWatcher_Created(object sender, FileSystemEventArgs e)
64+
{
65+
_collectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Add, e.FullPath));
66+
}
67+
68+
/// <inheritdoc/>
69+
public ValueTask DisposeAsync()
70+
{
71+
Dispose();
72+
return default;
73+
}
74+
75+
/// <inheritdoc/>
76+
public void Dispose()
77+
{
78+
if (_fileSystemWatcher is not null)
79+
{
80+
_fileSystemWatcher.EnableRaisingEvents = false;
81+
_fileSystemWatcher.Created -= FileSystemWatcher_Created;
82+
_fileSystemWatcher.Deleted -= FileSystemWatcher_Deleted;
83+
_fileSystemWatcher.Renamed -= FileSystemWatcher_Renamed;
84+
_fileSystemWatcher.Dispose();
85+
}
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)