// import to from 'await-to-js' import RxClass from '@uci-utils/rx-class' import moment from 'moment' import 'moment-duration-format' // import merge from 'deepmerge' // Single Device Scheduler User instance for each unique action const DEFAULT_SETTINGS = { timing:{ hour:6, minute:0, delta:12 }, enabled:true, simultaneous:false } class UCISchedule extends RxClass { constructor(opts={}) { // console.log('schedule options\n', opts) super(opts) // console.log('schedule options\n', opts) this.name = opts.name || 'a periodic schedule' this.id = opts.id || this.name.replace(/ /g, '_') this.desc = opts.desc // this.dev = opts.dev == null ? false : opts.dev // ,DEFAULT_SETTINGS this.set('settings',Object.assign({},DEFAULT_SETTINGS,opts.settings)) this.set('nextTS',0) // the next trigger time in seconds this.set('nextDT','') // the next trigger time in seconds this.set('active',false) this.set('countdown','') // console.log(this.name, 'done setup') // Actions MUST be promise returning or async this._startAction = opts.startAction // || defaultStartAction.bind(this) // single (or array) of function(s) this._stopAction = opts.stopAction // || defaultStopAction.bind(this) // single (or array) of function(s) this._countdown = this._countdown.bind(this) Object.keys(this.settings.timing).forEach(prop=>{ this.rxSubscribe(['settings.timing',prop], 'update',this.update.bind(this)) // console.log('added subs', prop, this._rxGetObj(['settings.timing',prop]).subs) }) this.rxSubscribe('settings.enabled','state', (value) => { // console.log('schedule enabled change', ev) this.timer=1.1 // any value but 1 or 60 will trigger if (value) this.update() }) this.set('timer',1) this.rxSubscribe('timer','countdown', (value) => { // console.log('timer interval', value, this.settings.enabled) clearInterval(this.countdownInterval) if (this.settings.enabled && value ) this.countdownInterval = setInterval(this._countdown.bind(this),value*1000) else this.countdown = '' }) } _countdown(unit) { const secs = this.nextTS - Math.floor(Date.now()/1000) unit = unit ? unit : secs/60 < 3 ? 'seconds' : 'minutes' this.timer = unit === 'seconds' ? 1 : 60 const format = unit === 'seconds'? 'mm:ss': 'dd hh:mm' this.countdown = `${moment.duration( parseInt( secs / (unit === 'seconds' ? 1 : 60) ) ,unit ).format(format)}\u00a0\u00a0(${format})` // console.log(secs, this.timer, this.id,this.countdown) return this.countdown } get countdownMS() { return (this.nextTS*1000 - Date.now()) } // make simple access for settings get hour () { return this.settings.timing.hour } set hour (hour) { this.settings.timing.hour= hour } get minute () { return this.settings.timing.minute } set minute (minute) { this.settings.timing.minute= minute } get delta () { return this.settings.timing.delta } set delta (delta) { this.settings.timing.delta= delta } get enabled () { return this.settings.enabled } set enabled (enable) { if (enable == null) enable=true // enabled by default this.settings.enabled=enable } async update(force) { // all TS in seconds const { hour, minute, delta } = this.get('settings.timing') const lastTS = this.nextTS // console.log('>>>>>>>>>>>>>',this.name, 'updating',hour,minute,delta,lastTS) let baseTS = hour*3600+minute*60 let dt = new Date() let intoDayTS = (dt.getSeconds() + (60 * dt.getMinutes()) + (60 * 60 * dt.getHours())) let nowTS = Math.floor(Date.now()/1000) + 1 // last TS and nowTS are within 1 sec so add one let nextTS = nowTS - intoDayTS + baseTS // console.log(this.name, nextTS-nowTS,'first computed time stamp', nextTS,nowTS) while (nowTS > nextTS) { // console.log(`now ${nowTS} is beyond next ${nextTS} adding delta ${delta * 60 * 60 } seconds`) nextTS += (delta * 60 * 60 ) // delta in hours, TS in secs } // console.log(lastTS,'computed time stamp', nextTS) if(nextTS !== lastTS || lastTS == null || force===true) { // console.log('UPDATED TS>>>>>>>>>>>>>>>>', this.name,hour,minute,delta,nextTS,nextTS-lastTS) this.nextDT = moment(nextTS*1000).format('ddd, MMM Do h:mm A') this.nextTS = nextTS this._countdown() // console.log(this.nextTS,this.nextDT, this.countdown) this.emit('updated',{id:this.id,nextTS:nextTS,nextDT:this.nextDT, timing:this.get('settings.timing')}) } } // MUST! be async or promise TODO make that check registerStartAction(func) { if (typeof func!=='function') throw new Error('start action must be a function and be async or promise returning') // console.log(arguments) const args = [...arguments] args.shift() // console.log(this.name,this.id,'registering start action with these arguments to be passed\n',args) this._startAction = func.bind(this,...args) } registerStopAction(func) { if (typeof func!=='function') throw new Error('stop action must be a function') const args = [...arguments] args.shift() // console.log('registering stop action with these arguments to be passed\n',args) this._stopAction = func.bind(this,...args) } async startAction () { if (this._startAction) { this.active = true await this._startAction(this) // this is active schedule copy this.active = false this.emit(this.runID) // runner listens for this ID of active schedule to know when it's done } else console.log('no registered start action, register a start action!') } async stopAction () { if (this._stopAction) { await this._stopAction(this) // this is active schedule copy this.emit(this.runID) this.active = false } else console.log('no registered stop action, unable to abort, action will complete normally') } } export default UCISchedule export {UCISchedule as Schedule}