From 6957c635827364fe493ae2724f7715412111548f Mon Sep 17 00:00:00 2001 From: Sunny Date: Mon, 6 Sep 2021 19:01:04 +0530 Subject: [PATCH] config: Check if a theme repo is installed (#20) Updates Repo::compare() to check if themes are already installed and update the theme directory accordingly in repo config. Adds Repo::installed_themes() which returns a list of all the existing themes by their names. It looks for "themes" directory under a given config directory path for installed themes. The symlinks are traversed to check the destination to be a theme directory. The "current" theme directory is ignored. --- Cargo.lock | 1 + Cargo.toml | 1 + src/models/config.rs | 108 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 108 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b00155..920d51f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -474,6 +474,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "tempfile", "toml", "xdg", ] diff --git a/Cargo.toml b/Cargo.toml index 44778ea..e8f3e14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,3 +26,4 @@ pretty_env_logger = "0.4.0" colored = "2.0.0" git2 = "0.13.17" edit-distance = "2.1.0" +tempfile = "3.2.0" diff --git a/src/models/config.rs b/src/models/config.rs index 9ba9853..4996d1e 100644 --- a/src/models/config.rs +++ b/src/models/config.rs @@ -8,6 +8,8 @@ use std::io::Write; use std::path::Path; use xdg::BaseDirectories; +const THEMES_DIR: &str = "themes"; + /// Contains a vector of all global repositories. #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct Config { @@ -134,8 +136,20 @@ impl Repo { pub fn compare(&mut self, theme_wrap: TempThemes) -> Result<&Repo> { let themes = theme_wrap.theme; trace!("Comparing themes"); - //iterate over all themes, and update/add if needed - for tema in themes { + + // Get a list of existing themes. + let path = BaseDirectories::with_prefix("leftwm")?; + let base_config_path = String::from(path.get_config_home().to_str().unwrap()); + let existing_themes = Repo::installed_themes(base_config_path).unwrap(); + let themes_dir = path.get_config_home().join(THEMES_DIR); + + // Iterate over all the themes, and update/add if needed. + for mut tema in themes { + // Check if the theme is already installed and update the theme + // directory attribute. + if existing_themes.contains(&tema.name.clone()) { + tema.directory = Some(themes_dir.join(tema.name.clone())); + } Repo::update_or_append(self, &tema); } Ok(self) @@ -157,6 +171,7 @@ impl Repo { target_theme.version = theme.version.clone(); target_theme.leftwm_versions = theme.leftwm_versions.clone(); target_theme.dependencies = theme.dependencies.clone(); + target_theme.directory = theme.directory.clone(); } // o/w insert a new leaf at the end None => { @@ -164,4 +179,93 @@ impl Repo { } } } + + // Returns a list of all the installed theme names under a given config + // path. + fn installed_themes(config_path: String) -> Result> { + let mut result: Vec = Vec::new(); + + let theme_path = Path::new(&config_path).join(THEMES_DIR); + + // Return empty result if the themes directory is not present. + if !theme_path.exists() { + return Ok(result); + } + + // Read the themes directory, iterate through the entries, determine + // which of them are theme directories and add them into the result. + let paths = fs::read_dir(theme_path).unwrap(); + for path in paths { + let p = path.unwrap(); + // NOTE: For symlinks, metadata() traverses any symlinks and queries + // the metadata information from the destination. + let metadata = fs::metadata(p.path())?; + let file_type = metadata.file_type(); + + // Only process directories. + if !file_type.is_dir() { + continue; + } + + // Ignore the "current" directory for installed theme list. + let current_dir = String::from("current"); + let target_path = p.path(); + if target_path + .file_name() + .unwrap() + .to_str() + .unwrap() + .eq(¤t_dir) + { + continue; + } + + // Extract only the theme name for the result. + let theme_name = target_path.file_name().unwrap(); + result.push(String::from(theme_name.to_str().unwrap())); + } + + Ok(result) + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::os::unix::fs as unix_fs; + + #[test] + fn test_installed_themes() { + // Create a temporary directory as the config path and create the + // directory layout within it for themes. + let tmpdir = tempfile::tempdir().unwrap(); + let themes_dir = tmpdir.path().join(THEMES_DIR); + let theme1 = themes_dir.join("test-theme1"); + let theme2 = themes_dir.join("test-theme2"); + let unrelated_file = themes_dir.join("some-file"); + assert!(fs::create_dir_all(&theme1).is_ok()); + assert!(fs::create_dir_all(&theme2).is_ok()); + assert!(File::create(unrelated_file).is_ok()); + + // Create current theme as a symlink to an existing theme. + let current = themes_dir.join("current"); + let src = theme2.to_str().unwrap(); + let dst = current.to_str().unwrap(); + assert!(unix_fs::symlink(src, dst).is_ok()); + + let config_dir = tmpdir.path().to_str().unwrap(); + let result = Repo::installed_themes(String::from(config_dir)); + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + vec!["test-theme2".to_string(), "test-theme1".to_string(),], + ) + } + + #[test] + fn test_installed_themes_no_themes_dir() { + let tmpdir = tempfile::tempdir().unwrap(); + let config_dir = tmpdir.path().to_str().unwrap(); + assert!(Repo::installed_themes(String::from(config_dir)).is_ok()); + } }