namespace XFESystem.Core;
/// <summary>Explicit RAM-live storage. This implementation never opens a physical disk or a host file.</summary>
public sealed class MemoryWorkspace : IWorkspaceBinaryStorage
{
private readonly Dictionary<string, string?> _entries = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, byte[]> _binary = new(StringComparer.OrdinalIgnoreCase);
private const int MaxFileCharacters = 256 * 1024;
private const int MaxEntries = 1024;
public bool IsPersistent => false;
public MemoryWorkspace(bool seed = true)
{
_entries.Add(SystemPaths.Root, null);
foreach (var path in new[] { SystemPaths.Configuration, SystemPaths.Programs, SystemPaths.Desktop, SystemPaths.Documents, SystemPaths.Downloads, SystemPaths.Temporary })
CreateDirectory(path);
if (seed)
{
WriteText(SystemPaths.Combine(SystemPaths.Desktop, "Welcome.md"), "# XFESystem | XFEstudio\n\nC# native desktop development.\n\nThis is a RAM-live workspace: files disappear after shutdown.\n\n- Open Files or Editor from the desktop.\n- Ctrl+S saves the current file to this session.\n- CoreCLR and independent .NET DLL loading are still being ported.\n");
WriteText(SystemPaths.Combine(SystemPaths.Documents, "Hello.js"), "const greeting = 'XFESystem';\nconsole.log(`Hello from ${greeting}!`);\n");
WriteText(SystemPaths.Combine(SystemPaths.Documents, "Hello.cs"), "using System;\n\nConsole.ForegroundColor = ConsoleColor.Cyan;\nConsole.WriteLine(\"Hello XFESystem!\");\nConsole.ResetColor();\n");
}
}
public bool Exists(string path) => _entries.ContainsKey(SystemPaths.Normalize(path));
public void CreateDirectory(string path)
{
path = SystemPaths.Normalize(path);
if (_entries.TryGetValue(path, out var old))
{
if (old is not null) throw new IOException("A file occupies this directory path.");
return;
}
CreateDirectory(SystemPaths.Parent(path));
CheckCapacity();
_entries.Add(path, null);
}
public WorkspaceEntry[] List(string directory)
{
directory = SystemPaths.Normalize(directory);
if (!_entries.TryGetValue(directory, out var content) || content is not null) throw new DirectoryNotFoundException(directory);
var result = new List<WorkspaceEntry>();
foreach (var item in _entries)
if (!item.Key.Equals(directory, StringComparison.OrdinalIgnoreCase) && SystemPaths.Parent(item.Key).Equals(directory, StringComparison.OrdinalIgnoreCase))
result.Add(new(item.Key, item.Value is null, _binary.TryGetValue(item.Key, out var bytes) ? bytes.Length : item.Value?.Length ?? 0));
result.Sort((a, b) => a.IsDirectory != b.IsDirectory ? (a.IsDirectory ? -1 : 1) : StringComparer.OrdinalIgnoreCase.Compare(a.Name, b.Name));
return result.ToArray();
}
public string ReadText(string path)
{
path = SystemPaths.Normalize(path);
if (_binary.ContainsKey(path)) throw new IOException("Binary files must be opened by their application handler.");
if (!_entries.TryGetValue(path, out var result) || result is null) throw new FileNotFoundException("Text file was not found.", path);
return result;
}
public void WriteText(string path, string content)
{
ArgumentNullException.ThrowIfNull(content);
path = SystemPaths.Normalize(path);
if (content.Length > MaxFileCharacters) throw new IOException("The RAM-live file size limit was reached.");
int used = 0;
foreach (var item in _entries) if (!_binary.ContainsKey(item.Key)) used += item.Value?.Length ?? 0;
int previous = !_binary.ContainsKey(path) && _entries.TryGetValue(path, out var existing) ? existing?.Length ?? 0 : 0;
if (used - previous + content.Length > 4 * 1024 * 1024) throw new IOException("RAM-live text quota reached.");
var parent = SystemPaths.Parent(path);
if (!_entries.TryGetValue(parent, out var directory) || directory is not null) throw new DirectoryNotFoundException(parent);
if (_entries.TryGetValue(path, out var old) && old is null) throw new IOException("Cannot overwrite a directory.");
if (!_entries.ContainsKey(path)) CheckCapacity();
_entries[path] = content;
_binary.Remove(path);
}
public byte[] ReadBytes(string path)
{
path = SystemPaths.Normalize(path);
if (_binary.TryGetValue(path, out var bytes)) return (byte[])bytes.Clone();
return System.Text.Encoding.UTF8.GetBytes(ReadText(path));
}
public void WriteBytes(string path, ReadOnlySpan<byte> content)
{
if (content.Length > 4 * 1024 * 1024) throw new IOException("Binary file size limit reached.");
int used = 0;
foreach (var item in _binary) used += item.Value.Length;
path = SystemPaths.Normalize(path);
int previous = _binary.TryGetValue(path, out var old) ? old.Length : 0;
if (used - previous + content.Length > 8 * 1024 * 1024) throw new IOException("Binary workspace quota reached.");
byte[] copy = content.ToArray();
WriteText(path, "");
_binary[path] = copy;
}
public void Delete(string path)
{
path = SystemPaths.Normalize(path);
if (path == SystemPaths.Root) throw new IOException("Cannot delete the system root.");
if (!_entries.ContainsKey(path)) throw new FileNotFoundException(path);
foreach (var entry in _entries.Keys)
if (entry.StartsWith(path + "\\", StringComparison.OrdinalIgnoreCase)) throw new IOException("The directory is not empty.");
_entries.Remove(path);
_binary.Remove(path);
}
public void Move(string source, string destination)
{
source = SystemPaths.Normalize(source);
destination = SystemPaths.Normalize(destination);
if (source == SystemPaths.Root || destination.StartsWith(source + "\\", StringComparison.OrdinalIgnoreCase)) throw new IOException("Invalid move destination.");
if (!_entries.TryGetValue(source, out var item)) throw new FileNotFoundException(source);
if (_entries.ContainsKey(destination)) throw new IOException("The destination already exists.");
var parent = SystemPaths.Parent(destination);
if (!_entries.TryGetValue(parent, out var parentData) || parentData is not null) throw new DirectoryNotFoundException(parent);
var descendants = new List<KeyValuePair<string, string?>>();
foreach (var entry in _entries)
if (entry.Key.Equals(source, StringComparison.OrdinalIgnoreCase) || entry.Key.StartsWith(source + "\\", StringComparison.OrdinalIgnoreCase)) descendants.Add(entry);
var targets = new List<string>();
foreach (var entry in descendants) targets.Add(SystemPaths.Normalize(destination + entry.Key[source.Length..]));
foreach (var entry in descendants) _entries.Remove(entry.Key);
for (int i = 0; i < descendants.Count; i++)
{
var entry = descendants[i];
var target = targets[i];
_entries.Add(target, entry.Value);
if (_binary.TryGetValue(entry.Key, out var bytes)) { _binary.Remove(entry.Key); _binary.Add(target, bytes); }
}
}
private void CheckCapacity()
{
if (_entries.Count >= MaxEntries) throw new IOException("The RAM-live entry limit was reached.");
}
}
namespace XFESystem.Core;
/// <summary>Explicit RAM-live storage. This implementation never opens a physical disk or a host file.</summary>
public sealed class MemoryWorkspace : IWorkspaceBinaryStorage
{
private readonly Dictionary<string, string?> _entries = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, byte[]> _binary = new(StringComparer.OrdinalIgnoreCase);
private const int MaxFileCharacters = 256 * 1024;
private const int MaxEntries = 1024;
public bool IsPersistent => false;
public MemoryWorkspace(bool seed = true)
{
_entries.Add(SystemPaths.Root, null);
foreach (var path in new[] { SystemPaths.Configuration, SystemPaths.Programs, SystemPaths.Desktop, SystemPaths.Documents, SystemPaths.Downloads, SystemPaths.Temporary })
CreateDirectory(path);
if (seed)
{
WriteText(SystemPaths.Combine(SystemPaths.Desktop, "Welcome.md"), "# XFESystem | XFEstudio\n\nC# native desktop development.\n\nThis is a RAM-live workspace: files disappear after shutdown.\n\n- Open Files or Editor from the desktop.\n- Ctrl+S saves the current file to this session.\n- CoreCLR and independent .NET DLL loading are still being ported.\n");
WriteText(SystemPaths.Combine(SystemPaths.Documents, "Hello.js"), "const greeting = 'XFESystem';\nconsole.log(`Hello from ${greeting}!`);\n");
WriteText(SystemPaths.Combine(SystemPaths.Documents, "Hello.cs"), "using System;\n\nConsole.ForegroundColor = ConsoleColor.Cyan;\nConsole.WriteLine(\"Hello XFESystem!\");\nConsole.ResetColor();\n");
}
}
public bool Exists(string path) => _entries.ContainsKey(SystemPaths.Normalize(path));
public void CreateDirectory(string path)
{
path = SystemPaths.Normalize(path);
if (_entries.TryGetValue(path, out var old))
{
if (old is not null) throw new IOException("A file occupies this directory path.");
return;
}
CreateDirectory(SystemPaths.Parent(path));
CheckCapacity();
_entries.Add(path, null);
}
public WorkspaceEntry[] List(string directory)
{
directory = SystemPaths.Normalize(directory);
if (!_entries.TryGetValue(directory, out var content) || content is not null) throw new DirectoryNotFoundException(directory);
var result = new List<WorkspaceEntry>();
foreach (var item in _entries)
if (!item.Key.Equals(directory, StringComparison.OrdinalIgnoreCase) && SystemPaths.Parent(item.Key).Equals(directory, StringComparison.OrdinalIgnoreCase))
result.Add(new(item.Key, item.Value is null, _binary.TryGetValue(item.Key, out var bytes) ? bytes.Length : item.Value?.Length ?? 0));
result.Sort((a, b) => a.IsDirectory != b.IsDirectory ? (a.IsDirectory ? -1 : 1) : StringComparer.OrdinalIgnoreCase.Compare(a.Name, b.Name));
return result.ToArray();
}
public string ReadText(string path)
{
path = SystemPaths.Normalize(path);
if (_binary.ContainsKey(path)) throw new IOException("Binary files must be opened by their application handler.");
if (!_entries.TryGetValue(path, out var result) || result is null) throw new FileNotFoundException("Text file was not found.", path);
return result;
}
public void WriteText(string path, string content)
{
ArgumentNullException.ThrowIfNull(content);
path = SystemPaths.Normalize(path);
if (content.Length > MaxFileCharacters) throw new IOException("The RAM-live file size limit was reached.");
int used = 0;
foreach (var item in _entries) if (!_binary.ContainsKey(item.Key)) used += item.Value?.Length ?? 0;
int previous = !_binary.ContainsKey(path) && _entries.TryGetValue(path, out var existing) ? existing?.Length ?? 0 : 0;
if (used - previous + content.Length > 4 * 1024 * 1024) throw new IOException("RAM-live text quota reached.");
var parent = SystemPaths.Parent(path);
if (!_entries.TryGetValue(parent, out var directory) || directory is not null) throw new DirectoryNotFoundException(parent);
if (_entries.TryGetValue(path, out var old) && old is null) throw new IOException("Cannot overwrite a directory.");
if (!_entries.ContainsKey(path)) CheckCapacity();
_entries[path] = content;
_binary.Remove(path);
}
public byte[] ReadBytes(string path)
{
path = SystemPaths.Normalize(path);
if (_binary.TryGetValue(path, out var bytes)) return (byte[])bytes.Clone();
return System.Text.Encoding.UTF8.GetBytes(ReadText(path));
}
public void WriteBytes(string path, ReadOnlySpan<byte> content)
{
if (content.Length > 4 * 1024 * 1024) throw new IOException("Binary file size limit reached.");
int used = 0;
foreach (var item in _binary) used += item.Value.Length;
path = SystemPaths.Normalize(path);
int previous = _binary.TryGetValue(path, out var old) ? old.Length : 0;
if (used - previous + content.Length > 8 * 1024 * 1024) throw new IOException("Binary workspace quota reached.");
byte[] copy = content.ToArray();
WriteText(path, "");
_binary[path] = copy;
}
public void Delete(string path)
{
path = SystemPaths.Normalize(path);
if (path == SystemPaths.Root) throw new IOException("Cannot delete the system root.");
if (!_entries.ContainsKey(path)) throw new FileNotFoundException(path);
foreach (var entry in _entries.Keys)
if (entry.StartsWith(path + "\\", StringComparison.OrdinalIgnoreCase)) throw new IOException("The directory is not empty.");
_entries.Remove(path);
_binary.Remove(path);
}
public void Move(string source, string destination)
{
source = SystemPaths.Normalize(source);
destination = SystemPaths.Normalize(destination);
if (source == SystemPaths.Root || destination.StartsWith(source + "\\", StringComparison.OrdinalIgnoreCase)) throw new IOException("Invalid move destination.");
if (!_entries.TryGetValue(source, out var item)) throw new FileNotFoundException(source);
if (_entries.ContainsKey(destination)) throw new IOException("The destination already exists.");
var parent = SystemPaths.Parent(destination);
if (!_entries.TryGetValue(parent, out var parentData) || parentData is not null) throw new DirectoryNotFoundException(parent);
var descendants = new List<KeyValuePair<string, string?>>();
foreach (var entry in _entries)
if (entry.Key.Equals(source, StringComparison.OrdinalIgnoreCase) || entry.Key.StartsWith(source + "\\", StringComparison.OrdinalIgnoreCase)) descendants.Add(entry);
var targets = new List<string>();
foreach (var entry in descendants) targets.Add(SystemPaths.Normalize(destination + entry.Key[source.Length..]));
foreach (var entry in descendants) _entries.Remove(entry.Key);
for (int i = 0; i < descendants.Count; i++)
{
var entry = descendants[i];
var target = targets[i];
_entries.Add(target, entry.Value);
if (_binary.TryGetValue(entry.Key, out var bytes)) { _binary.Remove(entry.Key); _binary.Add(target, bytes); }
}
}
private void CheckCapacity()
{
if (_entries.Count >= MaxEntries) throw new IOException("The RAM-live entry limit was reached.");
}
}