Eigener Installer mit Standard- und Portable-Modus, Updater fuer beide Varianten
This commit is contained in:
1 parent
df88f8abaa
commit
92d02d0008
6 files changed
+508
-36
No files matched your search
@@ -5,14 +5,22 @@ use std::path::PathBuf;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{PhysicalPosition, PhysicalSize};
|
||||
|
||||
fn exe_dir() -> PathBuf {
|
||||
pub fn exe_dir() -> PathBuf {
|
||||
std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.parent().map(PathBuf::from))
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
}
|
||||
|
||||
/// Der Installer legt bei der portablen Installation die Marker-Datei `portable.flag` neben die EXE.
|
||||
pub fn is_portable() -> bool {
|
||||
exe_dir().join("portable.flag").is_file()
|
||||
}
|
||||
|
||||
pub fn data_dir() -> PathBuf {
|
||||
if is_portable() {
|
||||
return exe_dir().join("data");
|
||||
}
|
||||
std::env::var_os("APPDATA")
|
||||
.map(|d| PathBuf::from(d).join("EuerChat"))
|
||||
.unwrap_or_else(exe_dir)
|
||||
|
||||
+87
-16
@@ -366,28 +366,65 @@ async fn download_to_file(
|
||||
// Installation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
enum InstallKind {
|
||||
/// Mit dem Standard-Installer installiert (uninstall.exe neben der EXE), meist in Program Files.
|
||||
Standard,
|
||||
/// Portable Installation (portable.flag neben der EXE).
|
||||
Portable,
|
||||
/// Lose EXE ohne Installation, z. B. aus dem Build-Ordner.
|
||||
Loose,
|
||||
}
|
||||
|
||||
fn detect_install_kind(dir: &Path) -> InstallKind {
|
||||
if dir.join("portable.flag").is_file() {
|
||||
InstallKind::Portable
|
||||
} else if dir.join("uninstall.exe").is_file() {
|
||||
InstallKind::Standard
|
||||
} else {
|
||||
InstallKind::Loose
|
||||
}
|
||||
}
|
||||
|
||||
/// PowerShell-Skript: wartet auf das Ende dieser App, installiert still und startet EuerChat neu.
|
||||
fn build_update_script(pid: u32, installer: &Path, current_exe: &Path, kind: InstallKind) -> String {
|
||||
let quote = |p: &Path| p.to_string_lossy().replace('\'', "''");
|
||||
let dir = current_exe.parent().map(quote).unwrap_or_default();
|
||||
|
||||
// Standard-Installationen liegen meist in Program Files und brauchen Administratorrechte (UAC).
|
||||
let (args, elevate) = match kind {
|
||||
InstallKind::Standard => (format!("/S /UPDATE /MODE=standard /D={dir}"), true),
|
||||
InstallKind::Portable => (format!("/S /UPDATE /MODE=portable /D={dir}"), false),
|
||||
InstallKind::Loose => ("/S /UPDATE /MODE=standard".to_string(), true),
|
||||
};
|
||||
let verb = if elevate { " -Verb RunAs" } else { "" };
|
||||
let relaunch = match kind {
|
||||
InstallKind::Loose => format!(
|
||||
"$t = Join-Path $env:ProgramFiles 'EuerChat\\euer-chat.exe'; \
|
||||
if (Test-Path $t) {{ Start-Process -FilePath $t }} else {{ Start-Process -FilePath '{}' }}",
|
||||
quote(current_exe)
|
||||
),
|
||||
_ => format!("Start-Process -FilePath '{}'", quote(current_exe)),
|
||||
};
|
||||
|
||||
// Bricht der Nutzer die UAC-Abfrage ab, wird trotzdem die bisherige Version neu gestartet.
|
||||
format!(
|
||||
"Wait-Process -Id {pid} -Timeout 30 -ErrorAction SilentlyContinue; \
|
||||
try {{ Start-Process -FilePath '{installer}' -ArgumentList '{args}'{verb} -Wait }} catch {{}}; \
|
||||
{relaunch}",
|
||||
installer = quote(installer),
|
||||
)
|
||||
}
|
||||
|
||||
/// Startet den Installer erst, wenn diese App beendet ist, und danach EuerChat neu.
|
||||
fn spawn_installer_after_exit(installer: &Path) -> std::io::Result<()> {
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
let quote = |p: &Path| p.to_string_lossy().replace('\'', "''");
|
||||
let current = std::env::current_exe()?;
|
||||
let installed_here = current
|
||||
let kind = current
|
||||
.parent()
|
||||
.is_some_and(|dir| dir.join("uninstall.exe").exists());
|
||||
|
||||
// Eine portable EXE wird per Installer in %LOCALAPPDATA%\EuerChat aktualisiert; dort neu starten.
|
||||
let script = format!(
|
||||
"Wait-Process -Id {pid} -Timeout 30 -ErrorAction SilentlyContinue; \
|
||||
Start-Process -FilePath '{installer}' -ArgumentList '/S','/UPDATE' -Wait; \
|
||||
$installed = Join-Path $env:LOCALAPPDATA 'EuerChat\\euer-chat.exe'; \
|
||||
if (-not {installed_here} -and (Test-Path $installed)) {{ Start-Process -FilePath $installed }} \
|
||||
else {{ Start-Process -FilePath '{current}' }}",
|
||||
pid = std::process::id(),
|
||||
installer = quote(installer),
|
||||
installed_here = if installed_here { "$true" } else { "$false" },
|
||||
current = quote(¤t),
|
||||
);
|
||||
.map_or(InstallKind::Loose, detect_install_kind);
|
||||
let script = build_update_script(std::process::id(), installer, ¤t, kind);
|
||||
|
||||
std::process::Command::new("powershell.exe")
|
||||
.args(["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", &script])
|
||||
@@ -529,6 +566,40 @@ mod tests {
|
||||
assert!(parse_expected_checksum(&other, "x.exe").unwrap_err().contains("x.exe"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn install_kind_detection() {
|
||||
let dir = std::env::temp_dir().join(format!("euerchat-kind-test-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
assert_eq!(detect_install_kind(&dir), InstallKind::Loose);
|
||||
std::fs::write(dir.join("uninstall.exe"), b"x").unwrap();
|
||||
assert_eq!(detect_install_kind(&dir), InstallKind::Standard);
|
||||
std::fs::write(dir.join("portable.flag"), b"x").unwrap();
|
||||
assert_eq!(detect_install_kind(&dir), InstallKind::Portable);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_script_per_install_kind() {
|
||||
let installer = Path::new(r"C:\Users\O'Brien\AppData\Roaming\EuerChat\updates\EuerChat_0.2.0_x64-setup.exe");
|
||||
let exe = Path::new(r"C:\Program Files\EuerChat\euer-chat.exe");
|
||||
|
||||
let standard = build_update_script(42, installer, exe, InstallKind::Standard);
|
||||
assert!(standard.contains("Wait-Process -Id 42"));
|
||||
assert!(standard.contains("/MODE=standard /D=C:\\Program Files\\EuerChat'"));
|
||||
assert!(standard.contains("-Verb RunAs"));
|
||||
assert!(standard.contains("O''Brien"), "Apostrophe müssen verdoppelt werden");
|
||||
assert!(standard.contains("Start-Process -FilePath 'C:\\Program Files\\EuerChat\\euer-chat.exe'"));
|
||||
|
||||
let portable = build_update_script(1, installer, Path::new(r"E:\Tools\EuerChat\euer-chat.exe"), InstallKind::Portable);
|
||||
assert!(portable.contains("/MODE=portable /D=E:\\Tools\\EuerChat'"));
|
||||
assert!(!portable.contains("RunAs"), "portable Updates brauchen keine Administratorrechte");
|
||||
|
||||
let loose = build_update_script(1, installer, Path::new(r"D:\build\euer-chat.exe"), InstallKind::Loose);
|
||||
assert!(loose.contains("/MODE=standard'"));
|
||||
assert!(!loose.contains("/D="));
|
||||
assert!(loose.contains("$env:ProgramFiles"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_installer_files() {
|
||||
assert!(is_managed_installer_file("EuerChat_0.2.0_x64-setup.exe"));
|
||||
|
||||
@@ -39,16 +39,7 @@
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": ["nsis"],
|
||||
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/icon.ico"],
|
||||
"shortDescription": "EuerChat - IRC-Chat und Livestream-Client fuer euer.tv",
|
||||
"windows": {
|
||||
"nsis": {
|
||||
"installMode": "currentUser",
|
||||
"languages": ["German"],
|
||||
"displayLanguageSelector": false
|
||||
}
|
||||
}
|
||||
"active": false,
|
||||
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/icon.ico"]
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user