51 lines
2.3 KiB
Swift
51 lines
2.3 KiB
Swift
import Foundation
|
|
|
|
/// One porn collection as reported by the script, with live fresh/total counts.
|
|
public struct PornCollection: Identifiable, Sendable, Equatable, Decodable {
|
|
public let name: String
|
|
public let desc: String
|
|
public let fresh: Int
|
|
public let total: Int
|
|
public var id: String { name }
|
|
}
|
|
|
|
/// Bridges TVAnarchy to portable-net-tv's `porn-rotation.py`, the source of truth
|
|
/// for the porn collections AND their freshness. Porn isn't in the shared
|
|
/// watchlog — the script keeps its own play-log (`porn-plays.json`) — so we shell
|
|
/// out rather than reimplement the filters here, which would lose the don't-replay
|
|
/// behavior that's the script's whole reason to exist. `paths(...)` marks what it
|
|
/// returns played, so freshness advances even though playback happens app-side.
|
|
public enum PornCollectionService {
|
|
private static var script: String {
|
|
ProcessInfo.processInfo.environment["PORN_ROTATION_SCRIPT"]
|
|
?? RepoPaths.governor.appendingPathComponent("tools/porn-rotation.py").path
|
|
}
|
|
|
|
private static func shq(_ s: String) -> String {
|
|
"'" + s.replacingOccurrences(of: "'", with: "'\\''") + "'"
|
|
}
|
|
|
|
private static func py(_ args: String) -> ProcessResult {
|
|
ProcessRunner.runShell("python3 \(shq(script)) \(args)", timeout: 40)
|
|
}
|
|
|
|
/// The defined collections with fresh/total counts. Empty when the script or
|
|
/// the media mount is unavailable — the UI just shows nothing then.
|
|
public static func list() async -> [PornCollection] {
|
|
await Task.detached(priority: .utility) {
|
|
let r = py("collections --json")
|
|
guard r.ok, let d = r.stdout.data(using: .utf8),
|
|
let cols = try? JSONDecoder().decode([PornCollection].self, from: d) else { return [] }
|
|
return cols
|
|
}.value
|
|
}
|
|
|
|
/// Fresh, shuffled file paths for a collection (plum-side), marking them played.
|
|
public static func paths(collection: String, count: Int) async -> [String] {
|
|
await Task.detached(priority: .utility) {
|
|
let r = py("paths -c \(shq(collection)) --count \(count) --mark")
|
|
guard r.ok else { return [] }
|
|
return r.stdout.split(separator: "\n").map(String.init).filter { !$0.isEmpty }
|
|
}.value
|
|
}
|
|
}
|