using System.Security.Cryptography; using Microsoft.Data.Sqlite; namespace ytLive.Services; public partial class LayoutStore : IDisposable { public byte[]? GetAssetBytes(string assetId) { using var cmd = _connection.CreateCommand(); cmd.CommandText = "SELECT Data FROM Asset WHERE Id = $id"; cmd.Parameters.AddWithValue("$id", assetId); return cmd.ExecuteScalar() as byte[]; } public string UpsertAsset(byte[] data, int pixelWidth, int pixelHeight) { var hash = Convert.ToHexString(SHA256.HashData(data)); var id = Guid.NewGuid().ToString(); using var cmd = _connection.CreateCommand(); cmd.CommandText = """ INSERT INTO Asset (Id, Hash, Data, PixelWidth, PixelHeight) VALUES ($id, $hash, $data, $w, $h) ON CONFLICT(Hash) DO NOTHING; """; cmd.Parameters.AddWithValue("$id", id); cmd.Parameters.AddWithValue("$hash", hash); cmd.Parameters.AddWithValue("$data", data); cmd.Parameters.AddWithValue("$w", pixelWidth); cmd.Parameters.AddWithValue("$h", pixelHeight); cmd.ExecuteNonQuery(); cmd.CommandText = "SELECT Id FROM Asset WHERE Hash = $hash;"; return (string)cmd.ExecuteScalar()!; } /// True when the asset already holds exactly these bytes. Used to decide whether a /// bundled image needs re-seeding: comparing bytes cannot go stale the way a version constant /// can (the constant and the embedded resource were built at different moments twice). public bool AssetMatches(string assetId, byte[] data) { if (string.IsNullOrWhiteSpace(assetId)) return false; var hash = Convert.ToHexString(SHA256.HashData(data)); using var cmd = _connection.CreateCommand(); cmd.CommandText = "SELECT 1 FROM Asset WHERE Id = $id AND Hash = $hash;"; cmd.Parameters.AddWithValue("$id", assetId); cmd.Parameters.AddWithValue("$hash", hash); return cmd.ExecuteScalar() != null; } // ─── License (TASK 10 — Polar billing) ─── /// The saved license key, or null if never entered. }