FileDatabase engine port from snailbird-content TS/Node program

This commit is contained in:
2025-09-01 16:55:28 -04:00
parent f0d60190cc
commit 9124e82e24
15 changed files with 1395 additions and 0 deletions
@@ -0,0 +1,118 @@
using System.Text.Json;
using DeepDrftContent.FileDatabase.Models;
namespace DeepDrftContent.FileDatabase.Utils;
/// <summary>
/// Utility class for file I/O operations, matching the TypeScript file utilities
/// </summary>
public static class FileUtils
{
/// <summary>
/// Reads a file and returns it as a FileBinary object
/// </summary>
/// <param name="mediaPath">Path to the media file</param>
/// <returns>FileBinary containing the file data</returns>
public static async Task<FileBinary> 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));
}
/// <summary>
/// Writes binary data to a file
/// </summary>
/// <param name="mediaPath">Path where to write the file</param>
/// <param name="buffer">Binary data to write</param>
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();
}
/// <summary>
/// Serializes an object to a file using JSON
/// </summary>
/// <param name="filePath">Path to the file</param>
/// <param name="obj">Object to serialize</param>
public static async Task PutObjectAsync<T>(string filePath, T obj)
{
var options = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var json = JsonSerializer.Serialize(obj, options);
await File.WriteAllTextAsync(filePath, json);
}
/// <summary>
/// Deserializes an object from a JSON file
/// </summary>
/// <param name="filePath">Path to the file</param>
/// <returns>Deserialized object</returns>
public static async Task<T> FetchObjectAsync<T>(string filePath)
{
var json = await File.ReadAllTextAsync(filePath);
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var result = JsonSerializer.Deserialize<T>(json, options);
return result ?? throw new InvalidOperationException($"Failed to deserialize object from {filePath}");
}
/// <summary>
/// Creates a directory if it doesn't exist
/// </summary>
/// <param name="directoryPath">Path to the directory</param>
public static Task MakeVaultDirectoryAsync(string directoryPath)
{
Directory.CreateDirectory(directoryPath);
return Task.CompletedTask;
}
/// <summary>
/// Checks if a file exists
/// </summary>
/// <param name="filePath">Path to check</param>
/// <returns>True if file exists</returns>
public static bool FileExists(string filePath)
{
return File.Exists(filePath);
}
/// <summary>
/// Checks if a directory exists
/// </summary>
/// <param name="directoryPath">Path to check</param>
/// <returns>True if directory exists</returns>
public static bool DirectoryExists(string directoryPath)
{
return Directory.Exists(directoryPath);
}
}
@@ -0,0 +1,107 @@
using System.Collections;
using System.Text.Json;
namespace DeepDrftContent.FileDatabase.Utils;
/// <summary>
/// A map implementation that uses structural equality for keys by serializing them to JSON.
/// This provides the same behavior as the TypeScript StructuralMap.
/// </summary>
/// <typeparam name="TKey">The key type</typeparam>
/// <typeparam name="TValue">The value type</typeparam>
public class StructuralMap<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
{
private readonly Dictionary<string, KeyValuePair<TKey, TValue>> _innerMap = new();
/// <summary>
/// Converts a key to its string representation for structural comparison
/// </summary>
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)
};
}
/// <summary>
/// Sets a key-value pair in the map
/// </summary>
public StructuralMap<TKey, TValue> Set(TKey key, TValue value)
{
var keyString = GetKeyString(key);
_innerMap[keyString] = new KeyValuePair<TKey, TValue>(key, value);
return this;
}
/// <summary>
/// Gets a value by key, or default if not found
/// </summary>
public TValue? Get(TKey key)
{
var keyString = GetKeyString(key);
return _innerMap.TryGetValue(keyString, out var pair) ? pair.Value : default;
}
/// <summary>
/// Checks if the map contains the specified key
/// </summary>
public bool Has(TKey key)
{
var keyString = GetKeyString(key);
return _innerMap.ContainsKey(keyString);
}
/// <summary>
/// Removes a key-value pair from the map
/// </summary>
public bool Delete(TKey key)
{
var keyString = GetKeyString(key);
return _innerMap.Remove(keyString);
}
/// <summary>
/// Clears all entries from the map
/// </summary>
public void Clear() => _innerMap.Clear();
/// <summary>
/// Gets the number of entries in the map
/// </summary>
public int Size => _innerMap.Count;
/// <summary>
/// Enumerates all key-value pairs
/// </summary>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return _innerMap.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Gets all keys in the map
/// </summary>
public IEnumerable<TKey> Keys => _innerMap.Values.Select(pair => pair.Key);
/// <summary>
/// Gets all values in the map
/// </summary>
public IEnumerable<TValue> Values => _innerMap.Values.Select(pair => pair.Value);
/// <summary>
/// Executes a callback for each key-value pair
/// </summary>
public void ForEach(Action<TValue, TKey, StructuralMap<TKey, TValue>> callback)
{
foreach (var (key, value) in this)
{
callback(value, key, this);
}
}
}
@@ -0,0 +1,84 @@
using System.Collections;
using System.Text.Json;
namespace DeepDrftContent.FileDatabase.Utils;
/// <summary>
/// A set implementation that uses structural equality for values by serializing them to JSON.
/// This provides the same behavior as the TypeScript StructuralSet.
/// </summary>
/// <typeparam name="T">The value type</typeparam>
public class StructuralSet<T> : IEnumerable<T>
{
private readonly Dictionary<string, T> _innerMap = new();
/// <summary>
/// Converts a value to its string representation for structural comparison
/// </summary>
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)
};
}
/// <summary>
/// Adds a value to the set
/// </summary>
public StructuralSet<T> Add(T value)
{
var valueString = GetValueString(value);
if (!_innerMap.ContainsKey(valueString))
{
_innerMap[valueString] = value;
}
return this;
}
/// <summary>
/// Checks if the set contains the specified value
/// </summary>
public bool Has(T value)
{
var valueString = GetValueString(value);
return _innerMap.ContainsKey(valueString);
}
/// <summary>
/// Removes a value from the set
/// </summary>
public bool Delete(T value)
{
var valueString = GetValueString(value);
return _innerMap.Remove(valueString);
}
/// <summary>
/// Clears all values from the set
/// </summary>
public void Clear() => _innerMap.Clear();
/// <summary>
/// Gets the number of values in the set
/// </summary>
public int Size => _innerMap.Count;
/// <summary>
/// Enumerates all values in the set
/// </summary>
public IEnumerator<T> GetEnumerator()
{
return _innerMap.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Gets all values in the set
/// </summary>
public IEnumerable<T> Values => _innerMap.Values;
}