uci-utils-sync/src/watcher.js

134 lines
4.1 KiB
JavaScript

// native imports
import { EventEmitter as Emitter } from 'events'
import path from 'path'
// third party imports
import { watch } from 'chokidar'
// local imports
import ignores from './read-lines'
// UCI imports
import logger from '@uci/logger'
let log = {}
const READY_TIMEOUT = 2000
class Watcher extends Emitter {
constructor(opts={}) {
super()
log = logger({ package:'@uci/sync', class:'Watcher', file:'src/watcher.js'})
opts.unlinkDir = Object.hasOwnProperty(opts.unlinkDir) ? opts.unlinkDir : true
this.opts = opts
this._ignored = []
this._ready=false
this.watching=false
return this
}
async init(opts) {
return new Promise(async (resolve, reject) => {
opts = opts || this.opts
if (opts.excludeFrom) await this.getIgnoreLists(opts.excludeFrom)
if (opts.ignoreList) await this.getIgnoreLists(opts.ignoreList)
if (opts.source) {
opts.ignored = opts.ignored ? [...this._ignored,...opts.ignored] : this._ignored
log.debug({ignored:opts.ignored, msg:'all ignores'})
this._watcher = watch(opts.source,opts)
this._watcher.on('error', error => {
log.error({error:error, msg:'Watcher error'})
})
this._watcher.on('ready', () => {
clearTimeout(readyTimeout)
log.info('initial scan sucessful, ready to start')
this._ready=true
this.opts = opts // save options
resolve()
})
let readyTimeout = setTimeout(() =>{
log.fatal({options:opts, timeout:READY_TIMEOUT, msg:'Timeout: unabled to complete initial scan'})
reject('timeout during intial scan')
},READY_TIMEOUT)
}
else {
log.fatal('MUST provide a source directory(s) option to watch')
reject('no source provided')
}
})
}
async start(opts) {
if(this.watching) {
log.warn(`watching aleady running for ${this.opts.source}`)
return false
}
if (!this._watcher) await this.init(opts)
if (this._ready) {
log.info(`now watching ${this.opts.source}`)
this._watcher.removeAllListeners() // just in case
this.watching = true
// define command listen handler
const handler =
(type, f) => {
log.debug(`file ${f} was ${type}`)
// convert this to a plugin/hook so it's not specific
const fname = path.basename(f)
if ( fname.toLowerCase() === 'package.json')
if (type !=='modified') {
this.emit('error',new Error('package.json was added or removed, ignoring sync and reinstall'))
return
} else{
this.emit('install', f)
}
// user might want to run debounce on the listener for this event
this.emit('changed', {file:f, type:type})
} // end handler
this._watcher
.on('add', handler.bind(this, 'added'))
.on('change', handler.bind(this, 'modified'))
.on('unlink', handler.bind(this, 'removed'))
if(this.opts.unlinkDir) this._watcher.on('unlinkDir', handler.bind(this, 'dir-deleted'))
if(this.opts.addDir) this._watcher.on('addDir', handler.bind(this, 'dir-added'))
} else {
log.warn('watcher is not ready to start, check options and try again')
return new Error('not ready to start check configuration')
}
}
stop() {
if(this.watching) {
this.watching = false
this._watcher.close()
}
else log.warn('not watching, nothing to close')
}
remove() {
this.stop()
delete(this._watcher)
this._ready=false
}
async restart(opts) {
this.remove()
await this.start(opts)
}
async getIgnoreLists(lists) {
// console.log('ignore lists', lists)
if (typeof lists === 'string') lists=[lists]
let ignored = await ignores(lists)
// console.log('==ignores from lists', ignored)
this._ignored = [...this._ignored,...ignored]
// console.log(this._ignored)
}
addIgnore(ignore) {
Array.isArray(ignore) ? this._ignored.push(...ignore) : this._ignored.push(ignore)
}
clearAllIgnore() { this._ignored = [] }
}
export default Watcher