-
Notifications
You must be signed in to change notification settings - Fork 0
Phase 1 Plan
The phase that turns Flume from a proof of the IPC path into a usable client.
Goal: a user can add a Linux ISO by magnet link or .torrent file, choose
which files they want, watch it download, and find it on disk afterwards — and
all of that survives a restart.
| Question | Decision | Consequence |
|---|---|---|
| Add flow | Show file picker first, then start | Adding is a two-step flow. Nothing downloads until the user confirms selection. |
| Layout | Single list + detail panel | One route. Selecting a row opens a detail panel rather than navigating away. |
| Settings storage | tauri-plugin-store (JSON) |
No SQLite dependency. Settings are an inspectable JSON file in app-data. |
Choosing "file picker first" is the right call for the ISO use case and it shapes the engine work. Distro torrents routinely bundle several images plus checksums, and downloading 4 GB of the wrong image is the exact frustration Flume exists to avoid.
It does mean the add path must resolve metadata before starting transfer.
For a .torrent file the file list is available immediately. For a magnet link
it is not — metadata must be fetched over the DHT first. So the add dialog needs
a genuine resolving state, and that state can fail or take a while.
This is the single most important interaction in the app. It should feel deliberate, not like a modal in the way.
Each step ends green-tested and committed. Issue numbers link the tracker.
1. Event-based telemetry (#5 groundwork)
Replace the Phase 0 polling hook before anything depends on it. Polling one status card is fine; polling a list of torrents is not.
- Rust: a 1 Hz task emitting a batched
TorrentsUpdatepayload viaemit - Frontend:
listen()subscription replacinguseCoreStatus's timer - Keep
get_core_statusas a command for initial paint
Why first: every later feature consumes this stream. Building it after the list means rewriting the list.
Engine layer, still Tauri-free:
-
add_torrent(source, options) -> TorrentIdwhere source is magnet or bytes list_torrents() -> Vec<TorrentSummary>-
pause,resume,remove(id, delete_files: bool) - Magnet URI validation in Rust, not just the UI — it is untrusted input
Integration tests against a real session using a well-seeded Linux ISO magnet,
#[ignore]d like the DHT test.
- Add dialog with magnet paste box and
.torrentpicker (tauri-plugin-dialog, permissiondialog:allow-openonly) - Clipboard magnet detection on window focus
- Resolving state for magnets, with a cancel affordance
- File tree with checkboxes, size per file, select-all/none
- Confirm starts the download with the chosen selection
4. Torrent list (#5)
- Row: name, progress, down/up speed, ETA, peers, ratio, state
- Pause/resume/remove per row
- Remove confirmation with an explicit "also delete files" checkbox, unchecked by default
- Empty state that tells a first-time user what to do
Selecting a row opens a panel showing file list with per-file progress, and basic peer/tracker counts. The richer detail view (piece heatmap) is Phase 2.
6. File selection changes after add (#6)
Session::update_only_files wired to the detail panel's file tree, so a user
can change their mind mid-download.
7. Settings (#7)
-
tauri-plugin-store, settings JSON in app-data - Download directory, global rate limits, max active torrents, listen port, UPnP toggle, DHT toggle, theme
- Port and DHT changes restart the session cleanly rather than needing an app relaunch
- Settings validated on load; a corrupt file falls back to defaults with a warning rather than refusing to start
8. Persistence verification (#8)
Not new code so much as proof: add a large torrent, kill the app mid-download, relaunch, confirm it resumes without a full re-hash. Automate what can be automated.
9. Windows seeding investigation (#9)
Confirm whether the file-locking problem reproduces on librqbit v9 before writing any patch. Document either way.
| Crate / package | Why |
|---|---|
tauri-plugin-dialog |
.torrent file picker |
tauri-plugin-store |
Settings persistence |
tauri-plugin-opener |
"Open containing folder" |
Each adds exactly one capability permission. No shell plugin.
Magnet metadata resolution can be slow or fail. The add dialog must handle a magnet that never resolves — a timeout with a clear message, not a spinner forever.
Rate limiting is per-session in librqbit. Per-torrent limits need verification against the v9 API before promising them in settings; if they are not supported, say so rather than shipping a control that does nothing.
Event volume. One batched update per second regardless of torrent count. Resist the temptation to emit per-torrent events.
- Add a Linux ISO by magnet and by
.torrent, choosing files first - Pause, resume, and remove work, with delete-files confirmation
- Settings persist and take effect without a relaunch
- Kill and relaunch mid-download resumes without full re-hash
- Telemetry is event-based and batched at ~1 Hz
- All CI gates green; new engine logic covered by tests
- Wiki User Guide updated to describe what actually shipped
Verified by hand on a fresh build: remove-with-delete, pause and resume, and settings surviving a close and reopen. Each is now also covered by a test, so the next regression is caught without another manual pass:
| Behaviour | Test |
|---|---|
| Remove keeps the data | remove_without_delete_leaves_files_on_disk |
| Remove deletes the data | remove_with_delete_takes_the_files_too |
| Pause and resume round trip | pause_and_resume_round_trip |
| Settings survive a restart | settings_survive_a_save_and_load |
| A first run has usable settings | absent_settings_load_as_usable_defaults_without_an_error |
| Limits apply without a relaunch | rate_limits_apply_to_the_running_session |
Two of those were writing themselves into a hole and are worth remembering.
remove_without_delete_leaves_files_on_disk never checked the files — it
asserted only that the torrent left the session, so the half its name promised
was untested. And the delete test asserts the file exists before removing it,
because a torrent that never laid anything down would make "the file is gone"
pass for the wrong reason.
A real torrent has been added, completed, seeded, and survived both a clean restart and a mid-download kill, resuming correctly each time. That is most of the first and fourth boxes, but neither is ticked yet and the reasons are worth keeping:
Both add routes are verified, on all three platforms. The .torrent route
took a Debian ISO through add, completion and seeding. The magnet route has
worked since early in the project, including the clipboard detection that offers
a magnet when the add sheet opens, and the flow after that has been exercised on
macOS, Windows and Linux.
They are worth distinguishing even though both now pass, because they reach the
same list_only: true preview differently: a .torrent is
AddTorrent::from_bytes and has its metadata in hand, so the listing returns
near-instantly. A magnet is AddTorrent::from_url and must fetch metadata
from peers over the DHT first, which is seconds rather than milliseconds and
depends on the DHT reading Ready. magnet_resolves_real_metadata_over_the_dht
covers the engine half; the UI across that wait is what manual use has now
shown.
The fourth box is met. Verified on a real Debian torrent: a mid-download
kill resumed correctly, and a clean quit relaunches straight into seeding with
no Checking state.
The mechanism, since "it looked fast" is not evidence on its own:
-
fastresume: trueonSessionOptions. librqbit defaults it to false, and with it false the JSON store is paired withNonPersistentBitVFactoryand every launch re-hashes everything. - The bitfield is flushed during the download, not only at exit.
on_piece_completedaccumulatesunflushed_bitv_bytesand flushes every 16 MiB, synchronously again when the torrent finishes, and once more onDrop. A killed process therefore leaves a bitfield at most 16 MiB stale, so a kill costs re-downloading up to 16 MiB — not re-hashing the torrent. -
RunEvent::Exitcallingsession.stop()is the final tidy-up rather than the thing that makes this work. An earlier version of this note had that backwards and concluded a kill must re-hash; it does not.
The state is observable rather than inferred: <info-hash>.bitv in the app data
directory is the persisted bitfield, and it is what a relaunch loads instead of
hashing. On a finished torrent every bit is set.
A re-hash, when it does happen, shows as the Checking state.
Deferred deliberately, not forgotten:
- Should completed torrents keep seeding by default, or stop at ratio 1.0?
- Does the tray icon own "pause all", or does the main window?
- Light theme: a true light palette, or a dimmed variant of the dark one?
Flume — Apache-2.0. This wiki is generated from docs/ by wiki-sync.yml; edits made here are overwritten on the next sync, so change the source instead and it gets reviewed with the code. The same pages, laid out for reading, are at flume.adamgreenwell.com/docs.
Using Flume
Developing
- Development-Setup
- Architecture
- Design-System
- Torrent-Engine-Notes
- CI-CD-and-Releases
- Signing-and-Distribution
Project