style(backend): rustfmt the whole tree; gate cargo fmt --check in CI
The backend had never been run through rustfmt. Doing it in one mechanical pass (134 files) so no future functional diff is buried under formatting churn, then gating `cargo fmt --check` in checks.yml so it stays clean. Formatting only — no logic, SQL, or behaviour changed. Verified after the reformat: cargo test 56 passed, clippy --all-targets -D warnings clean, cargo fmt --check clean. This is the deferred cleanup noted when CI's Format step was first left out. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -5,12 +5,12 @@ use anyhow::{Context, Result};
|
||||
use async_zip::tokio::write::ZipFileWriter;
|
||||
use async_zip::{Compression, ZipEntryBuilder};
|
||||
use chrono::{DateTime, Utc};
|
||||
use futures::io::{copy as fcopy, AllowStdIo};
|
||||
use include_dir::{include_dir, Dir};
|
||||
use futures::io::{AllowStdIo, copy as fcopy};
|
||||
use include_dir::{Dir, include_dir};
|
||||
use serde::Serialize;
|
||||
use sqlx::PgPool;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio_util::compat::TokioAsyncReadCompatExt;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -218,7 +218,11 @@ pub async fn invalidate_and_arm(
|
||||
let types: &[&str] = if carried { &["html"] } else { &["zip", "html"] };
|
||||
enqueue_types_at_epoch(&mut *conn, event_id, epoch, types).await?;
|
||||
|
||||
Ok(Some(PendingRegen { event_id, event_name, epoch }))
|
||||
Ok(Some(PendingRegen {
|
||||
event_id,
|
||||
event_name,
|
||||
epoch,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Startup export recovery: re-arm exports for any released event whose keepsake isn't fully
|
||||
@@ -275,7 +279,9 @@ pub async fn recover_exports(
|
||||
continue;
|
||||
}
|
||||
|
||||
tracing::warn!("export recovery: re-arming export jobs for event {event_id} @ epoch {epoch}");
|
||||
tracing::warn!(
|
||||
"export recovery: re-arming export jobs for event {event_id} @ epoch {epoch}"
|
||||
);
|
||||
let mut conn = match pool.acquire().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
@@ -416,7 +422,9 @@ pub fn spawn_export_jobs(
|
||||
if !delay.is_zero() {
|
||||
tokio::time::sleep(delay).await;
|
||||
}
|
||||
if let Err(e) = run_zip_export(event_id, epoch, &pool, &media_path, &export_path, &sse_tx).await {
|
||||
if let Err(e) =
|
||||
run_zip_export(event_id, epoch, &pool, &media_path, &export_path, &sse_tx).await
|
||||
{
|
||||
tracing::error!("ZIP export failed for event {event_id} @ epoch {epoch}: {e:#}");
|
||||
mark_failed(&pool, event_id, "zip", epoch, &e.to_string()).await;
|
||||
}
|
||||
@@ -427,9 +435,16 @@ pub fn spawn_export_jobs(
|
||||
if !delay.is_zero() {
|
||||
tokio::time::sleep(delay).await;
|
||||
}
|
||||
if let Err(e) =
|
||||
run_html_export(event_id, epoch, &event_name2, &pool2, &media_path2, &export_path2, &sse_tx2)
|
||||
.await
|
||||
if let Err(e) = run_html_export(
|
||||
event_id,
|
||||
epoch,
|
||||
&event_name2,
|
||||
&pool2,
|
||||
&media_path2,
|
||||
&export_path2,
|
||||
&sse_tx2,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!("HTML export failed for event {event_id} @ epoch {epoch}: {e:#}");
|
||||
mark_failed(&pool2, event_id, "html", epoch, &e.to_string()).await;
|
||||
@@ -459,8 +474,9 @@ async fn run_zip_export(
|
||||
// here so a failing export can't leak them (which is what fills the disk in the first place).
|
||||
let res = run_zip_export_inner(epoch, event_id, pool, media_path, export_path, sse_tx).await;
|
||||
if res.is_err() {
|
||||
let _ = tokio::fs::remove_file(export_path.join(gen_name(event_id, "Gallery", epoch, ".tmp")))
|
||||
.await;
|
||||
let _ =
|
||||
tokio::fs::remove_file(export_path.join(gen_name(event_id, "Gallery", epoch, ".tmp")))
|
||||
.await;
|
||||
}
|
||||
abandon_if_superseded("ZIP", event_id, epoch, res)
|
||||
}
|
||||
@@ -518,7 +534,11 @@ async fn run_zip_export_inner(
|
||||
let ext = ext_from_path(&row.original_path);
|
||||
let date = row.created_at.format("%Y-%m-%d_%H-%M").to_string();
|
||||
let name_safe = sanitize_name(&row.uploader_name);
|
||||
let folder = if row.mime_type.starts_with("video/") { "Videos" } else { "Photos" };
|
||||
let folder = if row.mime_type.starts_with("video/") {
|
||||
"Videos"
|
||||
} else {
|
||||
"Photos"
|
||||
};
|
||||
let entry_name = format!("{folder}/{date}_{name_safe}_{}.{ext}", row.id);
|
||||
|
||||
let builder = ZipEntryBuilder::new(entry_name.into(), Compression::Stored);
|
||||
@@ -579,7 +599,9 @@ async fn run_zip_export_inner(
|
||||
// IS the publish, atomically. A worker at a dead epoch simply writes a row nobody can see.
|
||||
if !finalize_job(pool, event_id, "zip", epoch, &format!("exports/{out_name}")).await {
|
||||
let _ = tokio::fs::remove_file(&out_path).await;
|
||||
tracing::info!("ZIP export for event {event_id} superseded (epoch {epoch} retired); discarded");
|
||||
tracing::info!(
|
||||
"ZIP export for event {event_id} superseded (epoch {epoch} retired); discarded"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -627,15 +649,25 @@ async fn run_html_export(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let res =
|
||||
run_html_export_inner(epoch, event_id, event_name, pool, media_path, export_path, sse_tx).await;
|
||||
let res = run_html_export_inner(
|
||||
epoch,
|
||||
event_id,
|
||||
event_name,
|
||||
pool,
|
||||
media_path,
|
||||
export_path,
|
||||
sse_tx,
|
||||
)
|
||||
.await;
|
||||
if res.is_err() {
|
||||
// Clean up this generation's temp artifacts so a failing (or abandoned) export can't leak
|
||||
// them — the leak is what fills the disk, which is what corrupts the next archive.
|
||||
let _ =
|
||||
tokio::fs::remove_file(export_path.join(gen_name(event_id, "Memories", epoch, ".tmp"))).await;
|
||||
let _ = tokio::fs::remove_dir_all(export_path.join(format!("viewer_tmp_{event_id}_{epoch}")))
|
||||
.await;
|
||||
tokio::fs::remove_file(export_path.join(gen_name(event_id, "Memories", epoch, ".tmp")))
|
||||
.await;
|
||||
let _ =
|
||||
tokio::fs::remove_dir_all(export_path.join(format!("viewer_tmp_{event_id}_{epoch}")))
|
||||
.await;
|
||||
}
|
||||
abandon_if_superseded("HTML", event_id, epoch, res)
|
||||
}
|
||||
@@ -724,7 +756,10 @@ async fn run_html_export_inner(
|
||||
match ffmpeg_result {
|
||||
Ok(output) if output.status.success() => {}
|
||||
_ => {
|
||||
tracing::warn!("ffmpeg thumbnail failed for upload {}, skipping thumb", row.id);
|
||||
tracing::warn!(
|
||||
"ffmpeg thumbnail failed for upload {}, skipping thumb",
|
||||
row.id
|
||||
);
|
||||
// Missing thumb entry — viewer handles missing thumbs gracefully.
|
||||
}
|
||||
}
|
||||
@@ -904,7 +939,10 @@ async fn run_html_export_inner(
|
||||
let src_file = match tokio::fs::File::open(path).await {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
tracing::warn!("HTML export: skipping media {name} — cannot read {}: {e}", path.display());
|
||||
tracing::warn!(
|
||||
"HTML export: skipping media {name} — cannot read {}: {e}",
|
||||
path.display()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
@@ -939,9 +977,19 @@ async fn run_html_export_inner(
|
||||
// Epoch-guarded finalize — writing `done` at a live epoch IS the publish (readiness is derived
|
||||
// from it), so there is no separate ready flag to flip. If our epoch was retired, we lost:
|
||||
// discard the stale archive.
|
||||
if !finalize_job(pool, event_id, "html", epoch, &format!("exports/{out_name}")).await {
|
||||
if !finalize_job(
|
||||
pool,
|
||||
event_id,
|
||||
"html",
|
||||
epoch,
|
||||
&format!("exports/{out_name}"),
|
||||
)
|
||||
.await
|
||||
{
|
||||
let _ = tokio::fs::remove_file(&out_path).await;
|
||||
tracing::info!("HTML export for event {event_id} superseded (epoch {epoch} retired); discarded");
|
||||
tracing::info!(
|
||||
"HTML export for event {event_id} superseded (epoch {epoch} retired); discarded"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -1077,7 +1125,10 @@ async fn finalize_job(
|
||||
/// `Gallery.<n>.zip`, `Gallery.zip.<n>.tmp`, `viewer_tmp_<event>_<n>`). Returns None if the
|
||||
/// name doesn't fit the shape, so unrelated files are left untouched.
|
||||
fn parse_gen_seq(name: &str, prefix: &str, suffix: &str) -> Option<i64> {
|
||||
name.strip_prefix(prefix)?.strip_suffix(suffix)?.parse::<i64>().ok()
|
||||
name.strip_prefix(prefix)?
|
||||
.strip_suffix(suffix)?
|
||||
.parse::<i64>()
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Best-effort removal of stale per-generation export artifacts for one export type. Deletes
|
||||
@@ -1276,7 +1327,13 @@ fn ext_from_path(path: &str) -> &str {
|
||||
|
||||
fn sanitize_name(name: &str) -> String {
|
||||
name.chars()
|
||||
.map(|c| if c.is_alphanumeric() || c == '-' { c } else { '_' })
|
||||
.map(|c| {
|
||||
if c.is_alphanumeric() || c == '-' {
|
||||
c
|
||||
} else {
|
||||
'_'
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user