random wallpaper rotator with tags and favorites
at main 5.9 kB view raw
1mod config; 2mod db; 3mod utils; 4 5use crate::config::*; 6use crate::db::*; 7use anyhow::{Context, Result}; 8use clap::{Parser, Subcommand}; 9use rusqlite::params; 10use std::fs::{self}; 11use std::path::{PathBuf}; 12 13#[derive(Parser, Debug)] 14#[clap( 15 name = "erm", 16 about = "background rotator using swaybg with tagging support" 17)] 18struct Args { 19 #[command(subcommand)] 20 command: Commands, 21} 22 23#[derive(Subcommand, Debug)] 24enum Commands { 25 /// Set a random background 26 Random { 27 /// Filter by tag 28 #[arg(short, long)] 29 tag: Option<String>, 30 31 /// Use only favorites 32 #[arg(short, long)] 33 favorite: bool, 34 35 /// Don't update the database (just set a new background) 36 #[arg(short, long)] 37 quick: bool, 38 }, 39 40 /// Refresh the image database 41 Refresh, 42 43 /// Tag management 44 Tag { 45 /// The path to the image 46 path: Option<String>, 47 48 /// Tag to add 49 #[arg(short, long)] 50 add: Option<String>, 51 52 /// Tag to remove 53 #[arg(short, long)] 54 remove: Option<String>, 55 56 /// List all tags for the image 57 #[arg(short, long)] 58 list: bool, 59 }, 60 61 /// Favorite management 62 Favorite { 63 /// The path to the image 64 path: Option<String>, 65 66 /// Add to favorites 67 #[arg(short, long)] 68 add: bool, 69 70 /// Remove from favorites 71 #[arg(short, long)] 72 remove: bool, 73 }, 74 75 /// List images 76 List { 77 /// Filter by tag 78 #[arg(short, long)] 79 tag: Option<String>, 80 81 /// Show only favorites 82 #[arg(short, long)] 83 favorite: bool, 84 }, 85 86 /// Current 87 Current, 88} 89 90fn main() -> Result<()> { 91 let args = Args::parse(); 92 93 let config_dir = get_config_dir()?; 94 let config_path = config_dir.join("config.toml"); 95 let db_path = config_dir.join("images.db"); 96 97 if !config_path.exists() { 98 create_default_config(&config_path)?; 99 } 100 101 let config = load_config(&config_path)?; 102 let mut conn = setup_database(&db_path)?; 103 104 create_indices(&conn)?; 105 106 match &args.command { 107 Commands::Random { 108 tag, 109 favorite, 110 quick, 111 } => { 112 let wallpaper = select_wallpaper(&conn, tag.as_deref(), *favorite)?; 113 114 if let Some(wallpaper_path) = wallpaper { 115 if !*quick { 116 update_usage(&conn, &wallpaper_path)?; 117 } 118 set_wallpaper(&conn, &wallpaper_path, &config)?; 119 } else { 120 let filter_desc = match (tag, favorite) { 121 (Some(t), true) => format!("favorite wallpapers with tag '{}'", t), 122 (Some(t), false) => format!("wallpapers with tag '{}'", t), 123 (None, true) => "favorite wallpapers".to_string(), 124 (None, false) => "wallpapers".to_string(), 125 }; 126 eprintln!( 127 "No {} found. Try running 'erm refresh' or adjusting filters.", 128 filter_desc 129 ); 130 } 131 } 132 Commands::Refresh => { 133 refresh_database(&mut conn, &config)?; 134 } 135 Commands::Current => { 136 let current = get_current_wallpaper(&conn)?; 137 println!("{}", current); 138 } 139 Commands::Tag { 140 path, 141 add, 142 remove, 143 list, 144 } => { 145 let path = match path { 146 Some(p) => resolve_path(p)?, 147 None => { 148 let current = get_current_wallpaper(&conn)?; 149 resolve_path(&current)? 150 } 151 }; 152 153 if *list { 154 list_tags(&conn, &path)?; 155 } else if let Some(tag) = add { 156 add_tag(&conn, &path, tag)?; 157 } else if let Some(tag) = remove { 158 remove_tag(&conn, &path, tag)?; 159 } else { 160 list_tags(&conn, &path)?; 161 } 162 } 163 Commands::Favorite { path, add, remove } => { 164 let path = match path { 165 Some(p) => resolve_path(p)?, 166 None => { 167 let current = get_current_wallpaper(&conn)?; 168 resolve_path(&current)? 169 } 170 }; 171 172 match (*add, *remove) { 173 (true, false) => add_favorite(&conn, &path)?, 174 (false, true) => remove_favorite(&conn, &path)?, 175 (true, true) => { 176 anyhow::bail!("Cannot add and remove favorite status simultaneously.") 177 } 178 (false, false) => { 179 match get_image_id(&conn, &path)? { 180 Some(id) => { 181 let is_fav: bool = conn.query_row( 182 "SELECT favorite FROM images WHERE id = ?", 183 params![id], 184 |row| row.get(0), 185 )?; 186 println!( 187 "Image '{}' is {}a favorite.", 188 path.display(), 189 if is_fav { "" } else { "not " } 190 ); 191 } 192 None => println!("Image not found in database: {}", path.display()), 193 } 194 } 195 } 196 } 197 Commands::List { tag, favorite } => { 198 list_images(&conn, tag.as_deref(), *favorite)?; 199 } 200 } 201 202 Ok(()) 203} 204 205fn resolve_path(path_str: &str) -> Result<PathBuf> { 206 let expanded = shellexpand::tilde(path_str).into_owned(); 207 fs::canonicalize(&expanded) 208 .with_context(|| format!("Failed to find or resolve path: {}", expanded)) 209}