feat(arr): report release-name match per candidate
The manual subtitle view shows the facts that decided a row (§9.3), and release-name match is §15's second ranking tier. The file's own release name is not otherwise on the wire, so the UI cannot derive it.
This commit is contained in:
@@ -116,6 +116,12 @@ pub struct SubtitleCandidate {
|
||||
/// Whether the provider matched the exact file by `moviehash` — §15's
|
||||
/// outright winner.
|
||||
pub hash_match: bool,
|
||||
/// Whether the candidate's release name is the one the file was
|
||||
/// imported under — §15's second ranking tier. Reported rather than
|
||||
/// left implicit in the order, because §9.3's manual view shows the
|
||||
/// facts that decided a row, and the release name of the file on disk
|
||||
/// is not otherwise on the wire.
|
||||
pub release_match: bool,
|
||||
pub release_name: Option<String>,
|
||||
pub group: Option<String>,
|
||||
pub source: Option<String>,
|
||||
@@ -448,6 +454,16 @@ fn language_of(tag: &str) -> Result<Language, ApiError> {
|
||||
Ok(arr_db::policy::language(tag))
|
||||
}
|
||||
|
||||
/// Whether two release names are the same one, matched exactly as
|
||||
/// `arr_core::subs`' ranking tier does — case-insensitively, and never when
|
||||
/// either side is unknown.
|
||||
fn same_release(candidate: Option<&str>, target: Option<&str>) -> bool {
|
||||
match (candidate, target) {
|
||||
(Some(candidate), Some(target)) => candidate.eq_ignore_ascii_case(target),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
async fn subtitles_of(state: &AppState, media_file_id: i64) -> Result<Vec<Subtitle>, ApiError> {
|
||||
let files = db::files_for(pool(state)?, media_file_id).await?;
|
||||
Ok(files.into_iter().map(Subtitle::from).collect())
|
||||
@@ -818,6 +834,10 @@ pub async fn search(
|
||||
candidate_id: candidate.id.to_string(),
|
||||
language: candidate.language.to_string(),
|
||||
hash_match: candidate.hash_match,
|
||||
release_match: same_release(
|
||||
candidate.release_name.as_deref(),
|
||||
target.release_name.as_deref(),
|
||||
),
|
||||
release_name: candidate.release_name.clone(),
|
||||
group: candidate.group.clone(),
|
||||
source: candidate.source.map(|source| source.to_string()),
|
||||
@@ -1188,6 +1208,21 @@ mod tests {
|
||||
|
||||
const SRT: &str = "1\n00:00:01,000 --> 00:00:02,000\nolá\n";
|
||||
|
||||
/// §15's second ranking tier, as the manual view reads it: the same
|
||||
/// release name matches whatever its casing, and an unknown name on
|
||||
/// either side is never a match.
|
||||
#[test]
|
||||
fn release_names_match_case_insensitively_and_never_when_unknown() {
|
||||
assert!(super::same_release(
|
||||
Some("Dune.2021.2160p.WEB-DL-GROUP"),
|
||||
Some("dune.2021.2160p.web-dl-group")
|
||||
));
|
||||
assert!(!super::same_release(Some("Dune.2021"), Some("Dune.2024")));
|
||||
assert!(!super::same_release(Some("Dune.2021"), None));
|
||||
assert!(!super::same_release(None, Some("Dune.2021")));
|
||||
assert!(!super::same_release(None, None));
|
||||
}
|
||||
|
||||
/// Offers three candidates for whatever it is asked: one plain, one that
|
||||
/// matched by hash, one forced.
|
||||
#[derive(Debug)]
|
||||
@@ -1559,6 +1594,9 @@ mod tests {
|
||||
assert_eq!(candidates[2]["candidate_id"], "forced");
|
||||
assert_eq!(candidates[2]["verdict"], "rejected");
|
||||
assert_eq!(candidates[2]["rejected_rule"], "forced");
|
||||
// No grab record backs this file, so nothing can claim its release
|
||||
// name — the chip reads "no", never "unknown".
|
||||
assert_eq!(candidates[0]["release_match"], false);
|
||||
assert!(body["provider_errors"]
|
||||
.as_array()
|
||||
.expect("errors")
|
||||
|
||||
Reference in New Issue
Block a user