From 421b0e27d777c418a26016bd8065715823504637 Mon Sep 17 00:00:00 2001 From: Bruce Leidl Date: Mon, 3 Jun 2024 12:05:58 -0400 Subject: [PATCH] Various fixes to desktop_sync including Flatpak support --- citadel-tool/src/sync/desktop_sync.rs | 20 +++--- citadel-tool/src/sync/icons.rs | 94 ++++++++++++++++++++------- citadel-tool/src/sync/mod.rs | 21 ++++-- citadel-tool/src/sync/notes.md | 30 --------- citadel-tool/src/sync/parser.rs | 6 +- libcitadel/src/util.rs | 40 ++++++++++++ systemd/citadel-current-watcher.path | 1 - systemd/citadel-desktop-watcher.path | 2 + 8 files changed, 142 insertions(+), 72 deletions(-) delete mode 100644 citadel-tool/src/sync/notes.md diff --git a/citadel-tool/src/sync/desktop_sync.rs b/citadel-tool/src/sync/desktop_sync.rs index f63f176..e7b889b 100644 --- a/citadel-tool/src/sync/desktop_sync.rs +++ b/citadel-tool/src/sync/desktop_sync.rs @@ -8,6 +8,7 @@ use crate::sync::parser::DesktopFileParser; use std::fs::DirEntry; use crate::sync::desktop_file::DesktopFile; use crate::sync::icons::IconSync; +use crate::sync::REALM_BASE_PATHS; /// Synchronize dot-desktop files from active realm to a target directory in Citadel. pub struct DesktopFileSync { @@ -73,8 +74,11 @@ impl DesktopFileSync { pub fn run_sync(&mut self, clear: bool) -> Result<()> { - self.collect_source_files("rootfs/usr/share/applications")?; - self.collect_source_files("home/.local/share/applications")?; + IconSync::ensure_theme_index_exists()?; + + for &base_path in REALM_BASE_PATHS { + self.collect_source_files(base_path)?; + } let target = Path::new(Self::CITADEL_APPLICATIONS); @@ -89,12 +93,14 @@ impl DesktopFileSync { self.synchronize_items()?; if let Some(ref icons) = self.icons { icons.write_known_cache()?; + IconSync::update_mtime()?; } Ok(()) } fn collect_source_files(&mut self, directory: impl AsRef) -> Result<()> { - let directory = Realms::current_realm_symlink().join(directory.as_ref()); + let mut directory = Realms::current_realm_symlink().join(directory.as_ref()); + directory.push("share/applications"); if directory.exists() { util::read_directory(&directory, |dent| { self.process_source_entry(dent); @@ -179,14 +185,6 @@ impl DesktopFileSync { if dfp.is_showable() { self.sync_item_icon(&mut dfp); dfp.write_to_dir(Self::CITADEL_APPLICATIONS, Some(&self.realm))?; - /* - if let Some(icon_name)= dfp.icon() { - if let Some(ref icons) = self.icons { - icons.sync_icon(icon_name)?; - } - } - - */ } else { debug!("Ignoring desktop file {} as not showable", dfp.filename()); } diff --git a/citadel-tool/src/sync/icons.rs b/citadel-tool/src/sync/icons.rs index cfd387c..28ac7b0 100644 --- a/citadel-tool/src/sync/icons.rs +++ b/citadel-tool/src/sync/icons.rs @@ -4,10 +4,12 @@ use std::path::{Path, PathBuf}; use libcitadel::{Result, util, Realm}; use std::cell::{RefCell, Cell}; +use std::fs; use crate::sync::desktop_file::DesktopFile; +use crate::sync::REALM_BASE_PATHS; pub struct IconSync { - realm_base: PathBuf, + realm: Realm, cache: IconCache, known: RefCell>, known_changed: Cell, @@ -15,53 +17,94 @@ pub struct IconSync { impl IconSync { const CITADEL_ICONS: &'static str = "/home/citadel/.local/share/icons"; + const HICOLOR_THEME_INDEX: &'static str = "/usr/share/icons/hicolor/index.theme"; const KNOWN_ICONS_FILE: &'static str = "/home/citadel/.local/share/icons/known.cache"; const PAPER_ICON_CACHE: &'static str = "/usr/share/icons/Paper/icon-theme.cache"; + const HOME_PATH_PREFIX: &'static str = "/home/user/"; + + pub fn ensure_theme_index_exists() -> Result<()> { + let target = Path::new(Self::CITADEL_ICONS).join("hicolor").join("index.theme"); + let parent_dir = target.parent().unwrap(); + if !parent_dir.exists() { + util::create_dir(parent_dir)?; + } + if !target.exists() { + util::copy_file(Self::HICOLOR_THEME_INDEX, &target)?; + } + Ok(()) + } + + pub fn update_mtime() -> Result<()> { + let target = Path::new(Self::CITADEL_ICONS).join("hicolor"); + util::touch_mtime(&target)?; + Ok(()) + } + pub fn new(realm: &Realm) -> Result { - let realm_base= realm.base_path(); + let realm = realm.clone(); let cache = IconCache::open(Self::PAPER_ICON_CACHE)?; let known = Self::read_known_cache()?; let known = RefCell::new(known); let known_changed = Cell::new(false); - Ok(IconSync { realm_base, cache, known, known_changed }) + Ok(IconSync { realm, cache, known, known_changed }) } + fn realm_icon_path(&self, icon_path: &Path, prefix: &str, citadel_base_path: &Path) -> Result { + let suffix = icon_path.strip_prefix(prefix) + .map_err(context!("Failed to strip prefix {} from icon path {}", prefix, icon_path.display()))?; + + let base_path = citadel_base_path.canonicalize() + .map_err(context!("Failed to canonicalize base path {}", citadel_base_path.display()))?; + + let joined = base_path.join(suffix); + let icon_path = joined.canonicalize() + .map_err(context!("Failed to canonicalize icon path {}", joined.display()))?; + + if !icon_path.starts_with(&base_path) { + bail!("Icon path {} should start with realm base path {}", icon_path.display(), citadel_base_path.display()); + } + Ok(icon_path) + } + + fn sync_icon_filepath(&self, file: &mut DesktopFile, path: &Path) -> Result<()> { - let icon_path = path.canonicalize() - .map_err(context!("Failed to canonicalize icon path {}", path.display()))?; + let icon_path = if path.starts_with(Self::HOME_PATH_PREFIX) { + let realm_home = self.realm.base_path().join("home"); + self.realm_icon_path(path, Self::HOME_PATH_PREFIX, &realm_home)? + } else { + let realm_root = self.realm.run_path().join("rootfs"); + self.realm_icon_path(path, "/", &realm_root)? + }; - let icon_path = icon_path.strip_prefix("/") - .map_err(context!("Failed to strip initial / from {}", icon_path.display()))?; - - let realm_path = self.realm_base.join(icon_path); - - if !realm_path.is_file() { - bail!("Failed to find icon file {}", realm_path.display()); + if !icon_path.is_file() { + bail!("Failed to find icon file {}", icon_path.display()); } let dir = Path::new(Self::CITADEL_ICONS).join("filepaths-icons"); - let target = dir.join(realm_path.file_name() + let target = dir.join(icon_path.file_name() .expect("Icon has no filename?")); - util::create_dir(&dir)?; - util::copy_file(&realm_path, &target)?; - util::chmod(&target, 0o644)?; - + if !target.exists() { + util::create_dir(&dir)?; + util::copy_file(&icon_path, &target)?; + util::chmod(&target, 0o644)?; + info!("Copied icon from {} to {}", icon_path.display(), target.display()); + } let target_str = target.display().to_string(); file.update_icon(&target_str); - info!("Copied icon from {} to {}", icon_path.display(), target_str); - Ok(()) } pub fn sync_icon(&self, file: &mut DesktopFile, icon_name: &str) -> Result<()> { + debug!("sync_icon({})", icon_name); if icon_name.starts_with("/") { return self.sync_icon_filepath(file, Path::new(icon_name)); } if self.is_known(icon_name) { + debug!("({}) is known", icon_name); return Ok(()) } if self.cache.find_image(icon_name)? { @@ -70,9 +113,12 @@ impl IconSync { return Ok(()); } - if !self.search("rootfs/usr/share/icons/hicolor", icon_name)? { - self.search("home/.local/share/icons/hicolor", icon_name)?; + for &base_path in REALM_BASE_PATHS { + if self.search(base_path, icon_name)? { + return Ok(()) + } } + debug!("not found: {} ", icon_name); Ok(()) } @@ -108,10 +154,14 @@ impl IconSync { } fn search(&self, subdir: impl AsRef, icon_name: &str) -> Result { - let base = self.realm_base.join(subdir.as_ref()); + let mut base = self.realm.run_path().join(subdir.as_ref()); + base.push("share/icons/hicolor"); if !base.exists() { + debug!("Does not exist: {:?} for {}", base, icon_name); + return Ok(false) } + debug!("Searching {:?} for {}", base, icon_name); let mut found = false; util::read_directory(&base, |dent| { let apps = dent.path().join("apps"); diff --git a/citadel-tool/src/sync/mod.rs b/citadel-tool/src/sync/mod.rs index 43dee64..6956027 100644 --- a/citadel-tool/src/sync/mod.rs +++ b/citadel-tool/src/sync/mod.rs @@ -8,20 +8,31 @@ mod icon_cache; use self::desktop_sync::DesktopFileSync; -fn has_first_arg(args: &[String], arg: &str) -> bool { - args.len() > 1 && args[1].as_str() == arg +fn has_arg(args: &[String], arg: &str) -> bool { + args.iter().any(|s| s.as_str() == arg) } +pub const REALM_BASE_PATHS:&[&str] = &[ + "rootfs/usr", + "rootfs/var/lib/flatpak/exports", + "home/.local", + "home/.local/share/flatpak/exports" +]; + pub fn main(args: Vec) { - Logger::set_log_level(LogLevel::Debug); + if has_arg(&args, "-v") { + Logger::set_log_level(LogLevel::Debug); + } else { + Logger::set_log_level(LogLevel::Info); + } - if has_first_arg(&args, "--all") { + if has_arg(&args, "--all") { if let Err(e) = DesktopFileSync::sync_active_realms() { println!("Sync all active realms failed: {}", e); } } else { - let clear = has_first_arg(&args, "--clear"); + let clear = has_arg(&args, "--clear"); if let Err(e) = sync(clear) { println!("Desktop file sync failed: {}", e); } diff --git a/citadel-tool/src/sync/notes.md b/citadel-tool/src/sync/notes.md deleted file mode 100644 index b583b24..0000000 --- a/citadel-tool/src/sync/notes.md +++ /dev/null @@ -1,30 +0,0 @@ - -### Old Sync - -#### citadel-current-watcher.path - - [Path] - PathChanged=/run/citadel/realms/current - -#### citadel-current-watcher.service - - [Service] - Type=oneshot - ExecStart=/usr/libexec/citadel-desktop-sync --clear - ExecStart=/usr/bin/systemctl restart citadel-desktop-watcher.path - -#### citadel-desktop-watcher.path - - [Path] - PathChanged=/run/citadel/realms/current/current.realm/rootfs/usr/share/applications - PathChanged=/run/citadel/realms/current/current.realm/home/.local/share/applications - -#### citadel-desktop-watcher.service - - [Service] - Type=oneshot - ExecStart=/usr/libexec/citadel-desktop-sync - -### New Sync - -* Added a new command line option `--all` for syncronizing all active realms diff --git a/citadel-tool/src/sync/parser.rs b/citadel-tool/src/sync/parser.rs index cb5d2bd..5355d43 100644 --- a/citadel-tool/src/sync/parser.rs +++ b/citadel-tool/src/sync/parser.rs @@ -9,10 +9,10 @@ lazy_static! { static ref KEY_WHITELIST: HashSet<&'static str> = [ "Type", "Version", "Name", "GenericName", "NoDisplay", "Comment", "Icon", "Hidden", "OnlyShowIn", "NotShowIn", "Path", "Terminal", "Actions", "MimeType", - "Categories", "Keywords", "StartupNotify", "StartupWMClass", "URL", "DocPath", + "Categories", "Keywords", "StartupNotify", "StartupWMClass", "URL", "DocPath", "SingleMainWindow", "X-GNOME-FullName", "X-GNOME-Provides", "X-Desktop-File-Install-Version", "X-GNOME-UsesNotifications", "X-GNOME-DocPath", "X-Geoclue-Reason", "X-GNOME-SingleWindow", "X-GNOME-Gettext-Domain", - "X-MultipleArgs", + "X-MultipleArgs", "X-Flatpak", "X-Flatpak-Tags", "X-SingleMainWindow", ].iter().cloned().collect(); // These are keys which are recognized but deliberately ignored. @@ -23,7 +23,7 @@ lazy_static! { "X-GNOME-Bugzilla-ExtraInfoScript", "X-GNOME-Bugzilla-OtherBinaries", "X-GNOME-Autostart-enabled", "X-AppInstall-Package", "X-KDE-SubstituteUID", "X-Ubuntu-Gettext-Domain", "X-AppInstall-Keywords", "X-Ayatana-Desktop-Shortcuts", "X-GNOME-Settings-Panel", "X-GNOME-WMSettingsModule", "X-GNOME-WMName", - "X-GnomeWMSettingsLibrary", + "X-GnomeWMSettingsLibrary", "X-Purism-FormFactor", "X-ExecArg", ].iter().cloned().collect(); } diff --git a/libcitadel/src/util.rs b/libcitadel/src/util.rs index 0a3e4df..e12f2f9 100644 --- a/libcitadel/src/util.rs +++ b/libcitadel/src/util.rs @@ -7,6 +7,7 @@ use std::env; use std::fs::{self, File, DirEntry}; use std::ffi::CString; use std::io::{self, Seek, Read, BufReader, SeekFrom}; +use std::time::{SystemTime, UNIX_EPOCH}; use walkdir::WalkDir; use libc; @@ -334,3 +335,42 @@ pub fn is_euid_root() -> bool { libc::geteuid() == 0 } } + + +fn utimes(path: &Path, atime: i64, mtime: i64) -> Result<()> { + let cstr = CString::new(path.as_os_str().as_bytes()) + .expect("path contains null byte"); + + let atimeval = libc::timeval { + tv_sec: atime, + tv_usec: 0, + }; + let mtimeval = libc::timeval { + tv_sec: mtime, + tv_usec: 0, + }; + let times = [atimeval,mtimeval]; + let ret = unsafe { libc::utimes(cstr.as_ptr(), times.as_ptr()) }; + if ret != 0 { + bail!("Failed to call utimes: {:?}", io::Error::last_os_error()); + } + Ok(()) +} + + +pub fn touch_mtime(path: &Path) -> Result<()> { + let meta = path.metadata() + .map_err(context!("failed to retrieve metadata from {:?}", path))?; + let now = SystemTime::now().duration_since(UNIX_EPOCH) + .map_err(context!("Could not get system time as UNIX_EPOCH"))?; + + let mtime = now.as_secs() as i64; + + utimes(path, meta.atime(),mtime)?; + Ok(()) + + + + + +} diff --git a/systemd/citadel-current-watcher.path b/systemd/citadel-current-watcher.path index 6a1e5a0..50058dc 100644 --- a/systemd/citadel-current-watcher.path +++ b/systemd/citadel-current-watcher.path @@ -1,6 +1,5 @@ [Unit] Description=Current realm directory watcher -Before=launch-default-realm.service [Path] PathChanged=/run/citadel/realms/current diff --git a/systemd/citadel-desktop-watcher.path b/systemd/citadel-desktop-watcher.path index 36b09dd..4b5fcd4 100644 --- a/systemd/citadel-desktop-watcher.path +++ b/systemd/citadel-desktop-watcher.path @@ -4,4 +4,6 @@ StartLimitIntervalSec=0 [Path] PathChanged=/run/citadel/realms/current/current.realm/rootfs/usr/share/applications +PathChanged=/run/citadel/realms/current/current.realm/rootfs/var/lib/flatpak/exports/share/applications PathChanged=/run/citadel/realms/current/current.realm/home/.local/share/applications +PathChanged=/run/citadel/realms/current/current.realm/home/.local/share/flatpak/exports/share/applications