Files
VirtualFS/VirtualFS/EmbeddedResource/EmbeddedResourceFileSystem.cs

223 lines
8.8 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 VirtualFS.Base;
using VirtualFS.Implementation;
namespace VirtualFS.EmbeddedResource
{
/// <summary>Embedded resource file system implementation.</summary>
public class EmbeddedResourceFileSystem : BaseFileSystem
{
private static object _lock = new object();
private Dictionary<Path, Entry> _pathToResource;
/// <summary>Gets the assembly on which file system operates.</summary>
public Assembly Assembly { get; private set; }
private string AssemblyLocation
{
get { return string.IsNullOrEmpty(Assembly.Location) ? null : Assembly.Location; }
}
private DateTime AssemblyTimestamp(Func<System.IO.FileInfo, DateTime> selector)
{
return string.IsNullOrEmpty(AssemblyLocation) ? DateTime.UtcNow : selector(new System.IO.FileInfo(AssemblyLocation));
}
/// <summary>Initializes a new instance of the <see cref="EmbeddedResourceFileSystem"/> class.</summary>
/// <param name="assembly">The assembly.</param>
/// <param name="rootNamespace">The root namespace.</param>
/// <param name="filter">The filter.</param>
public EmbeddedResourceFileSystem(Assembly assembly, string rootNamespace = null, Regex filter = null)
: base(true)
{
Assembly = assembly;
_pathToResource = Assembly.GetManifestResourceNames()
.Where(x => (string.IsNullOrEmpty(rootNamespace) || x.StartsWith(rootNamespace)) && (filter == null || filter.IsMatch(x)))
.Select(x => new File()
{
Data = x,
IsReadOnly = IsReadOnly,
Path = ResourceToPath(rootNamespace, x),
FileSystem = this,
CreationTime = AssemblyTimestamp(fi => fi.CreationTimeUtc),
LastAccessTime = AssemblyTimestamp(fi => fi.LastAccessTimeUtc),
LastWriteTime = AssemblyTimestamp(fi => fi.LastWriteTimeUtc),
Size = 0,
})
.ToDictionary(
k => k.Path,
v => (Entry)v);
_pathToResource
.Select(p => p.Key)
.Where(p => ((string)p).LastIndexOf(Path.SeparatorChar) > 0)
.Select(p => (Path)((string)p).Substring(0, ((string)p).LastIndexOf(Path.SeparatorChar) + 1))
.Distinct()
.ToList()
.ForEach(p => _pathToResource.Add(p, new Directory()
{
Data = null,
IsReadOnly = IsReadOnly,
Path = p,
FileSystem = this,
CreationTime = AssemblyTimestamp(fi => fi.CreationTimeUtc),
LastAccessTime = AssemblyTimestamp(fi => fi.LastAccessTimeUtc),
LastWriteTime = AssemblyTimestamp(fi => fi.LastWriteTimeUtc),
}));
// Ugly but it works (fix this - performance)
_pathToResource
.Keys
.Where(p => !p.IsDirectory)
.ToList()
.ForEach(p =>
{
var x = p.Parent;
while (x != null)
{
Exists(x);
x = x.Parent;
}
});
}
private static Path ResourceToPath(string rootNamespace, string resName)
{
string ret = resName;
int extDot = 0;
if (!string.IsNullOrEmpty(rootNamespace))
ret = ret.Substring(rootNamespace.Length).TrimStart('.');
extDot = ret.LastIndexOf('.');
ret = ret.Replace('.', Path.SeparatorChar);
if (extDot >= 0)
{
ret = ret
.Remove(extDot, 1)
.Insert(extDot, ".");
}
return (Path)ret.Insert(0, Path.SeparatorChar.ToString());
}
/// <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 = false;
lock (_lock)
{
ret = path == Path.Root ||
(_pathToResource.ContainsKey(path) &&
((_pathToResource[path] is Directory) == path.IsDirectory));
if (!ret && path.IsDirectory && _pathToResource.Keys.Any(x => x.IsParentOf(path)))
{
_pathToResource.Add(path, new Directory()
{
Data = null,
IsReadOnly = IsReadOnly,
Path = path,
FileSystem = this,
CreationTime = AssemblyTimestamp(fi => fi.CreationTimeUtc),
LastAccessTime = AssemblyTimestamp(fi => fi.LastAccessTimeUtc),
LastWriteTime = AssemblyTimestamp(fi => fi.LastWriteTimeUtc),
});
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 _pathToResource[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 AssemblyLocation;
}
/// <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 _pathToResource
.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, Assembly.GetManifestResourceStream(_pathToResource[path].Data.ToString()), path);
}
}
}