235 lines
8.9 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|
|
} |