use super::*;
pub(super) async fn dry_run_report(
ctx: &RunCtx<'_>,
assembled: &mut Assembled,
store: &suno_core::LineageStore,
) -> Result<ExitCode> {
let manifest = logs::load_manifest(ctx.dest)?;
suno_core::preview_synced_lrc(
&mut assembled.desired,
&manifest,
wallclock::now_secs(),
ctx.settings.lrc_sidecar,
);
let plan =
execute::reconcile_run(&assembled.reconcile_inputs(&manifest, ctx.dest, &store.albums))
.await;
if ctx.verbosity >= 1 {
let no_failures = HashSet::new();
for line in output::action_lines(&plan, &no_failures, ctx.verbosity) {
eprint_t!("{line}");
}
}
if ctx.verbosity >= -1 {
eprint_t!("{}", output::dry_summary(ctx.account, &plan));
let orphans = suno_core::untracked_audio(&manifest, &execute::walk_audio_files(ctx.dest));
if !orphans.is_empty() {
eprint_t!("{}", output::orphan_report(&orphans));
}
}
if ctx.verb == Verb::Check && ctx.exit_code && prompt::plan_has_changes(&plan) {
return Ok(ExitCode::General);
}
Ok(ExitCode::Ok)
}
pub(super) async fn execute_run(
ctx: &RunCtx<'_>,
mut assembled: Assembled,
store: &mut suno_core::LineageStore,
identity: &Identity,
) -> Result<ExitCode> {
let dest = ctx.dest;
let settings = ctx.settings;
let verbosity = ctx.verbosity;
std::fs::create_dir_all(dest)
.with_context(|| format!("could not create {}", dest.display()))?;
let _lock = logs::acquire_lock(dest)?;
let manifest = logs::load_manifest(dest)?;
let (synced, pending_checks) = synced_lyrics::resolve_synced_lyrics(
&mut assembled.desired,
&manifest,
ctx.client,
ctx.http,
settings.lrc_sidecar,
verbosity,
settings.concurrency,
)
.await;
let plan =
execute::reconcile_run(&assembled.reconcile_inputs(&manifest, dest, &store.albums)).await;
if assembled.graph_changed || identity.owner_dirty() {
logs::save_graph(dest, store)?;
}
if let Some(pin) = identity.pending_pin() {
if verbosity >= -1 {
eprint_t!("{}", pin.notice);
}
if let Some(owner) = store.owner() {
logs::append_owner_pin(dest, pin.action, &owner.user_id, &owner.display_name)?;
}
}
let is_sync = ctx.verb == Verb::Sync && !identity.force_additive();
let delete_count = plan.deletes() + plan.artifact_deletes() + plan.stem_deletes();
if is_sync
&& mass_delete_abort(
assembled.desired.len(),
manifest.len(),
delete_count,
settings.min_newest,
ctx.args.min_newest == Some(0),
ctx.global.yes,
)
{
eprint_t!(
"error: sync aborted -- deletion safety rule triggered\n\nThe listing yielded {} clip(s), which would delete {} of {} local file(s).\nThis is almost certainly a listing error. No files were deleted.\n\nIf you intended to delete everything, pass --min-newest 0 --yes to confirm.",
assembled.desired.len(),
delete_count,
manifest.len()
);
return Ok(ExitCode::Safety);
}
match confirm_decision(
is_sync,
delete_count,
ctx.global.yes,
std::io::stdin().is_terminal(),
) {
Confirm::Proceed => {}
Confirm::Prompt => {
if !prompt::prompt_delete(&plan, verbosity)? {
eprint_t!("Aborted; no changes made.");
return Ok(ExitCode::Ok);
}
}
Confirm::RefuseNonInteractive => {
eprint_t!(
"error: sync would delete {} file(s) but stdin is not a TTY and --yes was not passed\n Pass --yes to confirm, or use 'copy' to skip deletions.",
delete_count
);
return Ok(ExitCode::Safety);
}
}
if verbosity == 0 {
eprint_t!(
"{}",
output::progress_start(ctx.verb.progress_word(), ctx.account, &plan)
);
}
execute::execute_plan(execute::ExecutePlan {
summary_label: ctx.verb.summary_label(),
plan,
desired: &assembled.desired,
manifest,
synced,
pending_checks,
store,
client: ctx.client,
http: ctx.http,
dest,
settings,
account: ctx.account,
verbosity,
library_authoritative: assembled.library_authoritative,
})
.await
}