An agent driving the binary could search, fetch and chat, but it could not be
a peer: nothing kept a session online to serve files, nothing reported an
upload, shares could only be set at startup, and there was no way to ask
whether a peer was worth queueing to or to check credentials without doing
something else.
New commands:
serve [--follow] stay online sharing; one record per upload
whoami credentials, connection and what we offer
user <NAME> a peer's presence and share counts
room users <ROOM> the member roster
shares list|add|remove|status|reindex
config path|list|get|set
`config` and `shares list|add|remove` need no account, so they run before the
credential check — asking a script to log in for a `config get` would be a
poor trade. `serve` is the only place uploads are visible: they live in the
process serving them, so a one-shot `uploads list` could print nothing but an
empty list.
Library additions, all request/poll pairs matching the existing conventions:
- Client::room_members, kept current from the join and leave events
- Client::request_user_info / user_info, backed by new handlers for
GetUserStatus (7) and GetUserStats (36) — codes the dispatcher previously
logged as unhandled. The wire format is taken from soulfind's own source
and pinned by an e2e test that asserts a peer's real share count.
- UserInfo carries presence and stats as Options, so a reply that never
arrived cannot be read as "offline" or "shares nothing"
- request_user_info clears the previous answer, so the documented poll loop
observes this request rather than the last one
- ServerMessage and ClientOperation are now #[non_exhaustive]: every protocol
message the client learns adds a variant, and that should not break callers
- the crate root and prelude re-export every type a Client method returns
Also pins the output contract with tests: under --json, stdout carries only
newline-delimited JSON across fifteen command shapes while progress still
goes to stderr, and a failing command writes nothing to stdout at all.
Add Client::browse_user (sends GetShareFileList, code 4, connecting first if
needed via the same queue-and-flush path as search replies) and
take_browse_result. A new SharedFileListResponseHandler parses the peer's
code-5 reply into SharedDirectory entries, forwarded as
ClientOperation::BrowseResult and stored per user. Verified end to end: a
second real client browses a sharer and receives its file listing.
Add a std-only shares module: Shares::scan walks the shared directory (skipping
symlinks and unreadable entries) into a read-only snapshot keyed by peer-facing
virtual path (root name + backslash-separated relative path). Exposes
search (case-insensitive multi-term AND), get (O(1) by virtual path), and
file/folder counts for the login handshake. Unit-tested against a temp tree.
Wire private messaging through the client:
- MessageFactory::build_message_user (code 22) and build_message_acked
(code 23);
- the incoming MessageUser handler now acknowledges freshly delivered
messages and forwards them to the client instead of only logging them;
- ClientContext buffers received UserMessages;
- Client::send_private_message and Client::take_private_messages expose the
feature, and UserMessage (with accessors) is re-exported from the crate.
Verified end to end against soulfind: a_private_message_is_delivered_between_users
logs in two clients and asserts one receives the other's message.
Move the Vec<Download> and the download lifecycle methods (add, remove,
lookup, status update, pause, resume, remove-if-queued) into a new
download_store module. ClientContext keeps thin delegating shims so
peer/listen, peer/download_peer and the existing client tests carry on
unchanged. Client::pause_download / resume_download / remove_queued_download
now delegate one-liners. Client::process_failed_uploads keeps the
read-then-write lock dance but the inner filtering is the new
collect_failed_tokens helper.
Adds dedicated unit tests for the store's behavior (add/get/remove,
pause+resume, queued-only removal, failed-token collection) so the
state-machine logic is exercised independently of Client.