From e41d69a6b9c4a43d70d656e204e86b7a673638c5 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Wed, 20 Sep 2023 16:14:23 +0200 Subject: [PATCH 1/3] refactor(wallpaper): Create structs for paths This allows us to easily differentiate between file paths and directories. It also helps with reducing the complexity of the animated verifications. --- src/lib.rs | 1 + src/path.rs | 252 +++++++++++++++++++++++++++++++++++++++++++++++ src/setup.rs | 16 +-- src/wallpaper.rs | 149 ++++++++-------------------- 4 files changed, 303 insertions(+), 115 deletions(-) create mode 100644 src/path.rs diff --git a/src/lib.rs b/src/lib.rs index 9b87605..fd3c248 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,3 +2,4 @@ pub mod cli; pub mod configuration; pub mod setup; pub mod wallpaper; +pub mod path; diff --git a/src/path.rs b/src/path.rs new file mode 100644 index 0000000..57bc07f --- /dev/null +++ b/src/path.rs @@ -0,0 +1,252 @@ +use std::{ + fs::read_dir, + path::{Path, PathBuf}, +}; + +use crate::configuration::Settings; + +pub enum File { + Image(ImagePath), + Folder(AnimtaedFolder), +} + +impl File { + pub fn new(path: PathBuf) -> Option { + if !path.exists() { + None + } else if path.is_dir() { + Some(Self::Folder(AnimtaedFolder::new(path))) + } else { + Some(Self::Image(ImagePath::new(path)?)) + } + } + + pub(crate) fn is_animated(&mut self, settings: &Settings) -> bool { + match self { + Self::Image(image) => image.is_animated(settings), + Self::Folder(folder) => folder.path().exists(), + } + } + + pub(crate) fn get_animated_wallpaper_name(&self) -> String { + match self { + Self::Image(image) => image.get_animated_wallpaper_name(), + Self::Folder(folder) => folder.name().to_owned(), + } + } +} + +impl ToString for File { + fn to_string(&self) -> String { + match self { + Self::Image(image) => image.to_string(), + Self::Folder(folder) => folder.to_string(), + } + } +} + +impl From for File { + fn from(path: String) -> Self { + File::new(PathBuf::from(path)).unwrap() + } +} + +impl From for File { + fn from(path: PathBuf) -> Self { + File::new(path).unwrap() + } +} + +pub struct ImagePath { + path: PathBuf, + animated_info: Option, + animated_info_cached: bool, +} + +impl ImagePath { + pub fn new(path: PathBuf) -> Option { + if !path.exists() { + None + } else if path.is_dir() { + None + } else { + let instance = Self { + path, + animated_info: None, + animated_info_cached: false, + }; + Some(instance) + } + } + + /// Checks if the given path is an animated wallpaper, if the + /// value has been set before it will return the cached value, else + /// it will check if the path is an animated wallpaper and cache the result. + /// + /// An animated wallperper can be either a file or a folder. If it is a folder + /// it must be contained in the wallpaper directory. + /// If it is a file it must be contained in a folder that is contained in the wallpaper directory. + /// + /// Example: + /// wallpaper_dir + /// |--- folder1 + /// | |--- wallpaper1 + /// |-- wallpaper2 + /// + /// folder1 is an animated wallpaper + /// wallpaper1 is an animated wallpaper + /// wallpaper2 is not an animated wallpaper + pub fn is_animated(&mut self, settings: &Settings) -> bool { + if self.animated_info_cached { + return self.animated_info.is_some(); + } + + let is_animated = self.check_if_animated(settings); + if is_animated { + self.update_animated_info(); + } + self.animated_info_cached = true; + is_animated + } + + /// Helper function for is_animated. + fn check_if_animated(&mut self, settings: &Settings) -> bool { + if let Some(parent) = self.path.parent() { + if self.path.is_dir() { + if let Some(str) = parent.to_str() { + return str == settings.wallpaper_dir; + } + return false; + } else if let Some(parent) = parent.parent() { + if let Some(str) = parent.to_str() { + return str == settings.wallpaper_dir; + } + } else { + return false; + } + } + false + } + + fn update_animated_info(&mut self) { + self.animated_info = Some(AnimatedInfo::new(&self.path)); + } + + /// Gets the name of the folder that contains the given path. + /// If the folder name is not cached it will cache it. + /// + /// # Panics + /// + /// If the path is not an animated wallpaper it may panic. + pub fn get_animated_wallpaper_name(&self) -> String { + self.animated_info.as_ref().unwrap().animated_folder.clone() + } + + pub fn get_animated_number(&self) -> Option { + if let Some(info) = self.animated_info.as_ref() { + return Some(info.animated_number); + } + None + } + + pub fn path(&self) -> &Path { + &self.path + } + + pub fn get_sleep_time(&mut self, settings: &Settings) -> u64 { + if self.is_animated(settings) { + let number_of_wallpapers = + read_dir(self.path.parent().expect("failed to get parent directory")) + .expect("failed to open wallpaper directory") + .count(); + settings.sleep_time / number_of_wallpapers as u64 + } else { + settings.sleep_time + } + } +} + +impl ToString for ImagePath { + fn to_string(&self) -> String { + self.path.to_str().unwrap().to_owned() + } +} + +impl From for ImagePath { + fn from(path: String) -> Self { + //TODO: + Self::new(PathBuf::from(path)).unwrap() + } +} + +pub struct AnimtaedFolder { + path: PathBuf, + name: String, +} + +impl AnimtaedFolder { + fn new(path: PathBuf) -> Self { + let path_str = path.file_name().unwrap().to_str().unwrap().to_owned(); + Self { + path, + name: path_str, + } + } + + fn path(&self) -> &Path { + &self.path + } + + fn name(&self) -> &str { + &self.name + } +} + +impl ToString for AnimtaedFolder { + fn to_string(&self) -> String { + self.path.to_str().unwrap().to_owned() + } +} + +struct AnimatedInfo { + animated_folder: String, + animated_number: u32, +} + +impl AnimatedInfo { + fn new(path: &PathBuf) -> Self { + let animated_folder = Self::update_animated_folder(path); + let animated_number = Self::update_animated_number(path); + + Self { + animated_folder, + animated_number, + } + } + + fn update_animated_folder(path: &PathBuf) -> String { + path.parent() + .unwrap() + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_owned() + } + + fn update_animated_number(path: &PathBuf) -> u32 { + path.file_stem() + .expect("failed to get file name") + .to_str() + .expect("failed to convert file name to str") + .chars() + .rev() + .take_while(|c| c.is_ascii_digit()) + .collect::() + .chars() + .rev() + .collect::() + .parse::() + .expect("failed to parse animated number") + } +} diff --git a/src/setup.rs b/src/setup.rs index d2066ed..a6890ea 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -5,7 +5,7 @@ use daemonize::Daemonize; use crate::{ cli::Actions, configuration::Settings, - wallpaper::{get_next_wallpaper, get_sleep_time, update_wallpaper}, + wallpaper::{get_next_wallpaper, update_wallpaper}, }; pub fn run(settings: Settings, action: Actions) { @@ -13,12 +13,12 @@ pub fn run(settings: Settings, action: Actions) { Actions::Launch => run_daemon(settings), Actions::Toggle => { let wallpaper = get_next_wallpaper(&settings); - let path = wallpaper.to_str().unwrap(); - update_wallpaper(&settings, path); + let path = wallpaper.to_string(); + update_wallpaper(&settings, &path); } Actions::Get => { let wallpaper = get_next_wallpaper(&settings); - println!("{}", wallpaper.to_str().unwrap()); + println!("{}", wallpaper.to_string()); } } } @@ -41,10 +41,10 @@ fn run_daemon(settings: Settings) { fn launch_wallpaper_loop(settings: Settings) { loop { - let wallpaper = get_next_wallpaper(&settings); - let path = wallpaper.to_str().unwrap(); - update_wallpaper(&settings, path); - let sleep_time = get_sleep_time(&settings, &wallpaper); + let mut wallpaper = get_next_wallpaper(&settings); + let path = wallpaper.to_string(); + update_wallpaper(&settings, &path); + let sleep_time = wallpaper.get_sleep_time(&settings); thread::sleep(Duration::from_secs(sleep_time)); } } diff --git a/src/wallpaper.rs b/src/wallpaper.rs index e7782dd..1969f79 100644 --- a/src/wallpaper.rs +++ b/src/wallpaper.rs @@ -1,14 +1,16 @@ use rand::Rng; use std::{ fs::{read_dir, read_to_string}, - path::{Path, PathBuf}, process::Command, }; -use crate::configuration::Settings; +use crate::{ + configuration::Settings, + path::{File, ImagePath}, +}; /// Gets the current wallpaper. It assumes that the user is only using feh to set the wallpaper. -/// TODO: allow user to choose other wallpaper getter +/// TODO: allow user to choose other wallpaper setter /// /// /// The .fehbg file is a shell script that is run when the user logs in. It is used to set the @@ -23,7 +25,7 @@ use crate::configuration::Settings; /// /// Panics if the file $HOME/.fehbg does not exist. /// -pub fn get_current_wallpaper() -> PathBuf { +pub fn get_current_wallpaper() -> File { let feh_raw = read_to_string(format!( "{}/.fehbg", home::home_dir() @@ -32,6 +34,7 @@ pub fn get_current_wallpaper() -> PathBuf { .unwrap() )) .expect("failed to open .fehbg file"); + let wallpaper_path = feh_raw .lines() // We take the second line because the first line is the shebang @@ -45,7 +48,7 @@ pub fn get_current_wallpaper() -> PathBuf { .trim_matches('\'') .to_string(); - PathBuf::from(wallpaper_path) + File::from(wallpaper_path) } /// Gets a random wallpaper from the wallpaper directory. @@ -56,7 +59,7 @@ pub fn get_current_wallpaper() -> PathBuf { /// /// Panics if the wallpaper directory does not exist. /// -pub fn get_random_wallpaper(settings: &Settings) -> Option { +pub fn get_random_wallpaper(settings: &Settings) -> Option { let files = read_dir(settings.wallpaper_dir.clone()) .expect("failed to open wallpaper directory") .filter(|entry| { @@ -75,7 +78,8 @@ pub fn get_random_wallpaper(settings: &Settings) -> Option { } let random_number = rand::thread_rng().gen_range(0..files.len()); - Some(files.get(random_number).unwrap().as_ref().unwrap().path()) + let path = files.get(random_number).unwrap().as_ref().unwrap().path(); + File::new(path) } /// Gets a random wallpaper from the wallpaper directory. @@ -86,7 +90,7 @@ pub fn get_random_wallpaper(settings: &Settings) -> Option { /// /// Panics if the wallpaper directory does not exist. /// -pub fn get_random_wallpaper_file(settings: &Settings) -> Option { +pub fn get_random_wallpaper_file(settings: &Settings) -> Option { let files = read_dir(settings.wallpaper_dir.clone()) .expect("failed to open wallpaper directory") // Filter out folders @@ -101,61 +105,9 @@ pub fn get_random_wallpaper_file(settings: &Settings) -> Option { } let random_number = rand::thread_rng().gen_range(0..files.len()); - Some(files.get(random_number).unwrap().as_ref().unwrap().path()) -} - -/// Checks if the given path is an animated wallpaper.\ -/// -/// An animated wallperper can be either a file or a folder. If it is a folder -/// it must be contained in the wallpaper directory. -/// If it is a file it must be contained in a folder that is contained in the wallpaper directory. -/// -/// Example: -/// wallpaper_dir -/// |--- folder1 -/// | |--- wallpaper1 -/// |-- wallpaper2 -/// -/// folder1 is an animated wallpaper -/// wallpaper1 is an animated wallpaper -/// wallpaper2 is not an animated wallpaper -/// -pub fn is_animated_wallpaper(settings: &Settings, path: &Path) -> bool { - if let Some(parent) = path.parent() { - if path.is_dir() { - if let Some(str) = parent.to_str() { - return str == settings.wallpaper_dir; - } - return false; - } else if let Some(parent) = parent.parent() { - if let Some(str) = parent.to_str() { - return str == settings.wallpaper_dir; - } - } else { - return false; - } - } - false -} -/// Gets the name of the folder that contains the given path. -/// If the path is a folder it will return the name of the folder. -/// -/// # Panics -/// -/// If the path is not an animated wallpaper it may panic. -pub fn get_animated_wallpaper_name(path: &Path) -> String { - if path.is_dir() { - path.file_name().unwrap().to_str().unwrap().to_owned() - } else { - path.parent() - .unwrap() - .file_name() - .unwrap() - .to_str() - .unwrap() - .to_owned() - } + let path = files.get(random_number).unwrap().as_ref().unwrap().path(); + ImagePath::new(path) } /// Returns a path to the next animated wallpaper. @@ -169,58 +121,52 @@ pub fn get_animated_wallpaper_name(path: &Path) -> String { /// Panics if the wallpaper directory does not exist. /// Panics if the path is not a valid path. /// Panics if the name does not contain a number at the end. -pub fn get_next_animated_wallpaper(settings: &Settings, path: &Path) -> Option { - let name = get_animated_wallpaper_name(path); +pub fn get_next_animated_wallpaper(settings: &Settings, path: &File) -> Option { + let name = path.get_animated_wallpaper_name(); let next_index; - if path.is_dir() { - next_index = 1; - } else { - let max_index = read_dir(format!("{}/{name}", settings.wallpaper_dir)) - .expect("failed to open wallpaper directory") - .count(); - - // Get the last numbers of the name - let last_numbers = path - .file_stem() - .expect("failed to get file name") - .to_str() - .expect("failed to convert file name to str") - .chars() - .rev() - .take_while(|c| c.is_ascii_digit()) - .collect::() - .chars() - .rev() - .collect::(); - - next_index = last_numbers.parse::().unwrap() + 1; - if next_index > max_index as u32 { - return None; + match path { + File::Image(img) => { + let max_index = read_dir(format!("{}/{name}", settings.wallpaper_dir)) + .expect("failed to open wallpaper directory") + .count(); + + // Get the last numbers of the name + let last_numbers = img.get_animated_number().unwrap(); + next_index = last_numbers + 1; + if next_index > max_index as u32 { + return None; + } + } + File::Folder(_) => { + next_index = 1; } } //TODO: Add support for other file formats - Some(PathBuf::from(format!( + Some(ImagePath::from(format!( "{}/{name}/{name}{}.png", settings.wallpaper_dir, next_index ))) } /// Gets the next wallpaper. -pub fn get_next_wallpaper(settings: &Settings) -> PathBuf { - let current_wallpaper = get_current_wallpaper(); - let new_wallpaper = get_random_wallpaper(settings) +pub fn get_next_wallpaper(settings: &Settings) -> ImagePath { + let mut current_wallpaper = get_current_wallpaper(); + let mut new_wallpaper = get_random_wallpaper(settings) .expect("failed to get random wallpaper, not enough wallpapers in the wallpaper directory"); - if is_animated_wallpaper(settings, ¤t_wallpaper) { + if current_wallpaper.is_animated(settings) { update_animated(settings, ¤t_wallpaper) - } else if is_animated_wallpaper(settings, &new_wallpaper) { + } else if new_wallpaper.is_animated(settings) { update_animated(settings, &new_wallpaper) } else { - new_wallpaper + match new_wallpaper { + File::Image(img) => img, + File::Folder(_) => unreachable!(), + } } } -pub fn update_animated(settings: &Settings, path: &Path) -> PathBuf { +pub fn update_animated(settings: &Settings, path: &File) -> ImagePath { let next_wallpaper = get_next_animated_wallpaper(settings, path); if let Some(next_wallpaper) = next_wallpaper { next_wallpaper @@ -256,14 +202,3 @@ pub fn update_wallpaper(settings: &Settings, path: &str) { .expect("failed to call betterlockscreen"); } } - -pub fn get_sleep_time(settings: &Settings, path: &Path) -> u64 { - if is_animated_wallpaper(settings, path) { - let number_of_wallpapers = read_dir(path.parent().expect("failed to get parent directory")) - .expect("failed to open wallpaper directory") - .count(); - settings.sleep_time / number_of_wallpapers as u64 - } else { - settings.sleep_time - } -} From c0728b3f30dcc26f207150d9f0667ae91871c2fb Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Thu, 5 Oct 2023 11:49:36 +0200 Subject: [PATCH 2/3] docs: Add some comments to the new path file --- src/path.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/path.rs b/src/path.rs index 57bc07f..3494cd6 100644 --- a/src/path.rs +++ b/src/path.rs @@ -5,6 +5,7 @@ use std::{ use crate::configuration::Settings; +/// A wrapper for a path that can be either a file or a folder. pub enum File { Image(ImagePath), Folder(AnimtaedFolder), @@ -57,6 +58,8 @@ impl From for File { } } +/// A wrapper for a path that is a file. +/// It can be either an animated wallpaper or a static wallpaper. pub struct ImagePath { path: PathBuf, animated_info: Option, @@ -179,6 +182,7 @@ impl From for ImagePath { } } +/// A wrapper for a path that is a folder. pub struct AnimtaedFolder { path: PathBuf, name: String, From 8e0496c2991ab239e9b4afafa0008d7d21a15a47 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Thu, 5 Oct 2023 11:54:14 +0200 Subject: [PATCH 3/3] chore: Format and code smell --- src/lib.rs | 2 +- src/path.rs | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fd3c248..11f4994 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ pub mod cli; pub mod configuration; +pub mod path; pub mod setup; pub mod wallpaper; -pub mod path; diff --git a/src/path.rs b/src/path.rs index 3494cd6..6200502 100644 --- a/src/path.rs +++ b/src/path.rs @@ -68,9 +68,7 @@ pub struct ImagePath { impl ImagePath { pub fn new(path: PathBuf) -> Option { - if !path.exists() { - None - } else if path.is_dir() { + if !path.exists() || path.is_dir() { None } else { let instance = Self { @@ -218,7 +216,7 @@ struct AnimatedInfo { } impl AnimatedInfo { - fn new(path: &PathBuf) -> Self { + fn new(path: &Path) -> Self { let animated_folder = Self::update_animated_folder(path); let animated_number = Self::update_animated_number(path); @@ -228,7 +226,7 @@ impl AnimatedInfo { } } - fn update_animated_folder(path: &PathBuf) -> String { + fn update_animated_folder(path: &Path) -> String { path.parent() .unwrap() .file_name() @@ -238,7 +236,7 @@ impl AnimatedInfo { .to_owned() } - fn update_animated_number(path: &PathBuf) -> u32 { + fn update_animated_number(path: &Path) -> u32 { path.file_stem() .expect("failed to get file name") .to_str()