2017-04-26 20:55:54 +00:00
|
|
|
/* global getStyles, saveStyle, styleSectionsEqual, chromeLocal */
|
2017-04-24 13:29:48 +00:00
|
|
|
/* global getStyleDigests, updateStyleDigest */
|
2017-02-08 15:15:35 +00:00
|
|
|
'use strict';
|
|
|
|
|
2017-04-20 18:27:10 +00:00
|
|
|
// eslint-disable-next-line no-var
|
|
|
|
var updater = {
|
2017-02-08 15:15:35 +00:00
|
|
|
|
2017-04-20 18:27:10 +00:00
|
|
|
COUNT: 'count',
|
|
|
|
UPDATED: 'updated',
|
|
|
|
SKIPPED: 'skipped',
|
|
|
|
DONE: 'done',
|
2017-02-08 15:15:35 +00:00
|
|
|
|
2017-04-24 13:29:48 +00:00
|
|
|
// details for SKIPPED status
|
|
|
|
EDITED: 'locally edited',
|
2017-04-25 22:06:16 +00:00
|
|
|
MAYBE_EDITED: 'may be locally edited',
|
2017-04-24 13:29:48 +00:00
|
|
|
SAME_MD5: 'up-to-date: MD5 is unchanged',
|
|
|
|
SAME_CODE: 'up-to-date: code sections are unchanged',
|
|
|
|
ERROR_MD5: 'error: MD5 is invalid',
|
|
|
|
ERROR_JSON: 'error: JSON is invalid',
|
|
|
|
|
2017-04-20 18:27:10 +00:00
|
|
|
lastUpdateTime: parseInt(localStorage.lastUpdateTime) || Date.now(),
|
2017-02-08 15:15:35 +00:00
|
|
|
|
2017-04-24 13:29:48 +00:00
|
|
|
checkAllStyles({observer = () => {}, save = true, ignoreDigest} = {}) {
|
2017-04-28 16:44:03 +00:00
|
|
|
updater.checkAllStyles.running = true;
|
2017-04-20 18:27:10 +00:00
|
|
|
updater.resetInterval();
|
2017-04-25 21:48:27 +00:00
|
|
|
return getStyles({}).then(styles => {
|
|
|
|
styles = styles.filter(style => style.updateUrl);
|
|
|
|
observer(updater.COUNT, styles.length);
|
2017-04-28 16:44:03 +00:00
|
|
|
updater.log('');
|
2017-04-26 20:55:54 +00:00
|
|
|
updater.log(`${save ? 'Scheduled' : 'Manual'} update check for ${styles.length} styles`);
|
2017-04-25 21:48:27 +00:00
|
|
|
return Promise.all(
|
|
|
|
styles.map(style =>
|
|
|
|
updater.checkStyle({style, observer, save, ignoreDigest})));
|
|
|
|
}).then(() => {
|
|
|
|
observer(updater.DONE);
|
2017-04-26 20:55:54 +00:00
|
|
|
updater.log('');
|
2017-04-28 16:44:03 +00:00
|
|
|
updater.checkAllStyles.running = false;
|
2017-02-08 15:15:35 +00:00
|
|
|
});
|
2017-04-20 18:27:10 +00:00
|
|
|
},
|
|
|
|
|
2017-04-24 13:29:48 +00:00
|
|
|
checkStyle({style, observer = () => {}, save = true, ignoreDigest}) {
|
2017-04-23 12:19:18 +00:00
|
|
|
let hasDigest;
|
2017-04-24 13:29:48 +00:00
|
|
|
/*
|
|
|
|
Original style digests are calculated in these cases:
|
|
|
|
* style is installed or updated from server
|
|
|
|
* style is checked for an update and its code is equal to the server code
|
|
|
|
|
|
|
|
Update check proceeds in these cases:
|
|
|
|
* style has the original digest and it's equal to the current digest
|
|
|
|
* [ignoreDigest: true] style doesn't yet have the original digest but we ignore it
|
|
|
|
* [ignoreDigest: none/false] style doesn't yet have the original digest
|
|
|
|
so we compare the code to the server code and if it's the same we save the digest,
|
|
|
|
otherwise we skip the style and report MAYBE_EDITED status
|
|
|
|
|
|
|
|
'ignoreDigest' option is set on the second manual individual update check on the manage page.
|
|
|
|
*/
|
2017-04-23 12:19:18 +00:00
|
|
|
return getStyleDigests(style)
|
|
|
|
.then(fetchMd5IfNotEdited)
|
|
|
|
.then(fetchCodeIfMd5Changed)
|
|
|
|
.then(saveIfUpdated)
|
2017-04-26 20:55:54 +00:00
|
|
|
.then(saved => {
|
|
|
|
observer(updater.UPDATED, saved);
|
|
|
|
updater.log(updater.UPDATED + ` #${saved.id} ${saved.name}`);
|
|
|
|
})
|
|
|
|
.catch(err => {
|
|
|
|
observer(updater.SKIPPED, style, err);
|
|
|
|
err = err === 0 ? 'server unreachable' : err;
|
|
|
|
updater.log(updater.SKIPPED + ` (${err}) #${style.id} ${style.name}`);
|
|
|
|
});
|
2017-04-20 18:27:10 +00:00
|
|
|
|
2017-04-23 12:19:18 +00:00
|
|
|
function fetchMd5IfNotEdited([originalDigest, current]) {
|
|
|
|
hasDigest = Boolean(originalDigest);
|
2017-04-24 13:29:48 +00:00
|
|
|
if (hasDigest && !ignoreDigest && originalDigest != current) {
|
|
|
|
return Promise.reject(updater.EDITED);
|
2017-04-23 12:19:18 +00:00
|
|
|
}
|
|
|
|
return download(style.md5Url);
|
|
|
|
}
|
|
|
|
|
|
|
|
function fetchCodeIfMd5Changed(md5) {
|
|
|
|
if (!md5 || md5.length != 32) {
|
2017-04-24 13:29:48 +00:00
|
|
|
return Promise.reject(updater.ERROR_MD5);
|
2017-04-23 12:19:18 +00:00
|
|
|
}
|
2017-04-25 11:17:37 +00:00
|
|
|
if (md5 == style.originalMd5 && hasDigest && !ignoreDigest) {
|
2017-04-24 13:29:48 +00:00
|
|
|
return Promise.reject(updater.SAME_MD5);
|
2017-04-23 12:19:18 +00:00
|
|
|
}
|
|
|
|
return download(style.updateUrl);
|
|
|
|
}
|
|
|
|
|
|
|
|
function saveIfUpdated(text) {
|
|
|
|
const json = tryJSONparse(text);
|
|
|
|
if (!styleJSONseemsValid(json)) {
|
2017-04-24 13:29:48 +00:00
|
|
|
return Promise.reject(updater.ERROR_JSON);
|
2017-04-23 12:19:18 +00:00
|
|
|
}
|
|
|
|
json.id = style.id;
|
|
|
|
if (styleSectionsEqual(json, style)) {
|
2017-04-24 13:29:48 +00:00
|
|
|
// JSONs may have different order of items even if sections are effectively equal
|
|
|
|
// so we'll update the digest anyway
|
|
|
|
updateStyleDigest(json);
|
|
|
|
return Promise.reject(updater.SAME_CODE);
|
|
|
|
} else if (!hasDigest && !ignoreDigest) {
|
|
|
|
return Promise.reject(updater.MAYBE_EDITED);
|
2017-04-23 12:19:18 +00:00
|
|
|
}
|
|
|
|
return !save ? json :
|
|
|
|
saveStyle(Object.assign(json, {
|
|
|
|
name: null, // keep local name customizations
|
|
|
|
reason: 'update',
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
function styleJSONseemsValid(json) {
|
|
|
|
return json
|
|
|
|
&& json.sections
|
|
|
|
&& json.sections.length
|
|
|
|
&& typeof json.sections.every == 'function'
|
|
|
|
&& typeof json.sections[0].code == 'string';
|
|
|
|
}
|
2017-04-20 18:27:10 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
schedule() {
|
|
|
|
const interval = prefs.get('updateInterval') * 60 * 60 * 1000;
|
2017-02-14 15:35:53 +00:00
|
|
|
if (interval) {
|
2017-04-20 18:27:10 +00:00
|
|
|
const elapsed = Math.max(0, Date.now() - updater.lastUpdateTime);
|
|
|
|
debounce(updater.checkAllStyles, Math.max(10e3, interval - elapsed));
|
2017-04-22 18:02:49 +00:00
|
|
|
} else {
|
2017-04-20 18:27:10 +00:00
|
|
|
debounce.unregister(updater.checkAllStyles);
|
2017-02-14 15:35:53 +00:00
|
|
|
}
|
2017-04-20 18:27:10 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
resetInterval() {
|
|
|
|
localStorage.lastUpdateTime = updater.lastUpdateTime = Date.now();
|
|
|
|
updater.schedule();
|
|
|
|
},
|
2017-04-26 20:55:54 +00:00
|
|
|
|
2017-04-26 23:06:16 +00:00
|
|
|
log: (() => {
|
|
|
|
let queue = [];
|
|
|
|
let lastWriteTime = 0;
|
|
|
|
return text => {
|
2017-04-27 12:54:55 +00:00
|
|
|
queue.push({text, time: new Date().toLocaleString()});
|
2017-04-28 16:44:03 +00:00
|
|
|
debounce(flushQueue, text && updater.checkAllStyles.running ? 1e3 : 0);
|
2017-04-26 23:06:16 +00:00
|
|
|
};
|
|
|
|
function flushQueue() {
|
|
|
|
chromeLocal.getValue('updateLog').then((lines = []) => {
|
2017-04-27 12:54:55 +00:00
|
|
|
const time = Date.now() - lastWriteTime > 11e3 ? queue[0].time + ' ' : '';
|
2017-04-28 16:44:03 +00:00
|
|
|
if (!queue[0].text) {
|
|
|
|
queue.shift();
|
|
|
|
if (lines[lines.length - 1]) {
|
|
|
|
lines.push('');
|
|
|
|
}
|
|
|
|
}
|
2017-04-26 23:06:16 +00:00
|
|
|
lines.splice(0, lines.length - 1000);
|
2017-04-27 12:54:55 +00:00
|
|
|
lines.push(time + queue[0].text);
|
|
|
|
lines.push(...queue.slice(1).map(item => item.text));
|
2017-04-26 23:06:16 +00:00
|
|
|
chromeLocal.setValue('updateLog', lines);
|
2017-04-27 12:54:55 +00:00
|
|
|
lastWriteTime = Date.now();
|
2017-04-26 23:06:16 +00:00
|
|
|
queue = [];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
})(),
|
2017-04-20 18:27:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
updater.schedule();
|
|
|
|
prefs.subscribe(updater.schedule, ['updateInterval']);
|