Skip to content

Commit ce8a614

Browse files
authored
Feature: Progress bar for download of Specter binary in Electron (#2257)
* adding electron-progressbar as dependency * progress bar for download of specterd
1 parent ffac658 commit ce8a614

File tree

4 files changed

+2155
-2036
lines changed

4 files changed

+2155
-2036
lines changed

pyinstaller/electron/main.js

Lines changed: 115 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ const { app, nativeTheme, nativeImage, BrowserWindow, Menu, Tray, screen, shell,
44
const path = require('path')
55
const fs = require('fs')
66
const request = require('request')
7+
const https = require('https')
78
const extract = require('extract-zip')
89
const defaultMenu = require('electron-default-menu');
10+
const ProgressBar = require('electron-progressbar')
911
const { spawn, exec } = require('child_process');
1012

1113
const helpers = require('./helpers')
@@ -54,14 +56,6 @@ if (isDev) {
5456
logger.info('Running the Electron app in dev mode.')
5557
}
5658

57-
logger.info(process.env)
58-
let appSettings = getAppSettings()
59-
60-
let dimensions = { widIth: 1500, height: 1000 };
61-
62-
const contextMenu = require('electron-context-menu');
63-
const { options } = require('request')
64-
6559
// Set the dock icon (MacOS and for development only)
6660
if (isMac && isDev) {
6761
const dockIcon = nativeImage.createFromPath(
@@ -70,6 +64,11 @@ if (isMac && isDev) {
7064
app.dock.setIcon(dockIcon)
7165
}
7266

67+
let appSettings = getAppSettings()
68+
let dimensions = { width: 1500, height: 1000 };
69+
70+
// Modify the context menu
71+
const contextMenu = require('electron-context-menu');
7372
contextMenu({
7473
menu: (actions) => [
7574
{
@@ -91,16 +90,102 @@ contextMenu({
9190
]
9291
});
9392

93+
// The standard quit item cannot be replaced / modified and it is not triggering the
94+
// before-quit event on MacOS if a child window is open
95+
const dockMenuWithforceQuit = Menu.buildFromTemplate([
96+
{
97+
label: 'Force Quit during download',
98+
click: () => {
99+
// If the progress bar exists, close it
100+
if (progressBar) {
101+
progressBar.close();
102+
}
103+
// Quit the app
104+
app.quit();
105+
}
106+
}
107+
]);
108+
109+
// Download function with progress bar
110+
let progressBar
94111
const download = (uri, filename, callback) => {
95-
request.head(uri, (err, res, body) => {
96-
logger.info('content-type:', res.headers['content-type'])
97-
logger.info('content-length:', res.headers['content-length'])
98-
if (res.statusCode != 404) {
99-
request(uri).pipe(fs.createWriteStream(filename)).on('close', callback)
100-
} else {
101-
callback(true)
112+
// HEAD request first
113+
request.head(uri, (err, res, body) => {
114+
if (res.statusCode != 404) {
115+
let receivedBytes = 0
116+
const totalBytes = res.headers['content-length']
117+
logger.info(`Total size to download: ${totalBytes}`)
118+
progressBar = new ProgressBar({
119+
indeterminate: false,
120+
abortOnError: true,
121+
text: 'Downloading the Specter binary from GitHub',
122+
detail: 'This can take several minutes depending on your Internet connection. Specter will start once the download is finished.',
123+
maxValue: totalBytes,
124+
browserWindow: {
125+
parent: mainWindow
126+
},
127+
style: {
128+
detail: {
129+
'margin-bottom': '12px'
130+
},
131+
bar: {
132+
'background-color': '#fff'
133+
},
134+
value: {
135+
'background-color': '#000'
136+
}
137+
}
138+
})
139+
140+
// Add Force Quit item during download for MacOS dock
141+
if (isMac) {
142+
app.dock.setMenu(dockMenuWithforceQuit);
102143
}
103-
})
144+
145+
progressBar.on('completed', () => {
146+
progressBar.close()
147+
// Remove the Force Quit dock item again for Mac
148+
if (isMac) {
149+
const updatedDockMenu = Menu.buildFromTemplate(
150+
dockMenuWithforceQuit.items.filter(item => item.label !== 'Force Quit during download')
151+
);
152+
app.dock.setMenu(updatedDockMenu);
153+
}
154+
});
155+
156+
progressBar.on('aborted', () => {
157+
logger.info('Download was aborted before it could finish.')
158+
progressBar = null
159+
});
160+
161+
// Loggin the download progress
162+
let lastLogTime = 0;
163+
const logInterval = 5000; // log every 5 seconds
164+
progressBar.on('progress', () => {
165+
const currentTime = Date.now();
166+
if (currentTime - lastLogTime >= logInterval) {
167+
lastLogTime = currentTime;
168+
logger.info(`Download status: ${(receivedBytes/totalBytes * 100).toFixed(0)}%`);
169+
}
170+
});
171+
172+
// GET request
173+
request(uri)
174+
.on('data', (chunk) => {
175+
receivedBytes += chunk.length
176+
if (progressBar) {
177+
progressBar.value = receivedBytes
178+
}
179+
})
180+
.pipe(fs.createWriteStream(filename))
181+
.on('close', callback)
182+
}
183+
// If the download link was not found, call callback (updatingLoaderMsg with error feedback)
184+
else {
185+
logger.info(`Error while trying to download specterd: ${err}`)
186+
callback(true)
187+
}
188+
})
104189
}
105190

106191
let specterdProcess
@@ -253,11 +338,9 @@ app.whenReady().then(() => {
253338
if (fs.existsSync(specterdPath + (platformName == 'win64' ? '.exe' : ''))) {
254339
getFileHash(specterdPath + (platformName == 'win64' ? '.exe' : ''), function (specterdHash) {
255340
if (appSettings.specterdHash.toLowerCase() == specterdHash || appSettings.specterdHash == "") {
256-
257341
startSpecterd(specterdPath)
258342
} else if (appSettings.specterdVersion != "") {
259-
updatingLoaderMsg('Specterd version could not be validated. Retrying fetching specterd...')
260-
updateSpecterdStatus('Fetching Specter binary...')
343+
updatingLoaderMsg('Specterd version could not be validated. Trying again to download the Specter binary ...')
261344
downloadSpecterd(specterdPath)
262345
} else {
263346
updatingLoaderMsg('Specterd file could not be validated and no version is configured in the settings<br>Please go to Preferences and set version to fetch or add an executable manually...')
@@ -308,21 +391,20 @@ function initMainWindow() {
308391
}
309392

310393
function downloadSpecterd(specterdPath) {
311-
updatingLoaderMsg(`Fetching the ${appName} binary.<br>This might take a minute...`)
312-
updateSpecterdStatus(`Fetching ${appName} binary...`)
394+
updatingLoaderMsg(`Starting download`)
395+
updateSpecterdStatus(`Downloading the ${appName} binary...`)
396+
// Some logging
313397
logger.info("Using version " + appSettings.specterdVersion);
314398
logger.info("Using platformName " + platformName);
315-
316399
download_location = getDownloadLocation(appSettings.specterdVersion, platformName)
317400
logger.info("Downloading from "+download_location);
318401
download(download_location, specterdPath + '.zip', function(errored) {
319402
if (errored == true) {
320-
updatingLoaderMsg(`Fetching ${appNameLower} binary from the server failed, could not reach the server or the file could not have been found.`)
321-
updateSpecterdStatus(`Fetching ${appNameLower}d failed...`)
403+
updatingLoaderMsg(`Downloading the ${appNameLower} binary from GitHub failed, could not reach the server or the file wasn't found.`)
404+
updateSpecterdStatus(`Downloading ${appNameLower}d failed...`)
322405
return
323406
}
324-
325-
updatingLoaderMsg('Unpacking files...')
407+
updatingLoaderMsg('Download completed. Unpacking files...')
326408
logger.info("Extracting "+specterdPath);
327409

328410
extract(specterdPath + '.zip', { dir: specterdPath + '-dir' }).then(function () {
@@ -341,7 +423,6 @@ function downloadSpecterd(specterdPath) {
341423
var newPath = specterdPath + (platformName == 'win64' ? '.exe' : '')
342424

343425
fs.renameSync(oldPath, newPath)
344-
updatingLoaderMsg('Cleaning up...')
345426
fs.unlinkSync(specterdPath + '.zip')
346427
fs.rmdirSync(specterdPath + '-dir', { recursive: true });
347428
getFileHash(specterdPath + (platformName == 'win64' ? '.exe' : ''), function(specterdHash) {
@@ -352,10 +433,6 @@ function downloadSpecterd(specterdPath) {
352433
logger.error(`hash of downloaded file: ${specterdHash}`)
353434
logger.error(`Expected hash: ${appSettings.specterdHash}`)
354435
updateSpecterdStatus('Failed to launch specterd...')
355-
356-
// app.quit()
357-
// TODO: This should never happen unless the specterd file was swapped on GitHub.
358-
// Think of what would be the appropriate way to handle this...
359436
}
360437
})
361438
})
@@ -520,17 +597,21 @@ app.on('window-all-closed', function(){
520597
}
521598
});
522599

523-
app.on('before-quit', () => {
600+
app.on('before-quit', (event) => {
524601
if (!quitted) {
602+
logger.info('Quitting ...')
525603
quitted = true
526604
quitSpecterd()
527-
528605
if (mainWindow && !mainWindow.isDestroyed()) {
606+
if (progressBar) {
607+
progressBar.destroy()
608+
progressBar = null
609+
}
529610
mainWindow.destroy()
530611
mainWindow = null
531612
prefWindow = null
532613
tray = null
533-
}
614+
}
534615
}
535616
})
536617

pyinstaller/electron/package-lock.json

Lines changed: 20 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyinstaller/electron/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"dependencies": {
4848
"electron-context-menu": "^2.3.0",
4949
"electron-default-menu": "^1.0.2",
50+
"electron-progressbar": "^2.0.1",
5051
"extract-zip": "^2.0.1",
5152
"read-last-lines": "^1.8.0",
5253
"request": "^2.88.2",

0 commit comments

Comments
 (0)