Skip to content

Commit 364ddf1

Browse files
committed
Bugfix: Fix dir_stats count drift on file↔dir type changes
- Writer now handles file↔dir type changes in `UpsertEntryV2` automatically: detects the type change, deletes the old entry (propagating correct negative count/size deltas), then inserts fresh with the new type - Previously, in-place updates left `file_count`/`dir_count` wrong in parent `dir_stats` — the old type's count wasn't decremented and the new type's count wasn't incremented - `reconcile_subtree`: expanded the existing dir→file guard to also handle file→dir (sends `DeleteEntryById` before `UpsertEntryV2`), matching the pattern the verifier already uses - Hoisted `get_entry_by_id` from `upsert_update_existing` to `handle_upsert_entry_v2` and pass it down, eliminating a redundant DB read
1 parent 9bf0e00 commit 364ddf1

3 files changed

Lines changed: 46 additions & 22 deletions

File tree

apps/desktop/src-tauri/src/indexing/reconciler.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -443,10 +443,16 @@ pub(super) fn reconcile_subtree(
443443
};
444444

445445
if changed {
446-
// If the entry changed from directory to file (or vice versa),
447-
// delete the old subtree first to avoid orphaning children.
448-
if db_row.is_directory != is_dir && db_row.is_directory {
449-
let _ = writer.send(WriteMessage::DeleteSubtreeById(db_row.id));
446+
// Type change (file↔dir): delete first so counts propagate correctly.
447+
// Dir→file: DeleteSubtreeById removes children + propagates negative deltas.
448+
// File→dir: DeleteEntryById propagates negative file_count delta.
449+
// Then UpsertEntryV2 inserts fresh with the correct type and positive deltas.
450+
if db_row.is_directory != is_dir {
451+
if db_row.is_directory {
452+
let _ = writer.send(WriteMessage::DeleteSubtreeById(db_row.id));
453+
} else {
454+
let _ = writer.send(WriteMessage::DeleteEntryById(db_row.id));
455+
}
450456
}
451457

452458
let _ = writer.send(WriteMessage::UpsertEntryV2 {
@@ -2044,9 +2050,7 @@ mod tests {
20442050
}
20452051
// Sync the writer's next_id counter with what we just inserted
20462052
let db_next_id = IndexStore::get_next_id(&conn).unwrap();
2047-
writer
2048-
.next_id()
2049-
.fetch_max(db_next_id, Ordering::Relaxed);
2053+
writer.next_id().fetch_max(db_next_id, Ordering::Relaxed);
20502054
}
20512055

20522056
/// Create a temp directory outside indexing-excluded paths.

apps/desktop/src-tauri/src/indexing/scanner.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -612,9 +612,7 @@ mod tests {
612612
}
613613
// Sync the writer's next_id counter with what we just inserted
614614
let db_next_id = IndexStore::get_next_id(&conn).unwrap();
615-
writer
616-
.next_id()
617-
.fetch_max(db_next_id, Ordering::Relaxed);
615+
writer.next_id().fetch_max(db_next_id, Ordering::Relaxed);
618616
}
619617

620618
#[test]

apps/desktop/src-tauri/src/indexing/writer.rs

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,38 @@ fn handle_upsert_entry_v2(
656656
// PropagateDeltaById for upserted entries.
657657
match IndexStore::resolve_component(conn, parent_id, &name) {
658658
Ok(Some(existing_id)) => {
659+
// Type change (file↔dir): delete old entry and insert fresh so
660+
// file_count/dir_count deltas propagate correctly. An in-place update
661+
// would leave counts wrong because the old type's count isn't decremented.
662+
let old_entry = IndexStore::get_entry_by_id(conn, existing_id).ok().flatten();
663+
if let Some(ref old) = old_entry
664+
&& old.is_directory != is_directory {
665+
log::debug!(
666+
"Writer: UpsertEntryV2 type change for id={existing_id} \
667+
(was_dir={}, now_dir={is_directory}), converting to delete+insert",
668+
old.is_directory
669+
);
670+
if old.is_directory {
671+
handle_delete_subtree_by_id(conn, existing_id);
672+
} else {
673+
handle_delete_entry_by_id(conn, existing_id);
674+
}
675+
upsert_insert_new(
676+
conn,
677+
parent_id,
678+
&name,
679+
is_directory,
680+
is_symlink,
681+
logical_size,
682+
physical_size,
683+
modified_at,
684+
inode,
685+
should_dedup,
686+
next_id,
687+
);
688+
return;
689+
}
690+
659691
upsert_update_existing(
660692
conn,
661693
existing_id,
@@ -667,6 +699,7 @@ fn handle_upsert_entry_v2(
667699
modified_at,
668700
inode,
669701
should_dedup,
702+
old_entry,
670703
);
671704
}
672705
Ok(None) => {
@@ -707,6 +740,7 @@ fn upsert_update_existing(
707740
modified_at: Option<u64>,
708741
inode: Option<u64>,
709742
should_dedup: bool,
743+
old_entry: Option<EntryRow>,
710744
) {
711745
// Dedup: override sizes if another entry already has sizes for this inode
712746
let (logical_size, physical_size) = if should_dedup
@@ -716,9 +750,6 @@ fn upsert_update_existing(
716750
} else {
717751
(logical_size, physical_size)
718752
};
719-
720-
// Read old entry to compute delta after update
721-
let old_entry = IndexStore::get_entry_by_id(conn, existing_id).ok().flatten();
722753
if let Err(e) = IndexStore::update_entry(
723754
conn,
724755
existing_id,
@@ -731,15 +762,6 @@ fn upsert_update_existing(
731762
) {
732763
log::warn!("Index writer: update_entry failed for id={existing_id}: {e}");
733764
} else if let Some(old) = old_entry {
734-
// Type change (file↔dir) via update is not supported — callers must
735-
// delete+insert for type changes so counts propagate correctly.
736-
if old.is_directory != is_directory {
737-
log::warn!(
738-
"Writer: UpsertEntryV2 type change detected for id={existing_id} \
739-
(was_dir={}, now_dir={is_directory}). Callers should delete+insert instead.",
740-
old.is_directory
741-
);
742-
}
743765
// Propagate size delta if anything changed
744766
let old_logical = old.logical_size.unwrap_or(0) as i64;
745767
let new_logical = logical_size.unwrap_or(0) as i64;

0 commit comments

Comments
 (0)