Skip to content

Commit

Permalink
Merge pull request #56 from itsWindows11/imp/dupe-sysio-impl-exists-c…
Browse files Browse the repository at this point in the history
…heck

Remove redundant validation when enumerating files & folders in SystemFile/SystemFolder.
  • Loading branch information
Arlodotexe authored Jun 24, 2024
2 parents 4e2c5bb + 6a955c1 commit 1a6e589
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 42 deletions.
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

0 comments on commit 1a6e589

Please sign in to comment.