From d44041040d755306c39d6de8da5b42d7ded6808c Mon Sep 17 00:00:00 2001 From: fxqnlr Date: Wed, 25 Sep 2024 15:13:34 +0200 Subject: added notifications and improved stuff --- src/backup.rs | 37 ++++++++++--------- src/cli.rs | 7 +--- src/config.rs | 28 +++++++++------ src/error.rs | 17 +++++++++ src/main.rs | 47 +++++++++++++++++++----- src/packages.rs | 13 +++---- src/pathinfo.rs | 110 ++++++++++++++++++++++++++++---------------------------- 7 files changed, 153 insertions(+), 106 deletions(-) (limited to 'src') diff --git a/src/backup.rs b/src/backup.rs index 3d07ace..b468917 100644 --- a/src/backup.rs +++ b/src/backup.rs @@ -2,18 +2,18 @@ use std::{ fs::{create_dir_all, File}, io::{Read, Write}, path::PathBuf, - time::{SystemTime, UNIX_EPOCH}, + time::SystemTime, }; use serde::{Deserialize, Serialize}; -use tracing::info; +use tracing::{debug, info}; use uuid::Uuid; use crate::{ config::Config, error::{Error, Result}, - packages::{Manager, PackageList}, - pathinfo::PathInfo, + packages::PackageList, + pathinfo::PathInfo, send_notification, }; pub type Id = String; @@ -28,16 +28,20 @@ pub struct Backup { } impl Backup { - pub fn create(config: &Config, manager: Option) -> Result { + pub fn create(config: &Config) -> Result { let mut files: Vec = Vec::new(); for dir in &config.directories { files.push(PathInfo::from_path(config, dir)?); } Ok(Self { - // TODO: UUID not really needed, maybe a shorter hash id: Uuid::new_v4().to_string(), - timestamp: Self::get_timestamp(), - packages: Manager::get_manager(manager)?.get_installed()?, + timestamp: SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH)? + .as_secs(), + packages: config + .package_manager + .to_package_manager() + .get_installed()?, files, device: config.device.clone(), }) @@ -58,12 +62,14 @@ impl Backup { path.save(&backup_root)?; } + send_notification("Backup created" , "", notify_rust::Urgency::Normal)?; + Ok(()) } pub fn get_last(config: &Config) -> Result> { let backup_index_root = format!("{}/index.json", config.root); - info!(?backup_index_root, "backup index location:"); + debug!(?backup_index_root, "backup index location:"); let list: Vec = match Self::get_json_content(&backup_index_root) { Ok(list) => list, Err(err) => { @@ -74,8 +80,6 @@ impl Backup { } }; - info!(?list, "backup index:"); - Ok(Some(Self::from_index( config, &list.last().ok_or(Error::BackupNotFound)?.id, @@ -122,6 +126,8 @@ impl Backup { path.restore(config, &backup_root)?; } + send_notification("Backup restored" , "", notify_rust::Urgency::Normal)?; + Ok(()) } @@ -131,13 +137,6 @@ impl Backup { file.read_to_string(&mut content)?; Ok(serde_json::from_str(&content)?) } - - fn get_timestamp() -> u64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs() - } } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -165,7 +164,7 @@ impl IndexEntry { f.write_all(&serde_json::to_vec(&loc)?)?; } else { - create_dir_all(&config.root).unwrap(); + create_dir_all(&config.root)?; let mut f = File::create(backup_index_root)?; f.write_all(&serde_json::to_vec(&vec![self])?)?; }; diff --git a/src/cli.rs b/src/cli.rs index 1b62a84..588b6b0 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2,8 +2,6 @@ use std::path::PathBuf; use clap::{Parser, Subcommand}; -use crate::packages::Manager; - #[derive(Parser)] pub struct Cli { #[arg(short, long)] @@ -16,10 +14,7 @@ pub struct Cli { #[derive(Subcommand)] pub enum Subcommands { GenerateConfig, - Save { - #[arg(short, long)] - package_manager: Option, - }, + Save, Restore { #[arg(short, long)] package_install: bool diff --git a/src/config.rs b/src/config.rs index 46d2204..848be19 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,6 +4,8 @@ use config::{File, Map}; use serde::{Deserialize, Serialize}; use tracing::{debug, trace}; +use crate::{error::{Error, Result}, packages::Manager}; + #[derive(Debug, Serialize, Deserialize)] #[serde(default)] pub struct Config { @@ -11,6 +13,7 @@ pub struct Config { pub directories: Vec, pub custom_directories: Map, pub device: String, + pub package_manager: Manager, } impl Default for Config { @@ -22,17 +25,18 @@ impl Default for Config { device: gethostname::gethostname() .into_string() .expect("invalid hostname string"), + package_manager: Manager::from_sys().expect("couldn't get package manager"), } } } impl Config { - pub fn load(path: Option) -> core::result::Result { + pub fn load(path: Option) -> Result { debug!("load config"); let source = if let Some(source) = path { source } else { - Self::get_location() + Self::get_location()? }; let config = config::Config::builder() @@ -40,14 +44,14 @@ impl Config { .add_source(config::Environment::with_prefix("FXBAUP").separator("_")) .build()?; - let cfg = config.try_deserialize(); + let cfg = config.try_deserialize()?; trace!(?cfg, "loaded config"); - cfg + Ok(cfg) } - pub fn generate() -> crate::error::Result<()> { - let loc = Self::get_location(); + pub fn generate() -> Result<()> { + let loc = Self::get_location()?; create_dir_all(loc.parent().unwrap())?; let mut f = std::fs::File::create(loc)?; f.write_all(toml::to_string(&Self::default())?.as_bytes())?; @@ -55,10 +59,12 @@ impl Config { Ok(()) } - fn get_location() -> PathBuf { - let mut conf_dir = dirs::config_local_dir().unwrap(); - conf_dir.push("arbs"); - conf_dir.push("config.toml"); - conf_dir + fn get_location() -> Result { + let Some(mut config_dir) = dirs::config_dir() else { + return Err(Error::NoSysDir); + }; + config_dir.push(env!("CARGO_PKG_NAME")); + config_dir.push("config.toml"); + Ok(config_dir) } } diff --git a/src/error.rs b/src/error.rs index cb57e99..8270b45 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,5 @@ +use tracing::error; + pub type Result = std::result::Result; #[derive(Debug, thiserror::Error)] @@ -17,6 +19,10 @@ pub enum Error { #[error("Requested backup not found")] BackupNotFound, + // Utils + #[error("System directory not found")] + NoSysDir, + // Packages #[error("Unknown Package Manger Output")] UnknownOutput, @@ -25,12 +31,23 @@ pub enum Error { Unsupported, // Deps + #[error(transparent)] + Config(#[from] config::ConfigError), + #[error(transparent)] SerdeJson(#[from] serde_json::Error), #[error(transparent)] TomlSerialize(#[from] toml::ser::Error), + #[cfg(feature = "notifications")] + #[error(transparent)] + Notify(#[from] notify_rust::error::Error), + + // Rust #[error(transparent)] Io(#[from] std::io::Error), + + #[error(transparent)] + SystemTime(#[from] std::time::SystemTimeError), } diff --git a/src/main.rs b/src/main.rs index 487d095..ab23ab7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,8 +2,9 @@ use backup::Backup; use clap::Parser; use cli::Subcommands; use config::Config; -use error::Error; -use tracing::{debug, level_filters::LevelFilter}; +use error::{Error, Result}; +use notify_rust::Urgency; +use tracing::{debug, error, level_filters::LevelFilter}; use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; mod backup; @@ -13,9 +14,7 @@ mod error; mod packages; mod pathinfo; -fn main() -> color_eyre::Result<()> { - color_eyre::install()?; - +fn main() -> Result<()> { let file_appender = tracing_appender::rolling::never("./", "arbs.log"); let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); @@ -35,15 +34,27 @@ fn main() -> color_eyre::Result<()> { ) .init(); debug!("logging initialized"); + + match run_cli() { + Ok(()) => { println!("OK") ; Ok(())}, + Err(err) => { + error!(?err); + error!("{:?}", std::error::Error::source(&err)); + send_notification("Backup Error", &err.to_string(), Urgency::Critical)?; + Err(err) + } + } +} +fn run_cli() -> Result<()> { let cli = cli::Cli::parse(); let config = Config::load(cli.config)?; match cli.subcommand { Subcommands::GenerateConfig => Config::generate()?, - Subcommands::Save { package_manager } => { - let backup = Backup::create(&config, package_manager)?; + Subcommands::Save => { + let backup = Backup::create(&config)?; backup.save(&config)?; } Subcommands::Restore { package_install } => { @@ -56,7 +67,27 @@ fn main() -> color_eyre::Result<()> { } last_backup.restore(&config)?; - } + }, }; Ok(()) } + +fn send_notification(summary: &str, body: &str, urgency: Urgency) -> Result<()> { + #[cfg(feature = "notifications")] + { + let Some(mut icon) = dirs::data_dir() else { + return Err(Error::NoSysDir); + }; + icon.push(env!("CARGO_PKG_NAME")); + icon.push("icon.png"); + + notify_rust::Notification::new() + .summary(summary) + .body(body) + .icon(&icon.to_string_lossy()) + .timeout(0) + .urgency(urgency) + .show()?; + } + Ok(()) +} diff --git a/src/packages.rs b/src/packages.rs index de818f4..41b9478 100644 --- a/src/packages.rs +++ b/src/packages.rs @@ -37,15 +37,12 @@ pub enum Manager { } impl Manager { - pub fn get_manager(manager: Option) -> Result> { + pub fn from_sys() -> Result { #[cfg(not(target_os = "linux"))] return Err(Error::Unsupported); #[cfg(target_os = "linux")] { - if let Some(man) = manager { - return Ok(man.to_package_manager()); - } let mut os_release = File::open("/etc/os-release")?; let mut content = String::new(); os_release.read_to_string(&mut content)?; @@ -63,15 +60,15 @@ impl Manager { } } - fn from_str(value: &str) -> Result> { + fn from_str(value: &str) -> Result { Ok(match value { - "arch" => Box::new(Pacman), - "gentoo" => Box::new(Portage), + "arch" => Self::Pacman, + "gentoo" => Self::Portage, _ => return Err(Error::Unsupported), }) } - fn to_package_manager(&self) -> Box { + pub fn to_package_manager(&self) -> Box { match self { Self::Pacman => Box::new(Pacman), Self::Portage => Box::new(Portage), diff --git a/src/pathinfo.rs b/src/pathinfo.rs index 1231ff8..009f46a 100644 --- a/src/pathinfo.rs +++ b/src/pathinfo.rs @@ -6,7 +6,7 @@ use std::{ }; use serde::{Deserialize, Serialize}; -use tracing::{debug, info}; +use tracing::{debug, info, trace}; use crate::{ backup::{Backup, Id}, @@ -35,7 +35,7 @@ impl PathInfo { rel_location: &str, location_root: &LocationRoot, ) -> Result { - info!("Handling {rel_location}"); + trace!("Handling {rel_location}"); let path = Self::get_abs_path(&location_root.to_string(), rel_location); Ok(if path.is_dir() { let mut last_modified = Some(String::new()); @@ -83,7 +83,7 @@ impl PathInfo { ) -> Result { let last_modified = Self::compare_to_last_modified(config, location_root, rel_location)?; - info!("From file {rel_location} ({last_modified:?})"); + debug!("From file {rel_location} ({last_modified:?})"); Ok(Self { rel_location: rel_location.to_string(), @@ -301,14 +301,15 @@ mod tests { .custom_directories .insert("test".to_string(), "/usr/local/test".to_string()); - let mut values_ok: Vec<(&str, LocationRoot)> = Vec::new(); - values_ok.push(("u:test", LocationRoot::User)); - values_ok.push(("s:", LocationRoot::SystemConfig)); - values_ok.push(("r:", LocationRoot::Root)); - values_ok.push(( - "c:test", - LocationRoot::Custom("/usr/local/test".to_string()), - )); + let values_ok = vec![ + ("u:test", LocationRoot::User), + ("s:", LocationRoot::SystemConfig), + ("r:", LocationRoot::Root), + ( + "c:test", + LocationRoot::Custom("/usr/local/test".to_string()), + ), + ]; for value in values_ok { println!("Testing {value:?}"); @@ -316,18 +317,19 @@ mod tests { println!("\x1B[FTesting {value:?} ✓"); } - let mut values_err: Vec<(&str, String)> = Vec::new(); - values_err.push(( - "c:rest", - Error::CustomDirectory("rest".to_string()).to_string(), - )); - values_err.push(("t:test/", Error::InvalidIndex("t".to_string()).to_string())); - values_err.push(( - "test:test/usr", - Error::InvalidIndex("test".to_string()).to_string(), - )); - values_err.push(("/usr/local/test", Error::NoIndex.to_string())); - values_err.push(("c/usr/local/test", Error::NoIndex.to_string())); + let values_err = vec![ + ( + "c:rest", + Error::CustomDirectory("rest".to_string()).to_string(), + ), + ("t:test/", Error::InvalidIndex("t".to_string()).to_string()), + ( + "test:test/usr", + Error::InvalidIndex("test".to_string()).to_string(), + ), + ("/usr/local/test", Error::NoIndex.to_string()), + ("c/usr/local/test", Error::NoIndex.to_string()), + ]; for value in values_err { println!("Testing {value:?}"); @@ -351,47 +353,47 @@ mod tests { .custom_directories .insert("test".to_string(), "/usr/local/test".to_string()); - let mut values_ok: Vec<(&str, (String, LocationRoot))> = Vec::new(); - values_ok.push(( - "~/.config/nvim", - (".config/nvim".to_string(), LocationRoot::User), - )); - values_ok.push(( - "u:test/.config/nvim", - (".config/nvim".to_string(), LocationRoot::User), - )); - values_ok.push(( - "r:/.config/nvim", - (".config/nvim".to_string(), LocationRoot::Root), - )); - values_ok.push(( - "r:/.config/nvim", - (".config/nvim".to_string(), LocationRoot::Root), - )); - values_ok.push(( - "s:/.config/nvim", - (".config/nvim".to_string(), LocationRoot::SystemConfig), - )); - values_ok.push(( - "c:test/.config/nvim", + let values_ok = vec![ ( - ".config/nvim".to_string(), - LocationRoot::Custom("/usr/local/test".to_string()), + "~/.config/nvim", + (".config/nvim".to_string(), LocationRoot::User), + ), + ( + "u:test/.config/nvim", + (".config/nvim".to_string(), LocationRoot::User), ), - )); + ( + "r:/.config/nvim", + (".config/nvim".to_string(), LocationRoot::Root), + ), + ( + "r:/.config/nvim", + (".config/nvim".to_string(), LocationRoot::Root), + ), + ( + "s:/.config/nvim", + (".config/nvim".to_string(), LocationRoot::SystemConfig), + ), + ( + "c:test/.config/nvim", + ( + ".config/nvim".to_string(), + LocationRoot::Custom("/usr/local/test".to_string()), + ), + ), + ]; for value in values_ok { print!("Testing {value:?}"); - assert_eq!(PathInfo::parse_location(&value.0, &config)?, value.1); + assert_eq!(PathInfo::parse_location(value.0, &config)?, value.1); println!("\x1B[FTesting {value:?} ✓"); } Ok(()) } #[test] - fn compare_to_last_modified() -> color_eyre::Result<()> { - let mut config = Config::default(); - config.root = "./backup-test".to_string(); + fn compare_to_last_modified() -> Result<()> { + let mut config = Config { root: "./backup-test".to_string(), ..Default::default() }; config .directories .push("u:fx/code/proj/arbs/backup-test-dir".to_string()); @@ -404,7 +406,7 @@ mod tests { let mut f = File::create("./backup-test-dir/nothing.txt")?; f.write_all("unmodified".as_bytes())?; - let backup = Backup::create(&config, None)?; + let backup = Backup::create(&config)?; backup.save(&config)?; let mut f = File::create("./backup-test-dir/size.txt")?; -- cgit v1.2.3