'use strict' // most of this code was written by Andrew Kelley // licensed under the BSD license: see // https://github.com/andrewrk/node-mv/blob/master/package.json // this needs a cleanup const u = require('universalify').fromCallback const fs = require('graceful-fs') const ncp = require('../copy/ncp') const path = require('path') const remove = require('../remove').remove const mkdirp = require('../mkdirs').mkdirs function move (source, dest, options, callback) { if (typeof options === 'function') { callback = options options = {} } const shouldMkdirp = ('mkdirp' in options) ? options.mkdirp : true const overwrite = options.overwrite || options.clobber || false if (shouldMkdirp) { mkdirs() } else { doRename() } function mkdirs () { mkdirp(path.dirname(dest), err => { if (err) return callback(err) doRename() }) } function doRename () { if (path.resolve(source) === path.resolve(dest)) { fs.access(source, callback) } else if (overwrite) { fs.rename(source, dest, err => { if (!err) return callback() if (err.code === 'ENOTEMPTY' || err.code === 'EEXIST') { remove(dest, err => { if (err) return callback(err) options.overwrite = false // just overwriteed it, no need to do it again move(source, dest, options, callback) }) return } // weird Windows shit if (err.code === 'EPERM') { setTimeout(() => { remove(dest, err => { if (err) return callback(err) options.overwrite = false move(source, dest, options, callback) }) }, 200) return } if (err.code !== 'EXDEV') return callback(err) moveAcrossDevice(source, dest, overwrite, callback) }) } else { fs.link(source, dest, err => { if (err) { if (err.code === 'EXDEV' || err.code === 'EISDIR' || err.code === 'EPERM' || err.code === 'ENOTSUP') { moveAcrossDevice(source, dest, overwrite, callback) return } callback(err) return } fs.unlink(source, callback) }) } } } function moveAcrossDevice (source, dest, overwrite, callback) { fs.stat(source, (err, stat) => { if (err) { callback(err) return } if (stat.isDirectory()) { moveDirAcrossDevice(source, dest, overwrite, callback) } else { moveFileAcrossDevice(source, dest, overwrite, callback) } }) } function moveFileAcrossDevice (source, dest, overwrite, callback) { const flags = overwrite ? 'w' : 'wx' const ins = fs.createReadStream(source) const outs = fs.createWriteStream(dest, { flags }) ins.on('error', err => { ins.destroy() outs.destroy() outs.removeListener('close', onClose) // may want to create a directory but `out` line above // creates an empty file for us: See #108 // don't care about error here fs.unlink(dest, () => { // note: `err` here is from the input stream errror if (err.code === 'EISDIR' || err.code === 'EPERM') { moveDirAcrossDevice(source, dest, overwrite, callback) } else { callback(err) } }) }) outs.on('error', err => { ins.destroy() outs.destroy() outs.removeListener('close', onClose) callback(err) }) outs.once('close', onClose) ins.pipe(outs) function onClose () { fs.unlink(source, callback) } } function moveDirAcrossDevice (source, dest, overwrite, callback) { const options = { overwrite: false } if (overwrite) { remove(dest, err => { if (err) return callback(err) startNcp() }) } else { startNcp() } function startNcp () { ncp(source, dest, options, err => { if (err) return callback(err) remove(source, callback) }) } } module.exports = { move: u(move) }