Repository navigation
EchoConnect only: our own phone file transport; grid rename and sidebar fixes - #22
Conversation
The field was 16px wider than the tile and drawn across the tile's own focus frame, so two orange boxes crossed and it covered the neighbours' labels. Now the tile's frame steps aside while renaming, the field is centred under the picture at the label's size, sized to the name (growing as you type, up to 320px) and kept inside the pane. Also gives the test Model its missing `highlight` field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The phone's files no longer go through GVfs and the system ssh, whose
known_hosts check failed ("Host key verification failed") every time the
phone's file server started with a new key.
- New crate ef_phonefs: an in-process SFTP client (russh) that only accepts
the host key the phone named over the paired TLS link, mounted with FUSE
at $XDG_RUNTIME_DIR/echofiles/<phone name>. Requests run concurrently;
reads are split and pipelined; errors carry the phone's errno; a mount a
crash left behind is cleared before mounting.
- Phone file server: host key kept across restarts; fixed a path bug that
made every existing file fail with ENOTDIR (join("") added a slash);
nodelay (writes were waiting ~40ms each on Nagle); truncate, statvfs (free
space) and errno in error replies.
- EchoConnect reports file-sharing failures in the reply EchoFiles waits for,
instead of leaving it on "Checking…".
- The link turns away peers that aren't EchoFiles/EchoConnect (stock KDE
Connect, GSConnect); new security test covers it.
- Removed the stock-KDE fallbacks: photo scanning over the mount, local
thumbnails, GVfs filters for kdeconnect mounts. The hub's phone picture is
rebuilt on EchoConnect's photo list and thumbnails.
- Every "KDE Connect" label, hint and setup step now names EchoConnect.
- Sidebar phone name ends in "…" before the battery meter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the
|
On CI the UDP broadcast loops back, so the two "Bluetooth-only" services found each other over LAN too: phone.lan came back true, and with two links pairing could time out. Service::start_bluetooth_only skips UDP discovery; the test uses it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| fn mount_point(name: &str, id: &str) -> PathBuf { | ||
| let clean: String = name.chars().map(|c| if c == '/' || c.is_control() { '-' } else { c }).collect(); | ||
| let clean = clean.trim().trim_start_matches('.'); | ||
| config::runtime_dir().join(if clean.is_empty() { id } else { clean }) |
There was a problem hiding this comment.
mount_point gives two phones with the same name the same folder. When the second phone mounts, prepare treats the first phone's live mount as stale and unmounts it. The first phone's saved state still points there, so an existing tab can now show the second phone's files. Include the device ID in the mount path and avoid removing another live mount.
| let mut pieces = tokio::task::JoinSet::new(); | ||
| for (i, chunk) in data.chunks(WRITE_PIECE).enumerate() { | ||
| let (s, h, chunk) = (ctx.sftp.clone(), handle.clone(), chunk.to_vec()); | ||
| let off = offset + (i * WRITE_PIECE) as u64; | ||
| pieces.spawn(async move { s.write(h, off, chunk).await }); |
There was a problem hiding this comment.
write starts every 128 KiB piece as a separate task, but append handles cannot use offsets to put those pieces back in order. open_flags sends APPEND, and the phone opens the file with .append(true), so every piece goes to the end regardless of its supplied offset. A large append can therefore save its pieces out of order. Keep writes ordered for append handles, including writes split into several pieces.
| if flags.contains(RenameFlags::RENAME_NOREPLACE) && ctx.sftp.stat(ctx.remote(&to)).await.is_ok() { | ||
| return reply.error(Errno::EEXIST); | ||
| } | ||
| match ctx.sftp.rename(ctx.remote(&from), ctx.remote(&to)).await { |
There was a problem hiding this comment.
RENAME_NOREPLACE is implemented as a separate check followed by an overwriting rename. If the phone or another request creates the destination between those requests, the rename replaces that new file despite the caller asking not to replace anything. rename_noreplace explicitly relies on this guarantee for renaming and safe copy replacement. Enforce it in one operation on the phone, or return an unsupported error rather than silently weakening it.
| if a.size.is_some() || a.mtime.is_some() { | ||
| let f = blocking(|| std::fs::File::options().write(true).open(&p)).map_err(code)?; | ||
| set(&f, &a).map_err(code)?; |
There was a problem hiding this comment.
setstat now opens every path for writing before changing its modification time. Opening a directory this way fails with EISDIR on Android/Linux. EchoFiles calls set_times after copying each directory, so copying a folder into the phone fails at that step even after its contents have transferred. Open directories in a way that permits changing their times, and require write access only when changing a file's size.
| fn fsync(&self, _req: &Request, _ino: INodeNo, _fh: FileHandle, _datasync: bool, reply: ReplyEmpty) { | ||
| // The phone syncs a file when it's closed. | ||
| reply.ok(); |
There was a problem hiding this comment.
Saved changes remain in memory
fsync reports success without asking the phone to save the file to storage. Syncing only when the handle closes does not satisfy applications that call fsync while keeping a file open. They can be told their changes are safely saved when the phone still holds them only in memory. Add a phone-side sync request and wait for its result; do not report success without performing it.
| if stale { | ||
| let _ = std::process::Command::new("fusermount3").arg("-u").arg("-z").arg(at).status(); |
There was a problem hiding this comment.
Crash recovery lacks its helper
Stale-mount recovery now requires fusermount3, but the Arch package does not declare the helper as a dependency. If it is missing, this command fails silently and leaves the stale mount in place. Declare the required package and report cleanup failures instead of proceeding as though recovery succeeded.
audit: ctutils 0.4.2 -> 0.4.4 (RUSTSEC-2026-0385) and rustls-webpki 0.103.15 -> 0.103.16 (RUSTSEC-2026-0382), both published 2026-10-23. test: pair_and_talk timed out waiting for the pair request. On CI the UDP broadcast loops back, so the two services also found each other over LAN and opened a second link; the replaced link's pair packet was dropped. The test dials directly, so it now starts without discovery (start_bluetooth_only is renamed start_without_discovery, since it now serves both tests). shutdown_releases_listener ran in parallel with the other security tests, whose services take the lowest free port as they start and could grab the one it had just freed. It now has its own test binary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stacked on
fix/ui-pass(which sits on #21).Why
Opening the phone's files failed with "Host key verification failed": GVfs mounted EchoConnect's SFTP server through the system
ssh. The phone made a new host key on every start, so~/.ssh/known_hostsnever matched. The app also still said "KDE Connect" everywhere and carried stock-KDE fallbacks we don't need.What
Own file transport (
crates/phonefs, new)$XDG_RUNTIME_DIR/echofiles/<phone name>. Nossh, noknown_hosts, no GVfs.Phone file server (
crates/connect): needs a new APK, not built yetsftp-host-keyin the core's data dir).join("")added a trailing slash, so every existing file failed with ENOTDIR. This affected GVfs too.nodelay: each write was waiting about 40 ms on Nagle. A 50 MB write went from 9.2 s to 1.4 s on loopback.statvfs(free space) anderrno Nin error replies.Files.kt: file-sharing failures now arrive in the reply EchoFiles waits for. Before, the laptop sat on "Checking…" forever.EchoConnect only
echofiles.*packets (stock KDE Connect, GSConnect). New test:stock_kde_connect_is_turned_away. I checked it fails when the filter is off.kdeconnectfilters,forward_ask. The hub's phone picture now comes from EchoConnect's photo list and thumbnails. It never showed with EchoConnect before.kdeconnect.*) are unchanged.UI
Testing
cargo testfor phone, connect, index, phonefs and config, plus theechofilesbin tests: all pass.cargo clippy --workspace --all-targetsis clean.sftp_serve) against the FUSE client (phonefsmountexample) on loopback: listing, reading and writing files byte-for-byte, overwrite, mkdir, rmdir, rename, truncate, mtime,df,rm -rand stale-mount recovery. A wrong fingerprint and a wrong password are both refused. Killing the server gives instant ENOTCONN, with no hang.🤖 Generated with Claude Code
This PR is not ready to merge because the earlier mount, write-order, rename, directory-copy, and save guarantees remain broken.
Findings
Summary
EchoFiles now mounts phone storage through its own pinned SFTP client and FUSE, replaces stock-KDE paths with EchoConnect, and adjusts grid rename fields and sidebar names.
Diagram
sequenceDiagram participant UI as EchoFiles participant Phone as EchoConnect participant Mount as PhoneFs UI->>Phone: Request shared files over paired TLS Phone-->>UI: Address, password, and host-key fingerprint UI->>Mount: Mount shared folder Mount->>Phone: Verify SSH key and authenticate UI->>Mount: Read or write through FUSE Mount->>Phone: Send SFTP requests Phone-->>Mount: Return data or file errorsReviews (2) · Last reviewed commit: "fix(ci): RustSec bumps; phone tests stop..." · Reviewed by Greptile