index.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. // most of this code was written by Andrew Kelley
  2. // licensed under the BSD license: see
  3. // https://github.com/andrewrk/node-mv/blob/master/package.json
  4. // this needs a cleanup
  5. var fs = require('graceful-fs')
  6. var ncp = require('../copy/ncp')
  7. var path = require('path')
  8. var rimraf = require('rimraf')
  9. var mkdirp = require('../mkdirs').mkdirs
  10. function mv (source, dest, options, callback) {
  11. if (typeof options === 'function') {
  12. callback = options
  13. options = {}
  14. }
  15. var shouldMkdirp = ('mkdirp' in options) ? options.mkdirp : true
  16. var clobber = ('clobber' in options) ? options.clobber : false
  17. var limit = options.limit || 16
  18. if (shouldMkdirp) {
  19. mkdirs()
  20. } else {
  21. doRename()
  22. }
  23. function mkdirs () {
  24. mkdirp(path.dirname(dest), function (err) {
  25. if (err) return callback(err)
  26. doRename()
  27. })
  28. }
  29. function doRename () {
  30. if (clobber) {
  31. fs.rename(source, dest, function (err) {
  32. if (!err) return callback()
  33. if (err.code === 'ENOTEMPTY' || err.code === 'EEXIST') {
  34. rimraf(dest, function (err) {
  35. if (err) return callback(err)
  36. options.clobber = false // just clobbered it, no need to do it again
  37. mv(source, dest, options, callback)
  38. })
  39. return
  40. }
  41. // weird Windows shit
  42. if (err.code === 'EPERM') {
  43. setTimeout(function () {
  44. rimraf(dest, function (err) {
  45. if (err) return callback(err)
  46. options.clobber = false
  47. mv(source, dest, options, callback)
  48. })
  49. }, 200)
  50. return
  51. }
  52. if (err.code !== 'EXDEV') return callback(err)
  53. moveAcrossDevice(source, dest, clobber, limit, callback)
  54. })
  55. } else {
  56. fs.link(source, dest, function (err) {
  57. if (err) {
  58. if (err.code === 'EXDEV' || err.code === 'EISDIR' || err.code === 'EPERM') {
  59. moveAcrossDevice(source, dest, clobber, limit, callback)
  60. return
  61. }
  62. callback(err)
  63. return
  64. }
  65. fs.unlink(source, callback)
  66. })
  67. }
  68. }
  69. }
  70. function moveAcrossDevice (source, dest, clobber, limit, callback) {
  71. fs.stat(source, function (err, stat) {
  72. if (err) {
  73. callback(err)
  74. return
  75. }
  76. if (stat.isDirectory()) {
  77. moveDirAcrossDevice(source, dest, clobber, limit, callback)
  78. } else {
  79. moveFileAcrossDevice(source, dest, clobber, limit, callback)
  80. }
  81. })
  82. }
  83. function moveFileAcrossDevice (source, dest, clobber, limit, callback) {
  84. var outFlags = clobber ? 'w' : 'wx'
  85. var ins = fs.createReadStream(source)
  86. var outs = fs.createWriteStream(dest, {flags: outFlags})
  87. ins.on('error', function (err) {
  88. ins.destroy()
  89. outs.destroy()
  90. outs.removeListener('close', onClose)
  91. // may want to create a directory but `out` line above
  92. // creates an empty file for us: See #108
  93. // don't care about error here
  94. fs.unlink(dest, function () {
  95. // note: `err` here is from the input stream errror
  96. if (err.code === 'EISDIR' || err.code === 'EPERM') {
  97. moveDirAcrossDevice(source, dest, clobber, limit, callback)
  98. } else {
  99. callback(err)
  100. }
  101. })
  102. })
  103. outs.on('error', function (err) {
  104. ins.destroy()
  105. outs.destroy()
  106. outs.removeListener('close', onClose)
  107. callback(err)
  108. })
  109. outs.once('close', onClose)
  110. ins.pipe(outs)
  111. function onClose () {
  112. fs.unlink(source, callback)
  113. }
  114. }
  115. function moveDirAcrossDevice (source, dest, clobber, limit, callback) {
  116. var options = {
  117. stopOnErr: true,
  118. clobber: false,
  119. limit: limit
  120. }
  121. function startNcp () {
  122. ncp(source, dest, options, function (errList) {
  123. if (errList) return callback(errList[0])
  124. rimraf(source, callback)
  125. })
  126. }
  127. if (clobber) {
  128. rimraf(dest, function (err) {
  129. if (err) return callback(err)
  130. startNcp()
  131. })
  132. } else {
  133. startNcp()
  134. }
  135. }
  136. module.exports = {
  137. move: mv
  138. }