diff --git a/src/cmd/auth.rs b/src/cmd/auth.rs index 94dd1c4..42ab0f9 100644 --- a/src/cmd/auth.rs +++ b/src/cmd/auth.rs @@ -1,4 +1,4 @@ -use crate::{config::Config, util}; +use crate::config::Config; use anyhow::{anyhow, Result}; use clap::Args; use reqwest::Url; @@ -8,8 +8,9 @@ pub struct Cli {} impl Cli { pub fn run(self, config: &Config) -> Result<()> { - let auth_url = auth_url(config.server_url())?; - let server_hostname = auth_url.host().ok_or(anyhow!("invalid server URL"))?; + let server_url = config.get_server_url()?; + let server_hostname = server_url.host().ok_or(anyhow!("invalid server URL"))?; + let auth_url = auth_url(&server_url, &config.get_install_id()?); println!("Open the following URL in a web browser to authenticate this asciinema CLI with your {server_hostname} user account:\n"); println!("{}\n", auth_url); @@ -19,9 +20,9 @@ impl Cli { } } -fn auth_url(server_url: Option<&String>) -> Result { - let mut url = util::get_server_url(server_url)?; - url.set_path(&format!("connect/{}", util::get_install_id()?)); +fn auth_url(server_url: &Url, install_id: &str) -> Url { + let mut url = server_url.clone(); + url.set_path(&format!("connect/{install_id}")); - Ok(url) + url } diff --git a/src/cmd/upload.rs b/src/cmd/upload.rs index 261c6a8..2a1360e 100644 --- a/src/cmd/upload.rs +++ b/src/cmd/upload.rs @@ -1,5 +1,4 @@ use crate::config::Config; -use crate::util; use anyhow::{anyhow, Result}; use clap::Args; use reqwest::{ @@ -27,9 +26,9 @@ impl Cli { let form = Form::new().file("asciicast", self.filename)?; let response = client - .post(api_url(config.server_url())?) + .post(api_url(&config.get_server_url()?)) .multipart(form) - .basic_auth(get_username(), Some(util::get_install_id()?)) + .basic_auth(get_username(), Some(config.get_install_id()?)) .header(header::USER_AGENT, build_user_agent()) .header(header::ACCEPT, "application/json") .send()?; @@ -57,11 +56,11 @@ impl Cli { } } -fn api_url(server_url: Option<&String>) -> Result { - let mut url = util::get_server_url(server_url)?; +fn api_url(server_url: &Url) -> Url { + let mut url = server_url.clone(); url.set_path("api/asciicasts"); - Ok(url) + url } fn get_username() -> String { diff --git a/src/config.rs b/src/config.rs index 49284f4..f30be72 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,8 +1,14 @@ -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; +use reqwest::Url; use serde::Deserialize; use std::env; use std::fs; +use std::io::ErrorKind; use std::path::{Path, PathBuf}; +use uuid::Uuid; + +const DEFAULT_SERVER_URL: &str = "https://asciinema.org"; +const INSTALL_ID_FILENAME: &str = "install-id"; #[derive(Debug, Deserialize)] #[allow(unused)] @@ -80,12 +86,46 @@ impl Config { Ok(config.build()?.try_deserialize()?) } - pub fn server_url(&self) -> Option<&String> { - self.server.url.as_ref().or(self.api.url.as_ref()) + pub fn get_server_url(&self) -> Result { + match self.server.url.as_ref() { + Some(url) => Ok(Url::parse(url)?), + + None => { + let url = Url::parse(&ask_for_server_url()?)?; + save_default_server_url(url.as_ref())?; + + Ok(url) + } + } + } + + pub fn get_install_id(&self) -> Result { + let path = install_id_path()?; + + if let Some(id) = read_install_id(&path)? { + Ok(id) + } else { + let id = create_install_id(); + save_install_id(&path, &id)?; + + Ok(id) + } } } -pub fn save_default_server_url(url: &str) -> Result<()> { +fn ask_for_server_url() -> Result { + println!("No asciinema server configured for this CLI."); + let mut rl = rustyline::DefaultEditor::new()?; + let url = rl.readline_with_initial( + "Enter the server URL to use by default: ", + (DEFAULT_SERVER_URL, ""), + )?; + println!(); + + Ok(url) +} + +fn save_default_server_url(url: &str) -> Result<()> { let path = user_defaults_path()?; if let Some(dir) = path.parent() { @@ -97,12 +137,32 @@ pub fn save_default_server_url(url: &str) -> Result<()> { Ok(()) } -pub fn home() -> Result { - env::var("ASCIINEMA_CONFIG_HOME") - .map(PathBuf::from) - .or(env::var("XDG_CONFIG_HOME").map(|home| Path::new(&home).join("asciinema"))) - .or(env::var("HOME").map(|home| Path::new(&home).join(".config").join("asciinema"))) - .map_err(|_| anyhow!("need $HOME or $XDG_CONFIG_HOME or $ASCIINEMA_CONFIG_HOME")) +fn read_install_id(path: &PathBuf) -> Result> { + match fs::read_to_string(path) { + Ok(s) => Ok(Some(s.trim().to_string())), + + Err(e) => { + if e.kind() == ErrorKind::NotFound { + Ok(None) + } else { + bail!(e) + } + } + } +} + +fn create_install_id() -> String { + Uuid::new_v4().to_string() +} + +fn save_install_id(path: &PathBuf, id: &str) -> Result<()> { + if let Some(dir) = path.parent() { + fs::create_dir_all(dir)?; + } + + fs::write(path, &id)?; + + Ok(()) } fn user_config_path() -> Result { @@ -112,3 +172,15 @@ fn user_config_path() -> Result { fn user_defaults_path() -> Result { Ok(home()?.join("defaults.toml")) } + +fn install_id_path() -> Result { + Ok(home()?.join(INSTALL_ID_FILENAME)) +} + +fn home() -> Result { + env::var("ASCIINEMA_CONFIG_HOME") + .map(PathBuf::from) + .or(env::var("XDG_CONFIG_HOME").map(|home| Path::new(&home).join("asciinema"))) + .or(env::var("HOME").map(|home| Path::new(&home).join(".config").join("asciinema"))) + .map_err(|_| anyhow!("need $HOME or $XDG_CONFIG_HOME or $ASCIINEMA_CONFIG_HOME")) +} diff --git a/src/main.rs b/src/main.rs index 6b0b466..85b996c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ mod format; mod locale; mod pty; mod recorder; -mod util; use crate::config::Config; use anyhow::Result; use clap::{Parser, Subcommand}; diff --git a/src/util.rs b/src/util.rs deleted file mode 100644 index 462aa55..0000000 --- a/src/util.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::config; -use anyhow::{anyhow, bail, Result}; -use reqwest::Url; -use std::{env, fs, io::ErrorKind, path::Path, path::PathBuf}; -use uuid::Uuid; - -const DEFAULT_SERVER_URL: &str = "https://asciinema.org"; -const INSTALL_ID_FILENAME: &str = "install-id"; - -pub fn get_install_id() -> Result { - if let Some(install_id) = read_install_id()? { - Ok(install_id) - } else if let Some(install_id) = read_legacy_install_id()? { - Ok(install_id) - } else { - create_install_id() - } -} - -fn read_install_id() -> Result> { - read_state_file(INSTALL_ID_FILENAME) -} - -fn create_install_id() -> Result { - let id = Uuid::new_v4().to_string(); - write_state_file(INSTALL_ID_FILENAME, &id)?; - - Ok(id) -} - -fn read_legacy_install_id() -> Result> { - let path = config::home()?.join(INSTALL_ID_FILENAME); - - match fs::read_to_string(path) { - Ok(s) => Ok(Some(s.trim().to_string())), - - Err(e) => { - if e.kind() == ErrorKind::NotFound { - Ok(None) - } else { - bail!(e) - } - } - } -} - -pub fn get_server_url(server_url: Option<&String>) -> Result { - match server_url { - Some(url) => Ok(Url::parse(&url)?), - - None => { - let url = Url::parse(&ask_for_server_url()?)?; - config::save_default_server_url(url.as_ref())?; - - Ok(url) - } - } -} - -fn ask_for_server_url() -> Result { - println!("No asciinema server configured for this CLI."); - let mut rl = rustyline::DefaultEditor::new()?; - let url = rl.readline_with_initial( - "Enter the server URL to use by default: ", - (DEFAULT_SERVER_URL, ""), - )?; - println!(); - - Ok(url) -} - -fn read_state_file(filename: &str) -> Result> { - let path = state_home()?.join(filename); - - match fs::read_to_string(path) { - Ok(s) => Ok(Some(s.trim().to_string())), - - Err(e) => { - if e.kind() == ErrorKind::NotFound { - Ok(None) - } else { - bail!(e) - } - } - } -} - -fn write_state_file(filename: &str, contents: &str) -> Result<()> { - let path = state_home()?.join(filename); - - if let Some(dir) = path.parent() { - fs::create_dir_all(dir)?; - } - - fs::write(path, contents)?; - - Ok(()) -} - -fn state_home() -> Result { - env::var("ASCIINEMA_STATE_HOME") - .map(PathBuf::from) - .or(env::var("XDG_STATE_HOME").map(|home| Path::new(&home).join("asciinema"))) - .or(env::var("HOME").map(|home| { - Path::new(&home) - .join(".local") - .join("state") - .join("asciinema") - })) - .map_err(|_| anyhow!("need $HOME or $XDG_STATE_HOME or $ASCIINEMA_STATE_HOME")) -}