Advertisement
Guest User

ZipFetcher

a guest
Dec 1st, 2021
64
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. class ZipFetcher {
  2.   /**
  3.   * Класс для загрузки ZIP-файла и анализа его содержимого в реальном времени
  4.   */
  5.   constructor (reader, abortController) {
  6.     this.reader = reader;
  7.     this.abortController = abortController;
  8.     this.buffer = [];
  9.     this.utf8Decoder = new TextDecoder('utf-8');
  10.   }
  11.  
  12.   static async fromUrl(url) {
  13.     /**
  14.     * Точка входа, инициирует загрузку файла и получает ридер для передачи в
  15.     * конструктор
  16.     */
  17.     const abortController = new AbortController();
  18.     const abortSignal = abortController.signal;
  19.     const response = await fetch(url, { abortSignal });
  20.     const reader = response.body.getReader();
  21.     return new ZipFetcher(reader, abortController);
  22.   }
  23.  
  24.   async fetchUntilFileName(fileName) {
  25.     /**
  26.      * Последовательно получает файлы, хранящиеся в архиве
  27.      */
  28.     let localFile;
  29.     do {
  30.       localFile = await this.fetchLocalFile()
  31.     }
  32.     while (localFile.header.fileName !== fileName)
  33.     // TODO: {throw new Error(`${fileName} not found`)};
  34.     this.stop();
  35.     return localFile;
  36.   }
  37.  
  38.   async fetchLocalFile () {
  39.     /**
  40.     * Складывает заголовок и данные файла в один объект
  41.     */
  42.     const localFile = new Object();
  43.  
  44.     localFile.header = await this.fetchLocalFileHeader();
  45.  
  46.     const dataSize = localFile.header.compressedDataSize;
  47.     localFile.data = await this.fetchLocalFileData(dataSize);
  48.  
  49.     return localFile;
  50.   }
  51.  
  52.   async fetchLocalFileHeader () {
  53.     /**
  54.      * Получает первые байты из буфера, в которых хранится заголовок файла,
  55.      * и парсит их
  56.      */
  57.     const localFileHeader = new Object();
  58.     const constPartSize = 30;
  59.  
  60.     await this.fetchBufferToLength(constPartSize);
  61.     const constPart =
  62.       this.parseHeaderConstPart(this.buffer.splice(0, constPartSize));
  63.     Object.assign(localFileHeader, constPart);
  64.  
  65.     localFileHeader.varPartSize =
  66.       localFileHeader.fileNameSize + localFileHeader.extraFieldSize;
  67.  
  68.     await this.fetchBufferToLength(localFileHeader.varPartSize);
  69.     const varPartArray = this.buffer.splice(0, localFileHeader.varPartSize);
  70.     const varPart =
  71.       this.parseHeaderVarPart(varPartArray, localFileHeader.fileNameSize);
  72.     Object.assign(localFileHeader, varPart)
  73.  
  74.     return localFileHeader;
  75.   }
  76.  
  77.   parseHeaderConstPart (headerConstPart) {
  78.     /**
  79.      * Парсит первую часть заголовка, длина которой всегда 30 байт
  80.      */
  81.     const constPart = new Object();
  82.     const headerConstPartView =
  83.       new DataView(new  Uint8Array(headerConstPart).buffer);
  84.     constPart.signature = headerConstPartView.getUint32(0, true);
  85.     if (constPart.signature != 0x04034b50) {
  86.       throw new Error(
  87.           `Unexpected file signature - ${constPart.signature.toString(16)}`
  88.         );
  89.     }
  90.     constPart.compressedDataSize =  headerConstPartView.getUint32(18, true);
  91.     constPart.fileNameSize = headerConstPartView.getUint16(26, true);
  92.     constPart.extraFieldSize = headerConstPartView.getUint16(28, true);
  93.     return constPart;
  94.   }
  95.  
  96.   parseHeaderVarPart (headerVarPart, fileNameSize) {
  97.     /**
  98.      * Парсит вторую часть заголовка, где хранится имя файла и опциональное
  99.      * поле переменной длины
  100.      */
  101.     const varPart = new Object();
  102.  
  103.     const fileNameBytes = new Uint8Array(headerVarPart.splice(0, fileNameSize));
  104.     varPart.fileName = this.utf8Decoder.decode(fileNameBytes);
  105.     varPart.extraField = new Uint8Array(headerVarPart);
  106.  
  107.     return varPart;
  108.   }
  109.  
  110.   async fetchLocalFileData(dataSize) {
  111.     /**
  112.      * Получает данные из тела файла и разжимает их алгоритмом Inflate
  113.      */
  114.     await this.fetchBufferToLength(dataSize);
  115.     const compressedData = new Uint8Array(this.buffer.splice(0, dataSize));
  116.     return new Zlib.RawInflate(compressedData).decompress();
  117.   }
  118.  
  119.   async fetchBufferToLength(length) {
  120.     /**
  121.      * Подгружает новые данные в буфер из ридера пока длина
  122.      * буфера не станет равна dataSize байт (элементов).
  123.      */
  124.     let chunk, readerDone;
  125.     while (this.buffer.length < length && !readerDone) {
  126.       ({ value: chunk, done: readerDone } = await this.reader.read());
  127.       chunk && this.buffer.push(...chunk);
  128.     }
  129.     if (readerDone && buffer.length < length) {
  130.       throw new Error('Unexpected EOF')
  131.     }
  132.   }
  133.  
  134.   stop() {
  135.     /**
  136.      * Закывает ридер и отменяет загрузку архива
  137.      */
  138.     this.reader.cancel();
  139.     this.abortController.abort();
  140.   }
  141. }
  142.  
  143. async function run() {
  144.   const fetcher = await ZipFetcher.fromUrl("http://192.168.0.2/share/archive.zip");
  145.   foundFile = fetcher.fetchUntilFileName('file.txt');
  146. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement