1pub mod cache;
2pub mod fitername;
3use std::{
4 path::PathBuf,
5 sync::{Arc, Mutex},
6};
7
8use cache::{cache_entries, get_cached_entries};
9
10use freedesktop_file_parser::EntryType;
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub enum IconPath {
15 None,
16 Image(String),
17 Svg(String),
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct EntryData {
22 pub name: String,
23 pub score : Option<i32>,
24 pub exec: String,
26 icon: IconPath,
27}
28impl EntryData {
29 fn new(data: freedesktop_file_parser::DesktopEntry) -> Option<Self> {
30 if let EntryType::Application(app_data) = data.entry_type {
31 let name = data.name.default;
32 let exec = app_data.exec?;
33 let icon = match data.icon {
34 None => IconPath::None,
35 Some(icon) => {
36 let icon = icon.get_icon_path();
37 if let Some(path) = icon {
38 let path = path.to_string_lossy().to_string();
39 if path.ends_with("svg") {
40 IconPath::Svg(path)
41 } else {
42 IconPath::Image(path)
43 }
44 } else {
45 IconPath::None
46 }
47 }
48 };
49 Some(EntryData { name, score : None, exec, icon })
51 } else {
52 None
53 }
54 }
55 pub fn get_icon_path(&self) -> IconPath {
56 self.icon.clone()
57 }
58
59 pub fn run(&self) {
60 use std::process::Command;
61 let mut command = Command::new("sh");
62 let exenc = self
63 .exec
64 .clone()
65 .split_whitespace()
66 .filter(|s| *s != "%u" && *s != "%U")
67 .fold("".to_string(), |a, s| a + s + " ");
68 command.args(["-c", &exenc]);
69 let _ = command.spawn();
70 }
71}
72
73fn get_paths() -> Vec<PathBuf> {
74 let homepath = std::env::home_dir()
75 .unwrap_or("~".into())
76 .to_string_lossy()
77 .to_string();
78 let p = homepath + "/.local/share/applications";
79 let paths = [
80 "/usr/share/applications",
81 &p,
82 "/var/lib/flatpak/exports/share/applications/",
84 ];
85 paths
86 .into_iter()
87 .filter_map(|path| std::fs::read_dir(path).map(Some).unwrap_or(None))
88 .flat_map(|dir_p| {
89 dir_p
90 .filter_map(|m_dir_e| m_dir_e.ok().map(|p| p.path()))
91 .collect::<Vec<_>>()
92 })
93 .filter(|path| path.extension().is_some_and(|s| s == "desktop"))
94 .collect::<Vec<_>>()
95}
96
97fn load_entry_single(entry: Arc<Mutex<Option<EntryData>>>, path: PathBuf) -> Option<()> {
98 use freedesktop_file_parser::parse;
99 let path = std::fs::read_to_string(path).ok()?;
100 let entry_data = parse(&path).ok()?;
101 if let Ok(mut entry) = entry.lock() {
102 let entryy = EntryData::new(entry_data.entry)?;
103 *entry = Some(entryy);
104 }
105 Some(())
106}
107
108fn load_entries_no_cache() -> Vec<EntryData> {
109 use freedesktop_file_parser::parse;
110 let paths = get_paths();
111 let entries = paths
112 .into_iter()
113 .filter_map(|path| std::fs::read_to_string(path).ok())
114 .filter_map(|s| parse(&s).ok())
115 .collect::<Vec<_>>();
116 let mut name_set = std::collections::hash_set::HashSet::new();
117 let entries = entries
118 .into_iter()
119 .filter_map(|entry| EntryData::new(entry.entry))
120 .filter(|entry|{
121 if name_set.contains(&entry.name){
122 false
123 }else{
124 name_set.insert(entry.name.clone());
125 true
126 }
127 })
128 .collect::<Vec<_>>();
129 let _ = cache_entries(entries.clone());
130 entries
131}
132
133pub fn load_entries() -> Vec<EntryData> {
134 match get_cached_entries() {
135 Ok(ent) => {
136 std::thread::spawn(|| {
137 let _ = load_entries_no_cache();
138 });
139 ent
140 }
141 Err(_) => load_entries_no_cache(),
142 }
143}