mirror of
https://github.com/Felitendo/jellyfin-plugin-lyrics.git
synced 2026-11-01 17:23:25 +01:00
141 lines
4.5 KiB
C#
141 lines
4.5 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Jellyfin.Plugin.Lyrics.Models;
|
|
using MediaBrowser.Common.Configuration;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Jellyfin.Plugin.Lyrics;
|
|
|
|
/// <summary>
|
|
/// Persists retry and cursor state for the lyric download task.
|
|
/// </summary>
|
|
public class RetryStateStore
|
|
{
|
|
private static readonly JsonSerializerOptions SerializerOptions = new()
|
|
{
|
|
WriteIndented = false,
|
|
Converters = { new JsonStringEnumConverter() }
|
|
};
|
|
|
|
private readonly ILogger<RetryStateStore> _logger;
|
|
private readonly string _stateFilePath;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="RetryStateStore"/> class.
|
|
/// </summary>
|
|
/// <param name="applicationPaths">Application paths.</param>
|
|
/// <param name="logger">Logger.</param>
|
|
public RetryStateStore(IApplicationPaths applicationPaths, ILogger<RetryStateStore> logger)
|
|
{
|
|
_logger = logger;
|
|
|
|
var stateDir = Path.Combine(ResolveBasePath(applicationPaths), "plugins", "lyrics");
|
|
Directory.CreateDirectory(stateDir);
|
|
_stateFilePath = Path.Combine(stateDir, "retry-state.json");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads state from disk.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
/// <returns>Loaded state or empty default state.</returns>
|
|
public async Task<LyricsRetryState> LoadAsync(CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
if (!File.Exists(_stateFilePath))
|
|
{
|
|
return new LyricsRetryState();
|
|
}
|
|
|
|
using var stream = File.OpenRead(_stateFilePath);
|
|
var state = await JsonSerializer.DeserializeAsync<LyricsRetryState>(stream, SerializerOptions, cancellationToken).ConfigureAwait(false);
|
|
return state ?? new LyricsRetryState();
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to load lyrics retry state from {Path}. Using empty state.", _stateFilePath);
|
|
return new LyricsRetryState();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves state to disk.
|
|
/// </summary>
|
|
/// <param name="state">State to persist.</param>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
/// <returns>Task completion.</returns>
|
|
public async Task SaveAsync(LyricsRetryState state, CancellationToken cancellationToken)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(state);
|
|
|
|
try
|
|
{
|
|
var tempPath = _stateFilePath + ".tmp";
|
|
|
|
using (var stream = File.Create(tempPath))
|
|
{
|
|
await JsonSerializer.SerializeAsync(stream, state, SerializerOptions, cancellationToken).ConfigureAwait(false);
|
|
await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
File.Move(tempPath, _stateFilePath, true);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to save lyrics retry state to {Path}.", _stateFilePath);
|
|
}
|
|
}
|
|
|
|
private static string ResolveBasePath(IApplicationPaths applicationPaths)
|
|
{
|
|
var candidateProperties = new[]
|
|
{
|
|
"ProgramDataPath",
|
|
"DataPath",
|
|
"CachePath"
|
|
};
|
|
|
|
var pathsType = applicationPaths.GetType();
|
|
foreach (var propertyName in candidateProperties)
|
|
{
|
|
if (TryGetPathValue(pathsType, applicationPaths, propertyName) is string runtimePath)
|
|
{
|
|
return runtimePath;
|
|
}
|
|
|
|
if (TryGetPathValue(typeof(IApplicationPaths), applicationPaths, propertyName) is string interfacePath)
|
|
{
|
|
return interfacePath;
|
|
}
|
|
}
|
|
|
|
throw new InvalidOperationException("Unable to resolve application data path for retry-state persistence.");
|
|
}
|
|
|
|
private static string? TryGetPathValue(Type sourceType, object instance, string propertyName)
|
|
{
|
|
var property = sourceType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
if (property?.GetValue(instance) is string value
|
|
&& !string.IsNullOrWhiteSpace(value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|