Files
proton-drive-linux/docs/CHANGELOG.md
T

22 KiB
Raw Blame History

Changelog

All notable changes to the Linux client since the 1.0.0 release. Format follows Keep a Changelog; versions follow semver, and every release is tagged vX.Y.Z with Cargo.toml and packaging/PKGBUILD in agreement.

Schema: lines record the SQLite SCHEMA_VERSION (pdfs-core/src/db/migrations.rs) a release ships. Migrations are forward-only — a database written by a newer client is a hard refuse-to-open, not a downgrade, so rolling back a release means restoring the cache from scratch (user data in staging/ and recovery/ is never touched by this).

[1.8.1] — 2026-08-16

Some files read short through the mount and reported success — an application got a truncated copy with nothing anywhere saying so (docs/BUGS.md B84). Found because GNOME's file indexer kept complaining that PNGs under ~/ProtonDrive/Pictures were corrupt; it was right. The rest of this release is the long-standing architectural backlog from the July concurrency audits (B25, B32, B39, B40, B43, B44), each verified against the current code before being fixed. Schema: 28 — migration V28 adds sync_entry.local_mtime_ns.

Fixed

  • A read no longer trusts a revision whose recorded block sizes disagree with the file's size. Such a file leaves the block/range path entirely: it is fetched through the whole-file, manifest-verified download — the same path pdfs pin uses, and the reason pinning was the workaround — and served from the resulting cached blob. One download per file however many reads discover the problem at once, and files above 512 MiB fail EIO with a note to pin them rather than pulling down gigabytes for one read.
  • A block that comes back shorter than the file's size implies is never served, cached or kept in memory. Assembling a read out of short blocks is what quietly produced the truncation: the missing bytes were simply not appended, the kernel handed userspace a short read, and every tool reads a short read as end-of-file.
  • A synced folder no longer reports the same deletion as an error on every pass. Deleting a local file that something else had already deleted counted as a failure, which kept its baseline and made the next pass try again — forever. A path that is already gone is the state the sync pass wanted (docs/BUGS.md B42).
  • A mirror folder no longer misses an edit made in the same second as its last sync. Change detection compared whole seconds, so rewriting a file in place without changing its length read as "unchanged" and never uploaded — the remote silently stayed behind until something else about the file moved. Baselines now record the modification time to nanoseconds (docs/BUGS.md B25).
  • A sync download no longer overwrites an edit that finished while it was running. The destination is re-checked in the last moment before the download is published, and a local file that has moved on since the pass planned for it is kept as a conflict copy — the same resolution a both-sides-changed conflict gets (docs/BUGS.md B39).
  • An upload interrupted by a writer no longer reports itself as synchronized. The baseline now records the content that actually went up rather than whatever is on disk when the transfer ends, so a revision torn by a concurrent write is replaced by a clean one on the next pass instead of standing as the file's content on Drive (docs/BUGS.md B40).
  • Listing a directory that changes while it is being read no longer skips or repeats entries. readdir resumed by index into a listing rebuilt on every call, so a create or a trash between two pages shifted everything after it. The listing is now frozen for the life of the directory handle (docs/BUGS.md B43).
  • Opening a file for writing while a previous close is still publishing no longer discards that close's revision. The base is re-checked at the moment the write handle is installed, rather than trusted across a copy that can be the size of the whole file (docs/BUGS.md B32).
  • Unmounting now stops and joins the daemon's background workers instead of leaving them running. The drain, sync engine, conflict sweep, online probe, local indexer and control listener share a stop signal and interruptible waits, so an in-process remount no longer starts a second set on top of the first (docs/BUGS.md B44).

Notes

  • The size stat reports was always the correct one; it is the reader's belief about where blocks end that was wrong, so nothing about reported sizes changes.
  • Verified live on the affected account: four multi-block PNGs that had been reading short now deliver their full size, and the journal shows each one repaired. The diagnostics also answered the open question, and not as expected — the revision metadata is fine; those files simply do not have 4 MiB blocks, and this client assumes they do. Filed as docs/BUGS.md B85. Until that is fixed, a file with non-4-MiB blocks is downloaded whole on its first read.
  • Migration V28's new column is nullable rather than converted from the existing seconds. A baseline row written before this release has no sub-second time to recover, and inventing zero for it would make every already-synced file compare as changed and re-upload an entire mirror on first start; NULL keeps those rows comparing exactly as they did, and each gains a real value the next time its path syncs.
  • Three audit entries closed without a code change, having been fixed already or being correct as they stand: sync-folder removal is serialized against reconciliation (B41), the drain's database contention was removed by the read-only connection pool and the claim column (B30), and the refusals for symlinks, hard links, device nodes and chmod are deliberate — Drive cannot represent them, and a local-only representation no other Proton client can read would be worse than a clean refusal (B31).
  • Six of this release's fixes were then driven live against the installed 1.8.1 on a real account: the same-second edit uploads (B25), three rapid rewrites of a 64 MiB file publish the third one byte for byte (B32), a write racing a download survives as a conflict copy (B39), an upload torn by a concurrent write heals itself on the following pass (B40), a getdents64 walk with a 160-byte buffer against a directory mutated between pages returns every entry exactly once (B43), and a restart of a daemon with six mounts and a busy drain stops in 110 ms and comes back at the same thread count (B44). B42 could not be driven from a single machine — see B86 below.
  • One new bug came out of the verification: an on-demand mount serves a folder listing that predates the sync engine's own uploads, so files the mirror pass created are invisible until the folder is switched back (docs/BUGS.md B86). Nothing is lost — they are on the server and come back on the next mirror pass — but they cannot be seen or named while the folder is on-demand, which is also what blocks B42's live verification.

