1
0
forked from brl/citadel-tools

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 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<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() {
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());
}

View File

@ -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<HashSet<String>>,
known_changed: Cell<bool>,
@ -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<Self> {
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<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<()> {
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<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() {
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");

View File

@ -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<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() {
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);
}

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> = [
"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();
}

View File

@ -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(())
}

View File

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

View File

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