extract.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. 'use strict'
  2. // tar -x
  3. const hlo = require('./high-level-opt.js')
  4. const Unpack = require('./unpack.js')
  5. const fs = require('fs')
  6. const fsm = require('fs-minipass')
  7. const path = require('path')
  8. const x = module.exports = (opt_, files, cb) => {
  9. if (typeof opt_ === 'function')
  10. cb = opt_, files = null, opt_ = {}
  11. else if (Array.isArray(opt_))
  12. files = opt_, opt_ = {}
  13. if (typeof files === 'function')
  14. cb = files, files = null
  15. if (!files)
  16. files = []
  17. else
  18. files = Array.from(files)
  19. const opt = hlo(opt_)
  20. if (opt.sync && typeof cb === 'function')
  21. throw new TypeError('callback not supported for sync tar functions')
  22. if (!opt.file && typeof cb === 'function')
  23. throw new TypeError('callback only supported with file option')
  24. if (files.length)
  25. filesFilter(opt, files)
  26. return opt.file && opt.sync ? extractFileSync(opt)
  27. : opt.file ? extractFile(opt, cb)
  28. : opt.sync ? extractSync(opt)
  29. : extract(opt)
  30. }
  31. // construct a filter that limits the file entries listed
  32. // include child entries if a dir is included
  33. const filesFilter = (opt, files) => {
  34. const map = new Map(files.map(f => [f.replace(/\/+$/, ''), true]))
  35. const filter = opt.filter
  36. const mapHas = (file, r) => {
  37. const root = r || path.parse(file).root || '.'
  38. const ret = file === root ? false
  39. : map.has(file) ? map.get(file)
  40. : mapHas(path.dirname(file), root)
  41. map.set(file, ret)
  42. return ret
  43. }
  44. opt.filter = filter
  45. ? (file, entry) => filter(file, entry) && mapHas(file.replace(/\/+$/, ''))
  46. : file => mapHas(file.replace(/\/+$/, ''))
  47. }
  48. const extractFileSync = opt => {
  49. const u = new Unpack.Sync(opt)
  50. const file = opt.file
  51. let threw = true
  52. let fd
  53. const stat = fs.statSync(file)
  54. // This trades a zero-byte read() syscall for a stat
  55. // However, it will usually result in less memory allocation
  56. const readSize = opt.maxReadSize || 16*1024*1024
  57. const stream = new fsm.ReadStreamSync(file, {
  58. readSize: readSize,
  59. size: stat.size
  60. })
  61. stream.pipe(u)
  62. }
  63. const extractFile = (opt, cb) => {
  64. const u = new Unpack(opt)
  65. const readSize = opt.maxReadSize || 16*1024*1024
  66. const file = opt.file
  67. const p = new Promise((resolve, reject) => {
  68. u.on('error', reject)
  69. u.on('close', resolve)
  70. // This trades a zero-byte read() syscall for a stat
  71. // However, it will usually result in less memory allocation
  72. fs.stat(file, (er, stat) => {
  73. if (er)
  74. reject(er)
  75. else {
  76. const stream = new fsm.ReadStream(file, {
  77. readSize: readSize,
  78. size: stat.size
  79. })
  80. stream.on('error', reject)
  81. stream.pipe(u)
  82. }
  83. })
  84. })
  85. return cb ? p.then(cb, cb) : p
  86. }
  87. const extractSync = opt => {
  88. return new Unpack.Sync(opt)
  89. }
  90. const extract = opt => {
  91. return new Unpack(opt)
  92. }