import { MinPriorityQueue } from '@datastructures-js/priority-queue'; import { Arrays } from '../../../browser/tools/Arrays'; import { DateHelper } from '../../../browser/date/DateHelper'; import { DateMath } from '../../../browser/date/DateMath'; import { Task } from './Task'; import { iTaskScheduler } from './iTaskScheduler'; import { RJLog } from '../../log/RJLog'; import { DateFormatter } from '../../../browser/date/DateFormatter'; import { Duration } from '../../../browser/date/Duration'; export class Scheduler { // [ Tasks ] _queue:Task[] = []; _scheduledTasks = new Set(); // [ Next Task ] _taskTimerCallback:NodeJS.Timeout; _nextTaskID:string; // [ Settings ] _maxScheduleDurationDays:number = 1; // [ Schedulers ] _taskSchedulers:iTaskScheduler[] = []; get taskSchedulers(){ return this._taskSchedulers; } constructor() { this._queue =[]; this._taskTimerCallback = null; this._nextTaskID = null; } get maxDate():Date { return DateMath.fromNowAddDays( this._maxScheduleDurationDays ); } async update() { let maxDate = this.maxDate; let allTasks:Task[] = []; for ( let ts of this._taskSchedulers ) { let tasks = await ts.getTasksToSchedule( maxDate ); allTasks = allTasks.concat( tasks ); } this.scheduleTasks( allTasks ); } scheduleTasks( tasks:Task[] ) { let maxDate = DateMath.fromNowAddDays( this._maxScheduleDurationDays ); tasks = tasks.filter( t => ! this._scheduledTasks.has( t.id ) && ! DateMath.isAfter( t.date, maxDate ) ); this._queue = this._queue.concat( tasks ); tasks.forEach( t => this._scheduledTasks.add( t.id ) ); this._queue.sort( ( a, b ) => { return a.date.getTime() - b.date.getTime() } ); let newNextTask = this._queue[ 0 ]; if ( this._nextTaskID && newNextTask.id != this._nextTaskID ) { this._resetTimer(); } this._updateTimer(); } protected _resetTimer() { if ( this._taskTimerCallback ) { clearTimeout( this._taskTimerCallback ); this._taskTimerCallback = null; } this._nextTaskID = null; } protected _updateTimer() { // RJLog.log( "_updateTimer:" ); if ( this._taskTimerCallback || this._queue.length == 0 ) { // RJLog.log( "Nothing to do. Has Timer:", this._taskTimerCallback, "Queue Length:", this._queue.length ); return; } let task = this._queue[ 0 ]; this._nextTaskID = task.id; let delay = Math.max( 0, task.date.getTime() - Date.now() ); RJLog.log( task.id, DateFormatter.HMS( task.date ), "delaying:", Duration.fromMilliSeconds( delay ) + " sec" ); this._taskTimerCallback = setTimeout( () => { this._taskTimerCallback = null; Arrays.remove( this._queue, task ); this._scheduledTasks.delete( task.id ); this._nextTaskID = null; try { task.action(); } catch( e ) { RJLog.log( e ); } this._updateTimer(); }, delay ); } cancelUserAppTask( taskID:string, userID:string, appID:string ):boolean { if ( ! taskID || ! userID || ! appID ) { return false; } let index = this._queue.findIndex( e => e.id === taskID ); if ( index == -1 ) { RJLog.log( "Task not found:", taskID ); return false; } let foundTask = this._queue[ index ]; if ( foundTask.userID !== userID || foundTask.appID !== appID ) { RJLog.log( "Found task has not matching user:", foundTask.userID, foundTask.appID ); return false; } if ( index === -1 ) { return false; } Arrays.removeAt( this._queue, index ); if ( taskID !== this._nextTaskID ) { return true; } this._resetTimer(); this._updateTimer(); return true; } }