The creator's profile still showed the previous Starting art after the replacement, with `Art.DefaultVersion = 4` recorded — so the version gate had already declared itself done. It compared a constant against the embedded resource, and those two were compiled at different moments (version 4 landed with the OLD image embedded; the corrected image reached the assembly one build later). A version bump can always be raced that way. The seed now compares BYTES: `LayoutStore.AssetMatches` hashes the bundled resource and checks the row the background already points at. Same bytes → leave it untouched (so this is idempotent and never churns the database). Different bytes → re-stamp. The version constant and its setting are gone, because a heal that only runs once can silently skip the one launch it was needed for. `SceneThumbnailTests` planted its own art on a background still flagged as default art — in the real app that state only ever means stale bundled art, because Browse… sets the path and clears the flag. The fixture now models a creator's own picture. Tests: 572 green, 0 warnings. Verified the profile's Starting asset hash (167CDD3B…, the two-category render) differs from the shipped `starting-background.jpg` (5FC6DD22…), so the next launch re-seeds it.
56 lines
2.2 KiB
C#
56 lines
2.2 KiB
C#
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()!;
|
|
}
|
|
|
|
/// <summary>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).</summary>
|
|
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) ───
|
|
|
|
/// <summary>The saved license key, or null if never entered.</summary>
|
|
}
|