Map keys 0–9 to destinations, inspect files before deciding, and preview PDF/RAW/ZIP/video with an OLED dark UI polish plus demo seed and tests.
92 lines
3.1 KiB
JavaScript
92 lines
3.1 KiB
JavaScript
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { listZipEntries, formatZipListing } = require('../lib/zip-list');
|
|
|
|
function writeTinyZip(dir) {
|
|
// Minimal hand-rolled store-method zip with two files (no compression).
|
|
// Local file headers + central directory + EOCD.
|
|
function local(name, data) {
|
|
const n = Buffer.from(name);
|
|
const header = Buffer.alloc(30);
|
|
header.writeUInt32LE(0x04034b50, 0);
|
|
header.writeUInt16LE(20, 4); // version
|
|
header.writeUInt16LE(0, 6); // flags
|
|
header.writeUInt16LE(0, 8); // store
|
|
header.writeUInt16LE(0, 10);
|
|
header.writeUInt16LE(0, 12);
|
|
header.writeUInt32LE(0, 14); // crc ignored for our reader
|
|
header.writeUInt32LE(data.length, 18);
|
|
header.writeUInt32LE(data.length, 22);
|
|
header.writeUInt16LE(n.length, 26);
|
|
header.writeUInt16LE(0, 28);
|
|
return Buffer.concat([header, n, data]);
|
|
}
|
|
function central(name, data, localOffset) {
|
|
const n = Buffer.from(name);
|
|
const header = Buffer.alloc(46);
|
|
header.writeUInt32LE(0x02014b50, 0);
|
|
header.writeUInt16LE(20, 4);
|
|
header.writeUInt16LE(20, 6);
|
|
header.writeUInt16LE(0, 8);
|
|
header.writeUInt16LE(0, 10);
|
|
header.writeUInt16LE(0, 12);
|
|
header.writeUInt16LE(0, 14);
|
|
header.writeUInt32LE(0, 16);
|
|
header.writeUInt32LE(data.length, 20);
|
|
header.writeUInt32LE(data.length, 24);
|
|
header.writeUInt16LE(n.length, 28);
|
|
header.writeUInt16LE(0, 30);
|
|
header.writeUInt16LE(0, 32);
|
|
header.writeUInt16LE(0, 34);
|
|
header.writeUInt16LE(0, 36);
|
|
header.writeUInt32LE(0, 38);
|
|
header.writeUInt32LE(localOffset, 42);
|
|
return Buffer.concat([header, n]);
|
|
}
|
|
|
|
const a = Buffer.from('alpha\n');
|
|
const b = Buffer.from('item,qty\n1,2\n');
|
|
const locA = local('readme.txt', a);
|
|
const locB = local('data/rows.csv', b);
|
|
const body = Buffer.concat([locA, locB]);
|
|
const cen = Buffer.concat([
|
|
central('readme.txt', a, 0),
|
|
central('data/rows.csv', b, locA.length),
|
|
]);
|
|
const eocd = Buffer.alloc(22);
|
|
eocd.writeUInt32LE(0x06054b50, 0);
|
|
eocd.writeUInt16LE(0, 4);
|
|
eocd.writeUInt16LE(0, 6);
|
|
eocd.writeUInt16LE(2, 8);
|
|
eocd.writeUInt16LE(2, 10);
|
|
eocd.writeUInt32LE(cen.length, 12);
|
|
eocd.writeUInt32LE(body.length, 16);
|
|
eocd.writeUInt16LE(0, 20);
|
|
|
|
const zipPath = path.join(dir, 'sample.zip');
|
|
fs.writeFileSync(zipPath, Buffer.concat([body, cen, eocd]));
|
|
return zipPath;
|
|
}
|
|
|
|
test('listZipEntries reads names and sizes without extracting', async () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'swipeanything-zip-'));
|
|
const zipPath = writeTinyZip(dir);
|
|
const listing = await listZipEntries(zipPath);
|
|
assert.equal(listing.totalEntries, 2);
|
|
assert.equal(listing.truncated, false);
|
|
assert.deepEqual(
|
|
listing.entries.map((e) => e.name),
|
|
['readme.txt', 'data/rows.csv']
|
|
);
|
|
assert.equal(listing.entries[0].size, 6);
|
|
const text = formatZipListing(listing);
|
|
assert.match(text, /readme\.txt/);
|
|
assert.match(text, /data\/rows\.csv/);
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
});
|