forked from brl/citadel-tools
Various fixes to desktop_sync including Flatpak support
This commit is contained in:
parent
44c545a4a3
commit
421b0e27d7
@ -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());
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user