| var fs = require('fs'); | |
| var ncp = require('ncp').ncp; | |
| var path = require('path'); | |
| var rimraf = require('rimraf'); | |
| var mkdirp = require('mkdirp'); | |
| module.exports = mv; | |
| function mv(source, dest, options, cb){ | |
| if (typeof options === 'function') { | |
| cb = options; | |
| options = {}; | |
| } | |
| var shouldMkdirp = !!options.mkdirp; | |
| var clobber = options.clobber !== false; | |
| var limit = options.limit || 16; | |
| if (shouldMkdirp) { | |
| mkdirs(); | |
| } else { | |
| doRename(); | |
| } | |
| function mkdirs() { | |
| mkdirp(path.dirname(dest), function(err) { | |
| if (err) return cb(err); | |
| doRename(); | |
| }); | |
| } | |
| function doRename() { | |
| if (clobber) { | |
| fs.rename(source, dest, function(err) { | |
| if (!err) return cb(); | |
| if (err.code !== 'EXDEV') return cb(err); | |
| moveFileAcrossDevice(source, dest, clobber, limit, cb); | |
| }); | |
| } else { | |
| fs.link(source, dest, function(err) { | |
| if (err) { | |
| if (err.code === 'EXDEV') { | |
| moveFileAcrossDevice(source, dest, clobber, limit, cb); | |
| return; | |
| } | |
| if (err.code === 'EISDIR' || err.code === 'EPERM') { | |
| moveDirAcrossDevice(source, dest, clobber, limit, cb); | |
| return; | |
| } | |
| cb(err); | |
| return; | |
| } | |
| fs.unlink(source, cb); | |
| }); | |
| } | |
| } | |
| } | |
| function moveFileAcrossDevice(source, dest, clobber, limit, cb) { | |
| var outFlags = clobber ? 'w' : 'wx'; | |
| var ins = fs.createReadStream(source); | |
| var outs = fs.createWriteStream(dest, {flags: outFlags}); | |
| ins.on('error', function(err){ | |
| ins.destroy(); | |
| outs.destroy(); | |
| outs.removeListener('close', onClose); | |
| if (err.code === 'EISDIR' || err.code === 'EPERM') { | |
| moveDirAcrossDevice(source, dest, clobber, limit, cb); | |
| } else { | |
| cb(err); | |
| } | |
| }); | |
| outs.on('error', function(err){ | |
| ins.destroy(); | |
| outs.destroy(); | |
| outs.removeListener('close', onClose); | |
| cb(err); | |
| }); | |
| outs.once('close', onClose); | |
| ins.pipe(outs); | |
| function onClose(){ | |
| fs.unlink(source, cb); | |
| } | |
| } | |
| function moveDirAcrossDevice(source, dest, clobber, limit, cb) { | |
| var options = { | |
| stopOnErr: true, | |
| clobber: false, | |
| limit: limit, | |
| }; | |
| if (clobber) { | |
| rimraf(dest, { disableGlob: true }, function(err) { | |
| if (err) return cb(err); | |
| startNcp(); | |
| }); | |
| } else { | |
| startNcp(); | |
| } | |
| function startNcp() { | |
| ncp(source, dest, options, function(errList) { | |
| if (errList) return cb(errList[0]); | |
| rimraf(source, { disableGlob: true }, cb); | |
| }); | |
| } | |
| } | |