Skip to content

Commit 32a3440

Browse files
dan-onlineCopilot
andauthored
fix(targets): add multiple library matching (#253)
Co-authored-by: Copilot <[email protected]>
1 parent 3fc1876 commit 32a3440

File tree

6 files changed

+90
-40
lines changed

6 files changed

+90
-40
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ data
1515
*.sh
1616

1717
# Nix
18-
result
18+
result
19+
shell.nix

crates/service/src/settings/targets/autopulse.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ impl Autopulse {
5454

5555
impl TargetProcess for Autopulse {
5656
async fn process(&self, evs: &[&ScanEvent]) -> anyhow::Result<Vec<String>> {
57-
let mut succeded = Vec::new();
57+
let mut succeeded = Vec::new();
5858

5959
for ev in evs {
6060
match self.scan(ev).await {
6161
Ok(()) => {
62-
succeded.push(ev.id.clone());
62+
succeeded.push(ev.id.clone());
6363
debug!("file scanned: {}", ev.get_path(&self.rewrite));
6464
}
6565
Err(e) => {
@@ -68,6 +68,6 @@ impl TargetProcess for Autopulse {
6868
}
6969
}
7070

71-
Ok(succeded)
71+
Ok(succeeded)
7272
}
7373
}

crates/service/src/settings/targets/command.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,16 @@ impl Command {
8383

8484
impl TargetProcess for Command {
8585
async fn process(&self, evs: &[&ScanEvent]) -> anyhow::Result<Vec<String>> {
86-
let mut succeded = Vec::new();
86+
let mut succeeded = Vec::new();
8787

8888
for ev in evs {
8989
if let Err(e) = self.run(ev).await {
9090
error!("failed to process '{}': {}", ev.get_path(&self.rewrite), e);
9191
} else {
92-
succeded.push(ev.id.clone());
92+
succeeded.push(ev.id.clone());
9393
}
9494
}
9595

96-
Ok(succeded)
96+
Ok(succeeded)
9797
}
9898
}

crates/service/src/settings/targets/emby.rs

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -129,20 +129,21 @@ impl Emby {
129129
Ok(res.json().await?)
130130
}
131131

132-
fn get_library(&self, libraries: &[Library], path: &str) -> Option<Library> {
132+
fn get_libraries(&self, libraries: &[Library], path: &str) -> Vec<Library> {
133133
let ev_path = Path::new(path);
134+
let mut matched: Vec<Library> = vec![];
134135

135136
for library in libraries {
136137
for location in &library.locations {
137138
let path = Path::new(location);
138139

139140
if ev_path.starts_with(path) {
140-
return Some(library.clone());
141+
matched.push(library.clone());
141142
}
142143
}
143144
}
144145

145-
None
146+
matched
146147
}
147148

148149
async fn _get_item(&self, library: &Library, path: &str) -> anyhow::Result<Option<Item>> {
@@ -171,7 +172,7 @@ impl Emby {
171172

172173
let res = client.get(url).perform().await?;
173174

174-
// Possibly uneeded unless we can use streams
175+
// Possibly unneeded unless we can use streams
175176
let bytes = res.bytes().await?;
176177

177178
let mut json_reader = JsonStreamReader::new(Cursor::new(bytes));
@@ -352,7 +353,7 @@ impl TargetProcess for Emby {
352353
.await
353354
.context("failed to fetch libraries")?;
354355

355-
let mut succeded = Vec::new();
356+
let mut succeeded: HashMap<String, bool> = HashMap::new();
356357

357358
let mut to_find = HashMap::new();
358359
let mut to_refresh = Vec::new();
@@ -362,10 +363,15 @@ impl TargetProcess for Emby {
362363
for ev in evs {
363364
let ev_path = ev.get_path(&self.rewrite);
364365

365-
if let Some(library) = self.get_library(&libraries, &ev_path) {
366-
to_find.entry(library).or_insert_with(Vec::new).push(*ev);
367-
} else {
366+
let matched_libraries = self.get_libraries(&libraries, &ev_path);
367+
368+
if matched_libraries.is_empty() {
368369
error!("failed to find library for file: {}", ev_path);
370+
continue;
371+
}
372+
373+
for library in matched_libraries {
374+
to_find.entry(library).or_insert_with(Vec::new).push(*ev);
369375
}
370376
}
371377

@@ -388,10 +394,11 @@ impl TargetProcess for Emby {
388394
match self.refresh_item(&item).await {
389395
Ok(()) => {
390396
debug!("refreshed item: {}", item.id);
391-
succeded.push(ev.id.clone());
397+
*succeeded.entry(ev.id.clone()).or_insert(true) &= true;
392398
}
393399
Err(e) => {
394400
error!("failed to refresh item: {}", e);
401+
succeeded.insert(ev.id.clone(), false);
395402
}
396403
}
397404
}
@@ -400,15 +407,27 @@ impl TargetProcess for Emby {
400407
}
401408

402409
if !to_scan.is_empty() {
403-
self.scan(&to_scan).await.context("failed to scan files")?;
410+
match self.scan(&to_scan).await {
411+
Ok(()) => {
412+
for ev in &to_scan {
413+
debug!("scanned file: {}", ev.file_path);
414+
415+
*succeeded.entry(ev.id.clone()).or_insert(true) &= true;
416+
}
417+
}
418+
Err(e) => {
419+
error!("failed to scan items: {}", e);
404420

405-
for file in &to_scan {
406-
debug!("scanned file: {}", file.file_path);
421+
for ev in &to_scan {
422+
succeeded.insert(ev.id.clone(), false);
423+
}
424+
}
407425
}
408426
}
409427

410-
succeded.extend(to_scan.iter().map(|ev| ev.id.clone()));
411-
412-
Ok(succeded)
428+
Ok(succeeded
429+
.iter()
430+
.filter_map(|(k, v)| if *v { Some(k.clone()) } else { None })
431+
.collect())
413432
}
414433
}

crates/service/src/settings/targets/fileflows.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ impl FileFlows {
130130
Ok(files.first().cloned())
131131
}
132132

133-
async fn reprocess_library_filse(&self, evs: Vec<&FileFlowsLibraryFile>) -> anyhow::Result<()> {
133+
async fn reprocess_library_file(&self, evs: Vec<&FileFlowsLibraryFile>) -> anyhow::Result<()> {
134134
let client = self.get_client()?;
135135

136136
let url = get_url(&self.url)?.join("api/library-file/reprocess")?;
@@ -274,7 +274,7 @@ impl TargetProcess for FileFlows {
274274

275275
if !processed.is_empty() {
276276
match self
277-
.reprocess_library_filse(
277+
.reprocess_library_file(
278278
processed
279279
.iter()
280280
.filter_map(|(_, file)| file.as_ref())

crates/service/src/settings/targets/plex.rs

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ use autopulse_database::models::ScanEvent;
66
use autopulse_utils::{get_url, squash_directory, what_is, PathType};
77
use reqwest::header;
88
use serde::Deserialize;
9-
use std::path::Path;
9+
use std::{
10+
collections::{HashMap, HashSet},
11+
path::Path,
12+
};
1013
use tracing::{debug, error, trace};
1114

1215
#[derive(Deserialize, Clone)]
@@ -152,7 +155,7 @@ impl Plex {
152155
Ok(libraries.media_container.directory.unwrap())
153156
}
154157

155-
fn get_library(&self, libraries: &[Library], path: &str) -> Option<Library> {
158+
fn get_libraries(&self, libraries: &[Library], path: &str) -> Vec<Library> {
156159
let ev_path = Path::new(path);
157160
let mut matches: Vec<(usize, &Library)> = vec![];
158161

@@ -165,13 +168,13 @@ impl Plex {
165168
}
166169
}
167170

171+
// Sort the best matched library first
168172
matches.sort_by(|(len_a, _), (len_b, _)| len_b.cmp(len_a));
169173

170-
// Return the most specific match
171174
matches
172175
.into_iter()
173-
.next()
174176
.map(|(_, library)| library.clone())
177+
.collect()
175178
}
176179

177180
async fn get_episodes(&self, key: &str) -> anyhow::Result<LibraryResponse> {
@@ -398,13 +401,27 @@ impl TargetProcess for Plex {
398401
async fn process(&self, evs: &[&ScanEvent]) -> anyhow::Result<Vec<String>> {
399402
let libraries = self.libraries().await.context("failed to get libraries")?;
400403

401-
let mut succeeded = Vec::new();
404+
let mut succeeded: HashMap<String, bool> = HashMap::new();
402405

403406
for ev in evs {
407+
let succeeded_entry = succeeded.entry(ev.id.clone()).or_insert(true);
408+
404409
let ev_path = ev.get_path(&self.rewrite);
410+
let matched_libraries = self.get_libraries(&libraries, &ev_path);
411+
412+
if matched_libraries.is_empty() {
413+
error!("no matching library for {ev_path}");
414+
415+
*succeeded_entry = false;
416+
417+
continue;
418+
}
419+
420+
let mut processed_items = HashSet::new();
405421

406-
if let Some(library) = self.get_library(&libraries, &ev_path) {
422+
for library in matched_libraries {
407423
trace!("found library '{}' for {ev_path}", library.title);
424+
408425
match self.scan(ev, &library).await {
409426
Ok(()) => {
410427
debug!("scanned '{}'", ev_path);
@@ -417,7 +434,8 @@ impl TargetProcess for Plex {
417434
"failed to find items for file: '{}', leaving at scan",
418435
ev_path
419436
);
420-
succeeded.push(ev.id.clone());
437+
438+
*succeeded_entry = true;
421439
} else {
422440
trace!("found items for file '{}'", ev_path);
423441

@@ -426,6 +444,14 @@ impl TargetProcess for Plex {
426444
for item in items {
427445
let mut item_success = true;
428446

447+
if processed_items.contains(&item.key) {
448+
debug!(
449+
"already processed item '{}' earlier, skipping",
450+
item.key
451+
);
452+
continue;
453+
}
454+
429455
if self.refresh {
430456
match self.refresh_item(&item.key).await {
431457
Ok(()) => {
@@ -459,10 +485,12 @@ impl TargetProcess for Plex {
459485
if !item_success {
460486
all_success = false;
461487
}
488+
489+
processed_items.insert(item.key);
462490
}
463491

464492
if all_success {
465-
succeeded.push(ev.id.clone());
493+
*succeeded_entry &= true;
466494
}
467495
}
468496
}
@@ -471,19 +499,20 @@ impl TargetProcess for Plex {
471499
}
472500
};
473501
} else {
474-
succeeded.push(ev.id.clone());
502+
*succeeded_entry &= true;
475503
}
476504
}
477505
Err(e) => {
478506
error!("failed to scan file '{}': {}", ev_path, e);
479507
}
480508
}
481-
} else {
482-
error!("failed to find library for file: {}", ev_path);
483509
}
484510
}
485511

486-
Ok(succeeded)
512+
Ok(succeeded
513+
.into_iter()
514+
.filter_map(|(k, v)| if v { Some(k) } else { None })
515+
.collect())
487516
}
488517
}
489518

@@ -545,8 +574,8 @@ mod tests {
545574
}];
546575

547576
let path = "/media/movies/Inception.mkv";
548-
let library = plex.get_library(&libraries, path).unwrap();
549-
assert!(library.key == "library_key_movies");
577+
let libraries = plex.get_libraries(&libraries, path);
578+
assert!(libraries[0].key == "library_key_movies");
550579

551580
let nested_libraries = [
552581
Library {
@@ -567,7 +596,8 @@ mod tests {
567596

568597
let path = "/media/movies/4k/Inception.mkv";
569598

570-
let library = plex.get_library(&nested_libraries, path).unwrap();
571-
assert!(library.key == "library_key_movies_4k");
599+
let libraries = plex.get_libraries(&nested_libraries, path);
600+
assert!(libraries[0].key == "library_key_movies_4k");
601+
assert!(libraries[1].key == "library_key_movies");
572602
}
573603
}

0 commit comments

Comments
 (0)