From d4b62e9a9f8eadb4fc996713916d73a1de31de41 Mon Sep 17 00:00:00 2001 From: Jelle Glebbeek Date: Wed, 27 Oct 2021 02:18:16 +0200 Subject: [PATCH] feat: show progress while updating/installing dependencies --- modules/BinaryUpdater.js | 42 +++++++++++++++++----------- modules/FfmpegUpdater.js | 55 +++++++++++++++++++++++-------------- tests/BinaryUpdater.test.js | 4 +-- tests/FfmpegUpdater.test.js | 4 +-- 4 files changed, 65 insertions(+), 40 deletions(-) diff --git a/modules/BinaryUpdater.js b/modules/BinaryUpdater.js index c332f150..baa034e2 100644 --- a/modules/BinaryUpdater.js +++ b/modules/BinaryUpdater.js @@ -2,6 +2,7 @@ const axios = require("axios"); const fs = require("fs"); const Sentry = require("@sentry/node"); const util = require('util'); +const Utils = require('./Utils'); const exec = util.promisify(require('child_process').exec); class BinaryUpdater { @@ -9,6 +10,7 @@ class BinaryUpdater { constructor(paths, win) { this.paths = paths; this.win = win; + this.action = "Installing"; } //Checks for an update and download it if there is. @@ -28,7 +30,7 @@ class BinaryUpdater { } else if(localVersion == null) { transaction.setTag("download", "corrupted"); console.log("Downloading missing yt-dlp binary."); - this.win.webContents.send("binaryLock", {lock: true, placeholder: `Installing yt-dlp version: ${remoteVersion}...`}) + this.win.webContents.send("binaryLock", {lock: true, placeholder: `Installing yt-dlp version: ${remoteVersion}. Preparing...`}) await this.downloadUpdate(remoteUrl, remoteVersion); } else if(remoteVersion == null) { transaction.setTag("download", "down"); @@ -36,7 +38,8 @@ class BinaryUpdater { } else { console.log(`New version ${remoteVersion} found. Updating...`); transaction.setTag("download", "update"); - this.win.webContents.send("binaryLock", {lock: true, placeholder: `Updating yt-dlp to version: ${remoteVersion}...`}) + this.action = "Updating to"; + this.win.webContents.send("binaryLock", {lock: true, placeholder: `Updating yt-dlp to version: ${remoteVersion}. Preparing...`}) await this.downloadUpdate(remoteUrl, remoteVersion); } span.finish(); @@ -110,21 +113,28 @@ class BinaryUpdater { //Downloads the file at the given url and saves it to the ytdl path. async downloadUpdate(remoteUrl, remoteVersion) { const writer = fs.createWriteStream(this.paths.ytdl); - return await axios.get(remoteUrl, {responseType: 'stream'}).then(response => { - return new Promise((resolve, reject) => { - response.data.pipe(writer); - let error = null; - writer.on('error', err => { - error = err; - reject(err); - }); - writer.on('close', async () => { - if (!error) { - await this.writeVersionInfo(remoteVersion); - resolve(true); - } - }); + const { data, headers } = await axios.get(remoteUrl, {responseType: 'stream'}); + const totalLength = +headers['content-length']; + const total = Utils.convertBytes(totalLength); + let received = 0; + return await new Promise((resolve, reject) => { + let error = null; + data.on('data', (chunk) => { + received += chunk.length; + const percentage = ((received / totalLength) * 100).toFixed(0) + '%'; + this.win.webContents.send("binaryLock", {lock: true, placeholder: `${this.action} yt-dlp ${remoteVersion} - ${percentage} of ${total}`}) }); + writer.on('error', err => { + error = err; + reject(err); + }); + writer.on('close', async () => { + if (!error) { + await this.writeVersionInfo(remoteVersion); + resolve(true); + } + }); + data.pipe(writer); }); } diff --git a/modules/FfmpegUpdater.js b/modules/FfmpegUpdater.js index 3e6b8f66..712d18e0 100644 --- a/modules/FfmpegUpdater.js +++ b/modules/FfmpegUpdater.js @@ -6,12 +6,14 @@ const util = require('util'); const exec = util.promisify(require('child_process').exec); const os = require("os"); const AdmZip = require("adm-zip"); +const Utils = require('./Utils'); class FfmpegUpdater { constructor(paths, win) { this.paths = paths; this.win = win; + this.action = "Installing"; } //Checks for an update and download it if there is. @@ -31,9 +33,10 @@ class FfmpegUpdater { } else if(localVersion == null) { transaction.setTag("download", "corrupted"); console.log("Downloading missing ffmpeg binary."); - this.win.webContents.send("binaryLock", {lock: true, placeholder: `Installing ffmpeg version: ${remoteVersion}...`}) - await this.downloadUpdate(remoteFfmpegUrl, "ffmpeg" + this.getFileExtension()); - await this.downloadUpdate(remoteFfprobeUrl, "ffprobe" + this.getFileExtension()); + this.win.webContents.send("binaryLock", {lock: true, placeholder: `Installing ffmpeg version: ${remoteVersion}. Preparing...`}) + await this.downloadUpdate(remoteFfmpegUrl, remoteVersion, "ffmpeg" + this.getFileExtension()); + this.win.webContents.send("binaryLock", {lock: true, placeholder: `Installing ffprobe version: ${remoteVersion}. Preparing...`}) + await this.downloadUpdate(remoteFfprobeUrl, remoteVersion, "ffprobe" + this.getFileExtension()); await this.writeVersionInfo(remoteVersion); } else if(remoteVersion == null) { transaction.setTag("download", "down"); @@ -41,9 +44,11 @@ class FfmpegUpdater { } else { console.log(`New version ${remoteVersion} found. Updating...`); transaction.setTag("download", "update"); - this.win.webContents.send("binaryLock", {lock: true, placeholder: `Updating ffmpeg to version: ${remoteVersion}...`}) - await this.downloadUpdate(remoteFfmpegUrl, "ffmpeg" + this.getFileExtension()); - await this.downloadUpdate(remoteFfprobeUrl, "ffprobe" + this.getFileExtension()); + this.action = "Updating to"; + this.win.webContents.send("binaryLock", {lock: true, placeholder: `Updating ffmpeg to version: ${remoteVersion}. Preparing...`}) + await this.downloadUpdate(remoteFfmpegUrl, remoteVersion, "ffmpeg" + this.getFileExtension()); + this.win.webContents.send("binaryLock", {lock: true, placeholder: `Updating ffprobe to version: ${remoteVersion}. Preparing...`}) + await this.downloadUpdate(remoteFfprobeUrl, remoteVersion, "ffprobe" + this.getFileExtension()); await this.writeVersionInfo(remoteVersion); } span.finish(); @@ -106,27 +111,37 @@ class FfmpegUpdater { } //Downloads the file at the given url and saves it to the ffmpeg path. - async downloadUpdate(url, filename) { + async downloadUpdate(url, version, filename) { const downloadPath = path.join(this.paths.ffmpeg, "downloads"); if (!fs.existsSync(downloadPath)) { fs.mkdirSync(downloadPath); } const writer = fs.createWriteStream(path.join(downloadPath, filename)); - await axios.get(url, {responseType: 'stream'}).then(response => { - return new Promise((resolve, reject) => { - response.data.pipe(writer); - let error = null; - writer.on('error', err => { - error = err; - reject(err); - }); - writer.on('close', async () => { - if (!error) { - resolve(true); - } - }); + + const { data, headers } = await axios.get(url, {responseType: 'stream'}); + const totalLength = +headers['content-length']; + const total = Utils.convertBytes(totalLength); + const artifact = filename.replace(".exe", ""); + let received = 0; + await new Promise((resolve, reject) => { + let error = null; + data.on('data', (chunk) => { + received += chunk.length; + const percentage = ((received / totalLength) * 100).toFixed(0) + '%'; + this.win.webContents.send("binaryLock", {lock: true, placeholder: `${this.action} ${artifact} ${version} - ${percentage} of ${total}`}) + }); + writer.on('error', err => { + error = err; + reject(err); + }); + writer.on('close', async () => { + if (!error) { + resolve(true); + } }); + data.pipe(writer); }); + this.win.webContents.send("binaryLock", {lock: true, placeholder: `${this.action} ${artifact} ${version} - Extracting binaries...`}) const zipFile = new AdmZip(path.join(downloadPath, filename), {}); zipFile.extractEntryTo(filename, this.paths.ffmpeg, false, true, false, filename); fs.rmdirSync(path.join(this.paths.ffmpeg, "downloads"), { recursive: true, force: true }); diff --git a/tests/BinaryUpdater.test.js b/tests/BinaryUpdater.test.js index b8a2416b..f1986597 100644 --- a/tests/BinaryUpdater.test.js +++ b/tests/BinaryUpdater.test.js @@ -136,7 +136,7 @@ describe("downloadUpdate", () => { const mockReadable = new PassThrough(); const mockWriteable = new PassThrough(); jest.spyOn(fs, 'createWriteStream').mockReturnValueOnce(mockWriteable); - jest.spyOn(axios, 'get').mockResolvedValue({ data: mockReadable }); + jest.spyOn(axios, 'get').mockResolvedValue({ data: mockReadable, headers: { "content-length": 1200 } }); setTimeout(() => { mockWriteable.emit('error', "Test error"); }, 100); @@ -150,7 +150,7 @@ describe("downloadUpdate", () => { const mockReadable = new PassThrough(); const mockWriteable = new PassThrough(); jest.spyOn(fs, 'createWriteStream').mockReturnValueOnce(mockWriteable); - jest.spyOn(axios, 'get').mockResolvedValue({ data: mockReadable }); + jest.spyOn(axios, 'get').mockResolvedValue({ data: mockReadable, headers: { "content-length": 1200 } }); setTimeout(() => { mockWriteable.emit('close'); }, 100); diff --git a/tests/FfmpegUpdater.test.js b/tests/FfmpegUpdater.test.js index 25423447..e8717465 100644 --- a/tests/FfmpegUpdater.test.js +++ b/tests/FfmpegUpdater.test.js @@ -106,7 +106,7 @@ describe('checkUpdate', () => { jest.spyOn(instance, 'getRemoteVersion').mockResolvedValue(["link", "v2.0.0"]); return instance.checkUpdate().then(() => { expect(downloadUpdateSpy).toBeCalledTimes(2); - expect(instance.win.webContents.send).toBeCalledTimes(1); + expect(instance.win.webContents.send).toBeCalledTimes(2); }); }); it('downloads the latest remote version when local version is different', () => { @@ -117,7 +117,7 @@ describe('checkUpdate', () => { jest.spyOn(instance, 'getRemoteVersion').mockResolvedValue({ remoteUrl: "link", remoteVersion: "2021.10.10" }); return instance.checkUpdate().then(() => { expect(downloadUpdateSpy).toBeCalledTimes(2); - expect(instance.win.webContents.send).toBeCalledTimes(1); + expect(instance.win.webContents.send).toBeCalledTimes(2); }); }); });