Skip to content

Report how many servers are paired in status - #105

Open
chrisuthe wants to merge 2 commits into
mainfrom
feat/status-paired-servers
Open

chrisuthe wants to merge 2 commits into
mainfrom
feat/status-paired-servers

Conversation

@chrisuthe

Copy link
Copy Markdown
Member

Why

status's trust: line describes only the server connected right now. While nothing is connected it reads not connected, so a script cannot tell a player that has never been paired from one whose paired server just hasn't connected yet.

The guided installers are about to offer pairing during setup: show the pairing token, and display a dynamic code live while the server pairs. They need exactly that distinction, so they can offer pairing on a fresh install and stay quiet on an upgrade. That is the same "an upgrade asks nothing" rule they already follow for the name and output.

What

  • status prints paired servers: N, the number of servers with a stored pairing record, whether or not one is connected. It sits right after trust:.
  • StateStore::pairing_record_count() counts the occupied record slots, using the same rule has_pairing_record() used: a slot the library freed is kept as zeroes and doesn't count. has_pairing_record() is now defined as count > 0, so its one caller (whether to log the pairing token at startup) is unchanged.
  • ControlDispatcher takes a const StateStore&. It is read on the main loop only, which matches the store's threading rule.
  • Docs: the status examples in Controlling the Player and Pairing a Player. The latter's grep -E '^(trust|pairing)' wouldn't match the new line, so it now also matches paired.

The library has no query for its pairing records. It persists every record through our persistence provider into the StateStore, which makes the store the place to count them.

Testing

  • New StateStore.CountsEachOccupiedPairingSlot: occupied slots count, freed slots don't, the count survives a reload, and a slot zeroed on unpairing drops out. New FormatStatus.PairedServersAreReportedEvenWhileDisconnected.
  • Native Windows build (clang-cl, -Werror): 519/519 passed.
  • Against real data: a player started on a copy of an installed player's state file (paired with a Music Assistant server), with mDNS off so it stays disconnected, reports trust: not connected / paired servers: 1. A fresh state directory reports paired servers: 0. The copy was deleted afterwards.
  • scripts/format.sh --check with clang-format 20.1.8.

status's trust line describes only the server connected right now, so a script
could not tell a player that has never been paired from one whose paired
server is simply not connected yet. The installers need exactly that to offer
pairing on a fresh install without nagging on an upgrade. 'paired servers: N'
counts the stored pairing records, connected or not.
@chrisuthe
chrisuthe requested a balanced review from Copilot October 28, 2026 17:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

4 open issues
What changed in this PR

Adds a paired servers: N line to sendspin-cli status so scripts/installers can distinguish “never paired” from “paired but currently disconnected”.

Changes:

  • Introduces StateStore::pairing_record_count() and redefines has_pairing_record() in terms of it.
  • Extends status snapshot/formatting to always report the number of stored pairing records.
  • Updates docs and adds tests covering slot counting behavior and status output.
File Description
tests/​state_store_test.cpp Adds a unit test validating pairing record slot counting across freed/loaded states.
tests/​control_test.cpp Adds a formatting test ensuring paired servers appears even when disconnected.
src/​state_store.h Declares pairing_record_count() API.
src/​state_store.cpp Implements record-slot counting and updates has_pairing_record() accordingly.
src/​main.cpp Injects StateStore into ControlDispatcher to populate paired_servers in status snapshots.
src/​control_common.cpp Prints paired servers in status output.
src/​control.h Adds paired_servers field to StatusSnapshot.
docs/​wiki/​Pairing-a-Player.md Updates examples/grep and explains the distinction between trust and paired servers.
docs/​wiki/​Controlling-the-Player.md Updates status output example to include paired servers.

Review effort: Lite

Comment thread docs/wiki/Pairing-a-Player.md Outdated
Comment on lines +18 to +19
`trust` is about the server connected right now; `paired servers` counts every server this
player has paired with, connected or not.
Comment thread src/main.cpp
Comment on lines 497 to +500
snapshot.client_id = this->client_.client_id();
snapshot.connected = this->client_.is_connected();
snapshot.trust = this->pairing_listener_.trust();
snapshot.paired_servers = this->state_store_.pairing_record_count();
Comment thread src/state_store.cpp
const std::optional<std::vector<uint8_t>> record =
this->blob(sendspin::persistence_keys::record_slot_key(slot));
// A freed slot is kept as zeroes.
if (record.has_value() &&
Comment thread src/state_store.h
std::optional<std::vector<uint8_t>> blob(const std::string& key) const;
bool set_blob(const std::string& key, const uint8_t* data, size_t len);

/// How many servers the library has a pairing record stored for.
A slot of the wrong size, or with an all-zero PSK, was counted as a
pairing because it held a non-zero byte. Ask decode_pairing_record(),
the library's own rule, instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants