fetch essentials from url

This commit is contained in:
Julian Freeman
2026-03-30 17:45:09 -04:00
parent 8f55da1940
commit 626a9c21b0
7 changed files with 980 additions and 55 deletions

View File

@@ -4,12 +4,26 @@ use std::fs;
use std::process::{Command, Stdio};
use std::os::windows::process::CommandExt;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use tokio::sync::mpsc;
use tauri::{AppHandle, Manager, State, Emitter};
use serde::Serialize;
use serde::{Serialize, Deserialize};
use winget::{Software, list_all_software, list_updates, ensure_winget_dependencies};
use regex::Regex;
#[derive(Clone, Serialize, Deserialize)]
pub struct AppSettings {
pub repo_url: String,
}
impl Default for AppSettings {
fn default() -> Self {
Self {
repo_url: "https://karlblue.github.io/winget-repo".to_string(),
}
}
}
struct AppState {
install_tx: mpsc::Sender<String>,
}
@@ -34,46 +48,94 @@ pub fn emit_log(handle: &AppHandle, id: &str, command: &str, output: &str, statu
});
}
fn get_settings_path(app: &AppHandle) -> PathBuf {
let app_data_dir = app.path().app_data_dir().unwrap_or_default();
if !app_data_dir.exists() {
let _ = fs::create_dir_all(&app_data_dir);
}
app_data_dir.join("settings.json")
}
fn get_essentials_path(app: &AppHandle) -> PathBuf {
let app_data_dir = app.path().app_data_dir().unwrap_or_default();
if !app_data_dir.exists() {
let _ = fs::create_dir_all(&app_data_dir);
}
app_data_dir.join("setup-essentials.json")
}
#[tauri::command]
fn get_settings(app: AppHandle) -> AppSettings {
let path = get_settings_path(&app);
if !path.exists() {
let default_settings = AppSettings::default();
let _ = fs::write(&path, serde_json::to_string_pretty(&default_settings).unwrap());
return default_settings;
}
let content = fs::read_to_string(path).unwrap_or_default();
serde_json::from_str(&content).unwrap_or_default()
}
#[tauri::command]
fn save_settings(app: AppHandle, settings: AppSettings) -> Result<(), String> {
let path = get_settings_path(&app);
let content = serde_json::to_string_pretty(&settings).map_err(|e| e.to_string())?;
fs::write(path, content).map_err(|e| e.to_string())
}
#[tauri::command]
async fn sync_essentials(app: AppHandle) -> Result<bool, String> {
let settings = get_settings(app.clone());
let url = format!("{}/setup-essentials.json", settings.repo_url.trim_end_matches('/'));
emit_log(&app, "sync-essentials", "Syncing Essentials", &format!("Downloading from {}...", url), "info");
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(10))
.build()
.map_err(|e| e.to_string())?;
match client.get(&url).send().await {
Ok(response) => {
if response.status().is_success() {
let content = response.text().await.map_err(|e| e.to_string())?;
// 验证 JSON 格式
let validation: Result<Vec<Software>, _> = serde_json::from_str(&content);
if validation.is_ok() {
let path = get_essentials_path(&app);
fs::write(path, content).map_err(|e| e.to_string())?;
emit_log(&app, "sync-essentials", "Result", "Essentials list updated successfully.", "success");
Ok(true)
} else {
emit_log(&app, "sync-essentials", "Error", "Invalid JSON format from repository.", "error");
Err("Invalid JSON format".to_string())
}
} else {
let err_msg = format!("HTTP Error: {}", response.status());
emit_log(&app, "sync-essentials", "Error", &err_msg, "error");
Err(err_msg)
}
}
Err(e) => {
emit_log(&app, "sync-essentials", "Skipped", &format!("Network issue: {}. Using local cache.", e), "info");
Ok(false) // 静默失败,返回 false 表示未更新但可继续
}
}
}
#[tauri::command]
async fn initialize_app(app: AppHandle) -> Result<bool, String> {
let app_clone = app.clone();
tokio::task::spawn_blocking(move || {
ensure_winget_dependencies(&app).map(|_| true)
ensure_winget_dependencies(&app_clone).map(|_| true)
}).await.unwrap_or(Err("Initialization Task Panicked".to_string()))
}
#[tauri::command]
fn get_essentials(app: AppHandle) -> Vec<Software> {
let app_data_dir = app.path().app_data_dir().unwrap_or_default();
if !app_data_dir.exists() {
let _ = fs::create_dir_all(&app_data_dir);
}
let file_path = app_data_dir.join("setup-essentials.json");
let file_path = get_essentials_path(&app);
if !file_path.exists() {
let default_essentials = vec![
Software {
id: "Microsoft.PowerToys".to_string(),
name: "PowerToys".to_string(),
description: Some("Microsoft PowerToys 是一组实用程序,供高级用户调整和简化其 Windows 10 和 11 体验。".to_string()),
version: None,
available_version: None,
icon_url: Some("https://raw.githubusercontent.com/microsoft/PowerToys/master/doc/images/logo.png".to_string()),
status: "idle".to_string(),
progress: 0.0,
},
Software {
id: "Google.Chrome".to_string(),
name: "Google Chrome".to_string(),
description: Some("Google Chrome 是一款快速、安全且免费的浏览器。".to_string()),
version: None,
available_version: None,
icon_url: Some("https://www.google.com/chrome/static/images/chrome-logo.svg".to_string()),
status: "idle".to_string(),
progress: 0.0,
}
];
let _ = fs::write(&file_path, serde_json::to_string_pretty(&default_essentials).unwrap());
return default_essentials;
return vec![];
}
let content = fs::read_to_string(file_path).unwrap_or_else(|_| "[]".to_string());
@@ -210,6 +272,9 @@ pub fn run() {
})
.invoke_handler(tauri::generate_handler![
initialize_app,
get_settings,
save_settings,
sync_essentials,
get_essentials,
get_all_software,
get_updates,