fix(core): keep a folder's pin when the folder lands (B148)

A folder pinned before its mkdir landed kept the placeholder uid in
pins.uid, which named no node once the landing moved the row to the
real uid. The landing now moves the pin with it.
This commit is contained in:
nvrl committed 2026-10-04 04:45:47 +02:00
1 parent 9b3651e4e8
commit f029e652a1
4 files changed
+73

No files matched your search

+7
View File
@@ -733,6 +733,13 @@ pub(super) fn adopt_placeholder_row_tx(
local: &str,
real: &str,
) -> Result<()> {
// A folder pinned before it landed stays pinned: the pin keyed by the
// placeholder was dropped with it (`docs/BUGS.md` B148).
tx.execute(
"UPDATE OR IGNORE pins SET uid = ?2 WHERE uid = ?1",
params![local, real],
)?;
tx.execute("DELETE FROM pins WHERE uid = ?1", params![local])?;
// The node a row holds names its parent too, and is what a restart builds
// the tree from: a folder that landed with no write to its children after
// left them under a parent no row has (`docs/BUGS.md` B140).
+47
View File
@@ -3321,6 +3321,53 @@ fn a_landed_folder_keeps_a_row_until_the_server_copy_replaces_it() {
assert_eq!(found[0].path, "New folder/inside.txt");
}
/// B148: a folder pinned before it landed is still pinned once it has.
#[test]
fn a_folder_pinned_before_it_landed_stays_pinned() {
let db = Db::open_in_memory().unwrap();
db.upsert_node(&folder("root", None, "My Files")).unwrap();
let local = NodeUid::new(VolumeId::from("local"), LinkId::from("dir"));
let mut placeholder = folder("dir", Some("root"), "Keep");
placeholder.uid = local.clone();
db.upsert_node(&placeholder).unwrap();
db.pin_add(&local.to_string(), "Keep", true).unwrap();
let (id, _) = db
.enqueue_op(&PendingOp {
id: 0,
kind: OP_MKDIR.to_string(),
uid: local.to_string(),
parent_uid: Some(uid("root").to_string()),
name: Some("Keep".to_string()),
blob_path: None,
meta_json: None,
created_at: 1,
attempts: 0,
last_error: None,
next_attempt_at: 0,
})
.unwrap();
db.finish_create(
id,
None,
&landing(
&local.to_string(),
&uid("real").to_string(),
&uid("root").to_string(),
"Keep",
),
|_| None,
)
.unwrap();
assert!(db.is_pinned(&uid("real").to_string()).unwrap());
assert!(!db.is_pinned(&local.to_string()).unwrap());
let pins = db.pin_list().unwrap();
assert_eq!(pins.len(), 1);
assert_eq!(pins[0].uid, uid("real").to_string());
assert!(pins[0].recursive);
}
/// A file deleted while its create was on the wire still lands: nothing stops
/// an upload in flight. The node it made is unwanted, so a trash of it is
/// queued in the same transaction, already claimed by the drain that landed it
+17
View File
@@ -12,6 +12,23 @@ Conventions:
---
## B148 — A folder pinned before it has reached Drive loses its pin
**Status:** Fixed (unverified).
**Found:** 2026-10-04, while planning the move of queued ops to local ids: every table keyed by
uid was checked for what happens when a placeholder lands, and `pins` was the one no landing
touched.
**Where:** `adopt_placeholder_row_tx` in `crates/pdfs-core/src/db/nodes.rs`.
**Cause.** A folder made through the mount has a placeholder uid (`local~…`) until its mkdir lands.
Pinning it then stored the placeholder in `pins.uid`. The landing moved the node row, the children
and the queued ops to the real uid, but not the pin, which then named no node: the folder was no
longer kept offline and the pin list showed an entry for nothing.
**Fix.** The landing moves the pin to the real uid in the same transaction. A pin the real uid
already has wins, and the placeholder's is dropped.
## B147 — A new file made under a name just renamed away can share the first file's node
**Status:** Fixed (unverified).
+2
View File
@@ -31,6 +31,8 @@ are kept. Queued changes get a column for the reason Drive refused them.
for this release.
### Fixed
- **A folder kept offline before it reached Drive stays kept offline.** Its pin was lost when the
folder landed (B148).
- **A queued change no longer holds up the queue forever on a link that stops answering.**
Sending a new folder, a delete or a file waits at most 20 seconds for Drive, plus time for
the file's size, and then the queue treats the link as down and tries again.