vacuum.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. var assert = require('assert')
  2. var dirname = require('path').dirname
  3. var resolve = require('path').resolve
  4. var isInside = require('path-is-inside')
  5. var rimraf = require('rimraf')
  6. var lstat = require('graceful-fs').lstat
  7. var readdir = require('graceful-fs').readdir
  8. var rmdir = require('graceful-fs').rmdir
  9. var unlink = require('graceful-fs').unlink
  10. module.exports = vacuum
  11. function vacuum (leaf, options, cb) {
  12. assert(typeof leaf === 'string', 'must pass in path to remove')
  13. assert(typeof cb === 'function', 'must pass in callback')
  14. if (!options) options = {}
  15. assert(typeof options === 'object', 'options must be an object')
  16. var log = options.log ? options.log : function () {}
  17. leaf = leaf && resolve(leaf)
  18. var base = options.base && resolve(options.base)
  19. if (base && !isInside(leaf, base)) {
  20. return cb(new Error(leaf + ' is not a child of ' + base))
  21. }
  22. lstat(leaf, function (error, stat) {
  23. if (error) {
  24. if (error.code === 'ENOENT') return cb(null)
  25. log(error.stack)
  26. return cb(error)
  27. }
  28. if (!(stat && (stat.isDirectory() || stat.isSymbolicLink() || stat.isFile()))) {
  29. log(leaf, 'is not a directory, file, or link')
  30. return cb(new Error(leaf + ' is not a directory, file, or link'))
  31. }
  32. if (options.purge) {
  33. log('purging', leaf)
  34. rimraf(leaf, function (error) {
  35. if (error) return cb(error)
  36. next(dirname(leaf))
  37. })
  38. } else if (!stat.isDirectory()) {
  39. log('removing', leaf)
  40. unlink(leaf, function (error) {
  41. if (error) return cb(error)
  42. next(dirname(leaf))
  43. })
  44. } else {
  45. next(leaf)
  46. }
  47. })
  48. function next (branch) {
  49. branch = branch && resolve(branch)
  50. // either we've reached the base or we've reached the root
  51. if ((base && branch === base) || branch === dirname(branch)) {
  52. log('finished vacuuming up to', branch)
  53. return cb(null)
  54. }
  55. readdir(branch, function (error, files) {
  56. if (error) {
  57. if (error.code === 'ENOENT') return cb(null)
  58. log('unable to check directory', branch, 'due to', error.message)
  59. return cb(error)
  60. }
  61. if (files.length > 0) {
  62. log('quitting because other entries in', branch)
  63. return cb(null)
  64. }
  65. if (branch === process.env.HOME) {
  66. log('quitting because cannot remove home directory', branch)
  67. return cb(null)
  68. }
  69. log('removing', branch)
  70. lstat(branch, function (error, stat) {
  71. if (error) {
  72. if (error.code === 'ENOENT') return cb(null)
  73. log('unable to lstat', branch, 'due to', error.message)
  74. return cb(error)
  75. }
  76. var remove = stat.isDirectory() ? rmdir : unlink
  77. remove(branch, function (error) {
  78. if (error) {
  79. if (error.code === 'ENOENT') {
  80. log('quitting because lost the race to remove', branch)
  81. return cb(null)
  82. }
  83. if (error.code === 'ENOTEMPTY' || error.code === 'EEXIST') {
  84. log('quitting because new (racy) entries in', branch)
  85. return cb(null)
  86. }
  87. log('unable to remove', branch, 'due to', error.message)
  88. return cb(error)
  89. }
  90. next(dirname(branch))
  91. })
  92. })
  93. })
  94. }
  95. }