Skip to content
Draft

Clap #1689

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
729 changes: 402 additions & 327 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,6 @@ env_logger = { version = "0.11.2", default-features = false, features = [
"auto-color",
] }
futures-util = { version = "0.3", default-features = false }
getopts = "0.2"
log = "0.4"
sha1 = "0.10"
sysinfo = { version = "0.36", default-features = false, features = ["system"] }
Expand All @@ -181,6 +180,12 @@ tokio = { version = "1", features = [
"process",
] }
url = "2.2"
clap = { version = "4.5.54", features = ["derive"] }
clap-verbosity-flag = "3.0.4"
serde = { version = "1.0.228", features = ["derive"]}

[build-dependencies]
cfg_aliases = "0.2.1"

[package.metadata.deb]
maintainer = "Librespot Organization <[email protected]>"
Expand Down
7 changes: 7 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use cfg_aliases::cfg_aliases;

fn main() {
cfg_aliases! {
discovery: { any(feature = "with-libmdns", feature = "with-dns-sd", feature = "with-avahi") }
}
}
17 changes: 13 additions & 4 deletions connect/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,26 @@ pub struct ConnectConfig {
impl Default for ConnectConfig {
fn default() -> Self {
Self {
name: "librespot".to_string(),
device_type: DeviceType::Speaker,
name: ConnectConfig::DEFAULT_NAME.to_string(),
device_type: DeviceType::default(),
is_group: false,
initial_volume: u16::MAX / 2,
initial_volume: ConnectConfig::DEFAULT_INITIAL_VOLUME,
disable_volume: false,
volume_steps: 64,
volume_steps: ConnectConfig::DEFAULT_VOLUME_STEPS,
emit_set_queue_events: false,
}
}
}

impl ConnectConfig {
/// Default name
pub const DEFAULT_NAME: &str = "librespot";
/// Default initial_volume
pub const DEFAULT_INITIAL_VOLUME: u16 = u16::MAX / 2;
/// Default volume_steps
pub const DEFAULT_VOLUME_STEPS: u16 = 64;
}

#[derive(Default, Debug)]
pub(super) struct ConnectState {
/// the entire state that is updated to the remote server
Expand Down
1 change: 1 addition & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ tokio-tungstenite = { version = "0.28", default-features = false }
tokio-util = { version = "0.7", default-features = false }
url = "2"
uuid = { version = "1", default-features = false, features = ["v4"] }
clap = { version = "4.5.54", default-features = false }

[build-dependencies]
rand = { version = "0.9", default-features = false, features = ["thread_rng"] }
Expand Down
98 changes: 28 additions & 70 deletions core/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::{fmt, path::PathBuf, str::FromStr};
use std::path::PathBuf;

use clap::ValueEnum;
use librespot_protocol::devices::DeviceType as ProtoDeviceType;
use serde::{Deserialize, Serialize};
use url::Url;

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

impl Default for SessionConfig {
fn default() -> Self {
Self::default_for_os(OS)
}
}

impl SessionConfig {
pub(crate) fn default_for_os(os: &str) -> Self {
let device_id = uuid::Uuid::new_v4().as_hyphenated().to_string();
let client_id = match os {
"android" => ANDROID_CLIENT_ID,
"ios" => IOS_CLIENT_ID,
_ => KEYMASTER_CLIENT_ID,
}
.to_owned();
let device_id = uuid::Uuid::new_v4().as_hyphenated().to_string();

Self {
client_id,
Expand All @@ -52,13 +60,21 @@ impl SessionConfig {
}
}

impl Default for SessionConfig {
fn default() -> Self {
Self::default_for_os(OS)
}
}

#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq, Default)]
#[derive(
Clone,
Copy,
Debug,
Hash,
PartialOrd,
Ord,
PartialEq,
Eq,
Default,
ValueEnum,
Serialize,
Deserialize,
)]
#[clap(rename_all = "lower")]
pub enum DeviceType {
Unknown = 0,
Computer = 1,
Expand All @@ -81,67 +97,9 @@ pub enum DeviceType {
Observer = 102,
}

impl FromStr for DeviceType {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
use self::DeviceType::*;
match s.to_lowercase().as_ref() {
"computer" => Ok(Computer),
"tablet" => Ok(Tablet),
"smartphone" => Ok(Smartphone),
"speaker" => Ok(Speaker),
"tv" => Ok(Tv),
"avr" => Ok(Avr),
"stb" => Ok(Stb),
"audiodongle" => Ok(AudioDongle),
"gameconsole" => Ok(GameConsole),
"castaudio" => Ok(CastAudio),
"castvideo" => Ok(CastVideo),
"automobile" => Ok(Automobile),
"smartwatch" => Ok(Smartwatch),
"chromebook" => Ok(Chromebook),
"carthing" => Ok(CarThing),
_ => Err(()),
}
}
}

impl From<&DeviceType> for &str {
fn from(d: &DeviceType) -> &'static str {
use self::DeviceType::*;
match d {
Unknown => "Unknown",
Computer => "Computer",
Tablet => "Tablet",
Smartphone => "Smartphone",
Speaker => "Speaker",
Tv => "TV",
Avr => "AVR",
Stb => "STB",
AudioDongle => "AudioDongle",
GameConsole => "GameConsole",
CastAudio => "CastAudio",
CastVideo => "CastVideo",
Automobile => "Automobile",
Smartwatch => "Smartwatch",
Chromebook => "Chromebook",
UnknownSpotify => "UnknownSpotify",
CarThing => "CarThing",
Observer => "Observer",
}
}
}

impl From<DeviceType> for &str {
fn from(d: DeviceType) -> &'static str {
(&d).into()
}
}

