mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-04-12 00:38:22 +02:00
Update:Remove node-cron dependency
This commit is contained in:
parent
26ef275ab4
commit
b7e546f2f5
14
package-lock.json
generated
14
package-lock.json
generated
@ -22,7 +22,6 @@
|
|||||||
"htmlparser2": "^8.0.1",
|
"htmlparser2": "^8.0.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"libgen": "^2.1.0",
|
"libgen": "^2.1.0",
|
||||||
"node-cron": "^3.0.0",
|
|
||||||
"node-ffprobe": "^3.0.0",
|
"node-ffprobe": "^3.0.0",
|
||||||
"node-stream-zip": "^1.15.0",
|
"node-stream-zip": "^1.15.0",
|
||||||
"proper-lockfile": "^4.1.2",
|
"proper-lockfile": "^4.1.2",
|
||||||
@ -1381,14 +1380,6 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-cron": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-RAWZTNn2M5KDIUV/389UX0EXsqvdFAwc9QwHQceh0Ga56dygqSRthqIjwpgZsoDspHGt2rkHdk9Z4RgfPMdALw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-ffprobe": {
|
"node_modules/node-ffprobe": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-ffprobe/-/node-ffprobe-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-ffprobe/-/node-ffprobe-3.0.0.tgz",
|
||||||
@ -3022,11 +3013,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
|
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
|
||||||
},
|
},
|
||||||
"node-cron": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-RAWZTNn2M5KDIUV/389UX0EXsqvdFAwc9QwHQceh0Ga56dygqSRthqIjwpgZsoDspHGt2rkHdk9Z4RgfPMdALw=="
|
|
||||||
},
|
|
||||||
"node-ffprobe": {
|
"node-ffprobe": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-ffprobe/-/node-ffprobe-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-ffprobe/-/node-ffprobe-3.0.0.tgz",
|
||||||
|
@ -41,7 +41,6 @@
|
|||||||
"htmlparser2": "^8.0.1",
|
"htmlparser2": "^8.0.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"libgen": "^2.1.0",
|
"libgen": "^2.1.0",
|
||||||
"node-cron": "^3.0.0",
|
|
||||||
"node-ffprobe": "^3.0.0",
|
"node-ffprobe": "^3.0.0",
|
||||||
"node-stream-zip": "^1.15.0",
|
"node-stream-zip": "^1.15.0",
|
||||||
"proper-lockfile": "^4.1.2",
|
"proper-lockfile": "^4.1.2",
|
||||||
|
19
server/libs/nodeCron/background-scheduled-task/daemon.js
Normal file
19
server/libs/nodeCron/background-scheduled-task/daemon.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
const ScheduledTask = require('../scheduled-task');
|
||||||
|
|
||||||
|
let scheduledTask;
|
||||||
|
|
||||||
|
function register(message){
|
||||||
|
const script = require(message.path);
|
||||||
|
scheduledTask = new ScheduledTask(message.cron, script.task, message.options);
|
||||||
|
scheduledTask.on('task-done', (result) => {
|
||||||
|
process.send({ type: 'task-done', result});
|
||||||
|
});
|
||||||
|
process.send({ type: 'registred' });
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('message', (message) => {
|
||||||
|
switch(message.type){
|
||||||
|
case 'register':
|
||||||
|
return register(message);
|
||||||
|
}
|
||||||
|
});
|
67
server/libs/nodeCron/background-scheduled-task/index.js
Normal file
67
server/libs/nodeCron/background-scheduled-task/index.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
const EventEmitter = require('events');
|
||||||
|
const path = require('path');
|
||||||
|
const { fork } = require('child_process');
|
||||||
|
const { getId } = require('../../../utils/index')
|
||||||
|
|
||||||
|
const daemonPath = `${__dirname}/daemon.js`;
|
||||||
|
|
||||||
|
class BackgroundScheduledTask extends EventEmitter {
|
||||||
|
constructor(cronExpression, taskPath, options) {
|
||||||
|
super();
|
||||||
|
if (!options) {
|
||||||
|
options = {
|
||||||
|
scheduled: true,
|
||||||
|
recoverMissedExecutions: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.cronExpression = cronExpression;
|
||||||
|
this.taskPath = taskPath;
|
||||||
|
this.options = options;
|
||||||
|
this.options.name = this.options.name || getId()
|
||||||
|
|
||||||
|
if (options.scheduled) {
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.stop();
|
||||||
|
this.forkProcess = fork(daemonPath);
|
||||||
|
|
||||||
|
this.forkProcess.on('message', (message) => {
|
||||||
|
switch (message.type) {
|
||||||
|
case 'task-done':
|
||||||
|
this.emit('task-done', message.result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let options = this.options;
|
||||||
|
options.scheduled = true;
|
||||||
|
|
||||||
|
this.forkProcess.send({
|
||||||
|
type: 'register',
|
||||||
|
path: path.resolve(this.taskPath),
|
||||||
|
cron: this.cronExpression,
|
||||||
|
options: options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if (this.forkProcess) {
|
||||||
|
this.forkProcess.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pid() {
|
||||||
|
if (this.forkProcess) {
|
||||||
|
return this.forkProcess.pid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isRunning() {
|
||||||
|
return !this.forkProcess.killed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BackgroundScheduledTask;
|
@ -0,0 +1,21 @@
|
|||||||
|
'use strict';
|
||||||
|
module.exports = (() => {
|
||||||
|
function convertAsterisk(expression, replecement){
|
||||||
|
if(expression.indexOf('*') !== -1){
|
||||||
|
return expression.replace('*', replecement);
|
||||||
|
}
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertAsterisksToRanges(expressions){
|
||||||
|
expressions[0] = convertAsterisk(expressions[0], '0-59');
|
||||||
|
expressions[1] = convertAsterisk(expressions[1], '0-59');
|
||||||
|
expressions[2] = convertAsterisk(expressions[2], '0-23');
|
||||||
|
expressions[3] = convertAsterisk(expressions[3], '1-31');
|
||||||
|
expressions[4] = convertAsterisk(expressions[4], '1-12');
|
||||||
|
expressions[5] = convertAsterisk(expressions[5], '0-6');
|
||||||
|
return expressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertAsterisksToRanges;
|
||||||
|
})();
|
69
server/libs/nodeCron/convert-expression/index.js
Normal file
69
server/libs/nodeCron/convert-expression/index.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// SOURCE: https://github.com/node-cron/node-cron
|
||||||
|
// LICENSE: https://github.com/node-cron/node-cron/blob/master/LICENSE.md
|
||||||
|
|
||||||
|
const monthNamesConversion = require('./month-names-conversion');
|
||||||
|
const weekDayNamesConversion = require('./week-day-names-conversion');
|
||||||
|
const convertAsterisksToRanges = require('./asterisk-to-range-conversion');
|
||||||
|
const convertRanges = require('./range-conversion');
|
||||||
|
const convertSteps = require('./step-values-conversion');
|
||||||
|
|
||||||
|
module.exports = (() => {
|
||||||
|
|
||||||
|
function appendSeccondExpression(expressions) {
|
||||||
|
if (expressions.length === 5) {
|
||||||
|
return ['0'].concat(expressions);
|
||||||
|
}
|
||||||
|
return expressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSpaces(str) {
|
||||||
|
return str.replace(/\s{2,}/g, ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function that takes care of normalization.
|
||||||
|
function normalizeIntegers(expressions) {
|
||||||
|
for (let i = 0; i < expressions.length; i++) {
|
||||||
|
const numbers = expressions[i].split(',');
|
||||||
|
for (let j = 0; j < numbers.length; j++) {
|
||||||
|
numbers[j] = parseInt(numbers[j]);
|
||||||
|
}
|
||||||
|
expressions[i] = numbers;
|
||||||
|
}
|
||||||
|
return expressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The node-cron core allows only numbers (including multiple numbers e.g 1,2).
|
||||||
|
* This module is going to translate the month names, week day names and ranges
|
||||||
|
* to integers relatives.
|
||||||
|
*
|
||||||
|
* Month names example:
|
||||||
|
* - expression 0 1 1 January,Sep *
|
||||||
|
* - Will be translated to 0 1 1 1,9 *
|
||||||
|
*
|
||||||
|
* Week day names example:
|
||||||
|
* - expression 0 1 1 2 Monday,Sat
|
||||||
|
* - Will be translated to 0 1 1 1,5 *
|
||||||
|
*
|
||||||
|
* Ranges example:
|
||||||
|
* - expression 1-5 * * * *
|
||||||
|
* - Will be translated to 1,2,3,4,5 * * * *
|
||||||
|
*/
|
||||||
|
function interprete(expression) {
|
||||||
|
let expressions = removeSpaces(expression).split(' ');
|
||||||
|
expressions = appendSeccondExpression(expressions);
|
||||||
|
expressions[4] = monthNamesConversion(expressions[4]);
|
||||||
|
expressions[5] = weekDayNamesConversion(expressions[5]);
|
||||||
|
expressions = convertAsterisksToRanges(expressions);
|
||||||
|
expressions = convertRanges(expressions);
|
||||||
|
expressions = convertSteps(expressions);
|
||||||
|
|
||||||
|
expressions = normalizeIntegers(expressions);
|
||||||
|
|
||||||
|
return expressions.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
return interprete;
|
||||||
|
})();
|
@ -0,0 +1,22 @@
|
|||||||
|
'use strict';
|
||||||
|
module.exports = (() => {
|
||||||
|
const months = ['january','february','march','april','may','june','july',
|
||||||
|
'august','september','october','november','december'];
|
||||||
|
const shortMonths = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
|
||||||
|
'sep', 'oct', 'nov', 'dec'];
|
||||||
|
|
||||||
|
function convertMonthName(expression, items){
|
||||||
|
for(let i = 0; i < items.length; i++){
|
||||||
|
expression = expression.replace(new RegExp(items[i], 'gi'), parseInt(i, 10) + 1);
|
||||||
|
}
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
function interprete(monthExpression){
|
||||||
|
monthExpression = convertMonthName(monthExpression, months);
|
||||||
|
monthExpression = convertMonthName(monthExpression, shortMonths);
|
||||||
|
return monthExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
return interprete;
|
||||||
|
})();
|
39
server/libs/nodeCron/convert-expression/range-conversion.js
Normal file
39
server/libs/nodeCron/convert-expression/range-conversion.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
'use strict';
|
||||||
|
module.exports = ( () => {
|
||||||
|
function replaceWithRange(expression, text, init, end) {
|
||||||
|
|
||||||
|
const numbers = [];
|
||||||
|
let last = parseInt(end);
|
||||||
|
let first = parseInt(init);
|
||||||
|
|
||||||
|
if(first > last){
|
||||||
|
last = parseInt(init);
|
||||||
|
first = parseInt(end);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let i = first; i <= last; i++) {
|
||||||
|
numbers.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression.replace(new RegExp(text, 'i'), numbers.join());
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertRange(expression){
|
||||||
|
const rangeRegEx = /(\d+)-(\d+)/;
|
||||||
|
let match = rangeRegEx.exec(expression);
|
||||||
|
while(match !== null && match.length > 0){
|
||||||
|
expression = replaceWithRange(expression, match[0], match[1], match[2]);
|
||||||
|
match = rangeRegEx.exec(expression);
|
||||||
|
}
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertAllRanges(expressions){
|
||||||
|
for(let i = 0; i < expressions.length; i++){
|
||||||
|
expressions[i] = convertRange(expressions[i]);
|
||||||
|
}
|
||||||
|
return expressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertAllRanges;
|
||||||
|
})();
|
@ -0,0 +1,30 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = (() => {
|
||||||
|
function convertSteps(expressions){
|
||||||
|
var stepValuePattern = /^(.+)\/(\w+)$/;
|
||||||
|
for(var i = 0; i < expressions.length; i++){
|
||||||
|
var match = stepValuePattern.exec(expressions[i]);
|
||||||
|
var isStepValue = match !== null && match.length > 0;
|
||||||
|
if(isStepValue){
|
||||||
|
var baseDivider = match[2];
|
||||||
|
if(isNaN(baseDivider)){
|
||||||
|
throw baseDivider + ' is not a valid step value';
|
||||||
|
}
|
||||||
|
var values = match[1].split(',');
|
||||||
|
var stepValues = [];
|
||||||
|
var divider = parseInt(baseDivider, 10);
|
||||||
|
for(var j = 0; j <= values.length; j++){
|
||||||
|
var value = parseInt(values[j], 10);
|
||||||
|
if(value % divider === 0){
|
||||||
|
stepValues.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expressions[i] = stepValues.join(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertSteps;
|
||||||
|
})();
|
@ -0,0 +1,21 @@
|
|||||||
|
'use strict';
|
||||||
|
module.exports = (() => {
|
||||||
|
const weekDays = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
|
||||||
|
'friday', 'saturday'];
|
||||||
|
const shortWeekDays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
|
||||||
|
|
||||||
|
function convertWeekDayName(expression, items){
|
||||||
|
for(let i = 0; i < items.length; i++){
|
||||||
|
expression = expression.replace(new RegExp(items[i], 'gi'), parseInt(i, 10));
|
||||||
|
}
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertWeekDays(expression){
|
||||||
|
expression = expression.replace('7', '0');
|
||||||
|
expression = convertWeekDayName(expression, weekDays);
|
||||||
|
return convertWeekDayName(expression, shortWeekDays);
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertWeekDays;
|
||||||
|
})();
|
64
server/libs/nodeCron/index.js
Normal file
64
server/libs/nodeCron/index.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const ScheduledTask = require('./scheduled-task');
|
||||||
|
const BackgroundScheduledTask = require('./background-scheduled-task');
|
||||||
|
const validation = require('./pattern-validation');
|
||||||
|
const storage = require('./storage');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} CronScheduleOptions
|
||||||
|
* @prop {boolean} [scheduled] if a scheduled task is ready and running to be
|
||||||
|
* performed when the time matches the cron expression.
|
||||||
|
* @prop {string} [timezone] the timezone to execute the task in.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new task to execute the given function when the cron
|
||||||
|
* expression ticks.
|
||||||
|
*
|
||||||
|
* @param {string} expression The cron expression.
|
||||||
|
* @param {Function} func The task to be executed.
|
||||||
|
* @param {CronScheduleOptions} [options] A set of options for the scheduled task.
|
||||||
|
* @returns {ScheduledTask} The scheduled task.
|
||||||
|
*/
|
||||||
|
function schedule(expression, func, options) {
|
||||||
|
const task = createTask(expression, func, options);
|
||||||
|
|
||||||
|
storage.save(task);
|
||||||
|
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTask(expression, func, options) {
|
||||||
|
if (typeof func === 'string')
|
||||||
|
return new BackgroundScheduledTask(expression, func, options);
|
||||||
|
|
||||||
|
return new ScheduledTask(expression, func, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a cron expression is valid.
|
||||||
|
*
|
||||||
|
* @param {string} expression The cron expression.
|
||||||
|
* @returns {boolean} Whether the expression is valid or not.
|
||||||
|
*/
|
||||||
|
function validate(expression) {
|
||||||
|
try {
|
||||||
|
validation(expression);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the scheduled tasks.
|
||||||
|
*
|
||||||
|
* @returns {ScheduledTask[]} The scheduled tasks.
|
||||||
|
*/
|
||||||
|
function getTasks() {
|
||||||
|
return storage.getTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { schedule, validate, getTasks };
|
124
server/libs/nodeCron/pattern-validation.js
Normal file
124
server/libs/nodeCron/pattern-validation.js
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const convertExpression = require('./convert-expression');
|
||||||
|
|
||||||
|
const validationRegex = /^(?:\d+|\*|\*\/\d+)$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} expression The Cron-Job expression.
|
||||||
|
* @param {number} min The minimum value.
|
||||||
|
* @param {number} max The maximum value.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isValidExpression(expression, min, max) {
|
||||||
|
const options = expression.split(',');
|
||||||
|
|
||||||
|
for (const option of options) {
|
||||||
|
const optionAsInt = parseInt(option, 10);
|
||||||
|
|
||||||
|
if (
|
||||||
|
(!Number.isNaN(optionAsInt) &&
|
||||||
|
(optionAsInt < min || optionAsInt > max)) ||
|
||||||
|
!validationRegex.test(option)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} expression The Cron-Job expression.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isInvalidSecond(expression) {
|
||||||
|
return !isValidExpression(expression, 0, 59);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} expression The Cron-Job expression.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isInvalidMinute(expression) {
|
||||||
|
return !isValidExpression(expression, 0, 59);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} expression The Cron-Job expression.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isInvalidHour(expression) {
|
||||||
|
return !isValidExpression(expression, 0, 23);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} expression The Cron-Job expression.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isInvalidDayOfMonth(expression) {
|
||||||
|
return !isValidExpression(expression, 1, 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} expression The Cron-Job expression.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isInvalidMonth(expression) {
|
||||||
|
return !isValidExpression(expression, 1, 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} expression The Cron-Job expression.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isInvalidWeekDay(expression) {
|
||||||
|
return !isValidExpression(expression, 0, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} patterns The Cron-Job expression patterns.
|
||||||
|
* @param {string[]} executablePatterns The executable Cron-Job expression
|
||||||
|
* patterns.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function validateFields(patterns, executablePatterns) {
|
||||||
|
if (isInvalidSecond(executablePatterns[0]))
|
||||||
|
throw new Error(`${patterns[0]} is a invalid expression for second`);
|
||||||
|
|
||||||
|
if (isInvalidMinute(executablePatterns[1]))
|
||||||
|
throw new Error(`${patterns[1]} is a invalid expression for minute`);
|
||||||
|
|
||||||
|
if (isInvalidHour(executablePatterns[2]))
|
||||||
|
throw new Error(`${patterns[2]} is a invalid expression for hour`);
|
||||||
|
|
||||||
|
if (isInvalidDayOfMonth(executablePatterns[3]))
|
||||||
|
throw new Error(
|
||||||
|
`${patterns[3]} is a invalid expression for day of month`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isInvalidMonth(executablePatterns[4]))
|
||||||
|
throw new Error(`${patterns[4]} is a invalid expression for month`);
|
||||||
|
|
||||||
|
if (isInvalidWeekDay(executablePatterns[5]))
|
||||||
|
throw new Error(`${patterns[5]} is a invalid expression for week day`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a Cron-Job expression pattern.
|
||||||
|
*
|
||||||
|
* @param {string} pattern The Cron-Job expression pattern.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function validate(pattern) {
|
||||||
|
if (typeof pattern !== 'string')
|
||||||
|
throw new TypeError('pattern must be a string!');
|
||||||
|
|
||||||
|
const patterns = pattern.split(' ');
|
||||||
|
const executablePatterns = convertExpression(pattern).split(' ');
|
||||||
|
|
||||||
|
if (patterns.length === 5) patterns.unshift('0');
|
||||||
|
|
||||||
|
validateFields(patterns, executablePatterns);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = validate;
|
51
server/libs/nodeCron/scheduled-task.js
Normal file
51
server/libs/nodeCron/scheduled-task.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const EventEmitter = require('events');
|
||||||
|
const Task = require('./task');
|
||||||
|
const Scheduler = require('./scheduler');
|
||||||
|
const { getId } = require('../../utils/index')
|
||||||
|
|
||||||
|
class ScheduledTask extends EventEmitter {
|
||||||
|
constructor(cronExpression, func, options) {
|
||||||
|
super();
|
||||||
|
if (!options) {
|
||||||
|
options = {
|
||||||
|
scheduled: true,
|
||||||
|
recoverMissedExecutions: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.options = options;
|
||||||
|
this.options.name = this.options.name || getId()
|
||||||
|
|
||||||
|
this._task = new Task(func);
|
||||||
|
this._scheduler = new Scheduler(cronExpression, options.timezone, options.recoverMissedExecutions);
|
||||||
|
|
||||||
|
this._scheduler.on('scheduled-time-matched', (now) => {
|
||||||
|
this.now(now);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.scheduled !== false) {
|
||||||
|
this._scheduler.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.runOnInit === true) {
|
||||||
|
this.now('init');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
now(now = 'manual') {
|
||||||
|
let result = this._task.execute(now);
|
||||||
|
this.emit('task-done', result);
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this._scheduler.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this._scheduler.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ScheduledTask;
|
49
server/libs/nodeCron/scheduler.js
Normal file
49
server/libs/nodeCron/scheduler.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const EventEmitter = require('events');
|
||||||
|
const TimeMatcher = require('./time-matcher');
|
||||||
|
|
||||||
|
class Scheduler extends EventEmitter{
|
||||||
|
constructor(pattern, timezone, autorecover){
|
||||||
|
super();
|
||||||
|
this.timeMatcher = new TimeMatcher(pattern, timezone);
|
||||||
|
this.autorecover = autorecover;
|
||||||
|
}
|
||||||
|
|
||||||
|
start(){
|
||||||
|
// clear timeout if exists
|
||||||
|
this.stop();
|
||||||
|
|
||||||
|
let lastCheck = process.hrtime();
|
||||||
|
let lastExecution = this.timeMatcher.apply(new Date());
|
||||||
|
|
||||||
|
const matchTime = () => {
|
||||||
|
const delay = 1000;
|
||||||
|
const elapsedTime = process.hrtime(lastCheck);
|
||||||
|
const elapsedMs = (elapsedTime[0] * 1e9 + elapsedTime[1]) / 1e6;
|
||||||
|
const missedExecutions = Math.floor(elapsedMs / 1000);
|
||||||
|
|
||||||
|
for(let i = missedExecutions; i >= 0; i--){
|
||||||
|
const date = new Date(new Date().getTime() - i * 1000);
|
||||||
|
let date_tmp = this.timeMatcher.apply(date);
|
||||||
|
if(lastExecution.getTime() < date_tmp.getTime() && (i === 0 || this.autorecover) && this.timeMatcher.match(date)){
|
||||||
|
this.emit('scheduled-time-matched', date_tmp);
|
||||||
|
date_tmp.setMilliseconds(0);
|
||||||
|
lastExecution = date_tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastCheck = process.hrtime();
|
||||||
|
this.timeout = setTimeout(matchTime, delay);
|
||||||
|
};
|
||||||
|
matchTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(){
|
||||||
|
if(this.timeout){
|
||||||
|
clearTimeout(this.timeout);
|
||||||
|
}
|
||||||
|
this.timeout = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Scheduler;
|
19
server/libs/nodeCron/storage.js
Normal file
19
server/libs/nodeCron/storage.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
module.exports = (() => {
|
||||||
|
if(!global.scheduledTasks){
|
||||||
|
global.scheduledTasks = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
save: (task) => {
|
||||||
|
if(!task.options){
|
||||||
|
const uuid = require('uuid');
|
||||||
|
task.options = {};
|
||||||
|
task.options.name = uuid.v4();
|
||||||
|
}
|
||||||
|
global.scheduledTasks.set(task.options.name, task);
|
||||||
|
},
|
||||||
|
getTasks: () => {
|
||||||
|
return global.scheduledTasks;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
34
server/libs/nodeCron/task.js
Normal file
34
server/libs/nodeCron/task.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const EventEmitter = require('events');
|
||||||
|
|
||||||
|
class Task extends EventEmitter{
|
||||||
|
constructor(execution){
|
||||||
|
super();
|
||||||
|
if(typeof execution !== 'function') {
|
||||||
|
throw 'execution must be a function';
|
||||||
|
}
|
||||||
|
this._execution = execution;
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(now) {
|
||||||
|
let exec;
|
||||||
|
try {
|
||||||
|
exec = this._execution(now);
|
||||||
|
} catch (error) {
|
||||||
|
return this.emit('task-failed', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exec instanceof Promise) {
|
||||||
|
return exec
|
||||||
|
.then(() => this.emit('task-finished'))
|
||||||
|
.catch((error) => this.emit('task-failed', error));
|
||||||
|
} else {
|
||||||
|
this.emit('task-finished');
|
||||||
|
return exec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Task;
|
||||||
|
|
54
server/libs/nodeCron/time-matcher.js
Normal file
54
server/libs/nodeCron/time-matcher.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
const validatePattern = require('./pattern-validation');
|
||||||
|
const convertExpression = require('./convert-expression');
|
||||||
|
|
||||||
|
function matchPattern(pattern, value){
|
||||||
|
if( pattern.indexOf(',') !== -1 ){
|
||||||
|
const patterns = pattern.split(',');
|
||||||
|
return patterns.indexOf(value.toString()) !== -1;
|
||||||
|
}
|
||||||
|
return pattern === value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TimeMatcher{
|
||||||
|
constructor(pattern, timezone){
|
||||||
|
validatePattern(pattern);
|
||||||
|
this.pattern = convertExpression(pattern);
|
||||||
|
this.timezone = timezone;
|
||||||
|
this.expressions = this.pattern.split(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
match(date){
|
||||||
|
date = this.apply(date);
|
||||||
|
|
||||||
|
const runOnSecond = matchPattern(this.expressions[0], date.getSeconds());
|
||||||
|
const runOnMinute = matchPattern(this.expressions[1], date.getMinutes());
|
||||||
|
const runOnHour = matchPattern(this.expressions[2], date.getHours());
|
||||||
|
const runOnDay = matchPattern(this.expressions[3], date.getDate());
|
||||||
|
const runOnMonth = matchPattern(this.expressions[4], date.getMonth() + 1);
|
||||||
|
const runOnWeekDay = matchPattern(this.expressions[5], date.getDay());
|
||||||
|
|
||||||
|
return runOnSecond && runOnMinute && runOnHour && runOnDay && runOnMonth && runOnWeekDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(date){
|
||||||
|
if(this.timezone){
|
||||||
|
const dtf = new Intl.DateTimeFormat('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hourCycle: 'h23',
|
||||||
|
fractionalSecondDigits: 3,
|
||||||
|
timeZone: this.timezone
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Date(dtf.format(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TimeMatcher;
|
@ -1,6 +1,6 @@
|
|||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
|
|
||||||
const cron = require('node-cron')
|
const cron = require('../libs/nodeCron')
|
||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
const archiver = require('archiver')
|
const archiver = require('archiver')
|
||||||
const StreamZip = require('node-stream-zip')
|
const StreamZip = require('node-stream-zip')
|
||||||
@ -276,7 +276,7 @@ class BackupManager {
|
|||||||
reject(err)
|
reject(err)
|
||||||
})
|
})
|
||||||
archive.on('progress', ({ fs: fsobj }) => {
|
archive.on('progress', ({ fs: fsobj }) => {
|
||||||
const maxBackupSizeInBytes = this.serverSettings.maxBackupSize * 1000 * 1000 * 1000
|
const maxBackupSizeInBytes = this.serverSettings.maxBackupSize * 1000 * 1000 * 1000
|
||||||
if (fsobj.processedBytes > maxBackupSizeInBytes) {
|
if (fsobj.processedBytes > maxBackupSizeInBytes) {
|
||||||
Logger.error(`[BackupManager] Archiver is too large - aborting to prevent endless loop, Bytes Processed: ${fsobj.processedBytes}`)
|
Logger.error(`[BackupManager] Archiver is too large - aborting to prevent endless loop, Bytes Processed: ${fsobj.processedBytes}`)
|
||||||
archive.abort()
|
archive.abort()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
const cron = require('node-cron')
|
const cron = require('../libs/nodeCron')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
|
|
||||||
const { parsePodcastRssFeedXml } = require('../utils/podcastUtils')
|
const { parsePodcastRssFeedXml } = require('../utils/podcastUtils')
|
||||||
|
Loading…
Reference in New Issue
Block a user