diff --git a/package.json b/package.json index 816aee5..87188d3 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,10 @@ "uninstallerIcon": "static/icons/icon.ico", "installerHeaderIcon": "static/icons/icon.ico", "shortcutName": "Scoreko", - "useZip": false + "useZip": false, + "deleteAppDataOnUninstall": true, + "showInstDetails": "show", + "showUninstDetails": "show" }, "compression": "normal" }, diff --git a/src/main/updates/update-dialogs.ts b/src/main/updates/update-dialogs.ts index d448fae..48e7695 100644 --- a/src/main/updates/update-dialogs.ts +++ b/src/main/updates/update-dialogs.ts @@ -41,6 +41,22 @@ export async function askToInstallUpdate(update: ReleaseUpdate, parentWindow: Br return result.response === 0; } +export async function showDownloadFailedDialog( + update: ReleaseUpdate, + error: unknown, + parentWindow: BrowserWindow | null, +): Promise { + const errorMessage = error instanceof Error ? error.message : String(error); + await showMessageBox(parentWindow, { + type: "error", + title: "Error de descarga", + message: `No se pudo descargar la actualización para Scoreko ${update.version}.`, + detail: `Detalles del error: ${errorMessage}\n\nPor favor, comprueba tu conexión a internet e inténtalo de nuevo.`, + buttons: ["Aceptar"], + defaultId: 0, + }); +} + function showMessageBox( parentWindow: BrowserWindow | null, options: MessageBoxOptions, diff --git a/src/main/updates/update-download.ts b/src/main/updates/update-download.ts index 5e58d51..dd36419 100644 --- a/src/main/updates/update-download.ts +++ b/src/main/updates/update-download.ts @@ -23,6 +23,13 @@ export async function downloadInstaller(update: ReleaseUpdate, config: UpdateDow const targetPath = getSafeChildPath(downloadDirectory, safeFileName); const stagingPath = getSafeChildPath(downloadDirectory, `${safeFileName}.${process.pid}.${Date.now()}.download`); + if (fs.existsSync(targetPath)) { + const stats = fs.statSync(targetPath); + if (typeof update.installer.size === "number" && stats.size === update.installer.size) { + return targetPath; + } + } + fs.mkdirSync(downloadDirectory, { recursive: true }); fs.rmSync(stagingPath, { force: true }); diff --git a/src/main/updates/update-service.ts b/src/main/updates/update-service.ts index 5b74477..d350c71 100644 --- a/src/main/updates/update-service.ts +++ b/src/main/updates/update-service.ts @@ -1,7 +1,7 @@ import { app, BrowserWindow, shell } from "electron"; import { AppRuntimeConfig } from "../config/runtime-config"; -import { askToDownloadUpdate, askToInstallUpdate } from "./update-dialogs"; +import { askToDownloadUpdate, askToInstallUpdate, showDownloadFailedDialog } from "./update-dialogs"; import { loadUpdateSettings, UpdateSettings } from "./update-config"; import { downloadInstaller } from "./update-download"; import { buildReleaseUpdate, GiteaRelease, parseGiteaRelease } from "./update-schema"; @@ -76,10 +76,17 @@ async function checkForUpdates({ return; } - const installerPath = await downloadInstaller(update, { - tempDirectory: app.getPath("temp"), - allowInsecureHttp: protocolPolicy.allowInsecureHttp, - }); + let installerPath: string; + try { + installerPath = await downloadInstaller(update, { + tempDirectory: app.getPath("temp"), + allowInsecureHttp: protocolPolicy.allowInsecureHttp, + }); + } catch (error) { + log("Update installer download failed.", error); + await showDownloadFailedDialog(update, error, getParentWindow()); + return; + } const shouldInstall = await askToInstallUpdate(update, getParentWindow()); if (!shouldInstall) { await shell.showItemInFolder(installerPath); diff --git a/src/tests/update-download.test.ts b/src/tests/update-download.test.ts index 566b891..6259f45 100644 --- a/src/tests/update-download.test.ts +++ b/src/tests/update-download.test.ts @@ -56,3 +56,38 @@ test("downloadInstaller rejects insecure production download URLs", async () => /unsupported protocol/, ); }); + +test("downloadInstaller reuses existing file if size matches and does not download again", async () => { + const tempDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "scoreko-update-download-")); + const downloadDirectory = path.join(tempDirectory, "scoreko-updates"); + fs.mkdirSync(downloadDirectory, { recursive: true }); + + const installerPath = path.join(downloadDirectory, "Scoreko_setup_0.2.0.exe"); + fs.writeFileSync(installerPath, "cached-installer-bytes"); + const cachedSize = fs.statSync(installerPath).size; + + const previousFetch = globalThis.fetch; + globalThis.fetch = async () => { + throw new Error("Should not fetch when using cached file!"); + }; + + try { + const resultPath = await downloadInstaller( + { + version: "0.2.0", + title: "Scoreko 0.2.0", + installer: { + name: "Scoreko/setup:0.2.0.exe", + downloadUrl: "https://updates.local/Scoreko-setup-0.2.0.exe", + size: cachedSize, + }, + }, + { tempDirectory, allowInsecureHttp: false }, + ); + + assert.equal(resultPath, installerPath); + assert.equal(fs.readFileSync(resultPath, "utf8"), "cached-installer-bytes"); + } finally { + globalThis.fetch = previousFetch; + } +});