Commit Graph
54 Commits
Author SHA1 Message Date
Michel de Graaf 14491768a5 feat(cli): print completion scripts and the man page to stdout
`completions install` writes into a home directory, which a package
build does not have. `completions print <shell>` and `man` give it the
files to put under /usr/share instead.
2026-09-21 12:15:43 +02:00
Michel de Graaf 6a650d8c3e chore: release v19.0.0 2026-09-10 12:16:13 +02:00
Michel de Graaf bb31c9e7a4 chore: release v18.0.0 2026-09-08 02:09:34 +02:00
Michel de Graaf 943abfab43 chore: release v17.0.0 2026-09-07 18:04:21 +02:00
Michel de Graaf 4781e3571a chore: release v16.0.0 2026-09-07 11:13:52 +02:00
Michel de Graaf d397e92085 chore: release v15.0.0 2026-09-06 22:01:42 +02:00
Michel de Graaf 0c6976dff7 Merge branch 'develop' 2026-09-06 22:00:23 +02:00
Michel de Graaf 06bc48fcd9 perf(tui): bound the name scroll walk by the column width
Scrolled rows cost 31.7ms per frame at 30k results against 13.3ms on
develop, because every row segmented its whole filename into grapheme
clusters. Walk back from the end for at most one column of cells and
forward for at most the offset, so a row costs the column width rather
than the filename, and drop unicode-truncate for unicode-width. A tail
never starts on a combining mark.
2026-09-06 21:42:36 +02:00
Michel de Graaf 3193c73f5b feat(tui): scroll long result names and page through results
Closes #55.

While [2] Results is focused the Info pane describes the highlighted
result, so a truncated path can be read in full. h/l (or the arrow keys)
scroll the filename column by display cells, 0 and $ jump to either end
of the highlighted name, and Home/End, g/G, PgUp/PgDn and Ctrl-f/b/d/u
move through the list; the same keys work while a filter is being typed.

Ctrl chords are no longer read as their bare letter: outside [2] they do
nothing, where before Ctrl-d deleted a search and Ctrl-c opened rooms.
Space after a filter that matches nothing no longer indexes an empty
list.
2026-09-06 20:10:59 +02:00
Michel de Graaf 28325685df chore: release v14.2.1 2026-09-03 11:02:38 +02:00
Michel de Graaf daec81de50 chore: release v14.2.0 2026-09-01 15:26:38 +02:00
Michel de Graaf 693926434d chore: release v14.1.2 2026-08-29 15:45:30 +02:00
Michel de Graaf 21f60df1d9 chore: release v14.1.2 2026-08-18 08:40:23 +02:00
Michel de Graaf 078ffee0e4 chore: release v14.1.1 2026-08-17 10:46:26 +02:00
Michel de Graaf 742fc21629 chore: release v14.1.0 2026-08-14 23:57:17 +02:00
Michel de Graaf 4cce8deea0 chore: release v14.0.1 2026-08-13 14:34:57 +02:00
Michel de Graaf c2e10beee4 chore: release v13.0.1 2026-08-10 09:32:56 +02:00
Michel de Graaf 0bbb559f68 fix: survive busy sessions without exhausting file descriptors
The peer listener died with "Too many open files (os error 24)" on any
long-lived non-daemon session: only the daemon raised RLIMIT_NOFILE, and
peer sockets were unbounded — no idle reaping, no deadlines on accepted
transfer streams, and one peer could hold two connections under split
registry identities.

- raise the open-file limit at the top of run(), for every entry point,
  and warn from Client::connect when the soft limit is below 1024
- reap peer control connections idle for five minutes; a clean
  disconnect no longer discards the peer's queued uploads
- give listener-supplied download streams and upload serving the same
  read/write deadlines dialled sockets already had
- register inbound peers under their plain username so the registry
  holds one connection per peer, and announce them like pierced ones
- hold the registry lock across actor spawn + insert and surface
  thread-spawn failure, so an instantly-dead peer cannot leave a
  zombie entry
