Skip to content
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

Remove redundant validation when enumerating files & folders in SystemFile/SystemFolder. #56

Merged
Merged
45 changes: 40 additions & 5 deletions src/System/IO/SystemFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ public SystemFile(string path)
if (!File.Exists(path))
throw new FileNotFoundException($"File not found at path {path}.");

Id = path;
Path = path;
}

Expand All @@ -45,12 +44,48 @@ public SystemFile(FileInfo info)
_info = info;

_name = _info.Name;
Id = _info.FullName;
Path = _info.FullName;
}

/// <summary>
/// Creates a new instance of <see cref="SystemFile"/>
/// </summary>
/// <remarks>
/// NOTE: This constructor does not verify whether the file
/// actually exists beforehand. Do not use outside of enumeration
/// or when it's known that the file exists.
/// </remarks>
/// <param name="path">The path to the file.</param>
/// <param name="noValidation">
/// A required value for this overload. No functional difference between provided values.
/// </param>
internal SystemFile(string path, bool noValidation)
{
Path = path;
}

/// <summary>
/// Creates a new instance of <see cref="SystemFile"/>
/// </summary>
/// <remarks>
/// NOTE: This constructor does not verify whether the file
/// actually exists beforehand. Do not use outside of enumeration
/// or when it's known that the file exists.
/// </remarks>
/// <param name="info">The file info.</param>
/// <param name="noValidation">
/// A required value for this overload. No functional difference between provided values.
/// </param>
internal SystemFile(FileInfo info, bool noValidation)
{
_info = info;

_name = _info.Name;
Path = _info.FullName;
}

/// <inheritdoc />
public string Id { get; }
public string Id => Path;

/// <inheritdoc />
public string Name => _name ??= global::System.IO.Path.GetFileName(Path);
Expand Down Expand Up @@ -78,13 +113,13 @@ public Task<Stream> OpenStreamAsync(FileAccess accessMode = FileAccess.Read, Can
public Task<IFolder?> GetParentAsync(CancellationToken cancellationToken = default)
{
DirectoryInfo? parent = _info != null ? _info.Directory : Directory.GetParent(Path);
return Task.FromResult<IFolder?>(parent != null ? new SystemFolder(parent) : null);
return Task.FromResult<IFolder?>(parent != null ? new SystemFolder(parent, noValidation: true) : null);
}

/// <inheritdoc />
public Task<IFolder?> GetRootAsync(CancellationToken cancellationToken = default)
{
DirectoryInfo root = _info?.Directory != null ? _info.Directory.Root : new DirectoryInfo(Path).Root;
return Task.FromResult<IFolder?>(new SystemFolder(root));
return Task.FromResult<IFolder?>(new SystemFolder(root, noValidation: true));
}
}
104 changes: 69 additions & 35 deletions src/System/IO/SystemFolder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand All @@ -15,6 +15,7 @@ namespace OwlCore.Storage.System.IO;
/// </summary>
public class SystemFolder : IModifiableFolder, IChildFolder, ICreateCopyOf, IMoveFrom, IGetItem, IGetItemRecursive, IGetFirstByName, IGetRoot
{
private string? _name;
private DirectoryInfo? _info;

/// <summary>
Expand All @@ -29,14 +30,11 @@ public SystemFolder(string path)
throw new FormatException($"Provided path contains invalid character '{c}'.");
}

// For consistency, always remove the trailing directory separator.
Path = path.TrimEnd(global::System.IO.Path.PathSeparator, global::System.IO.Path.DirectorySeparatorChar, global::System.IO.Path.AltDirectorySeparatorChar);

if (!Directory.Exists(path))
throw new FileNotFoundException($"Directory not found at path '{Path}'.");

Id = Path;
Name = global::System.IO.Path.GetFileName(Path) ?? throw new ArgumentException($"Could not determine directory name from path '{Path}'.");
// For consistency, always remove the trailing directory separator.
Path = path.TrimEnd(global::System.IO.Path.PathSeparator, global::System.IO.Path.DirectorySeparatorChar, global::System.IO.Path.AltDirectorySeparatorChar);
}

/// <summary>
Expand All @@ -45,16 +43,54 @@ public SystemFolder(string path)
/// <param name="info">The directory to use.</param>
public SystemFolder(DirectoryInfo info)
{
if (!info.Exists)
throw new FileNotFoundException($"Directory not found at path '{Path}'.");

_info = info;

// For consistency, always remove the trailing directory separator.
Path = info.FullName.TrimEnd(global::System.IO.Path.PathSeparator, global::System.IO.Path.DirectorySeparatorChar, global::System.IO.Path.AltDirectorySeparatorChar);
_name = info.Name;
}

