feat(daemon): make the download folder a live setting in both modes

The full-surface audit found the settings story half-true: `config set
download_dir` and the TUI's settings form edited a file a running daemon
never rereads, while the attached TUI displayed the laptop's path and
discarded the edit entirely.

- `download.set_dir`: a session-wide move, published in the contract;
  the per-transfer destination stays refused. `daemon.status` reports
  it and the next transfer lands there.
- `config set download_dir` and `config set shared_dirs` push to a
  live daemon the way `shares add` does, through one shared helper;
  the socket probe is paid only by the keys a daemon owns.
- The attached TUI seeds its settings form from the daemon's status,
  pushes both folders over the socket as typed, reports a partial
  refusal as exactly that, and creates no client-side directories. It
  reads the daemon token from the resolved config — with the token only
  in config.toml it could not attach over TCP — and settings
  persistence honours --config instead of hard-coding the default path.
- `search.wishlist` records a start time, so an attached window
  renders it as running rather than instantly finished.

The rest is the coverage the audit demanded: CLI-level token auth
(wrong, missing, and valid over TCP), `daemon status|stop` with
nothing running, --sort, --limit, --pick first, --password-cmd,
--log-file, --upload-slots, the config-named download folder in both
modes, and headless settings-form tests attached and local. Every
guard was checked by disabling what it protects. Both e2e modes run
green: 94 CLI tests each way, 16 protocol, 285 unit, 34 lib.
This commit is contained in:
Michel de Graaf
2026-07-28 10:44:06 +02:00
parent 35f5cd4f22
commit cd56fb3b39
16 changed files with 917 additions and 89 deletions
+10 -5
View File
@@ -10,11 +10,11 @@
use super::proto::{
Ack, AuthParams, AuthResult, BrowseEvent, DaemonStatus, DirectoriesParams,
DownloadStartParams, DownloadStarted, DownloadStatusEvent, Downloads,
Event, IntervalSeconds, Members, MessageParams, Messages, Method,
QueryParams, RoomEventDto, RoomRef, SayParams, SearchResults, Searches,
Seconds, SessionLossEvent, SharesStatus, SlotsParams, TransferRef,
UploadInfoDto, Uploads, UserMessageDto, UserRef, UserResult,
DirectoryParams, DownloadStartParams, DownloadStarted, DownloadStatusEvent,
Downloads, Event, IntervalSeconds, Members, MessageParams, Messages,
Method, QueryParams, RoomEventDto, RoomRef, SayParams, SearchResults,
Searches, Seconds, SessionLossEvent, SharesStatus, SlotsParams,
TransferRef, UploadInfoDto, Uploads, UserMessageDto, UserRef, UserResult,
};
use schemars::{JsonSchema, SchemaGenerator, generate::SchemaSettings};
use serde_json::{Value, json};
@@ -49,6 +49,7 @@ fn params_schema(method: Method, generator: &mut SchemaGenerator) -> Value {
| Method::DownloadRemove
| Method::DownloadRemoveQueued
| Method::UploadCancel => schema_of::<TransferRef>(generator),
Method::DownloadSetDir => schema_of::<DirectoryParams>(generator),
Method::UploadSlots => schema_of::<SlotsParams>(generator),
Method::RoomJoin | Method::RoomLeave | Method::RoomMembers => {
schema_of::<RoomRef>(generator)
@@ -104,6 +105,7 @@ fn result_schema(method: Method, generator: &mut SchemaGenerator) -> Value {
| Method::DownloadResume
| Method::DownloadRemove
| Method::DownloadRemoveQueued
| Method::DownloadSetDir
| Method::UploadCancel
| Method::UploadSlots
| Method::PrivilegesCheck
@@ -155,6 +157,9 @@ fn summary(method: Method) -> &'static str {
Method::DownloadResume => "Resume a paused transfer.",
Method::DownloadRemove => "Forget a transfer entirely.",
Method::DownloadRemoveQueued => "Drop a transfer that has not started.",
Method::DownloadSetDir => {
"Land future transfers in a different folder on the daemon's host."
}
Method::UploadList => "Uploads being served, active first.",
Method::UploadCancel => "Stop serving an upload in progress.",
Method::UploadSlots => "How many uploads may run at once.",