Skip to content

Commit 864baf3

Browse files
committed
add clap and change structures for easier integration
1 parent 767e75c commit 864baf3

40 files changed

Lines changed: 2237 additions & 3043 deletions

Cargo.lock

Lines changed: 376 additions & 325 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,6 @@ env_logger = { version = "0.11.2", default-features = false, features = [
168168
"auto-color",
169169
] }
170170
futures-util = { version = "0.3", default-features = false }
171-
getopts = "0.2"
172171
log = "0.4"
173172
sha1 = "0.10"
174173
sysinfo = { version = "0.36", default-features = false, features = ["system"] }
@@ -181,6 +180,12 @@ tokio = { version = "1", features = [
181180
"process",
182181
] }
183182
url = "2.2"
183+
clap = { version = "4.5.54", features = ["derive"] }
184+
clap-verbosity-flag = "3.0.4"
185+
serde = { version = "1.0.228", features = ["derive"]}
186+
187+
[build-dependencies]
188+
cfg_aliases = "0.2.1"
184189

185190
[package.metadata.deb]
186191
maintainer = "Librespot Organization <[email protected]>"

build.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use cfg_aliases::cfg_aliases;
2+
3+
fn main() {
4+
cfg_aliases! {
5+
discovery: { any(feature = "with-libmdns", feature = "with-dns-sd", feature = "with-avahi") }
6+
}
7+
}

