[Splash > Backend] Full rewrite (updater parts)
This commit is contained in:
parent
db0b3f1319
commit
6172cf0dee
1 changed files with 118 additions and 160 deletions
|
@ -4,14 +4,17 @@ const _events = require('events');
|
||||||
const { BrowserWindow, app } = require('electron');
|
const { BrowserWindow, app } = require('electron');
|
||||||
|
|
||||||
const paths = require('../paths');
|
const paths = require('../paths');
|
||||||
const Backoff = require('../utils/Backoff');
|
|
||||||
const moduleUpdater = require("../updater/moduleUpdater");
|
const moduleUpdater = require("../updater/moduleUpdater");
|
||||||
const updater = require("../updater/updater");
|
const updater = require("../updater/updater");
|
||||||
|
|
||||||
let splashState = {};
|
let splashState = {};
|
||||||
|
let modulesListeners = {};
|
||||||
let launchedMainWindow = false;
|
let launchedMainWindow = false;
|
||||||
let updateAttempt = 0;
|
let updateAttempt = 0;
|
||||||
let restartRequired = false;
|
let restartRequired = false;
|
||||||
|
let splashWindow;
|
||||||
|
let updateTimeout;
|
||||||
|
let newUpdater;
|
||||||
|
|
||||||
|
|
||||||
exports.initSplash = (startMinimized = false) => {
|
exports.initSplash = (startMinimized = false) => {
|
||||||
|
@ -19,7 +22,7 @@ exports.initSplash = (startMinimized = false) => {
|
||||||
|
|
||||||
newUpdater = updater.getUpdater();
|
newUpdater = updater.getUpdater();
|
||||||
|
|
||||||
if (newUpdater == null) initOldUpdater();
|
if (newUpdater == null) initModuleUpdater();
|
||||||
|
|
||||||
launchSplashWindow(startMinimized);
|
launchSplashWindow(startMinimized);
|
||||||
|
|
||||||
|
@ -50,7 +53,7 @@ exports.pageReady = () => destroySplash() || process.nextTick(() => events.emit(
|
||||||
const destroySplash = () => {
|
const destroySplash = () => {
|
||||||
log('Splash', 'Destroy');
|
log('Splash', 'Destroy');
|
||||||
|
|
||||||
stopUpdateTimeout();
|
v1_timeoutStop();
|
||||||
if (!splashWindow) return;
|
if (!splashWindow) return;
|
||||||
|
|
||||||
splashWindow.setSkipTaskbar(true);
|
splashWindow.setSkipTaskbar(true);
|
||||||
|
@ -66,7 +69,7 @@ const destroySplash = () => {
|
||||||
const launchMainWindow = () => {
|
const launchMainWindow = () => {
|
||||||
log('Splash', 'Launch main');
|
log('Splash', 'Launch main');
|
||||||
|
|
||||||
removeModulesListeners();
|
for (const e in modulesListeners) moduleUpdater.events.removeListener(e, modulesListeners[e]); // Remove updater v1 listeners
|
||||||
|
|
||||||
if (!launchedMainWindow && splashWindow != null) {
|
if (!launchedMainWindow && splashWindow != null) {
|
||||||
launchedMainWindow = true;
|
launchedMainWindow = true;
|
||||||
|
@ -74,7 +77,7 @@ const launchMainWindow = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateSplashState = (status) => splashWindow && splashWindow.webContents.send('SPLASH_STATE', { status, ...splashState });
|
const sendState = (status) => splashWindow && splashWindow.webContents.send('SPLASH_STATE', { status, ...splashState });
|
||||||
|
|
||||||
|
|
||||||
const launchSplashWindow = (startMinimized) => {
|
const launchSplashWindow = (startMinimized) => {
|
||||||
|
@ -113,29 +116,6 @@ const launchSplashWindow = (startMinimized) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const addModulesListener = (event, listener) => {
|
|
||||||
if (newUpdater) return;
|
|
||||||
modulesListeners[event] = listener;
|
|
||||||
moduleUpdater.events.addListener(event, listener);
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeModulesListeners = () => {
|
|
||||||
if (newUpdater) return;
|
|
||||||
for (const e in modulesListeners) moduleUpdater.events.removeListener(e, modulesListeners[e]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const startUpdateTimeout = () => !updateTimeout && (updateTimeout = setTimeout(() => scheduleUpdateCheck(), 10000));
|
|
||||||
const stopUpdateTimeout = () => updateTimeout && clearTimeout(updateTimeout) && (updateTimeout = null);
|
|
||||||
|
|
||||||
const scheduleUpdateCheck = () => {
|
|
||||||
updateAttempt++;
|
|
||||||
|
|
||||||
const wait = Math.min(updateAttempt * 10, 60);
|
|
||||||
splashState.seconds = wait;
|
|
||||||
setTimeout(() => moduleUpdater.checkForUpdates(), retryInSeconds * 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const CHECKING_FOR_UPDATES = 'checking-for-updates';
|
const CHECKING_FOR_UPDATES = 'checking-for-updates';
|
||||||
const UPDATE_CHECK_FINISHED = 'update-check-finished';
|
const UPDATE_CHECK_FINISHED = 'update-check-finished';
|
||||||
const UPDATE_FAILURE = 'update-failure';
|
const UPDATE_FAILURE = 'update-failure';
|
||||||
|
@ -160,167 +140,145 @@ exports.APP_SHOULD_LAUNCH = APP_SHOULD_LAUNCH;
|
||||||
exports.APP_SHOULD_SHOW = APP_SHOULD_SHOW;
|
exports.APP_SHOULD_SHOW = APP_SHOULD_SHOW;
|
||||||
exports.events = events;
|
exports.events = events;
|
||||||
|
|
||||||
let splashWindow;
|
class UIProgress { // Generic class to track updating and sent states to splash
|
||||||
let modulesListeners;
|
|
||||||
let updateTimeout;
|
|
||||||
let newUpdater;
|
|
||||||
const updateBackoff = new Backoff(1000, 30000);
|
|
||||||
|
|
||||||
class TaskProgress {
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.inProgress = new Map();
|
Object.assign(this, {
|
||||||
this.finished = new Set();
|
progress: new Map(),
|
||||||
this.allTasks = new Set();
|
done: new Set(),
|
||||||
|
total: new Set()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
recordProgress(progress, task) {
|
record(id, state, percent) {
|
||||||
this.allTasks.add(task.package_sha256);
|
this.total.add(id);
|
||||||
|
|
||||||
if (progress.state !== updater.TASK_STATE_WAITING) {
|
if (state !== updater.TASK_STATE_WAITING) {
|
||||||
this.inProgress.set(task.package_sha256, progress.percent);
|
this.progress.set(id, percent);
|
||||||
|
|
||||||
if (progress.state === updater.TASK_STATE_COMPLETE) {
|
if (state === updater.TASK_STATE_COMPLETE) this.done.add(id);
|
||||||
this.finished.add(task.package_sha256);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSplashState(newState) {
|
sendState(id) {
|
||||||
if (this.inProgress.size > 0 && this.inProgress.size > this.finished.size) {
|
if (this.progress.size > 0 && this.progress.size > this.done.size) {
|
||||||
let totalPercent = 0;
|
|
||||||
|
|
||||||
for (const item of this.inProgress.values()) {
|
|
||||||
totalPercent += item;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalPercent /= this.allTasks.size;
|
|
||||||
splashState = {
|
splashState = {
|
||||||
current: this.finished.size + 1,
|
current: this.done.size + 1,
|
||||||
total: this.allTasks.size,
|
total: this.total.size,
|
||||||
progress: totalPercent
|
progress: [...this.progress.values()].reduce((a, x) => a + x, 0) / this.total.size
|
||||||
};
|
};
|
||||||
updateSplashState(newState);
|
|
||||||
|
sendState(id);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateUntilCurrent() {
|
const updateUntilCurrent = async () => {
|
||||||
const retryOptions = {
|
const retryOptions = {
|
||||||
skip_host_delta: false,
|
skip_host_delta: false,
|
||||||
skip_module_delta: {}
|
skip_module_delta: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
updateSplashState(CHECKING_FOR_UPDATES);
|
sendState(CHECKING_FOR_UPDATES);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let installedAnything = false;
|
let installedAnything = false;
|
||||||
const downloads = new TaskProgress();
|
const downloads = new UIProgress();
|
||||||
const installs = new TaskProgress();
|
const installs = new UIProgress();
|
||||||
await newUpdater.updateToLatestWithOptions(retryOptions, progress => {
|
|
||||||
const task = progress.task;
|
await newUpdater.updateToLatestWithOptions(retryOptions, ({ task, state, percent }) => {
|
||||||
const downloadTask = task.HostDownload || task.ModuleDownload;
|
const download = task.HostDownload || task.ModuleDownload;
|
||||||
const installTask = task.HostInstall || task.ModuleInstall;
|
const install = task.HostInstall || task.ModuleInstall;
|
||||||
|
|
||||||
installedAnything = true;
|
installedAnything = true;
|
||||||
|
|
||||||
if (downloadTask != null) {
|
if (download != null) downloads.record(download.package_sha256, state, percent);
|
||||||
downloads.recordProgress(progress, downloadTask);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (installTask != null) {
|
if (!downloads.sendState(DOWNLOADING_UPDATES)) installs.sendState(INSTALLING_UPDATES);
|
||||||
installs.recordProgress(progress, installTask);
|
|
||||||
|
if (install == null) return;
|
||||||
|
|
||||||
|
installs.record(install.package_sha256, state, percent);
|
||||||
|
|
||||||
if (progress.state.Failed != null) {
|
|
||||||
if (task.HostInstall != null) {
|
if (task.HostInstall != null) {
|
||||||
retryOptions.skip_host_delta = true;
|
retryOptions.skip_host_delta = true;
|
||||||
} else if (task.ModuleInstall != null) {
|
} else if (task.ModuleInstall != null) {
|
||||||
retryOptions.skip_module_delta[installTask.version.module.name] = true;
|
retryOptions.skip_module_delta[install.version.module.name] = true;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!downloads.updateSplashState(DOWNLOADING_UPDATES)) {
|
|
||||||
installs.updateSplashState(INSTALLING_UPDATES);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!installedAnything) {
|
if (!installedAnything) {
|
||||||
|
sendState(LAUNCHING);
|
||||||
|
|
||||||
await newUpdater.startCurrentVersion();
|
await newUpdater.startCurrentVersion();
|
||||||
newUpdater.setRunningInBackground();
|
newUpdater.setRunningInBackground();
|
||||||
newUpdater.collectGarbage();
|
newUpdater.collectGarbage();
|
||||||
launchMainWindow();
|
|
||||||
updateBackoff.succeed();
|
return launchMainWindow();
|
||||||
updateSplashState(LAUNCHING);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Update failed', e);
|
log('Splash', 'Update failed', e);
|
||||||
await new Promise(resolve => {
|
await new Promise(res => {
|
||||||
const delayMs = updateBackoff.fail(resolve);
|
scheduleNextUpdate(res);
|
||||||
splashState.seconds = Math.round(delayMs / 1000);
|
sendState(UPDATE_FAILURE);
|
||||||
updateSplashState(UPDATE_FAILURE);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initOldUpdater() {
|
const initModuleUpdater = () => { // "Old" (not v2 / new, win32 only)
|
||||||
modulesListeners = {};
|
const add = (event, listener) => {
|
||||||
addModulesListener(CHECKING_FOR_UPDATES, () => {
|
modulesListeners[event] = listener;
|
||||||
startUpdateTimeout();
|
moduleUpdater.events.addListener(event, listener);
|
||||||
updateSplashState(CHECKING_FOR_UPDATES);
|
};
|
||||||
|
|
||||||
|
const addBasic = (ev, key, ui = ev) => add(ev, (e) => {
|
||||||
|
splashState[key] = e[key];
|
||||||
|
sendState(ui);
|
||||||
});
|
});
|
||||||
addModulesListener(UPDATE_CHECK_FINISHED, ({
|
|
||||||
succeeded,
|
const callbackCheck = () => moduleUpdater.checkForUpdates();
|
||||||
updateCount
|
|
||||||
}) => {
|
const callbackProgress = (e) => {
|
||||||
stopUpdateTimeout();
|
delete splashState.progress;
|
||||||
|
if (e.name === 'host') restartRequired = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFail = () => {
|
||||||
|
scheduleNextUpdate();
|
||||||
|
sendState(UPDATE_FAILURE);
|
||||||
|
};
|
||||||
|
|
||||||
|
add(CHECKING_FOR_UPDATES, () => {
|
||||||
|
v1_timeoutStart();
|
||||||
|
sendState(CHECKING_FOR_UPDATES);
|
||||||
|
});
|
||||||
|
|
||||||
|
add(UPDATE_CHECK_FINISHED, ({ succeeded, updateCount }) => {
|
||||||
|
v1_timeoutStop();
|
||||||
|
|
||||||
if (!succeeded) {
|
if (!succeeded) {
|
||||||
scheduleUpdateCheck();
|
handleFail();
|
||||||
updateSplashState(UPDATE_FAILURE);
|
|
||||||
} else if (updateCount === 0) {
|
} else if (updateCount === 0) {
|
||||||
moduleUpdater.setInBackground();
|
moduleUpdater.setInBackground();
|
||||||
launchMainWindow();
|
launchMainWindow();
|
||||||
updateSplashState(LAUNCHING);
|
sendState(LAUNCHING);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
addModulesListener(DOWNLOADING_MODULE, ({
|
|
||||||
current,
|
|
||||||
total
|
|
||||||
}) => {
|
|
||||||
stopUpdateTimeout();
|
|
||||||
splashState = {
|
|
||||||
current,
|
|
||||||
total
|
|
||||||
};
|
|
||||||
updateSplashState(DOWNLOADING_UPDATES);
|
|
||||||
});
|
|
||||||
addModulesListener(DOWNLOADING_MODULE_PROGRESS, ({
|
|
||||||
progress
|
|
||||||
}) => {
|
|
||||||
splashState.progress = progress;
|
|
||||||
updateSplashState(DOWNLOADING_UPDATES);
|
|
||||||
});
|
|
||||||
addModulesListener(DOWNLOADED_MODULE, ({
|
|
||||||
name
|
|
||||||
}) => {
|
|
||||||
delete splashState.progress;
|
|
||||||
|
|
||||||
if (name === 'host') {
|
add(DOWNLOADING_MODULE, ({ current, total }) => {
|
||||||
restartRequired = true;
|
v1_timeoutStop();
|
||||||
}
|
|
||||||
|
splashState = { current, total };
|
||||||
|
sendState(DOWNLOADING_UPDATES);
|
||||||
});
|
});
|
||||||
addModulesListener(DOWNLOADING_MODULES_FINISHED, ({
|
|
||||||
failed
|
add(DOWNLOADING_MODULES_FINISHED, ({ failed }) => {
|
||||||
}) => {
|
|
||||||
if (failed > 0) {
|
if (failed > 0) {
|
||||||
scheduleUpdateCheck();
|
handleFail();
|
||||||
updateSplashState(UPDATE_FAILURE);
|
|
||||||
} else {
|
} else {
|
||||||
process.nextTick(() => {
|
process.nextTick(() => {
|
||||||
if (restartRequired) {
|
if (restartRequired) {
|
||||||
|
@ -331,30 +289,30 @@ function initOldUpdater() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
addModulesListener(NO_PENDING_UPDATES, () => moduleUpdater.checkForUpdates());
|
|
||||||
addModulesListener(INSTALLING_MODULE, ({
|
add(INSTALLING_MODULE, ({ current, total }) => {
|
||||||
current,
|
splashState = { current, total };
|
||||||
total
|
sendState(INSTALLING_UPDATES);
|
||||||
}) => {
|
|
||||||
splashState = {
|
|
||||||
current,
|
|
||||||
total
|
|
||||||
};
|
|
||||||
updateSplashState(INSTALLING_UPDATES);
|
|
||||||
});
|
});
|
||||||
addModulesListener(INSTALLED_MODULE, ({
|
|
||||||
}) => delete splashState.progress);
|
add(DOWNLOADED_MODULE, callbackProgress);
|
||||||
addModulesListener(INSTALLING_MODULE_PROGRESS, ({
|
add(INSTALLED_MODULE, callbackProgress);
|
||||||
progress
|
|
||||||
}) => {
|
add(INSTALLING_MODULES_FINISHED, callbackCheck);
|
||||||
splashState.progress = progress;
|
add(NO_PENDING_UPDATES, callbackCheck);
|
||||||
updateSplashState(INSTALLING_UPDATES);
|
|
||||||
});
|
addBasic(DOWNLOADING_MODULE_PROGRESS, 'progress', DOWNLOADING_UPDATES);
|
||||||
addModulesListener(INSTALLING_MODULES_FINISHED, () => moduleUpdater.checkForUpdates());
|
addBasic(INSTALLING_MODULE_PROGRESS, 'progress', INSTALLING_UPDATES);
|
||||||
addModulesListener(UPDATE_MANUALLY, ({
|
addBasic(UPDATE_MANUALLY, 'newVersion');
|
||||||
newVersion
|
};
|
||||||
}) => {
|
|
||||||
splashState.newVersion = newVersion;
|
const v1_timeoutStart = () => !updateTimeout && (updateTimeout = setTimeout(scheduleNextUpdate, 10000));
|
||||||
updateSplashState(UPDATE_MANUALLY);
|
const v1_timeoutStop = () => updateTimeout && (updateTimeout = clearTimeout(updateTimeout));
|
||||||
});
|
|
||||||
}
|
const scheduleNextUpdate = (callback = moduleUpdater.checkForUpdates) => { // Used by v1 and v2, default to v1 as used more widely in it
|
||||||
|
updateAttempt++;
|
||||||
|
|
||||||
|
const wait = Math.min(updateAttempt * 10, 60);
|
||||||
|
splashState.seconds = wait;
|
||||||
|
setTimeout(callback, wait * 1000);
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue