1pub mod error;
2pub mod macos;
3pub mod window;
4
5use error::{AppInfoError, Result};
6use std::path::PathBuf;
7
8#[derive(Debug, Clone)]
10pub struct AppInfo {
11 pub name: String,
13 pub version: Option<String>,
15 pub path: PathBuf,
17 pub icon: Option<Icon>,
19 pub identifier: Option<String>,
21 pub publisher: Option<String>,
23 pub install_date: Option<String>,
25}
26
27#[derive(Debug, Clone)]
29pub struct Icon {
30 pub width: u32,
32 pub height: u32,
34 pub pixels: Vec<u8>,
36}
37
38pub fn get_installed_apps(icon_size: u16) -> Result<Vec<AppInfo>> {
48 #[cfg(target_os = "macos")]
49 return macos::get_installed_apps(icon_size);
50
51 #[cfg(target_os = "windows")]
52 return window::get_installed_apps(icon_size);
53
54 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
55 Err(AppInfoError::UnsupportedPlatform)
56}
57
58pub fn find_app_by_name(name: &str, icon_size: u16) -> Result<AppInfo> {
69 let apps = get_installed_apps(icon_size)?;
70 apps.into_iter()
71 .find(|app| app.name.eq_ignore_ascii_case(name))
72 .ok_or_else(|| AppInfoError::AppNotFound {
73 name: name.to_string(),
74 })
75}
76
77pub fn get_file_icon(path: impl AsRef<std::path::Path>, size: u16) -> Result<Icon> {
79 let path = path.as_ref();
80 if !path.exists() {
81 return Err(AppInfoError::FileIconError(
82 error::FileIconError::PathDoesNotExist,
83 ));
84 }
85
86 if size == 0 {
87 return Err(AppInfoError::FileIconError(
88 error::FileIconError::NullIconSize,
89 ));
90 }
91
92 #[cfg(target_os = "macos")]
93 return macos::get_file_icon(path, size);
94
95 #[cfg(target_os = "windows")]
96 return window::get_file_icon(path, size);
97
98 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
99 Err(AppInfoError::FileIconError(
100 error::FileIconError::PlatformNotSupported,
101 ))
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn test_get_installed_apps() {
110 let apps = get_installed_apps(0).expect("Failed to get installed apps");
112 assert!(!apps.is_empty(), "Should find at least one application");
113 println!("Applications found: {:?}", apps);
114
115 let apps_with_icons =
117 get_installed_apps(32).expect("Failed to get installed apps with icons");
118 assert!(!apps_with_icons.is_empty());
119
120 #[cfg(any(target_os = "macos", target_os = "windows"))]
122 {
123 let app_with_icon = apps_with_icons.iter().find(|app| app.icon.is_some());
124 assert!(
125 app_with_icon.is_some(),
126 "At least one app should have an icon"
127 );
128
129 if let Some(app) = app_with_icon {
130 let icon = app.icon.as_ref().unwrap();
131 assert_eq!(icon.width, 32);
132 assert_eq!(icon.height, 32);
133 assert!(!icon.pixels.is_empty());
134 assert_eq!(icon.pixels.len(), (32 * 32 * 4) as usize);
135 }
136 }
137 }
138
139 #[test]
140 fn test_find_app_by_name() {
141 let apps = get_installed_apps(0).unwrap();
142 if apps.is_empty() {
143 return;
145 }
146
147 let first_app_name = &apps[0].name;
149 let found_app = find_app_by_name(first_app_name, 0).unwrap();
150 assert_eq!(found_app.name, *first_app_name);
151
152 let found_app_lower = find_app_by_name(&first_app_name.to_lowercase(), 0).unwrap();
154 assert_eq!(found_app_lower.name, *first_app_name);
155
156 let not_found_result = find_app_by_name("ThisAppSurelyDoesNotExist12345", 0);
158 assert!(matches!(
159 not_found_result,
160 Err(AppInfoError::AppNotFound { .. })
161 ));
162 }
163
164 #[test]
165 fn test_get_file_icon() {
166 let path_to_test = if cfg!(target_os = "macos") {
168 "/System/Applications/Calculator.app"
169 } else if cfg!(target_os = "windows") {
170 "C:\\Windows\\System32\\notepad.exe"
171 } else {
172 return;
174 };
175
176 let path = std::path::Path::new(path_to_test);
177
178 let result = get_file_icon(path, 0);
180 assert!(matches!(
181 result,
182 Err(AppInfoError::FileIconError(
183 error::FileIconError::NullIconSize
184 ))
185 ));
186
187 let result = get_file_icon("/path/to/non/existent/file", 32);
189 assert!(matches!(
190 result,
191 Err(AppInfoError::FileIconError(
192 error::FileIconError::PathDoesNotExist
193 ))
194 ));
195
196 if path.exists() {
198 let icon = get_file_icon(path, 64).expect("Failed to get file icon");
199 assert_eq!(icon.width, 64);
200 assert_eq!(icon.height, 64);
201 assert!(!icon.pixels.is_empty());
202 assert_eq!(icon.pixels.len(), (64 * 64 * 4) as usize);
203 }
204 }
205}