tv-anarchy/Sources/TVAnarchyCore/TrackPreferenceStore.swift
Natalie 92b38b1bae refactor(tv-anarchy): rename PlumTV→TVAnarchy and land session work
Renames Sources/PlumTV→TVAnarchy and PlumTVCore→TVAnarchyCore (the rename
the auto-commit service couldn't stage — it git-add'd the old, now-gone
paths and aborted every cycle), and commits the accumulated work:

- Library: black-built index fast path (LibraryIndex + scanFromIndex) with
  NFS-walk fallback; incremental --add on download-complete; mtime staleness
  gate; loose-file series-collapse fix; determinate scan/index progress.
- Cover art: keyless TVmaze cartoon-vs-live-action disambiguation (type/year).
- Player: sleep timer (timed + end-of-episode); visibility-gated polling.
- Home: Continue Watching cover art + live refresh; Recently Added; adult gate.
- Logs: multi-line selection + copy; truncated giant tx-list errors.
- Hover previews (opt-in) via black ffmpeg + scp.

Also gitignores foreign project trees (governor/mcp/fleet/recommender) that
sit in this directory but belong to their own repos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 22:04:22 -07:00

55 lines
2.2 KiB
Swift

import Foundation
/// The persisted sub/dub choices: a global default plus per-series overrides.
/// Keyed by normalized show name so a choice survives across episodes and app
/// restarts. anime defaults to `sub` at resolve time even without an override.
public struct TrackPreferences: Codable, Sendable, Equatable {
public var globalDefault: DubSub
public var perSeries: [String: DubSub]
public init(globalDefault: DubSub = .dub, perSeries: [String: DubSub] = [:]) {
self.globalDefault = globalDefault
self.perSeries = perSeries
}
static func key(_ name: String) -> String {
name.lowercased().trimmingCharacters(in: .whitespaces)
}
/// Resolve the effective choice: explicit per-series override wins; otherwise
/// anime defaults to `sub`; otherwise the global default (`dub` native audio,
/// no forced subs, which is right for English-language content).
public func choice(series: String?, category: String?) -> DubSub {
if let series, let explicit = perSeries[Self.key(series)] { return explicit }
if category == "anime" { return .sub }
return globalDefault
}
public mutating func setSeries(_ name: String, _ choice: DubSub) {
perSeries[Self.key(name)] = choice
}
}
/// JSON store at `~/.local/state/tv-anarchy/track-prefs.json`. Mirrors
/// PlayerStatusCache's load/save shape.
public enum TrackPreferenceStore {
private static var url: URL {
FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent(".local/state/tv-anarchy/track-prefs.json")
}
public static func load() -> TrackPreferences {
guard let data = try? Data(contentsOf: url),
let prefs = try? JSONDecoder().decode(TrackPreferences.self, from: data) else {
return TrackPreferences()
}
return prefs
}
public static func save(_ prefs: TrackPreferences) {
guard let data = try? JSONEncoder().encode(prefs) else { return }
try? FileManager.default.createDirectory(at: url.deletingLastPathComponent(),
withIntermediateDirectories: true)
try? data.write(to: url, options: .atomic)
}
}