// 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