From 893fb9d4be0c7365894010431e634c79d55a4d30 Mon Sep 17 00:00:00 2001 From: abose Date: Thu, 22 Feb 2024 18:44:19 +0530 Subject: [PATCH] chore: move mac to our own updater impl --- .../features/TaskManager-API.md | 13 ++- src-node/download-file.js | 93 ++++++++++++++++--- src/extensionsIntegrated/appUpdater/main.js | 17 +++- src/tauri-updater.html | 22 +---- 4 files changed, 110 insertions(+), 35 deletions(-) diff --git a/docs/generatedApiDocs/features/TaskManager-API.md b/docs/generatedApiDocs/features/TaskManager-API.md index 0c90ae7abc..3146434f08 100644 --- a/docs/generatedApiDocs/features/TaskManager-API.md +++ b/docs/generatedApiDocs/features/TaskManager-API.md @@ -6,6 +6,15 @@ TaskManager module deals with managing long running tasks in phcode. It handles bar where the user can see all running tasks, monitor its progress and close/pause the execution of the task if supported by the task. +## renderSpinnerIcon + +determines what the spinner icon to show(green-for success), red-fail, blue normal based on the active +tasks in list and renders. IF the active tasks has already been notified, it wont notify again. + +### Parameters + +* `newTaskAdded` + ## TaskObject Type: [Object][1] @@ -21,8 +30,8 @@ Type: [Object][1] * `getProgressPercent` **function (): [number][3]** Returns the task's current progress percentage. * `setFailed` **function (): void** Marks the task as failed. * `isFailed` **function (): [boolean][4]** Returns true if the task is marked as failed. -* `setSucceeded` **function (): void** Marks the task as succeeded. -* `isSucceeded` **function (): [boolean][4]** Returns true if the task is marked as succeeded. +* `setSucceded` **function (): void** Marks the task as succeeded. +* `isSucceded` **function (): [boolean][4]** Returns true if the task is marked as succeeded. * `showStopIcon` **function ([string][2]): void** Shows the stop icon with an optional tooltip message. * `hideStopIcon` **function (): void** Hides the stop icon. * `showPlayIcon` **function ([string][2]): void** Shows the play icon with an optional tooltip message. diff --git a/src-node/download-file.js b/src-node/download-file.js index 602184db2b..bc5009ea12 100644 --- a/src-node/download-file.js +++ b/src-node/download-file.js @@ -2,6 +2,8 @@ const { pipeline } = require('stream/promises'); const { Transform } = require('stream'); const fs = require('fs'); const path = require('path'); +const os = require('os'); +const { exec } = require('child_process'); const args = process.argv.slice(2); // Skip the first two elements const {downloadURL, appdataDir} = JSON.parse(args[0]); @@ -14,6 +16,7 @@ const EVENT_INSTALL_PATH= "InstallerPath:"; const fileName = path.basename(new URL(downloadURL).pathname); const installerFolder = path.join(appdataDir, 'installer'); const savePath = path.join(appdataDir, 'installer', fileName); +let extractPath; async function getFileSize(url) { try { @@ -73,6 +76,60 @@ async function downloadFile(url, outputPath) { console.log(`File has been downloaded and saved to ${outputPath}`); } +/** + * Extracts a .tar.gz file using the tar CLI utility available on macOS/linux. + * + * @param {string} filePath - The path to the .tar.gz file. + * @param {string} absoluteExtractPath - The directory to extract the files into. + */ +function extractTar(filePath, absoluteExtractPath) { + return new Promise((resolve, reject)=>{ + const command = `tar -xzf "${filePath}" -C "${absoluteExtractPath}"`; + + exec(command, (error, stdout, stderr) => { + if (error) { + console.error(`Extraction error: ${error.message}`); + reject(error.message); + return; + } + if (stderr) { + console.error(`Extraction stderr: ${stderr}`); + reject(stderr); + return; + } + console.log(`Extraction completed to ${absoluteExtractPath}`); + resolve(); + }); + }); +} + +function removeQuarantineAttributeIfMac(extractPath) { + return new Promise((resolve)=>{ + if (os.platform() === 'darwin') { + const command = `xattr -rd com.apple.quarantine "${extractPath}"`; + + exec(command, (error, stdout, stderr) => { + // we always resolve as the user will be promted by macos if this fails here. + if (error) { + console.error(`Error removing quarantine attribute: ${error.message}`); + resolve(); + return; + } + if (stderr) { + console.error(`Error output: ${stderr}`); + resolve(); + return; + } + console.log(`Quarantine attribute removed successfully for ${extractPath}`); + resolve(); + }); + } else { + console.log("Platform is not macOS, no need to remove quarantine attribute."); + resolve(); + } + }); +} + async function downloadFileIfNeeded() { try { @@ -88,17 +145,27 @@ async function downloadFileIfNeeded() { console.log('File already downloaded and complete.'); const totalSize = Math.floor(totalBytes/1024/1024); console.log(`${EVENT_PROGRESS}${100}:${totalSize}`); - return; + } else { + // if we are here, then it is a fresh installer download or there is a partial corrupt download or + // a new version installer has to be downloaded while the old outdated installer exists. + // we have to clean the installerFolder. + await fs.promises.rm(installerFolder, { recursive: true, force: true }); + fs.mkdirSync(installerFolder, { recursive: true }); + console.log(`Downloading installer to ${savePath}...`); + await downloadFile(downloadURL, savePath); } - - // if we are here, then it is a fresh installer download or there is a partial corrupt download or - // a new version installer has to be downloaded while the old outdated installer exists. - // we have to clean the installerFolder. - await fs.promises.rm(installerFolder, { recursive: true, force: true }); - fs.mkdirSync(installerFolder, { recursive: true }); - console.log('Downloading installer...'); - await downloadFile(downloadURL, savePath); - + extractPath = path.join(appdataDir, 'installer', "extracted"); + await fs.promises.rm(extractPath, { recursive: true, force: true }); + fs.mkdirSync(extractPath, { recursive: true }); + if(savePath.endsWith(".tar.gz")){ + await extractTar(savePath, extractPath); + } + const dirContents = fs.readdirSync(extractPath); + console.log("extracted dir contents: ", dirContents); + if(dirContents.length === 1){ + extractPath = path.join(extractPath, dirContents[0]); + } + await removeQuarantineAttributeIfMac(extractPath); } catch (error) { console.error('An error occurred:', error); } @@ -106,6 +173,10 @@ async function downloadFileIfNeeded() { downloadFileIfNeeded() .then(()=>{ - console.log(`${EVENT_INSTALL_PATH}${savePath}`); // do not change this name + if(extractPath){ + console.log(`${EVENT_INSTALL_PATH}${extractPath}`); + return; + } + console.log(`${EVENT_INSTALL_PATH}${savePath}`); }) .catch(()=>process.exit(1)); diff --git a/src/extensionsIntegrated/appUpdater/main.js b/src/extensionsIntegrated/appUpdater/main.js index 2687e9c2be..6f646a25d7 100644 --- a/src/extensionsIntegrated/appUpdater/main.js +++ b/src/extensionsIntegrated/appUpdater/main.js @@ -137,6 +137,9 @@ define(function (require, exports, module) { updatePlatform: updatePlatformKey }; try{ + if(!updaterWindow){ + updaterWindow = window.__TAURI__.window.WebviewWindow.getByLabel(TAURI_UPDATER_WINDOW_LABEL); + } const updateMetadata = await fetchJSON(brackets.config.app_update_url); const phoenixBinaryVersion = await NodeUtils.getPhoenixBinaryVersion(); const phoenixLoadedAppVersion = Phoenix.metadata.apiVersion; @@ -150,7 +153,11 @@ define(function (require, exports, module) { updateDetails.downloadURL = updateMetadata.platforms[updatePlatformKey].url; } } else if(semver.eq(updateMetadata.version, phoenixBinaryVersion) && - !semver.eq(phoenixLoadedAppVersion, phoenixBinaryVersion)){ + !semver.eq(phoenixLoadedAppVersion, phoenixBinaryVersion) && updaterWindow){ + // the updaterWindow check is here so that it only makes sense to show restart dialog if the update + // was actually done. We have a version number mismatch of 0.0.1 between phoenix-desktop and phoenix + // repo, and that means that this can get triggered on statup on development builds. Wont happen in + // actual pipeline generated build tho. console.log("Updates applied, waiting for app restart: ", phoenixBinaryVersion, phoenixLoadedAppVersion); updateDetails.updatePendingRestart = true; PreferencesManager.setViewState(KEY_UPDATE_AVAILABLE, true); @@ -296,14 +303,16 @@ define(function (require, exports, module) { updateTask.setTitle(Strings.UPDATE_DONE); updateTask.setMessage(Strings.UPDATE_RESTART); Dialogs.showInfoDialog(Strings.UPDATE_READY_RESTART_TITLE, Strings.UPDATE_READY_RESTART_MESSAGE); - } else if(data === UPDATE_STATUS.INSTALLER_DOWNLOADED && !updateInstalledDialogShown){ - updateInstalledDialogShown = true; + } else if(data === UPDATE_STATUS.INSTALLER_DOWNLOADED){ Metrics.countEvent(Metrics.EVENT_TYPE.UPDATES, 'downloaded', Phoenix.platform); updatePendingRestart = true; updateTask.setSucceded(); updateTask.setTitle(Strings.UPDATE_DONE); updateTask.setMessage(Strings.UPDATE_RESTART_INSTALL); - Dialogs.showInfoDialog(Strings.UPDATE_READY_RESTART_TITLE, Strings.UPDATE_READY_RESTART_INSTALL_MESSAGE); + if(!updateInstalledDialogShown){ + Dialogs.showInfoDialog(Strings.UPDATE_READY_RESTART_TITLE, Strings.UPDATE_READY_RESTART_INSTALL_MESSAGE); + updateInstalledDialogShown = true; + } _sendUpdateCommand(UPDATE_COMMANDS.GET_INSTALLER_LOCATION); } else if(data === UPDATE_STATUS.DOWNLOADING){ updateTask.setMessage(Strings.UPDATE_DOWNLOADING); diff --git a/src/tauri-updater.html b/src/tauri-updater.html index 0ed2ba394d..cdbc8d5309 100644 --- a/src/tauri-updater.html +++ b/src/tauri-updater.html @@ -41,18 +41,6 @@ } sendUpdateEvent(UPDATE_EVENT.STATUS, status); - async function updateMacOS() { - try { - await __TAURI__.updater.installUpdate(); - status = UPDATE_STATUS.INSTALLED; - sendUpdateEvent(UPDATE_EVENT.STATUS, UPDATE_STATUS.INSTALLED); - } catch (e) { - status = UPDATE_STATUS.FAILED; - sendUpdateEvent(UPDATE_EVENT.LOG_ERROR, e.stack); - sendUpdateEvent(UPDATE_EVENT.STATUS, UPDATE_STATUS.FAILED); - } - } - function getQueryStringParam(paramName) { const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); @@ -86,7 +74,7 @@ } } - async function updateWindows() { + async function downloadUpdate() { const EVENT_PROGRESS= "progress:"; const EVENT_INSTALL_PATH= "InstallerPath:"; const downloadURL = decodeURIComponent(getQueryStringParam('downloadURL')); @@ -134,12 +122,10 @@ async function update() { const platform = await __TAURI__.os.platform(); - if(platform === 'darwin'){ - await updateMacOS(); - } else if(platform === 'linux'){ + if(platform === 'linux'){ await updateLinux(); - } else if(platform === 'win32'){ - await updateWindows(); + } else if(platform === 'win32' || platform === 'darwin'){ + await downloadUpdate(); } else { status = UPDATE_STATUS.FAILED_UNKNOWN_OS; sendUpdateEvent(UPDATE_EVENT.STATUS, UPDATE_STATUS.FAILED_UNKNOWN_OS);