mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
* Removed the Interfaces folder. * All objects inside the GameObjects subfolders are now in the GameObjects namespace. * Added a Resharper DotSettings file to mark the GameObjects subfolders as not providing namespaces. * Simplified Robust.client.Graphics namespace. * Automated remove redundant using statements.
317 lines
9.7 KiB
C#
317 lines
9.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Robust.Shared.ContentPack
|
|
{
|
|
// TODO: Maybe provide consistency with System.IO on the front of which exception types get thrown.
|
|
/// <summary>
|
|
/// Writable dir provider that uses an in-memory virtual file system, not the real OS file system.
|
|
/// </summary>
|
|
public sealed class VirtualWritableDirProvider : IWritableDirProvider
|
|
{
|
|
// Just a simple tree. No need to over complicate this.
|
|
private readonly DirectoryNode _rootDirectoryNode = new();
|
|
|
|
/// <inheritdoc />
|
|
public string? RootDir => null;
|
|
|
|
public void CreateDir(ResourcePath path)
|
|
{
|
|
if (!path.IsRooted)
|
|
{
|
|
throw new ArgumentException("Path must be rooted", nameof(path));
|
|
}
|
|
|
|
path = path.Clean();
|
|
|
|
var directory = _rootDirectoryNode;
|
|
foreach (var segment in path.EnumerateSegments())
|
|
{
|
|
if (directory.Children.TryGetValue(segment, out var child))
|
|
{
|
|
if (!(child is DirectoryNode childDir))
|
|
{
|
|
throw new ArgumentException("A file already exists at that location.");
|
|
}
|
|
|
|
directory = childDir;
|
|
continue;
|
|
}
|
|
|
|
var newDir = new DirectoryNode();
|
|
|
|
directory.Children.Add(segment, newDir);
|
|
}
|
|
}
|
|
|
|
public void Delete(ResourcePath path)
|
|
{
|
|
if (!path.IsRooted)
|
|
{
|
|
throw new ArgumentException("Path must be rooted", nameof(path));
|
|
}
|
|
|
|
path = path.Clean();
|
|
|
|
var pathParent = path.Directory;
|
|
|
|
if (!TryGetNodeAt(pathParent, out var parent) || !(parent is DirectoryNode directory))
|
|
{
|
|
return;
|
|
}
|
|
|
|
directory.Children.Remove(path.Filename);
|
|
}
|
|
|
|
public bool Exists(ResourcePath path)
|
|
{
|
|
return TryGetNodeAt(path, out _);
|
|
}
|
|
|
|
public (IEnumerable<ResourcePath> files, IEnumerable<ResourcePath> directories) Find(string pattern,
|
|
bool recursive = true)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public bool IsDir(ResourcePath path)
|
|
{
|
|
return TryGetNodeAt(path, out var node) && node is DirectoryNode;
|
|
}
|
|
|
|
public Stream Open(ResourcePath path, FileMode fileMode, FileAccess access, FileShare share)
|
|
{
|
|
if (!path.IsRooted)
|
|
{
|
|
throw new ArgumentException("Path must be rooted", nameof(path));
|
|
}
|
|
|
|
path = path.Clean();
|
|
|
|
var parentPath = path.Directory;
|
|
if (!TryGetNodeAt(parentPath, out var parent) || !(parent is DirectoryNode parentDir))
|
|
{
|
|
throw new ArgumentException("Parent directory does not exist.");
|
|
}
|
|
|
|
var fileName = path.Filename;
|
|
|
|
if (parentDir.Children.TryGetValue(fileName, out var maybeFileNode) && maybeFileNode is DirectoryNode)
|
|
{
|
|
throw new ArgumentException("There is a directory at that location.");
|
|
}
|
|
|
|
var fileNode = (FileNode) maybeFileNode!;
|
|
|
|
switch (fileMode)
|
|
{
|
|
case FileMode.Append:
|
|
{
|
|
if (fileNode == null)
|
|
{
|
|
fileNode = new FileNode();
|
|
parentDir.Children.Add(fileName, fileNode);
|
|
}
|
|
|
|
return new VirtualFileStream(fileNode.Contents, false, true, fileNode.Contents.Length);
|
|
}
|
|
|
|
case FileMode.Create:
|
|
{
|
|
if (fileNode == null)
|
|
{
|
|
fileNode = new FileNode();
|
|
parentDir.Children.Add(fileName, fileNode);
|
|
}
|
|
else
|
|
{
|
|
// Clear contents if it already exists.
|
|
fileNode.Contents.SetLength(0);
|
|
}
|
|
|
|
return new VirtualFileStream(fileNode.Contents, true, true, 0);
|
|
}
|
|
|
|
case FileMode.CreateNew:
|
|
{
|
|
if (fileNode != null)
|
|
{
|
|
throw new IOException("File already exists.");
|
|
}
|
|
|
|
fileNode = new FileNode();
|
|
parentDir.Children.Add(fileName, fileNode);
|
|
|
|
return new VirtualFileStream(fileNode.Contents, true, true, 0);
|
|
}
|
|
|
|
case FileMode.Open:
|
|
{
|
|
if (fileNode == null)
|
|
{
|
|
throw new FileNotFoundException();
|
|
}
|
|
|
|
return new VirtualFileStream(fileNode.Contents, true, true, 0);
|
|
}
|
|
|
|
case FileMode.OpenOrCreate:
|
|
{
|
|
if (fileNode == null)
|
|
{
|
|
fileNode = new FileNode();
|
|
parentDir.Children.Add(fileName, fileNode);
|
|
}
|
|
|
|
return new VirtualFileStream(fileNode.Contents, true, true, 0);
|
|
}
|
|
|
|
case FileMode.Truncate:
|
|
{
|
|
if (fileNode == null)
|
|
{
|
|
throw new FileNotFoundException();
|
|
}
|
|
|
|
fileNode.Contents.SetLength(0);
|
|
|
|
return new VirtualFileStream(fileNode.Contents, true, true, 0);
|
|
}
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(fileMode), fileMode, null);
|
|
}
|
|
}
|
|
|
|
public void Rename(ResourcePath oldPath, ResourcePath newPath)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private bool TryGetNodeAt(ResourcePath path, [NotNullWhen(true)] out INode? node)
|
|
{
|
|
if (!path.IsRooted)
|
|
{
|
|
throw new ArgumentException("Path must be rooted", nameof(path));
|
|
}
|
|
|
|
path = path.Clean();
|
|
|
|
if (path == ResourcePath.Root)
|
|
{
|
|
node = _rootDirectoryNode;
|
|
return true;
|
|
}
|
|
|
|
var directory = _rootDirectoryNode;
|
|
var segments = path.EnumerateSegments().ToArray();
|
|
for (var i = 0; i < segments.Length; i++)
|
|
{
|
|
var segment = segments[i];
|
|
if (!directory.Children.TryGetValue(segment, out var child))
|
|
{
|
|
node = default;
|
|
return false;
|
|
}
|
|
|
|
if (i == segments.Length - 1)
|
|
{
|
|
node = child;
|
|
return true;
|
|
}
|
|
|
|
directory = (DirectoryNode) child;
|
|
}
|
|
|
|
throw new InvalidOperationException("Unreachable.");
|
|
}
|
|
|
|
private interface INode
|
|
{
|
|
}
|
|
|
|
private sealed class FileNode : INode
|
|
{
|
|
public MemoryStream Contents { get; } = new();
|
|
}
|
|
|
|
private sealed class DirectoryNode : INode
|
|
{
|
|
public Dictionary<string, INode> Children { get; } = new();
|
|
}
|
|
|
|
private sealed class VirtualFileStream : Stream
|
|
{
|
|
private readonly MemoryStream _source;
|
|
|
|
public VirtualFileStream(MemoryStream source, bool canRead, bool canWrite, long initialPosition)
|
|
{
|
|
_source = source;
|
|
CanRead = canRead;
|
|
CanWrite = canWrite;
|
|
Position = initialPosition;
|
|
}
|
|
|
|
public override void Flush()
|
|
{
|
|
// Nada.
|
|
}
|
|
|
|
public override int Read(byte[] buffer, int offset, int count)
|
|
{
|
|
if (!CanRead)
|
|
{
|
|
throw new InvalidOperationException("Cannot read from this stream.");
|
|
}
|
|
|
|
_source.Position = Position;
|
|
var read = _source.Read(buffer, offset, count);
|
|
Position = _source.Position;
|
|
return read;
|
|
}
|
|
|
|
public override long Seek(long offset, SeekOrigin origin)
|
|
{
|
|
switch (origin)
|
|
{
|
|
case SeekOrigin.Begin:
|
|
Position = offset;
|
|
break;
|
|
case SeekOrigin.Current:
|
|
Position += offset;
|
|
break;
|
|
case SeekOrigin.End:
|
|
Position = Length + offset;
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(origin), origin, null);
|
|
}
|
|
|
|
return Position;
|
|
}
|
|
|
|
public override void SetLength(long value)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
public override void Write(byte[] buffer, int offset, int count)
|
|
{
|
|
_source.Position = Position;
|
|
_source.Write(buffer, offset, count);
|
|
Position = _source.Position;
|
|
}
|
|
|
|
public override bool CanRead { get; }
|
|
public override bool CanSeek => true;
|
|
public override bool CanWrite { get; }
|
|
public override long Length => _source.Position;
|
|
public override long Position { get; set; }
|
|
}
|
|
}
|
|
}
|