diff --git a/src/Zio.Tests/FileSystems/TestZipArchiveFileSystem.cs b/src/Zio.Tests/FileSystems/TestZipArchiveFileSystem.cs
index 7888cbf..57a10a0 100644
--- a/src/Zio.Tests/FileSystems/TestZipArchiveFileSystem.cs
+++ b/src/Zio.Tests/FileSystems/TestZipArchiveFileSystem.cs
@@ -228,4 +228,105 @@ public void TestOpenStreamsMultithreaded()
thread1.Join();
thread2.Join();
}
+
+
+ [Theory]
+ [InlineData("TestData/Linux.zip")]
+ [InlineData("TestData/Windows.zip")]
+ public void TestCaseInSensitiveZip(string path)
+ {
+ using var stream = File.OpenRead(path);
+ using var archive = new ZipArchive(stream, ZipArchiveMode.Read);
+ var fs = new ZipArchiveFileSystem(archive);
+
+ Assert.True(fs.DirectoryExists("/Folder"));
+ Assert.True(fs.DirectoryExists("/folder"));
+
+ Assert.False(fs.FileExists("/Folder"));
+ Assert.False(fs.FileExists("/folder"));
+
+ Assert.True(fs.FileExists("/Folder/File.txt"));
+ Assert.True(fs.FileExists("/folder/file.txt"));
+
+ Assert.False(fs.DirectoryExists("/Folder/file.txt"));
+ Assert.False(fs.DirectoryExists("/folder/File.txt"));
+ }
+
+ [Theory]
+ [InlineData("TestData/Linux.zip")]
+ [InlineData("TestData/Windows.zip")]
+ public void TestCaseSensitiveZip(string path)
+ {
+ using var stream = File.OpenRead(path);
+ using var archive = new ZipArchive(stream, ZipArchiveMode.Read);
+ var fs = new ZipArchiveFileSystem(archive, true);
+
+ Assert.True(fs.DirectoryExists("/Folder"));
+ Assert.False(fs.DirectoryExists("/folder"));
+
+ Assert.False(fs.FileExists("/Folder"));
+ Assert.False(fs.FileExists("/folder"));
+
+ Assert.True(fs.FileExists("/Folder/File.txt"));
+ Assert.False(fs.FileExists("/folder/file.txt"));
+
+ Assert.False(fs.DirectoryExists("/Folder/file.txt"));
+ Assert.False(fs.DirectoryExists("/folder/File.txt"));
+ }
+
+ [Fact]
+ public void TestSaveStream()
+ {
+ var stream = new MemoryStream();
+
+ using var fs = new ZipArchiveFileSystem(stream);
+
+ fs.WriteAllText("/a/b.txt", "abc");
+ fs.Save();
+
+ stream.Seek(0, SeekOrigin.Begin);
+
+ using (var fs2 = new ZipArchiveFileSystem(stream, ZipArchiveMode.Read, leaveOpen: true))
+ {
+ Assert.Equal("abc", fs2.ReadAllText("/a/b.txt"));
+ }
+
+ Assert.Equal("abc", fs.ReadAllText("/a/b.txt"));
+ fs.WriteAllText("/a/b.txt", "def");
+ fs.Save();
+
+ stream.Seek(0, SeekOrigin.Begin);
+
+ using (var fs2 = new ZipArchiveFileSystem(stream, ZipArchiveMode.Read, leaveOpen: true))
+ {
+ Assert.Equal("def", fs2.ReadAllText("/a/b.txt"));
+ }
+ }
+
+ [Fact]
+ public void TestSaveFile()
+ {
+ var path = Path.Combine(SystemPath, Guid.NewGuid().ToString("N") + ".zip");
+
+ try
+ {
+ using var fs = new ZipArchiveFileSystem(path);
+
+ Assert.Equal(0, new FileInfo(path).Length);
+
+ fs.WriteAllText("/a/b.txt", "abc");
+ fs.Save();
+
+ // We cannot check the content because the file is still open
+ Assert.NotEqual(0, new FileInfo(path).Length);
+
+ // Ensure we can save multiple times
+ fs.WriteAllText("/a/b.txt", "def");
+ fs.Save();
+ }
+ finally
+ {
+ File.Delete(path);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Zio.Tests/TestData/Linux.zip b/src/Zio.Tests/TestData/Linux.zip
new file mode 100644
index 0000000..bc85ddb
Binary files /dev/null and b/src/Zio.Tests/TestData/Linux.zip differ
diff --git a/src/Zio.Tests/TestData/Windows.zip b/src/Zio.Tests/TestData/Windows.zip
new file mode 100644
index 0000000..73db649
Binary files /dev/null and b/src/Zio.Tests/TestData/Windows.zip differ
diff --git a/src/Zio.Tests/Zio.Tests.csproj b/src/Zio.Tests/Zio.Tests.csproj
index 194790e..686b35f 100644
--- a/src/Zio.Tests/Zio.Tests.csproj
+++ b/src/Zio.Tests/Zio.Tests.csproj
@@ -20,4 +20,10 @@
+
+
+
+ Always
+
+
diff --git a/src/Zio/FileSystems/FileSystemWatcher.cs b/src/Zio/FileSystems/FileSystemWatcher.cs
index 08e5832..d12a272 100644
--- a/src/Zio/FileSystems/FileSystemWatcher.cs
+++ b/src/Zio/FileSystems/FileSystemWatcher.cs
@@ -223,7 +223,7 @@ protected void UnregisterEvents(IFileSystemWatcher watcher)
return pathFromEvent;
}
- private void OnChanged(object sender, FileChangedEventArgs args)
+ private void OnChanged(object? sender, FileChangedEventArgs args)
{
var newPath = TryConvertPath(args.FullPath);
if (!newPath.HasValue)
@@ -235,7 +235,7 @@ private void OnChanged(object sender, FileChangedEventArgs args)
RaiseChanged(newArgs);
}
- private void OnCreated(object sender, FileChangedEventArgs args)
+ private void OnCreated(object? sender, FileChangedEventArgs args)
{
var newPath = TryConvertPath(args.FullPath);
if (!newPath.HasValue)
@@ -247,7 +247,7 @@ private void OnCreated(object sender, FileChangedEventArgs args)
RaiseCreated(newArgs);
}
- private void OnDeleted(object sender, FileChangedEventArgs args)
+ private void OnDeleted(object? sender, FileChangedEventArgs args)
{
var newPath = TryConvertPath(args.FullPath);
if (!newPath.HasValue)
@@ -259,12 +259,12 @@ private void OnDeleted(object sender, FileChangedEventArgs args)
RaiseDeleted(newArgs);
}
- private void OnError(object sender, FileSystemErrorEventArgs args)
+ private void OnError(object? sender, FileSystemErrorEventArgs args)
{
RaiseError(args);
}
- private void OnRenamed(object sender, FileRenamedEventArgs args)
+ private void OnRenamed(object? sender, FileRenamedEventArgs args)
{
var newPath = TryConvertPath(args.FullPath);
if (!newPath.HasValue)
diff --git a/src/Zio/FileSystems/MemoryFileSystem.cs b/src/Zio/FileSystems/MemoryFileSystem.cs
index 8180c6c..83e96cc 100644
--- a/src/Zio/FileSystems/MemoryFileSystem.cs
+++ b/src/Zio/FileSystems/MemoryFileSystem.cs
@@ -189,7 +189,7 @@ protected override void DeleteDirectoryImpl(UPath path, bool isRecursive)
}
finally
{
- if (deleteRootDirectory)
+ if (deleteRootDirectory && result.Node != null)
{
result.Node.DetachFromParent();
result.Node.Dispose();
@@ -1008,7 +1008,7 @@ private void MoveFileOrDirectory(UPath srcPath, UPath destPath, bool expectDirec
var parentSrcPath = srcPath.GetDirectory();
var parentDestPath = destPath.GetDirectory();
- void AssertNoDestination(FileSystemNode node)
+ void AssertNoDestination(FileSystemNode? node)
{
if (expectDirectory)
{
@@ -1140,7 +1140,7 @@ private static void ValidateFile([NotNull] FileSystemNode? node, UPath srcPath)
}
}
- private FileSystemNode TryFindNodeSafe(UPath path)
+ private FileSystemNode? TryFindNodeSafe(UPath path)
{
EnterFileSystemShared();
try
@@ -1193,7 +1193,7 @@ private void CreateDirectoryNode(UPath path)
private readonly struct NodeResult
{
- public NodeResult(DirectoryNode? directory, FileSystemNode node, string? name, FindNodeFlags flags)
+ public NodeResult(DirectoryNode? directory, FileSystemNode? node, string? name, FindNodeFlags flags)
{
Directory = directory;
Node = node;
@@ -1203,7 +1203,7 @@ public NodeResult(DirectoryNode? directory, FileSystemNode node, string? name, F
public readonly DirectoryNode? Directory;
- public readonly FileSystemNode Node;
+ public readonly FileSystemNode? Node;
public readonly string? Name;
diff --git a/src/Zio/FileSystems/ZipArchiveFileSystem.cs b/src/Zio/FileSystems/ZipArchiveFileSystem.cs
index 356ceaf..f2477ed 100644
--- a/src/Zio/FileSystems/ZipArchiveFileSystem.cs
+++ b/src/Zio/FileSystems/ZipArchiveFileSystem.cs
@@ -18,27 +18,26 @@ public class ZipArchiveFileSystem : FileSystem
{
private readonly bool _isCaseSensitive;
- private readonly ZipArchive _archive;
+ private ZipArchive _archive;
+ private Dictionary _entries;
+
+ private readonly string? _path;
+ private readonly Stream? _stream;
+ private readonly bool _disposeStream;
+
private readonly CompressionLevel _compressionLevel;
private readonly ReaderWriterLockSlim _entriesLock = new();
private FileSystemEventDispatcher? _dispatcher;
private readonly object _dispatcherLock = new();
-
+
private readonly DateTime _creationTime;
- private readonly Dictionary _entries;
-
private readonly Dictionary _openStreams;
private readonly object _openStreamsLock = new();
-#if NETFRAMEWORK // .Net4.5 uses a backslash as directory separator
- private const char DirectorySeparator = '\\';
-#else
private const char DirectorySeparator = '/';
-#endif
-
///
/// Initializes a new instance of the class.
@@ -56,23 +55,10 @@ public ZipArchiveFileSystem(ZipArchive archive, bool isCaseSensitive = false, Co
{
throw new ArgumentNullException(nameof(archive));
}
-#if NETFRAMEWORK // .Net4.5 uses a backslash as directory separator
- foreach (var entry in _archive.Entries)
- {
- entry.FullName.Replace('/', DirectorySeparator);
- }
-#else
- foreach (var entry in _archive.Entries)
- {
- entry.FullName.Replace('\\', DirectorySeparator);
- }
-#endif
- if (!_isCaseSensitive)
- {
- _entries = _archive.Entries.ToDictionary(e => e.FullName.ToLowerInvariant(), e => e);
- }
_openStreams = new Dictionary();
+ _entries = null!; // Loaded below
+ LoadEntries();
}
///
@@ -83,8 +69,10 @@ public ZipArchiveFileSystem(ZipArchive archive, bool isCaseSensitive = false, Co
/// True to leave the stream open when is disposed
///
public ZipArchiveFileSystem(Stream stream, ZipArchiveMode mode = ZipArchiveMode.Update, bool leaveOpen = false, bool isCaseSensitive = false, CompressionLevel compressionLevel = CompressionLevel.NoCompression)
- : this(new ZipArchive(stream, mode, leaveOpen), isCaseSensitive, compressionLevel)
+ : this(new ZipArchive(stream, mode, leaveOpen: true), isCaseSensitive, compressionLevel)
{
+ _disposeStream = !leaveOpen;
+ _stream = stream;
}
///
@@ -97,6 +85,7 @@ public ZipArchiveFileSystem(Stream stream, ZipArchiveMode mode = ZipArchiveMode.
public ZipArchiveFileSystem(string path, ZipArchiveMode mode = ZipArchiveMode.Update, bool leaveOpen = false, bool isCaseSensitive = false, CompressionLevel compressionLevel = CompressionLevel.NoCompression)
: this(new ZipArchive(File.Open(path, FileMode.OpenOrCreate), mode, leaveOpen), isCaseSensitive, compressionLevel)
{
+ _path = path;
}
///
@@ -106,40 +95,78 @@ public ZipArchiveFileSystem(string path, ZipArchiveMode mode = ZipArchiveMode.Up
/// True to leave the stream open when is disposed
/// Specifies if entry names should be case sensitive
public ZipArchiveFileSystem(ZipArchiveMode mode = ZipArchiveMode.Update, bool leaveOpen = false, bool isCaseSensitive = false, CompressionLevel compressionLevel = CompressionLevel.NoCompression)
- : this(new ZipArchive(new MemoryStream(), mode, leaveOpen), isCaseSensitive, compressionLevel)
+ : this(new MemoryStream(), mode, leaveOpen, isCaseSensitive, compressionLevel)
{
}
- private ZipArchiveEntry? GetEntry(string path)
+ ///
+ /// Saves the archive to the original path or stream.
+ ///
+ /// Cannot save archive without a path or stream
+ public void Save()
{
-#if NETFRAMEWORK
- path = path.Replace('/', DirectorySeparator);
-#else
- path = path.Replace('\\', DirectorySeparator);
-#endif
- if (path == null)
+ var mode = _archive.Mode;
+
+ if (_path != null)
+ {
+ _archive.Dispose();
+ _archive = new ZipArchive(File.Open(_path, FileMode.OpenOrCreate), mode);
+ }
+ else if (_stream != null)
+ {
+ if (!_stream.CanSeek)
+ {
+ throw new InvalidOperationException("Cannot save archive to a stream that doesn't support seeking");
+ }
+
+ _archive.Dispose();
+ _stream.Seek(0, SeekOrigin.Begin);
+ _archive = new ZipArchive(_stream, mode, leaveOpen: true);
+ }
+ else
{
- throw new ArgumentNullException(nameof(path));
+ throw new InvalidOperationException("Cannot save archive without a path or stream");
}
- path = RemoveLeadingSlash(path);
+ LoadEntries();
+ }
+ private void LoadEntries()
+ {
+ var comparer = _isCaseSensitive ? UPathComparer.Ordinal : UPathComparer.OrdinalIgnoreCase;
+
+ _entries = _archive.Entries.ToDictionary(
+ e => new UPath(e.FullName).ToAbsolute(),
+ static e =>
+ {
+ var lastChar = e.FullName[e.FullName.Length - 1];
+ return new InternalZipEntry(e, lastChar is '/' or '\\');
+ },
+ comparer);
+ }
+
+ private ZipArchiveEntry? GetEntry(UPath path, out bool isDirectory)
+ {
_entriesLock.EnterReadLock();
try
{
- if (_isCaseSensitive)
+ if (_entries.TryGetValue(path, out var foundEntry))
{
- return _archive.GetEntry(path);
+ isDirectory = foundEntry.IsDirectory;
+ return foundEntry.Entry;
}
-
- return _entries.TryGetValue(path.ToLowerInvariant(), out var foundEntry) ? foundEntry : null;
}
finally
{
_entriesLock.ExitReadLock();
}
+
+ isDirectory = false;
+ return null;
}
+ private ZipArchiveEntry? GetEntry(UPath path) => GetEntry(path, out _);
+
///
protected override UPath ConvertPathFromInternalImpl(string innerPath)
{
@@ -160,14 +187,15 @@ protected override void CopyFileImpl(UPath srcPath, UPath destPath, bool overwri
throw new IOException("Source and destination path must be different.");
}
- var srcEntry = GetEntry(srcPath.FullName);
- if (srcEntry == null)
+ var srcEntry = GetEntry(srcPath, out var isDirectory);
+
+ if (isDirectory)
{
- if (DirectoryExistsImpl(srcPath))
- {
- throw new UnauthorizedAccessException(nameof(srcPath) + " is a directory.");
- }
+ throw new UnauthorizedAccessException(nameof(srcPath) + " is a directory.");
+ }
+ if (srcEntry == null)
+ {
if (!DirectoryExistsImpl(srcPath.GetDirectory()))
{
throw new DirectoryNotFoundException(srcPath.GetDirectory().FullName);
@@ -190,7 +218,7 @@ protected override void CopyFileImpl(UPath srcPath, UPath destPath, bool overwri
}
}
- var destEntry = GetEntry(destPath.FullName);
+ var destEntry = GetEntry(destPath);
if (destEntry != null)
{
#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
@@ -242,25 +270,18 @@ protected override void CreateDirectoryImpl(UPath path)
}
}
- var entryPath = RemoveLeadingSlash(path);
-#if NETFRAMEWORK
- entryPath = entryPath.Replace('/', DirectorySeparator);
-#else
- entryPath = entryPath.Replace('\\', DirectorySeparator);
-#endif
- CreateEntry(ConvertPathToDirectory(entryPath));
- TryGetDispatcher()?.RaiseCreated(entryPath);
+ CreateEntry(path, isDirectory: true);
+ TryGetDispatcher()?.RaiseCreated(path);
}
///
protected override void DeleteDirectoryImpl(UPath path, bool isRecursive)
{
- var entryPath = RemoveLeadingSlash(ConvertPathToDirectory(path));
-#if NETFRAMEWORK
- entryPath = entryPath.Replace('/', DirectorySeparator);
-#else
- entryPath = entryPath.Replace('\\', DirectorySeparator);
-#endif
+ if (FileExistsImpl(path))
+ {
+ throw new IOException(nameof(path) + " is a file.");
+ }
+
var entries = new List();
if (!isRecursive)
{
@@ -268,7 +289,11 @@ protected override void DeleteDirectoryImpl(UPath path, bool isRecursive)
_entriesLock.EnterReadLock();
try
{
- entries = _archive.Entries.Where(x => x.FullName.StartsWith(entryPath, _isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)).Take(2).ToList();
+ entries = _entries
+ .Where(x => x.Key.FullName.StartsWith(path.FullName, _isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase))
+ .Take(2)
+ .Select(x => x.Value.Entry)
+ .ToList();
}
finally
{
@@ -277,11 +302,6 @@ protected override void DeleteDirectoryImpl(UPath path, bool isRecursive)
if (entries.Count == 0)
{
- if (FileExistsImpl(path))
- {
- throw new IOException($"{path} is a file");
- }
-
throw FileSystemExceptionHelper.NewDirectoryNotFoundException(path);
}
@@ -302,15 +322,13 @@ protected override void DeleteDirectoryImpl(UPath path, bool isRecursive)
_entriesLock.EnterReadLock();
try
{
- entries = _archive.Entries.Where(x => x.FullName.StartsWith(entryPath, _isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)).ToList();
+ entries = _entries
+ .Where(x => x.Key.FullName.StartsWith(path.FullName, _isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase))
+ .Select(x => x.Value.Entry)
+ .ToList();
+
if (entries.Count == 0)
{
- entryPath = entryPath.Substring(0, entryPath.Length - 1);
- if (_isCaseSensitive ? _archive.GetEntry(entryPath) != null : _entries.ContainsKey(entryPath.ToLowerInvariant()))
- {
- throw new IOException($"{path} is a file");
- }
-
throw FileSystemExceptionHelper.NewDirectoryNotFoundException(path);
}
@@ -331,7 +349,7 @@ protected override void DeleteDirectoryImpl(UPath path, bool isRecursive)
{
if ((entry.ExternalAttributes & (int)FileAttributes.ReadOnly) == (int)FileAttributes.ReadOnly)
{
- throw entry.FullName == entryPath
+ throw entry.FullName.Length == path.FullName.Length + 1
? new IOException("Directory is read only")
: new UnauthorizedAccessException($"Cannot delete directory that contains readonly entry {entry.FullName}");
}
@@ -348,12 +366,8 @@ protected override void DeleteDirectoryImpl(UPath path, bool isRecursive)
{
foreach (var entry in entries)
{
+ _entries.Remove(new UPath(entry.FullName).ToAbsolute());
entry.Delete();
-
- if (!_isCaseSensitive)
- {
- _entries.Remove(entry.FullName.ToLowerInvariant());
- }
}
}
finally
@@ -372,7 +386,7 @@ protected override void DeleteFileImpl(UPath path)
throw new IOException("Cannot delete a directory");
}
- var entry = GetEntry(path.FullName);
+ var entry = GetEntry(path);
if (entry == null)
{
return;
@@ -396,7 +410,16 @@ protected override bool DirectoryExistsImpl(UPath path)
return true;
}
- return GetEntry(ConvertPathToDirectory(path.FullName)) != null;
+ _entriesLock.EnterReadLock();
+
+ try
+ {
+ return _entries.TryGetValue(path, out var entry) && entry.IsDirectory;
+ }
+ finally
+ {
+ _entriesLock.ExitReadLock();
+ }
}
///
@@ -404,6 +427,11 @@ protected override void Dispose(bool disposing)
{
_archive.Dispose();
+ if (_stream != null && _disposeStream)
+ {
+ _stream.Dispose();
+ }
+
if (disposing)
{
TryGetDispatcher()?.Dispose();
@@ -425,20 +453,21 @@ protected override IEnumerable EnumeratePathsImpl(UPath path, string sear
private IEnumerable EnumeratePathsStr(UPath path, string searchPattern, SearchOption searchOption, SearchTarget searchTarget)
{
var search = SearchPattern.Parse(ref path, ref searchPattern);
- var entryPath = RemoveLeadingSlash(path);
- var root = ConvertPathToDirectory(entryPath);
-#if NETFRAMEWORK
- root = root.Replace('/', '\\');
-#else
- root = root.Replace('\\', '/');
-#endif
+
_entriesLock.EnterReadLock();
var entriesList = new List();
try
{
- entriesList = root == ""
- ? _archive.Entries.ToList()
- : _archive.Entries.Where(e => e.FullName.StartsWith(root, _isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) && e.FullName.Length > root.Length).ToList();
+ var internEntries = path == UPath.Root
+ ? _entries
+ : _entries.Where(kv => kv.Key.FullName.StartsWith(path.FullName, _isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) && kv.Key.FullName.Length > path.FullName.Length);
+
+ if (searchOption == SearchOption.TopDirectoryOnly)
+ {
+ internEntries = internEntries.Where(kv => kv.Key.IsInDirectory(path, false));
+ }
+
+ entriesList = internEntries.Select(kv => kv.Value.Entry).ToList();
}
finally
{
@@ -451,11 +480,6 @@ private IEnumerable EnumeratePathsStr(UPath path, string searchPattern,
}
var entries = (IEnumerable)entriesList;
- if (searchOption == SearchOption.TopDirectoryOnly)
- {
- var dir = GetParent(entries.First().FullName);
- entries = entries.Where(e => string.Equals(ConvertPathToDirectory(GetParent(e.FullName)), root, _isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
- }
if (searchTarget == SearchTarget.File)
{
@@ -477,13 +501,22 @@ private IEnumerable EnumeratePathsStr(UPath path, string searchPattern,
///
protected override bool FileExistsImpl(UPath path)
{
- return GetEntry(path.FullName) != null;
+ _entriesLock.EnterReadLock();
+
+ try
+ {
+ return _entries.TryGetValue(path, out var entry) && !entry.IsDirectory;
+ }
+ finally
+ {
+ _entriesLock.ExitReadLock();
+ }
}
///
protected override FileAttributes GetAttributesImpl(UPath path)
{
- var entry = GetEntry(path.FullName) ?? GetEntry(ConvertPathToDirectory(path));
+ var entry = GetEntry(path);
if (entry is null)
{
throw FileSystemExceptionHelper.NewFileNotFoundException(path);
@@ -501,16 +534,18 @@ protected override FileAttributes GetAttributesImpl(UPath path)
}
return externalAttributes | attributes;
-#endif
+#else
// return standard attributes if it's not NetStandard2.1
return attributes == FileAttributes.Directory ? FileAttributes.Directory : entry.LastWriteTime >= _creationTime ? FileAttributes.Archive : FileAttributes.Normal;
+#endif
}
///
protected override long GetFileLengthImpl(UPath path)
{
- var entry = GetEntry(path.FullName);
- if (entry == null)
+ var entry = GetEntry(path, out var isDirectory);
+
+ if (entry == null || isDirectory)
{
throw FileSystemExceptionHelper.NewFileNotFoundException(path);
}
@@ -546,7 +581,7 @@ protected override DateTime GetLastAccessTimeImpl(UPath path)
///
protected override DateTime GetLastWriteTimeImpl(UPath path)
{
- var entry = GetEntry(path.FullName) ?? GetEntry(ConvertPathToDirectory(path));
+ var entry = GetEntry(path);
if (entry == null)
{
return DefaultFileTime;
@@ -563,15 +598,13 @@ protected override void MoveDirectoryImpl(UPath srcPath, UPath destPath)
throw new IOException("Cannot move directory to itself or a subdirectory.");
}
- var srcDir = RemoveLeadingSlash(ConvertPathToDirectory(srcPath.FullName));
- var destDir = RemoveLeadingSlash(ConvertPathToDirectory(destPath.FullName));
-#if NETFRAMEWORK
- srcDir = srcDir.Replace('/', DirectorySeparator);
- destDir = destDir.Replace('/', DirectorySeparator);
-#else
- srcDir = srcDir.Replace('\\', DirectorySeparator);
- destDir = destDir.Replace('\\', DirectorySeparator);
-#endif
+ if (FileExistsImpl(srcPath))
+ {
+ throw new IOException(nameof(srcPath) + " is a file.");
+ }
+
+ var srcDir = srcPath.FullName;
+
_entriesLock.EnterReadLock();
var entries = Array.Empty();
try
@@ -585,11 +618,6 @@ protected override void MoveDirectoryImpl(UPath srcPath, UPath destPath)
if (entries.Length == 0)
{
- if (FileExistsImpl(srcPath))
- {
- throw new IOException(nameof(srcPath) + " is a file.");
- }
-
throw FileSystemExceptionHelper.NewDirectoryNotFoundException(srcPath);
}
@@ -605,7 +633,7 @@ protected override void MoveDirectoryImpl(UPath srcPath, UPath destPath)
using (var entryStream = entry.Open())
{
var entryName = entry.FullName.Substring(srcDir.Length);
- var destEntry = CreateEntry(destDir + entryName);
+ var destEntry = CreateEntry(destPath + entryName, isDirectory: true);
using (var destEntryStream = destEntry.Open())
{
entryStream.CopyTo(destEntryStream);
@@ -621,14 +649,14 @@ protected override void MoveDirectoryImpl(UPath srcPath, UPath destPath)
///
protected override void MoveFileImpl(UPath srcPath, UPath destPath)
{
- var srcEntry = GetEntry(srcPath.FullName) ?? throw FileSystemExceptionHelper.NewFileNotFoundException(srcPath);
+ var srcEntry = GetEntry(srcPath) ?? throw FileSystemExceptionHelper.NewFileNotFoundException(srcPath);
if (!DirectoryExistsImpl(destPath.GetDirectory()))
{
throw FileSystemExceptionHelper.NewDirectoryNotFoundException(destPath.GetDirectory());
}
- var destEntry = GetEntry(destPath.FullName);
+ var destEntry = GetEntry(destPath);
if (destEntry != null)
{
throw new IOException("Cannot overwrite existing file.");
@@ -659,7 +687,12 @@ protected override Stream OpenFileImpl(UPath path, FileMode mode, FileAccess acc
throw new ArgumentException("Cannot write in a read-only access.");
}
- var entry = GetEntry(path.FullName);
+ var entry = GetEntry(path, out var isDirectory);
+
+ if (isDirectory)
+ {
+ throw new UnauthorizedAccessException(nameof(path) + " is a directory.");
+ }
if (entry == null)
{
@@ -673,11 +706,6 @@ protected override Stream OpenFileImpl(UPath path, FileMode mode, FileAccess acc
}
else
{
- if (DirectoryExistsImpl(path))
- {
- throw new UnauthorizedAccessException(nameof(path) + " is a directory.");
- }
-
if (!DirectoryExistsImpl(path.GetDirectory()))
{
throw FileSystemExceptionHelper.NewDirectoryNotFoundException(path.GetDirectory());
@@ -727,13 +755,13 @@ protected override Stream OpenFileImpl(UPath path, FileMode mode, FileAccess acc
///
protected override void ReplaceFileImpl(UPath srcPath, UPath destPath, UPath destBackupPath, bool ignoreMetadataErrors)
{
- var sourceEntry = GetEntry(srcPath.FullName);
+ var sourceEntry = GetEntry(srcPath);
if (sourceEntry is null)
{
throw FileSystemExceptionHelper.NewFileNotFoundException(srcPath);
}
- var destEntry = GetEntry(destPath.FullName);
+ var destEntry = GetEntry(destPath);
if (destEntry == sourceEntry)
{
throw new IOException("Cannot replace the file with itself.");
@@ -742,7 +770,7 @@ protected override void ReplaceFileImpl(UPath srcPath, UPath destPath, UPath des
if (destEntry != null)
{
// create a backup at destBackupPath if its not null
- if (destBackupPath != null)
+ if (!destBackupPath.IsEmpty)
{
var destBackupEntry = CreateEntry(destBackupPath.FullName);
using var destBackupStream = destBackupEntry.Open();
@@ -777,8 +805,7 @@ protected override void ReplaceFileImpl(UPath srcPath, UPath destPath, UPath des
protected override void SetAttributesImpl(UPath path, FileAttributes attributes)
{
#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
- var entryPath = RemoveLeadingSlash(path);
- var entry = GetEntry(entryPath) ?? GetEntry(ConvertPathToDirectory(entryPath));
+ var entry = GetEntry(path);
if (entry == null)
{
throw FileSystemExceptionHelper.NewFileNotFoundException(path);
@@ -786,9 +813,9 @@ protected override void SetAttributesImpl(UPath path, FileAttributes attributes)
entry.ExternalAttributes = (int)attributes;
TryGetDispatcher()?.RaiseChange(path);
- return;
-#endif
+#else
Debug.WriteLine("SetAttributes don't work in NetStandard2.0 or older.");
+#endif
}
///
@@ -810,7 +837,7 @@ protected override void SetLastAccessTimeImpl(UPath path, DateTime time)
///
protected override void SetLastWriteTimeImpl(UPath path, DateTime time)
{
- var entry = GetEntry(path.FullName) ?? GetEntry(ConvertPathToDirectory(path.FullName));
+ var entry = GetEntry(path);
if (entry is null)
{
throw FileSystemExceptionHelper.NewFileNotFoundException(path);
@@ -852,11 +879,7 @@ private void RemoveEntry(ZipArchiveEntry entry)
try
{
entry.Delete();
-
- if (!_isCaseSensitive)
- {
- _entries.Remove(entry.FullName.ToLowerInvariant());
- }
+ _entries.Remove(new UPath(entry.FullName).ToAbsolute());
}
finally
{
@@ -864,24 +887,20 @@ private void RemoveEntry(ZipArchiveEntry entry)
}
}
- private ZipArchiveEntry CreateEntry(string path)
+ private ZipArchiveEntry CreateEntry(UPath path, bool isDirectory = false)
{
- path = RemoveLeadingSlash(path);
-#if NETFRAMEWORK
- path = path.Replace('/', DirectorySeparator);
-#else
- path = path.Replace('\\', DirectorySeparator);
-#endif
_entriesLock.EnterWriteLock();
try
{
- var entry = _archive.CreateEntry(path, _compressionLevel);
+ var internalPath = path.FullName;
- if (!_isCaseSensitive)
+ if (isDirectory)
{
- _entries.Add(entry.FullName.ToLowerInvariant(), entry);
+ internalPath += DirectorySeparator;
}
+ var entry = _archive.CreateEntry(internalPath, _compressionLevel);
+ _entries[path] = new InternalZipEntry(entry, isDirectory);
return entry;
}
finally
@@ -906,31 +925,6 @@ private static string GetParent(string path)
return lastIndex == -1 ? "" : path.Substring(0, lastIndex);
}
- private static string ConvertPathToDirectory(UPath path)
- {
- return ConvertPathToDirectory(path.FullName);
- }
-
- private static string ConvertPathToDirectory(string path)
- {
- if (string.IsNullOrEmpty(path))
- {
- return path;
- }
-
- return path[path.Length - 1] is DirectorySeparator ? path : path + DirectorySeparator;
- }
-
- private static string RemoveLeadingSlash(UPath path)
- {
- return path.FullName[0] is '\\' or '/' ? path.FullName.Substring(1) : path.FullName;
- }
-
- private static string RemoveLeadingSlash(string path)
- {
- return path[0] is '\\' or '/' ? path.Substring(1) : path;
- }
-
private FileSystemEventDispatcher? TryGetDispatcher()
{
lock (_dispatcherLock)
@@ -1031,7 +1025,10 @@ public override void Close()
_isDisposed = true;
lock (_fileSystem._openStreamsLock)
{
- _fileSystem._openStreams.TryGetValue(_entry, out var fileData);
+ if (!_fileSystem._openStreams.TryGetValue(_entry, out var fileData))
+ {
+ return;
+ }
fileData.Count--;
if (fileData.Count == 0)
{
@@ -1054,5 +1051,17 @@ public EntryState(FileShare share)
public int Count;
}
+
+ private readonly struct InternalZipEntry
+ {
+ public InternalZipEntry(ZipArchiveEntry entry, bool isDirectory)
+ {
+ Entry = entry;
+ IsDirectory = isDirectory;
+ }
+
+ public readonly ZipArchiveEntry Entry;
+ public readonly bool IsDirectory;
+ }
}
#endif
\ No newline at end of file
diff --git a/src/Zio/UPath.cs b/src/Zio/UPath.cs
index f80d9a5..3bf7ad1 100644
--- a/src/Zio/UPath.cs
+++ b/src/Zio/UPath.cs
@@ -36,12 +36,12 @@ namespace Zio;
///
/// The default comparer for a that is case sensitive.
///
- public static readonly IComparer DefaultComparer = new ComparerCaseSensitive();
+ public static readonly IComparer DefaultComparer = UPathComparer.Ordinal;
///
/// The default comparer for a that is case insensitive.
///
- public static readonly IComparer DefaultComparerIgnoreCase = new ComparerIgnoreCase();
+ public static readonly IComparer DefaultComparerIgnoreCase = UPathComparer.OrdinalIgnoreCase;
///
/// Initializes a new instance of the struct.
@@ -472,20 +472,4 @@ public int CompareTo(UPath other)
{
return string.Compare(FullName, other.FullName, StringComparison.Ordinal);
}
-
- private class ComparerCaseSensitive : IComparer
- {
- public int Compare(UPath x, UPath y)
- {
- return string.Compare(x.FullName, y.FullName, StringComparison.Ordinal);
- }
- }
-
- private class ComparerIgnoreCase : IComparer
- {
- public int Compare(UPath x, UPath y)
- {
- return string.Compare(x.FullName, y.FullName, StringComparison.OrdinalIgnoreCase);
- }
- }
}
\ No newline at end of file
diff --git a/src/Zio/UPathComparer.cs b/src/Zio/UPathComparer.cs
new file mode 100644
index 0000000..9bfdff3
--- /dev/null
+++ b/src/Zio/UPathComparer.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Alexandre Mutel. All rights reserved.
+// This file is licensed under the BSD-Clause 2 license.
+// See the license.txt file in the project root for more information.
+
+namespace Zio;
+
+public class UPathComparer : IComparer, IEqualityComparer
+{
+ public static readonly UPathComparer Ordinal = new(StringComparer.Ordinal);
+ public static readonly UPathComparer OrdinalIgnoreCase = new(StringComparer.OrdinalIgnoreCase);
+ public static readonly UPathComparer CurrentCulture = new(StringComparer.CurrentCulture);
+ public static readonly UPathComparer CurrentCultureIgnoreCase = new(StringComparer.CurrentCultureIgnoreCase);
+
+ private readonly StringComparer _comparer;
+
+ private UPathComparer(StringComparer comparer)
+ {
+ _comparer = comparer;
+ }
+
+ public int Compare(UPath x, UPath y)
+ {
+ return _comparer.Compare(x.FullName, y.FullName);
+ }
+
+ public bool Equals(UPath x, UPath y)
+ {
+ return _comparer.Equals(x.FullName, y.FullName);
+ }
+
+ public int GetHashCode(UPath obj)
+ {
+ return _comparer.GetHashCode(obj.FullName);
+ }
+}
diff --git a/src/Zio/Zio.csproj b/src/Zio/Zio.csproj
index 3c78bda..12a8576 100644
--- a/src/Zio/Zio.csproj
+++ b/src/Zio/Zio.csproj
@@ -68,6 +68,8 @@
+ true
+ true
$(AdditionalConstants);NETSTANDARD;HAS_ZIPARCHIVE;HAS_NULLABLEANNOTATIONS