if (!info.Exists)
throw new FileNotFoundException($"Directory not found at path '{Path}'.");
/// <summary>
/// Creates a new instance of <see cref="SystemFolder"/>
/// </summary>
/// <remarks>
/// NOTE: This constructor does not verify whether the directory
/// actually exists beforehand. Do not use outside of enumeration
/// or when it's known that the folder exists.
/// </remarks>
/// <param name="path">The path to the folder.</param>
/// <param name="noValidation">
/// A required value for this overload. No functional difference between provided values.
/// </param>
internal SystemFolder(string path, bool noValidation)
{
// For consistency, always remove the trailing directory separator.
Path = path.TrimEnd(global::System.IO.Path.PathSeparator, global::System.IO.Path.DirectorySeparatorChar, global::System.IO.Path.AltDirectorySeparatorChar);
}

Id = Path;
Name = global::System.IO.Path.GetFileName(Path) ?? throw new ArgumentException($"Could not determine directory name from path '{Path}'.");

/// <summary>
/// Creates a new instance of <see cref="SystemFolder"/>.
/// </summary>
/// <remarks>
/// NOTE: This constructor does not verify whether the directory
/// actually exists beforehand. Do not use outside of enumeration
/// or when it's known that the folder exists.
/// </remarks>
/// <param name="info">The directory to use.</param>
/// <param name="noValidation">
/// A required value for this overload. No functional difference between provided values.
/// </param>
internal SystemFolder(DirectoryInfo info, bool noValidation)
{
_info = info;

// For consistency, always remove the trailing directory separator.
Path = info.FullName.TrimEnd(global::System.IO.Path.PathSeparator, global::System.IO.Path.DirectorySeparatorChar, global::System.IO.Path.AltDirectorySeparatorChar);
_name = info.Name;
}

/// <summary>
Expand All @@ -63,10 +99,10 @@ public SystemFolder(DirectoryInfo info)
public DirectoryInfo Info => _info ??= new DirectoryInfo(Path);

/// <inheritdoc />
public string Id { get; }
public string Id => Path;

/// <inheritdoc />
public string Name { get; }
public string Name => _name ??= global::System.IO.Path.GetFileName(Path) ?? throw new ArgumentException($"Could not determine directory name from path '{Path}'.");

/// <summary>
/// Gets the path of the folder on disk.
Expand All @@ -83,18 +119,17 @@ public async IAsyncEnumerable<IStorableChild> GetItemsAsync(StorableType type =

if (type.HasFlag(StorableType.All))
{
foreach (var item in Directory.EnumerateFileSystemEntries(Path))
foreach (var item in Info.EnumerateFileSystemInfos())
{
cancellationToken.ThrowIfCancellationRequested();

if (item is null)
continue;

if (IsFolder(item))
yield return new SystemFolder(item);

else if (IsFile(item))
yield return new SystemFile(item);
if (item.Attributes.HasFlag(FileAttributes.Directory))
yield return new SystemFolder((DirectoryInfo)item, noValidation: true);
else
yield return new SystemFile((FileInfo)item, noValidation: true);
}

yield break;
Expand All @@ -109,7 +144,7 @@ public async IAsyncEnumerable<IStorableChild> GetItemsAsync(StorableType type =
if (file is null)
continue;

yield return new SystemFile(file);
yield return new SystemFile(file, noValidation: true);
}
}

Expand All @@ -122,7 +157,7 @@ public async IAsyncEnumerable<IStorableChild> GetItemsAsync(StorableType type =
if (folder is null)
continue;

yield return new SystemFolder(folder);
yield return new SystemFolder(folder, noValidation: true);
}
}
}
Expand All @@ -135,10 +170,10 @@ public Task<IStorableChild> GetItemRecursiveAsync(string id, CancellationToken c

// Since the path is used as the id, we can provide a fast method of getting a single item, without iterating.
if (IsFile(id))
return Task.FromResult<IStorableChild>(new SystemFile(id));
return Task.FromResult<IStorableChild>(new SystemFile(id, noValidation: true));

if (IsFolder(id))
return Task.FromResult<IStorableChild>(new SystemFolder(id));
return Task.FromResult<IStorableChild>(new SystemFolder(id, noValidation: true));

throw new ArgumentException($"Could not determine if the provided path is a file or folder. Path '{id}'.");
}
Expand All @@ -159,7 +194,7 @@ public Task<IStorableChild> GetItemAsync(string id, CancellationToken cancellati
if (!File.Exists(fullPath))
throw new FileNotFoundException($"The provided Id does not belong to an item in this folder.");

return Task.FromResult<IStorableChild>(new SystemFile(fullPath));
return Task.FromResult<IStorableChild>(new SystemFile(fullPath, noValidation: true));
}

