uci-utils-scheduler/src/schedule.js

163 lines
5.9 KiB
JavaScript

// 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)
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:ss 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}