81 lines
2.4 KiB
JavaScript
81 lines
2.4 KiB
JavaScript
'use strict';
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { createGzip } = require('zlib');
|
|
const tar = require('tar-stream');
|
|
const utils = require('./utils');
|
|
|
|
/** Flat archives need unique names: "a.txt", "a (2).txt", ... */
|
|
function uniqueNamer() {
|
|
const used = new Map();
|
|
return (rawName, fallback) => {
|
|
const base = utils.toSafeBasename(rawName, fallback);
|
|
const prev = used.get(base) || 0;
|
|
used.set(base, prev + 1);
|
|
if (prev === 0) return base;
|
|
const ext = path.extname(base);
|
|
const stem = ext ? base.slice(0, -ext.length) : base;
|
|
return `${ stem } (${ prev + 1 })${ ext }`;
|
|
};
|
|
}
|
|
|
|
const fileOf = (store, info) => store.getFilename(info.metadata.sid + '++' + info.key);
|
|
|
|
async function pipeZip(res, bucket, store) {
|
|
res.header('Content-Type', 'application/zip');
|
|
const { ZipArchive } = await import('archiver');
|
|
const archive = new ZipArchive();
|
|
archive.on('error', err => {
|
|
console.error(err);
|
|
res.destroy(err);
|
|
});
|
|
archive.pipe(res);
|
|
|
|
const name = uniqueNamer();
|
|
for (const info of bucket) {
|
|
await new Promise((resolve, reject) => {
|
|
const stream = fs.createReadStream(fileOf(store, info));
|
|
stream.on('end', resolve);
|
|
stream.on('error', reject);
|
|
archive.append(stream, { name: name(info.metadata.name, info.key) });
|
|
});
|
|
}
|
|
await archive.finalize();
|
|
}
|
|
|
|
async function pipeTarGz(res, bucket, store) {
|
|
res.header('Content-Type', 'application/gzip');
|
|
const pack = tar.pack();
|
|
pack.on('error', err => res.destroy(err));
|
|
pack.pipe(createGzip()).pipe(res);
|
|
|
|
const name = uniqueNamer();
|
|
for (const info of bucket) {
|
|
await new Promise((resolve, reject) => {
|
|
const readStream = fs.createReadStream(fileOf(store, info));
|
|
const entry = pack.entry({ name: name(info.metadata.name, info.key), size: info.size });
|
|
readStream.on('error', reject);
|
|
entry.on('error', reject);
|
|
entry.on('finish', resolve);
|
|
readStream.pipe(entry);
|
|
});
|
|
}
|
|
pack.finalize();
|
|
}
|
|
|
|
/**
|
|
* Stream all files of a bucket as zip or tar.gz. Errors while reading a file
|
|
* abort the response, so a broken archive is never mistaken for a complete one.
|
|
*/
|
|
async function pipeArchive(res, bucket, format, store) {
|
|
try {
|
|
if (format === 'zip') await pipeZip(res, bucket, store);
|
|
else await pipeTarGz(res, bucket, store);
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.destroy(err);
|
|
}
|
|
}
|
|
|
|
module.exports = { pipeArchive, uniqueNamer };
|