123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- // 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;
|