158 lines
4.0 KiB
JavaScript
158 lines
4.0 KiB
JavaScript
|
'use strict'
|
||
|
|
||
|
const fs = require('./wrapped-fs')
|
||
|
const path = require('path')
|
||
|
const tmp = require('tmp-promise')
|
||
|
const UINT64 = require('cuint').UINT64
|
||
|
|
||
|
const UINT32_MAX = 4294967295
|
||
|
|
||
|
class Filesystem {
|
||
|
constructor (src) {
|
||
|
this.src = path.resolve(src)
|
||
|
this.header = { files: {} }
|
||
|
this.offset = UINT64(0)
|
||
|
}
|
||
|
|
||
|
searchNodeFromDirectory (p) {
|
||
|
let json = this.header
|
||
|
const dirs = p.split(path.sep)
|
||
|
for (const dir of dirs) {
|
||
|
if (dir !== '.') {
|
||
|
json = json.files[dir]
|
||
|
}
|
||
|
}
|
||
|
return json
|
||
|
}
|
||
|
|
||
|
searchNodeFromPath (p) {
|
||
|
p = path.relative(this.src, p)
|
||
|
if (!p) { return this.header }
|
||
|
const name = path.basename(p)
|
||
|
const node = this.searchNodeFromDirectory(path.dirname(p))
|
||
|
if (node.files == null) {
|
||
|
node.files = {}
|
||
|
}
|
||
|
if (node.files[name] == null) {
|
||
|
node.files[name] = {}
|
||
|
}
|
||
|
return node.files[name]
|
||
|
}
|
||
|
|
||
|
insertDirectory (p, shouldUnpack) {
|
||
|
const node = this.searchNodeFromPath(p)
|
||
|
if (shouldUnpack) {
|
||
|
node.unpacked = shouldUnpack
|
||
|
}
|
||
|
node.files = {}
|
||
|
return node.files
|
||
|
}
|
||
|
|
||
|
async insertFile (p, shouldUnpack, file, options) {
|
||
|
const dirNode = this.searchNodeFromPath(path.dirname(p))
|
||
|
const node = this.searchNodeFromPath(p)
|
||
|
if (shouldUnpack || dirNode.unpacked) {
|
||
|
node.size = file.stat.size
|
||
|
node.unpacked = true
|
||
|
return Promise.resolve()
|
||
|
}
|
||
|
|
||
|
const handler = (resolve, reject) => {
|
||
|
const size = file.transformed ? file.transformed.stat.size : file.stat.size
|
||
|
|
||
|
// JavaScript can not precisely present integers >= UINT32_MAX.
|
||
|
if (size > UINT32_MAX) {
|
||
|
const error = new Error(`${p}: file size can not be larger than 4.2GB`)
|
||
|
if (reject) {
|
||
|
return reject(error)
|
||
|
} else {
|
||
|
throw error
|
||
|
}
|
||
|
}
|
||
|
|
||
|
node.size = size
|
||
|
node.offset = this.offset.toString()
|
||
|
if (process.platform !== 'win32' && (file.stat.mode & 0o100)) {
|
||
|
node.executable = true
|
||
|
}
|
||
|
this.offset.add(UINT64(size))
|
||
|
|
||
|
return resolve ? resolve() : Promise.resolve()
|
||
|
}
|
||
|
|
||
|
const transformed = options.transform && options.transform(p)
|
||
|
if (transformed) {
|
||
|
const tmpfile = await tmp.file()
|
||
|
return new Promise((resolve, reject) => {
|
||
|
const out = fs.createWriteStream(tmpfile.path)
|
||
|
const stream = fs.createReadStream(p)
|
||
|
|
||
|
stream.pipe(transformed).pipe(out)
|
||
|
return out.on('close', async () => {
|
||
|
file.transformed = {
|
||
|
path: tmpfile.path,
|
||
|
stat: await fs.lstat(tmpfile.path)
|
||
|
}
|
||
|
return handler(resolve, reject)
|
||
|
})
|
||
|
})
|
||
|
} else {
|
||
|
return handler()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
insertLink (p) {
|
||
|
const link = path.relative(fs.realpathSync(this.src), fs.realpathSync(p))
|
||
|
if (link.substr(0, 2) === '..') {
|
||
|
throw new Error(`${p}: file links out of the package`)
|
||
|
}
|
||
|
const node = this.searchNodeFromPath(p)
|
||
|
node.link = link
|
||
|
return link
|
||
|
}
|
||
|
|
||
|
listFiles (options) {
|
||
|
const files = []
|
||
|
|
||
|
const fillFilesFromMetadata = function (basePath, metadata) {
|
||
|
if (!metadata.files) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
for (const [childPath, childMetadata] of Object.entries(metadata.files)) {
|
||
|
const fullPath = path.join(basePath, childPath)
|
||
|
const packState = childMetadata.unpacked ? 'unpack' : 'pack '
|
||
|
files.push((options && options.isPack) ? `${packState} : ${fullPath}` : fullPath)
|
||
|
fillFilesFromMetadata(fullPath, childMetadata)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fillFilesFromMetadata('/', this.header)
|
||
|
return files
|
||
|
}
|
||
|
|
||
|
getNode (p) {
|
||
|
const node = this.searchNodeFromDirectory(path.dirname(p))
|
||
|
const name = path.basename(p)
|
||
|
if (name) {
|
||
|
return node.files[name]
|
||
|
} else {
|
||
|
return node
|
||
|
}
|
||
|
}
|
||
|
|
||
|
getFile (p, followLinks) {
|
||
|
followLinks = typeof followLinks === 'undefined' ? true : followLinks
|
||
|
const info = this.getNode(p)
|
||
|
|
||
|
// if followLinks is false we don't resolve symlinks
|
||
|
if (info.link && followLinks) {
|
||
|
return this.getFile(info.link)
|
||
|
} else {
|
||
|
return info
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = Filesystem
|