mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01: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') | ||||||
|  | |||||||
| @ -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