Skip to content

EchoConnect only: our own phone file transport; grid rename and sidebar fixes - #22

Merged
adityavardhansharma merged 4 commits into
fix/ui-passfrom
feat/echoconnect-own-files
Oct 24, 2026
Merged

adityavardhansharma merged 4 commits into
fix/ui-passfrom
feat/echoconnect-own-files

Conversation

@adityavardhansharma

@adityavardhansharma adityavardhansharma commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

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_hosts never 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)

  • An in-process SFTP client (russh) that accepts only the host key the phone sent over the paired TLS link, mounted with FUSE at $XDG_RUNTIME_DIR/echofiles/<phone name>. No ssh, no known_hosts, no GVfs.
  • Requests run concurrently, and large reads go out as several requests at once. The phone's errno comes through, so you see "Directory not empty" or "File exists" rather than a generic I/O error. A mount left behind by a crash is cleared before the next mount.

Phone file server (crates/connect): needs a new APK, not built yet

  • Host key kept across restarts (sftp-host-key in the core's data dir).
  • Bug fix: 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.
  • Truncate, statvfs (free space) and errno N in error replies.
  • Files.kt: file-sharing failures now arrive in the reply EchoFiles waits for. Before, the laptop sat on "Checking…" forever.

EchoConnect only

  • The link turns away peers that list no echofiles.* packets (stock KDE Connect, GSConnect). New test: stock_kde_connect_is_turned_away. I checked it fails when the filter is off.
  • Removed the stock-KDE paths: photo scanning over the mount, local thumbnails, GVfs kdeconnect filters, forward_ask. The hub's phone picture now comes from EchoConnect's photo list and thumbnails. It never showed with EchoConnect before.
  • Every "KDE Connect" label, hint and setup step now says EchoConnect. The new certificate identity says EchoFiles/EchoConnect; existing identities don't change. The wire packet names (kdeconnect.*) are unchanged.

UI

  • Grid rename: the tile's frame steps aside while renaming. The field is centred at the label's size, grows with the name as you type, and stays inside the pane.
  • Sidebar phone name ends in "…" before the battery meter.

Testing

  • cargo test for phone, connect, index, phonefs and config, plus the echofiles bin tests: all pass. cargo clippy --workspace --all-targets is clean.
  • Ran the real server (sftp_serve) against the FUSE client (phonefs mount example) on loopback: listing, reading and writing files byte-for-byte, overwrite, mkdir, rmdir, rename, truncate, mtime, df, rm -r and stale-mount recovery. A wrong fingerprint and a wrong password are both refused. Killing the server gives instant ENOTCONN, with no hang.
  • Grid rename checked in an isolated dev instance: short name, long name in the first column, typing past the original length, Enter commits.
  • Not yet tested: against the physical phone. That needs the new EchoConnect APK, which will be built through the release workflow once approved.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 0/5

This PR is not ready to merge because the earlier mount, write-order, rename, directory-copy, and save guarantees remain broken.

Findings

  1. P1 Phones share one mount ▶
  2. P1 Appended blocks change order ▶
  3. P1 Rename overwrites a new file ▶
  4. P1 Folder copies fail ▶
  5. P1 Saved changes remain in memory ▶
  6. P2 Crash recovery lacks its helper ▶

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.

  • EchoFiles shows a paired phone’s shared storage as a local folder.
  • The phone’s file server keeps its identity and supports more file operations.
  • The phone link accepts EchoFiles and EchoConnect peers only.
  • EchoFiles gets phone photos and thumbnails through EchoConnect.
  • Phone file browsing uses the new mount instead of GVfs.
  • Grid rename fields grow with the name and phone names fit beside the battery meter.
  • Phone setup text and labels use EchoConnect and EchoFiles names.
  • Phone service tests avoid competing for the same network ports.

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 errors
Loading

Reviews (2) · Last reviewed commit: "fix(ci): RustSec bumps; phone tests stop..." · Reviewed by Greptile

adityavardhansharma and others added 2 commits October 18, 2026 23:57
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-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 75721986-f6d9-4f23-93f5-c4d594639c85

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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>
Comment on lines +479 to +482
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 })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Phones share one mount

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.

Comment thread crates/phonefs/src/lib.rs
Comment on lines +780 to +784
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 });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Appended blocks change order

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.

Comment thread crates/phonefs/src/lib.rs
Comment on lines +664 to +667
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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Rename overwrites a new file

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.

Comment on lines +373 to +375
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)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Folder copies fail

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.

Comment thread crates/phonefs/src/lib.rs
Comment on lines +804 to +806
fn fsync(&self, _req: &Request, _ino: INodeNo, _fh: FileHandle, _datasync: bool, reply: ReplyEmpty) {
// The phone syncs a file when it's closed.
reply.ok();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Comment thread crates/phonefs/src/lib.rs
Comment on lines +153 to +154
if stale {
let _ = std::process::Command::new("fusermount3").arg("-u").arg("-z").arg(at).status();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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>
@adityavardhansharma
adityavardhansharma merged commit ed5b2ce into fix/ui-pass Oct 24, 2026
5 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant