feat: show progress while updating/installing dependencies

This commit is contained in:
Jelle Glebbeek
2021-10-27 02:18:16 +02:00
parent bc8b23e845
commit d4b62e9a9f
4 changed files with 65 additions and 40 deletions

View File

@@ -2,6 +2,7 @@ const axios = require("axios");
const fs = require("fs"); const fs = require("fs");
const Sentry = require("@sentry/node"); const Sentry = require("@sentry/node");
const util = require('util'); const util = require('util');
const Utils = require('./Utils');
const exec = util.promisify(require('child_process').exec); const exec = util.promisify(require('child_process').exec);
class BinaryUpdater { class BinaryUpdater {
@@ -9,6 +10,7 @@ class BinaryUpdater {
constructor(paths, win) { constructor(paths, win) {
this.paths = paths; this.paths = paths;
this.win = win; this.win = win;
this.action = "Installing";
} }
//Checks for an update and download it if there is. //Checks for an update and download it if there is.
@@ -28,7 +30,7 @@ class BinaryUpdater {
} else if(localVersion == null) { } else if(localVersion == null) {
transaction.setTag("download", "corrupted"); transaction.setTag("download", "corrupted");
console.log("Downloading missing yt-dlp binary."); 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); await this.downloadUpdate(remoteUrl, remoteVersion);
} else if(remoteVersion == null) { } else if(remoteVersion == null) {
transaction.setTag("download", "down"); transaction.setTag("download", "down");
@@ -36,7 +38,8 @@ class BinaryUpdater {
} else { } else {
console.log(`New version ${remoteVersion} found. Updating...`); console.log(`New version ${remoteVersion} found. Updating...`);
transaction.setTag("download", "update"); 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); await this.downloadUpdate(remoteUrl, remoteVersion);
} }
span.finish(); span.finish();
@@ -110,21 +113,28 @@ class BinaryUpdater {
//Downloads the file at the given url and saves it to the ytdl path. //Downloads the file at the given url and saves it to the ytdl path.
async downloadUpdate(remoteUrl, remoteVersion) { async downloadUpdate(remoteUrl, remoteVersion) {
const writer = fs.createWriteStream(this.paths.ytdl); const writer = fs.createWriteStream(this.paths.ytdl);
return await axios.get(remoteUrl, {responseType: 'stream'}).then(response => { const { data, headers } = await axios.get(remoteUrl, {responseType: 'stream'});
return new Promise((resolve, reject) => { const totalLength = +headers['content-length'];
response.data.pipe(writer); const total = Utils.convertBytes(totalLength);
let error = null; let received = 0;
writer.on('error', err => { return await new Promise((resolve, reject) => {
error = err; let error = null;
reject(err); data.on('data', (chunk) => {
}); received += chunk.length;
writer.on('close', async () => { const percentage = ((received / totalLength) * 100).toFixed(0) + '%';
if (!error) { this.win.webContents.send("binaryLock", {lock: true, placeholder: `${this.action} yt-dlp ${remoteVersion} - ${percentage} of ${total}`})
await this.writeVersionInfo(remoteVersion);
resolve(true);
}
});
}); });
writer.on('error', err => {
error = err;
reject(err);
});
writer.on('close', async () => {
if (!error) {
await this.writeVersionInfo(remoteVersion);
resolve(true);
}
});
data.pipe(writer);
}); });
} }

View File

@@ -6,12 +6,14 @@ const util = require('util');
const exec = util.promisify(require('child_process').exec); const exec = util.promisify(require('child_process').exec);
const os = require("os"); const os = require("os");
const AdmZip = require("adm-zip"); const AdmZip = require("adm-zip");
const Utils = require('./Utils');
class FfmpegUpdater { class FfmpegUpdater {
constructor(paths, win) { constructor(paths, win) {
this.paths = paths; this.paths = paths;
this.win = win; this.win = win;
this.action = "Installing";
} }
//Checks for an update and download it if there is. //Checks for an update and download it if there is.
@@ -31,9 +33,10 @@ class FfmpegUpdater {
} else if(localVersion == null) { } else if(localVersion == null) {
transaction.setTag("download", "corrupted"); transaction.setTag("download", "corrupted");
console.log("Downloading missing ffmpeg binary."); console.log("Downloading missing ffmpeg binary.");
this.win.webContents.send("binaryLock", {lock: true, placeholder: `Installing ffmpeg version: ${remoteVersion}...`}) this.win.webContents.send("binaryLock", {lock: true, placeholder: `Installing ffmpeg version: ${remoteVersion}. Preparing...`})
await this.downloadUpdate(remoteFfmpegUrl, "ffmpeg" + this.getFileExtension()); await this.downloadUpdate(remoteFfmpegUrl, remoteVersion, "ffmpeg" + this.getFileExtension());
await this.downloadUpdate(remoteFfprobeUrl, "ffprobe" + 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); await this.writeVersionInfo(remoteVersion);
} else if(remoteVersion == null) { } else if(remoteVersion == null) {
transaction.setTag("download", "down"); transaction.setTag("download", "down");
@@ -41,9 +44,11 @@ class FfmpegUpdater {
} else { } else {
console.log(`New version ${remoteVersion} found. Updating...`); console.log(`New version ${remoteVersion} found. Updating...`);
transaction.setTag("download", "update"); transaction.setTag("download", "update");
this.win.webContents.send("binaryLock", {lock: true, placeholder: `Updating ffmpeg to version: ${remoteVersion}...`}) this.action = "Updating to";
await this.downloadUpdate(remoteFfmpegUrl, "ffmpeg" + this.getFileExtension()); this.win.webContents.send("binaryLock", {lock: true, placeholder: `Updating ffmpeg to version: ${remoteVersion}. Preparing...`})
await this.downloadUpdate(remoteFfprobeUrl, "ffprobe" + this.getFileExtension()); 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); await this.writeVersionInfo(remoteVersion);
} }
span.finish(); span.finish();
@@ -106,27 +111,37 @@ class FfmpegUpdater {
} }
//Downloads the file at the given url and saves it to the ffmpeg path. //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"); const downloadPath = path.join(this.paths.ffmpeg, "downloads");
if (!fs.existsSync(downloadPath)) { if (!fs.existsSync(downloadPath)) {
fs.mkdirSync(downloadPath); fs.mkdirSync(downloadPath);
} }
const writer = fs.createWriteStream(path.join(downloadPath, filename)); const writer = fs.createWriteStream(path.join(downloadPath, filename));
await axios.get(url, {responseType: 'stream'}).then(response => {
return new Promise((resolve, reject) => { const { data, headers } = await axios.get(url, {responseType: 'stream'});
response.data.pipe(writer); const totalLength = +headers['content-length'];
let error = null; const total = Utils.convertBytes(totalLength);
writer.on('error', err => { const artifact = filename.replace(".exe", "");
error = err; let received = 0;
reject(err); await new Promise((resolve, reject) => {
}); let error = null;
writer.on('close', async () => { data.on('data', (chunk) => {
if (!error) { received += chunk.length;
resolve(true); 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), {}); const zipFile = new AdmZip(path.join(downloadPath, filename), {});
zipFile.extractEntryTo(filename, this.paths.ffmpeg, false, true, false, filename); zipFile.extractEntryTo(filename, this.paths.ffmpeg, false, true, false, filename);
fs.rmdirSync(path.join(this.paths.ffmpeg, "downloads"), { recursive: true, force: true }); fs.rmdirSync(path.join(this.paths.ffmpeg, "downloads"), { recursive: true, force: true });

View File

@@ -136,7 +136,7 @@ describe("downloadUpdate", () => {
const mockReadable = new PassThrough(); const mockReadable = new PassThrough();
const mockWriteable = new PassThrough(); const mockWriteable = new PassThrough();
jest.spyOn(fs, 'createWriteStream').mockReturnValueOnce(mockWriteable); 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(() => { setTimeout(() => {
mockWriteable.emit('error', "Test error"); mockWriteable.emit('error', "Test error");
}, 100); }, 100);
@@ -150,7 +150,7 @@ describe("downloadUpdate", () => {
const mockReadable = new PassThrough(); const mockReadable = new PassThrough();
const mockWriteable = new PassThrough(); const mockWriteable = new PassThrough();
jest.spyOn(fs, 'createWriteStream').mockReturnValueOnce(mockWriteable); 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(() => { setTimeout(() => {
mockWriteable.emit('close'); mockWriteable.emit('close');
}, 100); }, 100);

View File

@@ -106,7 +106,7 @@ describe('checkUpdate', () => {
jest.spyOn(instance, 'getRemoteVersion').mockResolvedValue(["link", "v2.0.0"]); jest.spyOn(instance, 'getRemoteVersion').mockResolvedValue(["link", "v2.0.0"]);
return instance.checkUpdate().then(() => { return instance.checkUpdate().then(() => {
expect(downloadUpdateSpy).toBeCalledTimes(2); 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', () => { 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" }); jest.spyOn(instance, 'getRemoteVersion').mockResolvedValue({ remoteUrl: "link", remoteVersion: "2021.10.10" });
return instance.checkUpdate().then(() => { return instance.checkUpdate().then(() => {
expect(downloadUpdateSpy).toBeCalledTimes(2); expect(downloadUpdateSpy).toBeCalledTimes(2);
expect(instance.win.webContents.send).toBeCalledTimes(1); expect(instance.win.webContents.send).toBeCalledTimes(2);
}); });
}); });
}); });