using Jellyfin.Plugin.Multilang.Services.Providers; using Jellyfin.Plugin.Multilang.Services.Refresh; using Microsoft.Data.Sqlite; using static Jellyfin.Plugin.Multilang.MultilangConstants; namespace Jellyfin.Plugin.Multilang.Data; public sealed record FetchState(string Scope, long CheckedAt, bool Complete); public sealed record StoredAsset(string Language, string Kind, string Path, string SourceUrl, string Provider); public sealed partial class TranslationStore { private void Write(SqliteTransaction? transaction, Action action) { if (transaction is not null) { action(transaction); return; } using var connection = Open(); connection.Open(); using var tx = connection.BeginTransaction(); action(tx); tx.Commit(); } public Dictionary GetFetchStates(string itemId) { using var connection = Open(); connection.Open(); using var cmd = connection.CreateCommand(); cmd.CommandText = "SELECT source, scope, checked_at, complete FROM fetch_state WHERE item_id = $id;"; cmd.Parameters.AddWithValue("$id", itemId); using var reader = cmd.ExecuteReader(); var result = new Dictionary(StringComparer.OrdinalIgnoreCase); while (reader.Read()) result.Add(reader.GetString(0), new(reader.GetString(1), reader.GetInt64(2), reader.GetBoolean(3))); return result; } private static void SaveFetchState(SqliteTransaction tx, string itemId, string source, FetchState state) { using var cmd = tx.Connection!.CreateCommand(); cmd.Transaction = tx; cmd.CommandText = "INSERT OR REPLACE INTO fetch_state(item_id, source, scope, checked_at, complete) VALUES($id, $source, $scope, $time, $complete);"; cmd.Parameters.AddWithValue("$id", itemId); cmd.Parameters.AddWithValue("$source", source); cmd.Parameters.AddWithValue("$scope", state.Scope); cmd.Parameters.AddWithValue("$time", state.CheckedAt); cmd.Parameters.AddWithValue("$complete", state.Complete); cmd.ExecuteNonQuery(); } public void SaveMetadata(RefreshItemInfo item, string language, TmdbMetadata? metadata, FetchState state, bool full, bool updateFacts = true) => Write(null, tx => { if (metadata is not null) { if (updateFacts) UpsertFacts(item, metadata, state.CheckedAt, full ? state.CheckedAt : 0, tx); UpsertTranslation(item.ItemId, language, TitleField, metadata.Title, tx); UpsertTranslation(item.ItemId, language, OverviewField, metadata.Overview, tx); UpsertTranslation(item.ItemId, language, TaglineField, metadata.Tagline, tx); foreach (var genre in metadata.Genres) UpsertGenre(genre.Id, item.Kind is "movie" or "collection" ? "movie" : "tv", language, genre.Name, tx); } else MarkFactsChecked(item, state.CheckedAt, full ? state.CheckedAt : 0, tx); SaveFetchState(tx, item.ItemId, "metadata:" + language, state); }); public void CopyOriginalTranslations(string itemId, string language) => Write(null, tx => { using var cmd = tx.Connection!.CreateCommand(); cmd.Transaction = tx; cmd.CommandText = @"DELETE FROM translations WHERE item_id = $id AND lang = 'Original'; INSERT INTO translations(item_id, lang, field, text) SELECT item_id, 'Original', field, text FROM translations WHERE item_id = $id AND lang = $lang;"; cmd.Parameters.AddWithValue("$id", itemId); cmd.Parameters.AddWithValue("$lang", language); cmd.ExecuteNonQuery(); }); public List GetStoredAssets(string itemId) { using var connection = Open(); connection.Open(); using var cmd = connection.CreateCommand(); cmd.CommandText = "SELECT lang, kind, path, source_url, provider FROM assets WHERE item_id = $id;"; cmd.Parameters.AddWithValue("$id", itemId); using var reader = cmd.ExecuteReader(); var result = new List(); while (reader.Read()) result.Add(new(reader.GetString(0), reader.GetString(1), reader.GetString(2), reader.GetString(3), reader.GetString(4))); return result; } public void SaveArtwork(string itemId, IEnumerable assets, IReadOnlyDictionary states) => Write(null, tx => { using var cmd = tx.Connection!.CreateCommand(); cmd.Transaction = tx; cmd.CommandText = "DELETE FROM assets WHERE item_id = $id;"; cmd.Parameters.AddWithValue("$id", itemId); cmd.ExecuteNonQuery(); cmd.CommandText = @"INSERT INTO assets(item_id, lang, kind, path, path_low, source_url, provider, updated_at) VALUES($id, $lang, $kind, $path, $low, $url, $provider, $time);"; foreach (var name in new[] { "$lang", "$kind", "$path", "$low", "$url", "$provider", "$time" }) cmd.Parameters.Add(new SqliteParameter(name, DBNull.Value)); foreach (var asset in assets) { cmd.Parameters["$lang"].Value = asset.Language; cmd.Parameters["$kind"].Value = asset.Kind; cmd.Parameters["$path"].Value = asset.Path; cmd.Parameters["$low"].Value = asset.Path.ToLowerInvariant(); cmd.Parameters["$url"].Value = asset.SourceUrl; cmd.Parameters["$provider"].Value = asset.Provider; cmd.Parameters["$time"].Value = NowUnixUtc(); cmd.ExecuteNonQuery(); } foreach (var (provider, state) in states) SaveFetchState(tx, itemId, provider, state); }); }