mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-04 16: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
@@ -2,8 +2,10 @@
|
||||
//! yet, why, and a way to stop waiting out a backoff.
|
||||
//!
|
||||
//! Read-only apart from "retry now", which only moves a row's next-attempt time,
|
||||
//! and "export", which copies a staged blob out. Nothing here drops a queued op:
|
||||
//! its staged blob is the only copy of the user's write (see `docs/RECOVERY.md`).
|
||||
//! "export", which copies a staged blob out, and "discard". Only a discard the
|
||||
//! user asks for drops a queued op: its staged blob is the only copy of the
|
||||
//! user's write (see `docs/RECOVERY.md`), so it is the user's call, made after
|
||||
//! an export or for a change with no content.
|
||||
|
||||
use super::*;
|
||||
use std::io::Write;
|
||||
@@ -91,6 +93,134 @@ impl Core {
|
||||
info!(id, dest = %dest.display(), written, "exported a queued change's content");
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
/// Drop queued op `id` and undo the local change it stood for. Returns the
|
||||
/// node's path.
|
||||
///
|
||||
/// A node only made here goes, as if it had been deleted before it was
|
||||
/// sent. A node Drive has goes back to what Drive has: every change queued
|
||||
/// on it goes, since each was made on top of the one before. What is queued
|
||||
/// below it stays.
|
||||
pub(crate) fn discard_pending_op(&self, id: i64) -> CoreResult<String> {
|
||||
let ops = self
|
||||
.db
|
||||
.pending_ops()
|
||||
.map_err(|e| CoreError::internal(format!("listing the queue: {e}")))?;
|
||||
let op = ops
|
||||
.into_iter()
|
||||
.find(|op| op.id == id)
|
||||
.ok_or_else(|| CoreError::not_found(format!("no queued change {id}")))?;
|
||||
let uid = parse_node_uid(&op.uid)
|
||||
.ok_or_else(|| CoreError::internal(format!("queued change {id} names {}", op.uid)))?;
|
||||
let path = self
|
||||
.db
|
||||
.node_path(&op.uid)
|
||||
.ok()
|
||||
.flatten()
|
||||
.filter(|path| !path.is_empty())
|
||||
.or(op.name)
|
||||
.unwrap_or_else(|| op.uid.clone());
|
||||
if self.is_open_anywhere(&uid) {
|
||||
return Err(CoreError::conflict(format!(
|
||||
"{path} is open; close it first"
|
||||
)));
|
||||
}
|
||||
if is_local_uid(&uid) {
|
||||
self.discard_local_node(&uid, &path)?;
|
||||
} else {
|
||||
self.discard_remote_change(&uid, &path)?;
|
||||
}
|
||||
info!(id, %uid, path, "discarded a queued change");
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Forget a node made here, and the ops that would have sent it.
|
||||
fn discard_local_node(&self, uid: &NodeUid, path: &str) -> CoreResult<()> {
|
||||
// Its children's ops go with its own, and their content with them.
|
||||
if self
|
||||
.db
|
||||
.has_children(uid)
|
||||
.map_err(|e| CoreError::internal(format!("listing {path}: {e}")))?
|
||||
{
|
||||
return Err(CoreError::conflict(format!(
|
||||
"{path} is not empty; discard what is in it first"
|
||||
)));
|
||||
}
|
||||
self.discard_queued_ops(uid)
|
||||
.map_err(|_| CoreError::internal(format!("dropping the changes to {path}")))?;
|
||||
self.for_each_mount(|st, notify| {
|
||||
background::forget_and_notify(st, notify, uid);
|
||||
});
|
||||
self.cache.evict(uid);
|
||||
self.evict_reader(uid);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Put a node Drive has back as Drive has it.
|
||||
fn discard_remote_change(&self, uid: &NodeUid, path: &str) -> CoreResult<()> {
|
||||
// Asked first: the change is only dropped once what replaces it is known.
|
||||
let node = self
|
||||
.fetch_node_remote(uid)
|
||||
.map_err(|e| CoreError::from_api(&e, "reading the node from Drive"))?
|
||||
.filter(|node| !node.trashed);
|
||||
self.cancel_upload(uid);
|
||||
let blobs = self
|
||||
.db
|
||||
.discard_ops_on_node(&uid.to_string())
|
||||
.map_err(|e| CoreError::internal(format!("dropping the changes to {path}: {e}")))?
|
||||
.ok_or_else(|| {
|
||||
CoreError::conflict(format!("{path} is being sent right now; try again"))
|
||||
})?;
|
||||
self.release_dropped_ops(uid, blobs);
|
||||
self.hidden.lock().remove(uid);
|
||||
match node {
|
||||
Some(node) => self.for_each_mount(|st, notify| {
|
||||
put_back(st, notify, node);
|
||||
}),
|
||||
None => self.for_each_mount(|st, notify| {
|
||||
background::forget_and_notify(st, notify, uid);
|
||||
}),
|
||||
}
|
||||
self.cache.evict(uid);
|
||||
self.evict_reader(uid);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Show `node` as Drive has it, where Drive has it, under the inode it has.
|
||||
fn put_back(st: &mut State, notify: &mut NotifyBatch, node: Node) {
|
||||
let parent = node.parent_uid.as_ref().and_then(|parent| {
|
||||
st.by_uid
|
||||
.get(parent)
|
||||
.copied()
|
||||
.map(|ino| (ino, parent.clone()))
|
||||
});
|
||||
let here = st.by_uid.get(&node.uid).and_then(|ino| {
|
||||
st.entries
|
||||
.get(ino)
|
||||
.map(|entry| (*ino, entry.parent, entry.node.name.clone()))
|
||||
});
|
||||
match (here, parent) {
|
||||
(Some((ino, from, name)), Some((to, to_uid))) => {
|
||||
st.relocate(ino, from, to, &to_uid, &node.name);
|
||||
st.write_through(node.clone());
|
||||
st.intern_mem(to, node);
|
||||
notify.inval_entry(from, name);
|
||||
notify.inval_inode(ino);
|
||||
}
|
||||
(here, parent) => {
|
||||
if let Some((_, from, name)) = here {
|
||||
st.forget_mem(&node.uid);
|
||||
st.invalidate_listing(from);
|
||||
notify.inval_entry(from, name);
|
||||
}
|
||||
if let Some((to, _)) = parent {
|
||||
st.invalidate_listing(to);
|
||||
notify.inval_entry(to, node.name.clone());
|
||||
}
|
||||
st.write_through(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy `blob` to `dest`, which must not exist: never replace something the
|
||||
|
||||
Reference in new issue
Block a user