140 lines
4.1 KiB
C#
140 lines
4.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using Jellyfin.Plugin.JRay.Models;
|
|
using Jellyfin.Plugin.JRay.Services.Interfaces;
|
|
using MediaBrowser.Common.Configuration;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Jellyfin.Plugin.JRay.Services;
|
|
|
|
/// <summary>
|
|
/// Persists the prioritise/ignore rules to a single JSON file under the
|
|
/// plugin's configuration directory, and keeps an in-memory copy for fast
|
|
/// reads. All access is synchronised so the work-discovery API and the
|
|
/// config page can touch it concurrently.
|
|
/// </summary>
|
|
public sealed class MediaPolicyStore : IMediaPolicyStore
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
|
|
{
|
|
WriteIndented = true,
|
|
Converters = { new JsonStringEnumConverter() }
|
|
};
|
|
|
|
private readonly IApplicationPaths _applicationPaths;
|
|
private readonly ILogger<MediaPolicyStore> _logger;
|
|
private readonly object _gate = new();
|
|
private List<MediaPolicyRule>? _rules;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="MediaPolicyStore"/> class.
|
|
/// </summary>
|
|
/// <param name="applicationPaths">The Jellyfin application paths.</param>
|
|
/// <param name="logger">The logger.</param>
|
|
public MediaPolicyStore(IApplicationPaths applicationPaths, ILogger<MediaPolicyStore> logger)
|
|
{
|
|
_applicationPaths = applicationPaths;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public IReadOnlyList<MediaPolicyRule> GetRules()
|
|
{
|
|
lock (_gate)
|
|
{
|
|
return EnsureLoaded().ToList();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void SetRule(MediaPolicyRule rule)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(rule);
|
|
|
|
lock (_gate)
|
|
{
|
|
var rules = EnsureLoaded();
|
|
rules.RemoveAll(r => r.Scope == rule.Scope && ValueEquals(r.Value, rule.Value));
|
|
rules.Add(rule);
|
|
Save(rules);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool RemoveRule(PolicyScope scope, string value)
|
|
{
|
|
lock (_gate)
|
|
{
|
|
var rules = EnsureLoaded();
|
|
var removed = rules.RemoveAll(r => r.Scope == scope && ValueEquals(r.Value, value));
|
|
if (removed > 0)
|
|
{
|
|
Save(rules);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static bool ValueEquals(string a, string b)
|
|
{
|
|
// Genre names are matched case-insensitively; ids happen to be
|
|
// case-insensitive too (GUID strings), so a single comparison suffices.
|
|
return string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private List<MediaPolicyRule> EnsureLoaded()
|
|
{
|
|
if (_rules is not null)
|
|
{
|
|
return _rules;
|
|
}
|
|
|
|
var path = GetPath();
|
|
if (!File.Exists(path))
|
|
{
|
|
_rules = new List<MediaPolicyRule>();
|
|
return _rules;
|
|
}
|
|
|
|
try
|
|
{
|
|
using var stream = File.OpenRead(path);
|
|
_rules = JsonSerializer.Deserialize<List<MediaPolicyRule>>(stream, JsonOptions)
|
|
?? new List<MediaPolicyRule>();
|
|
}
|
|
catch (Exception ex) when (ex is IOException or JsonException)
|
|
{
|
|
_logger.LogWarning(ex, "JRay: failed to read media policy file {Path}; starting empty", path);
|
|
_rules = new List<MediaPolicyRule>();
|
|
}
|
|
|
|
return _rules;
|
|
}
|
|
|
|
private void Save(List<MediaPolicyRule> rules)
|
|
{
|
|
var path = GetPath();
|
|
var directory = Path.GetDirectoryName(path) ?? throw new InvalidOperationException("Media policy path has no directory.");
|
|
Directory.CreateDirectory(directory);
|
|
|
|
var tempPath = path + ".tmp";
|
|
using (var stream = File.Create(tempPath))
|
|
{
|
|
JsonSerializer.Serialize(stream, rules, JsonOptions);
|
|
}
|
|
|
|
File.Move(tempPath, path, overwrite: true);
|
|
}
|
|
|
|
private string GetPath()
|
|
{
|
|
return Path.Combine(_applicationPaths.PluginConfigurationsPath, "JRay", "policy.json");
|
|
}
|
|
}
|