Skip to content

Driver Memory

Gustavo Viana edited this page Aug 4, 2026 · 9 revisions

Driver: Memory (in-process)

In-memory storage backed by Dictionary<string, ...> + MemoryStream. Ships in the core FileHub package. Primary use case: tests.

using FileHub.Memory;

var hub = new MemoryFileHub();          // Root.Name = "root"
var hub = new MemoryFileHub("mnt");     // Root.Name = "mnt"

Nested directory paths ("a/b/c") walk the in-process dictionary hierarchy — intermediate directories always materialise, no I/O involved.

Interface

public interface IMemoryFileHub : IFileHub { }
public class    MemoryFileHub : IMemoryFileHub { ... }

Swap in for tests

public class ReportService(IFileHub hub) { ... }

// in a test
var hub = new MemoryFileHub();
var svc = new ReportService(hub);
svc.Save("report.pdf", body);

Assert.Equal(body, hub.Root.OpenFile("report.pdf").ReadAllText());

More patterns: Testing.

Name matching

GetFiles / GetDirectories use Win32-style glob, aligned with what LocalFile accepts. Tests against MemoryFileHub match prod behaviour against LocalFileHub.

Pattern Matches
*, *.* everything
*.txt suffix
report_* prefix
q?.pdf single-char wildcard (e.g. q1.pdf, q4.pdf)
[abc]*.log character class — names starting with a, b, or c
name (no wildcards) exact, case-insensitive

All matching is case-insensitive. ? and * cannot match path separators (separators don't appear in a single directory's listing anyway).

Existence probes

FileExists(name) and DirectoryExists(name) are simple dictionary lookups.

Nested paths in CreateFile / OpenFile / TryOpenFile

CreateFile("a/b/c.txt") (and OpenFile / TryOpenFile) creates the file at the nested location, auto-creating the intermediate MemoryDirectory instances. Both / and \ separators work; .. is rejected with FileHubException.

hub.Root.CreateFile("reports/2026/q1.pdf").SetText("...");

Stream semantics

MemoryFile shares one MemoryStream between reads and writes, wrapped so callers' Dispose doesn't destroy the storage. Concurrent-access rules:

  • While a write stream is open, no other stream can be opened (throws).
  • Multiple concurrent read streams are allowed.
var f = hub.Root.CreateFile("a.txt");
using (var w = f.GetWriteStream())
{
    w.Write(bytes, 0, bytes.Length);
    // f.GetReadStream() here would throw — write lock held
}
using (var r = f.GetReadStream()) { /* ok */ }

Metadata

MemoryFileHub reports Features.Metadata == true and keeps per-object metadata in memory, so it round-trips FileWriteOptions the same way the cloud drivers do — handy for testing metadata-dependent code without a real bucket.

var f = hub.Root.CreateFile("img.bin");
f.SetBytes(bytes, new FileWriteOptions
{
    ContentType  = "image/png",
    CacheControl = "public,max-age=3600",
    Metadata     = new Dictionary<string, string> { ["owner"] = "team-x" },
});

var meta = f.GetMetadata();
// meta.ContentType   == "image/png"
// meta.CacheControl  == "public,max-age=3600"
// meta.Tags["owner"] == "team-x"

ContentType, CacheControl, and user Metadata are stored and returned. Driver-specific typed fields (S3 storage class / SSE) are not modelled — Memory ignores options it doesn't support, never throws. The returned FileMetadata is an immutable snapshot, same as every driver.

Lifecycle notes

  • Everything lives in process memory; nothing persists. Two hubs are independent.
  • Delete() clears collections and disposes the backing object — callers holding the old reference will see Exists() return false.
  • Rename on a directory disposes the old instance and returns a new one. Keep the returned reference.
  • Rename is leaf-only — a newName containing / or \ throws ArgumentException; use MoveTo to relocate. Rename never overwrites — a name already taken (file or directory) throws FileAlreadyExistsException. CopyTo / MoveTo accept overwrite (default false): an existing destination throws FileAlreadyExistsException, leaving the source intact; pass overwrite: true to replace it. Directory MoveTo(dir, name, overwrite: true) merges into an existing destination.

Does not implement

  • IUrlAccessible — in-process only, no URL.

Clone this wiki locally