if (IsFolder(id))
Expand All @@ -168,16 +203,16 @@ public Task<IStorableChild> GetItemAsync(string id, CancellationToken cancellati
if (global::System.IO.Path.GetDirectoryName(id) != Path || !Directory.Exists(id))
throw new FileNotFoundException($"The provided Id does not belong to an item in this folder.");

return Task.FromResult<IStorableChild>(new SystemFolder(id));
return Task.FromResult<IStorableChild>(new SystemFolder(id, noValidation: true));
}

throw new FileNotFoundException($"Could not determine if the provided path exists, or whether it's a file or folder. Id '{id}'.");
}

/// <inheritdoc/>
public async Task<IStorableChild> GetFirstByNameAsync(string name, CancellationToken cancellationToken = default)
public Task<IStorableChild> GetFirstByNameAsync(string name, CancellationToken cancellationToken = default)
{
return await GetItemAsync(global::System.IO.Path.Combine(Path, name), cancellationToken);
return GetItemAsync(global::System.IO.Path.Combine(Path, name), cancellationToken);
}

/// <inheritdoc />
Expand All @@ -195,8 +230,7 @@ public Task DeleteAsync(IStorableChild item, CancellationToken cancellationToken

if (IsFolder(item.Id))
Directory.Delete(item.Id, recursive: true);

if (IsFile(item.Id))
else if (IsFile(item.Id))
File.Delete(item.Id);

return Task.CompletedTask;
Expand All @@ -219,14 +253,14 @@ public async Task<IChildFile> CreateCopyOfAsync(IFile fileToCopy, bool overwrite
if (File.Exists(newPath))
{
if (!overwrite)
return new SystemFile(newPath);
return new SystemFile(newPath, noValidation: true);

File.Delete(newPath);
}

File.Copy(systemFile.Path, newPath, overwrite);

return new SystemFile(newPath);
return new SystemFile(newPath, noValidation: true);
}

/// <inheritdoc />
Expand All @@ -239,14 +273,14 @@ public async Task<IChildFile> MoveFromAsync(IChildFile fileToMove, IModifiableFo
// Handle using System.IO
var newPath = global::System.IO.Path.Combine(Path, systemFile.Name);
if (File.Exists(newPath) && !overwrite)
return new SystemFile(newPath);
return new SystemFile(newPath, noValidation: true);

if (overwrite)
File.Delete(newPath);

File.Move(systemFile.Path, newPath);

return new SystemFile(newPath);
return new SystemFile(newPath, noValidation: true);
}

/// <inheritdoc />
Expand Down Expand Up @@ -276,13 +310,13 @@ public Task<IChildFile> CreateFileAsync(string name, bool overwrite = false, Can
if (overwrite || !File.Exists(newPath))
File.Create(newPath).Dispose();

return Task.FromResult<IChildFile>(new SystemFile(newPath));
return Task.FromResult<IChildFile>(new SystemFile(newPath, noValidation: true));
}

/// <inheritdoc />
public Task<IFolder?> GetParentAsync(CancellationToken cancellationToken = default)
{
return Task.FromResult<IFolder?>(Directory.GetParent(Path) is { } di ? new SystemFolder(di) : null);
return Task.FromResult<IFolder?>(Directory.GetParent(Path) is { } di ? new SystemFolder(di, noValidation: true) : null);
}

/// <inheritdoc />
Expand Down Expand Up @@ -311,4 +345,4 @@ string GetParentDirectoryName(string relativePath)

return parentPath.Replace(parentParentPath, "").TrimEnd(global::System.IO.Path.DirectorySeparatorChar);
}
}
}
4 changes: 2 additions & 2 deletions src/System/IO/SystemFolderWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,15 @@ private static IStorable CreateStorableFromPath(string path, bool minimalImpleme
if (minimalImplementation)
return new SimpleStorableItem(id: path, name: Path.GetDirectoryName(path) ?? throw new ArgumentException($"Could not determine directory name from path '{path}'."));

return new SystemFolder(path);
return new SystemFolder(path, noValidation: true);
}

if (IsFile(path))
{
if (minimalImplementation)
return new SimpleStorableItem(id: path, name: Path.GetFileName(path));

return new SystemFile(path);
return new SystemFile(path, noValidation: true);
}

// The item is most likely deleted. Return all available information through SimpleStorableItem
Expand Down
Loading