Various fixes to desktop_sync including Flatpak support

This commit is contained in:
Bruce Leidl 2024-06-03 12:05:58 -04:00
parent 44c545a4a3
commit 421b0e27d7
8 changed files with 142 additions and 72 deletions

View File

@ -8,6 +8,7 @@ use crate::sync::parser::DesktopFileParser;
use std::fs::DirEntry; use std::fs::DirEntry;
use crate::sync::desktop_file::DesktopFile; use crate::sync::desktop_file::DesktopFile;
use crate::sync::icons::IconSync; use crate::sync::icons::IconSync;
use crate::sync::REALM_BASE_PATHS;
/// Synchronize dot-desktop files from active realm to a target directory in Citadel. /// Synchronize dot-desktop files from active realm to a target directory in Citadel.
pub struct DesktopFileSync { pub struct DesktopFileSync {
@ -73,8 +74,11 @@ impl DesktopFileSync {
pub fn run_sync(&mut self, clear: bool) -> Result<()> { pub fn run_sync(&mut self, clear: bool) -> Result<()> {
self.collect_source_files("rootfs/usr/share/applications")?; IconSync::ensure_theme_index_exists()?;
self.collect_source_files("home/.local/share/applications")?;
for &base_path in REALM_BASE_PATHS {
self.collect_source_files(base_path)?;
}
let target = Path::new(Self::CITADEL_APPLICATIONS); let target = Path::new(Self::CITADEL_APPLICATIONS);
@ -89,12 +93,14 @@ impl DesktopFileSync {
self.synchronize_items()?; self.synchronize_items()?;
if let Some(ref icons) = self.icons { if let Some(ref icons) = self.icons {
icons.write_known_cache()?; icons.write_known_cache()?;
IconSync::update_mtime()?;
} }
Ok(()) Ok(())
} }
fn collect_source_files(&mut self, directory: impl AsRef<Path>) -> Result<()> { fn collect_source_files(&mut self, directory: impl AsRef<Path>) -> 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() { if directory.exists() {
util::read_directory(&directory, |dent| { util::read_directory(&directory, |dent| {
self.process_source_entry(dent); self.process_source_entry(dent);
@ -179,14 +185,6 @@ impl DesktopFileSync {
if dfp.is_showable() { if dfp.is_showable() {
self.sync_item_icon(&mut dfp); self.sync_item_icon(&mut dfp);
dfp.write_to_dir(Self::CITADEL_APPLICATIONS, Some(&self.realm))?; 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 { } else {
debug!("Ignoring desktop file {} as not showable", dfp.filename()); debug!("Ignoring desktop file {} as not showable", dfp.filename());
} }

View File

@ -4,10 +4,12 @@ use std::path::{Path, PathBuf};
use libcitadel::{Result, util, Realm}; use libcitadel::{Result, util, Realm};
use std::cell::{RefCell, Cell}; use std::cell::{RefCell, Cell};
use std::fs;
use crate::sync::desktop_file::DesktopFile; use crate::sync::desktop_file::DesktopFile;
use crate::sync::REALM_BASE_PATHS;
pub struct IconSync { pub struct IconSync {
realm_base: PathBuf, realm: Realm,
cache: IconCache, cache: IconCache,
known: RefCell<HashSet<String>>, known: RefCell<HashSet<String>>,
known_changed: Cell<bool>, known_changed: Cell<bool>,
@ -15,53 +17,94 @@ pub struct IconSync {
impl IconSync { impl IconSync {
const CITADEL_ICONS: &'static str = "/home/citadel/.local/share/icons"; 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 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 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<Self> { pub fn new(realm: &Realm) -> Result<Self> {
let realm_base= realm.base_path(); let realm = realm.clone();
let cache = IconCache::open(Self::PAPER_ICON_CACHE)?; let cache = IconCache::open(Self::PAPER_ICON_CACHE)?;
let known = Self::read_known_cache()?; let known = Self::read_known_cache()?;
let known = RefCell::new(known); let known = RefCell::new(known);
let known_changed = Cell::new(false); 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<PathBuf> {
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<()> { fn sync_icon_filepath(&self, file: &mut DesktopFile, path: &Path) -> Result<()> {
let icon_path = path.canonicalize() let icon_path = if path.starts_with(Self::HOME_PATH_PREFIX) {
.map_err(context!("Failed to canonicalize icon path {}", path.display()))?; 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("/") if !icon_path.is_file() {
.map_err(context!("Failed to strip initial / from {}", icon_path.display()))?; bail!("Failed to find icon file {}", 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());
} }
let dir = Path::new(Self::CITADEL_ICONS).join("filepaths-icons"); 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?")); .expect("Icon has no filename?"));
util::create_dir(&dir)?; if !target.exists() {
util::copy_file(&realm_path, &target)?; util::create_dir(&dir)?;
util::chmod(&target, 0o644)?; 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(); let target_str = target.display().to_string();
file.update_icon(&target_str); file.update_icon(&target_str);
info!("Copied icon from {} to {}", icon_path.display(), target_str);
Ok(()) Ok(())
} }
pub fn sync_icon(&self, file: &mut DesktopFile, icon_name: &str) -> Result<()> { pub fn sync_icon(&self, file: &mut DesktopFile, icon_name: &str) -> Result<()> {
debug!("sync_icon({})", icon_name);
if icon_name.starts_with("/") { if icon_name.starts_with("/") {
return self.sync_icon_filepath(file, Path::new(icon_name)); return self.sync_icon_filepath(file, Path::new(icon_name));
} }
if self.is_known(icon_name) { if self.is_known(icon_name) {
debug!("({}) is known", icon_name);
return Ok(()) return Ok(())
} }
if self.cache.find_image(icon_name)? { if self.cache.find_image(icon_name)? {
@ -70,9 +113,12 @@ impl IconSync {
return Ok(()); return Ok(());
} }
if !self.search("rootfs/usr/share/icons/hicolor", icon_name)? { for &base_path in REALM_BASE_PATHS {
self.search("home/.local/share/icons/hicolor", icon_name)?; if self.search(base_path, icon_name)? {
return Ok(())
}
} }
debug!("not found: {} ", icon_name);
Ok(()) Ok(())
} }
@ -108,10 +154,14 @@ impl IconSync {
} }
fn search(&self, subdir: impl AsRef<Path>, icon_name: &str) -> Result<bool> { fn search(&self, subdir: impl AsRef<Path>, icon_name: &str) -> Result<bool> {
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() { if !base.exists() {
debug!("Does not exist: {:?} for {}", base, icon_name);
return Ok(false) return Ok(false)
} }
debug!("Searching {:?} for {}", base, icon_name);
let mut found = false; let mut found = false;
util::read_directory(&base, |dent| { util::read_directory(&base, |dent| {
let apps = dent.path().join("apps"); let apps = dent.path().join("apps");

View File

@ -8,20 +8,31 @@ mod icon_cache;
use self::desktop_sync::DesktopFileSync; use self::desktop_sync::DesktopFileSync;
fn has_first_arg(args: &[String], arg: &str) -> bool { fn has_arg(args: &[String], arg: &str) -> bool {
args.len() > 1 && args[1].as_str() == arg 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<String>) { pub fn main(args: Vec<String>) {
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() { if let Err(e) = DesktopFileSync::sync_active_realms() {
println!("Sync all active realms failed: {}", e); println!("Sync all active realms failed: {}", e);
} }
} else { } else {
let clear = has_first_arg(&args, "--clear"); let clear = has_arg(&args, "--clear");
if let Err(e) = sync(clear) { if let Err(e) = sync(clear) {
println!("Desktop file sync failed: {}", e); println!("Desktop file sync failed: {}", e);
} }

View File

@ -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

View File

@ -9,10 +9,10 @@ lazy_static! {
static ref KEY_WHITELIST: HashSet<&'static str> = [ static ref KEY_WHITELIST: HashSet<&'static str> = [
"Type", "Version", "Name", "GenericName", "NoDisplay", "Comment", "Icon", "Hidden", "Type", "Version", "Name", "GenericName", "NoDisplay", "Comment", "Icon", "Hidden",
"OnlyShowIn", "NotShowIn", "Path", "Terminal", "Actions", "MimeType", "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-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-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(); ].iter().cloned().collect();
// These are keys which are recognized but deliberately ignored. // 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-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-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-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(); ].iter().cloned().collect();
} }

View File

@ -7,6 +7,7 @@ use std::env;
use std::fs::{self, File, DirEntry}; use std::fs::{self, File, DirEntry};
use std::ffi::CString; use std::ffi::CString;
use std::io::{self, Seek, Read, BufReader, SeekFrom}; use std::io::{self, Seek, Read, BufReader, SeekFrom};
use std::time::{SystemTime, UNIX_EPOCH};
use walkdir::WalkDir; use walkdir::WalkDir;
use libc; use libc;
@ -334,3 +335,42 @@ pub fn is_euid_root() -> bool {
libc::geteuid() == 0 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(())
}

View File

@ -1,6 +1,5 @@
[Unit] [Unit]
Description=Current realm directory watcher Description=Current realm directory watcher
Before=launch-default-realm.service
[Path] [Path]
PathChanged=/run/citadel/realms/current PathChanged=/run/citadel/realms/current

View File

@ -4,4 +4,6 @@ StartLimitIntervalSec=0
[Path] [Path]
PathChanged=/run/citadel/realms/current/current.realm/rootfs/usr/share/applications 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/applications
PathChanged=/run/citadel/realms/current/current.realm/home/.local/share/flatpak/exports/share/applications