mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-04 08:56:59 +01:00
feat(core,fuse,cli,gui): discard a queued change and undo it here
A change Drive will not take could be exported or retried, never let go of. The Sync page's Discard button (with a confirmation) and `pdfs sync discard <id>` drop it: a node made here goes, as if deleted before it was sent; a node Drive has goes back to what Drive has, under the same inode, with every change queued on it, while what is queued below it stays. A change on the wire or an open file is refused.
This commit is contained in:
1 parent
da013c29d6
commit
9dfe4c5450
22 files changed
+1530
-710
No files matched your search
@@ -908,6 +908,41 @@ impl Db {
|
||||
Ok(blobs)
|
||||
}
|
||||
|
||||
/// Drop the ops queued on the node `uid` names, and nothing else, returning
|
||||
/// the staged blobs they held. `None`, dropping nothing, while one of them
|
||||
/// is on the wire: it may land after the drop.
|
||||
///
|
||||
/// For a change the user discards on a node Drive has. Unlike
|
||||
/// [`Db::delete_ops_for_uid`], what is queued below the node stays: the
|
||||
/// files made in a folder whose rename is discarded still go in it.
|
||||
pub fn discard_ops_on_node(&self, uid: &str) -> Result<Option<Vec<String>>> {
|
||||
let mut conn = self.conn.lock();
|
||||
let tx = conn.transaction()?;
|
||||
let lid = node_lid(&tx, uid)?;
|
||||
let on = on_node(2);
|
||||
let sending: bool = tx.query_row(
|
||||
&format!("SELECT EXISTS (SELECT 1 FROM pending_op WHERE {on} AND claimed_at <> 0)"),
|
||||
params![uid, lid],
|
||||
|r| r.get(0),
|
||||
)?;
|
||||
if sending {
|
||||
return Ok(None);
|
||||
}
|
||||
let blobs = {
|
||||
let mut stmt = tx.prepare(&format!(
|
||||
"SELECT blob_path FROM pending_op WHERE {on} AND blob_path IS NOT NULL"
|
||||
))?;
|
||||
let rows = stmt.query_map(params![uid, lid], |r| r.get::<_, String>(0))?;
|
||||
rows.collect::<std::result::Result<Vec<_>, _>>()?
|
||||
};
|
||||
tx.execute(
|
||||
&format!("DELETE FROM pending_op WHERE {on}"),
|
||||
params![uid, lid],
|
||||
)?;
|
||||
tx.commit()?;
|
||||
Ok(Some(blobs))
|
||||
}
|
||||
|
||||
/// What [`Db::finish_create`] does to the node rows and pins when a create
|
||||
/// lands, without the op ([`adopt_placeholder_row_tx`]).
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -2633,6 +2633,58 @@ fn failed_atomic_trash_insert_keeps_prior_revision_and_blob_ownership() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Discarding a change to a node Drive has drops that node's ops and nothing
|
||||
/// queued below it, and nothing at all while one of them is on the wire.
|
||||
#[test]
|
||||
fn discarding_a_nodes_change_keeps_what_is_queued_inside_it() {
|
||||
let db = Db::open_in_memory().unwrap();
|
||||
db.upsert_nodes(&[
|
||||
folder("root", None, "My Files"),
|
||||
folder("dir", Some("root"), "dir"),
|
||||
file("inner", "dir", "inner.bin", 1),
|
||||
])
|
||||
.unwrap();
|
||||
let op = |kind: &str, link: &str, blob: Option<&str>| PendingOp {
|
||||
id: 0,
|
||||
kind: kind.to_string(),
|
||||
uid: uid(link).to_string(),
|
||||
parent_uid: None,
|
||||
name: Some("renamed".to_string()),
|
||||
blob_path: blob.map(str::to_string),
|
||||
meta_json: Some("{}".to_string()),
|
||||
created_at: 1,
|
||||
attempts: 0,
|
||||
last_error: None,
|
||||
next_attempt_at: 0,
|
||||
};
|
||||
db.enqueue_op(&op(OP_RENAME, "dir", None)).unwrap();
|
||||
db.enqueue_op(&op(OP_REVISION, "inner", Some("/staging/inner")))
|
||||
.unwrap();
|
||||
|
||||
let dir = uid("dir").to_string();
|
||||
assert_eq!(db.discard_ops_on_node(&dir).unwrap(), Some(vec![]));
|
||||
let left: Vec<String> = db
|
||||
.pending_ops()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|op| op.uid)
|
||||
.collect();
|
||||
assert_eq!(left, vec![uid("inner").to_string()]);
|
||||
|
||||
let inner = uid("inner").to_string();
|
||||
let claimed = db.claim_next_due_op(2).unwrap().unwrap();
|
||||
assert_eq!(claimed.uid, inner);
|
||||
assert_eq!(db.discard_ops_on_node(&inner).unwrap(), None);
|
||||
assert_eq!(db.pending_ops().unwrap().len(), 1);
|
||||
db.record_op_failure(claimed.id, "no answer", 0).unwrap();
|
||||
db.release_op_claim(claimed.id).unwrap();
|
||||
assert_eq!(
|
||||
db.discard_ops_on_node(&inner).unwrap(),
|
||||
Some(vec!["/staging/inner".to_string()])
|
||||
);
|
||||
assert!(db.pending_ops().unwrap().is_empty());
|
||||
}
|
||||
|
||||
/// A revision op names no parent, so the queue alone cannot see that its file
|
||||
/// sits inside a folder being removed. The node tree can: trashing the folder
|
||||
/// has to take the uploads of the files below it along, or they fail forever
|
||||
|
||||
Reference in new issue
Block a user