diff --git a/main.js b/main.js index 45ef465..76352e8 100644 --- a/main.js +++ b/main.js @@ -276,14 +276,14 @@ async function start() { if (release && release.installer && process.platform === 'win32' && process.env.VYLA_DEV !== '1') { const appVersion = process.env.VYLA_APP_VERSION || '0.0.0'; const tried = updateClient.readCache().selfUpdate || {}; - const recentlyTried = tried.version === release.installer.version && Date.now() - (tried.at || 0) < 24 * 60 * 60 * 1000; + const recentlyTried = tried.version === release.installer.version && Date.now() - (tried.at || 0) < 10 * 60 * 1000; if (isNewer(release.installer.version, appVersion) && !recentlyTried) { try { const dest = path.join(APPDATA_DIR, `vyla-installer-${normalizeVer(release.installer.version)}.exe`); sendToTauri('status', { message: 'Downloading launcher update...' }); await downloadVerified('launcher', release.installer, dest); updateClient.writeCache({ selfUpdate: { version: release.installer.version, at: Date.now() } }); - sendToTauri('status', { message: 'Installing launcher update...' }); + sendToTauri('status', { message: 'Removing old version and installing the latest...' }); sendToTauri('self-update', { path: dest }); await sleep(30000); sendToTauri('notice', { message: 'Launcher update did not start. Continuing on the current version.' }); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 503cbcb..73a2456 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -564,6 +564,87 @@ const LINK_HANDLER_SCRIPT: &str = r##" })(); "##; +#[cfg(windows)] +const REINSTALL_SCRIPT: &str = r##" +param([int]$ProcId,[string]$Installer,[string]$InstallDir,[string]$ExePath) +$ErrorActionPreference = 'SilentlyContinue' +function Log($m) { Add-Content -LiteralPath (Join-Path $env:TEMP 'vyla-reinstall.log') -Value "$(Get-Date -Format s) $m" } +Log "start pid=$ProcId installer=$Installer dir=$InstallDir exe=$ExePath" +$setup = Join-Path $env:TEMP 'vyla-setup.exe' +Copy-Item -LiteralPath $Installer -Destination $setup -Force +if (-not (Test-Path -LiteralPath $setup)) { exit 1 } +Wait-Process -Id $ProcId -Timeout 30 +Get-Process | Where-Object { $_.Path -and $_.Path.StartsWith($InstallDir, [System.StringComparison]::OrdinalIgnoreCase) } | Stop-Process -Force +Start-Sleep -Seconds 2 +$un = Join-Path $InstallDir 'uninstall.exe' +if (Test-Path -LiteralPath $un) { + Start-Process -FilePath $un -ArgumentList '/S', "_?=$InstallDir" -Wait +} +$targets = @($InstallDir, "$env:APPDATA\vyla-home", "$env:APPDATA\com.vyla.home", "$env:LOCALAPPDATA\com.vyla.home", "$env:LOCALAPPDATA\vyla-home") +foreach ($t in $targets) { + for ($i = 0; $i -lt 5; $i++) { + if (-not (Test-Path -LiteralPath $t)) { break } + Remove-Item -LiteralPath $t -Recurse -Force + if (Test-Path -LiteralPath $t) { Start-Sleep -Seconds 1 } + } +} +Log "uninstall and cleanup done, installing" +Start-Process -FilePath $setup -ArgumentList '/S', "/D=$InstallDir" -Wait +Log "installer finished" +Remove-Item -LiteralPath $setup -Force +$target = $null +for ($i = 0; $i -lt 40; $i++) { + if (Test-Path -LiteralPath $ExePath) { $target = $ExePath; break } + $found = Get-ChildItem -LiteralPath $InstallDir -Filter '*.exe' | Where-Object { $_.Name -notmatch 'uninstall|node' } | Select-Object -First 1 + if ($found) { $target = $found.FullName; break } + Start-Sleep -Milliseconds 500 +} +Log "launch target: $target" +if ($target) { + Start-Process -FilePath $target -WorkingDirectory (Split-Path -Parent $target) + Start-Sleep -Seconds 4 + if (-not (Get-Process | Where-Object { $_.Path -eq $target })) { + Log "not running, retrying via explorer" + Start-Process -FilePath 'explorer.exe' -ArgumentList "`"$target`"" + } +} +Log "done" +"##; + +#[cfg(windows)] +fn run_clean_reinstall(installer: &std::path::Path) -> bool { + use std::os::windows::process::CommandExt; + let exe = match std::env::current_exe() { + Ok(e) => e, + Err(_) => return false, + }; + let dir = match exe.parent() { + Some(d) => d.to_path_buf(), + None => return false, + }; + if !dir.join("uninstall.exe").exists() { + return false; + } + let script = std::env::temp_dir().join("vyla-reinstall.ps1"); + if std::fs::write(&script, REINSTALL_SCRIPT).is_err() { + return false; + } + Command::new("powershell") + .args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-File"]) + .arg(&script) + .arg("-ProcId") + .arg(std::process::id().to_string()) + .arg("-Installer") + .arg(installer) + .arg("-InstallDir") + .arg(clean_path(&dir)) + .arg("-ExePath") + .arg(clean_path(&exe)) + .creation_flags(0x08000000 | 0x00000200) + .spawn() + .is_ok() +} + #[tauri::command] fn win_minimize(window: tauri::Window) -> Result<(), String> { window.minimize().map_err(|e| e.to_string()) @@ -949,21 +1030,18 @@ fn main() { && name.ends_with(".exe") && p.exists() { - let state: State = w.state(); - if let Ok(mut lock) = state.0.lock() { - if let Some(mut child) = lock.take() { - kill_child_process(&mut child); - } - }; - let mut installer = Command::new(&p); - installer.args(["/S", "/UPDATE"]); #[cfg(windows)] { - use std::os::windows::process::CommandExt; - installer.creation_flags(0x00000008 | 0x00000200); + if run_clean_reinstall(&p) { + let state: State = w.state(); + if let Ok(mut lock) = state.0.lock() { + if let Some(mut child) = lock.take() { + kill_child_process(&mut child); + } + }; + w.app_handle().exit(0); + } } - let _ = installer.spawn(); - w.app_handle().exit(0); } } } diff --git a/update-client.js b/update-client.js index be1bdac..215bbd0 100644 --- a/update-client.js +++ b/update-client.js @@ -358,7 +358,8 @@ class UpdateClient { } return 0; }; - const release = releases.slice().sort((a, b) => cmp(b.tag_name, a.tag_name))[0]; + const ordered = releases.slice().sort((a, b) => cmp(b.tag_name, a.tag_name)); + const release = ordered[0]; const links = release.assets && release.assets.links ? release.assets.links : []; const manifestAsset = links.find((a) => a.name === 'manifest.json'); @@ -403,6 +404,10 @@ class UpdateClient { } } + if (!installer) { + installer = await this.findInstaller(ordered, release.tag_name); + } + const out = { version: release.tag_name, sha256: manifest.sha256 || null, @@ -414,6 +419,28 @@ class UpdateClient { return out; } + async findInstaller(ordered, skipTag) { + for (const r of ordered.slice(0, 6)) { + if (r.tag_name === skipTag) continue; + const rl = r.assets && r.assets.links ? r.assets.links : []; + const ma = rl.find((a) => a.name === 'manifest.json'); + if (!ma) continue; + try { + const m = await this.fetchAssetJson(ma.url); + if (!m.installer || !verifyManifest(m)) continue; + const link = rl.find((a) => a.name === m.installer.file); + if (!link) continue; + return { + url: link.url, + sha256: m.installer.sha256, + size: m.installer.size, + version: m.installer.version + }; + } catch (e) { } + } + return null; + } + async downloadUpdate(release, destPath, onProgress) { await this.downloadFile(release.url, destPath, onProgress); return destPath;