2026-08-09 22:34:36 +02:00
Michel de Graaf 0d892c32be chore: release v13.0.0 (#25) 2026-07-29 11:04:39 +02:00
Michel de Graaf 220beb4e41 fix(daemon): ask the OS for enough open files, and stop spinning when it says no
A search fans out to hundreds of peers, each holding a socket. A shell hands
its children a soft limit of 256 open files on macOS, which one busy search
blows straight through; a one-shot command never noticed because it exited
before it mattered, but a resident daemon hits it and then cannot accept peers
or control connections. From a client that looks like 'cannot reach the
daemon: Connection closed', intermittently, for no visible reason.

The daemon now raises its own soft limit to 8192 at startup, before anything
opens a socket, and says so plainly when the hard limit will not allow it.
macOS rejects an unlimited hard limit for open files, so both fields are named.

Both accept loops also slept zero on failure. Out of descriptors does not clear
by the next instruction, so one exhausted moment became a spinning core and
thousands of identical log lines — which is what the report looked like. They
now back off.
2026-07-27 20:16:41 +02:00
Michel de Graaf f54f8f9b82 feat(daemon): resident session, auth-gated control socket, and event fan-out
The daemon drains the library's destructive take_* accessors once and fans
events out per connection, which is what lets more than one client share a
session. A Unix socket at 0600 is the local transport and its permissions
are the authentication; TCP is opt-in and needs the generated token. Rooms,
transfers, and the conversation are daemon-owned and restored on restart.
2026-07-27 10:05:25 +02:00
Michel de Graaf df089fbab3 feat(daemon): wire protocol with a type-safe method vocabulary and a generated OpenRPC contract
Methods and events are enums built by one macro, so every consumer matches
exhaustively: adding a member is a compile error until the dispatcher, the
published schema, and the remote client all handle it. docs/openrpc.json is
derived from the DTOs rather than hand-written, and a test fails the build
when the checked-in copy drifts.
2026-07-27 09:56:48 +02:00
Michel de Graaf 4214848a8a chore: release v12.0.0 (#24) 2026-07-26 19:08:00 +02:00
Michel de Graaf 093f32b360 chore: release v11.0.0 (#23) 2026-07-26 15:04:08 +02:00
Michel de Graaf 1854784b22 chore: release v10.0.0 (#21) 2026-07-26 14:31:00 +02:00
Michel de Graaf d6b8bec940 chore: release v9.1.0 (#19) 2026-07-26 12:59:49 +02:00
Michel de Graaf 481c815983 chore: release v9.0.0 (#17) 2026-07-26 12:21:11 +02:00
Michel de Graaf 534b09a20a feat: install tab completion for bash, zsh, and fish (#16)
`completions install` generates the script from the same clap definition
the binary parses with, so it cannot drift from `--help`, and writes it
where each shell already looks. fish scans its own completions directory
unprompted; bash and zsh get one marked `source` line appended to their rc
file, because nothing puts a per-user directory on their search path by
default. `completions uninstall` takes back exactly those lines and leaves
the rest of the file alone.
2026-07-26 12:18:53 +02:00
Michel de Graaf 45b83546ae chore: release v8.0.0 (#14) 2026-07-26 11:42:01 +02:00
github-actions[bot]andMichel de Graaf 69f3621f72 chore: release v7.1.0 (#11)
Co-authored-by: Michel de Graaf <michel@re-invention.nl>
2026-07-25 07:59:54 +02:00
github-actions[bot] 9de1547680 chore: release v7.0.0 (#8)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-25 03:21:14 +02:00
Michel de Graaf be5dc6c064 fix(peer): serve full browse listings + harden the listener; TUI transfer/UX work
Peer layer:
- Stream peer messages with a partial-write queue instead of write_all, so a
  large SharedFileListResponse over the non-blocking socket is no longer
  truncated (third-party clients like SoulseekQt can now browse our shares).
- Bound the peer-init handshake with a read timeout and handle each accepted
  connection on its own thread, so one stalled peer can't wedge the listener.
- e2e: third-party direct/brokered/large-share browse + stalled-peer coverage.

TUI:
- Uploads share the downloads table columns (real Speed) and the info pane now
  renders details for a selected upload.
- Download progress bar counts only in-progress transfers and hides when idle.
- Always-visible unread badge (private messages + rooms) in the shortcuts bar,
  self-drawn blink so it works under tmux.
- Persist the unread private-message count across restarts.
2026-07-24 21:18:20 +02:00
github-actions[bot]andMichel de Graaf eef7bf755f chore: release v6.0.0 (#4)
* chore: release v6.0.0

* docs: document the 6.0.0 breaking changes

cargo-semver-checks flagged four major-breaking changes in soulseek-rs-lib,
none of which carried a `!` or BREAKING CHANGE footer, so the generated
changelog had no migration note for a major bump.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Michel de Graaf <michel@re-invention.nl>
2026-07-24 12:47:21 +02:00
michel 5e34722a1b fix: drop libdbus dependency from keyring Linux backend
CI (and any Linux build without libdbus-1-dev) failed compiling
libdbus-sys, pulled in by keyring's sync-secret-service feature. Switch
to the pure-Rust zbus backend (async-secret-service + async-io +
crypto-rust): same Secret Service keychain, no system C library
needed.
2026-07-22 17:14:02 +02:00
michel aaeb62efa4 feat: password resolution via OS keychain and password_cmd
SecretStore trait with a keyring-backed implementation (macOS Keychain,
Windows Credential Manager, Linux Secret Service) under service
soulseek-rs. Precedence: --password/env > keychain > password_cmd from
config.toml. Keychain failures degrade to not-found so headless boxes
fall through to password_cmd.
2026-07-20 20:11:41 +02:00
michel 86e3d8c58f feat: config.toml settings layer (CLI > env > file > defaults)
New persist module in the binary crate (lib stays zero-dep): FileConfig
loaded from a platform-appropriate config path via the directories crate,
with resolution precedence CLI/env (clap) > config.toml > built-in
defaults. Clap defaults move into the resolver so the file layer can sit
between.
2026-07-20 20:10:33 +02:00
Michel de Graaf 2918b0ce75 refactor: resolve all lint findings
- remove 34 trivial restating comments (auto-fix)
- replace all 20 production .unwrap() calls with graceful handling:
  ?-propagation via LockPoisoned, unwrap_or_default on infallible
  conversions, let _ = on actor-channel sends, and a downloads_sender()
  helper in the TUI that also dedupes three channel-init blocks
- collapse nested if into match guard (clippy::collapsible_match)
- split client.rs (2275 lines) into a client/ module: connection,
  search, downloads, uploads, rooms, operations
- split ui/main_tui.rs (2027 lines) into a main_tui/ module: render,
  input, browse, rooms, downloads, search
- extract oversized functions: peer_actor/server_actor handle_message
  into per-message helpers, main() into init_logging/run_default_tui,
  download_selector and main_tui render into per-section helpers

Mechanical only, zero behavior change. Verified: clippy clean
(pedantic+nursery), fmt clean, 143 unit tests + 16 e2e tests against
soulfind pass, lint clean.
2026-07-20 10:53:57 +02:00
michel 5ba2f9e181 feat(net): auto-map the listen port via UPnP-IGD and NAT-PMP
Behind a home router the client is unreachable for incoming peer
connections, so browsing/downloading firewalled peers (and the
server-brokered connect-back) times out. Add a best-effort port mapper
to the binary crate that, when listening is enabled, makes us reachable:

- NAT-PMP (RFC 6886) hand-rolled on std::net::UdpSocket, zero deps, with
  an injectable gateway address so it is fully testable.
- UPnP-IGD via the blocking igd-next crate as the primary path.
- A background PortMapper that discovers off the main thread (never
  blocks startup), renews at half the lease, and removes the mapping on
  drop. Any failure is non-fatal and logged.
- Maps the EXACT listen port only (we advertise it verbatim via
  SetWaitPort, so a different external port would be unreachable) and
  warns the user to forward manually otherwise.

Wired into the TUI, search, and CLI browse (the P2P modes); chat/rooms/
messaging are server-mediated and skip it. e2e-tested against an
in-process mock NAT-PMP gateway: the map request bytes, response parsing,
result-code/timeout handling, and the full spawn -> map -> drop -> unmap
service lifecycle.
2026-07-19 21:10:15 +02:00
michel daac398f68 chore: update Rust toolchain and dependencies to latest
- clap 4.5.51 -> 4.6.2
- ratatui 0.29.0 -> 0.30.2
- cargo update for transitive deps
- toolchains updated: stable 1.97.1, nightly 1.99.0
2026-07-18 12:24:03 +02:00
Ariel D'Alessandro 3696e95760 soulseek-rs: main: Add support for dotenv environment
Signed-off-by: Ariel D'Alessandro <ariel.dalessandro@collabora.com>
2026-01-29 19:36:15 -03:00
Michel de Graaf 467de9cb91 chore: bump version 2025-11-13 14:13:28 +01:00
Michel de Graaf 6ca179373f chore: bump version 2025-11-12 21:18:36 +01:00
Michel de Graaf e87853c66a chore: bump version 2025-11-12 02:03:17 +01:00
Michel de Graaf 689e667ade chore: bump version 2025-11-12 01:41:43 +01:00
Michel de Graaf 98a8f53094 feat: improved cli 2025-11-11 23:38:59 +01:00
Michel de Graaf 7bf2748e1f feat: seperate into lib and cli packages 2025-11-11 21:55:53 +01:00
Michel de Graaf a4ece85eaf chore: bump version 2025-11-11 19:01:32 +01:00
Michel de Graaf 9655f09b77 chore: version bump 2025-11-11 14:53:40 +01:00
Michel de Graaf cd3dca40c5 feat: rename module to soulseek-rs 2025-07-31 22:24:34 +02:00
Michel de Graaf 78ac774e82 chore: remove flate2 as dependency for zlib decompression 2025-06-29 21:59:14 +02:00