Files
RobustToolbox/Robust.Shared/ContentPack/ResourceManager.cs
2020-12-12 11:12:37 +01:00

323 lines
9.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.Interfaces.Resources;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
namespace Robust.Shared.ContentPack
{
/// <summary>
/// Virtual file system for all disk resources.
/// </summary>
internal partial class ResourceManager : IResourceManagerInternal
{
[Dependency] private readonly IConfigurationManager _config = default!;
private readonly ReaderWriterLockSlim _contentRootsLock = new(LockRecursionPolicy.SupportsRecursion);
private readonly List<(ResourcePath prefix, IContentRoot root)> _contentRoots =
new();
// Special file names on Windows like serial ports.
private static readonly Regex BadPathSegmentRegex =
new("^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$", RegexOptions.IgnoreCase);
// Literally not characters that can't go into filenames on Windows.
private static readonly Regex BadPathCharacterRegex =
new("[<>:\"|?*\0\\x01-\\x1f]", RegexOptions.IgnoreCase);
/// <inheritdoc />
public IWritableDirProvider UserData { get; private set; } = default!;
/// <inheritdoc />
public void Initialize(string? userData)
{
if (userData != null)
{
UserData = new WritableDirProvider(Directory.CreateDirectory(userData));
}
else
{
UserData = new VirtualWritableDirProvider();
}
}
/// <inheritdoc />
public void MountDefaultContentPack()
{
//Assert server only
var zipPath = _config.GetCVar<string>("resource.pack");
// no pack in config
if (string.IsNullOrWhiteSpace(zipPath))
{
Logger.WarningS("res", "No default ContentPack to load in configuration.");
return;
}
MountContentPack(zipPath);
}
/// <inheritdoc />
public void MountContentPack(string pack, ResourcePath? prefix = null)
{
prefix = SanitizePrefix(prefix);
if (!Path.IsPathRooted(pack))
pack = PathHelpers.ExecutableRelativeFile(pack);
var packInfo = new FileInfo(pack);
if (!packInfo.Exists)
{
throw new FileNotFoundException("Specified ContentPack does not exist: " + packInfo.FullName);
}
//create new PackLoader
var loader = new PackLoader(packInfo);
AddRoot(prefix, loader);
}
public void MountContentPack(Stream zipStream, ResourcePath? prefix = null)
{
prefix = SanitizePrefix(prefix);
var loader = new PackLoader(zipStream);
AddRoot(prefix, loader);
}
protected void AddRoot(ResourcePath prefix, IContentRoot loader)
{
loader.Mount();
_contentRootsLock.EnterWriteLock();
try
{
_contentRoots.Add((prefix, loader));
}
finally
{
_contentRootsLock.ExitWriteLock();
}
}
private static ResourcePath SanitizePrefix(ResourcePath? prefix)
{
if (prefix == null)
{
prefix = ResourcePath.Root;
}
else if (!prefix.IsRooted)
{
throw new ArgumentException("Prefix must be rooted.", nameof(prefix));
}
return prefix;
}
/// <inheritdoc />
public void MountContentDirectory(string path, ResourcePath? prefix = null)
{
prefix = SanitizePrefix(prefix);
if (!Path.IsPathRooted(path))
path = PathHelpers.ExecutableRelativeFile(path);
var pathInfo = new DirectoryInfo(path);
if (!pathInfo.Exists)
{
throw new DirectoryNotFoundException("Specified directory does not exist: " + pathInfo.FullName);
}
var loader = new DirLoader(pathInfo, Logger.GetSawmill("res"));
AddRoot(prefix, loader);
}
/// <inheritdoc />
public Stream ContentFileRead(string path)
{
return ContentFileRead(new ResourcePath(path));
}
/// <inheritdoc />
public Stream ContentFileRead(ResourcePath path)
{
if (TryContentFileRead(path, out var fileStream))
{
return fileStream;
}
throw new FileNotFoundException($"Path does not exist in the VFS: '{path}'");
}
/// <inheritdoc />
public bool TryContentFileRead(string path, [NotNullWhen(true)] out Stream? fileStream)
{
return TryContentFileRead(new ResourcePath(path), out fileStream);
}
/// <inheritdoc />
public bool TryContentFileRead(ResourcePath path, [NotNullWhen(true)] out Stream? fileStream)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
if (!path.IsRooted)
{
throw new ArgumentException("Path must be rooted", nameof(path));
}
#if DEBUG
if (!IsPathValid(path))
{
throw new FileNotFoundException($"Path '{path}' contains invalid characters/filenames.");
}
#endif
_contentRootsLock.EnterReadLock();
try
{
foreach (var (prefix, root) in _contentRoots)
{
if (!path.TryRelativeTo(prefix, out var relative))
{
continue;
}
if (root.TryGetFile(relative, out fileStream))
{
return true;
}
}
fileStream = null;
return false;
}
finally
{
_contentRootsLock.ExitReadLock();
}
}
/// <inheritdoc />
public bool ContentFileExists(string path)
{
return ContentFileExists(new ResourcePath(path));
}
/// <inheritdoc />
public bool ContentFileExists(ResourcePath path)
{
return TryContentFileRead(path, out var _);
}
/// <inheritdoc />
public IEnumerable<ResourcePath> ContentFindFiles(string path)
{
return ContentFindFiles(new ResourcePath(path));
}
/// <inheritdoc />
public IEnumerable<ResourcePath> ContentFindFiles(ResourcePath path)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
if (!path.IsRooted)
{
throw new ArgumentException("Path is not rooted", nameof(path));
}
var alreadyReturnedFiles = new HashSet<ResourcePath>();
_contentRootsLock.EnterReadLock();
try
{
foreach (var (prefix, root) in _contentRoots)
{
if (!path.TryRelativeTo(prefix, out var relative))
{
continue;
}
foreach (var filename in root.FindFiles(relative))
{
var newPath = prefix / filename;
if (!alreadyReturnedFiles.Contains(newPath))
{
alreadyReturnedFiles.Add(newPath);
yield return newPath;
}
}
}
}
finally
{
_contentRootsLock.ExitReadLock();
}
}
public bool TryGetDiskFilePath(ResourcePath path, [NotNullWhen(true)] out string? diskPath)
{
// loop over each root trying to get the file
_contentRootsLock.EnterReadLock();
try
{
foreach (var (prefix, root) in _contentRoots)
{
if (root is not DirLoader dirLoader || !path.TryRelativeTo(prefix, out var tempPath))
{
continue;
}
diskPath = dirLoader.GetPath(tempPath);
if (File.Exists(diskPath))
return true;
}
diskPath = null;
return false;
}
finally
{
_contentRootsLock.ExitReadLock();
}
}
public void MountStreamAt(MemoryStream stream, ResourcePath path)
{
var loader = new SingleStreamLoader(stream, path.ToRelativePath());
AddRoot(ResourcePath.Root, loader);
}
internal static bool IsPathValid(ResourcePath path)
{
var asString = path.ToString();
if (BadPathCharacterRegex.IsMatch(asString))
{
return false;
}
foreach (var segment in path.EnumerateSegments())
{
if (BadPathSegmentRegex.IsMatch(segment))
{
return false;
}
}
return true;
}
}
}