connect/src/state.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,25 @@ pub struct ConnectConfig {
9595
impl Default for ConnectConfig {
9696
fn default() -> Self {
9797
Self {
98-
name: "librespot".to_string(),
99-
device_type: DeviceType::Speaker,
98+
name: ConnectConfig::DEFAULT_NAME.to_string(),
99+
device_type: DeviceType::default(),
100100
is_group: false,
101-
initial_volume: u16::MAX / 2,
101+
initial_volume: ConnectConfig::DEFAULT_INITIAL_VOLUME,
102102
disable_volume: false,
103-
volume_steps: 64,
103+
volume_steps: ConnectConfig::DEFAULT_VOLUME_STEPS,
104104
}
105105
}
106106
}
107107

108+
impl ConnectConfig {
109+
/// Default name
110+
pub const DEFAULT_NAME: &str = "librespot";
111+
/// Default initial_volume
112+
pub const DEFAULT_INITIAL_VOLUME: u16 = u16::MAX / 2;
113+
/// Default volume_steps
114+
pub const DEFAULT_VOLUME_STEPS: u16 = 64;
115+
}
116+
108117
#[derive(Default, Debug)]
109118
pub(super) struct ConnectState {
110119
/// the entire state that is updated to the remote server

core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ tokio-tungstenite = { version = "0.28", default-features = false }
108108
tokio-util = { version = "0.7", default-features = false }
109109
url = "2"
110110
uuid = { version = "1", default-features = false, features = ["v4"] }
111+
clap = { version = "4.5.54", default-features = false }
111112

112113
[build-dependencies]
113114
rand = { version = "0.9", default-features = false, features = ["thread_rng"] }

core/src/config.rs

Lines changed: 28 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
use std::{fmt, path::PathBuf, str::FromStr};
1+
use std::path::PathBuf;
22

3+
use clap::ValueEnum;
34
use librespot_protocol::devices::DeviceType as ProtoDeviceType;
5+
use serde::{Deserialize, Serialize};
46
use url::Url;
57

68
pub(crate) const KEYMASTER_CLIENT_ID: &str = "65b708073fc0480ea92a077233ca87bd";
@@ -31,15 +33,21 @@ pub struct SessionConfig {
3133
pub autoplay: Option<bool>,
3234
}
3335

36+
impl Default for SessionConfig {
37+
fn default() -> Self {
38+
Self::default_for_os(OS)
39+
}
40+
}
41+
3442
impl SessionConfig {
3543
pub(crate) fn default_for_os(os: &str) -> Self {
36-
let device_id = uuid::Uuid::new_v4().as_hyphenated().to_string();
3744
let client_id = match os {
3845
"android" => ANDROID_CLIENT_ID,
3946
"ios" => IOS_CLIENT_ID,
4047
_ => KEYMASTER_CLIENT_ID,
4148
}
4249
.to_owned();
50+
let device_id = uuid::Uuid::new_v4().as_hyphenated().to_string();
4351

4452
Self {
4553
client_id,
@@ -52,13 +60,21 @@ impl SessionConfig {
5260
}
5361
}
5462

55-
impl Default for SessionConfig {
56-
fn default() -> Self {
57-
Self::default_for_os(OS)
58-
}
59-
}
60-
61-
#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq, Default)]
63+
#[derive(
64+
Clone,
65+
Copy,
66+
Debug,
67+
Hash,
68+
PartialOrd,
69+
Ord,
70+
PartialEq,
71+
Eq,
72+
Default,
73+
ValueEnum,
74+
Serialize,
75+
Deserialize,
76+
)]
77+
#[clap(rename_all = "lower")]
6278
pub enum DeviceType {
6379
Unknown = 0,
6480
Computer = 1,
@@ -81,67 +97,9 @@ pub enum DeviceType {
8197
Observer = 102,
8298
}
8399

84-
impl FromStr for DeviceType {
85-
type Err = ();
86-
fn from_str(s: &str) -> Result<Self, Self::Err> {
87-
use self::DeviceType::*;
88-
match s.to_lowercase().as_ref() {
89-
"computer" => Ok(Computer),
90-
"tablet" => Ok(Tablet),
91-
"smartphone" => Ok(Smartphone),
92-
"speaker" => Ok(Speaker),
93-
"tv" => Ok(Tv),
94-
"avr" => Ok(Avr),
95-
"stb" => Ok(Stb),
96-
"audiodongle" => Ok(AudioDongle),
97-
"gameconsole" => Ok(GameConsole),
98-
"castaudio" => Ok(CastAudio),
99-
"castvideo" => Ok(CastVideo),
100-
"automobile" => Ok(Automobile),
101-
"smartwatch" => Ok(Smartwatch),
102-
"chromebook" => Ok(Chromebook),
103-
"carthing" => Ok(CarThing),
104-
_ => Err(()),
105-
}
106-
}
107-
}
108-
109-
impl From<&DeviceType> for &str {
110-
fn from(d: &DeviceType) -> &'static str {
111-
use self::DeviceType::*;
112-
match d {
113-
Unknown => "Unknown",
114-
Computer => "Computer",
115-
Tablet => "Tablet",
116-
Smartphone => "Smartphone",
117-
Speaker => "Speaker",
118-
Tv => "TV",
119-
Avr => "AVR",
120-
Stb => "STB",
121-
AudioDongle => "AudioDongle",
122-
GameConsole => "GameConsole",
123-
CastAudio => "CastAudio",
124-
CastVideo => "CastVideo",
125-
Automobile => "Automobile",
126-
Smartwatch => "Smartwatch",
127-
Chromebook => "Chromebook",
128-
UnknownSpotify => "UnknownSpotify",
129-
CarThing => "CarThing",
130-
Observer => "Observer",
131-
}
132-
}
133-
}
134-
135-
impl From<DeviceType> for &str {
136-
fn from(d: DeviceType) -> &'static str {
137-
(&d).into()
138-
}
139-
}
140-
141-
impl fmt::Display for DeviceType {
142-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143-
let str: &str = self.into();
144-
f.write_str(str)
100+
impl From<DeviceType> for String {
101+
fn from(value: DeviceType) -> Self {
102+
format!("{value:?}")
145103
}
146104
}
147105

core/src/session.rs

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ struct SessionData {
9898
}
9999

100100
struct SessionInternal {
101-
config: SessionConfig,
101+
config: Arc<SessionConfig>,
102102
data: RwLock<SessionData>,
103103

104104
http_client: HttpClient,
@@ -117,20 +117,14 @@ struct SessionInternal {
117117
handle: tokio::runtime::Handle,
118118
}
119119

120-
/// A shared reference to a Spotify session.
121-
///
122-
/// After instantiating, you need to login via [Session::connect].
123-
/// You can either implement the whole playback logic yourself by using
124-
/// this structs interface directly or hand it to a
125-
/// `Player`.
126-
///
127-
/// *Note*: [Session] instances cannot yet be reused once invalidated. After
128-
/// an unexpectedly closed connection, you'll need to create a new [Session].
129-
#[derive(Clone)]
130-
pub struct Session(Arc<SessionInternal>);
120+
impl Drop for SessionInternal {
121+
fn drop(&mut self) {
122+
debug!("drop Session");
123+
}
124+
}
131125

132-
impl Session {
133-
pub fn new(config: SessionConfig, cache: Option<Cache>) -> Self {
126+
impl SessionInternal {
127+
pub fn new(config: Arc<SessionConfig>, cache: Option<Arc<Cache>>) -> Self {
134128
let http_client = HttpClient::new(config.proxy.as_ref());
135129

136130
debug!("new Session");
@@ -142,12 +136,12 @@ impl Session {
142136
..SessionData::default()
143137
};
144138

145-
Self(Arc::new(SessionInternal {
139+
Self {
146140
config,
147141
data: RwLock::new(session_data),
148142
http_client,
149143
tx_connection: OnceLock::new(),
150-
cache: cache.map(Arc::new),
144+
cache,
151145
apresolver: OnceLock::new(),
152146
audio_key: OnceLock::new(),
153147
channel: OnceLock::new(),
@@ -157,7 +151,35 @@ impl Session {
157151
token_provider: OnceLock::new(),
158152
login5: OnceLock::new(),
159153
handle: tokio::runtime::Handle::current(),
160-
}))
154+
}
155+
}
156+
}
157+
158+
/// A shared reference to a Spotify session.
159+
///
160+
/// After instantiating, you need to login via [Session::connect].
161+
/// You can either implement the whole playback logic yourself by using
162+
/// this structs interface directly or hand it to a
163+
/// `Player`.
164+
///
165+
/// *Note*: [Session] instances cannot yet be reused once invalidated. After
166+
/// an unexpectedly closed connection, you'll need to create a new [Session].
167+
#[derive(Clone)]
168+
pub struct Session(Arc<SessionInternal>);
169+
170+
impl Session {
171+
pub fn new(config: SessionConfig, cache: Option<Cache>) -> Self {
172+
Self(Arc::new(SessionInternal::new(
173+
Arc::new(config),
174+
cache.map(Arc::new),
175+
)))
176+
}
177+
178+
pub fn renew(&self) -> Session {
179+
Self(Arc::new(SessionInternal::new(
180+
self.0.config.clone(),
181+
self.0.cache.clone(),
182+
)))
161183
}
162184

163185
async fn connect_inner(
@@ -661,12 +683,6 @@ impl SessionWeak {
661683
}
662684
}
663685

664-
impl Drop for SessionInternal {
665-
fn drop(&mut self) {
666-
debug!("drop Session");
667-
}
668-
}
669-
670686
#[derive(Clone, Copy, Default, Debug, PartialEq)]
671687
enum KeepAliveState {
672688
#[default]

core/src/spclient.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ impl SpClient {
176176

177177
let client_data = request.mut_client_data();
178178

179-
client_data.client_version = spotify_semantic_version();
179+
client_data.client_version = spotify_semantic_version().to_string();
180180

181181
// Current state of affairs: keymaster ID works on all tested platforms, but may be phased out,
182182
// so it seems a good idea to mimick the real clients. `self.session().client_id()` returns the

core/src/version.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,20 @@ pub fn spotify_version() -> String {
4545
}
4646
}
4747

48-
pub fn spotify_semantic_version() -> String {
48+
pub fn spotify_semantic_version() -> &'static str {
4949
match crate::config::OS {
50-
"android" | "ios" => SPOTIFY_MOBILE_VERSION.to_owned(),
51-
_ => SPOTIFY_SEMANTIC_VERSION.to_string(),
50+
"android" | "ios" => SPOTIFY_MOBILE_VERSION,
51+
_ => SPOTIFY_SEMANTIC_VERSION,
5252
}
5353
}
54+
55+
pub fn libresport_version() -> String {
56+
#[cfg(debug_assertions)]
57+
const BUILD_PROFILE: &str = "debug";
58+
#[cfg(not(debug_assertions))]
59+
const BUILD_PROFILE: &str = "release";
60+
61+
format!(
62+
"librespot {SEMVER} {SHA_SHORT} (Built on {BUILD_DATE}, Build ID: {BUILD_ID}, Profile: {BUILD_PROFILE})"
63+
)
64+
}

discovery/Cargo.toml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,20 @@ description = "The discovery logic for librespot"
88
repository.workspace = true
99
edition.workspace = true
1010

11+
[[example]]
12+
name = "discovery_group"
13+
required-features = ["default"]
14+
15+
[[example]]
16+
name = "discovery"
17+
required-features = ["default"]
18+
1119
[features]
1220
# Refer to the workspace Cargo.toml for the list of features
1321
default = ["with-libmdns", "native-tls"]
1422

1523
# Discovery backends
16-
with-avahi = ["dep:serde", "dep:zbus", "futures-util/async-await-macro"]
24+
with-avahi = ["dep:zbus", "futures-util/async-await-macro"]
1725
with-dns-sd = ["dep:dns-sd"]
1826
with-libmdns = ["dep:libmdns"]
1927

@@ -46,7 +54,7 @@ log = "0.4"
4654
rand = { version = "0.9", default-features = false, features = ["thread_rng"] }
4755
serde = { version = "1", default-features = false, features = [
4856
"derive",
49-
], optional = true }
57+
]}
5058
serde_repr = "0.1"
5159
serde_json = "1.0"
5260
sha1 = "0.10"
@@ -55,11 +63,16 @@ tokio = { version = "1", features = ["sync", "rt"] }
5563
zbus = { version = "5", default-features = false, features = [
5664
"tokio",
5765
], optional = true }
66+
clap = { version = "4.5.54", default-features = false }
67+
enum-assoc = "1.2.4"
5868

5969
[dev-dependencies]
6070
futures = "0.3"
6171
hex = "0.4"
6272
tokio = { version = "1", features = ["macros", "rt"] }
6373

74+
[build-dependencies]
75+
cfg_aliases = "0.2.1"
76+
6477
[lints]
6578
workspace = true

0 commit comments

Comments
 (0)