[1.8.0] — 2026-08-16

A queued write whose node had left the local tree was retried every five seconds forever and counted as an ordinary pending upload — 28 of them, holding 18.4 GiB, had been doing that for 30 days on the machine that found it (docs/BUGS.md B83). Schema: 27 — migration V27 adds pending_op.access_deferred_since.

Fixed

  • The drain no longer reads "I have never heard of this node" as "you may not write this node". A missing authority is now resolved against the remote: the node is re-interned if it is still there, and reported as a real failure if it is gone. Ops that had been stuck invisibly for weeks now say what is wrong with them on the first pass.
  • A create or mkdir resolves its parent as the authority, so the refetch asks about the folder rather than the node it has not made yet.

Added

  • Access deferrals are bounded. A deferral still costs no attempt and records no error for the first five minutes — the case it exists for is a share downgraded mid-queue and undone a moment later — but past that window each recheck records a failure, so the op enters the ordinary backoff and appears in failing_ops and the pdfs status error text like any other stuck operation. The first deferral of a run is logged at warn rather than debug.

Notes

  • No staged data is touched by any of this. A reported failure keeps its pending_op row and its staged blob exactly as before; the change is in what the user is told, and in the queue no longer waiting on a permission change that was never coming.

1.7.1 — 2026-08-16

Write-back throughput follow-up to 1.7.0, same audit (docs/plans/audit-2026-08-15-perf-locks.md). Schema: 26 — migration V26 adds pending_op.claimed_at.

Added

  • Parallel write-back drain: three workers share the queue through a pending_op.claimed_at claim column (migration V26), so a large upload no longer holds every queued rename, trash and small write behind it. Ordering is preserved per node — the claim query never offers two workers ops for the same uid — and claims left by a crashed run are cleared at open.
  • An upload already on the wire is cancelled when a newer write, a trash or a discard supersedes it, instead of spending the uplink on a revision the next operation replaces.

Changed

  • The revision debounce is adaptive: it widens toward how long that node's last upload actually took (bounded to 2–60 s), so a file saved faster than it can be sent supersedes in the queue rather than mid-upload.
  • Workspace deps bumped to proton-sdk / proton-drive-rs 0.6.0.

1.7.0 — 2026-08-16

Performance and robustness pass over the mount, from the architecture audit in docs/plans/audit-2026-08-15-perf-locks.md. Schema: 25.

Added

  • Sequential-read prefetch (Core::prefetch_ahead): per-file sequential detection, depth doubling 2→8, reset to 0 after a seek, skipping blocks already cached, and bounded by its own permit budget so it can never queue ahead of a demand read.
  • Single-flight block fetching (BlockFlight) keyed on (uid, mtime, size, block), so a demand read, kernel read-ahead and prefetch of the same block share one download+decrypt.
  • In-memory block LRU (BlockRing) in front of both block paths; a block read off disk is promoted into it, so the following ~31 kernel reads of that 4 MiB block cost nothing.
  • Read-only SQLite connection pool (ReadPool / Db::read) with every SELECT-only method routed at it, so a read no longer waits on the write connection.
  • nodes.path persisted and backfilled, replacing the recursive path_of walk.
  • Single-writer flock on the database; a SQLITE_CORRUPT/NOTADB file is quarantined as cache.db.corrupt-<ts> (WAL sidecars too) and rebuilt rather than failing the mount.
  • VACUUM and the deep integrity_check now ack immediately and run as background jobs; pdfs doctor polls for the verdict.
  • Staging reconcile at mount: staged blobs no queued operation refers to are re-queued, or named in the log when they cannot be addressed.
  • Queue/staging visibility — Status gained parked_uploads, failing_ops, failing_error, staged_bytes, staged_oldest_secs; pdfs status prints them when non-zero.
  • statfs implemented against the account quota; FUSE init negotiates max_readahead, max_background, PARALLEL_DIROPS and CACHE_SYMLINKS; blksize raised to 1 MiB.

