move.js 1.97 KB
'use strict'
module.exports = move

var nodeFs = require('fs')
var rimraf = require('rimraf')
var validate = require('aproba')
var copy = require('copy-concurrently')
var RunQueue = require('run-queue')
var extend = Object.assign || require('util')._extend

function promisify (Promise, fn) {
  return function () {
    var args = [].slice.call(arguments)
    return new Promise(function (resolve, reject) {
      return fn.apply(null, args.concat(function (err, value) {
        if (err) {
          reject(err)
        } else {
          resolve(value)
        }
      }))
    })
  }
}

function move (from, to, opts) {
  validate('SSO|SS', arguments)
  opts = extend({}, opts || {})

  var Promise = opts.Promise || global.Promise
  var fs = opts.fs || nodeFs
  var rimrafAsync = promisify(Promise, rimraf)
  var renameAsync = promisify(Promise, fs.rename)

  opts.top = from

  var queue = new RunQueue({
    maxConcurrency: opts.maxConcurrency,
    Promise: Promise
  })
  opts.queue = queue
  opts.recurseWith = rename

  queue.add(0, rename, [from, to, opts])

  return queue.run().then(function () {
    return remove(from)
  }, function (err) {
    // if the target already exists don't clobber it
    if (err.code === 'EEXIST' || err.code === 'EPERM') {
      return passThroughError()
    } else {
      return remove(to).then(passThroughError, passThroughError)
    }
    function passThroughError () {
      return Promise.reject(err)
    }
  })

  function remove (target) {
    var opts = {
      unlink: fs.unlink,
      chmod: fs.chmod,
      stat: fs.stat,
      lstat: fs.lstat,
      rmdir: fs.rmdir,
      readdir: fs.readdir,
      glob: false
    }
    return rimrafAsync(target, opts)
  }

  function rename (from, to, opts, done) {
    return renameAsync(from, to).catch(function (err) {
      if (err.code !== 'EXDEV') {
        return Promise.reject(err)
      } else {
        return remove(to).then(function () {
          return copy.item(from, to, opts)
        })
      }
    })
  }
}