Skip to content

Commit

Permalink
🐛 Restore media/series when previously missing (#529)
Browse files Browse the repository at this point in the history
* 🐛 Correctly restore media/series when previously missing

* fix panic from overflow calc
  • Loading branch information
aaronleopold authored Dec 22, 2024
1 parent 6dda732 commit 21cced8
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 31 deletions.
13 changes: 6 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions core/src/db/entity/media/prisma_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ media::select!(media_id_select { id });
media::select!(media_path_select { path });

media::select!(media_path_modified_at_select {
id
path
modified_at
status
});

media::select!(media_thumbnail {
Expand Down
76 changes: 72 additions & 4 deletions core/src/filesystem/scanner/library_scan_job.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ use crate::{
use super::{
series_scan_job::SeriesScanTask,
utils::{
handle_missing_media, handle_missing_series, safely_build_and_insert_media,
safely_build_series, visit_and_update_media, MediaBuildOperation,
MediaOperationOutput, MissingSeriesOutput,
handle_missing_media, handle_missing_series, handle_restored_media,
safely_build_and_insert_media, safely_build_series, visit_and_update_media,
MediaBuildOperation, MediaOperationOutput, MissingSeriesOutput,
},
walk_library, walk_series, ScanOptions, WalkedLibrary, WalkedSeries, WalkerCtx,
};
Expand All @@ -50,6 +50,7 @@ pub enum LibraryScanTask {
pub struct InitTaskInput {
series_to_create: Vec<PathBuf>,
missing_series: Vec<PathBuf>,
recovered_series: Vec<String>,
}

/// A job that scans a library and updates the database with the results
Expand Down Expand Up @@ -152,6 +153,7 @@ impl JobExt for LibraryScanJob {
ctx.report_progress(JobProgress::msg("Performing task discovery"));
let WalkedLibrary {
series_to_create,
recovered_series,
series_to_visit,
missing_series,
library_is_missing,
Expand All @@ -171,6 +173,7 @@ impl JobExt for LibraryScanJob {
series_to_create = series_to_create.len(),
series_to_visit = series_to_visit.len(),
missing_series = missing_series.len(),
recovered_series = recovered_series.len(),
library_is_missing,
"Walked library"
);
Expand All @@ -193,6 +196,7 @@ impl JobExt for LibraryScanJob {
let init_task_input = InitTaskInput {
series_to_create: series_to_create.clone(),
missing_series,
recovered_series,
};

let series_to_visit = series_to_visit
Expand Down Expand Up @@ -275,17 +279,60 @@ impl JobExt for LibraryScanJob {
let InitTaskInput {
series_to_create,
missing_series,
recovered_series,
} = input;

let recovered_series_step_count =
if !recovered_series.is_empty() { 1 } else { 0 };

// Task count: build each series + 1 for insertion step, +1 for update tx on missing series
let total_subtask_count = (series_to_create.len() + 1)
+ usize::from(!missing_series.is_empty());
+ usize::from(!missing_series.is_empty())
+ recovered_series_step_count;
let mut current_subtask_index = 0;
ctx.report_progress(JobProgress::subtask_position(
current_subtask_index,
total_subtask_count as i32,
));

if !recovered_series.is_empty() {
ctx.report_progress(JobProgress::msg("Recovering series"));

let affected_rows = ctx
.db
.series()
.update_many(
vec![series::id::in_vec(recovered_series)],
vec![series::status::set(FileStatus::Ready.to_string())],
)
.exec()
.await
.map_or_else(
|error| {
tracing::error!(error = ?error, "Failed to recover series");
logs.push(JobExecuteLog::error(format!(
"Failed to recover series: {:?}",
error.to_string()
)));
0
},
|count| {
output.updated_series = count as u64;
count
},
);

ctx.report_progress(JobProgress::subtask_position(
(affected_rows > 0)
.then(|| {
current_subtask_index += 1;
current_subtask_index
})
.unwrap_or(0),
total_subtask_count as i32,
));
}

if !missing_series.is_empty() {
ctx.report_progress(JobProgress::msg("Handling missing series"));
let missing_series_str = missing_series
Expand Down Expand Up @@ -440,6 +487,7 @@ impl JobExt for LibraryScanJob {
series_is_missing,
media_to_create,
media_to_visit,
recovered_media,
missing_media,
seen_files,
ignored_files,
Expand Down Expand Up @@ -504,6 +552,8 @@ impl JobExt for LibraryScanJob {
[
(!missing_media.is_empty())
.then_some(SeriesScanTask::MarkMissingMedia(missing_media)),
(!recovered_media.is_empty())
.then_some(SeriesScanTask::RestoreMedia(recovered_media)),
(!media_to_create.is_empty())
.then_some(SeriesScanTask::CreateMedia(media_to_create)),
(!media_to_visit.is_empty())
Expand All @@ -523,6 +573,24 @@ impl JobExt for LibraryScanJob {
path: _series_path,
task: series_task,
} => match series_task {
SeriesScanTask::RestoreMedia(ids) => {
ctx.report_progress(JobProgress::msg("Restoring media entities"));
let MediaOperationOutput {
updated_media,
logs: new_logs,
..
} = handle_restored_media(ctx, &series_id, ids).await;
ctx.send_batch(vec![
JobProgress::msg("Restored media entities").into_worker_send(),
CoreEvent::CreatedOrUpdatedManyMedia {
count: updated_media,
series_id,
}
.into_worker_send(),
]);
output.updated_media += updated_media;
logs.extend(new_logs);
},
SeriesScanTask::MarkMissingMedia(paths) => {
ctx.report_progress(JobProgress::msg("Handling missing media"));
let MediaOperationOutput {
Expand Down
35 changes: 27 additions & 8 deletions core/src/filesystem/scanner/series_scan_job.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ use crate::{

use super::{
utils::{
handle_missing_media, safely_build_and_insert_media, visit_and_update_media,
MediaBuildOperation, MediaOperationOutput,
handle_missing_media, handle_restored_media, safely_build_and_insert_media,
visit_and_update_media, MediaBuildOperation, MediaOperationOutput,
},
walk_series, ScanOptions, WalkedSeries, WalkerCtx,
};
Expand All @@ -32,6 +32,7 @@ use super::{
#[derive(Serialize, Deserialize)]
pub enum SeriesScanTask {
MarkMissingMedia(Vec<PathBuf>),
RestoreMedia(Vec<String>),
CreateMedia(Vec<PathBuf>),
VisitMedia(Vec<PathBuf>),
}
Expand Down Expand Up @@ -138,6 +139,7 @@ impl JobExt for SeriesScanJob {
series_is_missing,
media_to_create,
media_to_visit,
recovered_media,
missing_media,
seen_files,
ignored_files,
Expand Down Expand Up @@ -172,14 +174,13 @@ impl JobExt for SeriesScanJob {
let tasks = VecDeque::from(chain_optional_iter(
[],
[
missing_media
.is_empty()
(!missing_media.is_empty())
.then_some(SeriesScanTask::MarkMissingMedia(missing_media)),
media_to_create
.is_empty()
(!recovered_media.is_empty())
.then_some(SeriesScanTask::RestoreMedia(recovered_media)),
(!media_to_create.is_empty())
.then_some(SeriesScanTask::CreateMedia(media_to_create)),
media_to_visit
.is_empty()
(!media_to_visit.is_empty())
.then_some(SeriesScanTask::VisitMedia(media_to_visit)),
],
));
Expand Down Expand Up @@ -237,6 +238,24 @@ impl JobExt for SeriesScanJob {
let max_concurrency = ctx.config.max_scanner_concurrency;

match task {
SeriesScanTask::RestoreMedia(ids) => {
ctx.report_progress(JobProgress::msg("Restoring media entities"));
let MediaOperationOutput {
updated_media,
logs: new_logs,
..
} = handle_restored_media(ctx, &self.id, ids).await;
ctx.send_batch(vec![
JobProgress::msg("Restored media entities").into_worker_send(),
CoreEvent::CreatedOrUpdatedManyMedia {
count: updated_media,
series_id: self.id.clone(),
}
.into_worker_send(),
]);
output.updated_media += updated_media;
logs.extend(new_logs);
},
SeriesScanTask::MarkMissingMedia(paths) => {
ctx.report_progress(JobProgress::msg("Handling missing media"));
let MediaOperationOutput {
Expand Down
47 changes: 47 additions & 0 deletions core/src/filesystem/scanner/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ pub(crate) struct MediaOperationOutput {
pub logs: Vec<JobExecuteLog>,
}

/// Handles missing media by updating the database with the latest information. A media is
/// considered missing if it was previously marked as ready and is no longer found on disk.
pub(crate) async fn handle_missing_media(
ctx: &WorkerCtx,
series_id: &str,
Expand Down Expand Up @@ -326,6 +328,51 @@ pub(crate) async fn handle_missing_media(
output
}

/// Handles restored media by updating the database with the latest information. A
/// media is considered restored if it was previously marked as missing and has been
/// found on disk.
pub(crate) async fn handle_restored_media(
ctx: &WorkerCtx,
series_id: &str,
ids: Vec<String>,
) -> MediaOperationOutput {
let mut output = MediaOperationOutput::default();

if ids.is_empty() {
tracing::debug!("No restored media to handle");
return output;
}

let _affected_rows = ctx
.db
.media()
.update_many(
vec![
media::series::is(vec![series::id::equals(series_id.to_string())]),
media::id::in_vec(ids),
],
vec![media::status::set(FileStatus::Ready.to_string())],
)
.exec()
.await
.map_or_else(
|error| {
tracing::error!(error = ?error, "Failed to restore recovered media");
output.logs.push(JobExecuteLog::error(format!(
"Failed to update recovered media: {:?}",
error.to_string()
)));
0
},
|count| {
output.updated_media += count as u64;
count
},
);

output
}

/// Builds a series from the given path
///
/// # Arguments
Expand Down
Loading

0 comments on commit 21cced8

Please sign in to comment.