Skip to content

Instantly share code, notes, and snippets.

@guest271314
Forked from gildas-lormeau/unzip
Last active November 5, 2023 15:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save guest271314/8256d618b4a94ccc368c1a61e65e4cfa to your computer and use it in GitHub Desktop.
Save guest271314/8256d618b4a94ccc368c1a61e65e4cfa to your computer and use it in GitHub Desktop.
Basic unzip implementation based on zip.js and Deno (Fetch and unzip Chrome-For-Testing Canary Channel)
// Fetch and unzip Chrome-For-Testing Canary Channel
// deno run -A unzip.js
/* eslint-disable no-console */
/* global Deno, Intl */
"use strict";
// import { parse } from "https://deno.land/std/flags/mod.ts";
import { exists } from "https://deno.land/std/fs/mod.ts";
import { basename, dirname } from "https://deno.land/std/path/mod.ts";
import {
ERR_HTTP_RANGE,
HttpReader,
terminateWorkers,
Uint8ArrayReader,
Uint8ArrayWriter,
ZipReader,
} from "https://raw.githubusercontent.com/gildas-lormeau/zip.js/master/index.js";
const executables = new Set([
"chrome",
"chrome-wrapper",
"chrome_crashpad_handler",
"chrome_sandbox",
"libEGL.so",
"libGLESv2.so",
"libvk_swiftshader.so",
"libvulkan.so.1",
"nacl_helper",
"nacl_helper_bootstrap",
"nacl_irt_x86_64.nexe",
"xdg-mime",
"xdg-settings",
]);
const json = await (await fetch(
"https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json",
)).json();
const {
url,
} = json.channels.Canary.downloads.chrome.find(({
platform,
}) => platform === "linux64");
console.log(`Fetch ${url}`);
const response = await fetch(url);
const length = response.headers.get("content-length");
const ab = new ArrayBuffer(length);
const uint8 = new Uint8Array(ab);
const encoder = new TextEncoder();
let offset = 0;
async function log(bytes) {
// https://medium.com/deno-the-complete-reference/deno-nuggets-overwrite-a-console-log-line-2513e52e264b
await Deno.stdout.write(
encoder.encode(`${bytes} of ${length} bytes written.\r`),
);
}
// Just so we see what's going on
await response.body.pipeTo(
new WritableStream({
start() {
console.log("Start reading stream.");
},
async write(value) {
uint8.set(value, offset);
await log(offset += value.length);
},
close() {
console.log("\nDone reading stream.");
},
}),
);
// const ab = await response.arrayBuffer();
// console.log(ab.byteLength);
await Deno.writeFile("chrome-linux64.zip", uint8);
unzip({ _: ["chrome-linux64.zip"] }).catch((error) =>
console.error(error.toString())
);
async function unzip(args) {
if (args.l) {
await listEntries(args.l || args._[0]);
} else {
const archive = args._.shift();
if (archive) {
await unzipEntries(archive, args._);
await Deno.remove("chrome-linux64.zip");
}
}
}
async function unzipEntries(archive, filenames) {
const zipReader = new ZipReader(await getReader(archive));
const entries = await zipReader.getEntries();
let selectedEntries;
if (filenames.length) {
selectedEntries = entries.filter((entry) =>
filenames.includes(entry.filename)
);
} else {
selectedEntries = entries;
}
await Promise.all(selectedEntries.map(async (entry) => {
const entryDirectory = dirname(entry.filename);
if (!await exists(entryDirectory)) {
await Deno.mkdir(entryDirectory, { recursive: true });
}
if (!entry.directory) {
await Deno.writeFile(
entry.filename,
await entry.getData(new Uint8ArrayWriter()),
);
}
}));
await terminateWorkers();
for (const file of executables) {
await Deno.chmod(`chrome-linux64/${file}`, 0o764);
}
}
async function listEntries(archive) {
const zipReader = new ZipReader(await getReader(archive));
const entries = await zipReader.getEntries();
let totalSize = 0;
console.log("Archive: ", archive);
let maxNameLength = 0;
const formattedData = entries.map((entry) => {
const length = formatLength(entry.uncompressedSize);
const splitDate = entry.lastModDate.toISOString().split("T");
const date = splitDate[0].padStart(11);
const time = splitDate[1].match(/([^:]+:[^:]+):/)[1].padEnd(7);
const name = entry.filename;
totalSize += entry.uncompressedSize;
maxNameLength = Math.max(maxNameLength, length.length);
return { length, date, time, name };
});
console.log(
"Length".padStart(maxNameLength - 1, " "),
" Date Time Name",
);
const lengthSeparator = "-".padStart(maxNameLength, "-");
console.log(lengthSeparator, " ---------- ----- ----");
formattedData.forEach(({ length, date, time, name }) =>
console.log(length.padStart(maxNameLength), date, time, name)
);
console.log(lengthSeparator, " ----");
console.log(formatLength(totalSize));
}
function formatLength(length) {
return new Intl.NumberFormat().format(length);
}
async function getReader(archive) {
if (/^https?:/.test(archive)) {
try {
return new HttpReader(archive, {
useRangeHeader: true,
forceRangeRequests: true,
});
} catch (error) {
if (error.message == ERR_HTTP_RANGE) {
try {
return new HttpReader(archive, { useRangeHeader: true });
} catch (error) {
if (error.message == ERR_HTTP_RANGE) {
return new HttpReader(archive);
} else {
throw error;
}
}
} else {
throw error;
}
}
} else {
if (!basename(archive).includes(".")) {
archive += ".zip";
}
return new Uint8ArrayReader(await Deno.readFile(archive));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment