`pdfs sync list` and `pdfs devices list` printed the last sync as epoch
seconds, such as `synced: 1793570850` in issue #46. Both columns now go
through `last_synced`, which formats it like `pdfs activity` and prints
`never` for a folder that never synced. `--json` output is unchanged
(B278).
The service can run while its mount fails on every try, for example over
a folder with files in it (B277). The page then said "Can't open your
Drive", but the sidebar status read "Not connected / Proton Drive isn't
running", which was wrong. paint_disconnected now reads the same failure
record the pages read, once the service is known to be running, and
shows "Can't open your Drive" with the reason. A reply from the daemon
that lands while it looks wins.
The wipe guards refused a pass only when every synced path was missing
on one side. One surviving file cleared them, so a half-mounted disk,
a restored backup that brought back one file, or a script that emptied
the folder and wrote one back made the next pass trash everything else
on Drive, and on every computer syncing the folder (B270).
Both guards now also refuse when nine in ten or more of the tracked
paths of a folder with at least 20 are missing at once. A smaller
folder still stops only on a total loss, since an ordinary cleanup can
delete most of it. The reason now gives the count.
B270 stays partly fixed: a smaller loss still goes through, and the
app does not yet offer to confirm the deletions.
FUSE mounts over a non-empty folder without complaint, so files already
in the My files folder were hidden under the mount: not uploaded, and
back only once the service stopped, so they looked deleted (#67, B277).
- pdfs-core: new `mountpoint` module. `not_empty` lists what is in a
folder (hidden entries count); `mounted_fs_type` moves here from the
CLI so the daemon can use it. `MountFailure` gains an optional
`not_empty` field, which older readers ignore.
- pdfs-fuse: `mount_with` checks the folder before the root fetch and
before any worker starts. A dead mount is cleared first; one of our
own live mounts is left to `spawn_session`, as before.
- pdfs-cli: `cmd_daemon` records the folder in the failure record. The
refusal backs off and retries like any mount failure, so emptying the
folder is enough.
- pdfs-gui: the status pages explain the refusal, and a dialog offers
to open the folder or choose another one, once per run of failures.
The folder picker refuses a folder with files in it.
Translated in all catalogs. Docs: BUGS (B277), CHANGELOG, FAQ,
TROUBLESHOOTING, CONFIGURATION.
A create that carries a file's bytes retired its op, then cached the
bytes and gave the tree the real uid. Nothing counted that window, so
`pdfs status` read empty while it ran, which B119 promises means the
file reads without the network.
A delete in that window found the placeholder uid in the tree and
trashed the node by its real uid. `queue_trash` asked the tree whether
that uid was open, found no entry, and dropped the cached bytes the open
handle reads. If the cache store ran at the same moment, its tag was
written after the eviction and never removed. CI caught that as a
stray .meta file.
The create's landing is now counted like an upload's, from the op's
retirement until the tree has the real uid. The delete and the replace
pass whether they found the file open. Opening the cache removes a tag
whose bytes are gone, which also covers a crash between an eviction's
two removals. The simulations wait for `Daemon::drained`, which counts
the landing, before they go offline.
B255 and B256 move to fixed: the account run checked both.
A switch asked for before any pass saw the folder empty found it still
idle, so the check for what only this computer has ran at once and
refused it for good: every synced path read as "deleted here but not on
Proton Drive yet". The account run hit it in the B255 case.
A local copy gone in full is the wipe guard's case, which the push pass
of a queued switch settles without trashing anything on Drive. The
switch now returns NotNow for it, so the request is queued and that pass
runs first. A partial deletion is still refused as before.
New cases for what 3.6.0 adds:
- copy_file_range over several calls, into the middle of a file, past
its end, at the source's end, and from a file whose writes are not
released yet; on a live mount also read back from Drive.
- pdfs copy of a tree and of a file beside itself, read back as listed
and after a refresh, with nothing queued; refused while a file has
writes Drive lacks, and never the version before a save (B256).
- pdfs du counts every item below a folder, trash included.
- pdfs public-link update keeps the URL and the options it was not
asked to change, and refuses a link id that is not the folder's.
- In a sync folder of their own: Sync From Scratch changes nothing that
already matched, and an emptied mirror folder still switches to
on-demand with every file intact on Drive (B255).
docs/TESTING.md gains a checklist for the new screens in the app. The
CHANGELOG moves these into 3.6.0, which is not tagged yet.
The first pass over a synced folder has no baseline to estimate from, so
its scan showed only "checking for changes…" in the app and "scanning…"
in `pdfs sync list`. For a large folder that scan reads every item on
Proton Drive before anything downloads, which takes many minutes and
looked stuck. Both now show how many items the scan has checked so far.
A mirror folder whose files were all gone from this computer stayed in
error for good. The wipe guard rightly refuses to trash the whole folder
on Drive, but nothing got the folder out of that state: a queued switch
to on-demand waits for a pass that ends idle, and its push pass hit the
same guard. Only removing the folder from sync cleared the baseline that
made the empty folder read as "delete everything".
- The push pass for a switch to on-demand forgets the baseline rows of a
local copy that is gone in full instead of trashing them, so the
switch goes ahead and the mount shows what Drive has.
- New Request::ResetSyncFolder, `pdfs sync reset ID` and "Sync From
Scratch…" in a synced folder's menu clear a mirror folder's baseline
(and a queued switch to on-demand, whose push pass would upload a
duplicate of every file). The next pass compares both sides as on the
first sync: one-sided files are copied across, files that differ keep
both versions, nothing is deleted.
- The wipe guards' messages name the reset; restore logs why it skipped
a folder.
Two simulations cover it. The on-demand one failed in exactly the
reported state (mirror, pending ondemand, error) before the fix.
All 8 catalogs translated.
A copy inside the mount went through the kernel's read/write loop, one
FUSE round trip per page, because copy_file_range answered ENOSYS. The
daemon now copies a range whose source bytes are local (an open write
handle, a queued write, the cache) into the destination's scratch file,
the same way write stages them. A range it would have to download gets
EOPNOTSUPP, which makes the kernel copy that one call itself and keeps
the operation enabled. The copy still uploads: Drive cannot copy into a
file cp has already created.
In the simulation, a warm cp of a 256 MiB file went from about
100 MiB/s to about 450 MiB/s (debug build).
The share dialog gets Link settings under an existing link: role, password
and expiry apply at once and keep the URL. The daemon fills in the options a
change keeps from the link as it stands, and refuses to keep a password it
cannot read rather than dropping it. pdfs public-link update does the same
from a terminal, and pdfs members shows the expiry.
Details shows a folder's size and item count as Proton Drive counts it,
trash included, and pdfs du prints it. Both are refused offline and for a
folder that is not on Drive yet.
Needs proton-drive-rs with update_public_link, get_folder_size and
PublicLink::custom_password.
The entry menu gains Make a Copy, which copies an item next to itself, and
Copy To…, which reuses the move folder picker limited to My files; Copy
To… is also in the selection menu. Both send Request::Copy, so the copy is
made on Proton Drive.
The move and copy reports share one request loop. Docs describe the new
actions, and the changelog notes that saved public links now show their
names once the SDK decrypts them.
`pdfs copy PATH... NEW_PARENT` sends a new control request, Copy, which
copies a file with its content or a folder with its subtree on Proton
Drive through proton-drive-rs's copy_node. Nothing is downloaded or
uploaded. A name already taken in the destination gets the next free
" (n)" suffix through get_available_name.
A copy cannot wait in the queue, because Drive copies what it holds.
So it is refused offline, and while the mount still owes Drive part of
the node: a local placeholder, a queued op, unsaved writes, or, for a
folder, any queued op at all. Local-first does not change this.
DriveApi gains copy_node and get_available_name, and the fake drive
copies subtrees, so a FUSE sim test covers the copy, the suffix, the
folder subtree and both refusals. The activity feed logs a new Copy
kind, translated in every catalog.
Needs the proton-drive-rs release that adds copy_node; until then, build
with a [patch.crates-io] pointing at proton-sdk-rs.
- scripts/publish-wiki.sh publishes every page listed in SECTIONS, copies
images, writes a sectioned sidebar and footer, and removes stale pages.
--check fails on links to missing files or headings and on docs pages
that are neither published nor excluded.
- New wiki.yml runs the check on pull requests and pushes to main, and
publishes when release.yml calls it or when started by hand.
- Split USER_GUIDE.md into topic pages (files, synced folders, photos,
sharing, computers, launcher and tray); add Getting started and FAQ.
- Make the COPR repository the main Fedora path, with update and
uninstall steps for every package type.
- Fix the stale CAPTCHA note in CLI.md, document pdfs unlock and lock,
and drop the broken AGENTS.md link from CONTRIBUTING.md.
When the session is stored and unlocked but the daemon does not answer,
pdfs diagnose asks Proton for a key report and prints a Keys section:
each account key, each address key and the account key its token is
for, and the address key the My Files share is encrypted to. It holds
key IDs only, so it can be posted in an issue.
Refs #40
Preferences gets a switch that asks GitHub once a day for the latest
release and shows a banner over the sidebar when it is newer. Off by
default. Packages whose package manager brings updates install the
marker share/proton-drive-linux/managed-updates, which hides the switch.
Refs #42
The spec builds from the tag archive and a vendored crates tarball when
git_dir is not defined, so COPR can build it without network access.
release.yml publishes the vendor tarball and submits the build to COPR
after the release is published.
Refs #42
A mirror upload that Drive did not list yet when it was read back left a
baseline row with no revision id. The next walk reused the recorded size
whenever the modification time matched, and a row without an id never
counts as moved. So a revision another computer saved in the same second
looked unchanged and never arrived (B254). The B250 simulation hit this
about one run in five on CI.
A row without an id now reuses its size only while Drive gives no id
either. Otherwise the walk reads the size, and an unchanged file gets
Drive's revision id written into its row, so each row costs one extra
read. Rows from before schema 44 are read once after upgrading.
Add the window size and the narrow layout to the 3.5.0 changelog with
B253, document the `window` config field, and explain in the
troubleshooting guide why a two-password account asks for its mailbox
password after every reboot, and how to keep it unlocked.
Add B248 to B252 to the bug ledger, the shared-device feature and schema 44
to the 3.5.0 changelog, and describe the join flow, per-machine profiles,
conflict copy names and the mirror's multi-writer rules in the user guide,
recovery guide, CLI and configuration references and the architecture notes.
The name checks the drain makes before each create, rename and trash,
and the wake of ops waiting for a name, read the whole queue, which made
a large offline batch quadratic to drain. Schema 43 adds an index on
pending_op(kind, name) (B238).
Proton answers a sign-in it blocks for abuse or rate limits with code
2028 and the text "This version of the app is no longer supported". The
SDK read 2028 as Unknown, so the app passed that text through and told
users to update a current release (issues #28, #39).
- deps: proton-sdk and proton-drive-rs 0.8.3, which name 2028 Blocked
- core: error::sign_in_blocked detects it
- gui: the login page says Proton is refusing sign-in for now and that
waiting helps, translated in every catalog
- cli: pdfs login adds the same hint to the error
- cli: pdfs diagnose warns instead of failing when cache.db does not
exist yet; the daemon creates it after the first sign-in
- docs: BUGS.md B209, changelog, troubleshooting entry
The account run of 3.3.0 failed "move refuses a file still being
written" on the on-demand to mirror pairing: pdfs move moved the folder
while open.txt in it was open for writing, and the write landed after
the move.
retire_create re-keys a landed create's nodes row to the real uid first
and repoints the tree and the open write handles after. A file opened in
between holds a handle with the placeholder uid. has_pending_work_under
placed each open handle by looking its uid up in the nodes table, which
no longer has the placeholder, so it saw nothing open under the folder,
and with the queue already empty the move went ahead. Open handles are
now placed in the in-memory tree by inode, which a landing create does
not change.
The simulation test holds the drain at that point with the new
FakeClient::hold_next_landing_of_any, and fails without the fix.
The daemon has kept recursive folder pins for a long time, but the app
offered "Available offline" for files only. The context menu, the
details pane and the selection bar now offer it for folders too, and a
pinned folder carries the offline badge in the browser and the launcher.
A folder pin used to download the whole subtree inside the control
request, so a large folder outlasted the client's 120 s read timeout,
and every pinned file was read into memory before it was cached. The
daemon now records the pin, answers at once and downloads on a
background thread as a job the tray and the app show, stopping when the
folder is unpinned or the daemon stops. Pins of files and folders both
go through fetch_content, which streams to disk and skips fresh blobs.
A folder pin is refused while offline instead of looking kept.
A file kept only because a folder above it is pinned has no pin of its
own, and unpinning it silently did nothing. ListDir now reports
pinned_by_folder; the app shows such an entry as available offline with
the toggle greyed out, and the daemon refuses the unpin with a message
naming the folder. Unpinning a folder no longer evicts files that are
pinned on their own.
Requested on Reddit.
Removing the package left the user service running from the deleted
binary, with ~/ProtonDrive still mounted. A user who then deleted the
folder to clean up had every file in it moved to the Proton Drive trash,
as any delete through the mount is.
The .deb (prerm on remove), the .rpm (%preun on erase) and the Arch
packages (pre_remove) now stop proton-drive.service for every user with
a running user manager, through runuser with that user's
XDG_RUNTIME_DIR, so no systemd-machined is needed. Upgrades are left
alone. Queued writes stay in the state directory for a later install.
The pacman install script lives once in packaging/ and the AUR
directories link it; push.sh copies its content into each AUR
repository. The uninstall guide now warns not to delete the mount point
while the service runs.
Reported on Reddit by a user who restored the files from the trash.
The RPM recommended both gnome-keyring and kwallet unconditionally, and
dnf installs weak dependencies by default, so a GNOME install on Fedora
pulled in KWallet and KDE Frameworks with it. The recommendations are
now rich dependencies: KWallet only with plasma-workspace, GNOME Keyring
only without it, and the AppIndicator extension only with gnome-shell.
RPM does not accept `unless` in a weak dependency, so the keyring uses
the equivalent `(gnome-keyring or plasma-workspace)`.
Reported on Reddit by a Fedora GNOME user.
Every Nix build of main and of each release tag now goes to
proton-drive-linux.cachix.org, so installing the flake downloads the
client instead of compiling it. The flake names the cache in nixConfig
for nix run and nix shell, and the install guide shows the nix.settings
a NixOS system build needs, since it does not read an input's
nixConfig. A fork's pull request has no token and only pulls.
A package upgrade replaces the binaries but does not restart the user
service, so the daemon keeps the old release and its bugs until the next
login. On 3.2.0 the reporter of #33 still saw "Couldn't sync Documents:
read .../snapshots/37: Permission denied", which only a daemon older than
3.2.0 can produce: since then the scan leaves an unreadable subfolder out
and only an unreadable sync root fails the pass (B206).
The daemon now reports its release in Status; one from before the field
omits it, which reads as older. When the daemon is older than the
front-end, the tray says "Update not finished - restart Proton Drive" and
offers a restart, the sync card shows "Restart to finish updating" with a
Restart button, and pdfs status and pdfs diagnose print the systemctl
command. A newer daemon is not flagged, since then the front-end is the
stale one.
Refs #33
Every SDK error that was not an API reply was classified as Offline.
Only a transport failure is. An upload on an account without a photo
library fails in the SDK before any request, and the app said "You're
offline" while every other call worked (issue #38).
Classify by variant: Transport is Offline, InvalidOperation is Invalid,
Serialization is Remote, Crypto and KeysLocked are Internal. The daemon
logs a failed photo upload, and the Photos page greys out Upload when
the account has no photo library.
docs/BUGS.md B205.
The CPU spin while offline, the expired session shown as offline, the
lost refresh tokens and the keyring-refused rotation were fixed without
a changelog entry.
Builds the four binaries with rustPlatform.buildRustPackage from
Cargo.lock, installs the desktop entries, icon, user service and
translations as the other packages do, and wraps the binaries with
exiftool and ffmpeg on PATH. The NixOS module installs the package,
pulls proton-drive.service into the graphical session with the
fusermount3 wrapper on its PATH, and enables FUSE and GNOME Keyring.
A Nix workflow builds the flake when its recipe, the lock files or the
code change.
Refs #37
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EuyxByBpK3fSWtyPkugeHz
A partial write whose base was not in the content cache queued an
incomplete revision, and every write open over it was refused with EIO
until the drain uploaded it. CI's one-client-lan simulation hit this on
seed 1: a create over a file whose partial edit had not drained yet.
The write open now copies the blob and fills its gaps from the remote
base with Core::fill_gaps, as the drain does, and fails only when that
fill fails (B204).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EuyxByBpK3fSWtyPkugeHz