using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Robust.Shared.Log;
using Robust.Shared.Utility;
namespace Robust.Shared.ContentPack
{
internal partial class ResourceManager
{
///
/// Holds info about a directory that is mounted in the VFS.
///
sealed class DirLoader : IContentRoot
{
private readonly DirectoryInfo _directory;
private readonly ISawmill _sawmill;
private readonly bool _checkCasing;
///
/// Constructor.
///
/// Directory to mount.
///
///
public DirLoader(DirectoryInfo directory, ISawmill sawmill, bool checkCasing)
{
_directory = directory;
_sawmill = sawmill;
_checkCasing = checkCasing;
}
///
public void Mount()
{
// Looks good to me
// Nothing to check here since the ResourceManager handles checking permissions.
}
///
public bool TryGetFile(ResPath relPath, [NotNullWhen(true)] out Stream? stream)
{
var path = GetPath(relPath);
CheckPathCasing(relPath);
var ret = FileHelper.TryOpenFileRead(path, out var fStream);
stream = fStream;
return ret;
}
public bool FileExists(ResPath relPath)
{
var path = GetPath(relPath);
return File.Exists(path);
}
internal string GetPath(ResPath relPath)
{
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
}
///
public IEnumerable FindFiles(ResPath path)
{
var fullPath = GetPath(path);
if (!Directory.Exists(fullPath))
{
yield break;
}
var paths = PathHelpers.GetFiles(fullPath);
// GetFiles returns full paths, we want them relative to root
foreach (var filePath in paths)
{
var relPath = filePath.Substring(_directory.FullName.Length);
yield return ResPath.FromRelativeSystemPath(relPath);
}
}
public IEnumerable GetEntries(ResPath path)
{
var fullPath = GetPath(path);
if (!Directory.Exists(fullPath))
return Enumerable.Empty();
return Directory.EnumerateFileSystemEntries(fullPath)
.Select(c =>
{
var rel = Path.GetRelativePath(fullPath, c);
if (Directory.Exists(c))
return rel + "/";
return rel;
});
}
[Conditional("DEBUG")]
private void CheckPathCasing(ResPath path)
{
if (!_checkCasing)
return;
// Run this inside the thread pool due to overhead.
Task.Run(() =>
{
var prevPath = GetPath(ResPath.Root);
var diskPath = ResPath.Root;
var mismatch = false;
foreach (var segment in path.CanonPath.Split('/'))
{
var prevDir = new DirectoryInfo(prevPath);
var found = false;
foreach (var info in prevDir.EnumerateFileSystemInfos())
{
if (!string.Equals(info.Name, segment, StringComparison.InvariantCultureIgnoreCase))
{
// Not the dir info for this path segment, ignore it.
continue;
}
if (!string.Equals(info.Name, segment, StringComparison.InvariantCulture))
{
// Segments match insensitively but not the other way around. Bad.
mismatch = true;
}
diskPath /= info.Name;
prevPath = Path.Combine(prevPath, info.Name);
found = true;
break;
}
if (!found)
{
// File doesn't exist. Let somebody else throw the exception.
return;
}
}
if (mismatch)
{
_sawmill.Warning("Path '{0}' has mismatching case from file on disk ('{1}'). " +
"This can cause loading failures on certain file system configurations " +
"and should be corrected.", path, diskPath);
}
});
}
public IEnumerable GetRelativeFilePaths()
{
return GetRelativeFilePaths(_directory);
}
private IEnumerable GetRelativeFilePaths(DirectoryInfo dir)
{
foreach (var file in dir.EnumerateFiles())
{
if ((file.Attributes & FileAttributes.Hidden) != 0 || file.Name[0] == '.')
{
continue;
}
var filePath = file.FullName;
var relPath = filePath.Substring(_directory.FullName.Length);
yield return ResPath.FromRelativeSystemPath(relPath).ToRootedPath().ToString();
}
foreach (var subDir in dir.EnumerateDirectories())
{
if (((subDir.Attributes & FileAttributes.Hidden) != 0) || subDir.Name[0] == '.')
{
continue;
}
foreach (var relPath in GetRelativeFilePaths(subDir))
{
yield return relPath;
}
}
}
}
}
}