Remove obsolete rule formats, batch SQLite work, preserve artwork source URLs, and coordinate refresh workers with maintenance. Add regression coverage and document backup format changes.
126 lines
5.8 KiB
C#
126 lines
5.8 KiB
C#
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<SqliteTransaction> 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<string, FetchState> 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<string, FetchState>(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<StoredAsset> 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<StoredAsset>();
|
|
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<StoredAsset> assets, IReadOnlyDictionary<string, FetchState> 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);
|
|
});
|
|
}
|