1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-27 10:32:58 +02:00
joplin/packages/app-mobile/utils/fs-driver/tarExtract.ts
2024-08-02 14:51:49 +01:00

97 lines
2.5 KiB
TypeScript

import { extract as tarStreamExtract } from 'tar-stream';
import { resolve, dirname } from 'path';
import shim from '@joplin/lib/shim';
import { chunkSize } from './constants';
export interface TarExtractOptions {
cwd: string;
file: string;
}
const tarExtract = async (options: TarExtractOptions) => {
const cwd = options.cwd;
// resolve doesn't correctly handle file:// or content:// URLs. Thus, we don't resolve relative
// to cwd if the source is a URL.
const isSourceUrl = options.file.match(/$[a-z]+:\/\//);
const filePath = isSourceUrl ? options.file : resolve(cwd, options.file);
const fsDriver = shim.fsDriver();
if (!(await fsDriver.exists(filePath))) {
throw new Error('tarExtract: Source file does not exist');
}
const extract = tarStreamExtract({ defaultEncoding: 'base64' });
extract.on('entry', async (header, stream, next) => {
const outPath = fsDriver.resolveRelativePathWithinDir(cwd, header.name);
if (await fsDriver.exists(outPath)) {
throw new Error(`Extracting ${outPath} would overwrite`);
}
// Allows moving to the next item after all data for this entry has been read
// **and** this data has been processed.
// See https://github.com/laurent22/joplin/issues/10285
const streamEndPromise = new Promise<void>((resolve) => {
stream.once('end', () => resolve());
});
if (header.type === 'directory') {
await fsDriver.mkdir(outPath);
} else if (header.type === 'file') {
const parentDir = dirname(outPath);
await fsDriver.mkdir(parentDir);
await fsDriver.appendBinaryReadableToFile(outPath, stream);
} else {
throw new Error(`Unsupported file system entity type: ${header.type}`);
}
// Drain the rest of the stream.
stream.resume();
await streamEndPromise;
next();
});
let finished = false;
const finishPromise = new Promise<void>((resolve, reject) => {
extract.once('finish', () => {
finished = true;
resolve();
});
extract.once('error', (error) => {
reject(error);
});
});
const fileHandle = await fsDriver.open(filePath, 'r');
const readChunk = async () => {
return await fsDriver.readFileChunkAsBuffer(fileHandle, chunkSize);
};
try {
let chunk = await readChunk();
let nextChunk = await readChunk();
do {
extract.write(chunk);
chunk = nextChunk;
nextChunk = await readChunk();
} while (nextChunk !== null && !finished);
if (chunk !== null) {
extract.end(chunk);
} else {
extract.end();
}
} finally {
await fsDriver.close(fileHandle);
}
await finishPromise;
};
export default tarExtract;