Skip to content

Feature: Added file tags widget #10477

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 40 commits into from
Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
953404c
Initial changes
d2dyno1 Nov 15, 2022
99ef5c8
Initial architecture
d2dyno1 Nov 15, 2022
58e5644
Fixed conflicts
d2dyno1 Nov 15, 2022
82aaa00
Added IColorModel
d2dyno1 Dec 7, 2022
5e17989
Merge remote-tracking branch 'upstream/main' into f_bndlsv2
d2dyno1 Dec 7, 2022
7094fe0
Implemented IFileTagsService
d2dyno1 Dec 12, 2022
93004f1
More work on the UI
d2dyno1 Dec 22, 2022
fcc9db7
Storage revision
d2dyno1 Dec 26, 2022
8d54066
Clearer docs for IFileTagsService
d2dyno1 Dec 26, 2022
f903e30
Updated to main
d2dyno1 Dec 26, 2022
0d064b5
Revert Files.App.csproj
d2dyno1 Dec 26, 2022
b90af07
Revert Files.App.csproj
d2dyno1 Dec 26, 2022
fba0a83
Display empty UI for File Tags widget
d2dyno1 Dec 27, 2022
f013115
Added docs
d2dyno1 Dec 28, 2022
c5d6919
Fixed conflicts
d2dyno1 Dec 29, 2022
fc8da52
Fixed conflicts
d2dyno1 Jan 9, 2023
483cd76
Fixed build
d2dyno1 Jan 9, 2023
cdd0f98
More work on File Tags Widget
d2dyno1 Jan 26, 2023
f2cbafa
Changed spacing to tabs
d2dyno1 Jan 26, 2023
3cb022d
Nice UI updates
d2dyno1 Jan 26, 2023
5b55ad6
Nice UI updates 2
d2dyno1 Jan 26, 2023
0ccc9e3
Fixed conflicts
d2dyno1 Jan 26, 2023
af3e9ad
Update FileTagsSettingsService.cs
yaira2 Jan 26, 2023
62d9fb6
Added the option to open items
d2dyno1 Jan 26, 2023
a6931aa
Merge branch 'f_bndlsv2' of https://github.com/files-community/Files …
d2dyno1 Jan 26, 2023
e27870d
Implemented "View more" option
d2dyno1 Jan 26, 2023
bfb79a4
String
yaira2 Jan 26, 2023
887ef76
Strings
yaira2 Jan 26, 2023
1c2f21c
Cleanup
yaira2 Jan 26, 2023
e7c4045
Removed old brush converter
d2dyno1 Jan 27, 2023
313bbdd
Merge branch 'f_bndlsv2' of https://github.com/files-community/Files …
d2dyno1 Jan 27, 2023
aeecd56
Merge branch 'main' into f_bndlsv2
d2dyno1 Jan 27, 2023
ccf900e
Merge branch 'main' into f_bndlsv2
QuaintMako Jan 29, 2023
e07d6fb
Merge branch 'main' into f_bndlsv2
yaira2 Jan 29, 2023
f0d4974
Merge branch 'main' into f_bndlsv2
yaira2 Jan 29, 2023
3fcce5a
Merge branch 'main' into f_bndlsv2
yaira2 Jan 30, 2023
3b5e955
Fixed conflicts
d2dyno1 Jan 30, 2023
9e9e3fe
Merge branch 'f_bndlsv2' of https://github.com/files-community/Files …
d2dyno1 Jan 30, 2023
49fcbbb
Apply changes from code review
d2dyno1 Jan 30, 2023
ab4a805
Apply changes from code review 2
d2dyno1 Jan 30, 2023
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
7 changes: 5 additions & 2 deletions src/Files.App.Storage/FtpStorage/FtpStorable.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Files.Sdk.Storage.LocatableStorage;
using Files.Shared.Helpers;
using FluentFTP;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -7,22 +8,24 @@ namespace Files.App.Storage.FtpStorage
{
public abstract class FtpStorable : ILocatableStorable
{
private string? _computedId;

/// <inheritdoc/>
public string Path { get; protected set; }

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

/// <inheritdoc/>
public string Id { get; protected set; }
public virtual string Id => _computedId ??= ChecksumHelpers.CalculateChecksumForPath(Path);

protected internal FtpStorable(string path, string name)
{
Path = FtpHelpers.GetFtpPath(path);
Name = name;
Id = string.Empty;
}

/// <inheritdoc/>
public virtual Task<ILocatableFolder?> GetParentAsync(CancellationToken cancellationToken = default)
{
return Task.FromResult<ILocatableFolder?>(null);
Expand Down
4 changes: 2 additions & 2 deletions src/Files.App.Storage/FtpStorage/FtpStorageFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public FtpStorageFile(string path, string name)
}

/// <inheritdoc/>
public async Task<Stream> OpenStreamAsync(FileAccess access, FileShare share, CancellationToken cancellationToken = default)
public async Task<Stream> OpenStreamAsync(FileAccess access, CancellationToken cancellationToken = default)
{
using var ftpClient = GetFtpClient();
await ftpClient.EnsureConnectedAsync(cancellationToken);
Expand All @@ -30,7 +30,7 @@ public async Task<Stream> OpenStreamAsync(FileAccess access, FileShare share, Ca
}
else
{
throw new ArgumentException($"Invalid {nameof(share)} flag.");
throw new ArgumentException($"Invalid {nameof(access)} flag.");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using Files.Sdk.Storage;
using Files.Sdk.Storage.LocatableStorage;
using Files.Sdk.Storage.Services;
using FluentFTP;
using System;
using System.IO;
Expand All @@ -8,9 +8,9 @@

namespace Files.App.Storage.FtpStorage
{
public sealed class FtpFileSystemService : IFileSystemService
public sealed class FtpStorageService : IStorageService
{
public Task<bool> IsFileSystemAccessibleAsync(CancellationToken cancellationToken = default)
public Task<bool> IsAccessibleAsync(CancellationToken cancellationToken = default)
{
return Task.FromResult(true); // TODO: Check if FTP is available
}
Expand Down
32 changes: 32 additions & 0 deletions src/Files.App.Storage/NativeStorage/NativeFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Files.Sdk.Storage;
using Files.Sdk.Storage.ExtendableStorage;
using Files.Sdk.Storage.LocatableStorage;
using Files.Sdk.Storage.ModifiableStorage;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Files.App.Storage.NativeStorage
{
/// <inheritdoc cref="IFile"/>
public sealed class NativeFile : NativeStorable, ILocatableFile, IModifiableFile, IFileExtended
{
public NativeFile(string path)
: base(path)
{
}

/// <inheritdoc/>
public Task<Stream> OpenStreamAsync(FileAccess access, CancellationToken cancellationToken = default)
{
return OpenStreamAsync(access, FileShare.None, cancellationToken);
}

/// <inheritdoc/>
public Task<Stream> OpenStreamAsync(FileAccess access, FileShare share = FileShare.None, CancellationToken cancellationToken = default)
{
var stream = File.Open(Path, FileMode.Open, access, share);
return Task.FromResult<Stream>(stream);
}
}
}
202 changes: 202 additions & 0 deletions src/Files.App.Storage/NativeStorage/NativeFolder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
using Files.Sdk.Storage;
using Files.Sdk.Storage.Enums;
using Files.Sdk.Storage.Extensions;
using Files.Sdk.Storage.LocatableStorage;
using Files.Sdk.Storage.ModifiableStorage;
using Files.Sdk.Storage.MutableStorage;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace Files.App.Storage.NativeStorage
{
/// <inheritdoc cref="IFolder"/>
public sealed class NativeFolder : NativeStorable, ILocatableFolder, IModifiableFolder, IMutableFolder
{
public NativeFolder(string path)
: base(path)
{
}

/// <inheritdoc/>
public Task<IFile> GetFileAsync(string fileName, CancellationToken cancellationToken = default)
{
var path = System.IO.Path.Combine(Path, fileName);

if (!File.Exists(path))
throw new FileNotFoundException();

return Task.FromResult<IFile>(new NativeFile(path));
}

/// <inheritdoc/>
public Task<IFolder> GetFolderAsync(string folderName, CancellationToken cancellationToken = default)
{
var path = System.IO.Path.Combine(Path, folderName);

if (!Directory.Exists(path))
throw new FileNotFoundException();

return Task.FromResult<IFolder>(new NativeFolder(path));
}

/// <inheritdoc/>
public async IAsyncEnumerable<IStorable> GetItemsAsync(StorableKind kind = StorableKind.All, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
if (kind == StorableKind.Files)
{
foreach (var item in Directory.EnumerateFiles(Path))
yield return new NativeFile(item);
}
else if (kind == StorableKind.Folders)
{
foreach (var item in Directory.EnumerateDirectories(Path))
yield return new NativeFolder(item);
}
else
{
foreach (var item in Directory.EnumerateFileSystemEntries(Path))
{
if (File.Exists(item))
yield return new NativeFile(item);
else
yield return new NativeFolder(item);
}
}

await Task.CompletedTask;
}

/// <inheritdoc/>
public Task DeleteAsync(IStorable item, bool permanently = false, CancellationToken cancellationToken = default)
{
_ = permanently;

if (item is ILocatableFile locatableFile)
{
File.Delete(locatableFile.Path);
}
else if (item is ILocatableFolder locatableFolder)
{
Directory.Delete(locatableFolder.Path, true);
}
else
throw new ArgumentException($"Could not delete {item}.");

return Task.CompletedTask;
}

/// <inheritdoc/>
public async Task<IStorable> CreateCopyOfAsync(IStorable itemToCopy, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default)
{
var overwrite = collisionOption == CreationCollisionOption.ReplaceExisting;

if (itemToCopy is IFile sourceFile)
{
if (itemToCopy is ILocatableFile sourceLocatableFile)
{
var newPath = System.IO.Path.Combine(Path, itemToCopy.Name);
File.Copy(sourceLocatableFile.Path, newPath, overwrite);

return new NativeFile(newPath);
}

var copiedFile = await CreateFileAsync(itemToCopy.Name, collisionOption, cancellationToken);
await sourceFile.CopyContentsToAsync(copiedFile, cancellationToken);

return copiedFile;
}
else if (itemToCopy is IFolder sourceFolder)
{
// TODO: Implement folder copy
throw new NotSupportedException();
}

throw new ArgumentException($"Could not copy type {itemToCopy.GetType()}");
}

/// <inheritdoc/>
public async Task<IStorable> MoveFromAsync(IStorable itemToMove, IModifiableFolder source, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default)
{
var overwrite = collisionOption == CreationCollisionOption.ReplaceExisting;

if (itemToMove is IFile sourceFile)
{
if (itemToMove is ILocatableFile sourceLocatableFile)
{
var newPath = System.IO.Path.Combine(Path, itemToMove.Name);
File.Move(sourceLocatableFile.Path, newPath, overwrite);

return new NativeFile(newPath);
}

var copiedFile = await CreateFileAsync(itemToMove.Name, collisionOption, cancellationToken);
await sourceFile.CopyContentsToAsync(copiedFile, cancellationToken);
await source.DeleteAsync(itemToMove, true, cancellationToken);

return copiedFile;
}
else if (itemToMove is IFolder sourceFolder)
{
throw new NotImplementedException();
}

throw new ArgumentException($"Could not move type {itemToMove.GetType()}");
}

/// <inheritdoc/>
public async Task<IFile> CreateFileAsync(string desiredName, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default)
{
var path = System.IO.Path.Combine(Path, desiredName);
if (File.Exists(path))
{
switch (collisionOption)
{
case CreationCollisionOption.GenerateUniqueName:
return await CreateFileAsync($"{System.IO.Path.GetFileNameWithoutExtension(desiredName)} (1){System.IO.Path.GetExtension(desiredName)}", collisionOption, cancellationToken);

case CreationCollisionOption.OpenIfExists:
return new NativeFile(path);

case CreationCollisionOption.FailIfExists:
throw new IOException("File already exists with the same name.");
}
}

await File.Create(path).DisposeAsync();
return new NativeFile(path);
}

/// <inheritdoc/>
public Task<IFolder> CreateFolderAsync(string desiredName, CreationCollisionOption collisionOption = default, CancellationToken cancellationToken = default)
{
var path = System.IO.Path.Combine(Path, desiredName);
if (Directory.Exists(path))
{
switch (collisionOption)
{
case CreationCollisionOption.GenerateUniqueName:
return CreateFolderAsync($"{desiredName} (1)", collisionOption, cancellationToken);

case CreationCollisionOption.OpenIfExists:
return Task.FromResult<IFolder>(new NativeFolder(path));

case CreationCollisionOption.FailIfExists:
throw new IOException("Folder already exists with the same name.");
}
}

_ = Directory.CreateDirectory(path);
return Task.FromResult<IFolder>(new NativeFolder(path));
}

/// <inheritdoc/>
public Task<IFolderWatcher> GetFolderWatcherAsync(CancellationToken cancellationToken = default)
{
return Task.FromResult<IFolderWatcher>(new NativeFolderWatcher(this));
}
}
}
88 changes: 88 additions & 0 deletions src/Files.App.Storage/NativeStorage/NativeFolderWatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using Files.Sdk.Storage.LocatableStorage;
using Files.Sdk.Storage.MutableStorage;
using System.Collections.Specialized;
using System.IO;
using System.Threading.Tasks;

namespace Files.App.Storage.NativeStorage
{
/// <inheritdoc cref="IFolderWatcher"/>
public sealed class NativeFolderWatcher : IFolderWatcher
{
private FileSystemWatcher? _fileSystemWatcher;
private NotifyCollectionChangedEventHandler? _collectionChanged;

public IMutableFolder Folder { get; }

/// <inheritdoc/>
public event NotifyCollectionChangedEventHandler? CollectionChanged
{
add
{
if (_fileSystemWatcher is not null)
_fileSystemWatcher.EnableRaisingEvents = true;

_collectionChanged += value;
}
remove
{
if (_fileSystemWatcher is not null)
_fileSystemWatcher.EnableRaisingEvents = false;

_collectionChanged -= value;
}
}

public NativeFolderWatcher(IMutableFolder folder)
{
Folder = folder;
SetupWatcher();
}

private void SetupWatcher()
{
if (Folder is ILocatableFolder locatableFolder)
{
_fileSystemWatcher = new(locatableFolder.Path);
_fileSystemWatcher.Created += FileSystemWatcher_Created;
_fileSystemWatcher.Deleted += FileSystemWatcher_Deleted;
_fileSystemWatcher.Renamed += FileSystemWatcher_Renamed;
}
}

private void FileSystemWatcher_Renamed(object sender, RenamedEventArgs e)
{
_collectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Replace, e.FullPath, e.OldFullPath));
}

private void FileSystemWatcher_Deleted(object sender, FileSystemEventArgs e)
{
_collectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Remove, e.FullPath));
}

private void FileSystemWatcher_Created(object sender, FileSystemEventArgs e)
{
_collectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Add, e.FullPath));
}

/// <inheritdoc/>
public ValueTask DisposeAsync()
{
Dispose();
return default;
}

/// <inheritdoc/>
public void Dispose()
{
if (_fileSystemWatcher is not null)
{
_fileSystemWatcher.EnableRaisingEvents = false;
_fileSystemWatcher.Created -= FileSystemWatcher_Created;
_fileSystemWatcher.Deleted -= FileSystemWatcher_Deleted;
_fileSystemWatcher.Renamed -= FileSystemWatcher_Renamed;
_fileSystemWatcher.Dispose();
}
}
}
}
Loading