Changed

  • State no longer writes to SQLite under the inode lock — mutations queue into an outbox applied after the lock is released, so interning a 5000-child listing no longer commits a transaction with the whole mount frozen.
  • Re-listing an unchanged folder no longer touches its subtree; when a subtree really moved, one recursive walk carries both the new path and the inherited trashed flag.
  • release(2) no longer gap-fills from the network: it stages an incomplete blob from local caches only and the drain thread finishes it off the dispatch loop.
  • A partial write over an undrained one is merged instead of refused.
  • Size-upgrade waiters park on a queue instead of occupying a worker thread, capped at 8 folders in flight; past the cap stat answers provisionally.
  • Block-cache disk writes moved off the read's critical path; no more fsync per cached block.
  • Cache LRU touches buffered in memory and flushed in one transaction.
  • Search: sync-folder list and pin lookups hoisted out of the per-hit loop.
  • SQLite tuned (cache_size, mmap_size, temp_store=MEMORY, wal_autocheckpoint) and three new indexes added.
  • Mount registry lock released before FUSE teardown/join; kernel notifications batched under the state lock and flushed after it.

Fixed

  • Data loss: ENOSPC is now propagated from write/fsync/staging instead of being swallowed, with emergency eviction wired up and scratch marked durable before any staging error; a failed record_pending_write fails release rather than dropping the write.
  • Data loss: the conflict sweep no longer discards queued operations after the trash round trip.
  • Data loss: rescue_scratch moves a blob with an unreadable sidecar to recovery/ unaccompanied instead of fabricating a sidecar for it.
  • Own-sealed revisions are persisted (7-day TTL), so a restart no longer reopens the transient-download fork window fixed in 1.1.0.
  • Depth-capped is_pinned (cycle-safe), daemon write timeout, and every client-supplied limit clamped.
  • recover_fsynced_writes no longer scans the recovery directory on every idle pass.

1.6.0 — 2026-08-12

Search-index correctness and ranking (docs/plans/search-index.md). Schema: 21 — migration V21 rebuilds nodes_fts.

Fixed

  • B80 — 29% of the account was missing from the search index. node_is_indexable_tx required a null-parent ancestor, which only the My Files root has, so every node under a device folder was dropped from nodes_fts on its first upsert after the v16 backfill (2,705 of 9,252 nodes on the account this was diagnosed against). The rule now matches the backfill, migration V21 replays it, nodes under a trashed folder stay out, and the walk is depth-capped.
  • B81 — the prompt labelled every Drive hit "My files", including device folders; pin rows also took is_dir from Pin::recursive.
  • B82 — a parent_uid cycle hung the daemon on any short search: the recursive CTEs in path_of and the pin walk use UNION ALL, which does not deduplicate. path_of is now depth-capped at 256 and returns the truncated path.

Changed

  • Local search ranking: whole-query prefix matches get their own lane (newest first) ahead of trigram rank, which on a home directory full of dependency trees was burying the obvious answer — 73% of the 500 best-ranked candidates for "test" came from one Go module cache. The single-character lane, which recovers a typo that destroys every trigram, stays behind it.

1.5.0 — 2026-08-11

Schema: 20.

Added

  • pdfs-prompt --dmenu — the same search through an external dmenu-style launcher (fuzzel, rofi, wofi, tofi, bemenu, dmenu) instead of the built-in GTK HUD, so the prompt matches a tiling-WM desktop. A launcher filters a fixed list rather than re-querying per keystroke, so searching is a two-step loop; --query skips the first step, fuzzel and rofi get file-type icons. Configurable as "prompt": { "mode": "dmenu", "menu": [...] }, with --gtk overriding for one invocation.
  • Configurable open-with rules (pdfs-core/src/opener.rs): an ordered list of name/class patterns in config.json under open_with, each mapped to argv and optionally wrapped in a terminal, first match wins, falling back to xdg-open. Lets "open a text file" mean nvim in a terminal without breaking every graphical caller. Absent config reproduces the old behaviour exactly.

Fixed

  • The prompt and dmenu openers hand applications the cached file while matching rules against the original Drive filename, so e.g. .md files stop being routed by their cache-path extension.

1.4.0 — 2026-08-10

Schema: 20.

