From 151e8032c9630e398e115bea6dfe0ba9cb0ff7a3 Mon Sep 17 00:00:00 2001 From: Marcin Kulik Date: Mon, 27 May 2024 22:42:38 +0200 Subject: [PATCH] Support recording to a directory, with auto-generated filename --- Cargo.lock | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ src/cli.rs | 7 +++- src/cmd/rec.rs | 54 +++++++++++++++++++++++++---- src/config.rs | 6 ++++ 5 files changed, 154 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6f1c81..31fa919 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.11" @@ -87,11 +102,13 @@ dependencies = [ "anyhow", "avt", "axum", + "chrono", "clap", "clap_complete", "clap_mangen", "config", "futures-util", + "hostname", "mime_guess", "nix", "reqwest", @@ -292,6 +309,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.0", +] + [[package]] name = "clap" version = "4.4.18" @@ -816,6 +847,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "hostname" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" +dependencies = [ + "cfg-if", + "libc", + "windows", +] + [[package]] name = "html5ever" version = "0.26.0" @@ -971,6 +1013,29 @@ dependencies = [ "tokio", ] +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.5.0" @@ -1198,6 +1263,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -2750,6 +2824,25 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index d1ac5d4..6a9c852 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,8 @@ url = "2.5.0" tokio-tungstenite = { version = "0.21.0", features = ["rustls-tls-webpki-roots"] } sha2 = "0.10.8" tokio-util = "0.7.10" +chrono = "0.4.38" +hostname = "0.4.0" [build-dependencies] clap = { version = "4.4.7", features = ["derive"] } diff --git a/src/cli.rs b/src/cli.rs index c42bfe0..74a4e44 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -48,7 +48,8 @@ pub enum Commands { #[derive(Debug, Args)] pub struct Record { - pub filename: String, + /// Output path - either a file or a directory path + pub path: String, /// Enable input recording #[arg(long, short = 'I', alias = "stdin")] @@ -73,6 +74,10 @@ pub struct Record { #[arg(short, long)] pub command: Option, + /// Filename template, used when recording to a directory + #[arg(long, value_name = "TEMPLATE")] + pub filename: Option, + /// List of env vars to save [default: TERM,SHELL] #[arg(long)] pub env: Option, diff --git a/src/cmd/rec.rs b/src/cmd/rec.rs index a8c8497..46119c3 100644 --- a/src/cmd/rec.rs +++ b/src/cmd/rec.rs @@ -13,12 +13,14 @@ use cli::Format; use std::collections::{HashMap, HashSet}; use std::env; use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; +use std::process; impl Command for cli::Record { - fn run(self, config: &Config) -> Result<()> { + fn run(mut self, config: &Config) -> Result<()> { locale::check_utf8_locale()?; + self.ensure_filename(config)?; let format = self.get_format(); let (append, overwrite) = self.get_mode()?; let file = self.open_file(append, overwrite)?; @@ -30,7 +32,7 @@ impl Command for cli::Record { let exec_command = super::build_exec_command(command.as_ref().cloned()); let exec_extra_env = super::build_exec_extra_env(&[]); - logger::info!("Recording session started, writing to {}", self.filename); + logger::info!("Recording session started, writing to {}", self.path); if command.is_none() { logger::info!("Press or type 'exit' to end"); @@ -58,10 +60,48 @@ impl Command for cli::Record { } impl cli::Record { + fn ensure_filename(&mut self, config: &Config) -> Result<()> { + let mut path = PathBuf::from(&self.path); + + if path.exists() && fs::metadata(&path)?.is_dir() { + let mut tpl = self.filename.clone().unwrap_or(config.cmd_rec_filename()); + + if tpl.contains("{pid}") { + let pid = process::id().to_string(); + tpl = tpl.replace("{pid}", &pid); + } + + if tpl.contains("{user}") { + let user = env::var("USER").ok().unwrap_or("unknown".to_owned()); + tpl = tpl.replace("{user}", &user); + } + + if tpl.contains("{hostname}") { + let hostname = hostname::get() + .ok() + .and_then(|h| h.into_string().ok()) + .unwrap_or("unknown".to_owned()); + + tpl = tpl.replace("{hostname}", &hostname); + } + + let filename = chrono::Local::now().format(&tpl).to_string(); + path.push(Path::new(&filename)); + + if let Some(dir) = path.parent() { + fs::create_dir_all(dir)?; + } + + self.path = path.to_string_lossy().to_string(); + } + + Ok(()) + } + fn get_mode(&self) -> Result<(bool, bool)> { let mut overwrite = self.overwrite; let mut append = self.append; - let path = Path::new(&self.filename); + let path = Path::new(&self.path); if path.exists() { let metadata = fs::metadata(path)?; @@ -88,7 +128,7 @@ impl cli::Record { .create(overwrite) .create_new(!overwrite && !append) .truncate(overwrite) - .open(&self.filename)?; + .open(&self.path)?; Ok(file) } @@ -97,7 +137,7 @@ impl cli::Record { self.format.unwrap_or_else(|| { if self.raw { Format::Raw - } else if self.filename.to_lowercase().ends_with(".txt") { + } else if self.path.to_lowercase().ends_with(".txt") { Format::Txt } else { Format::Asciicast @@ -107,7 +147,7 @@ impl cli::Record { fn get_time_offset(&self, append: bool, format: Format) -> Result { if append && format == Format::Asciicast { - asciicast::get_duration(&self.filename) + asciicast::get_duration(&self.path) } else { Ok(0) } diff --git a/src/config.rs b/src/config.rs index 898c57c..83b3748 100644 --- a/src/config.rs +++ b/src/config.rs @@ -38,6 +38,7 @@ pub struct Cmd { #[allow(unused)] pub struct Rec { pub command: Option, + pub filename: String, pub input: bool, pub env: Option, pub idle_time_limit: Option, @@ -78,6 +79,7 @@ impl Config { let mut config = config::Config::builder() .set_default("server.url", None::>)? .set_default("cmd.rec.input", false)? + .set_default("cmd.rec.filename", "%Y-%m-%d-%H-%M-%S-{pid}.cast")? .set_default("cmd.play.speed", None::>)? .set_default("cmd.stream.input", false)? .set_default("notifications.enabled", true)? @@ -134,6 +136,10 @@ impl Config { self.cmd.rec.command.as_ref().cloned() } + pub fn cmd_rec_filename(&self) -> String { + self.cmd.rec.filename.clone() + } + pub fn cmd_rec_input(&self) -> bool { self.cmd.rec.input }