diff --git a/DeepDrftContent/FileDatabase/FileDatabase.csproj b/DeepDrftContent/FileDatabase/FileDatabase.csproj
new file mode 100644
index 0000000..125f4c9
--- /dev/null
+++ b/DeepDrftContent/FileDatabase/FileDatabase.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
diff --git a/DeepDrftContent/FileDatabase/Models/EntryKey.cs b/DeepDrftContent/FileDatabase/Models/EntryKey.cs
new file mode 100644
index 0000000..71040e5
--- /dev/null
+++ b/DeepDrftContent/FileDatabase/Models/EntryKey.cs
@@ -0,0 +1,9 @@
+namespace DeepDrftContent.FileDatabase.Models;
+
+///
+/// Represents a key for entries in the file database system.
+/// Combines a string key with a media vault type for type-safe operations.
+///
+/// The string identifier for the entry
+/// The media vault type this entry belongs to
+public record EntryKey(string Key, MediaVaultType Type);
diff --git a/DeepDrftContent/FileDatabase/Models/IIndex.cs b/DeepDrftContent/FileDatabase/Models/IIndex.cs
new file mode 100644
index 0000000..867f3e1
--- /dev/null
+++ b/DeepDrftContent/FileDatabase/Models/IIndex.cs
@@ -0,0 +1,27 @@
+namespace DeepDrftContent.FileDatabase.Models;
+
+///
+/// Base interface for all index types
+///
+public interface IIndex
+{
+ ///
+ /// Gets the key identifier for this index
+ ///
+ string GetKey();
+
+ ///
+ /// Gets all entry keys in this index
+ ///
+ IReadOnlyList GetEntries();
+
+ ///
+ /// Gets the number of entries in this index
+ ///
+ int GetEntriesSize();
+
+ ///
+ /// Checks if the index contains the specified entry key
+ ///
+ bool HasEntry(EntryKey entryKey);
+}
diff --git a/DeepDrftContent/FileDatabase/Models/IndexData.cs b/DeepDrftContent/FileDatabase/Models/IndexData.cs
new file mode 100644
index 0000000..c5f0fe4
--- /dev/null
+++ b/DeepDrftContent/FileDatabase/Models/IndexData.cs
@@ -0,0 +1,112 @@
+using DeepDrftContent.FileDatabase.Utils;
+
+namespace DeepDrftContent.FileDatabase.Models;
+
+///
+/// Base class for index data used in serialization
+///
+public abstract class IndexData
+{
+ public string IndexKey { get; }
+
+ protected IndexData(string indexKey)
+ {
+ IndexKey = indexKey;
+ }
+}
+
+///
+/// Serializable data for directory indexes
+///
+public class DirectoryIndexData : IndexData
+{
+ public List Entries { get; set; } = new();
+
+ public DirectoryIndexData(string indexKey) : base(indexKey) { }
+
+ public static DirectoryIndexData FromIndex(DirectoryIndex index)
+ {
+ var data = new DirectoryIndexData(index.GetKey())
+ {
+ Entries = index.GetEntries().ToList()
+ };
+ return data;
+ }
+}
+
+///
+/// Serializable data for vault indexes
+///
+public class VaultIndexData : IndexData
+{
+ public List<(EntryKey Key, MetaData Value)> Entries { get; set; } = new();
+
+ public VaultIndexData(string indexKey) : base(indexKey) { }
+
+ public static VaultIndexData FromIndex(VaultIndex index)
+ {
+ var data = new VaultIndexData(index.GetKey())
+ {
+ Entries = index.Entries.Select(kvp => (kvp.Key, kvp.Value)).ToList()
+ };
+ return data;
+ }
+}
+
+///
+/// Directory index implementation using StructuralSet for entries
+///
+public class DirectoryIndex : IndexData, IIndex
+{
+ public StructuralSet Entries { get; }
+
+ public DirectoryIndex(DirectoryIndexData indexData) : base(indexData.IndexKey)
+ {
+ Entries = new StructuralSet();
+ // Load entries from data
+ foreach (var entry in indexData.Entries)
+ {
+ Entries.Add(entry);
+ }
+ }
+
+ public string GetKey() => IndexKey;
+
+ public IReadOnlyList GetEntries() => Entries.ToList().AsReadOnly();
+
+ public int GetEntriesSize() => Entries.Size;
+
+ public bool HasEntry(EntryKey entryKey) => Entries.Has(entryKey);
+
+ public void PutEntry(EntryKey entryKey) => Entries.Add(entryKey);
+}
+
+///
+/// Vault index implementation using StructuralMap for entries with metadata
+///
+public class VaultIndex : IndexData, IIndex
+{
+ public StructuralMap Entries { get; }
+
+ public VaultIndex(VaultIndexData indexData) : base(indexData.IndexKey)
+ {
+ Entries = new StructuralMap();
+ // Load entries from data
+ foreach (var (key, value) in indexData.Entries)
+ {
+ Entries.Set(key, value);
+ }
+ }
+
+ public string GetKey() => IndexKey;
+
+ public IReadOnlyList GetEntries() => Entries.Keys.ToList().AsReadOnly();
+
+ public int GetEntriesSize() => Entries.Size;
+
+ public bool HasEntry(EntryKey entryKey) => Entries.Has(entryKey);
+
+ public MetaData? GetEntry(EntryKey entryKey) => Entries.Get(entryKey);
+
+ public void PutEntry(EntryKey entryKey, MetaData metaData) => Entries.Set(entryKey, metaData);
+}
diff --git a/DeepDrftContent/FileDatabase/Models/MediaFactories.cs b/DeepDrftContent/FileDatabase/Models/MediaFactories.cs
new file mode 100644
index 0000000..1db85eb
--- /dev/null
+++ b/DeepDrftContent/FileDatabase/Models/MediaFactories.cs
@@ -0,0 +1,147 @@
+namespace DeepDrftContent.FileDatabase.Models;
+
+///
+/// Type mappings for media vault types to their corresponding classes
+///
+public static class MediaVaultTypeMap
+{
+ public static Type GetBinaryType(MediaVaultType vaultType) => vaultType switch
+ {
+ MediaVaultType.Media => typeof(MediaBinary),
+ MediaVaultType.Image => typeof(ImageBinary),
+ _ => throw new ArgumentException($"Unknown vault type: {vaultType}")
+ };
+
+ public static Type GetDtoType(MediaVaultType vaultType) => vaultType switch
+ {
+ MediaVaultType.Media => typeof(MediaBinaryDto),
+ MediaVaultType.Image => typeof(ImageBinaryDto),
+ _ => throw new ArgumentException($"Unknown vault type: {vaultType}")
+ };
+
+ public static Type GetParamsType(MediaVaultType vaultType) => vaultType switch
+ {
+ MediaVaultType.Media => typeof(MediaBinaryParams),
+ MediaVaultType.Image => typeof(ImageBinaryParams),
+ _ => throw new ArgumentException($"Unknown vault type: {vaultType}")
+ };
+
+ public static Type GetMetaDataType(MediaVaultType vaultType) => vaultType switch
+ {
+ MediaVaultType.Media => typeof(MetaData),
+ MediaVaultType.Image => typeof(ImageMetaData),
+ _ => throw new ArgumentException($"Unknown vault type: {vaultType}")
+ };
+}
+
+///
+/// Factory for creating metadata objects based on vault type
+///
+public static class MetaDataFactory
+{
+ public static MetaData Create(MediaVaultType type, string entryKey, string extension, double aspectRatio = 1.0)
+ {
+ return type switch
+ {
+ MediaVaultType.Media => new MetaData(entryKey, extension),
+ MediaVaultType.Image => new ImageMetaData(entryKey, extension, aspectRatio),
+ _ => throw new ArgumentException($"Unknown vault type: {type}")
+ };
+ }
+
+ public static T Create(MediaVaultType type, string entryKey, string extension, double aspectRatio = 1.0)
+ where T : MetaData
+ {
+ var metaData = Create(type, entryKey, extension, aspectRatio);
+ return (T)metaData;
+ }
+}
+
+///
+/// Factory for creating media parameter objects
+///
+public static class MediaParamsFactory
+{
+ public static object Create(MediaVaultType type, FileBinary fileBinary, MetaData metaData)
+ {
+ return type switch
+ {
+ MediaVaultType.Media => new MediaBinaryParams(fileBinary.Buffer, fileBinary.Size, metaData.Extension),
+ MediaVaultType.Image when metaData is ImageMetaData imageMetaData =>
+ new ImageBinaryParams(fileBinary.Buffer, fileBinary.Size, metaData.Extension, imageMetaData.AspectRatio),
+ _ => throw new ArgumentException($"Invalid vault type {type} or metadata type mismatch")
+ };
+ }
+
+ public static T Create(MediaVaultType type, FileBinary fileBinary, MetaData metaData)
+ {
+ var parameters = Create(type, fileBinary, metaData);
+ return (T)parameters;
+ }
+}
+
+///
+/// Factory for creating media binary objects from parameters
+///
+public static class FileBinaryFactory
+{
+ public static object Create(MediaVaultType vaultType, object parameters)
+ {
+ return vaultType switch
+ {
+ MediaVaultType.Media when parameters is MediaBinaryParams mediaParams =>
+ new MediaBinary(mediaParams),
+ MediaVaultType.Image when parameters is ImageBinaryParams imageParams =>
+ new ImageBinary(imageParams),
+ _ => throw new ArgumentException($"Invalid vault type {vaultType} or parameter type mismatch")
+ };
+ }
+
+ public static T Create(MediaVaultType vaultType, object parameters) where T : FileBinary
+ {
+ var binary = Create(vaultType, parameters);
+ return (T)binary;
+ }
+
+ public static object From(MediaVaultType type, object mediaBinaryDto)
+ {
+ return type switch
+ {
+ MediaVaultType.Media when mediaBinaryDto is MediaBinaryDto mediaDto =>
+ MediaBinary.From(mediaDto),
+ MediaVaultType.Image when mediaBinaryDto is ImageBinaryDto imageDto =>
+ ImageBinary.From(imageDto),
+ _ => throw new ArgumentException($"Invalid type {type} or DTO type mismatch")
+ };
+ }
+
+ public static T From(MediaVaultType type, object mediaBinaryDto) where T : FileBinary
+ {
+ var binary = From(type, mediaBinaryDto);
+ return (T)binary;
+ }
+}
+
+///
+/// Factory for creating DTO objects from media binaries
+///
+public static class FileBinaryDtoFactory
+{
+ public static object From(MediaVaultType type, object mediaBinary)
+ {
+ return type switch
+ {
+ MediaVaultType.Media when mediaBinary is MediaBinary media =>
+ new MediaBinaryDto(media),
+ MediaVaultType.Image when mediaBinary is ImageBinary image =>
+ new ImageBinaryDto(image),
+ _ => throw new ArgumentException($"Invalid type {type} or binary type mismatch")
+ };
+ }
+
+ public static T From(MediaVaultType type, object mediaBinary)
+ {
+ var dto = From(type, mediaBinary);
+ return (T)dto;
+ }
+}
diff --git a/DeepDrftContent/FileDatabase/Models/MediaModels.cs b/DeepDrftContent/FileDatabase/Models/MediaModels.cs
new file mode 100644
index 0000000..a54d278
--- /dev/null
+++ b/DeepDrftContent/FileDatabase/Models/MediaModels.cs
@@ -0,0 +1,175 @@
+namespace DeepDrftContent.FileDatabase.Models;
+
+///
+/// Parameters for creating a FileBinary
+///
+/// The binary data
+/// The size of the data in bytes
+public record FileBinaryParams(byte[] Buffer, int Size);
+
+///
+/// Base class for file binary data
+///
+public class FileBinary
+{
+ public byte[] Buffer { get; }
+ public int Size { get; }
+
+ public FileBinary(FileBinaryParams parameters)
+ {
+ Buffer = parameters.Buffer;
+ Size = parameters.Size;
+ }
+
+ public static FileBinary From(FileBinaryDto dto)
+ {
+ var buffer = Convert.FromBase64String(dto.Base64);
+ return new FileBinary(new FileBinaryParams(buffer, dto.Size));
+ }
+}
+
+///
+/// DTO for FileBinary serialization
+///
+/// Base64 encoded binary data
+/// Size of the original data
+public record FileBinaryDto(string Base64, int Size)
+{
+ public FileBinaryDto(FileBinary fileBinary) : this(
+ Convert.ToBase64String(fileBinary.Buffer),
+ fileBinary.Size) { }
+}
+
+///
+/// Parameters for creating a MediaBinary
+///
+/// The binary data
+/// The size of the data in bytes
+/// The file extension
+public record MediaBinaryParams(byte[] Buffer, int Size, string Extension)
+ : FileBinaryParams(Buffer, Size);
+
+///
+/// Media binary with extension information
+///
+public class MediaBinary : FileBinary
+{
+ public string Extension { get; }
+
+ public MediaBinary(MediaBinaryParams parameters) : base(parameters)
+ {
+ Extension = parameters.Extension;
+ }
+
+ public static MediaBinary From(MediaBinaryDto dto)
+ {
+ var buffer = Convert.FromBase64String(dto.Base64);
+ var extension = GetExtensionType(dto.Mime);
+ return new MediaBinary(new MediaBinaryParams(buffer, dto.Size, extension));
+ }
+
+ private static string GetExtensionType(string mime)
+ {
+ return MimeTypeExtensions.GetExtension(mime);
+ }
+}
+
+///
+/// DTO for MediaBinary serialization
+///
+/// Base64 encoded binary data
+/// Size of the original data
+/// MIME type of the media
+public record MediaBinaryDto(string Base64, int Size, string Mime) : FileBinaryDto(Base64, Size)
+{
+ public MediaBinaryDto(MediaBinary mediaBinary) : this(
+ Convert.ToBase64String(mediaBinary.Buffer),
+ mediaBinary.Size,
+ MimeTypeExtensions.GetMimeType(mediaBinary.Extension)) { }
+}
+
+///
+/// Parameters for creating an ImageBinary
+///
+/// The binary data
+/// The size of the data in bytes
+/// The file extension
+/// The aspect ratio of the image
+public record ImageBinaryParams(byte[] Buffer, int Size, string Extension, double AspectRatio)
+ : MediaBinaryParams(Buffer, Size, Extension);
+
+///
+/// Image binary with aspect ratio information
+///
+public class ImageBinary : MediaBinary
+{
+ public double AspectRatio { get; }
+
+ public ImageBinary(ImageBinaryParams parameters) : base(parameters)
+ {
+ AspectRatio = parameters.AspectRatio;
+ }
+
+ public static ImageBinary From(ImageBinaryDto dto)
+ {
+ var buffer = Convert.FromBase64String(dto.Base64);
+ var extension = GetExtensionType(dto.Mime);
+ return new ImageBinary(new ImageBinaryParams(buffer, dto.Size, extension, dto.AspectRatio));
+ }
+
+ private static string GetExtensionType(string mime)
+ {
+ return MimeTypeExtensions.GetExtension(mime);
+ }
+}
+
+///
+/// DTO for ImageBinary serialization
+///
+/// Base64 encoded binary data
+/// Size of the original data
+/// MIME type of the media
+/// The aspect ratio of the image
+public record ImageBinaryDto(string Base64, int Size, string Mime, double AspectRatio)
+ : MediaBinaryDto(Base64, Size, Mime)
+{
+ public ImageBinaryDto(ImageBinary imageBinary) : this(
+ Convert.ToBase64String(imageBinary.Buffer),
+ imageBinary.Size,
+ MimeTypeExtensions.GetMimeType(imageBinary.Extension),
+ imageBinary.AspectRatio) { }
+}
+
+///
+/// Utility class for MIME type and extension conversions
+///
+public static class MimeTypeExtensions
+{
+ private static readonly Dictionary MimeTypes = new()
+ {
+ { ".jpg", "image/jpeg" },
+ { ".jpeg", "image/jpeg" },
+ { ".png", "image/png" },
+ { ".gif", "image/gif" },
+ { ".webp", "image/webp" },
+ { ".svg", "image/svg+xml" },
+ { ".bmp", "image/bmp" }
+ };
+
+ private static readonly Dictionary Extensions =
+ MimeTypes.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
+
+ public static string GetMimeType(string extension)
+ {
+ return MimeTypes.TryGetValue(extension.ToLowerInvariant(), out var mime)
+ ? mime
+ : "application/octet-stream";
+ }
+
+ public static string GetExtension(string mime)
+ {
+ return Extensions.TryGetValue(mime.ToLowerInvariant(), out var extension)
+ ? extension
+ : ".bin";
+ }
+}
diff --git a/DeepDrftContent/FileDatabase/Models/MediaVaultType.cs b/DeepDrftContent/FileDatabase/Models/MediaVaultType.cs
new file mode 100644
index 0000000..3ed2263
--- /dev/null
+++ b/DeepDrftContent/FileDatabase/Models/MediaVaultType.cs
@@ -0,0 +1,10 @@
+namespace DeepDrftContent.FileDatabase.Models;
+
+///
+/// Enum representing different types of media vaults
+///
+public enum MediaVaultType
+{
+ Media,
+ Image
+}
diff --git a/DeepDrftContent/FileDatabase/Models/MetaData.cs b/DeepDrftContent/FileDatabase/Models/MetaData.cs
new file mode 100644
index 0000000..12d84d5
--- /dev/null
+++ b/DeepDrftContent/FileDatabase/Models/MetaData.cs
@@ -0,0 +1,17 @@
+namespace DeepDrftContent.FileDatabase.Models;
+
+///
+/// Base metadata for media entries
+///
+/// The key used to identify the media file
+/// The file extension of the media
+public record MetaData(string MediaKey, string Extension);
+
+///
+/// Extended metadata for image entries, including aspect ratio
+///
+/// The key used to identify the media file
+/// The file extension of the media
+/// The aspect ratio of the image
+public record ImageMetaData(string MediaKey, string Extension, double AspectRatio)
+ : MetaData(MediaKey, Extension);
diff --git a/DeepDrftContent/FileDatabase/README.md b/DeepDrftContent/FileDatabase/README.md
new file mode 100644
index 0000000..26ca9b2
--- /dev/null
+++ b/DeepDrftContent/FileDatabase/README.md
@@ -0,0 +1,132 @@
+# FileDatabase C# Port
+
+This is a C# port of the TypeScript file database system, maintaining architectural integrity while leveraging C# language features.
+
+## Architecture Overview
+
+The C# port preserves the original three-layer architecture:
+
+### 1. **FileDatabase** (Main Orchestrator)
+- **Location**: `Services/FileDatabase.cs`
+- **Purpose**: Root-level manager coordinating multiple media vaults
+- **Key Features**:
+ - Manages collection of `MediaVault` instances using `StructuralMap`
+ - Provides async factory method `FromAsync()` for initialization
+ - Handles vault creation, resource loading, and resource registration
+
+### 2. **MediaVault System** (Storage Containers)
+- **Location**: `Services/MediaVault.cs`
+- **Components**:
+ - `MediaVault` (Abstract base class)
+ - `ImageDirectoryVault` (Concrete implementation for images)
+- **Key Features**:
+ - File path normalization and media key generation
+ - Generic type-safe operations using `MediaVaultType` enum
+ - Metadata association with stored files
+ - Async factory pattern for initialization
+
+### 3. **Index Management** (Metadata & Organization)
+- **Location**: `Services/IndexSystem.cs`
+- **Two-tier indexing**:
+ - `DirectoryIndex`: Manages vault entries (what vaults exist)
+ - `VaultIndex`: Manages media entries within vaults (what files exist + metadata)
+- **Features**:
+ - `IndexFactory` handles index creation/loading
+ - Automatic JSON serialization to filesystem
+ - Strong typing with generic constraints
+
+## Key Components
+
+### Models (`Models/` directory)
+- **`EntryKey`**: Composite key structure with string key + MediaVaultType
+- **`MetaData` hierarchy**: Base metadata → `ImageMetaData` with aspect ratio
+- **Media Types**: `FileBinary` → `MediaBinary` → `ImageBinary`
+- **Factory Classes**: Type-safe creation of media objects and metadata
+
+### Utilities (`Utils/` directory)
+- **`StructuralMap`**: JSON-based structural equality for complex keys
+- **`StructuralSet`**: Set with structural equality semantics
+- **`FileUtils`**: Async file I/O operations with chunked reading/writing
+
+## C# Design Improvements
+
+### SOLID Principles Applied
+1. **Single Responsibility**: Each class handles one concern
+2. **Open/Closed**: Extensible media types via generic constraints
+3. **Liskov Substitution**: Proper inheritance hierarchies
+4. **Interface Segregation**: Focused interfaces (`IIndex`)
+5. **Dependency Inversion**: Abstract base classes and interfaces
+
+### C# Language Features
+- **Records**: Immutable data structures for `EntryKey`, `MetaData`
+- **Pattern Matching**: Switch expressions for type-safe factory methods
+- **Nullable Reference Types**: Explicit nullability handling
+- **Async/Await**: Full async support with `Task` and `ValueTask`
+- **Generic Constraints**: Strong typing with `where` clauses
+
+### DRY Implementation
+- **Factory Pattern**: Centralized object creation logic
+- **Generic Type Maps**: Reusable type mappings for different media types
+- **Template Method Pattern**: Common functionality in base classes
+
+## Usage Example
+
+```csharp
+// Initialize the database
+var database = await FileDatabase.FromAsync("/path/to/database");
+
+// Create a vault
+var vaultKey = new EntryKey("images", MediaVaultType.Image);
+await database.CreateVaultAsync(vaultKey);
+
+// Store an image
+var entryKey = new EntryKey("photo1", MediaVaultType.Image);
+var imageData = new ImageBinary(new ImageBinaryParams(buffer, size, ".jpg", 1.5));
+await database.RegisterResourceAsync(MediaVaultType.Image, vaultKey, entryKey, imageData);
+
+// Load an image
+var loadedImage = await database.LoadResourceAsync(
+ MediaVaultType.Image, vaultKey, entryKey);
+```
+
+## Project Structure
+
+```
+FileDatabase/
+├── Models/
+│ ├── EntryKey.cs # Composite key structure
+│ ├── MetaData.cs # Metadata hierarchy
+│ ├── MediaModels.cs # Binary data classes
+│ ├── MediaFactories.cs # Factory pattern implementations
+│ ├── MediaVaultType.cs # Enum for vault types
+│ ├── IIndex.cs # Index interface
+│ └── IndexData.cs # Index implementations
+├── Services/
+│ ├── FileDatabase.cs # Main orchestrator
+│ ├── MediaVault.cs # Vault system
+│ └── IndexSystem.cs # Index management
+├── Utils/
+│ ├── StructuralMap.cs # Structural equality map
+│ ├── StructuralSet.cs # Structural equality set
+│ └── FileUtils.cs # File I/O utilities
+└── FileDatabase.csproj # Project file (.NET 9.0)
+```
+
+## Key Architectural Decisions
+
+1. **Async-First Design**: All I/O operations are asynchronous
+2. **Strong Type Safety**: Extensive use of generics and constraints
+3. **Structural Equality**: JSON-based equality for composite keys
+4. **Separation of Concerns**: Clear boundaries between indexing, storage, and media handling
+5. **Factory-Based Initialization**: Handles complex async setup patterns
+6. **Metadata-Driven**: Rich metadata system supporting extensible media types
+
+## Differences from TypeScript Version
+
+1. **Explicit Type Safety**: C# compiler enforces type constraints at compile time
+2. **Memory Management**: Automatic garbage collection vs manual buffer management
+3. **Serialization**: System.Text.Json instead of V8 serialization
+4. **Error Handling**: Exceptions vs try/catch patterns (maintained original behavior)
+5. **Nullability**: Explicit nullable reference types for better null safety
+
+This port maintains the architectural integrity of the original TypeScript design while leveraging C#'s type system and language features for improved maintainability and performance.
diff --git a/DeepDrftContent/FileDatabase/Services/FileDatabase.cs b/DeepDrftContent/FileDatabase/Services/FileDatabase.cs
new file mode 100644
index 0000000..09613d3
--- /dev/null
+++ b/DeepDrftContent/FileDatabase/Services/FileDatabase.cs
@@ -0,0 +1,159 @@
+using DeepDrftContent.FileDatabase.Models;
+using DeepDrftContent.FileDatabase.Utils;
+
+namespace DeepDrftContent.FileDatabase.Services;
+
+///
+/// Main file database class that orchestrates multiple media vaults
+///
+public class FileDatabase : DirectoryIndexDirectory
+{
+ private readonly StructuralMap _vaults;
+
+ ///
+ /// Factory method to create a FileDatabase instance
+ ///
+ public static async Task FromAsync(string rootPath)
+ {
+ var factory = new IndexFactory(rootPath, IndexType.Directory);
+ var rootIndex = await factory.BuildIndexAsync();
+
+ if (rootIndex is DirectoryIndex directoryIndex)
+ {
+ var db = new FileDatabase(rootPath, directoryIndex);
+ await db.InitVaultsAsync();
+ return db;
+ }
+
+ return null;
+ }
+
+ private FileDatabase(string rootPath, DirectoryIndex index) : base(rootPath, index)
+ {
+ _vaults = new StructuralMap();
+ }
+
+ ///
+ /// Initializes all vaults found in the index
+ ///
+ private async Task InitVaultsAsync()
+ {
+ foreach (var vaultKey in GetIndexEntries())
+ {
+ await InitVaultAsync(vaultKey);
+ }
+ }
+
+ ///
+ /// Initializes a specific vault
+ ///
+ private async Task InitVaultAsync(EntryKey vaultKey)
+ {
+ var path = Path.Combine(RootPath, vaultKey.Key);
+ var directoryVault = await ImageDirectoryVault.FromAsync(path);
+
+ if (directoryVault != null)
+ {
+ _vaults.Set(vaultKey, directoryVault);
+ }
+ }
+
+ ///
+ /// Checks if a vault exists for the given key
+ ///
+ public bool HasVault(EntryKey vaultKey)
+ {
+ return _vaults.Has(vaultKey);
+ }
+
+ ///
+ /// Gets a vault by key
+ ///
+ public MediaVault? GetVault(EntryKey vaultKey)
+ {
+ return HasVault(vaultKey) ? _vaults.Get(vaultKey) : null;
+ }
+
+ ///
+ /// Creates a new vault
+ ///
+ public async Task CreateVaultAsync(EntryKey vaultKey)
+ {
+ try
+ {
+ var path = Path.Combine(RootPath, vaultKey.Key);
+ var directoryVault = await ImageDirectoryVault.FromAsync(path);
+
+ if (directoryVault != null)
+ {
+ _vaults.Set(vaultKey, directoryVault);
+ await AddToIndexAsync(vaultKey);
+ }
+ }
+ catch
+ {
+ // Re-throw to maintain the same error behavior as TypeScript version
+ throw;
+ }
+ }
+
+ ///
+ /// Loads a resource from a specific vault
+ ///
+ public async Task LoadResourceAsync(MediaVaultType vaultType, EntryKey vaultKey, EntryKey entryKey)
+ where T : FileBinary
+ {
+ try
+ {
+ var vault = _vaults.Get(vaultKey);
+ if (vault != null)
+ {
+ return await vault.GetEntryAsync(vaultType, entryKey);
+ }
+ }
+ catch
+ {
+ // Swallow exceptions and return null, matching TypeScript behavior
+ }
+
+ return null;
+ }
+
+ ///
+ /// Registers a resource in a specific vault
+ ///
+ public async Task RegisterResourceAsync(MediaVaultType vaultType, EntryKey vaultKey, EntryKey entryKey, object media)
+ {
+ try
+ {
+ var directoryVault = _vaults.Get(vaultKey);
+ if (directoryVault != null)
+ {
+ await directoryVault.AddEntryAsync(vaultType, entryKey, media);
+ return true;
+ }
+ }
+ catch
+ {
+ // Swallow exceptions and return false, matching TypeScript behavior
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets all vault keys managed by this database
+ ///
+ public IReadOnlyList GetVaultKeys()
+ {
+ return _vaults.Keys.ToList().AsReadOnly();
+ }
+
+ ///
+ /// Gets the total number of vaults
+ ///
+ public int GetVaultCount()
+ {
+ return _vaults.Size;
+ }
+}
diff --git a/DeepDrftContent/FileDatabase/Services/IndexSystem.cs b/DeepDrftContent/FileDatabase/Services/IndexSystem.cs
new file mode 100644
index 0000000..76fc656
--- /dev/null
+++ b/DeepDrftContent/FileDatabase/Services/IndexSystem.cs
@@ -0,0 +1,162 @@
+using DeepDrftContent.FileDatabase.Models;
+using DeepDrftContent.FileDatabase.Utils;
+
+namespace DeepDrftContent.FileDatabase.Services;
+
+///
+/// Enum representing different types of indexes
+///
+public enum IndexType
+{
+ Directory,
+ Vault
+}
+
+///
+/// Abstract base class for index containers
+///
+public abstract class AbstractIndexContainer
+{
+ protected IndexType Type { get; }
+ public string RootPath { get; }
+
+ protected AbstractIndexContainer(string path, IndexType type)
+ {
+ RootPath = path;
+ Type = type;
+ }
+
+ public string GetKey() => Path.GetFileName(RootPath);
+
+ protected async Task SaveIndexAsync(T index) where T : IIndex
+ {
+ var indexPath = Path.Combine(RootPath, "index");
+
+ object indexData = Type switch
+ {
+ IndexType.Directory when index is DirectoryIndex dirIndex => DirectoryIndexData.FromIndex(dirIndex),
+ IndexType.Vault when index is VaultIndex vaultIndex => VaultIndexData.FromIndex(vaultIndex),
+ _ => throw new ArgumentException($"Invalid index type {Type} for index {typeof(T)}")
+ };
+
+ await FileUtils.PutObjectAsync(indexPath, indexData);
+ }
+}
+
+///
+/// Factory for creating and loading indexes
+///
+public class IndexFactory : AbstractIndexContainer
+{
+ public IndexFactory(string path, IndexType type) : base(path, type) { }
+
+ ///
+ /// Builds an index by loading existing or creating new
+ ///
+ public async Task BuildIndexAsync()
+ {
+ try
+ {
+ return await LoadOrCreateIndexAsync();
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ private async Task LoadOrCreateIndexAsync()
+ {
+ try
+ {
+ return await LoadIndexAsync();
+ }
+ catch
+ {
+ return await CreateIndexAsync();
+ }
+ }
+
+ private async Task LoadIndexAsync()
+ {
+ var indexPath = Path.Combine(RootPath, "index");
+
+ IIndex result = Type switch
+ {
+ IndexType.Directory => new DirectoryIndex(await FileUtils.FetchObjectAsync(indexPath)),
+ IndexType.Vault => new VaultIndex(await FileUtils.FetchObjectAsync(indexPath)),
+ _ => throw new ArgumentException($"Unknown index type: {Type}")
+ };
+ return result;
+ }
+
+ private async Task CreateIndexAsync()
+ {
+ IIndex index = Type switch
+ {
+ IndexType.Directory => new DirectoryIndex(new DirectoryIndexData(RootPath)),
+ IndexType.Vault => new VaultIndex(new VaultIndexData(RootPath)),
+ _ => throw new ArgumentException($"Unknown index type: {Type}")
+ };
+
+ await FileUtils.MakeVaultDirectoryAsync(RootPath);
+ await SaveIndexAsync(index);
+
+ return index;
+ }
+}
+
+///
+/// Abstract base class for directory containers that manage indexes
+///
+public abstract class IndexDirectory : AbstractIndexContainer
+{
+ protected IIndex Index { get; }
+
+ protected IndexDirectory(string rootPath, IndexType type, IIndex index) : base(rootPath, type)
+ {
+ Index = index;
+ }
+
+ protected IReadOnlyList GetIndexEntries() => Index.GetEntries();
+
+ public int GetIndexSize() => Index.GetEntriesSize();
+
+ public bool HasIndexEntry(EntryKey entryKey) => Index.HasEntry(entryKey);
+}
+
+///
+/// Directory index directory implementation
+///
+public class DirectoryIndexDirectory : IndexDirectory
+{
+ public DirectoryIndexDirectory(string rootPath, DirectoryIndex index)
+ : base(rootPath, IndexType.Directory, index) { }
+
+ protected async Task AddToIndexAsync(EntryKey entryKey)
+ {
+ if (Index is DirectoryIndex dirIndex)
+ {
+ dirIndex.PutEntry(entryKey);
+ await SaveIndexAsync(dirIndex);
+ }
+ }
+}
+
+///
+/// Vault index directory implementation
+///
+public class VaultIndexDirectory : IndexDirectory
+{
+ public VaultIndexDirectory(string rootPath, VaultIndex index)
+ : base(rootPath, IndexType.Vault, index) { }
+
+ protected async Task AddToIndexAsync(EntryKey entryKey, MetaData metaData)
+ {
+ if (Index is VaultIndex vaultIndex)
+ {
+ vaultIndex.PutEntry(entryKey, metaData);
+ await SaveIndexAsync(vaultIndex);
+ }
+ }
+}
diff --git a/DeepDrftContent/FileDatabase/Services/MediaVault.cs b/DeepDrftContent/FileDatabase/Services/MediaVault.cs
new file mode 100644
index 0000000..54027c9
--- /dev/null
+++ b/DeepDrftContent/FileDatabase/Services/MediaVault.cs
@@ -0,0 +1,127 @@
+using System.Text.RegularExpressions;
+using DeepDrftContent.FileDatabase.Models;
+using DeepDrftContent.FileDatabase.Utils;
+
+namespace DeepDrftContent.FileDatabase.Services;
+
+///
+/// Abstract base class for media vaults that store and manage media files
+///
+public abstract class MediaVault : VaultIndexDirectory
+{
+ protected MediaVault(string rootPath, VaultIndex index) : base(rootPath, index) { }
+
+ ///
+ /// Generates a media key from an entry key by sanitizing special characters
+ ///
+ protected string GetMediaKey(string entryKey, string extension)
+ {
+ var sanitized = Regex.Replace(entryKey, @"[^a-zA-Z0-9]", "-");
+ return $"{sanitized}{extension}";
+ }
+
+ ///
+ /// Gets the full file path for a media file from an entry key
+ ///
+ protected string GetMediaPathFromEntryKey(string entryKey, string extension)
+ {
+ return Path.Combine(RootPath, GetMediaKey(entryKey, extension));
+ }
+
+ ///
+ /// Gets the full file path for a media file from a media key
+ ///
+ protected string GetMediaPathFromMediaKey(string mediaKey)
+ {
+ return Path.Combine(RootPath, mediaKey);
+ }
+
+ ///
+ /// Adds a new entry to the vault with the specified media data
+ ///
+ public async Task AddEntryAsync(MediaVaultType vaultType, EntryKey entryKey, object media)
+ {
+ // Extract properties from media object based on type
+ var (buffer, extension) = ExtractMediaProperties(media);
+
+ var mediaPath = GetMediaPathFromEntryKey(entryKey.Key, extension);
+ var metaData = MetaDataFactory.Create(vaultType, entryKey.Key, extension, GetAspectRatio(media));
+
+ await AddToIndexAsync(entryKey, metaData);
+ await FileUtils.PutFileAsync(mediaPath, buffer);
+ }
+
+ ///
+ /// Retrieves an entry from the vault
+ ///
+ public async Task GetEntryAsync(MediaVaultType vaultType, EntryKey entryKey) where T : FileBinary
+ {
+ if (!HasIndexEntry(entryKey))
+ return null;
+
+ if (Index is not VaultIndex vaultIndex)
+ return null;
+
+ var metaData = vaultIndex.GetEntry(entryKey);
+ if (metaData == null)
+ return null;
+
+ var mediaPath = GetMediaPathFromEntryKey(metaData.MediaKey, metaData.Extension);
+
+ if (!FileUtils.FileExists(mediaPath))
+ return null;
+
+ var fileBinary = await FileUtils.FetchFileAsync(mediaPath);
+ var parameters = MediaParamsFactory.Create(vaultType, fileBinary, metaData);
+
+ var result = FileBinaryFactory.Create(vaultType, parameters);
+ return (T)result;
+ }
+
+ ///
+ /// Extracts buffer and extension from a media object
+ ///
+ private static (byte[] buffer, string extension) ExtractMediaProperties(object media)
+ {
+ return media switch
+ {
+ ImageBinary imageBinary => (imageBinary.Buffer, imageBinary.Extension),
+ MediaBinary mediaBinary => (mediaBinary.Buffer, mediaBinary.Extension),
+ _ => throw new ArgumentException($"Unsupported media type: {media.GetType()}")
+ };
+ }
+
+ ///
+ /// Extracts aspect ratio from media object if it's an image
+ ///
+ private static double GetAspectRatio(object media)
+ {
+ return media is ImageBinary imageBinary ? imageBinary.AspectRatio : 1.0;
+ }
+}
+
+///
+/// Concrete implementation of MediaVault for image storage
+///
+public class ImageDirectoryVault : MediaVault
+{
+ private ImageDirectoryVault(string rootPath, VaultIndex index) : base(rootPath, index) { }
+
+ ///
+ /// Factory method to create an ImageDirectoryVault instance
+ ///
+ public static async Task FromAsync(string rootPath)
+ {
+ var factory = new IndexFactory(rootPath, IndexType.Vault);
+ var index = await factory.BuildIndexAsync();
+
+ if (index is VaultIndex vaultIndex)
+ {
+ return new ImageDirectoryVault(rootPath, vaultIndex);
+ }
+
+ return null;
+ }
+
+
+}
diff --git a/DeepDrftContent/FileDatabase/Utils/FileUtils.cs b/DeepDrftContent/FileDatabase/Utils/FileUtils.cs
new file mode 100644
index 0000000..dbbe6ce
--- /dev/null
+++ b/DeepDrftContent/FileDatabase/Utils/FileUtils.cs
@@ -0,0 +1,118 @@
+using System.Text.Json;
+using DeepDrftContent.FileDatabase.Models;
+
+namespace DeepDrftContent.FileDatabase.Utils;
+
+///
+/// Utility class for file I/O operations, matching the TypeScript file utilities
+///
+public static class FileUtils
+{
+ ///
+ /// Reads a file and returns it as a FileBinary object
+ ///
+ /// Path to the media file
+ /// FileBinary containing the file data
+ public static async Task FetchFileAsync(string mediaPath)
+ {
+ using var fileStream = new FileStream(mediaPath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 64 * 1024);
+
+ var buffer = new byte[fileStream.Length];
+ var totalBytesRead = 0;
+
+ while (totalBytesRead < fileStream.Length)
+ {
+ var bytesRead = await fileStream.ReadAsync(buffer.AsMemory(totalBytesRead));
+ if (bytesRead == 0)
+ throw new EndOfStreamException($"Unexpected end of stream while reading {mediaPath}");
+
+ totalBytesRead += bytesRead;
+ }
+
+ return new FileBinary(new FileBinaryParams(buffer, buffer.Length));
+ }
+
+ ///
+ /// Writes binary data to a file
+ ///
+ /// Path where to write the file
+ /// Binary data to write
+ public static async Task PutFileAsync(string mediaPath, byte[] buffer)
+ {
+ const int chunkSize = 64 * 1024;
+
+ using var fileStream = new FileStream(mediaPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: chunkSize);
+
+ for (int offset = 0; offset < buffer.Length; offset += chunkSize)
+ {
+ var length = Math.Min(chunkSize, buffer.Length - offset);
+ await fileStream.WriteAsync(buffer.AsMemory(offset, length));
+ }
+
+ await fileStream.FlushAsync();
+ }
+
+ ///
+ /// Serializes an object to a file using JSON
+ ///
+ /// Path to the file
+ /// Object to serialize
+ public static async Task PutObjectAsync(string filePath, T obj)
+ {
+ var options = new JsonSerializerOptions
+ {
+ WriteIndented = true,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ };
+
+ var json = JsonSerializer.Serialize(obj, options);
+ await File.WriteAllTextAsync(filePath, json);
+ }
+
+ ///
+ /// Deserializes an object from a JSON file
+ ///
+ /// Path to the file
+ /// Deserialized object
+ public static async Task FetchObjectAsync(string filePath)
+ {
+ var json = await File.ReadAllTextAsync(filePath);
+ var options = new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ };
+
+ var result = JsonSerializer.Deserialize(json, options);
+ return result ?? throw new InvalidOperationException($"Failed to deserialize object from {filePath}");
+ }
+
+ ///
+ /// Creates a directory if it doesn't exist
+ ///
+ /// Path to the directory
+ public static Task MakeVaultDirectoryAsync(string directoryPath)
+ {
+ Directory.CreateDirectory(directoryPath);
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// Checks if a file exists
+ ///
+ /// Path to check
+ /// True if file exists
+ public static bool FileExists(string filePath)
+ {
+ return File.Exists(filePath);
+ }
+
+ ///
+ /// Checks if a directory exists
+ ///
+ /// Path to check
+ /// True if directory exists
+ public static bool DirectoryExists(string directoryPath)
+ {
+ return Directory.Exists(directoryPath);
+ }
+}
diff --git a/DeepDrftContent/FileDatabase/Utils/StructuralMap.cs b/DeepDrftContent/FileDatabase/Utils/StructuralMap.cs
new file mode 100644
index 0000000..67ab43f
--- /dev/null
+++ b/DeepDrftContent/FileDatabase/Utils/StructuralMap.cs
@@ -0,0 +1,107 @@
+using System.Collections;
+using System.Text.Json;
+
+namespace DeepDrftContent.FileDatabase.Utils;
+
+///
+/// A map implementation that uses structural equality for keys by serializing them to JSON.
+/// This provides the same behavior as the TypeScript StructuralMap.
+///
+/// The key type
+/// The value type
+public class StructuralMap : IEnumerable>
+{
+ private readonly Dictionary> _innerMap = new();
+
+ ///
+ /// Converts a key to its string representation for structural comparison
+ ///
+ private string GetKeyString(TKey key)
+ {
+ return key switch
+ {
+ null => "null",
+ string s => s,
+ int or long or float or double or decimal => key.ToString()!,
+ _ => JsonSerializer.Serialize(key)
+ };
+ }
+
+ ///
+ /// Sets a key-value pair in the map
+ ///
+ public StructuralMap Set(TKey key, TValue value)
+ {
+ var keyString = GetKeyString(key);
+ _innerMap[keyString] = new KeyValuePair(key, value);
+ return this;
+ }
+
+ ///
+ /// Gets a value by key, or default if not found
+ ///
+ public TValue? Get(TKey key)
+ {
+ var keyString = GetKeyString(key);
+ return _innerMap.TryGetValue(keyString, out var pair) ? pair.Value : default;
+ }
+
+ ///
+ /// Checks if the map contains the specified key
+ ///
+ public bool Has(TKey key)
+ {
+ var keyString = GetKeyString(key);
+ return _innerMap.ContainsKey(keyString);
+ }
+
+ ///
+ /// Removes a key-value pair from the map
+ ///
+ public bool Delete(TKey key)
+ {
+ var keyString = GetKeyString(key);
+ return _innerMap.Remove(keyString);
+ }
+
+ ///
+ /// Clears all entries from the map
+ ///
+ public void Clear() => _innerMap.Clear();
+
+ ///
+ /// Gets the number of entries in the map
+ ///
+ public int Size => _innerMap.Count;
+
+ ///
+ /// Enumerates all key-value pairs
+ ///
+ public IEnumerator> GetEnumerator()
+ {
+ return _innerMap.Values.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ ///
+ /// Gets all keys in the map
+ ///
+ public IEnumerable Keys => _innerMap.Values.Select(pair => pair.Key);
+
+ ///
+ /// Gets all values in the map
+ ///
+ public IEnumerable Values => _innerMap.Values.Select(pair => pair.Value);
+
+ ///
+ /// Executes a callback for each key-value pair
+ ///
+ public void ForEach(Action> callback)
+ {
+ foreach (var (key, value) in this)
+ {
+ callback(value, key, this);
+ }
+ }
+}
diff --git a/DeepDrftContent/FileDatabase/Utils/StructuralSet.cs b/DeepDrftContent/FileDatabase/Utils/StructuralSet.cs
new file mode 100644
index 0000000..4bdc000
--- /dev/null
+++ b/DeepDrftContent/FileDatabase/Utils/StructuralSet.cs
@@ -0,0 +1,84 @@
+using System.Collections;
+using System.Text.Json;
+
+namespace DeepDrftContent.FileDatabase.Utils;
+
+///
+/// A set implementation that uses structural equality for values by serializing them to JSON.
+/// This provides the same behavior as the TypeScript StructuralSet.
+///
+/// The value type
+public class StructuralSet : IEnumerable
+{
+ private readonly Dictionary _innerMap = new();
+
+ ///
+ /// Converts a value to its string representation for structural comparison
+ ///
+ private string GetValueString(T value)
+ {
+ return value switch
+ {
+ null => "null",
+ string s => s,
+ int or long or float or double or decimal => value.ToString()!,
+ _ => JsonSerializer.Serialize(value)
+ };
+ }
+
+ ///
+ /// Adds a value to the set
+ ///
+ public StructuralSet Add(T value)
+ {
+ var valueString = GetValueString(value);
+ if (!_innerMap.ContainsKey(valueString))
+ {
+ _innerMap[valueString] = value;
+ }
+ return this;
+ }
+
+ ///
+ /// Checks if the set contains the specified value
+ ///
+ public bool Has(T value)
+ {
+ var valueString = GetValueString(value);
+ return _innerMap.ContainsKey(valueString);
+ }
+
+ ///
+ /// Removes a value from the set
+ ///
+ public bool Delete(T value)
+ {
+ var valueString = GetValueString(value);
+ return _innerMap.Remove(valueString);
+ }
+
+ ///
+ /// Clears all values from the set
+ ///
+ public void Clear() => _innerMap.Clear();
+
+ ///
+ /// Gets the number of values in the set
+ ///
+ public int Size => _innerMap.Count;
+
+ ///
+ /// Enumerates all values in the set
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ return _innerMap.Values.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ ///
+ /// Gets all values in the set
+ ///
+ public IEnumerable Values => _innerMap.Values;
+}