Added

  • File version history (pdfs-fuse/src/revisions.rs + a GTK versions dialog): list a file's revisions, restore one, delete one, or write an old one out to a local path. A restore re-points the file server-side, so no content crosses the wire and nothing enters the drain queue; the daemon drops its cached content and repopulates on the next read.
  • Persistent SDK entity cache (pdfs-core/src/sdkcache.rs): the SDK's decrypted node metadata now survives a daemon restart instead of re-walking the tree. It lives in its own sdk_cache.db, deliberately off the daemon's hot cache.db mutex, holds no key material, needs no migrations, and is cleared on logout.
  • Per-node outcomes for batch operations (pdfs-core/src/batch.rs): trash/restore/ delete/move report one result per node, so a node the server rejects no longer fails the whole batch.
  • Photo viewer page in the GUI and richer photo metadata in the details pane.

Changed

  • SDK telemetry routed into tracing, so its transfer/block-storage/request spans land in the daemon log instead of a no-op sink.
  • A file that fits in one block uploads as a single atomic request rather than the draft/block/commit sequence.

1.3.0 — 2026-07-31

Schema: 19.

Added

  • Photo albums — PhotoAlbums / AlbumPhotos control requests, daemon-side albums.rs and a GUI albums page (the former gallery view, renamed). Albums other people share with you are included; their photos are persisted per album because a shared album's photos never appear in your own timeline. Served stale-while-refreshing like the timeline.
  • GUI "Locations" page — one row per mount spec: the primary ~/ProtonDrive mount alongside every folder this computer backs up, mirrored or on-demand. Called Locations rather than Mounts because a mirror folder is a plain directory with no FUSE session. It absorbs the old Computers page's actions and the mountpoint chooser from Settings.
  • role on shared-node metadata (viewer/editor/admin), defaulted for wire-compat with older clients and daemons.
  • FUSE acceptance coverage for the permission-based mounts.

1.2.1 — 2026-07-31

Schema: 18.

Fixed

  • Migration bug that could leave the ProtonDrive mount unwritable after upgrade.
  • Permission error on on-demand mounts.
  • B78.

Changed

  • Release pipeline: .deb signing added and .rpm signing fixed.

1.2.0 — 2026-07-29

Sharing and multi-mount release. Schema: 18.

Added

  • Shared with me: shares others sent you are listed, verified and mountable, including on the primary mount. They appear under a synthetic Shared with me directory on a virtual volume (pdfs-fuse/src/virtual.rs), with name collisions and over-long components resolved by its own naming rules.
  • Per-share access model (db/share_access.rs): the effective role of each shared root is persisted and kept resident in mount state, so interning an inode never costs a query. The database stays the restart/offline authority — every role change is written through before the cache updates. A share you cannot write is presented POSIX read-only.
  • Unified location model (pdfs-core/src/mounts.rs, db/mounts.rs): one presentation shape for every local Proton Drive location, joining device sync state with a new mount table. MountMode is Mirror (full local copy, reconciled both ways) or OnDemand (FUSE session fetching on access), with an Unknown fallback so a mode written by a newer client does not break an older one.

1.1.1 — 2026-07-27

Schema: 16.

Fixed

  • Trash refresh did not pull updates.
  • Packaging: PKGBUILD was left at 1.1.0 by the 1.1.1 version bump, failing the release workflow's tag/manifest/PKGBUILD agreement check.

1.1.0 — 2026-07-26

Conflict-handling and concurrency release. Schema: 16.

Added

  • Conflict sweep (pdfs-fuse/src/sweep.rs, 5-minute cadence): removes conflict copies proven identical by size + content_sha1, surfaces divergent ones once as an activity entry, and never removes what it cannot prove.
  • Sweep mode and .pdfsignore-driven conflict handling; ignore rules are rebuilt per sync pass so edits take effect without a restart.
  • Substantially extended FUSE acceptance suite.

Fixed

  • B69 — spurious (sync-conflict) copies. Revision identity now uses the server's revision_id rather than (mtime, size), which the server re-stamps on seal; the planner gained AdoptBaseline so an equal-size no-baseline pair is recorded instead of forked on first reconcile.
  • B70 — browser downloads into an on-demand mount had their in-flight temporaries sealed as canonical revisions. Transient names are recognised and held until the name is finalized.
  • Assorted race conditions and concurrency bugs on the write/drain paths.

Changed

  • Workspace deps bumped to proton-sdk / proton-drive-rs 0.2.2.

1.0.0 — 2026-07-22

First stable release: FUSE files-on-demand mount, sync daemon under proton-drive.service, pdfs CLI and the GTK4 app, on top of the pure-Rust Proton Drive SDK. Schema: 16.

Fixed

  • The outstanding FUSE defects tracked in docs/BUGS.md, plus a truncate defect, validated by a new POSIX compliance suite for the filesystem.