diff --git a/crates/arr-core/src/lib.rs b/crates/arr-core/src/lib.rs index 328b332..be1badf 100644 --- a/crates/arr-core/src/lib.rs +++ b/crates/arr-core/src/lib.rs @@ -1,14 +1,290 @@ -//! arr-core — domain types, policy engine, scoring. See DESIGN.md §4 and §5. +//! Pure domain types shared by the arr crates. //! -//! This crate must never depend on `axum`, `sqlx` or `reqwest`. It holds the -//! logic that is tested constantly and it has to stay fast to compile. +//! This crate must never depend on `axum`, `sqlx` or `reqwest`. + +use std::{collections::BTreeMap, path::PathBuf, time::SystemTime}; + +macro_rules! id_type { + ($name:ident) => { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct $name(pub i64); + }; +} + +id_type!(RootId); +id_type!(PolicyId); +id_type!(MovieId); +id_type!(MediaFileId); +id_type!(ReleaseId); +id_type!(GrabId); +id_type!(OwnerId); + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Audience { + Main, + Kids, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Root { + pub id: RootId, + pub audience: Audience, + pub path: PathBuf, + pub policy_id: PolicyId, +} + +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum Language { + PortuguesePortugal, + PortugueseBrazil, + PortugueseUnverified, + Other(String), +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum Resolution { + R2160p, + R1080p, + R720p, + Other(u16), +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum Source { + Remux, + BluRay, + WebDl, + WebRip, + Hdtv, + Telesync, + Cam, + Screener, + Other, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct DolbyVisionProfile { + pub profile: u8, + pub compatibility_id: Option, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum HdrFormat { + Sdr, + Hdr10, + Hdr10Plus, + Hlg, + DolbyVision(DolbyVisionProfile), +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum HdrClaim { + Hdr10, + Hdr10Plus, + Hlg, + DolbyVision, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum RequiredAudio { + OriginalLanguage, + AnyOf(Vec), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct HdrRules { + pub rejected_dolby_vision_profiles: Vec, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SizeBand { + pub floor_bytes: u64, + pub target_bytes: u64, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Policy { + pub id: PolicyId, + pub name: String, + pub required_audio: RequiredAudio, + pub dub_blacklist: Vec, + pub hdr_rules: HdrRules, + pub size_bands: BTreeMap, + pub resolution_preference: Vec, + pub source_weights: BTreeMap, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct MovieOverrides { + pub only_4k: bool, + pub allow_english_audio: bool, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MovieState { + Missing, + Downloading, + Available, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Movie { + pub id: MovieId, + pub tmdb_id: u64, + pub title: String, + pub year: u16, + pub original_language: Language, + pub root_id: RootId, + pub wanted: bool, + pub overrides: MovieOverrides, + pub state: MovieState, + pub blocked: bool, + pub search_attempts: u32, + pub last_searched_at: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AudioTrack { + pub language: Language, + pub title: Option, + pub handler_name: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SubtitleTrack { + pub language: Language, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ProbedMedia { + pub resolution: Resolution, + pub source: Option, + pub hdr: HdrFormat, + pub audio_tracks: Vec, + pub subtitle_tracks: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Rule { + RequiredAudio, + DubBlacklist(Language), + DolbyVisionProfile(DolbyVisionProfile), + Resolution(Resolution), + Source(Source), + Size, + Other(String), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Verdict { + Eligible, + Waived(Rule), + Rejected(Rule), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MediaFile { + pub id: MediaFileId, + pub movie_id: MovieId, + pub path: PathBuf, + pub size: u64, + pub probed: ProbedMedia, + pub waiver: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ParsedRelease { + pub resolution: Option, + pub source: Option, + pub hdr: Vec, + pub audio_languages: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Release { + pub id: ReleaseId, + pub indexer_id: i64, + pub guid: String, + pub name: String, + pub size: u64, + pub seeders: u32, + pub publish_date: SystemTime, + pub download_url: String, + pub parsed: ParsedRelease, + pub score: i64, + pub verdict: Verdict, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum GrabState { + Downloading, + Downloaded, + Imported, + Failed, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Grab { + pub id: GrabId, + pub release_id: ReleaseId, + pub movie_id: MovieId, + pub infohash: String, + pub state: GrabState, + pub grabbed_at: SystemTime, + pub imported_at: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BlacklistEntry { + pub infohash: String, + pub normalised_name: String, + pub reason: String, + pub created_at: SystemTime, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Owner { + pub id: OwnerId, + pub name: String, + pub ntfy_topic: String, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct MovieOwner { + pub movie_id: MovieId, + pub owner_id: OwnerId, +} #[cfg(test)] mod tests { - /// Placeholder so the test gate is meaningful on an empty workspace — - /// `cargo nextest` fails a run with zero tests, and relaxing that would - /// let a broken filter go green later. Replaced by real tests in the - /// domain-types and policy-engine issues. + use super::{DolbyVisionProfile, HdrFormat, Language, Rule, Verdict}; + #[test] - fn workspace_builds_and_tests_run() {} + fn portuguese_variants_are_distinct() { + assert_ne!(Language::PortuguesePortugal, Language::PortugueseBrazil); + assert_ne!(Language::PortugueseBrazil, Language::PortugueseUnverified); + } + + #[test] + fn dolby_vision_profile_is_preserved() { + assert_ne!( + HdrFormat::DolbyVision(DolbyVisionProfile { + profile: 8, + compatibility_id: Some(1), + }), + HdrFormat::DolbyVision(DolbyVisionProfile { + profile: 8, + compatibility_id: Some(2), + }) + ); + } + + #[test] + fn non_eligible_verdicts_name_the_rule() { + let rule = Rule::DolbyVisionProfile(DolbyVisionProfile { + profile: 5, + compatibility_id: None, + }); + assert_eq!(Verdict::Rejected(rule.clone()), Verdict::Rejected(rule)); + } }