Make multi-file phone→laptop shares prompt and self-reporting - #13
Open
X-Ryl669 wants to merge 1 commit into
Open
Make multi-file phone→laptop shares prompt and self-reporting#13X-Ryl669 wants to merge 1 commit into
X-Ryl669 wants to merge 1 commit into
Conversation
…ting Selecting more than two files to send from the phone took minutes, while a single file was instant. Measured on 2026-08-20: a five-file share sat 2m34s before its first byte moved, then completed in 8 s once a heartbeat round finally ran. Everything below came out of that one report; each part is a distinct defect on the same path, and the path only works once they are all fixed. The pull is piggybacked on the LAN heartbeat, ONE file per round, so the heartbeat's cadence IS the transfer rate: * the cadence knew nothing about the queue. Four failed rounds and it fell to the idle tick — 240 s while BLE is live — parking a half-received batch for minutes. It now stays at 2 s while files are queued AND the queue is moving (`file_pull_active()`), easing to 12 s, never 240, once it stalls: a queue that can never drain must not spin a TCP+IK every 2 s for the session; * the cached-IP probe got a single 2 s connect. `resolve_peer_addr` already retries 3× because a dozing Wi-Fi radio answers a cold connect late, and excused the heartbeat on the grounds that it "ticks again in a few seconds" — untrue for a round carrying a file. It now retries when work is queued; * the phone was often unreachable at the cached address at all. While BLE is up it releases the multicast lock and answers no mDNS, so a DHCP renew left the laptop dialing a dead lease with no way to learn better (observed: 2m34s spent on 192.168.0.15 while the phone sat on .125). `LanServer.keepLanHot()` now holds the throughput Wi-Fi lock AND the multicast lock for 60 s around a pull, re-announces over mDNS, and the first offer of a batch pushes AppState over BLE — that carries the live `wifi_ip`, which repoints the cache with no mDNS involved. The throughput lock was also per-blob, so the radio parked between rounds (one file pulled at 0.2 MB/s where warm rounds do 3.5-7.8); * a token the phone had evicted wedged the queue for ever. It answers `"nomatch"` in the bulk-sync done frame, which `exchange_bulk` logged and discarded, so the laptop re-requested a dead token every round, blocking every file behind it. `LanReconnectOutcome.bulk_status` now carries that map and the pull arm drops the entry. Silence is never treated as an answer, and the pop is guarded on the queue front still being the requested token. The offer that starts it all was fire-and-forget, and both ways it could fail were invisible from the phone: * `sealAndNotify` returning false (no BLE session — app restarted, out of range, laptop re-handshaking) was discarded and logged as success. Observed: three files shared 31 s before a BLE reconnect completed went nowhere while the phone logged "offered to laptop" and toasted "Sending 3 files…". Offers are now retried every 3 s for ~1 min, and immediately when the link returns; * returning TRUE only means the local stack queued the notify. Three offers 4-9 ms apart cost one outright — the laptop logged `resynced past dropped BLE frame(s) dropped=1` and queued 2 of 3 files. Offers are now paced 60 ms (the only bulk BLE path here that had no pacing at all), the LAN warming is deferred 250 ms past the burst so its own mDNS re-announce and STATE notify stop cutting in line, and a sent-but-unfetched offer is re-announced. The laptop dedups by content token, so a re-announce of one that DID arrive can't queue a second pull, write a spurious "name (1).ext", or re-prompt for consent. A dropped offer no longer waits out the 30 s resend timer either: the laptop's pull queue is FIFO in arrival order, so it fetching a LATER offer while an earlier one is outstanding PROVES the earlier one never arrived (`offersPresumedDropped()`), and that one is re-announced within ~1 s. Finally, feedback, because a share could sit a minute with nothing on screen after the share sheet's "Sending…": the phone reports "Laptop unreachable — keeping the file(s) queued" once per outage, "File sent: <name>" as each file is FETCHED (the only moment this device can honestly call success), and names what was lost if an offer ends up nowhere. Verified on the live pair (Plasma 6 laptop + phone over BLE/Wi-Fi): a 3-file share that took 2m34s+ before now completes in ~6 s, with each file's "File sent:" toast; a force-stopped phone app recovers by itself — offers held through a 29 s BLE reconnect, delivered on retry, and a notify dropped in flight re-announced and fetched — where before all three files vanished silently. Tests: 141 daemon + 37 app (Rust), 88 Android unit tests, no new clippy warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Selecting more than two files to send from the phone took minutes, while a single file was instant. A test shown that a five-file share sat 2m34s before its first byte moved, then completed in 8 s once a heartbeat round finally ran. Everything below came out of that one report; each part is a distinct defect on the same path, and the path only works once they are all fixed.
This PR implements a faster (single and multi) file transfer with user feedback on the smartphone (via toasts). It's also able to resume a failed transfer (by emptying the queue of a failed item that the current code doesn't do and get struck infinitely). This is authored by Claude.
The pull is piggybacked on the LAN heartbeat, ONE file per round, so the heartbeat's cadence IS the transfer rate:
file_pull_active()), easing to 12 s, never 240, once it stalls: a queue that can never drain must not spin a TCP+IK every 2 s for the session;resolve_peer_addralready retries 3× because a dozing Wi-Fi radio answers a cold connect late, and excused the heartbeat on the grounds that it "ticks again in a few seconds" — untrue for a round carrying a file. It now retries when work is queued;LanServer.keepLanHot()now holds the throughput Wi-Fi lock AND the multicast lock for 60 s around a pull, re-announces over mDNS, and the first offer of a batch pushes AppState over BLE — that carries the livewifi_ip, which repoints the cache with no mDNS involved. The throughput lock was also per-blob, so the radio parked between rounds (one file pulled at 0.2 MB/s where warm rounds do 3.5-7.8);"nomatch"in the bulk-sync done frame, whichexchange_bulklogged and discarded, so the laptop re-requested a dead token every round, blocking every file behind it.LanReconnectOutcome.bulk_statusnow carries that map and the pull arm drops the entry. Silence is never treated as an answer, and the pop is guarded on the queue front still being the requested token.The offer that starts it all was fire-and-forget, and both ways it could fail were invisible from the phone:
sealAndNotifyreturning false (no BLE session — app restarted, out of range, laptop re-handshaking) was discarded and logged as success. Observed: three files shared 31 s before a BLE reconnect completed went nowhere while the phone logged "offered to laptop" and toasted "Sending 3 files…". Offers are now retried every 3 s for ~1 min, and immediately when the link returns;resynced past dropped BLE frame(s) dropped=1and queued 2 of 3 files. Offers are now paced 60 ms (the only bulk BLE path here that had no pacing at all), the LAN warming is deferred 250 ms past the burst so its own mDNS re-announce and STATE notify stop cutting in line, and a sent-but-unfetched offer is re-announced. The laptop dedups by content token, so a re-announce of one that DID arrive can't queue a second pull, write a spurious "name (1).ext", or re-prompt for consent.A dropped offer no longer waits out the 30 s resend timer either: the laptop's pull queue is FIFO in arrival order, so it fetching a LATER offer while an earlier one is outstanding PROVES the earlier one never arrived (
offersPresumedDropped()), and that one is re-announced within ~1 s.Finally, feedback, because a share could sit a minute with nothing on screen after the share sheet's "Sending…": the phone reports "Laptop unreachable — keeping the file(s) queued" once per outage, "File sent: " as each file is FETCHED (the only moment this device can honestly call success), and names what was lost if an offer ends up nowhere.
Verified on the live pair (Plasma 6 laptop + phone over BLE/Wi-Fi): a 3-file share that took 2m34s+ before now completes in ~6 s, with each file's "File sent:" toast; a force-stopped phone app recovers by itself — offers held through a 29 s BLE reconnect, delivered on retry, and a notify dropped in flight re-announced and fetched — where before all three files vanished silently. Tests: 141 daemon + 37 app (Rust), 88 Android unit tests, no new clippy warnings.