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(¤t)?
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(¤t)?
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}