cozy-labs/cozy-desktop

View on GitHub
core/local/chokidar/event_buffer.js

Summary

Maintainability
A
0 mins
Test Coverage
/**
 * @module core/local/chokidar/event_buffer
 * @flow
 */

const autoBind = require('auto-bind')

/*::
type EventBufferMode = 'idle' | 'timeout'
type FlushCallback<EventType> = (EventType[]) => any
*/

/**
 * An event buffer.
 * Needs a flush callback to be called everytime the buffer is flushed.
 *
 * In *idle* mode (default), the buffer will only store pushed events and
 * you'll have to flush it manually.
 * In *timeout* mode, the buffer flushes stored events by itself when nothing
 * happens during `timeoutInMs`.
 *
 * The buffer will also flush automatically when switching from one mode to
 * another.
 *
 * Right now this class is in the local/ namespace because this is where it is
 * used, but it could be anywhere else since it doesn't have any dependency.
 */
class EventBuffer /*:: <EventType> */ {
  /*::
  locked: boolean
  events: EventType[]
  mode: EventBufferMode
  timeoutInMs: number
  timeout: *
  flushed: FlushCallback<EventType>
  */

  constructor(
    timeoutInMs /*: number */,
    flushed /*: FlushCallback<EventType> */
  ) {
    this.locked = false
    this.events = []
    this.mode = 'idle'
    this.timeoutInMs = timeoutInMs
    this.timeout = null
    this.flushed = flushed

    autoBind(this)
  }

  lock() {
    if (this.locked) {
      throw new Error('lock unavailable')
    }

    this.locked = true
  }

  unlock() {
    this.locked = false
  }

  push(event /*: EventType */) /*: void */ {
    this.events.push(event)
    this.shiftTimeout()
  }

  unflush(events /*: Array<EventType> */) /*: void */ {
    this.events = events.concat(this.events)
    this.shiftTimeout()
  }

  shiftTimeout() /*: void */ {
    if (this.mode === 'timeout') {
      this.clearTimeout()
      this.timeout = setTimeout(this.flush, this.timeoutInMs)
    }
  }

  clearTimeout() /*: void */ {
    if (this.timeout != null) {
      clearTimeout(this.timeout)
      delete this.timeout
    }
  }

  async flush() {
    try {
      this.lock()
    } catch (err) {
      this.shiftTimeout()
      return
    }

    try {
      this.clearTimeout()
      if (this.events.length > 0) {
        const flushedEvents = this.events
        this.events = []
        await this.flushed(flushedEvents)
      }
    } finally {
      this.unlock()
    }
  }

  switchMode(mode /*: EventBufferMode */) /*: void */ {
    this.flush()
    this.mode = mode
  }

  clear() /*: void */ {
    this.events = []
    this.clearTimeout()
  }
}

module.exports = EventBuffer