Path: blob/trunk/javascript/selenium-webdriver/io/zip.js
2884 views
// Licensed to the Software Freedom Conservancy (SFC) under one1// or more contributor license agreements. See the NOTICE file2// distributed with this work for additional information3// regarding copyright ownership. The SFC licenses this file4// to you under the Apache License, Version 2.0 (the5// "License"); you may not use this file except in compliance6// with the License. You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing,11// software distributed under the License is distributed on an12// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13// KIND, either express or implied. See the License for the14// specific language governing permissions and limitations15// under the License.1617'use strict'1819const jszip = require('jszip')20const path = require('node:path')2122const io = require('./index')23const { InvalidArgumentError } = require('../lib/error')2425/**26* Manages a zip archive.27*/28class Zip {29constructor() {30/** @private @const */31this.z_ = new jszip()3233/** @private @const {!Set<!Promise<?>>} */34this.pendingAdds_ = new Set()35}3637/**38* Adds a file to this zip.39*40* @param {string} filePath path to the file to add.41* @param {string=} zipPath path to the file in the zip archive, defaults42* to the basename of `filePath`.43* @return {!Promise<?>} a promise that will resolve when added.44*/45addFile(filePath, zipPath = path.basename(filePath)) {46let add = io47.read(filePath)48.then((buffer) => this.z_.file(/** @type {string} */ (zipPath.replace(/\\/g, '/')), buffer))49this.pendingAdds_.add(add)50return add.then(51() => this.pendingAdds_.delete(add),52(e) => {53this.pendingAdds_.delete(add)54throw e55},56)57}5859/**60* Recursively adds a directory and all of its contents to this archive.61*62* @param {string} dirPath path to the directory to add.63* @param {string=} zipPath path to the folder in the archive to add the64* directory contents to. Defaults to the root folder.65* @return {!Promise<?>} returns a promise that will resolve when66* the operation is complete.67*/68addDir(dirPath, zipPath = '') {69return io.walkDir(dirPath).then((entries) => {70let archive = this.z_71if (zipPath) {72archive = archive.folder(zipPath)73}7475let files = []76entries.forEach((spec) => {77if (spec.dir) {78archive.folder(spec.path)79} else {80files.push(this.addFile(path.join(dirPath, spec.path), path.join(zipPath, spec.path)))81}82})8384return Promise.all(files)85})86}8788/**89* @param {string} path File path to test for within the archive.90* @return {boolean} Whether this zip archive contains an entry with the given91* path.92*/93has(path) {94return this.z_.file(path) !== null95}9697/**98* Returns the contents of the file in this zip archive with the given `path`.99* The returned promise will be rejected with an {@link InvalidArgumentError}100* if either `path` does not exist within the archive, or if `path` refers101* to a directory.102*103* @param {string} path the path to the file whose contents to return.104* @return {!Promise<!Buffer>} a promise that will be resolved with the file's105* contents as a buffer.106*/107getFile(path) {108let file = this.z_.file(path)109if (!file) {110return Promise.reject(new InvalidArgumentError(`No such file in zip archive: ${path}`))111}112113if (file.dir) {114return Promise.reject(new InvalidArgumentError(`The requested file is a directory: ${path}`))115}116117return Promise.resolve(file.async('nodebuffer'))118}119120/**121* Returns the compressed data for this archive in a buffer. _This method will122* not wait for any outstanding {@link #addFile add}123* {@link #addDir operations} before encoding the archive._124*125* @param {string} compression The desired compression.126* Must be `STORE` (the default) or `DEFLATE`.127* @return {!Promise<!Buffer>} a promise that will resolve with this archive128* as a buffer.129*/130toBuffer(compression = 'STORE') {131if (compression !== 'STORE' && compression !== 'DEFLATE') {132return Promise.reject(new InvalidArgumentError(`compression must be one of {STORE, DEFLATE}, got ${compression}`))133}134return Promise.resolve(this.z_.generateAsync({ compression, type: 'nodebuffer' }))135}136}137138/**139* Asynchronously opens a zip archive.140*141* @param {string} path to the zip archive to load.142* @return {!Promise<!Zip>} a promise that will resolve with the opened143* archive.144*/145function load(path) {146return io.read(path).then((data) => {147let zip = new Zip()148return zip.z_.loadAsync(data).then(() => zip)149})150}151152/**153* Asynchronously unzips an archive file.154*155* @param {string} src path to the source file to unzip.156* @param {string} dst path to the destination directory.157* @return {!Promise<string>} a promise that will resolve with `dst` once the158* archive has been unzipped.159*/160function unzip(src, dst) {161return load(src).then((zip) => {162const promisedDirs = new Map()163const promises = []164165zip.z_.forEach((relPath, file) => {166let p167if (file.dir) {168p = createDir(relPath)169} else {170let dirname = path.dirname(relPath)171if (dirname === '.') {172p = writeFile(relPath, file)173} else {174p = createDir(dirname).then(() => writeFile(relPath, file))175}176}177promises.push(p)178})179180return Promise.all(promises).then(() => dst)181182function createDir(dir) {183let p = promisedDirs.get(dir)184if (!p) {185p = io.mkdirp(path.join(dst, dir))186promisedDirs.set(dir, p)187}188return p189}190191function writeFile(relPath, file) {192return file.async('nodebuffer').then((buffer) => io.write(path.join(dst, relPath), buffer))193}194})195}196197// PUBLIC API198module.exports = { Zip, load, unzip }199200201