cozy-labs/cozy-desktop

View on GitHub
core/local/channel_watcher/add_checksum.js

Summary

Maintainability
A
0 mins
Test Coverage
/** This step adds md5sum for files:
 *
 * - for created and updated events, it is mandatory
 * - for scan events, it is always done but an optimization could be to do it
 *   only for new files and files whose mtime or path has changed
 * - for renamed events, it is done in case a file was created while the client
 *   was stopped and is renamed while the client is starting (the renamed event
 *   that will be transformed in a created event in dispatch), but we could be
 *   smarter
 *
 * TODO the 2 optimizations ↑
 *
 * @module core/local/channel_watcher/add_checksum
 * @flow
 */

const _ = require('lodash')
const path = require('path')

const logger = require('../../utils/logger')

const STEP_NAME = 'addChecksum'

const log = logger({
  component: `ChannelWatcher/${STEP_NAME}`
})
const contentActions = new Set(['created', 'modified', 'renamed', 'scan'])

/*::
import type Channel from './channel'
import type { Config } from '../../config'
import type { Checksumer } from '../checksumer'
import type { ChannelEvent } from './event'
*/

module.exports = {
  loop
}

/** Compute checksums for event batches pulled from the given Channel.
 *
 * Returns a new Channel were events with computed checksums will be pushed.
 *
 * Skip checksuming when:
 *
 * - File is supposed not to exist anymore according to the event data.
 * - Checksum is already assigned because it is not supposed to have changed.
 *
 * @see .isFileWithContent
 * @see module:core/local/channel_watcher/initial_diff
 */
function loop(
  channel /*: Channel */,
  opts /*: { config: Config , checksumer: Checksumer, fatal: Error => any } */
) /*: Channel */ {
  const syncPath = opts.config.syncPath

  return channel.asyncMap(async events => {
    for (const event of events) {
      try {
        if (event.incomplete) {
          continue
        }
        if (isFileWithContent(event) && !event.md5sum) {
          const absPath = path.join(syncPath, event.path)
          event.md5sum = await opts.checksumer.push(absPath)
          log.debug({ path: event.path, event }, 'computed checksum')
        }
      } catch (err) {
        // Even if the file is no longer at the expected path, we want to
        // keep the event. Maybe it was one if its parents directory that was
        // moved, and then we can refine the event later (in incompleteFixer).
        _.set(event, ['incomplete', STEP_NAME], err.message)
        log.debug({ err, event }, 'Cannot compute checksum')
      }
    }
    return events
  }, opts.fatal)
}

/** Return true when file is supposed to exist according to event data.
 *
 * Return false for directories & deleted files.
 */
function isFileWithContent(event /*: ChannelEvent */) /*: boolean %checks */ {
  return event.kind === 'file' && contentActions.has(event.action)
}