diff --git a/src/cron.js b/src/cron.js index 653e548c5..b232dc0db 100644 --- a/src/cron.js +++ b/src/cron.js @@ -27,7 +27,8 @@ var apps = require('./apps.js'), var gJobs = { alive: null, // send periodic stats - autoUpdater: null, + appAutoUpdater: null, + boxAutoUpdater: null, appUpdateChecker: null, backup: null, boxUpdateChecker: null, @@ -78,14 +79,16 @@ function initialize(callback) { }); settings.events.on(settings.TIME_ZONE_KEY, recreateJobs); - settings.events.on(settings.AUTOUPDATE_PATTERN_KEY, autoupdatePatternChanged); + settings.events.on(settings.APP_AUTOUPDATE_PATTERN_KEY, appAutoupdatePatternChanged); + settings.events.on(settings.BOX_AUTOUPDATE_PATTERN_KEY, boxAutoupdatePatternChanged); settings.events.on(settings.DYNAMIC_DNS_KEY, dynamicDnsChanged); settings.getAll(function (error, allSettings) { if (error) return callback(error); recreateJobs(allSettings[settings.TIME_ZONE_KEY]); - autoupdatePatternChanged(allSettings[settings.AUTOUPDATE_PATTERN_KEY]); + appAutoupdatePatternChanged(allSettings[settings.APP_AUTOUPDATE_PATTERN_KEY]); + boxAutoupdatePatternChanged(allSettings[settings.BOX_AUTOUPDATE_PATTERN_KEY]); dynamicDnsChanged(allSettings[settings.DYNAMIC_DNS_KEY]); callback(); @@ -189,17 +192,17 @@ function recreateJobs(tz) { }); } -function autoupdatePatternChanged(pattern) { +function boxAutoupdatePatternChanged(pattern) { assert.strictEqual(typeof pattern, 'string'); assert(gJobs.boxUpdateCheckerJob); - debug('Auto update pattern changed to %s', pattern); + debug('Box auto update pattern changed to %s', pattern); - if (gJobs.autoUpdater) gJobs.autoUpdater.stop(); + if (gJobs.boxAutoUpdater) gJobs.boxAutoUpdater.stop(); if (pattern === constants.AUTOUPDATE_PATTERN_NEVER) return; - gJobs.autoUpdater = new CronJob({ + gJobs.boxAutoUpdater = new CronJob({ cronTime: pattern, onTick: function() { var updateInfo = updateChecker.getUpdateInfo(); @@ -210,11 +213,34 @@ function autoupdatePatternChanged(pattern) { } else { debug('Block automatic update for major version'); } - } else if (updateInfo.apps) { + } else { + debug('No box auto updates available'); + } + }, + start: true, + timeZone: gJobs.boxUpdateCheckerJob.cronTime.zone // hack + }); +} + +function appAutoupdatePatternChanged(pattern) { + assert.strictEqual(typeof pattern, 'string'); + assert(gJobs.boxUpdateCheckerJob); + + debug('Apps auto update pattern changed to %s', pattern); + + if (gJobs.appAutoUpdater) gJobs.appAutoUpdater.stop(); + + if (pattern === constants.AUTOUPDATE_PATTERN_NEVER) return; + + gJobs.appAutoUpdater = new CronJob({ + cronTime: pattern, + onTick: function() { + var updateInfo = updateChecker.getUpdateInfo(); + if (updateInfo.apps) { debug('Starting app update to %j', updateInfo.apps); apps.autoupdateApps(updateInfo.apps, AUDIT_SOURCE, NOOP_CALLBACK); } else { - debug('No auto updates available'); + debug('No app auto updates available'); } }, start: true, @@ -245,7 +271,8 @@ function uninitialize(callback) { assert.strictEqual(typeof callback, 'function'); settings.events.removeListener(settings.TIME_ZONE_KEY, recreateJobs); - settings.events.removeListener(settings.AUTOUPDATE_PATTERN_KEY, autoupdatePatternChanged); + settings.events.removeListener(settings.APP_AUTOUPDATE_PATTERN_KEY, appAutoupdatePatternChanged); + settings.events.removeListener(settings.BOX_AUTOUPDATE_PATTERN_KEY, boxAutoupdatePatternChanged); settings.events.removeListener(settings.DYNAMIC_DNS_KEY, dynamicDnsChanged); for (var job in gJobs) { diff --git a/src/routes/settings.js b/src/routes/settings.js index 2c4fe993c..850577e3c 100644 --- a/src/routes/settings.js +++ b/src/routes/settings.js @@ -1,8 +1,11 @@ 'use strict'; exports = module.exports = { - getAutoupdatePattern: getAutoupdatePattern, - setAutoupdatePattern: setAutoupdatePattern, + getAppAutoupdatePattern: getAppAutoupdatePattern, + setAppAutoupdatePattern: setAppAutoupdatePattern, + + getBoxAutoupdatePattern: getBoxAutoupdatePattern, + setBoxAutoupdatePattern: setBoxAutoupdatePattern, getCloudronName: getCloudronName, setCloudronName: setCloudronName, @@ -27,20 +30,41 @@ var assert = require('assert'), settings = require('../settings.js'), SettingsError = settings.SettingsError; -function getAutoupdatePattern(req, res, next) { - settings.getAutoupdatePattern(function (error, pattern) { +function getAppAutoupdatePattern(req, res, next) { + settings.getAppAutoupdatePattern(function (error, pattern) { if (error) return next(new HttpError(500, error)); next(new HttpSuccess(200, { pattern: pattern })); }); } -function setAutoupdatePattern(req, res, next) { +function setAppAutoupdatePattern(req, res, next) { assert.strictEqual(typeof req.body, 'object'); if (typeof req.body.pattern !== 'string') return next(new HttpError(400, 'pattern is required')); - settings.setAutoupdatePattern(req.body.pattern, function (error) { + settings.setAppAutoupdatePattern(req.body.pattern, function (error) { + if (error && error.reason === SettingsError.BAD_FIELD) return next(new HttpError(400, error.message)); + if (error) return next(new HttpError(500, error)); + + next(new HttpSuccess(200)); + }); +} + +function getBoxAutoupdatePattern(req, res, next) { + settings.getBoxAutoupdatePattern(function (error, pattern) { + if (error) return next(new HttpError(500, error)); + + next(new HttpSuccess(200, { pattern: pattern })); + }); +} + +function setBoxAutoupdatePattern(req, res, next) { + assert.strictEqual(typeof req.body, 'object'); + + if (typeof req.body.pattern !== 'string') return next(new HttpError(400, 'pattern is required')); + + settings.setBoxAutoupdatePattern(req.body.pattern, function (error) { if (error && error.reason === SettingsError.BAD_FIELD) return next(new HttpError(400, error.message)); if (error) return next(new HttpError(500, error)); diff --git a/src/routes/test/settings-test.js b/src/routes/test/settings-test.js index fd4795c2e..1d9393ad6 100644 --- a/src/routes/test/settings-test.js +++ b/src/routes/test/settings-test.js @@ -63,9 +63,9 @@ describe('Settings API', function () { before(setup); after(cleanup); - describe('autoupdate_pattern', function () { - it('can get auto update pattern (default)', function (done) { - superagent.get(SERVER_URL + '/api/v1/settings/autoupdate_pattern') + describe('app_autoupdate_pattern', function () { + it('can get app auto update pattern (default)', function (done) { + superagent.get(SERVER_URL + '/api/v1/settings/app_autoupdate_pattern') .query({ access_token: token }) .end(function (err, res) { expect(res.statusCode).to.equal(200); @@ -74,8 +74,8 @@ describe('Settings API', function () { }); }); - it('cannot set autoupdate_pattern without pattern', function (done) { - superagent.post(SERVER_URL + '/api/v1/settings/autoupdate_pattern') + it('cannot set app_autoupdate_pattern without pattern', function (done) { + superagent.post(SERVER_URL + '/api/v1/settings/app_autoupdate_pattern') .query({ access_token: token }) .end(function (err, res) { expect(res.statusCode).to.equal(400); @@ -83,13 +83,13 @@ describe('Settings API', function () { }); }); - it('can set autoupdate_pattern', function (done) { + it('can set app_autoupdate_pattern', function (done) { var eventPattern = null; - settings.events.on(settings.AUTOUPDATE_PATTERN_KEY, function (pattern) { + settings.events.on(settings.APP_AUTOUPDATE_PATTERN_KEY, function (pattern) { eventPattern = pattern; }); - superagent.post(SERVER_URL + '/api/v1/settings/autoupdate_pattern') + superagent.post(SERVER_URL + '/api/v1/settings/app_autoupdate_pattern') .query({ access_token: token }) .send({ pattern: '00 30 11 * * 1-5' }) .end(function (err, res) { @@ -99,13 +99,13 @@ describe('Settings API', function () { }); }); - it('can set autoupdate_pattern to never', function (done) { + it('can set app_autoupdate_pattern to never', function (done) { var eventPattern = null; - settings.events.on(settings.AUTOUPDATE_PATTERN_KEY, function (pattern) { + settings.events.on(settings.APP_AUTOUPDATE_PATTERN_KEY, function (pattern) { eventPattern = pattern; }); - superagent.post(SERVER_URL + '/api/v1/settings/autoupdate_pattern') + superagent.post(SERVER_URL + '/api/v1/settings/app_autoupdate_pattern') .query({ access_token: token }) .send({ pattern: constants.AUTOUPDATE_PATTERN_NEVER }) .end(function (err, res) { @@ -115,8 +115,71 @@ describe('Settings API', function () { }); }); - it('cannot set invalid autoupdate_pattern', function (done) { - superagent.post(SERVER_URL + '/api/v1/settings/autoupdate_pattern') + it('cannot set invalid app_autoupdate_pattern', function (done) { + superagent.post(SERVER_URL + '/api/v1/settings/app_autoupdate_pattern') + .query({ access_token: token }) + .send({ pattern: '1 3 x 5 6' }) + .end(function (err, res) { + expect(res.statusCode).to.equal(400); + done(); + }); + }); + }); + + describe('box_autoupdate_pattern', function () { + it('can get app auto update pattern (default)', function (done) { + superagent.get(SERVER_URL + '/api/v1/settings/box_autoupdate_pattern') + .query({ access_token: token }) + .end(function (err, res) { + expect(res.statusCode).to.equal(200); + expect(res.body.pattern).to.be.ok(); + done(); + }); + }); + + it('cannot set box_autoupdate_pattern without pattern', function (done) { + superagent.post(SERVER_URL + '/api/v1/settings/box_autoupdate_pattern') + .query({ access_token: token }) + .end(function (err, res) { + expect(res.statusCode).to.equal(400); + done(); + }); + }); + + it('can set box_autoupdate_pattern', function (done) { + var eventPattern = null; + settings.events.on(settings.BOX_AUTOUPDATE_PATTERN_KEY, function (pattern) { + eventPattern = pattern; + }); + + superagent.post(SERVER_URL + '/api/v1/settings/box_autoupdate_pattern') + .query({ access_token: token }) + .send({ pattern: '00 30 11 * * 1-5' }) + .end(function (err, res) { + expect(res.statusCode).to.equal(200); + expect(eventPattern === '00 30 11 * * 1-5').to.be.ok(); + done(); + }); + }); + + it('can set box_autoupdate_pattern to never', function (done) { + var eventPattern = null; + settings.events.on(settings.BOX_AUTOUPDATE_PATTERN_KEY, function (pattern) { + eventPattern = pattern; + }); + + superagent.post(SERVER_URL + '/api/v1/settings/box_autoupdate_pattern') + .query({ access_token: token }) + .send({ pattern: constants.AUTOUPDATE_PATTERN_NEVER }) + .end(function (err, res) { + expect(res.statusCode).to.equal(200); + expect(eventPattern).to.eql(constants.AUTOUPDATE_PATTERN_NEVER); + done(); + }); + }); + + it('cannot set invalid box_autoupdate_pattern', function (done) { + superagent.post(SERVER_URL + '/api/v1/settings/box_autoupdate_pattern') .query({ access_token: token }) .send({ pattern: '1 3 x 5 6' }) .end(function (err, res) { diff --git a/src/server.js b/src/server.js index 740ebaa17..d98730e02 100644 --- a/src/server.js +++ b/src/server.js @@ -195,8 +195,10 @@ function initializeExpressSync() { router.post('/api/v1/apps/:id/upload', appsScope, routes.user.requireAdmin, multipart, routes.apps.uploadFile); // settings routes (these are for the settings tab - avatar & name have public routes for normal users. see above) - router.get ('/api/v1/settings/autoupdate_pattern', settingsScope, routes.user.requireAdmin, routes.settings.getAutoupdatePattern); - router.post('/api/v1/settings/autoupdate_pattern', settingsScope, routes.user.requireAdmin, routes.settings.setAutoupdatePattern); + router.get ('/api/v1/settings/app_autoupdate_pattern', settingsScope, routes.user.requireAdmin, routes.settings.getAppAutoupdatePattern); + router.post('/api/v1/settings/app_autoupdate_pattern', settingsScope, routes.user.requireAdmin, routes.settings.setAppAutoupdatePattern); + router.get ('/api/v1/settings/box_autoupdate_pattern', settingsScope, routes.user.requireAdmin, routes.settings.getBoxAutoupdatePattern); + router.post('/api/v1/settings/box_autoupdate_pattern', settingsScope, routes.user.requireAdmin, routes.settings.setBoxAutoupdatePattern); router.get ('/api/v1/settings/cloudron_name', settingsScope, routes.user.requireAdmin, routes.settings.getCloudronName); router.post('/api/v1/settings/cloudron_name', settingsScope, routes.user.requireAdmin, routes.settings.setCloudronName); router.get ('/api/v1/settings/cloudron_avatar', settingsScope, routes.user.requireAdmin, routes.settings.getCloudronAvatar); diff --git a/src/settings.js b/src/settings.js index 2bc695a3d..dacee89ff 100644 --- a/src/settings.js +++ b/src/settings.js @@ -6,8 +6,11 @@ exports = module.exports = { initialize: initialize, uninitialize: uninitialize, - getAutoupdatePattern: getAutoupdatePattern, - setAutoupdatePattern: setAutoupdatePattern, + getAppAutoupdatePattern: getAppAutoupdatePattern, + setAppAutoupdatePattern: setAppAutoupdatePattern, + + getBoxAutoupdatePattern: getBoxAutoupdatePattern, + setBoxAutoupdatePattern: setBoxAutoupdatePattern, getTimeZone: getTimeZone, setTimeZone: setTimeZone, @@ -45,7 +48,8 @@ exports = module.exports = { CAAS_CONFIG_KEY: 'caas_config', // strings - AUTOUPDATE_PATTERN_KEY: 'autoupdate_pattern', + APP_AUTOUPDATE_PATTERN_KEY: 'autoupdate_pattern', + BOX_AUTOUPDATE_PATTERN_KEY: 'box_autoupdate_pattern', TIME_ZONE_KEY: 'time_zone', CLOUDRON_NAME_KEY: 'cloudron_name', @@ -69,7 +73,8 @@ var assert = require('assert'), var gDefaults = (function () { var result = { }; - result[exports.AUTOUPDATE_PATTERN_KEY] = constants.AUTOUPDATE_PATTERN_NEVER; + result[exports.APP_AUTOUPDATE_PATTERN_KEY] = constants.AUTOUPDATE_PATTERN_NEVER; + result[exports.BOX_AUTOUPDATE_PATTERN_KEY] = '00 00 1,3,5,23 * * *'; result[exports.TIME_ZONE_KEY] = 'America/Los_Angeles'; result[exports.CLOUDRON_NAME_KEY] = 'Cloudron'; result[exports.DYNAMIC_DNS_KEY] = false; @@ -125,7 +130,7 @@ function uninitialize(callback) { callback(); } -function setAutoupdatePattern(pattern, callback) { +function setAppAutoupdatePattern(pattern, callback) { assert.strictEqual(typeof pattern, 'string'); assert.strictEqual(typeof callback, 'function'); @@ -134,20 +139,49 @@ function setAutoupdatePattern(pattern, callback) { if (!job) return callback(new SettingsError(SettingsError.BAD_FIELD, 'Invalid pattern')); } - settingsdb.set(exports.AUTOUPDATE_PATTERN_KEY, pattern, function (error) { + settingsdb.set(exports.APP_AUTOUPDATE_PATTERN_KEY, pattern, function (error) { if (error) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, error)); - exports.events.emit(exports.AUTOUPDATE_PATTERN_KEY, pattern); + exports.events.emit(exports.APP_AUTOUPDATE_PATTERN_KEY, pattern); return callback(null); }); } -function getAutoupdatePattern(callback) { +function getAppAutoupdatePattern(callback) { assert.strictEqual(typeof callback, 'function'); - settingsdb.get(exports.AUTOUPDATE_PATTERN_KEY, function (error, pattern) { - if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, gDefaults[exports.AUTOUPDATE_PATTERN_KEY]); + settingsdb.get(exports.APP_AUTOUPDATE_PATTERN_KEY, function (error, pattern) { + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, gDefaults[exports.APP_AUTOUPDATE_PATTERN_KEY]); + if (error) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, error)); + + callback(null, pattern); + }); +} + +function setBoxAutoupdatePattern(pattern, callback) { + assert.strictEqual(typeof pattern, 'string'); + assert.strictEqual(typeof callback, 'function'); + + if (pattern !== constants.AUTOUPDATE_PATTERN_NEVER) { // check if pattern is valid + var job = safe.safeCall(function () { return new CronJob(pattern); }); + if (!job) return callback(new SettingsError(SettingsError.BAD_FIELD, 'Invalid pattern')); + } + + settingsdb.set(exports.BOX_AUTOUPDATE_PATTERN_KEY, pattern, function (error) { + if (error) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, error)); + + exports.events.emit(exports.BOX_AUTOUPDATE_PATTERN_KEY, pattern); + + return callback(null); + }); +} + +function getBoxAutoupdatePattern(callback) { + assert.strictEqual(typeof callback, 'function'); + + settingsdb.get(exports.BOX_AUTOUPDATE_PATTERN_KEY, function (error, pattern) { + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, gDefaults[exports.BOX_AUTOUPDATE_PATTERN_KEY]); if (error) return callback(new SettingsError(SettingsError.INTERNAL_ERROR, error)); callback(null, pattern); diff --git a/src/test/settings-test.js b/src/test/settings-test.js index ade975169..79daf848d 100644 --- a/src/test/settings-test.js +++ b/src/test/settings-test.js @@ -68,14 +68,22 @@ describe('Settings', function () { }); }); - it('can get default autoupdate_pattern', function (done) { - settings.getAutoupdatePattern(function (error, pattern) { + it('can get default app_autoupdate_pattern', function (done) { + settings.getAppAutoupdatePattern(function (error, pattern) { expect(error).to.be(null); expect(pattern).to.be(constants.AUTOUPDATE_PATTERN_NEVER); done(); }); }); + it('can get default box_autoupdate_pattern', function (done) { + settings.getBoxAutoupdatePattern(function (error, pattern) { + expect(error).to.be(null); + expect(pattern).to.be('00 00 1,3,5,23 * * *'); + done(); + }); + }); + it ('can get default cloudron name', function (done) { settings.getCloudronName(function (error, name) { expect(error).to.be(null); @@ -131,7 +139,8 @@ describe('Settings', function () { settings.getAll(function (error, allSettings) { expect(error).to.be(null); expect(allSettings[settings.TIME_ZONE_KEY]).to.be.a('string'); - expect(allSettings[settings.AUTOUPDATE_PATTERN_KEY]).to.be.a('string'); + expect(allSettings[settings.APP_AUTOUPDATE_PATTERN_KEY]).to.be.a('string'); + expect(allSettings[settings.BOX_AUTOUPDATE_PATTERN_KEY]).to.be.a('string'); expect(allSettings[settings.CLOUDRON_NAME_KEY]).to.be.a('string'); done(); }); diff --git a/src/updatechecker.js b/src/updatechecker.js index 97d24ab01..3439c6560 100644 --- a/src/updatechecker.js +++ b/src/updatechecker.js @@ -112,7 +112,7 @@ function checkAppUpdates(callback) { } // only send notifications if update pattern is 'never' - settings.getAutoupdatePattern(function (error, result) { + settings.getAppAutoupdatePattern(function (error, result) { if (error) { debug(error); } else if (result === constants.AUTOUPDATE_PATTERN_NEVER) { @@ -168,7 +168,7 @@ function checkBoxUpdates(callback) { } // only send notifications if update pattern is 'never' - settings.getAutoupdatePattern(function (error, result) { + settings.getAppAutoupdatePattern(function (error, result) { if (error) debug(error); else if (result === constants.AUTOUPDATE_PATTERN_NEVER) mailer.boxUpdateAvailable(true /* hasSubscription */, updateInfo.version, updateInfo.changelog);