1use std::path::PathBuf;
4
5use serde::{Deserialize, Serialize};
6
7const CATEGORY_PLASMA_WIDGET: u16 = 705;
9const CATEGORY_WALLPAPER_PLUGIN: u16 = 715;
10const CATEGORY_KWIN_EFFECT: u16 = 719;
11const CATEGORY_KWIN_SCRIPT: u16 = 720;
12const CATEGORY_KWIN_SWITCHER: u16 = 721;
13const CATEGORY_GLOBAL_THEME: u16 = 722;
14const CATEGORY_PLASMA_STYLE: u16 = 709;
15const CATEGORY_AURORAE_DECORATION: u16 = 114;
16const CATEGORY_COLOR_SCHEME: u16 = 112;
17const CATEGORY_SPLASH_SCREEN: u16 = 708;
18const CATEGORY_SDDM_THEME: u16 = 101;
19const CATEGORY_ICON_THEME: u16 = 132;
20const CATEGORY_WALLPAPER: u16 = 299;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
27#[serde(rename_all = "snake_case")]
28pub enum ComponentType {
29 PlasmaWidget,
30 WallpaperPlugin,
31 KWinEffect,
32 KWinScript,
33 KWinSwitcher,
34 GlobalTheme,
35 PlasmaStyle,
36 AuroraeDecoration,
37 ColorScheme,
38 SplashScreen,
39 SddmTheme,
40 IconTheme,
41 Wallpaper,
42}
43
44impl ComponentType {
45 pub(crate) const fn category_id(self) -> u16 {
46 match self {
47 Self::PlasmaWidget => CATEGORY_PLASMA_WIDGET,
48 Self::WallpaperPlugin => CATEGORY_WALLPAPER_PLUGIN,
49 Self::KWinEffect => CATEGORY_KWIN_EFFECT,
50 Self::KWinScript => CATEGORY_KWIN_SCRIPT,
51 Self::KWinSwitcher => CATEGORY_KWIN_SWITCHER,
52 Self::GlobalTheme => CATEGORY_GLOBAL_THEME,
53 Self::PlasmaStyle => CATEGORY_PLASMA_STYLE,
54 Self::AuroraeDecoration => CATEGORY_AURORAE_DECORATION,
55 Self::ColorScheme => CATEGORY_COLOR_SCHEME,
56 Self::SplashScreen => CATEGORY_SPLASH_SCREEN,
57 Self::SddmTheme => CATEGORY_SDDM_THEME,
58 Self::IconTheme => CATEGORY_ICON_THEME,
59 Self::Wallpaper => CATEGORY_WALLPAPER,
60 }
61 }
62
63 pub(crate) const fn matches_type_id(self, type_id: u16) -> bool {
70 if self.category_id() == type_id {
71 return true;
72 }
73 matches!((self, type_id), (Self::PlasmaWidget, 706..=713 | 723))
75 }
76
77 pub(crate) const fn kpackage_type(self) -> Option<&'static str> {
78 match self {
79 Self::PlasmaWidget => Some("Plasma/Applet"),
80 Self::WallpaperPlugin => Some("Plasma/Wallpaper"),
81 Self::KWinEffect => Some("KWin/Effect"),
82 Self::KWinScript => Some("KWin/Script"),
83 Self::KWinSwitcher => Some("KWin/WindowSwitcher"),
84 _ => None,
85 }
86 }
87
88 pub(crate) const fn registry_only(self) -> bool {
91 matches!(self, Self::IconTheme | Self::Wallpaper | Self::ColorScheme)
92 }
93
94 pub(crate) const fn user_suffix(self) -> Option<&'static str> {
98 match self {
99 Self::PlasmaWidget => Some("plasma/plasmoids"),
100 Self::WallpaperPlugin => Some("plasma/wallpapers"),
101 Self::KWinEffect => Some("kwin/effects"),
102 Self::KWinScript => Some("kwin/scripts"),
103 Self::KWinSwitcher => Some("kwin/tabbox"),
104 Self::GlobalTheme | Self::SplashScreen => Some("plasma/look-and-feel"),
105 Self::PlasmaStyle => Some("plasma/desktoptheme"),
106 Self::AuroraeDecoration => Some("aurorae/themes"),
107 Self::ColorScheme => Some("color-schemes"),
108 Self::SddmTheme => None,
109 Self::IconTheme => Some("icons"),
110 Self::Wallpaper => Some("wallpapers"),
111 }
112 }
113
114 pub fn user_path(self) -> PathBuf {
116 match self.user_suffix() {
117 Some(suffix) => crate::paths::data_home().join(suffix),
118 None => PathBuf::new(),
119 }
120 }
121
122 pub fn system_path(self) -> PathBuf {
124 PathBuf::from(match self {
125 Self::PlasmaWidget => "/usr/share/plasma/plasmoids",
126 Self::WallpaperPlugin => "/usr/share/plasma/wallpapers",
127 Self::KWinEffect => "/usr/share/kwin/effects",
128 Self::KWinScript => "/usr/share/kwin/scripts",
129 Self::KWinSwitcher => "/usr/share/kwin/tabbox",
130 Self::GlobalTheme | Self::SplashScreen => "/usr/share/plasma/look-and-feel",
131 Self::PlasmaStyle => "/usr/share/plasma/desktoptheme",
132 Self::AuroraeDecoration => "/usr/share/aurorae/themes",
133 Self::ColorScheme => "/usr/share/color-schemes",
134 Self::SddmTheme => "/usr/share/sddm/themes",
135 Self::IconTheme => "/usr/share/icons",
136 Self::Wallpaper => "/usr/share/wallpapers",
137 })
138 }
139
140 pub(crate) const fn backup_subdir(self) -> &'static str {
142 match self {
143 Self::PlasmaWidget => "plasma-plasmoids",
144 Self::WallpaperPlugin => "plasma-wallpapers",
145 Self::KWinEffect => "kwin-effects",
146 Self::KWinScript => "kwin-scripts",
147 Self::KWinSwitcher => "kwin-tabbox",
148 Self::GlobalTheme => "plasma-look-and-feel",
149 Self::PlasmaStyle => "plasma-desktoptheme",
150 Self::AuroraeDecoration => "aurorae-themes",
151 Self::ColorScheme => "color-schemes",
152 Self::SplashScreen => "plasma-splash",
153 Self::SddmTheme => "sddm-themes",
154 Self::IconTheme => "icons",
155 Self::Wallpaper => "wallpapers",
156 }
157 }
158
159 pub(crate) const fn registry_file(self) -> Option<&'static str> {
162 match self {
163 Self::PlasmaWidget => Some("plasmoids.knsregistry"),
164 Self::KWinEffect => Some("kwineffect.knsregistry"),
165 Self::KWinScript => Some("kwinscripts.knsregistry"),
166 Self::KWinSwitcher => Some("kwinswitcher.knsregistry"),
167 Self::WallpaperPlugin => Some("wallpaperplugin.knsregistry"),
168 Self::GlobalTheme => Some("lookandfeel.knsregistry"),
169 Self::PlasmaStyle => Some("plasma-themes.knsregistry"),
170 Self::AuroraeDecoration => Some("aurorae.knsregistry"),
171 Self::ColorScheme => Some("colorschemes.knsregistry"),
172 Self::SplashScreen => Some("ksplash.knsregistry"),
173 Self::SddmTheme => Some("sddmtheme.knsregistry"),
174 Self::IconTheme => Some("icons.knsregistry"),
175 Self::Wallpaper => Some("wallpaper.knsregistry"),
176 }
177 }
178
179 pub const fn all() -> &'static [ComponentType] {
182 &[
183 Self::PlasmaWidget,
184 Self::WallpaperPlugin,
185 Self::KWinEffect,
186 Self::KWinScript,
187 Self::KWinSwitcher,
188 Self::GlobalTheme,
189 Self::PlasmaStyle,
190 Self::AuroraeDecoration,
191 Self::ColorScheme,
192 Self::SplashScreen,
193 Self::SddmTheme,
194 Self::IconTheme,
195 Self::Wallpaper,
196 ]
197 }
198
199 pub const fn all_user() -> &'static [ComponentType] {
200 &[
201 Self::PlasmaWidget,
202 Self::WallpaperPlugin,
203 Self::KWinEffect,
204 Self::KWinScript,
205 Self::KWinSwitcher,
206 Self::GlobalTheme,
207 Self::PlasmaStyle,
208 Self::AuroraeDecoration,
209 Self::ColorScheme,
210 Self::SplashScreen,
211 Self::IconTheme,
212 Self::Wallpaper,
213 ]
214 }
215}
216
217impl std::fmt::Display for ComponentType {
218 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219 match self {
220 Self::PlasmaWidget => write!(f, "Plasma Widget"),
221 Self::WallpaperPlugin => write!(f, "Wallpaper Plugin"),
222 Self::KWinEffect => write!(f, "KWin Effect"),
223 Self::KWinScript => write!(f, "KWin Script"),
224 Self::KWinSwitcher => write!(f, "KWin Switcher"),
225 Self::GlobalTheme => write!(f, "Global Theme"),
226 Self::PlasmaStyle => write!(f, "Plasma Style"),
227 Self::AuroraeDecoration => write!(f, "Aurorae Decoration"),
228 Self::ColorScheme => write!(f, "Color Scheme"),
229 Self::SplashScreen => write!(f, "Splash Screen"),
230 Self::SddmTheme => write!(f, "SDDM Theme"),
231 Self::IconTheme => write!(f, "Icon Theme"),
232 Self::Wallpaper => write!(f, "Wallpaper"),
233 }
234 }
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct InstalledComponent {
242 pub name: String,
243 pub directory_name: String,
244 pub version: String,
245 pub component_type: ComponentType,
246 #[serde(with = "pathbuf_serde")]
247 pub path: PathBuf,
248 pub is_system: bool,
249 pub release_date: String,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct AvailableUpdate {
255 pub installed: InstalledComponent,
256 pub content_id: u64,
257 pub latest_version: String,
258 pub download_url: String,
259 pub store_url: String,
260 pub release_date: String,
261 #[serde(skip_serializing_if = "Option::is_none")]
262 pub checksum: Option<String>,
263 #[serde(skip_serializing_if = "Option::is_none")]
264 pub download_size: Option<u64>,
265}
266
267pub(crate) struct AvailableUpdateBuilder {
269 installed: InstalledComponent,
270 content_id: u64,
271 latest_version: String,
272 download_url: String,
273 release_date: String,
274 checksum: Option<String>,
275 download_size: Option<u64>,
276}
277
278impl AvailableUpdateBuilder {
279 pub(crate) fn checksum(mut self, checksum: Option<String>) -> Self {
280 self.checksum = checksum;
281 self
282 }
283
284 pub(crate) fn download_size(mut self, size: Option<u64>) -> Self {
285 self.download_size = size;
286 self
287 }
288
289 pub(crate) fn build(self) -> AvailableUpdate {
290 let store_url = format!("https://store.kde.org/p/{}", self.content_id);
291 AvailableUpdate {
292 installed: self.installed,
293 content_id: self.content_id,
294 latest_version: self.latest_version,
295 download_url: self.download_url,
296 store_url,
297 release_date: self.release_date,
298 checksum: self.checksum,
299 download_size: self.download_size,
300 }
301 }
302}
303
304impl AvailableUpdate {
305 pub(crate) fn builder(
306 installed: InstalledComponent,
307 content_id: u64,
308 latest_version: String,
309 download_url: String,
310 release_date: String,
311 ) -> AvailableUpdateBuilder {
312 AvailableUpdateBuilder {
313 installed,
314 content_id,
315 latest_version,
316 download_url,
317 release_date,
318 checksum: None,
319 download_size: None,
320 }
321 }
322}
323
324#[derive(Debug, Clone)]
326pub(crate) struct StoreEntry {
327 pub id: u64,
328 pub name: String,
329 pub version: String,
330 pub type_id: u16,
331 pub download_links: Vec<DownloadLink>,
332 pub changed_date: String,
333}
334
335#[derive(Debug, Clone)]
337pub(crate) struct DownloadLink {
338 pub url: String,
339 pub version: String,
340 pub checksum: Option<String>,
341 pub size_kb: Option<u64>,
342}
343
344#[derive(Debug, Clone, Default, Deserialize)]
346pub(crate) struct PackageMetadata {
347 #[serde(rename = "KPlugin")]
348 pub kplugin: Option<KPluginInfo>,
349}
350
351#[derive(Debug, Clone, Default, Deserialize, Serialize)]
353pub(crate) struct KPluginInfo {
354 #[serde(rename = "Name")]
355 pub name: Option<String>,
356 #[serde(rename = "Version")]
357 pub version: Option<String>,
358 #[serde(rename = "Description")]
359 pub description: Option<String>,
360 #[serde(rename = "Icon")]
361 pub icon: Option<String>,
362}
363
364impl PackageMetadata {
365 pub(crate) fn name(&self) -> Option<&str> {
366 self.kplugin.as_ref()?.name.as_deref()
367 }
368
369 pub(crate) fn version(&self) -> Option<&str> {
370 self.kplugin.as_ref()?.version.as_deref()
371 }
372}
373
374#[derive(Debug, Clone, Serialize, Deserialize)]
379pub struct Diagnostic {
380 pub name: String,
381 pub reason: String,
382 #[serde(skip_serializing_if = "Option::is_none")]
383 pub installed_version: Option<String>,
384 #[serde(skip_serializing_if = "Option::is_none")]
385 pub available_version: Option<String>,
386 #[serde(skip_serializing_if = "Option::is_none")]
387 pub content_id: Option<u64>,
388}
389
390impl Diagnostic {
391 pub(crate) fn new(name: String, reason: String) -> Self {
392 Self {
393 name,
394 reason,
395 installed_version: None,
396 available_version: None,
397 content_id: None,
398 }
399 }
400
401 pub(crate) fn with_versions(
402 mut self,
403 installed: Option<String>,
404 available: Option<String>,
405 ) -> Self {
406 self.installed_version = installed;
407 self.available_version = available;
408 self
409 }
410
411 pub(crate) fn with_content_id(mut self, id: u64) -> Self {
412 self.content_id = Some(id);
413 self
414 }
415}
416
417#[derive(Debug, Clone, Default, Serialize, Deserialize)]
419pub(crate) struct UpdateCheckResult {
420 pub updates: Vec<AvailableUpdate>,
421 pub unresolved: Vec<Diagnostic>,
422 pub check_failures: Vec<Diagnostic>,
423}
424
425impl UpdateCheckResult {
426 pub fn add_update(&mut self, update: AvailableUpdate) {
427 self.updates.push(update);
428 }
429
430 pub fn add_unresolved(&mut self, diagnostic: Diagnostic) {
431 self.unresolved.push(diagnostic);
432 }
433
434 pub fn add_check_failure(&mut self, diagnostic: Diagnostic) {
435 self.check_failures.push(diagnostic);
436 }
437}
438
439mod pathbuf_serde {
440 use std::path::{Path, PathBuf};
441
442 use serde::{self, Deserialize, Deserializer, Serializer};
443
444 pub fn serialize<S>(path: &Path, serializer: S) -> Result<S::Ok, S::Error>
445 where
446 S: Serializer,
447 {
448 serializer.serialize_str(&path.to_string_lossy())
449 }
450
451 pub fn deserialize<'de, D>(deserializer: D) -> Result<PathBuf, D::Error>
452 where
453 D: Deserializer<'de>,
454 {
455 let s = String::deserialize(deserializer)?;
456 Ok(PathBuf::from(s))
457 }
458}