Files
VirtualFS/VirtualFS/Compressed/ZipReadFileSystem.cs
2024-05-15 09:46:01 +02:00

235 lines
8.9 KiB
C#

/*
* VirtualFS - Virtual File System library.
* Copyright (c) 2013, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using ICSharpCode.SharpZipLib.Zip;
using VirtualFS.Base;
using VirtualFS.Implementation;
namespace VirtualFS.Compressed
{
/// <summary>Zip file based read only file system implementation.</summary>
public class ZipReadFileSystem : BaseFileSystem
{
/// <summary>Gets the Zip file.</summary>
public ZipFile File { get; private set; }
private Dictionary<Path, Entry> _pathToEntry = new Dictionary<Path, Entry>();
/// <summary>Gets the assembly on which file system operates.</summary>
public Assembly Assembly { get; private set; }
/// <summary>Initializes a new instance of the <see cref="ZipReadFileSystem"/> class.</summary>
/// <param name="stream">The stream with zip file.</param>
/// <param name="root">The root path.</param>
/// <param name="filter">The file filter.</param>
public ZipReadFileSystem(System.IO.Stream stream, Path root = null, Regex filter = null)
: base(true)
{
if (stream == null)
throw new ArgumentNullException("stream");
if (!stream.CanSeek)
throw new InvalidOperationException("Zip steram must be capable of seeking.");
File = new ZipFile(stream);
foreach (ZipEntry zipEntry in File)
{
if (filter == null || filter.IsMatch(zipEntry.Name))
{
if (zipEntry.IsDirectory)
{
var path = ZipEntryToPath(zipEntry, root, true);
if (path != null)
_pathToEntry.Add(path, new Directory
{
Data = zipEntry,
Path = path,
FileSystem = this,
IsReadOnly = true,
CreationTime = zipEntry.DateTime,
LastAccessTime = zipEntry.DateTime,
LastWriteTime = zipEntry.DateTime,
});
}
else if (zipEntry.IsFile)
{
var path = ZipEntryToPath(zipEntry, root);
if (path != null)
_pathToEntry.Add(path, new File
{
Data = zipEntry,
Path = path,
FileSystem = this,
IsReadOnly = true,
CreationTime = zipEntry.DateTime,
LastAccessTime = zipEntry.DateTime,
LastWriteTime = zipEntry.DateTime,
Size = zipEntry.Size,
});
}
}
}
// Ugly but it works (fix this - performance)
_pathToEntry
.Keys
.Where(p => !p.IsDirectory)
.ToList()
.ForEach(p =>
{
var x = p.Parent;
while (x != null)
{
Exists(x);
x = x.Parent;
}
});
}
private static Path ZipEntryToPath(ZipEntry zipEntry, Path root, bool dir = false)
{
var ret = zipEntry.Name.Replace('\\', Path.SeparatorChar);
if (ret[0] != Path.SeparatorChar)
ret = Path.SeparatorChar + ret;
if (dir && !ret.EndsWith(Path.SeparatorChar.ToString()))
ret += Path.SeparatorChar;
if (root != null)
{
if (root.IsParentOf(ret))
ret = ((Path)ret).RemoveParent(root);
else
return null;
}
return ret;
}
/// <summary>Check if given path exists.</summary>
/// <param name="path">Path to check.</param>
/// <returns>Returns <c>true</c> if entry does
/// exist, otherwise <c>false</c>.</returns>
public override bool Exists(Path path)
{
bool ret = path == Path.Root ||
(_pathToEntry.ContainsKey(path) &&
((_pathToEntry[path] is Directory) == path.IsDirectory));
if (!ret && _pathToEntry.Keys.Any(x => x.IsParentOf(path)))
{
_pathToEntry.Add(path, new Directory()
{
Data = null,
IsReadOnly = IsReadOnly,
Path = path,
FileSystem = this,
CreationTime = DateTime.Now,
LastAccessTime = DateTime.Now,
LastWriteTime = DateTime.Now,
});
ret = true;
}
return ret;
}
/// <summary>Get entry located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <returns>Returns entry information.</returns>
public override Entry GetEntry(Path path)
{
if (!Exists(path))
return null;
return _pathToEntry[path];
}
/// <summary>Get path to physical file containing entry.</summary>
/// <param name="path">Virtual file system path.</param>
/// <returns>Real file system path or <c>null</c> if real
/// path doesn't exist.</returns>
/// <remarks>This may not work with all file systems. Implementation
/// should return null if real path can't be determined.</remarks>
public override string EntryRealPath(Path path)
{
return null;
}
/// <summary>Get entries located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <param name="mask">Mask to filter out unwanted entries.</param>
/// <returns>Returns entry information.</returns>
public override IEnumerable<Entry> GetEntries(Path path, Regex mask = null)
{
if (!path.IsDirectory)
throw new InvalidOperationException(string.Format("Path '{0}' is not a directory, thus it has no entries."));
if (Exists(path))
return _pathToEntry
.Where(k => k.Key.Parent == path)
.Where(k => mask == null || mask.IsMatch(k.Key.Name))
.Select(v => v.Value);
return null;
}
/// <summary>Opens an existing file for reading.</summary>
/// <param name="path">The file to be opened for reading.</param>
/// <returns>A read-only <see cref="Stream" /> on the specified path.</returns>
public override System.IO.Stream FileOpenRead(Path path)
{
base.FileOpenRead(path);
return new VirtualStream(this, File.GetInputStream((ZipEntry)_pathToEntry[path].Data), path);
}
/// <summary>Performs application-defined tasks associated with
/// freeing, releasing, or resetting unmanaged resources.</summary>
public override void Dispose()
{
if (File != null)
{
// Makes close also shut the underlying stream
File.IsStreamOwner = true;
// Ensure we release resources
File.Close();
}
}
}
}