chore: cargo fmt + clippy --fix sweep across the crate
Pure mechanical cleanup of accumulated drift in files outside the
HLS-content-hash branch's main change set. No behavior change.
- `cargo fmt` on every previously-misformatted file
(`ai/insight_generator.rs`, `database/knowledge_dao.rs`,
`faces.rs`, `knowledge.rs`, `libraries.rs`).
- `cargo clippy --fix`:
- `needless_borrow`: `&library` → `library` in `handlers/image.rs`
(two sites in the photo-listing path).
- Manual clippy pass for warnings clippy emits but can't auto-apply:
- `field_reassign_with_default` in `database/reconcile.rs::run` —
consolidated into a struct-literal initializer.
- `needless_range_loop` in `database/knowledge_dao.rs::union_perceptual_tags`
— inner `for b in (a+1)..indices.len() { let ib = indices[b]; ... }`
becomes `for &ib in &indices[a + 1..] { ... }`.
- Doc-list indentation: continuation lines under nested bullets in
`database/mod.rs::get_memories_in_window` and
`database/knowledge_dao.rs::build_entity_graph` realigned to the
list-item content column.
Deliberately not touched (each deserves its own focused commit, with
testing, rather than getting bundled into a sweep):
- 4× `deprecated count_distinct` in `faces.rs` — diesel API migration
to `AggregateExpressionMethods::aggregate_distinct` may shift result
types; needs verification against the existing stats queries.
- `await_holding_lock` in `knowledge.rs:807` — `std::sync::Mutex` held
across `ollama.generate(...).await`. Genuine concurrency bug; fix
requires understanding the surrounding flow before just dropping
the guard.
- 2× `type_complexity` in `database/mod.rs` — cosmetic, would need a
`type` alias and corresponding callers updated.
- Dead `total_deleted` on `library_maintenance::GcStats` and
`file_scan::enumerate_indexable_files` — both are public surface
retained for future use; deletion is a separate decision.
All 707 tests still pass. Release build clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4548,7 +4548,10 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn strip_mark_tags_handles_common_patterns() {
|
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!(
|
assert_eq!(
|
||||||
InsightGenerator::strip_mark_tags("…the <mark>lake</mark>…"),
|
InsightGenerator::strip_mark_tags("…the <mark>lake</mark>…"),
|
||||||
"…the lake…"
|
"…the lake…"
|
||||||
|
|||||||
@@ -235,6 +235,7 @@ pub trait KnowledgeDao: Sync + Send {
|
|||||||
/// - entity_type: optional, restricts nodes to one type
|
/// - entity_type: optional, restricts nodes to one type
|
||||||
/// - node_limit: caps the number of nodes; lower-fact-count
|
/// - node_limit: caps the number of nodes; lower-fact-count
|
||||||
/// entities drop first
|
/// entities drop first
|
||||||
|
///
|
||||||
/// Edges between dropped entities are pruned. Persona scoping
|
/// Edges between dropped entities are pruned. Persona scoping
|
||||||
/// affects fact_count + edge inclusion (rejected / superseded
|
/// affects fact_count + edge inclusion (rejected / superseded
|
||||||
/// excluded; All vs Single mirrors the existing pattern).
|
/// 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 conn = self.connection.lock().expect("KnowledgeDao lock");
|
||||||
let mut q = sql_query(sql).into_boxed();
|
let mut q = sql_query(sql).into_boxed();
|
||||||
match persona {
|
match persona {
|
||||||
PersonaFilter::Single { user_id, persona_id } => {
|
PersonaFilter::Single {
|
||||||
|
user_id,
|
||||||
|
persona_id,
|
||||||
|
} => {
|
||||||
q = q
|
q = q
|
||||||
.bind::<Integer, _>(*user_id)
|
.bind::<Integer, _>(*user_id)
|
||||||
.bind::<Text, _>(persona_id.clone());
|
.bind::<Text, _>(persona_id.clone());
|
||||||
@@ -977,7 +981,10 @@ impl KnowledgeDao for SqliteKnowledgeDao {
|
|||||||
// rows flip — REVIEWED survives so the curator can preserve
|
// rows flip — REVIEWED survives so the curator can preserve
|
||||||
// a hand-approved exception under the same predicate.
|
// a hand-approved exception under the same predicate.
|
||||||
let touched = match persona {
|
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
|
entity_facts
|
||||||
.filter(predicate.eq(target_predicate))
|
.filter(predicate.eq(target_predicate))
|
||||||
.filter(user_id.eq(*uid))
|
.filter(user_id.eq(*uid))
|
||||||
@@ -1282,8 +1289,7 @@ impl KnowledgeDao for SqliteKnowledgeDao {
|
|||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
for b in (a + 1)..indices.len() {
|
for &ib in &indices[a + 1..] {
|
||||||
let ib = indices[b];
|
|
||||||
let vb = match &decoded[ib] {
|
let vb = match &decoded[ib] {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => continue,
|
None => continue,
|
||||||
|
|||||||
+3
-3
@@ -502,9 +502,9 @@ pub trait ExifDao: Sync + Send {
|
|||||||
/// whose calendar position matches the request's span:
|
/// whose calendar position matches the request's span:
|
||||||
/// - `"day"` — same month + day-of-month (any year)
|
/// - `"day"` — same month + day-of-month (any year)
|
||||||
/// - `"week"` — same week-of-year (SQLite `%W`, Monday-anchored —
|
/// - `"week"` — same week-of-year (SQLite `%W`, Monday-anchored —
|
||||||
/// close to but not exactly ISO week 8601; the
|
/// close to but not exactly ISO week 8601; the boundary cases
|
||||||
/// boundary cases at year-start/end can shift by ±1
|
/// at year-start/end can shift by ±1 vs the prior request-time
|
||||||
/// vs the prior request-time `iso_week()` filter)
|
/// `iso_week()` filter)
|
||||||
/// - `"month"` — same month (any year)
|
/// - `"month"` — same month (any year)
|
||||||
///
|
///
|
||||||
/// `tz_offset_minutes` is applied to both sides of the strftime
|
/// `tz_offset_minutes` is applied to both sides of the strftime
|
||||||
|
|||||||
+22
-24
@@ -57,30 +57,28 @@ impl ReconcileStats {
|
|||||||
/// watcher tick. Errors are logged but never propagated; reconciliation
|
/// watcher tick. Errors are logged but never propagated; reconciliation
|
||||||
/// is best-effort and a transient DB hiccup must not stall the watcher.
|
/// is best-effort and a transient DB hiccup must not stall the watcher.
|
||||||
pub fn run(conn: &mut SqliteConnection) -> ReconcileStats {
|
pub fn run(conn: &mut SqliteConnection) -> ReconcileStats {
|
||||||
let mut stats = ReconcileStats::default();
|
let stats = ReconcileStats {
|
||||||
|
tagged_photo_hashes_filled: match backfill_tagged_photo_hashes(conn) {
|
||||||
stats.tagged_photo_hashes_filled = match backfill_tagged_photo_hashes(conn) {
|
Ok(n) => n,
|
||||||
Ok(n) => n,
|
Err(e) => {
|
||||||
Err(e) => {
|
warn!("reconcile: tagged_photo hash backfill failed: {:?}", e);
|
||||||
warn!("reconcile: tagged_photo hash backfill failed: {:?}", e);
|
0
|
||||||
0
|
}
|
||||||
}
|
},
|
||||||
};
|
photo_insights_hashes_filled: match backfill_photo_insights_hashes(conn) {
|
||||||
|
Ok(n) => n,
|
||||||
stats.photo_insights_hashes_filled = match backfill_photo_insights_hashes(conn) {
|
Err(e) => {
|
||||||
Ok(n) => n,
|
warn!("reconcile: photo_insights hash backfill failed: {:?}", e);
|
||||||
Err(e) => {
|
0
|
||||||
warn!("reconcile: photo_insights hash backfill failed: {:?}", e);
|
}
|
||||||
0
|
},
|
||||||
}
|
photo_insights_demoted: match collapse_insight_currents(conn) {
|
||||||
};
|
Ok(n) => n,
|
||||||
|
Err(e) => {
|
||||||
stats.photo_insights_demoted = match collapse_insight_currents(conn) {
|
warn!("reconcile: photo_insights scalar merge failed: {:?}", e);
|
||||||
Ok(n) => n,
|
0
|
||||||
Err(e) => {
|
}
|
||||||
warn!("reconcile: photo_insights scalar merge failed: {:?}", e);
|
},
|
||||||
0
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if stats.changed() {
|
if stats.changed() {
|
||||||
|
|||||||
+4
-1
@@ -2118,7 +2118,10 @@ async fn update_face_handler<D: FaceDao>(
|
|||||||
// the short context string we surface in the response body —
|
// the short context string we surface in the response body —
|
||||||
// SQLITE_BUSY here usually means another DAO's writer held the
|
// SQLITE_BUSY here usually means another DAO's writer held the
|
||||||
// lock past `busy_timeout` (5s), which is invisible in `{}`.
|
// 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());
|
return HttpResponse::InternalServerError().body(e.to_string());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+11
-10
@@ -183,14 +183,15 @@ pub async fn get_image(
|
|||||||
// review JPEG, ~1–2 MP). Falls through to NamedFile if no preview is
|
// review JPEG, ~1–2 MP). Falls through to NamedFile if no preview is
|
||||||
// available, which preserves the historical behavior for callers
|
// available, which preserves the historical behavior for callers
|
||||||
// that genuinely want the original bytes.
|
// that genuinely want the original bytes.
|
||||||
if image_size == PhotoSize::Full && exif::is_tiff_raw(&path) {
|
if image_size == PhotoSize::Full
|
||||||
if let Some(preview) = exif::extract_embedded_jpeg_preview(&path) {
|
&& exif::is_tiff_raw(&path)
|
||||||
span.set_status(Status::Ok);
|
&& let Some(preview) = exif::extract_embedded_jpeg_preview(&path)
|
||||||
return HttpResponse::Ok()
|
{
|
||||||
.content_type("image/jpeg")
|
span.set_status(Status::Ok);
|
||||||
.insert_header(("Cache-Control", "public, max-age=3600"))
|
return HttpResponse::Ok()
|
||||||
.body(preview);
|
.content_type("image/jpeg")
|
||||||
}
|
.insert_header(("Cache-Control", "public, max-age=3600"))
|
||||||
|
.body(preview);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(file) = NamedFile::open(&path) {
|
if let Ok(file) = NamedFile::open(&path) {
|
||||||
@@ -706,7 +707,7 @@ pub async fn set_image_date(
|
|||||||
Ok(row) => {
|
Ok(row) => {
|
||||||
span.set_status(Status::Ok);
|
span.set_status(Status::Ok);
|
||||||
HttpResponse::Ok().json(build_metadata_response_for_date_mutation(
|
HttpResponse::Ok().json(build_metadata_response_for_date_mutation(
|
||||||
&library,
|
library,
|
||||||
&normalized_path,
|
&normalized_path,
|
||||||
row,
|
row,
|
||||||
))
|
))
|
||||||
@@ -757,7 +758,7 @@ pub async fn clear_image_date(
|
|||||||
Ok(row) => {
|
Ok(row) => {
|
||||||
span.set_status(Status::Ok);
|
span.set_status(Status::Ok);
|
||||||
HttpResponse::Ok().json(build_metadata_response_for_date_mutation(
|
HttpResponse::Ok().json(build_metadata_response_for_date_mutation(
|
||||||
&library,
|
library,
|
||||||
&normalized_path,
|
&normalized_path,
|
||||||
row,
|
row,
|
||||||
))
|
))
|
||||||
|
|||||||
+3
-8
@@ -444,8 +444,7 @@ where
|
|||||||
)
|
)
|
||||||
.service(web::resource("/graph").route(web::get().to(get_graph::<D>)))
|
.service(web::resource("/graph").route(web::get().to(get_graph::<D>)))
|
||||||
.service(
|
.service(
|
||||||
web::resource("/predicate-stats")
|
web::resource("/predicate-stats").route(web::get().to(get_predicate_stats::<D>)),
|
||||||
.route(web::get().to(get_predicate_stats::<D>)),
|
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/predicates/{predicate}/bulk-reject")
|
web::resource("/predicates/{predicate}/bulk-reject")
|
||||||
@@ -1261,12 +1260,8 @@ async fn bulk_reject_predicate<D: KnowledgeDao + 'static>(
|
|||||||
let persona = resolve_persona_filter(&req, &claims, &persona_dao);
|
let persona = resolve_persona_filter(&req, &claims, &persona_dao);
|
||||||
let cx = opentelemetry::Context::current();
|
let cx = opentelemetry::Context::current();
|
||||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||||
match dao.bulk_reject_facts_by_predicate(
|
match dao.bulk_reject_facts_by_predicate(&cx, &persona, &predicate, Some(("manual", "manual")))
|
||||||
&cx,
|
{
|
||||||
&persona,
|
|
||||||
&predicate,
|
|
||||||
Some(("manual", "manual")),
|
|
||||||
) {
|
|
||||||
Ok(rejected) => HttpResponse::Ok().json(BulkRejectResponse { rejected }),
|
Ok(rejected) => HttpResponse::Ok().json(BulkRejectResponse { rejected }),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("bulk_reject_predicate error: {:?}", e);
|
log::error!("bulk_reject_predicate error: {:?}", e);
|
||||||
|
|||||||
+10
-11
@@ -94,7 +94,7 @@ pub fn parse_excluded_dirs_column(raw: Option<&str>) -> Vec<String> {
|
|||||||
match raw {
|
match raw {
|
||||||
None => Vec::new(),
|
None => Vec::new(),
|
||||||
Some(s) => s
|
Some(s) => s
|
||||||
.split(|c: char| matches!(c, ',' | '\n' | '\r'))
|
.split([',', '\n', '\r'])
|
||||||
.map(str::trim)
|
.map(str::trim)
|
||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
.map(String::from)
|
.map(String::from)
|
||||||
@@ -148,10 +148,7 @@ pub fn validate_excluded_dirs_entry(entry: &str) -> Result<String, String> {
|
|||||||
if let Some(rel) = trimmed.strip_prefix('/') {
|
if let Some(rel) = trimmed.strip_prefix('/') {
|
||||||
// Path form. Reject `..` traversal — `base.join(\"../x\")` doesn't
|
// Path form. Reject `..` traversal — `base.join(\"../x\")` doesn't
|
||||||
// canonicalise, so `path.starts_with(...)` never matches.
|
// canonicalise, so `path.starts_with(...)` never matches.
|
||||||
if rel
|
if rel.split('/').any(|seg| seg == "..") {
|
||||||
.split('/')
|
|
||||||
.any(|seg| seg == "..")
|
|
||||||
{
|
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"'{}': '..' segments don't normalise — the prefix-match never fires",
|
"'{}': '..' segments don't normalise — the prefix-match never fires",
|
||||||
trimmed
|
trimmed
|
||||||
@@ -542,7 +539,10 @@ pub async fn patch_library(
|
|||||||
{
|
{
|
||||||
Ok(n) => affected = affected.max(n),
|
Ok(n) => affected = affected.max(n),
|
||||||
Err(e) => {
|
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));
|
return HttpResponse::InternalServerError().body(format!("{}", e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -600,7 +600,9 @@ pub async fn patch_library(
|
|||||||
);
|
);
|
||||||
HttpResponse::Ok().json(lib)
|
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]
|
#[test]
|
||||||
fn validate_strips_trailing_slash_on_path_entries() {
|
fn validate_strips_trailing_slash_on_path_entries() {
|
||||||
assert_eq!(
|
assert_eq!(validate_excluded_dirs_entry("/photos/").unwrap(), "/photos");
|
||||||
validate_excluded_dirs_entry("/photos/").unwrap(),
|
|
||||||
"/photos"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
validate_excluded_dirs_entry("/photos//").unwrap(),
|
validate_excluded_dirs_entry("/photos//").unwrap(),
|
||||||
"/photos"
|
"/photos"
|
||||||
|
|||||||
Reference in New Issue
Block a user