在JS中重建解压zip文件的文件/文件夹结构

Posted

技术标签:

【中文标题】在JS中重建解压zip文件的文件/文件夹结构【英文标题】:Reconstructing file/folder structure of a decompressed zip file in JS 【发布时间】:2018-11-09 01:30:19 【问题描述】:

我正在尝试使用 javascript 在浏览器中重建解压缩 zip 文件的文件/文件夹结构。理想情况下,我希望将所有文件都放在FileList (就像他们刚刚通过网页上传一样)或其他可迭代对象。例如,一个包含

的压缩文件夹
folder/file1
folder/file2
someotherfile

应该重构为一个 FileList/iterable 对象,其中每个项目对应于包中的一个文件(据我所知,在 JS 中没有办法保留文件夹结构)。

我已经非常成功地读取了 tar.gz 文件并使用pako 将其解压缩,代码位于此问题的底部。然而,pako 的结果是一个大的 ArrayBuffer(下面代码中的 inflator.result),当我试图重建原始文件和文件夹时,我无法从中做出正面或反面。我遇到了以下问题:

    我如何知道 ArrayBuffer 中一个文件的结束位置和另一个文件的开始位置? 如何判断当前文件的原始文件类型?

一旦我知道了这一点,我应该能够将 ArrayBuffer 数据转换为带有

的文件
File(segment, type: filetype)

在网上搜索也没有提供任何有用的信息。有没有人知道如何解决这个问题?

这是我用来解压 zipfile 的代码。

import pako from 'pako';
import isFunction from 'lodash/isFunction'

class FileStreamer 
  constructor(file, chunkSize = 64 * 1024) 
    this.file = file;
    this.offset = 0;
    this.chunkSize = chunkSize; // bytes
    this.rewind();
  
  rewind() 
    this.offset = 0;
  
  isEndOfFile() 
    return this.offset >= this.getFileSize();
  
  readBlock() 
    const fileReader = new FileReader();
    const blob = this.file.slice(this.offset, this.offset + this.chunkSize);

    return new Promise((resolve, reject) => 
      fileReader.onloadend = (event) => 
        const target = (event.target);
        if (target.error) 
          return reject(target.error);
        

        this.offset += target.result.byteLength;

        resolve(
          data: target.result,
          progress: Math.min(this.offset / this.file.size, 1)
        );
      ;

      fileReader.readAsArrayBuffer(blob);
    );
  
  getFileSize() 
    return this.file.size;
  


export async function decompress(zipfile, onProgress) 
  const fs = new FileStreamer(zipfile);
  const inflator = new pako.Inflate();
  let block;

  while (!fs.isEndOfFile()) 
    block = await fs.readBlock();
    inflator.push(block.data, fs.isEndOfFile());
    if (inflator.err) 
      throw inflator.err
    
    if (isFunction(onProgress)) onProgress(block.progress)
  

  return inflator.result;

【问题讨论】:

【参考方案1】:

.tar.gz 文件是一个 tar 文件('Tape ARchive' - 因为最初捆绑文件以存储在磁带上是它的主要目的),然后随后被压缩。您可以获得诸如 tar.bz 之类的变体,用于基于 bzip 的压缩。

请注意,这与 PKZIP 最初创建的 .zip 文件格式不同,后者在单个步骤/规范中处理捆绑 (tar) 和压缩 (gz)。

无论如何,鉴于此,您将需要另一种工具来解释 tar 数据并将其转化为对您的目的有用的东西。我搜索了“tar文件阅读器js”,发现js-untar:https://github.com/InvokIT/js-untar

这似乎需要一个 ArrayBuffer 并将其转换为一系列 File 对象。项目页面的示例代码:

import untar from "js-untar";

// Load the source ArrayBuffer from a XMLHttpRequest (or any other way you may need).
var sourceBuffer = [...];

untar(sourceBuffer)
.progress(function(extractedFile) 
    ... // Do something with a single extracted file.
)
.then(function(extractedFiles) 
    ... // Do something with all extracted files.
);

// or

untar(sourceBuffer).then(
    function(extractedFiles)  // onSuccess
        ... // Do something with all extracted files.
    ,
    function(err)  // onError
        ... // Handle the error.
    ,
    function(extractedFile)  // onProgress
        ... // Do something with a single extracted file.
    
);

这似乎正是您所需要的。

(请注意,我无法保证此模块的适用性或可靠性,因为我从未使用过它,但这应该可以为您提供一个起点和上下文来继续)。

【讨论】:

谢谢!我将尝试使用该库,如果可行,我将接受您的回答。【参考方案2】:

在 ChrisM 的回答和他对 js-untar 的参考的帮助下,我设法编写了以下内容,它做得很好:

import pako from 'pako';
import isFunction from 'lodash/isFunction';
import untar from 'js-untar';

class FileStreamer 
  ...


export async function decompress(zipfile, onProgress) 
  const fs = new FileStreamer(zipfile);
  const inflator = new pako.Inflate();
  let block;

  while (!fs.isEndOfFile()) 
    block = await fs.readBlock();
    inflator.push(block.data, fs.isEndOfFile());
    if (inflator.err) 
      throw inflator.err
    
    if (isFunction(onProgress)) onProgress(block.progress)
  
  return await untar(inflator.result.buffer);

解压缩函数的结果现在是一个包含 File 对象的数组。甚至可以检索到存档文件中有关原始路径的信息。

【讨论】:

以上是关于在JS中重建解压zip文件的文件/文件夹结构的主要内容,如果未能解决你的问题,请参考以下文章

使用zip.js压缩文件和解压文件

Android查看zip文件而不解压

7-30 目录树

在java中,gzip 压缩和解压多个文件?

时如何去除目录结构及其解压到指定目录 (

linux下如何用unzip解压目录内所有zip文件?