mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-04 00:47:00 +01:00
fix(core): a folder listed just as its create lands is listed from the database (B177)
This commit is contained in:
1 parent
4f141c78ca
commit
0f3c06f982
5 files changed
+75
-5
No files matched your search
@@ -533,9 +533,10 @@ impl Db {
|
|||||||
/// re-enumerates from the remote on next access.
|
/// re-enumerates from the remote on next access.
|
||||||
pub fn set_listed(&self, uid: &NodeUid, listed: bool) -> Result<()> {
|
pub fn set_listed(&self, uid: &NodeUid, listed: bool) -> Result<()> {
|
||||||
let conn = self.conn.lock();
|
let conn = self.conn.lock();
|
||||||
|
let (key, uid) = row_key(&uid.to_string());
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"UPDATE nodes SET listed = ?2 WHERE uid = ?1",
|
&format!("UPDATE nodes SET listed = ?2 WHERE {key} = ?1"),
|
||||||
params![uid.to_string(), listed as i64],
|
params![uid, listed as i64],
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -694,10 +695,11 @@ impl Db {
|
|||||||
/// server head, so changes made while unmounted are still applied (P2).
|
/// server head, so changes made while unmounted are still applied (P2).
|
||||||
pub fn children_if_listed(&self, parent: &NodeUid) -> Result<Option<Vec<Node>>> {
|
pub fn children_if_listed(&self, parent: &NodeUid) -> Result<Option<Vec<Node>>> {
|
||||||
let conn = self.read();
|
let conn = self.read();
|
||||||
|
let (key, folder) = row_key(&parent.to_string());
|
||||||
let listed: Option<i64> = conn
|
let listed: Option<i64> = conn
|
||||||
.query_row(
|
.query_row(
|
||||||
"SELECT listed FROM nodes WHERE uid = ?1",
|
&format!("SELECT listed FROM nodes WHERE {key} = ?1"),
|
||||||
params![parent.to_string()],
|
params![folder],
|
||||||
|r| r.get(0),
|
|r| r.get(0),
|
||||||
)
|
)
|
||||||
.optional()?;
|
.optional()?;
|
||||||
@@ -792,6 +794,17 @@ pub(super) fn node_lid(conn: &rusqlite::Connection, uid: &str) -> Result<Option<
|
|||||||
Ok(row.optional()?)
|
Ok(row.optional()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The column and value that find `uid`'s own row. A stand-in is found by its
|
||||||
|
/// local id: a create that lands gives the row the uid Drive made before the
|
||||||
|
/// tree has it, and the tree asks by the stand-in until then (`docs/BUGS.md`
|
||||||
|
/// B177).
|
||||||
|
fn row_key(uid: &str) -> (&'static str, Value) {
|
||||||
|
match local_lid(uid) {
|
||||||
|
Some(lid) => ("lid", Value::Integer(lid)),
|
||||||
|
None => ("uid", Value::Text(uid.to_owned())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The column and value a row names `parent` by: its local id when it has a
|
/// The column and value a row names `parent` by: its local id when it has a
|
||||||
/// row, which the children keep when it lands, or its uid when it has none,
|
/// row, which the children keep when it lands, or its uid when it has none,
|
||||||
/// as a device folder's root does.
|
/// as a device folder's root does.
|
||||||
|
|||||||
@@ -3282,6 +3282,37 @@ fn a_stand_in_keeps_its_access_once_its_create_has_landed() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_stand_in_folder_stays_listed_once_its_create_has_landed() {
|
||||||
|
let db = Db::open_in_memory().unwrap();
|
||||||
|
db.upsert_node(&folder("parent", None, "My Files")).unwrap();
|
||||||
|
let parent = uid("parent").to_string();
|
||||||
|
let lid = db.add_local_row(&parent, "dir", true, 1).unwrap();
|
||||||
|
let local = local_uid(lid);
|
||||||
|
let stand_in = NodeUid::new(VolumeId::from("local"), LinkId::from(lid.to_string()));
|
||||||
|
let mut node = folder("new", Some("parent"), "dir");
|
||||||
|
node.uid = stand_in.clone();
|
||||||
|
db.upsert_node(&node).unwrap();
|
||||||
|
db.set_listed(&stand_in, true).unwrap();
|
||||||
|
let real = uid("real").to_string();
|
||||||
|
let (id, _) = db
|
||||||
|
.enqueue_op(&op_on(OP_MKDIR, &local, Some(&parent)))
|
||||||
|
.unwrap();
|
||||||
|
db.claim_next_due_op(1).unwrap().unwrap();
|
||||||
|
db.finish_create(id, None, &landing(&local, &real, &parent, "dir"), |_| None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// The tree still names the folder by its stand-in for a moment; a listing
|
||||||
|
// asked by it must not go to Drive, which has never heard of it.
|
||||||
|
assert!(
|
||||||
|
db.children_if_listed(&stand_in)
|
||||||
|
.unwrap()
|
||||||
|
.is_some_and(|kids| kids.is_empty())
|
||||||
|
);
|
||||||
|
db.set_listed(&stand_in, false).unwrap();
|
||||||
|
assert!(db.children_if_listed(&uid("real")).unwrap().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_node_queued_under_both_its_uids_is_sent_one_op_at_a_time() {
|
fn a_node_queued_under_both_its_uids_is_sent_one_op_at_a_time() {
|
||||||
let db = Db::open_in_memory().unwrap();
|
let db = Db::open_in_memory().unwrap();
|
||||||
|
|||||||
@@ -12,6 +12,28 @@ Conventions:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## B177 — A folder listed just as its create lands answers "Input/output error"
|
||||||
|
|
||||||
|
**Status:** Fixed (unverified).
|
||||||
|
**Found:** 2026-10-04, by the account run on 378667f, on an on-demand sync folder: B100's
|
||||||
|
`lstat` of `b100-copy/dir-0/sub-0/file-00.bin` failed with `EIO`. The daemon logged
|
||||||
|
`enumerate folder children failed folder_uid=local~163903 ... InvalidEncryptedIdFormat` 85 ms
|
||||||
|
before `dir-0`'s create landed.
|
||||||
|
|
||||||
|
**Where:** `Db::children_if_listed` and `Db::set_listed` in `crates/pdfs-core/src/db/nodes.rs`.
|
||||||
|
|
||||||
|
**Cause.** A folder made on the mount is marked as listed, so its listing comes from the
|
||||||
|
database and never from Drive, which has not heard of it. The flag was looked up by uid. In
|
||||||
|
B173's gap the row already has the uid Drive gave the folder and the tree still asks by the
|
||||||
|
stand-in, so the lookup found no row, and the listing went to Drive with the stand-in.
|
||||||
|
|
||||||
|
**Fix.** A stand-in finds its row by its local id, before and after its create lands.
|
||||||
|
|
||||||
|
**Test:** `a_stand_in_folder_stays_listed_once_its_create_has_landed` in
|
||||||
|
`crates/pdfs-core/src/db/tests.rs`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## B176 — A write closed just as its file's create lands is dropped
|
## B176 — A write closed just as its file's create lands is dropped
|
||||||
|
|
||||||
**Status:** Fixed (unverified).
|
**Status:** Fixed (unverified).
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ by its local id, in the queue, the stored tree and pins alike.
|
|||||||
- **A file replaced by a rename just as it reached Drive is removed from Drive too.** It no
|
- **A file replaced by a rename just as it reached Drive is removed from Drive too.** It no
|
||||||
longer stayed there, and the next rename of its name no longer moved it instead (B175).
|
longer stayed there, and the next rename of its name no longer moved it instead (B175).
|
||||||
- **A write closed just as its file reached Drive is no longer lost** (B176).
|
- **A write closed just as its file reached Drive is no longer lost** (B176).
|
||||||
|
- **Opening a folder just as it reaches Drive no longer fails with "Input/output error"**
|
||||||
|
(B177).
|
||||||
- **A file renamed just as its upload finished keeps its new name.** The mount no longer showed
|
- **A file renamed just as its upload finished keeps its new name.** The mount no longer showed
|
||||||
the old name, and a second rename no longer failed with "No such file or directory" (B152,
|
the old name, and a second rename no longer failed with "No such file or directory" (B152,
|
||||||
B172).
|
B172).
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Tracks `docs/MILESTONE-3.0.0.md`. Update it with every commit that moves a phase
|
|||||||
- **Phase 2**: local ids, inode = local id, one tree for every mount (2.10.0).
|
- **Phase 2**: local ids, inode = local id, one tree for every mount (2.10.0).
|
||||||
- **Phase 3, first part**: mkdir, create, rename, unlink and rmdir on the mount are recorded
|
- **Phase 3, first part**: mkdir, create, rename, unlink and rmdir on the mount are recorded
|
||||||
locally and sent from the queue; `"local_first": false` brings back the old path (5e0f30c).
|
locally and sent from the queue; `"local_first": false` brings back the old path (5e0f30c).
|
||||||
- Fixes from the simulation and account runs since then: B127 and B141 to B176.
|
- Fixes from the simulation and account runs since then: B127 and B141 to B177.
|
||||||
- **Sync issues, first cut** (§6.2): refusals sorted into issues, shown at once in the app,
|
- **Sync issues, first cut** (§6.2): refusals sorted into issues, shown at once in the app,
|
||||||
tray and CLI; Export button and `pdfs sync export`; `pdfs sync issues`.
|
tray and CLI; Export button and `pdfs sync export`; `pdfs sync issues`.
|
||||||
|
|
||||||
@@ -121,6 +121,8 @@ Tracks `docs/MILESTONE-3.0.0.md`. Update it with every commit that moves a phase
|
|||||||
in the same gap was refused with `EACCES` (B174, fixed). The loop on 378667f failed
|
in the same gap was refused with `EACCES` (B174, fixed). The loop on 378667f failed
|
||||||
seed 8: a file replaced by a rename in that gap stayed on Drive (B175, fixed), and
|
seed 8: a file replaced by a rename in that gap stayed on Drive (B175, fixed), and
|
||||||
seed 12: a write closed as its file's create landed was dropped (B176, fixed).
|
seed 12: a write closed as its file's create landed was dropped (B176, fixed).
|
||||||
|
The account run on 378667f: a folder listed as its create landed asked Drive by
|
||||||
|
its stand-in and answered `EIO` (B177, fixed).
|
||||||
5. **Release**: 2.10.0 is tagged; the 2.9.0 tag still waits for the user's go-ahead.
|
5. **Release**: 2.10.0 is tagged; the 2.9.0 tag still waits for the user's go-ahead.
|
||||||
|
|
||||||
## After 3.0.0
|
## After 3.0.0
|
||||||
|
|||||||
Reference in new issue
Block a user