XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Text;

namespace XFESystem.Core;

/// <summary>USB-backed workspace with a RAM cache; every successful persistent mutation commits to media.</summary>
public sealed class PersistentUsbWorkspace : IWorkspaceBinaryStorage
{
    public const string ImportedFiles = "X:\\UsbFiles";
    private readonly object gate = new();
    private readonly UsbWorkspaceJournal journal;
    private readonly ReadOnlyFat32Volume files;
    private MemoryWorkspace memory;
    public bool IsPersistent => true;
    public ulong Generation => journal.Generation;
    public PersistentUsbWorkspace(UsbWorkspaceJournal journal, ReadOnlyFat32Volume files)
    {
        this.journal = journal; this.files = files;
        memory = journal.Load() is { } payload ? Decode(payload) : new MemoryWorkspace(false);
        memory.CreateDirectory(ImportedFiles);
    }
    private static bool Transient(string path) => Under(path,SystemPaths.Temporary) || Under(path,"X:\\System\\Diagnostics");
    private static bool Under(string path,string root) => path.Equals(root,StringComparison.OrdinalIgnoreCase) || path.StartsWith(root+"\\",StringComparison.OrdinalIgnoreCase);
    private static bool Imported(string path) => Under(path,ImportedFiles);
    private static string FatPath(string path) => "UserFiles"+path[ImportedFiles.Length..].Replace('\\','/');
    public bool Exists(string path)
    {
        path = SystemPaths.Normalize(path); lock(gate) {
            if (!Imported(path) || path.Equals(ImportedFiles,StringComparison.OrdinalIgnoreCase)) return memory.Exists(path);
            try { files.Find(FatPath(path)); return true; } catch(FileNotFoundException) { return false; }
        }
    }
    public WorkspaceEntry[] List(string directory)
    {
        directory = SystemPaths.Normalize(directory); lock(gate) {
            if (!Imported(directory)) return memory.List(directory);
            return files.List(FatPath(directory)).Select(e => new WorkspaceEntry(SystemPaths.Combine(directory,e.Name),e.IsDirectory,e.Length)).ToArray();
        }
    }
    public string ReadText(string path)
    {
        path = SystemPaths.Normalize(path); lock(gate) {
            if (!Imported(path)) return memory.ReadText(path);
            if (path.EndsWith(".dll",StringComparison.OrdinalIgnoreCase)||path.EndsWith(".exe",StringComparison.OrdinalIgnoreCase)) throw new IOException("Binary USB file.");
            return new UTF8Encoding(false,true).GetString(files.ReadFile(FatPath(path)));
        }
    }
    public byte[] ReadBytes(string path)
    { path = SystemPaths.Normalize(path); lock(gate) return Imported(path) ? files.ReadFile(FatPath(path)) : memory.ReadBytes(path); }
    public void CreateDirectory(string path)
    { path = SystemPaths.Normalize(path); lock(gate) { if(memory.Exists(path)) return; Mutate(path,m => m.CreateDirectory(path)); } }
    public void WriteText(string path,string content)
    { path = SystemPaths.Normalize(path); lock(gate) { if(!Imported(path)&&memory.Exists(path)) { try { if(memory.ReadText(path)==content)return; }catch(IOException){} } Mutate(path,m => m.WriteText(path,content)); } }
    public void WriteBytes(string path,ReadOnlySpan<byte> content)
    { path = SystemPaths.Normalize(path); byte[] copy = content.ToArray(); lock(gate) Mutate(path,m => m.WriteBytes(path,copy)); }
    public void Delete(string path)
    { path = SystemPaths.Normalize(path); lock(gate) Mutate(path,m => m.Delete(path)); }
    public void Move(string source,string destination)
    {
        source = SystemPaths.Normalize(source); destination = SystemPaths.Normalize(destination);
        if(Imported(destination)||Transient(source)!=Transient(destination)) throw new IOException("Unsupported cross-volume move; use copy instead.");
        lock(gate) Mutate(source,m => m.Move(source,destination));
    }
    private void Mutate(string path,Action<MemoryWorkspace> action)
    {
        if(Imported(path)) throw new IOException("UsbFiles is an import folder. Copy files into the workspace before editing them.");
        if(Transient(path)) { action(memory); return; }
        var before = memory.Clone();
        try { action(memory); journal.Save(Encode(memory)); }
        catch { memory = before; throw; }
    }
    private static byte[] Encode(MemoryWorkspace workspace)
    {
        using var stream = new MemoryStream(); using var writer = new BinaryWriter(stream,Encoding.UTF8,true);
        var records = workspace.Export().Where(r => !Transient(r.Path)).ToArray();
        writer.Write(0x31575358u); writer.Write(records.Length);
        foreach(var record in records) { writer.Write(record.Path); writer.Write(record.Kind); writer.Write(record.Content.Length); writer.Write(record.Content); }
        if(stream.Length>UsbWorkspaceFormat.MaxPayloadBytes)throw new IOException("USB workspace is full.");
        return stream.ToArray();
    }
    private static MemoryWorkspace Decode(byte[] payload)
    {
        using var stream = new MemoryStream(payload,false); using var reader = new BinaryReader(stream,Encoding.UTF8,true);
        if(reader.ReadUInt32()!=0x31575358u)throw new IOException("Unknown USB workspace format.");
        int count = reader.ReadInt32(); if(count<0||count>1024)throw new IOException("USB workspace entry count is invalid.");
        var result = new MemoryWorkspace(false); var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        for(int i=0;i<count;i++)
        {
            string path = SystemPaths.Normalize(reader.ReadString()); byte kind = reader.ReadByte(); int length = reader.ReadInt32();
            if(path==SystemPaths.Root||!seen.Add(path)||kind>2||length<0||length>4*1024*1024||length>stream.Length-stream.Position||kind==0&&length!=0||Transient(path))throw new IOException("Invalid USB workspace record.");
            byte[] content = reader.ReadBytes(length); result.CreateDirectory(SystemPaths.Parent(path));
            if(kind==0)result.CreateDirectory(path);else if(kind==1)result.WriteText(path,new UTF8Encoding(false,true).GetString(content));else result.WriteBytes(path,content);
        }
        if(stream.Position!=stream.Length)throw new IOException("USB workspace has trailing data.");
        return result;
    }
}