using System.Globalization; namespace Jellyfin.Plugin.Audience; /// /// Remembers every item this plugin has ever tagged. /// /// /// This is what makes manual curation stick. The selection rules are a guess; the person /// reviewing the result will remove titles that do not belong. Without a record of what was /// tagged, the next run cannot tell "never seen, should tag" apart from "tagged once, human /// removed it", and would re-apply the tag every night forever. /// public static class LedgerStore { private const string FileName = "tagged-ids.txt"; private static string LedgerPath => Path.Combine( Plugin.Instance?.DataFolderPath ?? throw new InvalidOperationException("Plugin instance is not initialised."), FileName); /// /// Loads the set of previously tagged item ids. Returns an empty set on first run. /// public static HashSet Load() { var result = new HashSet(); var path = LedgerPath; if (!File.Exists(path)) { return result; } foreach (var line in File.ReadLines(path)) { var trimmed = line.Trim(); if (trimmed.Length == 0) { continue; } if (Guid.TryParse(trimmed, out var id)) { result.Add(id); } } return result; } /// /// Writes the ledger atomically, so an interrupted run cannot truncate it. /// /// /// A truncated ledger is not a cosmetic problem: every id lost from it becomes an item /// the next run believes it has never seen, which silently re-applies tags a human /// deliberately removed. /// public static void Save(HashSet ids) { var path = LedgerPath; var directory = Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(directory)) { Directory.CreateDirectory(directory); } var temp = path + ".tmp"; File.WriteAllLines( temp, ids.Select(id => id.ToString("N", CultureInfo.InvariantCulture))); File.Move(temp, path, overwrite: true); } }