diff --git a/src/Zio.Tests/FileSystems/TestMemoryFileSystem.cs b/src/Zio.Tests/FileSystems/TestMemoryFileSystem.cs index 8ec0028..e1bb9c6 100644 --- a/src/Zio.Tests/FileSystems/TestMemoryFileSystem.cs +++ b/src/Zio.Tests/FileSystems/TestMemoryFileSystem.cs @@ -1,5 +1,5 @@ // Copyright (c) Alexandre Mutel. All rights reserved. -// This file is licensed under the BSD-Clause 2 license. +// This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. using Zio.FileSystems; @@ -36,10 +36,10 @@ public void TestCopyFileSystemSubFolder() fs.CopyTo(dest, subFolder, true); var destSubFileSystem = dest.GetOrCreateSubFileSystem(subFolder); - + AssertFileSystemEqual(fs, destSubFileSystem); } - + [Fact] public void TestWatcher() @@ -63,4 +63,57 @@ public void TestDispose() memfs.Dispose(); Assert.Throws(() => memfs.DirectoryExists("/")); } + + [Fact] + public void TestCopyFileCross() + { + var fs = new TriggerMemoryFileSystem(); + fs.CreateDirectory("/sub1"); + fs.CreateDirectory("/sub2"); + var sub1 = new SubFileSystem(fs, "/sub1"); + var sub2 = new SubFileSystem(fs, "/sub2"); + sub1.WriteAllText("/file.txt", "test"); + sub1.CopyFileCross("/file.txt", sub2, "/file.txt", overwrite: false); + Assert.Equal("test", sub2.ReadAllText("/file.txt")); + Assert.Equal(TriggerMemoryFileSystem.TriggerType.Copy, fs.Triggered); + } + + [Fact] + public void TestMoveFileCross() + { + var fs = new TriggerMemoryFileSystem(); + fs.CreateDirectory("/sub1"); + fs.CreateDirectory("/sub2"); + var sub1 = new SubFileSystem(fs, "/sub1"); + var sub2 = new SubFileSystem(fs, "/sub2"); + sub1.WriteAllText("/file.txt", "test"); + sub1.MoveFileCross("/file.txt", sub2, "/file.txt"); + Assert.Equal("test", sub2.ReadAllText("/file.txt")); + Assert.False(sub1.FileExists("/file.txt")); + Assert.Equal(TriggerMemoryFileSystem.TriggerType.Move, fs.Triggered); + } + + private sealed class TriggerMemoryFileSystem : MemoryFileSystem + { + public enum TriggerType + { + None, + Copy, + Move + } + + public TriggerType Triggered { get; private set; } = TriggerType.None; + + protected override void CopyFileImpl(UPath srcPath, UPath destPath, bool overwrite) + { + Triggered = TriggerType.Copy; + base.CopyFileImpl(srcPath, destPath, overwrite); + } + + protected override void MoveFileImpl(UPath srcPath, UPath destPath) + { + Triggered = TriggerType.Move; + base.MoveFileImpl(srcPath, destPath); + } + } } \ No newline at end of file diff --git a/src/Zio.Tests/FileSystems/TestPhysicalFileSystem.cs b/src/Zio.Tests/FileSystems/TestPhysicalFileSystem.cs index abd40fd..069630c 100644 --- a/src/Zio.Tests/FileSystems/TestPhysicalFileSystem.cs +++ b/src/Zio.Tests/FileSystems/TestPhysicalFileSystem.cs @@ -1,5 +1,5 @@ // Copyright (c) Alexandre Mutel. All rights reserved. -// This file is licensed under the BSD-Clause 2 license. +// This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. using System.IO; @@ -336,7 +336,7 @@ public void TestEnumerate() var expectedPaths = Directory.EnumerateFileSystemEntries(Path.GetFullPath(Path.Combine(SystemPath, "../.."))).ToList(); Assert.Equal(expectedPaths, paths); } - + [SkippableFact] public void TestFileWindowsExceptions() { @@ -536,4 +536,14 @@ public void TestFileSymlink() SafeDeleteFile(systemPathDest); } } + + [Fact] + public void TestResolvePath() + { + var fs = new PhysicalFileSystem(); + var testPath = fs.ConvertPathFromInternal(SystemPath); + var (resFs, resPath) = fs.ResolvePath(testPath); + Assert.Equal(testPath, resPath); + Assert.Equal(fs, resFs); + } } \ No newline at end of file diff --git a/src/Zio.Tests/FileSystems/TestSubFileSystem.cs b/src/Zio.Tests/FileSystems/TestSubFileSystem.cs index 174e450..6de2c8f 100644 --- a/src/Zio.Tests/FileSystems/TestSubFileSystem.cs +++ b/src/Zio.Tests/FileSystems/TestSubFileSystem.cs @@ -1,5 +1,5 @@ // Copyright (c) Alexandre Mutel. All rights reserved. -// This file is licensed under the BSD-Clause 2 license. +// This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. using System.IO; @@ -36,7 +36,7 @@ public void TestBasic() } // TODO: We could add another test just to make sure that files can be created...etc. But the test above should already cover the code provided in SubFileSystem - } + } [Fact] public void TestGetOrCreateFileSystem() @@ -110,6 +110,27 @@ public void TestWatcherCaseSensitive(string physicalDir, string subDir, string f Assert.True(waitHandle.WaitOne(100)); } + [Fact] + public void TestResolvePath() + { + var fs = GetCommonMemoryFileSystem(); + var subFs = fs.GetOrCreateSubFileSystem("/a/b"); + var (resFs, resPath) = subFs.ResolvePath("/c"); + Assert.Equal("/a/b/c", resPath); + Assert.NotEqual(subFs, resFs); + Assert.Equal(fs, resFs); + (resFs, resPath) = subFs.ResolvePath("/c/d"); + Assert.Equal("/a/b/c/d", resPath); + Assert.NotEqual(subFs, resFs); + Assert.Equal(fs, resFs); + + var subFs2 = subFs.GetOrCreateSubFileSystem("/q"); + (resFs, resPath) = subFs2.ResolvePath("/c"); + Assert.Equal("/a/b/q/c", resPath); + Assert.NotEqual(subFs2, resFs); + Assert.Equal(fs, resFs); + } + [SkippableFact] public void TestDirectorySymlink() { diff --git a/src/Zio.Tests/Zio.Tests.csproj b/src/Zio.Tests/Zio.Tests.csproj index 686b35f..896d525 100644 --- a/src/Zio.Tests/Zio.Tests.csproj +++ b/src/Zio.Tests/Zio.Tests.csproj @@ -1,7 +1,7 @@  - net472;net8.0 + net8.0;net472 false 10 diff --git a/src/Zio/FileSystemExtensions.cs b/src/Zio/FileSystemExtensions.cs index 3113f48..002ec03 100644 --- a/src/Zio/FileSystemExtensions.cs +++ b/src/Zio/FileSystemExtensions.cs @@ -1,5 +1,5 @@ // Copyright (c) Alexandre Mutel. All rights reserved. -// This file is licensed under the BSD-Clause 2 license. +// This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. using System.IO; @@ -149,6 +149,9 @@ public static void CopyFileCross(this IFileSystem fs, UPath srcPath, IFileSystem { if (destFileSystem is null) throw new ArgumentNullException(nameof(destFileSystem)); + (fs, srcPath) = fs.ResolvePath(srcPath); + (destFileSystem, destPath) = destFileSystem.ResolvePath(destPath); + // If this is the same filesystem, use the file system directly to perform the action if (fs == destFileSystem) { @@ -224,6 +227,9 @@ public static void MoveFileCross(this IFileSystem fs, UPath srcPath, IFileSystem { if (destFileSystem is null) throw new ArgumentNullException(nameof(destFileSystem)); + (fs, srcPath) = fs.ResolvePath(srcPath); + (destFileSystem, destPath) = destFileSystem.ResolvePath(destPath); + // If this is the same filesystem, use the file system directly to perform the action if (fs == destFileSystem) { @@ -340,11 +346,11 @@ public static byte[] ReadAllBytes(this IFileSystem fs, UPath path) public static string ReadAllText(this IFileSystem fs, UPath path) { var stream = fs.OpenFile(path, FileMode.Open, FileAccess.Read, FileShare.Read); - + using (var reader = new StreamReader(stream)) { return reader.ReadToEnd(); - } + } } /// @@ -358,7 +364,7 @@ public static string ReadAllText(this IFileSystem fs, UPath path, Encoding encod { if (encoding is null) throw new ArgumentNullException(nameof(encoding)); var stream = fs.OpenFile(path, FileMode.Open, FileAccess.Read, FileShare.Read); - + using (var reader = new StreamReader(stream, encoding)) { return reader.ReadToEnd(); @@ -576,7 +582,7 @@ public static IEnumerable EnumerateDirectories(this IFileSystem fileSyste /// /// The file system. /// The path of the directory to look for directories. - /// The search string to match against the names of directories in path. This parameter can contain a combination + /// The search string to match against the names of directories in path. This parameter can contain a combination /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions. /// An enumerable collection of the full names (including paths) for the directories in the directory specified by path. public static IEnumerable EnumerateDirectories(this IFileSystem fileSystem, UPath path, string searchPattern) @@ -590,9 +596,9 @@ public static IEnumerable EnumerateDirectories(this IFileSystem fileSyste /// /// The file system. /// The path of the directory to look for directories. - /// The search string to match against the names of directories in path. This parameter can contain a combination + /// The search string to match against the names of directories in path. This parameter can contain a combination /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions. - /// One of the enumeration values that specifies whether the search operation should include only the current directory + /// One of the enumeration values that specifies whether the search operation should include only the current directory /// or should include all subdirectories. /// The default value is TopDirectoryOnly. /// An enumerable collection of the full names (including paths) for the directories in the directory specified by path. @@ -618,7 +624,7 @@ public static IEnumerable EnumerateFiles(this IFileSystem fileSystem, UPa /// /// The file system. /// The path of the directory to look for files. - /// The search string to match against the names of directories in path. This parameter can contain a combination + /// The search string to match against the names of directories in path. This parameter can contain a combination /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions. /// An enumerable collection of the full names (including paths) for the files in the directory specified by path. public static IEnumerable EnumerateFiles(this IFileSystem fileSystem, UPath path, string searchPattern) @@ -632,9 +638,9 @@ public static IEnumerable EnumerateFiles(this IFileSystem fileSystem, UPa /// /// The file system. /// The path of the directory to look for files. - /// The search string to match against the names of directories in path. This parameter can contain a combination + /// The search string to match against the names of directories in path. This parameter can contain a combination /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions. - /// One of the enumeration values that specifies whether the search operation should include only the current directory + /// One of the enumeration values that specifies whether the search operation should include only the current directory /// or should include all subdirectories. /// The default value is TopDirectoryOnly. /// An enumerable collection of the full names (including paths) for the files in the directory specified by path. @@ -660,7 +666,7 @@ public static IEnumerable EnumeratePaths(this IFileSystem fileSystem, UPa /// /// The file system. /// The path of the directory to look for files or directories. - /// The search string to match against the names of directories in path. This parameter can contain a combination + /// The search string to match against the names of directories in path. This parameter can contain a combination /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions. /// An enumerable collection of the full names (including paths) for the files and directories in the directory specified by path. public static IEnumerable EnumeratePaths(this IFileSystem fileSystem, UPath path, string searchPattern) @@ -674,9 +680,9 @@ public static IEnumerable EnumeratePaths(this IFileSystem fileSystem, UPa /// /// The file system. /// The path of the directory to look for files or directories. - /// The search string to match against the names of directories in path. This parameter can contain a combination + /// The search string to match against the names of directories in path. This parameter can contain a combination /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions. - /// One of the enumeration values that specifies whether the search operation should include only the current directory + /// One of the enumeration values that specifies whether the search operation should include only the current directory /// or should include all subdirectories. /// The default value is TopDirectoryOnly. /// An enumerable collection of the full names (including paths) for the files and directories in the directory specified by path. @@ -702,7 +708,7 @@ public static IEnumerable EnumerateFileEntries(this IFileSystem fileS /// /// The file system. /// The path of the directory to look for files. - /// The search string to match against the names of directories in path. This parameter can contain a combination + /// The search string to match against the names of directories in path. This parameter can contain a combination /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions. /// An enumerable collection of from the specified path. public static IEnumerable EnumerateFileEntries(this IFileSystem fileSystem, UPath path, string searchPattern) @@ -716,9 +722,9 @@ public static IEnumerable EnumerateFileEntries(this IFileSystem fileS /// /// The file system. /// The path of the directory to look for files. - /// The search string to match against the names of directories in path. This parameter can contain a combination + /// The search string to match against the names of directories in path. This parameter can contain a combination /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions. - /// One of the enumeration values that specifies whether the search operation should include only the current directory + /// One of the enumeration values that specifies whether the search operation should include only the current directory /// or should include all subdirectories. /// The default value is TopDirectoryOnly. /// An enumerable collection of from the specified path. @@ -747,7 +753,7 @@ public static IEnumerable EnumerateDirectoryEntries(this IFileSy /// /// The file system. /// The path of the directory to look for directories. - /// The search string to match against the names of directories in path. This parameter can contain a combination + /// The search string to match against the names of directories in path. This parameter can contain a combination /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions. /// An enumerable collection of from the specified path. public static IEnumerable EnumerateDirectoryEntries(this IFileSystem fileSystem, UPath path, string searchPattern) @@ -761,9 +767,9 @@ public static IEnumerable EnumerateDirectoryEntries(this IFileSy /// /// The file system. /// The path of the directory to look for directories. - /// The search string to match against the names of directories in path. This parameter can contain a combination + /// The search string to match against the names of directories in path. This parameter can contain a combination /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions. - /// One of the enumeration values that specifies whether the search operation should include only the current directory + /// One of the enumeration values that specifies whether the search operation should include only the current directory /// or should include all subdirectories. /// The default value is TopDirectoryOnly. /// An enumerable collection of from the specified path. @@ -792,7 +798,7 @@ public static IEnumerable EnumerateFileSystemEntries(this IFile /// /// The file system. /// The path of the directory to look for files and directories. - /// The search string to match against the names of directories in path. This parameter can contain a combination + /// The search string to match against the names of directories in path. This parameter can contain a combination /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions. /// An enumerable collection of that match a search pattern in a specified path. public static IEnumerable EnumerateFileSystemEntries(this IFileSystem fileSystem, UPath path, string searchPattern) @@ -806,9 +812,9 @@ public static IEnumerable EnumerateFileSystemEntries(this IFile /// /// The file system. /// The path of the directory to look for files and directories. - /// The search string to match against the names of directories in path. This parameter can contain a combination + /// The search string to match against the names of directories in path. This parameter can contain a combination /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions. - /// One of the enumeration values that specifies whether the search operation should include only the current directory + /// One of the enumeration values that specifies whether the search operation should include only the current directory /// or should include all subdirectories. /// The default value is TopDirectoryOnly. /// The search target either or only or . Default is diff --git a/src/Zio/FileSystems/ComposeFileSystem.cs b/src/Zio/FileSystems/ComposeFileSystem.cs index f201821..1dc14b4 100644 --- a/src/Zio/FileSystems/ComposeFileSystem.cs +++ b/src/Zio/FileSystems/ComposeFileSystem.cs @@ -1,5 +1,5 @@ // Copyright (c) Alexandre Mutel. All rights reserved. -// This file is licensed under the BSD-Clause 2 license. +// This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. using System.Diagnostics; using System.IO; @@ -7,7 +7,7 @@ namespace Zio.FileSystems; /// -/// Provides an abstract base for composing a filesystem with another FileSystem. +/// Provides an abstract base for composing a filesystem with another FileSystem. /// This implementation delegates by default its implementation to the filesystem passed to the constructor. /// public abstract class ComposeFileSystem : FileSystem @@ -274,4 +274,7 @@ protected override UPath ConvertPathFromInternalImpl(string innerPath) /// The path used by the underlying /// A new path translated to this filesystem protected abstract UPath ConvertPathFromDelegate(UPath path); + + protected override (IFileSystem FileSystem, UPath Path) ResolvePathImpl(UPath path) + => FallbackSafe.ResolvePath(ConvertPathToDelegate(path)); } \ No newline at end of file diff --git a/src/Zio/FileSystems/FileSystem.cs b/src/Zio/FileSystems/FileSystem.cs index f0de432..c867d17 100644 --- a/src/Zio/FileSystems/FileSystem.cs +++ b/src/Zio/FileSystems/FileSystem.cs @@ -584,6 +584,25 @@ public UPath ConvertPathFromInternal(string systemPath) /// The converted path according to the system path. protected abstract UPath ConvertPathFromInternalImpl(string innerPath); + /// + public (IFileSystem FileSystem, UPath Path) ResolvePath(UPath path) + { + AssertNotDisposed(); + return ResolvePathImpl(ValidatePath(path)); + } + + /// + /// Implementation for . + /// Resolves the specified path to a path in the underlying file system, if one exists. For instance, a would + /// resolve the path to a qualified path in the underlying file system. + /// + /// The path to resolve. + /// + /// A tuple of the resolved path and file system. If there is no underlying file + /// system, the returned path is the same, and the file system is the same. + /// + protected virtual (IFileSystem FileSystem, UPath Path) ResolvePathImpl(UPath path) => (this, path); + /// /// User overridable implementation for to validate the specified path. /// diff --git a/src/Zio/IFileSystem.cs b/src/Zio/IFileSystem.cs index 7210000..1056c0d 100644 --- a/src/Zio/IFileSystem.cs +++ b/src/Zio/IFileSystem.cs @@ -1,5 +1,5 @@ // Copyright (c) Alexandre Mutel. All rights reserved. -// This file is licensed under the BSD-Clause 2 license. +// This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. using System.IO; @@ -36,7 +36,7 @@ public interface IFileSystem : IDisposable void MoveDirectory(UPath srcPath, UPath destPath); /// - /// Deletes the specified directory and, if indicated, any subdirectories and files in the directory. + /// Deletes the specified directory and, if indicated, any subdirectories and files in the directory. /// /// The path of the directory to remove. /// true to remove directories, subdirectories, and files in path; otherwise, false. @@ -74,9 +74,9 @@ public interface IFileSystem : IDisposable /// Determines whether the specified file exists. /// /// The path. - /// true if the caller has the required permissions and path contains the name of an existing file; - /// otherwise, false. This method also returns false if path is null, an invalid path, or a zero-length string. - /// If the caller does not have sufficient permissions to read the specified file, + /// true if the caller has the required permissions and path contains the name of an existing file; + /// otherwise, false. This method also returns false if path is null, an invalid path, or a zero-length string. + /// If the caller does not have sufficient permissions to read the specified file, /// no exception is thrown and the method returns false regardless of the existence of path. bool FileExists(UPath path); @@ -88,7 +88,7 @@ public interface IFileSystem : IDisposable void MoveFile(UPath srcPath, UPath destPath); /// - /// Deletes the specified file. + /// Deletes the specified file. /// /// The path of the file to be deleted. void DeleteFile(UPath path); @@ -224,7 +224,7 @@ public interface IFileSystem : IDisposable // ---------------------------------------------- /// - /// Converts the specified path to the underlying path used by this . In case of a , it + /// Converts the specified path to the underlying path used by this . In case of a , it /// would represent the actual path on the disk. /// /// The path. @@ -237,10 +237,21 @@ public interface IFileSystem : IDisposable /// The system path. /// The converted path according to the system path. UPath ConvertPathFromInternal(string systemPath); + + /// + /// Resolves the specified path to a path in the underlying file system, if one exists. For instance, a would + /// resolve the path to a qualified path in the underlying file system. + /// + /// The path to resolve. + /// + /// A tuple of the resolved path and file system. If there is no underlying file + /// system, the returned path is the same, and the file system is the same. + /// + (IFileSystem FileSystem, UPath Path) ResolvePath(UPath path); } /// -/// Used by . +/// Used by . /// /// The file system item to filer. /// true if the item should be kept; otherwise false. diff --git a/src/Zio/Zio.csproj b/src/Zio/Zio.csproj index 12a8576..a7d52a3 100644 --- a/src/Zio/Zio.csproj +++ b/src/Zio/Zio.csproj @@ -30,13 +30,16 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + 4.3.0 + + 4.5.0 + - + 4.3.0 @@ -45,7 +48,7 @@ 4.3.0 - + 4.3.0 @@ -54,7 +57,7 @@ 4.3.0 - + $(AdditionalConstants);NETSTANDARD;HAS_ZIPARCHIVE @@ -72,7 +75,7 @@ true $(AdditionalConstants);NETSTANDARD;HAS_ZIPARCHIVE;HAS_NULLABLEANNOTATIONS - + $(AdditionalConstants);HAS_ZIPARCHIVE