use koan_core::config;
use owo_colors::OwoColorize;
use super::open_db;
pub fn cmd_remote_login(url: &str, username: &str) {
if !url.starts_with("https://") && !url.contains("localhost") && !url.contains("127.0.0.1") {
eprintln!("warning: server URL does not use HTTPS — credentials will be sent in plaintext");
}
let password = rpassword::prompt_password("password: ").unwrap_or_else(|e| {
eprintln!("{} {}", "error:".red().bold(), e);
std::process::exit(1);
});
let client = koan_core::remote::client::SubsonicClient::new(url, username, &password);
match client.ping() {
Ok(()) => println!("{} {}", "connected".green(), url),
Err(e) => {
eprintln!("{} {}", "connection failed:".red().bold(), e);
std::process::exit(1);
}
}
let mut remote_vals = toml::map::Map::new();
remote_vals.insert("enabled".into(), toml::Value::Boolean(true));
remote_vals.insert("url".into(), toml::Value::String(url.to_string()));
remote_vals.insert("username".into(), toml::Value::String(username.to_string()));
remote_vals.insert("password".into(), toml::Value::String(password));
if let Err(e) = config::Config::patch_local("remote", &remote_vals) {
eprintln!("{} {}", "config error:".red().bold(), e);
std::process::exit(1);
}
println!("{}", "credentials saved to config.local.toml".green());
}
pub fn cmd_remote_sync(full: bool) {
let cfg = config::Config::load().unwrap_or_default();
let client = match koan_core::helpers::subsonic_client(&cfg) {
Some(c) => c,
None => {
eprintln!(
"{} no remote server configured — run {} first",
"error:".red().bold(),
"koan remote login".bold()
);
std::process::exit(1);
}
};
let db = open_db();
let start = std::time::Instant::now();
match koan_core::remote::sync::sync_library(
&db,
&client,
full,
&cfg.remote.url,
&cfg.remote.username,
) {
Ok(result) => {
let elapsed = start.elapsed();
let headline = if result.is_complete() {
"sync complete".green().bold().to_string()
} else {
"sync incomplete".yellow().bold().to_string()
};
println!(
"{} {} {} artists, {} albums, {} tracks",
headline,
format!("({:.1}s)", elapsed.as_secs_f64()).dimmed(),
result.artists_synced.to_string().bold(),
result.albums_synced.to_string().bold(),
result.tracks_synced.to_string().bold(),
);
if !result.is_complete() {
eprintln!(
"{} {} album(s) could not be fetched — the sync watermark was left \
unchanged, so the next sync will retry them",
"warning:".yellow().bold(),
result.albums_failed.to_string().bold(),
);
}
}
Err(e) => {
eprintln!("{} {}", "sync failed:".red().bold(), e);
std::process::exit(1);
}
}
print!("{}", "syncing favourites...".dimmed());
use std::io::Write;
std::io::stdout().flush().ok();
let synced = koan_core::helpers::reconcile_favourites(&db, &client);
println!(
"\r{} {} pushed, {} imported",
"favourites synced:".green().bold(),
synced.pushed.to_string().bold(),
synced.imported.to_string().bold(),
);
}
pub fn cmd_remote_status() {
use koan_core::helpers::PasswordSource;
let cfg = config::Config::load().unwrap_or_default();
if !cfg.remote.enabled || cfg.remote.url.is_empty() {
println!("no remote server configured");
return;
}
println!("{} {}", "server:".cyan(), cfg.remote.url);
println!("{} {}", "username:".cyan(), cfg.remote.username);
let (_, source) = koan_core::helpers::remote_password(&cfg);
let described = match &source {
PasswordSource::Keychain => "from the keychain".green().to_string(),
PasswordSource::Config => "from config.local.toml".green().to_string(),
PasswordSource::Missing => "not set".red().to_string(),
PasswordSource::Unreadable(why) => format!(
"{} {}",
"in the keychain, but koan cannot read it".red(),
format!("\u{2014} {why}").dimmed()
),
};
println!("{} {}", "password:".cyan(), described);
let Some(client) = koan_core::helpers::subsonic_client(&cfg) else {
if matches!(source, PasswordSource::Unreadable(_)) {
println!(
"{} {}",
"hint:".yellow(),
"`koan remote login` rewrites the entry for this build".dimmed()
);
}
return;
};
match client.ping() {
Ok(()) => println!("{} {}", "status:".cyan(), "connected".green()),
Err(e) => println!(
"{} {} {}",
"status:".cyan(),
"error".red(),
format!("\u{2014} {}", e).dimmed()
),
}
}