Files
emby-tui-client/src/config.rs
T
janisslsmandClaude Opus 4.7 af43b21f5d Initial commit: Emby TUI client with libmpv playback
A ratatui-based terminal browser for an Emby server with mpv as the player
backend. Authenticates via /Users/AuthenticateByName, lists libraries and
items, streams via /Videos/{id}/stream, and reports playback progress so
"Continue Watching" stays in sync across clients.

Credentials are read in priority order: CLI flags, env vars
(EMBY_SERVER/EMBY_USER/EMBY_PASSWORD), ./config.toml or
$XDG_CONFIG_HOME/emby-client/config.toml, then an in-TUI login prompt with
an option to persist to config.toml.

Modules: config (CLI + TOML loader), login (TUI prompt), emby (HTTP client
+ progress Reporter), player (libmpv2 wrapper, blocks until file ends),
app (browse state, Continue Watching virtual entry), ui (ratatui frontend).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-16 21:38:22 +03:00

112 lines
3.7 KiB
Rust

use anyhow::{Context, Result};
use clap::Parser;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
#[derive(Parser, Debug, Clone)]
#[command(name = "emby-client", about = "TUI Emby client with libmpv playback")]
pub struct CliArgs {
/// Emby server URL (e.g. http://emby.home.local)
#[arg(long, env = "EMBY_SERVER")]
pub server: Option<String>,
/// Emby username
#[arg(short, long, env = "EMBY_USER")]
pub user: Option<String>,
/// Emby password
#[arg(short, long, env = "EMBY_PASSWORD")]
pub password: Option<String>,
/// Path to config file. Defaults: ./config.toml then $XDG_CONFIG_HOME/emby-client/config.toml
#[arg(short, long)]
pub config: Option<PathBuf>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FileConfig {
pub server: Option<String>,
pub user: Option<String>,
pub password: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Config {
pub server: String,
pub user: String,
pub password: String,
}
/// Returns `(Some(config), save_target)` when all credentials are available,
/// or `(None, save_target)` when at least one is missing — the caller can then
/// prompt the user. `save_target` is the path where new credentials should
/// be persisted if the user chooses to save them.
pub fn load(args: &CliArgs) -> Result<(Option<Config>, Option<PathBuf>)> {
let (file_cfg, save_target) = read_file_config(args.config.as_deref())?;
let server = args.server.clone().or(file_cfg.server);
let user = args.user.clone().or(file_cfg.user);
let password = args.password.clone().or(file_cfg.password);
let cfg = match (server, user, password) {
(Some(server), Some(user), Some(password)) => Some(Config {
server: server.trim_end_matches('/').to_string(),
user,
password,
}),
_ => None,
};
Ok((cfg, Some(save_target)))
}
fn read_file_config(explicit: Option<&Path>) -> Result<(FileConfig, PathBuf)> {
let default_target = default_save_path();
let candidates: Vec<PathBuf> = if let Some(p) = explicit {
vec![p.to_path_buf()]
} else {
let mut v = vec![PathBuf::from("config.toml")];
if let Some(dirs) = directories::ProjectDirs::from("", "", "emby-client") {
v.push(dirs.config_dir().join("config.toml"));
}
v
};
let mut save_target = explicit
.map(|p| p.to_path_buf())
.unwrap_or(default_target);
for path in &candidates {
if path.exists() {
let text = std::fs::read_to_string(path)
.with_context(|| format!("reading config {}", path.display()))?;
let cfg: FileConfig = toml::from_str(&text)
.with_context(|| format!("parsing config {}", path.display()))?;
save_target = path.clone();
return Ok((cfg, save_target));
}
}
Ok((FileConfig::default(), save_target))
}
fn default_save_path() -> PathBuf {
if let Some(dirs) = directories::ProjectDirs::from("", "", "emby-client") {
dirs.config_dir().join("config.toml")
} else {
PathBuf::from("config.toml")
}
}
pub fn save(path: &Path, cfg: &Config) -> Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("creating {}", parent.display()))?;
}
let file_cfg = FileConfig {
server: Some(cfg.server.clone()),
user: Some(cfg.user.clone()),
password: Some(cfg.password.clone()),
};
let text = toml::to_string_pretty(&file_cfg).context("serializing config")?;
std::fs::write(path, text).with_context(|| format!("writing {}", path.display()))?;
Ok(())
}