mirror of
https://github.com/ByteAtATime/raycast-linux.git
synced 2025-09-12 00:46:22 +00:00
refactor; split lib.rs into multiple files
This commit is contained in:
parent
c69f10102e
commit
3116a0c271
5 changed files with 293 additions and 209 deletions
|
@ -1,216 +1,21 @@
|
|||
use freedesktop_file_parser::{parse, EntryType};
|
||||
use rayon::prelude::*;
|
||||
use selection::get_text;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
env, fs,
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
thread,
|
||||
time::{Duration, SystemTime},
|
||||
mod app;
|
||||
mod cache;
|
||||
mod desktop;
|
||||
mod error;
|
||||
|
||||
use crate::{
|
||||
app::App,
|
||||
cache::AppCache,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CacheError {
|
||||
Io,
|
||||
Bincode,
|
||||
DirectoryNotFound,
|
||||
}
|
||||
|
||||
impl From<io::Error> for CacheError {
|
||||
fn from(_: io::Error) -> Self {
|
||||
CacheError::Io
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bincode::error::DecodeError> for CacheError {
|
||||
fn from(_: bincode::error::DecodeError) -> Self {
|
||||
CacheError::Bincode
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bincode::error::EncodeError> for CacheError {
|
||||
fn from(_: bincode::error::EncodeError) -> Self {
|
||||
CacheError::Bincode
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct App {
|
||||
pub name: String,
|
||||
pub comment: Option<String>,
|
||||
pub exec: Option<String>,
|
||||
pub icon_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct AppCache {
|
||||
apps: Vec<App>,
|
||||
dir_mod_times: HashMap<PathBuf, SystemTime>,
|
||||
}
|
||||
|
||||
fn get_app_dirs() -> Vec<PathBuf> {
|
||||
let mut app_dirs = vec![
|
||||
PathBuf::from("/usr/share/applications"),
|
||||
PathBuf::from("/usr/local/share/applications"),
|
||||
];
|
||||
|
||||
if let Ok(home_dir) = env::var("HOME") {
|
||||
app_dirs.push(PathBuf::from(home_dir).join(".local/share/applications"));
|
||||
}
|
||||
app_dirs
|
||||
}
|
||||
|
||||
fn get_cache_path() -> Result<PathBuf, CacheError> {
|
||||
let cache_dir = env::var("XDG_CACHE_HOME")
|
||||
.map(PathBuf::from)
|
||||
.or_else(|_| env::var("HOME").map(|home| PathBuf::from(home).join(".cache")))
|
||||
.map_err(|_| CacheError::DirectoryNotFound)?;
|
||||
|
||||
let app_cache_dir = cache_dir.join("raycast-linux");
|
||||
fs::create_dir_all(&app_cache_dir)?;
|
||||
Ok(app_cache_dir.join("apps.bincode"))
|
||||
}
|
||||
|
||||
fn find_desktop_files(path: &Path) -> Vec<PathBuf> {
|
||||
let mut desktop_files = Vec::new();
|
||||
if let Ok(entries) = fs::read_dir(path) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
desktop_files.extend(find_desktop_files(&path));
|
||||
} else if path.extension().map_or(false, |ext| ext == "desktop") {
|
||||
desktop_files.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
desktop_files
|
||||
}
|
||||
|
||||
fn scan_and_parse_apps() -> Result<(Vec<App>, HashMap<PathBuf, SystemTime>), CacheError> {
|
||||
let app_dirs = get_app_dirs();
|
||||
let desktop_files: Vec<PathBuf> = app_dirs
|
||||
.iter()
|
||||
.filter(|dir| dir.exists())
|
||||
.flat_map(|dir| find_desktop_files(dir))
|
||||
.collect();
|
||||
|
||||
let apps: Vec<App> = desktop_files
|
||||
.par_iter()
|
||||
.filter_map(|file_path| {
|
||||
let content = fs::read_to_string(file_path).ok()?;
|
||||
let desktop_file = parse(&content).ok()?;
|
||||
|
||||
if desktop_file.entry.hidden.unwrap_or(false)
|
||||
|| desktop_file.entry.no_display.unwrap_or(false)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
if let EntryType::Application(app_fields) = desktop_file.entry.entry_type {
|
||||
if app_fields.exec.is_some() && !desktop_file.entry.name.default.is_empty() {
|
||||
return Some(App {
|
||||
name: desktop_file.entry.name.default,
|
||||
comment: desktop_file.entry.comment.map(|lc| lc.default),
|
||||
exec: app_fields.exec,
|
||||
icon_path: desktop_file
|
||||
.entry
|
||||
.icon
|
||||
.and_then(|ic| ic.get_icon_path())
|
||||
.and_then(|p| p.to_str().map(String::from)),
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut unique_apps = Vec::new();
|
||||
let mut seen_app_names = HashSet::new();
|
||||
for app in apps {
|
||||
if seen_app_names.insert(app.name.clone()) {
|
||||
unique_apps.push(app);
|
||||
}
|
||||
}
|
||||
unique_apps.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
|
||||
|
||||
let dir_mod_times = app_dirs
|
||||
.into_iter()
|
||||
.filter_map(|dir| {
|
||||
fs::metadata(&dir)
|
||||
.and_then(|m| m.modified())
|
||||
.ok()
|
||||
.map(|mod_time| (dir, mod_time))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok((unique_apps, dir_mod_times))
|
||||
}
|
||||
|
||||
fn read_cache(path: &Path) -> Result<AppCache, CacheError> {
|
||||
let file_content = fs::read(path)?;
|
||||
let (decoded, _) = bincode::serde::decode_from_slice(&file_content, bincode::config::standard())?;
|
||||
Ok(decoded)
|
||||
}
|
||||
|
||||
fn write_cache(path: &Path, cache: &AppCache) -> Result<(), CacheError> {
|
||||
let encoded = bincode::serde::encode_to_vec(cache, bincode::config::standard())?;
|
||||
fs::write(path, encoded)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn refresh_app_cache() {
|
||||
if let (Ok(cache_path), Ok((apps, dir_mod_times))) =
|
||||
(get_cache_path(), scan_and_parse_apps())
|
||||
{
|
||||
let cache_data = AppCache {
|
||||
apps,
|
||||
dir_mod_times,
|
||||
};
|
||||
if let Err(e) = write_cache(&cache_path, &cache_data) {
|
||||
eprintln!("Error refreshing app cache in background: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
use selection::get_text;
|
||||
use std::{process::Command, thread, time::Duration};
|
||||
|
||||
#[tauri::command]
|
||||
fn get_installed_apps() -> Vec<App> {
|
||||
let cache_path = match get_cache_path() {
|
||||
Ok(path) => path,
|
||||
match AppCache::get_apps() {
|
||||
Ok(apps) => apps,
|
||||
Err(e) => {
|
||||
eprintln!("Could not get cache path: {:?}. Falling back to scan.", e);
|
||||
return scan_and_parse_apps().map_or_else(|_| Vec::new(), |(apps, _)| apps);
|
||||
}
|
||||
};
|
||||
|
||||
if let Ok(cached_data) = read_cache(&cache_path) {
|
||||
let is_stale = get_app_dirs().into_iter().any(|dir| {
|
||||
let current_mod_time = fs::metadata(&dir).ok().and_then(|m| m.modified().ok());
|
||||
let cached_mod_time = cached_data.dir_mod_times.get(&dir);
|
||||
|
||||
match (current_mod_time, cached_mod_time) {
|
||||
(Some(current), Some(cached)) => current > *cached,
|
||||
_ => true,
|
||||
}
|
||||
});
|
||||
|
||||
if !is_stale {
|
||||
return cached_data.apps;
|
||||
}
|
||||
}
|
||||
|
||||
match scan_and_parse_apps() {
|
||||
Ok((apps, dir_mod_times)) => {
|
||||
let cache_data = AppCache { apps: apps.clone(), dir_mod_times };
|
||||
if let Err(e) = write_cache(&cache_path, &cache_data) {
|
||||
eprintln!("Failed to write to app cache: {:?}", e);
|
||||
}
|
||||
apps
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to scan and parse apps: {:?}", e);
|
||||
eprintln!("Failed to get apps: {:?}", e);
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
@ -257,7 +62,7 @@ pub fn run() {
|
|||
thread::spawn(|| {
|
||||
thread::sleep(Duration::from_secs(60));
|
||||
loop {
|
||||
refresh_app_cache();
|
||||
AppCache::refresh_background();
|
||||
thread::sleep(Duration::from_secs(300));
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue