fix(desktop): nuke all drive-scoped tables on drive removal (#165)

nuke_drive only deleted file_metadata rows; queued/finished tasks,
upload sessions, and cached drive props lingered for removed drives.
Now deletes all four tables in one transaction. Regression test covers
metadata + task_queue + drive_props isolation.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
pull/3587/head
Tomas Dvorak 2 weeks ago
parent f6faf17a98
commit 75d31e24a9

@ -27,13 +27,35 @@ impl InventoryDb {
Ok(()) Ok(())
} }
/// Delete every inventory row belonging to a removed drive: file metadata,
/// queued/finished tasks, upload sessions, and cached drive props.
/// Runs in a single transaction so a drive is never half-removed.
pub fn nuke_drive(&self, drive: &str) -> Result<()> { pub fn nuke_drive(&self, drive: &str) -> Result<()> {
use crate::inventory::schema::{drive_props, task_queue, upload_sessions};
let mut conn = self.connection()?; let mut conn = self.connection()?;
diesel::delete( (&mut *conn)
file_metadata_dsl::file_metadata.filter(file_metadata_dsl::drive_id.eq(drive)), .transaction::<(), diesel::result::Error, _>(|tx| {
) diesel::delete(
.execute(&mut conn) file_metadata_dsl::file_metadata
.context("Failed to delete inventory rows for drive")?; .filter(file_metadata_dsl::drive_id.eq(drive)),
)
.execute(tx)?;
diesel::delete(
task_queue::table.filter(task_queue::drive_id.eq(drive)),
)
.execute(tx)?;
diesel::delete(
upload_sessions::table.filter(upload_sessions::drive_id.eq(drive)),
)
.execute(tx)?;
diesel::delete(
drive_props::table.filter(drive_props::drive_id.eq(drive)),
)
.execute(tx)?;
Ok(())
})
.context("Failed to delete inventory rows for drive")?;
Ok(()) Ok(())
} }
@ -403,3 +425,70 @@ impl FileMetadataChangeset {
}) })
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::inventory::models::{DrivePropsUpdate, NewTaskRecord};
use uuid::Uuid;
fn test_db() -> (tempfile::TempDir, InventoryDb) {
let dir = tempfile::tempdir().unwrap();
let db = InventoryDb::with_path(dir.path().join("meta.db")).unwrap();
(dir, db)
}
#[test]
fn nuke_drive_clears_all_drive_scoped_tables() {
let (_dir, db) = test_db();
let drive_a = Uuid::new_v4().to_string();
let drive_b = Uuid::new_v4().to_string();
for drive in [&drive_a, &drive_b] {
db.insert(&MetadataEntry::new(
Uuid::parse_str(drive).unwrap(),
format!("{drive}/file.txt"),
false,
))
.unwrap();
db.insert_task_if_not_exist(&NewTaskRecord::new(
format!("task-{drive}"),
drive.clone(),
"upload",
format!("{drive}/file.txt"),
))
.unwrap();
db.upsert_drive_props(drive, DrivePropsUpdate {
capacity: Some(None),
..Default::default()
})
.unwrap();
}
db.nuke_drive(&drive_a).unwrap();
// Drive A: everything gone
assert!(db
.query_by_path(&format!("{drive_a}/file.txt"))
.unwrap()
.is_none());
assert!(db
.list_tasks(Some(&drive_a), None)
.unwrap()
.is_empty());
assert!(!db.has_drive_props(&drive_a).unwrap());
// Drive B: untouched
assert!(db
.query_by_path(&format!("{drive_b}/file.txt"))
.unwrap()
.is_some());
assert_eq!(
db.list_tasks(Some(&drive_b), None)
.unwrap()
.len(),
1
);
assert!(db.has_drive_props(&drive_b).unwrap());
}
}

Loading…
Cancel
Save