2e130ef99d
The appstore can then known if a user clicked the check for updates button manually or if it was done by the automatic updater. We will fix appstore so that updates are always provided for manual checks. automatic updates will follow our roll out plan. We do have one issue that the automatic update checker will reset the manual updates when it runs, but this is OK.
175 lines
5.7 KiB
JavaScript
175 lines
5.7 KiB
JavaScript
'use strict';
|
|
|
|
exports = module.exports = {
|
|
checkAppUpdates: checkAppUpdates,
|
|
checkBoxUpdates: checkBoxUpdates,
|
|
|
|
getUpdateInfo: getUpdateInfo,
|
|
resetUpdateInfo: resetUpdateInfo,
|
|
resetAppUpdateInfo: resetAppUpdateInfo,
|
|
|
|
_setUpdateInfo: setUpdateInfo
|
|
};
|
|
|
|
var apps = require('./apps.js'),
|
|
appstore = require('./appstore.js'),
|
|
assert = require('assert'),
|
|
async = require('async'),
|
|
constants = require('./constants.js'),
|
|
debug = require('debug')('box:updatechecker'),
|
|
mailer = require('./mailer.js'),
|
|
notifications = require('./notifications.js'),
|
|
paths = require('./paths.js'),
|
|
safe = require('safetydance'),
|
|
settings = require('./settings.js'),
|
|
users = require('./users.js');
|
|
|
|
var gAppUpdateInfo = { }, // id -> update info { creationDate, manifest }
|
|
gBoxUpdateInfo = null; // { version, changelog, upgrade, sourceTarballUrl }
|
|
|
|
function loadState() {
|
|
var state = safe.JSON.parse(safe.fs.readFileSync(paths.UPDATE_CHECKER_FILE, 'utf8'));
|
|
return state || { };
|
|
}
|
|
|
|
function saveState(mailedUser) {
|
|
safe.fs.writeFileSync(paths.UPDATE_CHECKER_FILE, JSON.stringify(mailedUser, null, 4), 'utf8');
|
|
}
|
|
|
|
function getUpdateInfo() {
|
|
return {
|
|
apps: gAppUpdateInfo,
|
|
box: gBoxUpdateInfo
|
|
};
|
|
}
|
|
|
|
function setUpdateInfo(info) {
|
|
gBoxUpdateInfo = info.box;
|
|
gAppUpdateInfo = info.apps;
|
|
}
|
|
|
|
function resetUpdateInfo() {
|
|
gBoxUpdateInfo = null;
|
|
resetAppUpdateInfo();
|
|
}
|
|
|
|
// If no appId provided all apps are reset
|
|
function resetAppUpdateInfo(appId) {
|
|
if (!appId) {
|
|
gAppUpdateInfo = {};
|
|
} else {
|
|
delete gAppUpdateInfo[appId];
|
|
}
|
|
}
|
|
|
|
function checkAppUpdates(options, callback) {
|
|
assert.strictEqual(typeof options, 'object');
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
debug('Checking App Updates');
|
|
|
|
gAppUpdateInfo = { };
|
|
var oldState = loadState();
|
|
var newState = { }; // create new state so that old app ids are removed
|
|
|
|
settings.getAppAutoupdatePattern(function (error, result) {
|
|
if (error) return callback(error);
|
|
const autoupdatesEnabled = (result !== constants.AUTOUPDATE_PATTERN_NEVER);
|
|
|
|
var notificationPending = [];
|
|
|
|
apps.getAll(function (error, result) {
|
|
if (error) return callback(error);
|
|
|
|
async.eachSeries(result, function (app, iteratorDone) {
|
|
if (app.appStoreId === '') return iteratorDone(); // appStoreId can be '' for dev apps
|
|
if (app.runState === apps.RSTATE_STOPPED) return iteratorDone(); // stopped apps won't run migration scripts and shouldn't be updated
|
|
|
|
appstore.getAppUpdate(app, options, function (error, updateInfo) {
|
|
if (error) {
|
|
debug('Error getting app update info for %s', app.id, error);
|
|
return iteratorDone(); // continue to next
|
|
}
|
|
|
|
// skip if no next version is found
|
|
if (!updateInfo) {
|
|
delete gAppUpdateInfo[app.id];
|
|
return iteratorDone();
|
|
}
|
|
|
|
gAppUpdateInfo[app.id] = updateInfo;
|
|
|
|
// decide whether to send email
|
|
newState[app.id] = updateInfo.manifest.version;
|
|
|
|
if (oldState[app.id] === newState[app.id]) {
|
|
debug('Skipping notification of app update %s since user was already notified', app.id);
|
|
return iteratorDone();
|
|
}
|
|
|
|
const canAutoupdateApp = apps.canAutoupdateApp(app, updateInfo.manifest);
|
|
if (autoupdatesEnabled && canAutoupdateApp) return iteratorDone();
|
|
|
|
debug('Notifying of app update for %s from %s to %s', app.id, app.manifest.version, updateInfo.manifest.version);
|
|
notificationPending.push({
|
|
app: app,
|
|
updateInfo: updateInfo
|
|
});
|
|
|
|
iteratorDone();
|
|
});
|
|
}, function () {
|
|
newState.box = loadState().box; // preserve the latest box state information
|
|
saveState(newState);
|
|
|
|
if (notificationPending.length === 0) return callback();
|
|
|
|
users.getAdmins(function (error, admins) {
|
|
if (error) {
|
|
console.error(error);
|
|
return callback();
|
|
}
|
|
|
|
async.eachSeries(admins, (admin, done) => mailer.appUpdatesAvailable(admin.email, notificationPending, true /* subscription */, done), callback);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function checkBoxUpdates(options, callback) {
|
|
assert.strictEqual(typeof options, 'object');
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
debug('Checking Box Updates');
|
|
|
|
gBoxUpdateInfo = null;
|
|
|
|
appstore.getBoxUpdate(options, function (error, updateInfo) {
|
|
if (error || !updateInfo) return callback(error);
|
|
|
|
gBoxUpdateInfo = updateInfo;
|
|
|
|
// decide whether to send email
|
|
var state = loadState();
|
|
|
|
if (state.box === gBoxUpdateInfo.version) {
|
|
debug('Skipping notification of box update as user was already notified');
|
|
return callback();
|
|
}
|
|
|
|
const changelog = updateInfo.changelog.map((m) => `* ${m}\n`).join('');
|
|
|
|
const message = `Changelog:\n${changelog}\n\nGo to the settings view to update.\n\n`;
|
|
|
|
notifications.alert(notifications.ALERT_BOX_UPDATE, `Cloudron v${updateInfo.version} is available`, message, function (error) {
|
|
if (error) return callback(error);
|
|
|
|
state.box = updateInfo.version;
|
|
saveState(state);
|
|
|
|
callback();
|
|
});
|
|
});
|
|
}
|