support custom manifest

This commit is contained in:
Julian Freeman
2026-03-30 22:31:49 -04:00
parent 5717b94c90
commit a7b5955540
4 changed files with 62 additions and 25 deletions

View File

@@ -26,6 +26,8 @@ pub struct EssentialsRepo {
pub struct InstallTask { pub struct InstallTask {
pub id: String, pub id: String,
pub version: Option<String>, pub version: Option<String>,
pub use_manifest: bool,
pub manifest_url: Option<String>,
} }
impl Default for AppSettings { impl Default for AppSettings {
@@ -165,8 +167,14 @@ async fn get_updates(app: AppHandle) -> Vec<Software> {
} }
#[tauri::command] #[tauri::command]
async fn install_software(id: String, version: Option<String>, state: State<'_, AppState>) -> Result<(), String> { async fn install_software(
state.install_tx.send(InstallTask { id, version }).await.map_err(|e| e.to_string()) id: String,
version: Option<String>,
use_manifest: bool,
manifest_url: Option<String>,
state: State<'_, AppState>
) -> Result<(), String> {
state.install_tx.send(InstallTask { id, version, use_manifest, manifest_url }).await.map_err(|e| e.to_string())
} }
#[tauri::command] #[tauri::command]
@@ -196,6 +204,8 @@ pub fn run() {
while let Some(task) = rx.recv().await { while let Some(task) = rx.recv().await {
let id = task.id; let id = task.id;
let version = task.version; let version = task.version;
let use_manifest = task.use_manifest;
let manifest_url = task.manifest_url;
let log_id = format!("install-{}", id); let log_id = format!("install-{}", id);
let _ = handle.emit("install-status", InstallProgress { let _ = handle.emit("install-status", InstallProgress {
@@ -204,31 +214,44 @@ pub fn run() {
progress: 0.0, progress: 0.0,
}); });
let display_cmd = match &version { let mut args = vec!["install".to_string()];
Some(v) => format!("Winget Install: {} (v{})", id, v), let display_cmd: String;
None => format!("Winget Install: {}", id),
if use_manifest && manifest_url.is_some() {
let url = manifest_url.unwrap();
args.push("--manifest".to_string());
args.push(url.clone());
display_cmd = format!("Winget Install (Manifest): {} from {}", id, url);
} else {
args.push("--id".to_string());
args.push(id.clone());
args.push("-e".to_string());
if let Some(v) = &version {
if !v.is_empty() {
args.push("--version".to_string());
args.push(v.clone());
}
}
display_cmd = match &version {
Some(v) if !v.is_empty() => format!("Winget Install: {} (v{})", id, v),
_ => format!("Winget Install: {}", id),
}; };
emit_log(&handle, &log_id, &display_cmd, "Starting...", "info"); }
let id_for_cmd = id.clone(); // 共同的静默与协议参数
let h = handle.clone(); args.extend([
let mut args = vec![
"install".to_string(),
"--id".to_string(), id_for_cmd.clone(),
"-e".to_string(),
"--silent".to_string(), "--silent".to_string(),
"--accept-package-agreements".to_string(), "--accept-package-agreements".to_string(),
"--accept-source-agreements".to_string(), "--accept-source-agreements".to_string(),
"--disable-interactivity".to_string(), "--disable-interactivity".to_string(),
]; ]);
if let Some(v) = version { emit_log(&handle, &log_id, &display_cmd, "Starting...", "info");
if !v.is_empty() {
args.push("--version".to_string()); let id_for_progress = id.clone();
args.push(v); let h = handle.clone();
}
}
let child = Command::new("winget") let child = Command::new("winget")
.args(&args) .args(&args)
@@ -253,7 +276,7 @@ pub fn run() {
if let Some(caps) = perc_re.captures(clean_line) { if let Some(caps) = perc_re.captures(clean_line) {
if let Ok(p_val) = caps[1].parse::<f32>() { if let Ok(p_val) = caps[1].parse::<f32>() {
let _ = h.emit("install-status", InstallProgress { let _ = h.emit("install-status", InstallProgress {
id: id_for_cmd.clone(), id: id_for_progress.clone(),
status: "installing".to_string(), status: "installing".to_string(),
progress: p_val / 100.0, progress: p_val / 100.0,
}); });
@@ -265,7 +288,7 @@ pub fn run() {
let total = caps[2].parse::<f32>().unwrap_or(1.0); let total = caps[2].parse::<f32>().unwrap_or(1.0);
if total > 0.0 { if total > 0.0 {
let _ = h.emit("install-status", InstallProgress { let _ = h.emit("install-status", InstallProgress {
id: id_for_cmd.clone(), id: id_for_progress.clone(),
status: "installing".to_string(), status: "installing".to_string(),
progress: (current / total).min(1.0), progress: (current / total).min(1.0),
}); });

View File

@@ -16,10 +16,14 @@ pub struct Software {
pub status: String, // "idle", "pending", "installing", "success", "error" pub status: String, // "idle", "pending", "installing", "success", "error"
#[serde(default = "default_progress")] #[serde(default = "default_progress")]
pub progress: f32, pub progress: f32,
#[serde(default = "default_false")]
pub use_manifest: bool,
pub manifest_url: Option<String>,
} }
fn default_status() -> String { "idle".to_string() } fn default_status() -> String { "idle".to_string() }
fn default_progress() -> f32 { 0.0 } fn default_progress() -> f32 { 0.0 }
fn default_false() -> bool { false }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
@@ -276,5 +280,7 @@ fn map_package(p: WingetPackage) -> Software {
icon_url: p.icon_url, icon_url: p.icon_url,
status: "idle".to_string(), status: "idle".to_string(),
progress: 0.0, progress: 0.0,
use_manifest: false,
manifest_url: None,
} }
} }

View File

@@ -119,10 +119,13 @@ const props = defineProps<{
name: string; name: string;
description?: string; description?: string;
version?: string; version?: string;
recommended_version?: string;
available_version?: string; available_version?: string;
icon_url?: string; icon_url?: string;
status: string; status: string;
progress: number; progress: number;
use_manifest?: boolean;
manifest_url?: string;
}, },
actionLabel?: string, actionLabel?: string,
selectable?: boolean, selectable?: boolean,
@@ -139,7 +142,7 @@ const displayProgress = computed(() => {
const isInstalled = computed(() => { const isInstalled = computed(() => {
return props.software.status === 'installed' || return props.software.status === 'installed' ||
(props.software.status === 'idle' && props.actionLabel === '更新') || (props.software.status === 'idle' && props.actionLabel === '更新') ||
(props.software.status === 'idle' && !props.actionLabel && props.software.installed_version); (props.software.status === 'idle' && !props.actionLabel && props.software.version);
}); });
const isVersionLower = (current: string | undefined | null, target: string | undefined | null) => { const isVersionLower = (current: string | undefined | null, target: string | undefined | null) => {

View File

@@ -194,7 +194,12 @@ export const useSoftwareStore = defineStore('software', {
const software = this.findSoftware(id) const software = this.findSoftware(id)
if (software) { if (software) {
software.status = 'pending'; software.status = 'pending';
await invoke('install_software', { id, version: software.version }) await invoke('install_software', {
id,
version: software.version,
use_manifest: software.use_manifest,
manifest_url: software.manifest_url
})
} }
}, },
findSoftware(id: string) { findSoftware(id: string) {