feat(core): add movie domain types (#47)
ci / web (push) Successful in 6s
ci / rust (push) Successful in 36s
e2e / e2e (push) Successful in 1m10s

This commit was merged in pull request #47.
This commit is contained in:
2026-08-22 19:40:48 +01:00
parent 84777169d0
commit 7b57c387fb
+284 -8
View File
@@ -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<u8>,
}
#[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<Language>),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HdrRules {
pub rejected_dolby_vision_profiles: Vec<DolbyVisionProfile>,
}
#[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<Language>,
pub hdr_rules: HdrRules,
pub size_bands: BTreeMap<Resolution, SizeBand>,
pub resolution_preference: Vec<Resolution>,
pub source_weights: BTreeMap<Source, i32>,
}
#[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<SystemTime>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AudioTrack {
pub language: Language,
pub title: Option<String>,
pub handler_name: Option<String>,
}
#[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<Source>,
pub hdr: HdrFormat,
pub audio_tracks: Vec<AudioTrack>,
pub subtitle_tracks: Vec<SubtitleTrack>,
}
#[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<Rule>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParsedRelease {
pub resolution: Option<Resolution>,
pub source: Option<Source>,
pub hdr: Vec<HdrClaim>,
pub audio_languages: Vec<Language>,
}
#[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<SystemTime>,
}
#[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));
}
}