Release 0.2.5: harden proxy and streamline metadata fetching
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.
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user