refactor 2
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
use tauri::AppHandle;
|
||||
|
||||
use crate::domain::models::{AppSettings, EssentialsRepo, LogPayload, SyncEssentialsResult};
|
||||
use crate::domain::models::{
|
||||
AppSettings, DashboardSnapshot, EssentialsRepo, EssentialsStatusItem, LogPayload, SyncEssentialsResult,
|
||||
UpdateCandidate,
|
||||
};
|
||||
use crate::services::{essentials_service, settings_service, software_state_service};
|
||||
use crate::winget::Software;
|
||||
|
||||
@@ -39,6 +42,21 @@ pub async fn get_updates(app: AppHandle) -> Vec<Software> {
|
||||
software_state_service::get_updates(app).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_dashboard_snapshot(app: AppHandle) -> DashboardSnapshot {
|
||||
software_state_service::get_dashboard_snapshot(app).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_essentials_status(app: AppHandle) -> (String, Vec<EssentialsStatusItem>) {
|
||||
software_state_service::get_essentials_status(app).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_update_candidates(app: AppHandle) -> Vec<UpdateCandidate> {
|
||||
software_state_service::get_update_candidates(app).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_software_info(app: AppHandle, id: String) -> Option<Software> {
|
||||
software_state_service::get_software_info(app, id).await
|
||||
|
||||
@@ -63,3 +63,44 @@ pub struct ResolvedPostInstall {
|
||||
pub software: Software,
|
||||
pub steps: Vec<PostInstallStep>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct EssentialsStatusItem {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub version: Option<String>,
|
||||
pub recommended_version: Option<String>,
|
||||
pub available_version: Option<String>,
|
||||
pub icon_url: Option<String>,
|
||||
pub use_manifest: bool,
|
||||
pub manifest_url: Option<String>,
|
||||
pub post_install: Option<Vec<PostInstallStep>>,
|
||||
pub post_install_url: Option<String>,
|
||||
pub action_label: String,
|
||||
pub target_version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct UpdateCandidate {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub version: Option<String>,
|
||||
pub available_version: Option<String>,
|
||||
pub icon_url: Option<String>,
|
||||
pub use_manifest: bool,
|
||||
pub manifest_url: Option<String>,
|
||||
pub post_install: Option<Vec<PostInstallStep>>,
|
||||
pub post_install_url: Option<String>,
|
||||
pub action_label: String,
|
||||
pub target_version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct DashboardSnapshot {
|
||||
pub essentials_version: String,
|
||||
pub essentials: Vec<EssentialsStatusItem>,
|
||||
pub updates: Vec<UpdateCandidate>,
|
||||
pub installed_software: Vec<Software>,
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ pub fn run() {
|
||||
commands::app_commands::get_essentials,
|
||||
commands::app_commands::get_installed_software,
|
||||
commands::app_commands::get_updates,
|
||||
commands::app_commands::get_dashboard_snapshot,
|
||||
commands::app_commands::get_essentials_status,
|
||||
commands::app_commands::get_update_candidates,
|
||||
commands::app_commands::get_software_icon,
|
||||
commands::app_commands::get_software_info,
|
||||
tasks::install_queue::install_software,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod essentials_service;
|
||||
pub mod log_service;
|
||||
pub mod reconcile_service;
|
||||
pub mod settings_service;
|
||||
pub mod software_state_service;
|
||||
|
||||
158
src-tauri/src/services/reconcile_service.rs
Normal file
158
src-tauri/src/services/reconcile_service.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::domain::models::{DashboardSnapshot, EssentialsRepo, EssentialsStatusItem, UpdateCandidate};
|
||||
use crate::winget::Software;
|
||||
|
||||
pub fn build_dashboard_snapshot(
|
||||
repo: Option<EssentialsRepo>,
|
||||
installed_software: Vec<Software>,
|
||||
updates: Vec<Software>,
|
||||
) -> DashboardSnapshot {
|
||||
let essentials_version = repo
|
||||
.as_ref()
|
||||
.map(|item| item.version.clone())
|
||||
.unwrap_or_default();
|
||||
let definitions = repo.map(|item| item.essentials).unwrap_or_default();
|
||||
let essentials = build_essentials_status(&definitions, &installed_software, &updates);
|
||||
let update_candidates = build_update_candidates(&definitions, updates);
|
||||
|
||||
DashboardSnapshot {
|
||||
essentials_version,
|
||||
essentials,
|
||||
updates: update_candidates,
|
||||
installed_software,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_essentials_status(
|
||||
definitions: &[Software],
|
||||
installed_software: &[Software],
|
||||
updates: &[Software],
|
||||
) -> Vec<EssentialsStatusItem> {
|
||||
definitions
|
||||
.iter()
|
||||
.map(|definition| {
|
||||
let installed = installed_software
|
||||
.iter()
|
||||
.find(|item| item.id.eq_ignore_ascii_case(&definition.id));
|
||||
let update = updates
|
||||
.iter()
|
||||
.find(|item| item.id.eq_ignore_ascii_case(&definition.id));
|
||||
|
||||
let current_version = installed.and_then(|item| item.version.clone());
|
||||
let recommended_version = definition.version.clone();
|
||||
let available_version = update.and_then(|item| item.available_version.clone());
|
||||
|
||||
let (action_label, target_version) = if installed.is_some() {
|
||||
match compare_versions(current_version.as_deref(), recommended_version.as_deref()) {
|
||||
Some(std::cmp::Ordering::Less) => (
|
||||
"更新".to_string(),
|
||||
recommended_version.clone().or(available_version.clone()),
|
||||
),
|
||||
Some(_) => ("已安装".to_string(), None),
|
||||
None => ("已安装".to_string(), None),
|
||||
}
|
||||
} else {
|
||||
(
|
||||
"安装".to_string(),
|
||||
recommended_version.clone().or(available_version.clone()),
|
||||
)
|
||||
};
|
||||
|
||||
EssentialsStatusItem {
|
||||
id: definition.id.clone(),
|
||||
name: definition.name.clone(),
|
||||
description: definition.description.clone(),
|
||||
version: current_version,
|
||||
recommended_version,
|
||||
available_version,
|
||||
icon_url: definition.icon_url.clone(),
|
||||
use_manifest: definition.use_manifest,
|
||||
manifest_url: definition.manifest_url.clone(),
|
||||
post_install: definition.post_install.clone(),
|
||||
post_install_url: definition.post_install_url.clone(),
|
||||
action_label,
|
||||
target_version,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn build_update_candidates(
|
||||
definitions: &[Software],
|
||||
updates: Vec<Software>,
|
||||
) -> Vec<UpdateCandidate> {
|
||||
let definition_map: HashMap<String, &Software> = definitions
|
||||
.iter()
|
||||
.map(|item| (item.id.to_ascii_lowercase(), item))
|
||||
.collect();
|
||||
|
||||
let mut result: Vec<UpdateCandidate> = updates
|
||||
.into_iter()
|
||||
.map(|update| {
|
||||
let definition = definition_map.get(&update.id.to_ascii_lowercase()).copied();
|
||||
UpdateCandidate {
|
||||
id: update.id.clone(),
|
||||
name: update.name.clone(),
|
||||
description: definition.and_then(|item| item.description.clone()),
|
||||
version: update.version.clone(),
|
||||
available_version: update.available_version.clone(),
|
||||
icon_url: update.icon_url.clone().or_else(|| definition.and_then(|item| item.icon_url.clone())),
|
||||
use_manifest: definition.map(|item| item.use_manifest).unwrap_or(false),
|
||||
manifest_url: definition.and_then(|item| item.manifest_url.clone()),
|
||||
post_install: definition.and_then(|item| item.post_install.clone()),
|
||||
post_install_url: definition.and_then(|item| item.post_install_url.clone()),
|
||||
action_label: "更新".to_string(),
|
||||
target_version: update.available_version.clone(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
result.sort_by(|left, right| left.name.locale_compare(&right.name));
|
||||
result
|
||||
}
|
||||
|
||||
trait LocaleCompare {
|
||||
fn locale_compare(&self, other: &str) -> std::cmp::Ordering;
|
||||
}
|
||||
|
||||
impl LocaleCompare for String {
|
||||
fn locale_compare(&self, other: &str) -> std::cmp::Ordering {
|
||||
self.to_lowercase().cmp(&other.to_lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
fn compare_versions(left: Option<&str>, right: Option<&str>) -> Option<std::cmp::Ordering> {
|
||||
let left = left?;
|
||||
let right = right?;
|
||||
if left == right {
|
||||
return Some(std::cmp::Ordering::Equal);
|
||||
}
|
||||
|
||||
let clean = |value: &str| {
|
||||
value
|
||||
.trim_start_matches('v')
|
||||
.trim_start_matches('V')
|
||||
.split(['-', '+'])
|
||||
.next()
|
||||
.unwrap_or(value)
|
||||
.split('.')
|
||||
.map(|item| item.parse::<u32>().unwrap_or(0))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let left_parts = clean(left);
|
||||
let right_parts = clean(right);
|
||||
let max_len = left_parts.len().max(right_parts.len());
|
||||
|
||||
for index in 0..max_len {
|
||||
let left_value = *left_parts.get(index).unwrap_or(&0);
|
||||
let right_value = *right_parts.get(index).unwrap_or(&0);
|
||||
match left_value.cmp(&right_value) {
|
||||
std::cmp::Ordering::Equal => continue,
|
||||
ordering => return Some(ordering),
|
||||
}
|
||||
}
|
||||
|
||||
Some(std::cmp::Ordering::Equal)
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
use tauri::AppHandle;
|
||||
|
||||
use crate::domain::models::{DashboardSnapshot, EssentialsStatusItem, UpdateCandidate};
|
||||
use crate::providers::winget_client;
|
||||
use crate::services::{essentials_service, reconcile_service};
|
||||
use crate::winget::Software;
|
||||
|
||||
pub async fn initialize_app(app: AppHandle) -> Result<bool, String> {
|
||||
@@ -33,3 +35,28 @@ pub async fn get_software_icon(app: AppHandle, id: String, name: String) -> Opti
|
||||
.await
|
||||
.unwrap_or(None)
|
||||
}
|
||||
|
||||
pub async fn get_dashboard_snapshot(app: AppHandle) -> DashboardSnapshot {
|
||||
let repo = essentials_service::get_essentials(&app);
|
||||
let app_for_installed = app.clone();
|
||||
let app_for_updates = app.clone();
|
||||
|
||||
let installed_handle =
|
||||
tokio::task::spawn_blocking(move || winget_client::list_installed_packages(&app_for_installed));
|
||||
let updates_handle =
|
||||
tokio::task::spawn_blocking(move || winget_client::list_upgrade_candidates(&app_for_updates));
|
||||
|
||||
let installed_software = installed_handle.await.unwrap_or_default();
|
||||
let updates = updates_handle.await.unwrap_or_default();
|
||||
|
||||
reconcile_service::build_dashboard_snapshot(repo, installed_software, updates)
|
||||
}
|
||||
|
||||
pub async fn get_essentials_status(app: AppHandle) -> (String, Vec<EssentialsStatusItem>) {
|
||||
let snapshot = get_dashboard_snapshot(app).await;
|
||||
(snapshot.essentials_version, snapshot.essentials)
|
||||
}
|
||||
|
||||
pub async fn get_update_candidates(app: AppHandle) -> Vec<UpdateCandidate> {
|
||||
get_dashboard_snapshot(app).await.updates
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user