initial discovery implementation

This commit is contained in:
2024-10-03 18:29:25 -05:00
parent bbe5e24e33
commit bafdd942e6
4 changed files with 3182 additions and 79 deletions

3099
Cargo.lock generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -4,3 +4,15 @@ version = "0.0.5"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
librespot-discovery = { git = "https://github.com/librespot-org/librespot", branch = "dev" }
librespot-core = { git = "https://github.com/librespot-org/librespot", branch = "dev" }
serde = { version = "1.0.210", features = ["derive"] }
tokio = { version = "1.40.0", features = ["full"] }
futures = "0.3.30"
serde_json = "1.0.128"
sha1 = "0.10.6"
hex = "0.4.3"
log = "0.4.22"
hex-literal = "0.4.1"
home = "0.5.9"
env_logger = "0.11.5"

View File

@@ -4,7 +4,7 @@ A simple CLI-based application for creating a `credentials.json` file, used by t
## Usage ## Usage
You can install this applicaiton, but most people will just need it once. You can install this application, but most people will just need it once. The following commands will run the application without installing it.
For Linux and macOS, you can paste this command into your terminal: For Linux and macOS, you can paste this command into your terminal:

View File

@@ -1,82 +1,74 @@
use std::env; use std::{env, fs::File, process::exit};
use std::process::Command;
fn main() { use futures::StreamExt;
let os = env::consts::OS; use librespot_core::config::DeviceType;
let arch = env::consts::ARCH; use librespot_discovery::Discovery;
let username = if os == "windows" { use log::{info, warn};
env::var("USERNAME").unwrap_or_else(|_| "unknown".to_string()) use sha1::{Digest, Sha1};
} else { use librespot_core::SessionConfig;
env::var("USER").unwrap_or_else(|_| "unknown".to_string()) use std::io::Write;
#[tokio::main(flavor = "current_thread")]
async fn main() {
if env::var("RUST_LOG").is_err() {
env::set_var("RUST_LOG", "info")
}
env_logger::builder().init();
let credentials_file = match home::home_dir() {
// ~/.cache/spotify_player/credentials.json
Some(path) => path.join(".cache/spotify_player/credentials.json"),
None => {
warn!("Cannot determine home directory for credentials file.");
exit(1);
}
}; };
let hostname = Command::new("hostname") info!("Credentials file: {}", &credentials_file.display());
.output()
.map(|output| String::from_utf8_lossy(&output.stdout).trim().to_string())
.unwrap_or_else(|_| "unknown".to_string());
let execution_path = env::current_exe()
.map(|path| path.display().to_string())
.unwrap_or_else(|_| "unknown".to_string());
let working_directory = env::current_dir()
.map(|path| path.display().to_string())
.unwrap_or_else(|_| "unknown".to_string());
println!( // TODO: If credentials file exists, confirm overwrite
"{}@{} ({}/{}) [{}] [{}]", if credentials_file.exists() {
username, hostname, os, arch, working_directory, execution_path warn!("Credentials file already exists: {}", &credentials_file.display());
); exit(1);
} }
// TODO: If spotifyd is running, ask if shutdown is desired
#[cfg(test)] let username = match env::consts::OS {
mod tests { "windows" => env::var("USERNAME"),
use super::*; _ => env::var("USER"),
}.unwrap_or_else(|_| "unknown".to_string());
#[test] let device_name = format!("spotify-quickauth-{}", username);
fn test_os_arch() { let device_id = hex::encode(Sha1::digest(device_name.as_bytes()));
let os = env::consts::OS; let device_type = DeviceType::Computer;
let arch = env::consts::ARCH;
// Make sure that OS and ARCH are not empty let mut server = Discovery::builder(device_id, SessionConfig::default().client_id)
assert!(!os.is_empty()); .name(device_name.clone())
assert!(!arch.is_empty()); .device_type(device_type)
} .launch()
.unwrap();
#[test] println!("Open Spotify and select output device: {}", device_name);
fn test_username() {
let username = env::var("USER").unwrap_or_else(|_| "unknown".to_string());
// In CI environments, USER might not be set, so allow "unknown" let mut written = false;
assert!(!username.is_empty()); while let Some(credentials) = server.next().await {
} let result = File::create(&credentials_file).and_then(|mut file| {
let data = serde_json::to_string(&credentials)?;
write!(file, "{data}")
});
written = true;
#[test] if let Err(e) = result {
fn test_hostname() { warn!("Cannot save credentials to cache: {}", e);
let hostname = Command::new("hostname") exit(1);
.output() } else {
.map(|output| String::from_utf8_lossy(&output.stdout).trim().to_string()) println!("Credentials saved: {}", &credentials_file.display());
.unwrap_or_else(|_| "unknown".to_string()); exit(0);
}
// Since hostname might vary, just ensure it's not empty }
assert!(!hostname.is_empty());
} if !written {
warn!("No credentials were written.");
#[test] exit(1);
fn test_execution_path() {
let execution_path = env::current_exe()
.map(|path| path.display().to_string())
.unwrap_or_else(|_| "unknown".to_string());
// Ensure execution path is not "unknown" when it's expected to resolve
assert!(execution_path != "unknown");
}
#[test]
fn test_working_directory() {
let working_directory = env::current_dir()
.map(|path| path.display().to_string())
.unwrap_or_else(|_| "unknown".to_string());
// Ensure the working directory is a valid path
assert!(working_directory != "unknown");
} }
} }