import fs from 'node:fs' import crypto from 'node:crypto' import path from 'node:path' import Stream from 'node:stream' import { callbackify, promisify } from 'node:util' import metrics from '@overleaf/metrics' import Settings from '@overleaf/settings' import Errors from './Errors.js' const { WriteError } = Errors export default { promises: { writeStream, deleteFile, }, writeStream: callbackify(writeStream), deleteFile: callbackify(deleteFile), } const pipeline = promisify(Stream.pipeline) async function writeStream(stream, key) { const timer = new metrics.Timer('writingFile') const fsPath = _getPath(key) const writeStream = fs.createWriteStream(fsPath) try { await pipeline(stream, writeStream) timer.done() return fsPath } catch (err) { await deleteFile(fsPath) throw new WriteError('problem writing file locally', { fsPath }, err) } } async function deleteFile(fsPath) { if (!fsPath) { return } try { await fs.promises.unlink(fsPath) } catch (err) { if (err.code !== 'ENOENT') { throw new WriteError('failed to delete file', { fsPath }, err) } } } function _getPath(key) { if (key == null) { key = crypto.randomUUID() } key = key.replace(/\//g, '-') return path.join(Settings.path.uploadFolder, key) }