v0.27.0: Cassette invites — one paste connects two friends
- Connections ▸ Share Library with a Friend… makes a one-time invite code (device id, display name, token) and the inviter's send-only folder. - Connections ▸ Add Friend Library… takes the pasted code: adds the inviter, shares our folder, and accepts theirs receive-only when it's offered back. - Syncthing hides an unknown device's folders (verified on 1.30), so the inviter probes a new knock only while an invite is open: added with nothing shared, completed on the right token, otherwise removed and never probed again. Used and cancelled codes go nowhere. - Sync Settings lists open invites (Cancel Invite) and gives each friend a page: avatar, plain-language status with a next step, last seen, last synced, Remove Friend. Removals are staged until OK. - scripts/cassette_pair.py runs throwaway Syncthings on loopback; pytest --syncthing drives a real three-machine handshake. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -456,6 +456,32 @@ persistence) → GUI (Qt widgets that read the manager and connect to its signal
|
||||
registered in `setup.py` via `package_data`; loaded with
|
||||
`importlib.resources` so an installed copy finds them.
|
||||
|
||||
- **`lintunes/cassette/`** — friend library sharing (Round 63+; the spec is
|
||||
`Cassette friend library.md`). LinTunes never talks to a friend: it drives
|
||||
the **local** Syncthing's REST API (`syncthing_api.py`, key/address from
|
||||
Syncthing's `config.xml`, overridable in `config.json`) and Syncthing moves
|
||||
the bytes. Every failure is a `SyncthingError(step, detail, kind)`, because
|
||||
the UI promises never to fail silently. One of trav's machines is the
|
||||
**host** (`host.py`: `config.json` flag + a synced `preferences.json` record
|
||||
naming it); everything lives in the machine-local
|
||||
`~/.local/share/lintunes/cassette/` (`state.json` + `friends/<token>/
|
||||
{out,in,cache}`), never the synced data dir — Syncthing folders must not
|
||||
nest. A friendship is two folders, `lintunes-cassette-<token>-a` (inviter →
|
||||
invitee) and `-b`, each send-only for its writer and receive-only for the
|
||||
reader. **Syncthing hides an unknown device's folders** (verified on 1.30: a
|
||||
stranger is only a pending *device*), so while an invite is open the
|
||||
inviter's `CassetteService` *probes* a new knock — adds it with nothing
|
||||
shared, reads which folder it offers, completes on the right `-b` token or
|
||||
removes and remembers it (`state.rejected`). With no open invite nothing is
|
||||
probed and knocks stay pending, untouched. The paste side can't tell "their
|
||||
computer is off" from "their LinTunes is closed" (Syncthing briefly reports a
|
||||
stranger's connection as up), so it shows one combined status.
|
||||
`scripts/cassette_pair.py` starts throwaway Syncthings on loopback (no
|
||||
discovery there, hence `CassetteService(addresses_for=…)`); tests marked
|
||||
`syncthing` run a real handshake with `pytest --syncthing`. The service
|
||||
starts from `run_gui`, never from constructing `MainWindow` — tests build
|
||||
windows by the dozen and must not reach a real Syncthing.
|
||||
|
||||
- **`lintunes/cast/`** — Chromecast playback (Connections menu), using the
|
||||
**media-receiver model**: `server.py` runs a `ThreadingHTTPServer` on an
|
||||
ephemeral port for the life of a session and the device fetches the *original*
|
||||
|
||||
Reference in New Issue
Block a user