feat: seperate into lib and cli packages

This commit is contained in:
Michel de Graaf
2025-11-11 21:55:53 +01:00
parent 00046f2d59
commit 7bf2748e1f
51 changed files with 334 additions and 70 deletions
Generated
+7
View File
@@ -5,3 +5,10 @@ version = 4
[[package]] [[package]]
name = "soulseek-rs" name = "soulseek-rs"
version = "0.1.0" version = "0.1.0"
dependencies = [
"soulseek-rs-lib",
]
[[package]]
name = "soulseek-rs-lib"
version = "0.1.0"
+9 -4
View File
@@ -1,5 +1,10 @@
[package] [workspace]
name = "soulseek-rs" members = ["soulseek-rs-lib", "soulseek-rs"]
version = "0.1.0" resolver = "2"
edition = "2021"
[workspace.package]
version = "0.1.0"
authors = ["Michel de Graaf"]
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/yourusername/soulseek-rs"
+38 -14
View File
@@ -2,8 +2,8 @@
## ⚠️WARNING THIS IS UNDER DEVELOPMENT AND NOT READY FOR USE ⚠️ ## ⚠️WARNING THIS IS UNDER DEVELOPMENT AND NOT READY FOR USE ⚠️
Souleek-rs is an experimental Soulseek client built in Rust. It's under Souleek-rs is an experimental Soulseek library and client built in Rust. It's
development and not yet ready for use. Soulseek is a closed-source P2P under development and not yet ready for use. Soulseek is a closed-source P2P
file-sharing network from the 2000s. It's still used by music enthusiasts file-sharing network from the 2000s. It's still used by music enthusiasts
around the world to share niche music. around the world to share niche music.
@@ -17,9 +17,12 @@ about asynchronous and concurrent network programming and reverse engineering
It's not intended to be a production-ready client (yet). It's not intended to be a production-ready client (yet).
Since it's a learning project, I have a self-imposed restriction not to use Since it's a learning project, I have a self-imposed restriction not to use
external dependencies. This means I can't use any external crates that are not external dependencies in the library. This means I can't use any external
part of the Rust standard library. This is a good challenge to learn how to crates that are not part of the Rust standard library. This is a good challenge
build complex systems with only the standard library. to learn how to build complex systems with only the standard library.
In the client crate, external dependencies are allowed for building a rich
experience. For me, this is a good balance between learning and practicality.
## Planned Features ## Planned Features
@@ -33,18 +36,30 @@ build complex systems with only the standard library.
- [ ] Private messaging - [ ] Private messaging
- [ ] Headless mode daemon mode with remote control - [ ] Headless mode daemon mode with remote control
## Project structure ## Project Structure
Soulseek-rs is planned to be structured as a generic reusable library This project is organized as a Cargo workspace with two crates:
(soulseek-rs-lib) with a CLI client (soulseek-rs).
The CLI client will have a simple command-line interface to interact with the - **soulseek-rs-lib** - The core library implementing the Soulseek protocol
library. A rich TUI mode will be added in the future. - **soulseek-rs** - A CLI client built on top of the library
This structure allows:
- Other developers to build custom Soulseek clients using `soulseek-rs-lib`
- Users to install the ready-made client via `cargo install soulseek-rs`
- Clean separation of concerns between protocol implementation and user interface
## Installation ## Installation
To install Soulseek-rs, you'll need to have Rust installed on your system. Then, ### For Users
you can clone the repository and build it with cargo:
```bash
cargo install soulseek-rs
```
### For Developers
Clone and build from source:
```bash ```bash
git clone git@github.com:michel/soulseek-rs.git git clone git@github.com:michel/soulseek-rs.git
@@ -52,12 +67,21 @@ cd soulseek-rs
cargo build --release cargo build --release
``` ```
The binary will be available at `target/release/soulseek-rs`. The binary will be available at `target/release/soulseek`.
### For Library Users
To build your own Soulseek client, add to your `Cargo.toml`:
```toml
[dependencies]
soulseek-rs-lib = "0.1.0"
```
## Usage ## Usage
```bash ```bash
./target/release/soulseek-rs "the weeknd Blinding Lights" ./target/release/soulseek "the weeknd Blinding Lights"
``` ```
## Development ## Development
+122
View File
@@ -0,0 +1,122 @@
# Release Guide
This guide explains how to publish the `soulseek-rs-lib` and `soulseek-rs` crates to crates.io.
## Prerequisites
1. Create an account on [crates.io](https://crates.io/)
2. Generate an API token: https://crates.io/me
3. Login to cargo: `cargo login <your-token>`
## Publishing Process
### Step 1: Publish the Library First
The client depends on the library, so the library must be published first.
```bash
cd soulseek-rs-lib
cargo publish --dry-run # Test the publish
cargo publish # Actually publish
```
### Step 2: Wait for crates.io to Index
After publishing the library, wait 1-2 minutes for crates.io to index it before publishing the client.
### Step 3: Update Client Dependency (for releases)
When publishing to crates.io, the client's `Cargo.toml` should reference the published library version, not the local path.
Edit `soulseek-rs/Cargo.toml`:
```toml
[dependencies]
# For local development:
soulseek-rs-lib = { version = "0.1.0", path = "../soulseek-rs-lib" }
# For publishing (comment out path):
# soulseek-rs-lib = "0.1.0"
```
Or use both with optional features:
```toml
[dependencies]
soulseek-rs-lib = "0.1.0"
[dev-dependencies]
soulseek-rs-lib = { version = "0.1.0", path = "../soulseek-rs-lib" }
```
### Step 4: Publish the Client
```bash
cd ../soulseek-rs
cargo publish --dry-run # Test the publish
cargo publish # Actually publish
```
## Version Management
When bumping versions:
1. Update the version in root `Cargo.toml` under `[workspace.package]`
2. Both crates will inherit this version automatically
3. Publish library first, then client
## Automated Releases (Optional)
You can automate this with GitHub Actions. Create `.github/workflows/release.yml`:
```yaml
name: Release
on:
push:
tags:
- 'v*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Publish library
run: |
cd soulseek-rs-lib
cargo publish --token ${{ secrets.CARGO_TOKEN }}
- name: Wait for crates.io
run: sleep 60
- name: Publish client
run: |
cd soulseek-rs
cargo publish --token ${{ secrets.CARGO_TOKEN }}
```
## Testing Installation
After publishing, test the installation:
```bash
cargo install soulseek-rs
soulseek --help # Should work
```
## For Developers Building Custom Clients
Developers can use the library by adding to their `Cargo.toml`:
```toml
[dependencies]
soulseek-rs-lib = "0.1.0"
```
They'll get access to all the types and functionality exported in `lib.rs`.
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "soulseek-rs-lib"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
description = "Library for Soulseek protocol implementation in Rust"
keywords = ["soulseek", "p2p", "protocol", "file-sharing"]
categories = ["network-programming"]
[lib]
name = "soulseek_rs"
path = "src/lib.rs"
[dependencies]
# Add your dependencies here as needed
# For example:
# tokio = { version = "1", features = ["full"] }
# serde = { version = "1", features = ["derive"] }
+55
View File
@@ -0,0 +1,55 @@
# soulseek-rs-lib
A Rust library for implementing the Soulseek peer-to-peer protocol.
## About
This library provides the core functionality for interacting with the Soulseek
network. It can be used to build custom Soulseek clients or bots.
## Usage
Add this to your `Cargo.toml`:
```toml
[dependencies]
soulseek-rs-lib = "0.1.0"
```
## Example
```rust
use soulseek_rs::{Client, PeerAddress};
use std::time::Duration;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create and connect to Soulseek server
let mut client = Client::new(
PeerAddress::new("server.slsknet.org".to_string(), 2242),
"username".to_string(),
"password".to_string(),
false,
None,
);
client.connect();
client.login()?;
// Search for files
let results = client.search("Alex Kassian lifestream", Duration::from_secs(10))?;
// Download first available file
if let Some(result) = results.iter().find(|r| !r.files.is_empty()) {
let file = &result.files[0];
client.download(
file.name.clone(),
file.username.clone(),
file.size,
"~/Downloads".to_string(),
)?;
println!("Downloaded: {}", file.name);
}
Ok(())
}
```
+23
View File
@@ -0,0 +1,23 @@
[package]
name = "soulseek-rs"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
description = "Command-line client for Soulseek protocol"
keywords = ["soulseek", "p2p", "client", "file-sharing"]
categories = ["command-line-utilities"]
[[bin]]
name = "soulseek"
path = "src/main.rs"
[dependencies]
# Depend on your library
soulseek-rs-lib = { version = "0.1.0", path = "../soulseek-rs-lib" }
# Add client-specific dependencies here
# For example:
# clap = { version = "4", features = ["derive"] }
# anyhow = "1"
+26
View File
@@ -0,0 +1,26 @@
# soulseek-rs
A command-line client for the Soulseek peer-to-peer network.
## Installation
`bash cargo install soulseek-rs `
This will install the `soulseek` binary to your system.
## Usage
The client provides a simple interface to search and download files from the
Soulseek network.
## Building from Source
````bash git clone https://github.com/yourusername/soulseek-rs cd soulseek-rs
cargo build --release ```
## Library
If you want to build your own Soulseek client, check out the
[soulseek-rs-lib](https://crates.io/crates/soulseek-rs-lib) library crate.
````
+34
View File
@@ -0,0 +1,34 @@
use soulseek_rs::{Client, PeerAddress};
use std::time::Duration;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create and connect to Soulseek server
let mut client = Client::new(
PeerAddress::new("server.slsknet.org".to_string(), 2242),
"username".to_string(),
"password".to_string(),
false,
None,
);
client.connect();
client.login()?;
// Search for files
let results =
client.search("Alex Kassian lifestream", Duration::from_secs(10))?;
// Download first available file
if let Some(result) = results.iter().find(|r| !r.files.is_empty()) {
let file = &result.files[0];
client.download(
file.name.clone(),
file.username.clone(),
file.size,
"~/Downloads".to_string(),
)?;
println!("Downloaded: {}", file.name);
}
Ok(())
}
-52
View File
@@ -1,52 +0,0 @@
fn main() {
use soulseek_rs::{Client, PeerAddress};
use std::time::Duration;
let mut client = Client::new(
PeerAddress::new(String::from("server.slsknet.org"), 2242),
String::from("xxxxxx"),
String::from("xxxxxx"),
false,
None,
);
client.connect();
match client.login() {
Ok(_) => {
match client
.search("Eternity Alex Kassian", Duration::from_secs(30))
{
Ok(results) => {
if let Some(result) =
results.iter().find(|r| !r.files.is_empty())
{
let file = result.files[0].clone();
match client.download(
file.name,
file.username,
file.size,
"~/Downloads".to_string(),
) {
Ok(download_result) => {
println!(
"Download result: {download_result:?}"
);
}
Err(e) => {
eprintln!("Failed to download: {e}");
}
}
} else {
eprint!("No results");
}
}
Err(e) => {
eprintln!("Failed to search: {e}");
}
}
}
Err(e) => {
eprintln!("Failed to login: {}", e);
}
}
}