159 lines
5.4 KiB
JavaScript
159 lines
5.4 KiB
JavaScript
|
const ProjectEntityHandler = require('/overleaf/services/web/app/src/Features/Project/ProjectEntityHandler')
|
||
|
const DocumentUpdaterHandler = require('/overleaf/services/web/app/src/Features/DocumentUpdater/DocumentUpdaterHandler')
|
||
|
const { waitForDb } = require('/overleaf/services/web/app/src/infrastructure/mongodb')
|
||
|
const archiver = require('/overleaf/node_modules/archiver')
|
||
|
const ProjectGetter = require('/overleaf/services/web/app/src/Features/Project/ProjectGetter');
|
||
|
const settings = require('/overleaf/node_modules/@overleaf/settings')
|
||
|
const fs = require('fs');
|
||
|
const { promisify } = require('util');
|
||
|
|
||
|
async function main() {
|
||
|
// Get command-line arguments
|
||
|
const args = process.argv.slice(2);
|
||
|
if (args.length < 2) {
|
||
|
console.error('Usage: node download_zip_time.js <projectId> <filename> [<timestamp>]');
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
const projectId = args[0];
|
||
|
const output_filename = args[1];
|
||
|
let time_filter_active = false;
|
||
|
let referenceDate;
|
||
|
|
||
|
// Check if reference date parameter was provided
|
||
|
if (args.length >= 3) {
|
||
|
try {
|
||
|
referenceDate = new Date(args[2]);
|
||
|
if (isNaN(referenceDate.getTime())) {
|
||
|
console.error('Error: Invalid date format provided');
|
||
|
process.exit(1);
|
||
|
}
|
||
|
time_filter_active = true;
|
||
|
} catch (error) {
|
||
|
console.error('Error: Invalid date format provided');
|
||
|
process.exit(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
await waitForDb();
|
||
|
|
||
|
// Flush project to MongoDB
|
||
|
await new Promise((resolve, reject) => {
|
||
|
DocumentUpdaterHandler.flushProjectToMongo(projectId, (error) => {
|
||
|
if (error) process.exit(1);
|
||
|
else resolve();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
// Get project info
|
||
|
const project_info = await new Promise((resolve, reject) => {
|
||
|
ProjectGetter.getProject(projectId, {
|
||
|
'overleaf.history.id': true,
|
||
|
}, (error, project) => {
|
||
|
if (error) process.exit(1);
|
||
|
else resolve(project);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
// Get all files
|
||
|
let list_files = await new Promise((resolve, reject) => {
|
||
|
ProjectEntityHandler.getAllFiles(projectId, (error, result) => {
|
||
|
if (error) process.exit(1);
|
||
|
else resolve(result);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
if (time_filter_active) {
|
||
|
list_files = Object.fromEntries(
|
||
|
Object.entries(list_files).filter(([_, value]) =>
|
||
|
value.created > referenceDate
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// Get all docs
|
||
|
const list_docs = await new Promise((resolve, reject) => {
|
||
|
ProjectEntityHandler.getAllDocs(projectId, (error, result) => {
|
||
|
if (error) process.exit(1);
|
||
|
else resolve(result);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
// Create archive
|
||
|
const archive = archiver('zip', {
|
||
|
zlib: { level: 0 }
|
||
|
});
|
||
|
|
||
|
// Create write stream
|
||
|
const output = fs.createWriteStream(output_filename);
|
||
|
|
||
|
// Set up archive error handling
|
||
|
archive.on('error', err => {
|
||
|
console.log('Archive error:', err);
|
||
|
process.exit(1);
|
||
|
});
|
||
|
|
||
|
archive.on('warning', err => {
|
||
|
if (err.code === 'ENOENT') {
|
||
|
console.log('Archive warning:', err);
|
||
|
}
|
||
|
process.exit(1);
|
||
|
});
|
||
|
|
||
|
// Create promise for output stream
|
||
|
const outputFinished = new Promise((resolve, reject) => {
|
||
|
output.on('close', resolve);
|
||
|
output.on('error', reject);
|
||
|
});
|
||
|
|
||
|
// Pipe archive to output file
|
||
|
archive.pipe(output);
|
||
|
|
||
|
// Add documents to archive
|
||
|
for (const [path, doc] of Object.entries(list_docs)) {
|
||
|
const cleanPath = path.startsWith('/') ? path.slice(1) : path;
|
||
|
console.log('Adding doc', { path: cleanPath });
|
||
|
archive.append(doc.lines.join('\n'), { name: cleanPath });
|
||
|
}
|
||
|
|
||
|
// Add files to archive using promises
|
||
|
const filePromises = Object.entries(list_files).map(async ([path, file]) => {
|
||
|
const cleanPath = path.startsWith('/') ? path.slice(1) : path;
|
||
|
const file_name_on_disk = `${settings.filestore.stores.user_files}/${projectId}_${file._id}`;
|
||
|
|
||
|
// Check if file exists before trying to add it
|
||
|
try {
|
||
|
await promisify(fs.access)(file_name_on_disk);
|
||
|
console.log('Adding file', { path: cleanPath });
|
||
|
return new Promise((resolve, reject) => {
|
||
|
const stream = fs.createReadStream(file_name_on_disk);
|
||
|
stream.on('error', reject);
|
||
|
stream.on('end', resolve);
|
||
|
archive.append(stream, { name: cleanPath });
|
||
|
});
|
||
|
} catch (err) {
|
||
|
console.warn(`File not found: ${cleanPath}`, err);
|
||
|
return Promise.resolve(); // Skip this file but continue with others
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Wait for all files to be processed
|
||
|
await Promise.all(filePromises);
|
||
|
|
||
|
// Finalize the archive
|
||
|
await archive.finalize();
|
||
|
|
||
|
// Wait for the output file to be fully written
|
||
|
await outputFinished;
|
||
|
|
||
|
console.log('Done.');
|
||
|
process.exit(0);
|
||
|
|
||
|
} catch (error) {
|
||
|
console.error("An error occurred:", error);
|
||
|
process.exit(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
main();
|