diff --git a/src/ai/insight_generator.rs b/src/ai/insight_generator.rs index 3617261..db3fb20 100644 --- a/src/ai/insight_generator.rs +++ b/src/ai/insight_generator.rs @@ -4548,7 +4548,10 @@ mod tests { #[test] fn strip_mark_tags_handles_common_patterns() { - assert_eq!(InsightGenerator::strip_mark_tags("plain text"), "plain text"); + assert_eq!( + InsightGenerator::strip_mark_tags("plain text"), + "plain text" + ); assert_eq!( InsightGenerator::strip_mark_tags("…the lake…"), "…the lake…" diff --git a/src/database/knowledge_dao.rs b/src/database/knowledge_dao.rs index 069dd38..06b2b2d 100644 --- a/src/database/knowledge_dao.rs +++ b/src/database/knowledge_dao.rs @@ -235,6 +235,7 @@ pub trait KnowledgeDao: Sync + Send { /// - entity_type: optional, restricts nodes to one type /// - node_limit: caps the number of nodes; lower-fact-count /// entities drop first + /// /// Edges between dropped entities are pruned. Persona scoping /// affects fact_count + edge inclusion (rejected / superseded /// excluded; All vs Single mirrors the existing pattern). @@ -937,7 +938,10 @@ impl KnowledgeDao for SqliteKnowledgeDao { let mut conn = self.connection.lock().expect("KnowledgeDao lock"); let mut q = sql_query(sql).into_boxed(); match persona { - PersonaFilter::Single { user_id, persona_id } => { + PersonaFilter::Single { + user_id, + persona_id, + } => { q = q .bind::(*user_id) .bind::(persona_id.clone()); @@ -977,7 +981,10 @@ impl KnowledgeDao for SqliteKnowledgeDao { // rows flip — REVIEWED survives so the curator can preserve // a hand-approved exception under the same predicate. let touched = match persona { - PersonaFilter::Single { user_id: uid, persona_id: pid } => diesel::update( + PersonaFilter::Single { + user_id: uid, + persona_id: pid, + } => diesel::update( entity_facts .filter(predicate.eq(target_predicate)) .filter(user_id.eq(*uid)) @@ -1282,8 +1289,7 @@ impl KnowledgeDao for SqliteKnowledgeDao { Some(v) => v, None => continue, }; - for b in (a + 1)..indices.len() { - let ib = indices[b]; + for &ib in &indices[a + 1..] { let vb = match &decoded[ib] { Some(v) => v, None => continue, diff --git a/src/database/mod.rs b/src/database/mod.rs index 873b662..4a20702 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -502,9 +502,9 @@ pub trait ExifDao: Sync + Send { /// whose calendar position matches the request's span: /// - `"day"` — same month + day-of-month (any year) /// - `"week"` — same week-of-year (SQLite `%W`, Monday-anchored — - /// close to but not exactly ISO week 8601; the - /// boundary cases at year-start/end can shift by ±1 - /// vs the prior request-time `iso_week()` filter) + /// close to but not exactly ISO week 8601; the boundary cases + /// at year-start/end can shift by ±1 vs the prior request-time + /// `iso_week()` filter) /// - `"month"` — same month (any year) /// /// `tz_offset_minutes` is applied to both sides of the strftime diff --git a/src/database/reconcile.rs b/src/database/reconcile.rs index 57f69f3..2fade4e 100644 --- a/src/database/reconcile.rs +++ b/src/database/reconcile.rs @@ -57,30 +57,28 @@ impl ReconcileStats { /// watcher tick. Errors are logged but never propagated; reconciliation /// is best-effort and a transient DB hiccup must not stall the watcher. pub fn run(conn: &mut SqliteConnection) -> ReconcileStats { - let mut stats = ReconcileStats::default(); - - stats.tagged_photo_hashes_filled = match backfill_tagged_photo_hashes(conn) { - Ok(n) => n, - Err(e) => { - warn!("reconcile: tagged_photo hash backfill failed: {:?}", e); - 0 - } - }; - - stats.photo_insights_hashes_filled = match backfill_photo_insights_hashes(conn) { - Ok(n) => n, - Err(e) => { - warn!("reconcile: photo_insights hash backfill failed: {:?}", e); - 0 - } - }; - - stats.photo_insights_demoted = match collapse_insight_currents(conn) { - Ok(n) => n, - Err(e) => { - warn!("reconcile: photo_insights scalar merge failed: {:?}", e); - 0 - } + let stats = ReconcileStats { + tagged_photo_hashes_filled: match backfill_tagged_photo_hashes(conn) { + Ok(n) => n, + Err(e) => { + warn!("reconcile: tagged_photo hash backfill failed: {:?}", e); + 0 + } + }, + photo_insights_hashes_filled: match backfill_photo_insights_hashes(conn) { + Ok(n) => n, + Err(e) => { + warn!("reconcile: photo_insights hash backfill failed: {:?}", e); + 0 + } + }, + photo_insights_demoted: match collapse_insight_currents(conn) { + Ok(n) => n, + Err(e) => { + warn!("reconcile: photo_insights scalar merge failed: {:?}", e); + 0 + } + }, }; if stats.changed() { diff --git a/src/faces.rs b/src/faces.rs index f4bd5cc..ba47508 100644 --- a/src/faces.rs +++ b/src/faces.rs @@ -2118,7 +2118,10 @@ async fn update_face_handler( // the short context string we surface in the response body — // SQLITE_BUSY here usually means another DAO's writer held the // lock past `busy_timeout` (5s), which is invisible in `{}`. - warn!("PATCH /image/faces/{}: 500 — update_face failed: {:#}", id, e); + warn!( + "PATCH /image/faces/{}: 500 — update_face failed: {:#}", + id, e + ); return HttpResponse::InternalServerError().body(e.to_string()); } }; diff --git a/src/handlers/image.rs b/src/handlers/image.rs index 7266e34..07e977d 100644 --- a/src/handlers/image.rs +++ b/src/handlers/image.rs @@ -183,14 +183,15 @@ pub async fn get_image( // review JPEG, ~1–2 MP). Falls through to NamedFile if no preview is // available, which preserves the historical behavior for callers // that genuinely want the original bytes. - if image_size == PhotoSize::Full && exif::is_tiff_raw(&path) { - if let Some(preview) = exif::extract_embedded_jpeg_preview(&path) { - span.set_status(Status::Ok); - return HttpResponse::Ok() - .content_type("image/jpeg") - .insert_header(("Cache-Control", "public, max-age=3600")) - .body(preview); - } + if image_size == PhotoSize::Full + && exif::is_tiff_raw(&path) + && let Some(preview) = exif::extract_embedded_jpeg_preview(&path) + { + span.set_status(Status::Ok); + return HttpResponse::Ok() + .content_type("image/jpeg") + .insert_header(("Cache-Control", "public, max-age=3600")) + .body(preview); } if let Ok(file) = NamedFile::open(&path) { @@ -706,7 +707,7 @@ pub async fn set_image_date( Ok(row) => { span.set_status(Status::Ok); HttpResponse::Ok().json(build_metadata_response_for_date_mutation( - &library, + library, &normalized_path, row, )) @@ -757,7 +758,7 @@ pub async fn clear_image_date( Ok(row) => { span.set_status(Status::Ok); HttpResponse::Ok().json(build_metadata_response_for_date_mutation( - &library, + library, &normalized_path, row, )) diff --git a/src/knowledge.rs b/src/knowledge.rs index 4c3f5a8..66815b2 100644 --- a/src/knowledge.rs +++ b/src/knowledge.rs @@ -444,8 +444,7 @@ where ) .service(web::resource("/graph").route(web::get().to(get_graph::))) .service( - web::resource("/predicate-stats") - .route(web::get().to(get_predicate_stats::)), + web::resource("/predicate-stats").route(web::get().to(get_predicate_stats::)), ) .service( web::resource("/predicates/{predicate}/bulk-reject") @@ -1261,12 +1260,8 @@ async fn bulk_reject_predicate( let persona = resolve_persona_filter(&req, &claims, &persona_dao); let cx = opentelemetry::Context::current(); let mut dao = dao.lock().expect("Unable to lock KnowledgeDao"); - match dao.bulk_reject_facts_by_predicate( - &cx, - &persona, - &predicate, - Some(("manual", "manual")), - ) { + match dao.bulk_reject_facts_by_predicate(&cx, &persona, &predicate, Some(("manual", "manual"))) + { Ok(rejected) => HttpResponse::Ok().json(BulkRejectResponse { rejected }), Err(e) => { log::error!("bulk_reject_predicate error: {:?}", e); diff --git a/src/libraries.rs b/src/libraries.rs index 59b614a..6248cfa 100644 --- a/src/libraries.rs +++ b/src/libraries.rs @@ -94,7 +94,7 @@ pub fn parse_excluded_dirs_column(raw: Option<&str>) -> Vec { match raw { None => Vec::new(), Some(s) => s - .split(|c: char| matches!(c, ',' | '\n' | '\r')) + .split([',', '\n', '\r']) .map(str::trim) .filter(|s| !s.is_empty()) .map(String::from) @@ -148,10 +148,7 @@ pub fn validate_excluded_dirs_entry(entry: &str) -> Result { if let Some(rel) = trimmed.strip_prefix('/') { // Path form. Reject `..` traversal — `base.join(\"../x\")` doesn't // canonicalise, so `path.starts_with(...)` never matches. - if rel - .split('/') - .any(|seg| seg == "..") - { + if rel.split('/').any(|seg| seg == "..") { return Err(format!( "'{}': '..' segments don't normalise — the prefix-match never fires", trimmed @@ -542,7 +539,10 @@ pub async fn patch_library( { Ok(n) => affected = affected.max(n), Err(e) => { - warn!("PATCH /libraries/{}: enabled update failed: {:?}", lib_id, e); + warn!( + "PATCH /libraries/{}: enabled update failed: {:?}", + lib_id, e + ); return HttpResponse::InternalServerError().body(format!("{}", e)); } } @@ -600,7 +600,9 @@ pub async fn patch_library( ); HttpResponse::Ok().json(lib) } - None => HttpResponse::NotFound().body(format!("library id {} not found after update", lib_id)), + None => { + HttpResponse::NotFound().body(format!("library id {} not found after update", lib_id)) + } } } @@ -930,10 +932,7 @@ mod tests { #[test] fn validate_strips_trailing_slash_on_path_entries() { - assert_eq!( - validate_excluded_dirs_entry("/photos/").unwrap(), - "/photos" - ); + assert_eq!(validate_excluded_dirs_entry("/photos/").unwrap(), "/photos"); assert_eq!( validate_excluded_dirs_entry("/photos//").unwrap(), "/photos"