impl fmt::Display for DeviceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let str: &str = self.into();
f.write_str(str)
impl From<DeviceType> for String {
fn from(value: DeviceType) -> Self {
format!("{value:?}")
}
}

Expand Down
62 changes: 39 additions & 23 deletions core/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ struct SessionData {
}

struct SessionInternal {
config: SessionConfig,
config: Arc<SessionConfig>,
data: RwLock<SessionData>,

http_client: HttpClient,
Expand All @@ -117,20 +117,14 @@ struct SessionInternal {
handle: tokio::runtime::Handle,
}

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

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

debug!("new Session");
Expand All @@ -142,12 +136,12 @@ impl Session {
..SessionData::default()
};

Self(Arc::new(SessionInternal {
Self {
config,
data: RwLock::new(session_data),
http_client,
tx_connection: OnceLock::new(),
cache: cache.map(Arc::new),
cache,
apresolver: OnceLock::new(),
audio_key: OnceLock::new(),
channel: OnceLock::new(),
Expand All @@ -157,7 +151,35 @@ impl Session {
token_provider: OnceLock::new(),
login5: OnceLock::new(),
handle: tokio::runtime::Handle::current(),
}))
}
}
}

/// A shared reference to a Spotify session.
///
/// After instantiating, you need to login via [Session::connect].
/// You can either implement the whole playback logic yourself by using
/// this structs interface directly or hand it to a
/// `Player`.
///
/// *Note*: [Session] instances cannot yet be reused once invalidated. After
/// an unexpectedly closed connection, you'll need to create a new [Session].
#[derive(Clone)]
pub struct Session(Arc<SessionInternal>);

impl Session {
pub fn new(config: SessionConfig, cache: Option<Cache>) -> Self {
Self(Arc::new(SessionInternal::new(
Arc::new(config),
cache.map(Arc::new),
)))
}

pub fn renew(&self) -> Session {
Self(Arc::new(SessionInternal::new(
self.0.config.clone(),
self.0.cache.clone(),
)))
}

async fn connect_inner(
Expand Down Expand Up @@ -661,12 +683,6 @@ impl SessionWeak {
}
}

impl Drop for SessionInternal {
fn drop(&mut self) {
debug!("drop Session");
}
}

#[derive(Clone, Copy, Default, Debug, PartialEq)]
enum KeepAliveState {
#[default]
Expand Down
2 changes: 1 addition & 1 deletion core/src/spclient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ impl SpClient {

let client_data = request.mut_client_data();

client_data.client_version = spotify_semantic_version();
client_data.client_version = spotify_semantic_version().to_string();

// Current state of affairs: keymaster ID works on all tested platforms, but may be phased out,
// so it seems a good idea to mimick the real clients. `self.session().client_id()` returns the
Expand Down
17 changes: 14 additions & 3 deletions core/src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,20 @@ pub fn spotify_version() -> String {
}
}

pub fn spotify_semantic_version() -> String {
pub fn spotify_semantic_version() -> &'static str {
match crate::config::OS {
"android" | "ios" => SPOTIFY_MOBILE_VERSION.to_owned(),
_ => SPOTIFY_SEMANTIC_VERSION.to_string(),
"android" | "ios" => SPOTIFY_MOBILE_VERSION,
_ => SPOTIFY_SEMANTIC_VERSION,
}
}

pub fn libresport_version() -> String {
#[cfg(debug_assertions)]
const BUILD_PROFILE: &str = "debug";
#[cfg(not(debug_assertions))]
const BUILD_PROFILE: &str = "release";

format!(
"librespot {SEMVER} {SHA_SHORT} (Built on {BUILD_DATE}, Build ID: {BUILD_ID}, Profile: {BUILD_PROFILE})"
)
}
17 changes: 15 additions & 2 deletions discovery/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,20 @@ description = "The discovery logic for librespot"
repository.workspace = true
edition.workspace = true

[[example]]
name = "discovery_group"
required-features = ["default"]

[[example]]
name = "discovery"
required-features = ["default"]

[features]
# Refer to the workspace Cargo.toml for the list of features
default = ["with-libmdns", "native-tls"]

# Discovery backends
with-avahi = ["dep:serde", "dep:zbus", "futures-util/async-await-macro"]
with-avahi = ["dep:zbus", "futures-util/async-await-macro"]
with-dns-sd = ["dep:dns-sd"]
with-libmdns = ["dep:libmdns"]

Expand Down Expand Up @@ -46,7 +54,7 @@ log = "0.4"
rand = { version = "0.9", default-features = false, features = ["thread_rng"] }
serde = { version = "1", default-features = false, features = [
"derive",
], optional = true }
]}
serde_repr = "0.1"
serde_json = "1.0"
sha1 = "0.10"
Expand All @@ -55,11 +63,16 @@ tokio = { version = "1", features = ["sync", "rt"] }
zbus = { version = "5", default-features = false, features = [
"tokio",
], optional = true }
clap = { version = "4.5.54", default-features = false }
enum-assoc = "1.2.4"

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

[build-dependencies]
cfg_aliases = "0.2.1"

[lints]
workspace = true
7 changes: 7 additions & 0 deletions discovery/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use cfg_aliases::cfg_aliases;

fn main() {
cfg_aliases! {
discovery: { any(feature = "with-libmdns", feature = "with-dns-sd", feature = "with-avahi") }
}
}
Loading
Loading