'use strict'
|
|
|
|
const fs = require('graceful-fs')
|
|
const path = require('path')
|
|
const copySync = require('../copy-sync').copySync
|
|
const removeSync = require('../remove').removeSync
|
|
const mkdirpSync = require('../mkdirs').mkdirsSync
|
|
const buffer = require('../util/buffer')
|
|
|
|
function moveSync (src, dest, options) {
|
|
options = options || {}
|
|
const overwrite = options.overwrite || options.clobber || false
|
|
|
|
src = path.resolve(src)
|
|
dest = path.resolve(dest)
|
|
|
|
if (src === dest) return fs.accessSync(src)
|
|
|
|
if (isSrcSubdir(src, dest)) throw new Error(`Cannot move '${src}' into itself '${dest}'.`)
|
|
|
|
mkdirpSync(path.dirname(dest))
|
|
tryRenameSync()
|
|
|
|
function tryRenameSync () {
|
|
if (overwrite) {
|
|
try {
|
|
return fs.renameSync(src, dest)
|
|
} catch (err) {
|
|
if (err.code === 'ENOTEMPTY' || err.code === 'EEXIST' || err.code === 'EPERM') {
|
|
removeSync(dest)
|
|
options.overwrite = false // just overwriteed it, no need to do it again
|
|
return moveSync(src, dest, options)
|
|
}
|
|
|
|
if (err.code !== 'EXDEV') throw err
|
|
return moveSyncAcrossDevice(src, dest, overwrite)
|
|
}
|
|
} else {
|
|
try {
|
|
fs.linkSync(src, dest)
|
|
return fs.unlinkSync(src)
|
|
} catch (err) {
|
|
if (err.code === 'EXDEV' || err.code === 'EISDIR' || err.code === 'EPERM' || err.code === 'ENOTSUP') {
|
|
return moveSyncAcrossDevice(src, dest, overwrite)
|
|
}
|
|
throw err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function moveSyncAcrossDevice (src, dest, overwrite) {
|
|
const stat = fs.statSync(src)
|
|
|
|
if (stat.isDirectory()) {
|
|
return moveDirSyncAcrossDevice(src, dest, overwrite)
|
|
} else {
|
|
return moveFileSyncAcrossDevice(src, dest, overwrite)
|
|
}
|
|
}
|
|
|
|
function moveFileSyncAcrossDevice (src, dest, overwrite) {
|
|
const BUF_LENGTH = 64 * 1024
|
|
const _buff = buffer(BUF_LENGTH)
|
|
|
|
const flags = overwrite ? 'w' : 'wx'
|
|
|
|
const fdr = fs.openSync(src, 'r')
|
|
const stat = fs.fstatSync(fdr)
|
|
const fdw = fs.openSync(dest, flags, stat.mode)
|
|
let pos = 0
|
|
|
|
while (pos < stat.size) {
|
|
const bytesRead = fs.readSync(fdr, _buff, 0, BUF_LENGTH, pos)
|
|
fs.writeSync(fdw, _buff, 0, bytesRead)
|
|
pos += bytesRead
|
|
}
|
|
|
|
fs.closeSync(fdr)
|
|
fs.closeSync(fdw)
|
|
return fs.unlinkSync(src)
|
|
}
|
|
|
|
function moveDirSyncAcrossDevice (src, dest, overwrite) {
|
|
const options = {
|
|
overwrite: false
|
|
}
|
|
|
|
if (overwrite) {
|
|
removeSync(dest)
|
|
tryCopySync()
|
|
} else {
|
|
tryCopySync()
|
|
}
|
|
|
|
function tryCopySync () {
|
|
copySync(src, dest, options)
|
|
return removeSync(src)
|
|
}
|
|
}
|
|
|
|
// return true if dest is a subdir of src, otherwise false.
|
|
// extract dest base dir and check if that is the same as src basename
|
|
function isSrcSubdir (src, dest) {
|
|
try {
|
|
return fs.statSync(src).isDirectory() &&
|
|
src !== dest &&
|
|
dest.indexOf(src) > -1 &&
|
|
dest.split(path.dirname(src) + path.sep)[1].split(path.sep)[0] === path.basename(src)
|
|
} catch (e) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
moveSync
|
|
}
|