Восстановление структуры файла / папки распакованного zip-файла в JS

Я пытаюсь восстановить структуру файла / папки распакованного zip-файла в браузере с помощью JavaScript. В идеале я хотел бы, чтобы все файлы были в FileList (как будто они только что были загружены через веб-страницу) или в другом итерируемом объекте. Например, сжатая папка, содержащая

folder/file1
folder/file2
someotherfile

должен быть реконструирован в FileList / итерируемый объект, в котором каждый элемент соответствует одному из файлов в пакете (насколько мне известно, нет способа сохранить структуру папок в JS).

Я довольно успешно прочитал файл tar.gz и распаковал его, используя pako с кодом внизу этого вопроса. Тем не менее, результат Пако является одним большим ArrayBuffer (inflator.result в приведенном ниже коде), и я не могу сделать из этого ни головы, ни хвоста, когда пытаюсь восстановить исходные файлы и папки. Я сталкиваюсь со следующими проблемами:

  1. Как узнать, где заканчивается один файл, а другой начинается в ArrayBuffer?
  2. Как определить исходный тип файла текущего файла?

Как только я это знаю, я смогу преобразовать данные ArrayBuffer в файл с

File(segment, {type: filetype})

Поиск в Интернете также не дал никакой полезной информации. У кого-нибудь есть какие-нибудь подсказки о том, как подойти к этой проблеме?

Вот код, который я использую для распаковки zip-файла.

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;
}

2 ответа

Решение

Файл.tar.gz представляет собой tar-файл ('Tape ARchive' - поскольку его основной целью было изначально объединение файлов для хранения на ленте), который затем был впоследствии сжат. Вы можете получить варианты, такие как tar.bz для сжатия на основе bzip.

Обратите внимание, что это отличается от формата файла.zip, первоначально созданного PKZIP, который обрабатывает пакетирование (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.
    }
);

Это похоже на то, что вам нужно.

(Обратите внимание, что я не могу ручаться за пригодность или надежность этого модуля, поскольку я никогда не использовал его, но это должно дать вам отправную точку и контекст для продолжения).

С помощью ответа 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. Информация об исходных путях в архивном файле даже извлекается.

Другие вопросы по тегам