Skip to content

Commit 3b43d57

Browse files
committed
fix: mac updates will be extracted nad deleted on appquit to prevent multiple app listing
1 parent 73b5981 commit 3b43d57

File tree

2 files changed

+68
-59
lines changed

2 files changed

+68
-59
lines changed

src-node/installer/download-file.js

Lines changed: 8 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ const { pipeline } = require('stream/promises');
22
const { Transform } = require('stream');
33
const fs = require('fs');
44
const path = require('path');
5-
const os = require('os');
65
const { exec } = require('child_process');
76

87
const args = process.argv.slice(2); // Skip the first two elements
@@ -16,7 +15,7 @@ const EVENT_INSTALL_PATH= "InstallerPath,"; // its , here as separator as window
1615
const fileName = path.basename(new URL(downloadURL).pathname);
1716
const installerFolder = path.join(appdataDir, 'installer');
1817
const savePath = path.join(appdataDir, 'installer', fileName);
19-
let extractPath;
18+
let extractPath = null;
2019

2120
async function getFileSize(url) {
2221
try {
@@ -76,33 +75,6 @@ async function downloadFile(url, outputPath) {
7675
console.log(`File has been downloaded and saved to ${outputPath}`);
7776
}
7877

79-
/**
80-
* Extracts a .tar.gz file using the tar CLI utility available on macOS/linux.
81-
*
82-
* @param {string} filePath - The path to the .tar.gz file.
83-
* @param {string} absoluteExtractPath - The directory to extract the files into.
84-
*/
85-
function extractTar(filePath, absoluteExtractPath) {
86-
return new Promise((resolve, reject)=>{
87-
const command = `tar -xzf "${filePath}" -C "${absoluteExtractPath}"`;
88-
89-
exec(command, (error, stdout, stderr) => {
90-
if (error) {
91-
console.error(`Extraction error: ${error.message}`);
92-
reject(error.message);
93-
return;
94-
}
95-
if (stderr) {
96-
console.error(`Extraction stderr: ${stderr}`);
97-
reject(stderr);
98-
return;
99-
}
100-
console.log(`Extraction completed to ${absoluteExtractPath}`);
101-
resolve();
102-
});
103-
});
104-
}
105-
10678
/**
10779
* Extracts a ZIP file to the specified directory on Windows.
10880
*
@@ -130,33 +102,6 @@ function extractZipFileWindows(zipFilePath, absoluteExtractPath) {
130102
});
131103
}
132104

133-
function removeQuarantineAttributeIfMac(extractPath) {
134-
return new Promise((resolve)=>{
135-
if (os.platform() === 'darwin') {
136-
const command = `xattr -rd com.apple.quarantine "${extractPath}"`;
137-
138-
exec(command, (error, stdout, stderr) => {
139-
// we always resolve as the user will be promted by macos if this fails here.
140-
if (error) {
141-
console.error(`Error removing quarantine attribute: ${error.message}`);
142-
resolve();
143-
return;
144-
}
145-
if (stderr) {
146-
console.error(`Error output: ${stderr}`);
147-
resolve();
148-
return;
149-
}
150-
console.log(`Quarantine attribute removed successfully for ${extractPath}`);
151-
resolve();
152-
});
153-
} else {
154-
console.log("Platform is not macOS, no need to remove quarantine attribute.");
155-
resolve();
156-
}
157-
});
158-
}
159-
160105
async function downloadFileIfNeeded() {
161106
try {
162107

@@ -181,11 +126,17 @@ async function downloadFileIfNeeded() {
181126
console.log(`Downloading installer to ${savePath}...`);
182127
await downloadFile(downloadURL, savePath);
183128
}
129+
// extract path is assumed to be appdata/installer/extracted in phoenix side too,
130+
// if changing location, update there too.
184131
extractPath = path.join(appdataDir, 'installer', "extracted");
185132
await fs.promises.rm(extractPath, { recursive: true, force: true });
186133
fs.mkdirSync(extractPath, { recursive: true });
187134
if(savePath.endsWith(".tar.gz")){
188-
await extractTar(savePath, extractPath);
135+
// mac installer tar.gz will be extracted at phoenix side as if we extract it now, there will be
136+
// 2 `phoenix code.app` in the system, and mac will show both in the `open with` section in finder!
137+
// so we only extract the app just before install.
138+
extractPath = null;
139+
return;
189140
}
190141
if(savePath.endsWith(".zip")){
191142
await extractZipFileWindows(savePath, extractPath);
@@ -195,7 +146,6 @@ async function downloadFileIfNeeded() {
195146
if(dirContents.length === 1){
196147
extractPath = path.join(extractPath, dirContents[0]);
197148
}
198-
await removeQuarantineAttributeIfMac(extractPath);
199149
} catch (error) {
200150
console.error('An error occurred:', error);
201151
}

src/extensionsIntegrated/appUpdater/main.js

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
define(function (require, exports, module) {
2727
const AppInit = require("utils/AppInit"),
2828
Metrics = require("utils/Metrics"),
29+
FileSystem = require("filesystem/FileSystem"),
2930
Commands = require("command/Commands"),
3031
CommandManager = require("command/CommandManager"),
3132
Menus = require("command/Menus"),
@@ -334,13 +335,56 @@ define(function (require, exports, module) {
334335
return null;
335336
}
336337

338+
async function _extractMacInstaller() {
339+
// todo delete .app files already in the install folder
340+
const appdataDir = window._tauriBootVars.appLocalDir;
341+
let extractPlatformPath = path.join(appdataDir, 'installer', "extracted");
342+
// extract the .app file
343+
const extractCommand = new window.__TAURI__.shell
344+
.Command(`tar-unix`, ['-xzf', installerLocation, "-C", extractPlatformPath]);
345+
let result = await extractCommand.execute();
346+
if(result.code !== 0){
347+
console.error("Could not extract installer at", installerLocation, "to", extractPlatformPath);
348+
throw new Error("Could not extract installer at " + installerLocation + " to " + extractPlatformPath);
349+
}
350+
// remove the quarantine flag
351+
const removeAttrCommand = new window.__TAURI__.shell
352+
.Command(`mac-remove-quarantine`, ["-rd", "com.apple.quarantine", extractPlatformPath]);
353+
result = await removeAttrCommand.execute();
354+
if(result.code !== 0){
355+
console.error("Could not remove quarantine attribute for", extractPlatformPath, "ignoring...");
356+
// we can ignore this failure as the user will be asked for permission by os on clicking anyway.
357+
}
358+
// now get the .app path from extracted path
359+
const extractedVirtualPath = window.fs.getTauriVirtualPath(extractPlatformPath);
360+
let directory = FileSystem.getDirectoryForPath(extractedVirtualPath);
361+
const {entries} = await directory.getContentsAsync();
362+
if(entries.length !== 1){
363+
throw new Error("Could not resolve .app to update from extracted folder" + extractedVirtualPath);
364+
}
365+
installerLocation = window.fs.getTauriPlatformPath(entries[0].fullPath);
366+
}
367+
368+
function _cleanExtractedFolderSilent() {
369+
return new Promise(resolve=>{
370+
const appdataDir = window._tauriBootVars.appLocalDir;
371+
let extractPlatformPath = path.join(appdataDir, 'installer', "extracted");
372+
const extractedVirtualPath = window.fs.getTauriVirtualPath(extractPlatformPath);
373+
let directory = FileSystem.getDirectoryForPath(extractedVirtualPath);
374+
directory.unlinkAsync()
375+
.catch(console.error)
376+
.finally(resolve);
377+
});
378+
}
379+
337380
async function doMacUpdate() {
381+
await _extractMacInstaller(installerLocation);
338382
const currentAppPath = await getCurrentMacAppPath();
339383
if(!currentAppPath || !installerLocation || !currentAppPath.endsWith(".app") ||
340384
!installerLocation.endsWith(".app")){
341385
throw new Error("Cannot resolve .app location to copy.");
342386
}
343-
const removeCommand = new window.__TAURI__.shell
387+
let removeCommand = new window.__TAURI__.shell
344388
.Command(`recursive-rm-unix`, ['-r', currentAppPath]);
345389
let result = await removeCommand.execute();
346390
if(result.code !== 0){
@@ -353,6 +397,8 @@ define(function (require, exports, module) {
353397
if(result.code !== 0){
354398
throw new Error("Update script exit with non-0 exit code: " + result.code);
355399
}
400+
// now remove the original .app
401+
await _cleanExtractedFolderSilent();
356402
}
357403

358404
let installerLocation;
@@ -413,6 +459,19 @@ define(function (require, exports, module) {
413459
// app updates are only for desktop builds
414460
return;
415461
}
462+
if (brackets.platform === "mac") {
463+
// in mac, the `update.app.tar.gz` is downloaded, and only extracted on app quit.
464+
// we do this only in mac as the `.app` file is extracted only at app quit and deleted
465+
// and if we see the `extracted file` at app boot, it means the update was broken,and we clear
466+
// the updated folder. if not, the extracted app may be corrupt, or mac will show that app
467+
// too in the finder `open with` section.
468+
// in windows, the `setup.exe.zip` is downloaded and extracted to `setup.exe`. The exe is executed
469+
// only on app quit. so if we do this in windows, the extracted installer.exe will be
470+
// deleted on new widow create and the final update will fail if other windows were opened
471+
// after the installer was downloaded and extracted.
472+
// in Linux, it is an online installer, nothing is downloaded.
473+
_cleanExtractedFolderSilent();
474+
}
416475
updaterWindow = window.__TAURI__.window.WebviewWindow.getByLabel(TAURI_UPDATER_WINDOW_LABEL);
417476
window.__TAURI__.event.listen("updater-event", (receivedEvent)=> {
418477
console.log("received Event updater-event", receivedEvent);

0 commit comments

Comments
 (0)