215 lines
6.1 KiB
JavaScript
Executable file
215 lines
6.1 KiB
JavaScript
Executable file
// Licensed to the Software Freedom Conservancy (SFC) under one
|
|
// or more contributor license agreements. See the NOTICE file
|
|
// distributed with this work for additional information
|
|
// regarding copyright ownership. The SFC licenses this file
|
|
// to you under the Apache License, Version 2.0 (the
|
|
// "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
'use strict';
|
|
|
|
const jszip = require('jszip');
|
|
const path = require('path');
|
|
|
|
const io = require('./index');
|
|
const {InvalidArgumentError} = require('../lib/error');
|
|
|
|
/**
|
|
* Manages a zip archive.
|
|
*/
|
|
class Zip {
|
|
constructor() {
|
|
/** @private @const */
|
|
this.z_ = new jszip;
|
|
|
|
/** @private @const {!Set<!Promise<?>>} */
|
|
this.pendingAdds_ = new Set;
|
|
}
|
|
|
|
/**
|
|
* Adds a file to this zip.
|
|
*
|
|
* @param {string} filePath path to the file to add.
|
|
* @param {string=} zipPath path to the file in the zip archive, defaults
|
|
* to the basename of `filePath`.
|
|
* @return {!Promise<?>} a promise that will resolve when added.
|
|
*/
|
|
addFile(filePath, zipPath = path.basename(filePath)) {
|
|
let add = io.read(filePath)
|
|
.then(buffer => this.z_.file(/** @type {string} */(zipPath), buffer));
|
|
this.pendingAdds_.add(add);
|
|
return add.then(
|
|
() => this.pendingAdds_.delete(add),
|
|
(e) => {
|
|
this.pendingAdds_.delete(add);
|
|
throw e;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Recursively adds a directory and all of its contents to this archive.
|
|
*
|
|
* @param {string} dirPath path to the directory to add.
|
|
* @param {string=} zipPath path to the folder in the archive to add the
|
|
* directory contents to. Defaults to the root folder.
|
|
* @return {!Promise<?>} returns a promise that will resolve when the
|
|
* the operation is complete.
|
|
*/
|
|
addDir(dirPath, zipPath = '') {
|
|
return io.walkDir(dirPath).then(entries => {
|
|
let archive = this.z_;
|
|
if (zipPath) {
|
|
archive = archive.folder(zipPath);
|
|
}
|
|
|
|
let files = [];
|
|
entries.forEach(spec => {
|
|
if (spec.dir) {
|
|
archive.folder(spec.path);
|
|
} else {
|
|
files.push(
|
|
this.addFile(
|
|
path.join(dirPath, spec.path),
|
|
path.join(zipPath, spec.path)));
|
|
}
|
|
});
|
|
|
|
return Promise.all(files);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {string} path File path to test for within the archive.
|
|
* @return {boolean} Whether this zip archive contains an entry with the given
|
|
* path.
|
|
*/
|
|
has(path) {
|
|
return this.z_.file(path) !== null;
|
|
}
|
|
|
|
/**
|
|
* Returns the contents of the file in this zip archive with the given `path`.
|
|
* The returned promise will be rejected with an {@link InvalidArgumentError}
|
|
* if either `path` does not exist within the archive, or if `path` refers
|
|
* to a directory.
|
|
*
|
|
* @param {string} path the path to the file whose contents to return.
|
|
* @return {!Promise<!Buffer>} a promise that will be resolved with the file's
|
|
* contents as a buffer.
|
|
*/
|
|
getFile(path) {
|
|
let file = this.z_.file(path);
|
|
if (!file) {
|
|
return Promise.reject(
|
|
new InvalidArgumentError(`No such file in zip archive: ${path}`));
|
|
}
|
|
|
|
if (file.dir) {
|
|
return Promise.reject(
|
|
new InvalidArgumentError(
|
|
`The requested file is a directory: ${path}`));
|
|
}
|
|
|
|
return Promise.resolve(file.async('nodebuffer'));
|
|
}
|
|
|
|
/**
|
|
* Returns the compressed data for this archive in a buffer. _This method will
|
|
* not wait for any outstanding {@link #addFile add}
|
|
* {@link #addDir operations} before encoding the archive._
|
|
*
|
|
* @param {string} compression The desired compression.
|
|
* Must be `STORE` (the default) or `DEFLATE`.
|
|
* @return {!Promise<!Buffer>} a promise that will resolve with this archive
|
|
* as a buffer.
|
|
*/
|
|
toBuffer(compression = 'STORE') {
|
|
if (compression !== 'STORE' && compression !== 'DEFLATE') {
|
|
return Promise.reject(
|
|
new InvalidArgumentError(
|
|
`compression must be one of {STORE, DEFLATE}, got ${compression}`));
|
|
}
|
|
return Promise.resolve(
|
|
this.z_.generateAsync({compression, type: 'nodebuffer'}));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Asynchronously opens a zip archive.
|
|
*
|
|
* @param {string} path to the zip archive to load.
|
|
* @return {!Promise<!Zip>} a promise that will resolve with the opened
|
|
* archive.
|
|
*/
|
|
function load(path) {
|
|
return io.read(path).then(data => {
|
|
let zip = new Zip;
|
|
return zip.z_.loadAsync(data).then(() => zip);
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Asynchronously unzips an archive file.
|
|
*
|
|
* @param {string} src path to the source file to unzip.
|
|
* @param {string} dst path to the destination directory.
|
|
* @return {!Promise<string>} a promise that will resolve with `dst` once the
|
|
* archive has been unzipped.
|
|
*/
|
|
function unzip(src, dst) {
|
|
return load(src).then(zip => {
|
|
let promisedDirs = new Map;
|
|
let promises = [];
|
|
|
|
zip.z_.forEach((relPath, file) => {
|
|
let p;
|
|
if (file.dir) {
|
|
p = createDir(relPath);
|
|
} else {
|
|
let dirname = path.dirname(relPath);
|
|
if (dirname === '.') {
|
|
p = writeFile(relPath, file);
|
|
} else {
|
|
p = createDir(dirname).then(() => writeFile(relPath, file));
|
|
}
|
|
}
|
|
promises.push(p);
|
|
});
|
|
|
|
return Promise.all(promises).then(() => dst);
|
|
|
|
function createDir(dir) {
|
|
let p = promisedDirs.get(dir);
|
|
if (!p) {
|
|
p = io.mkdirp(path.join(dst, dir));
|
|
promisedDirs.set(dir, p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
function writeFile(relPath, file) {
|
|
return file.async('nodebuffer')
|
|
.then(buffer => io.write(path.join(dst, relPath), buffer));
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// PUBLIC API
|
|
|
|
|
|
exports.Zip = Zip;
|
|
exports.load = load;
|
|
exports.unzip = unzip;
|