import path from 'path'; import { createWriteStream } from 'fs'; import { pipeline } from 'stream'; import { promisify } from 'util'; import multer from 'multer'; import { promises as fs } from 'fs'; export async function getDirectoryContents(dirPath) { try { await fs.access(dirPath); } catch { // Create directory if it doesn't exist await fs.mkdir(dirPath, { recursive: true }); } const items = await fs.readdir(dirPath, { withFileTypes: true }); const files = []; console.log(dirPath, items); for (const item of items) { const itemPath = path.join(dirPath, item.name); const stats = await fs.stat(itemPath); files.push({ name: item.name, path: itemPath, isDirectory: item.isDirectory(), size: item.isDirectory() ? 0 : stats.size, modified: stats.mtime.toISOString() }); } // Sort: directories first, then files, both alphabetically return files.sort((a, b) => { if (a.isDirectory && !b.isDirectory) return -1; if (!a.isDirectory && b.isDirectory) return 1; return a.name.localeCompare(b.name); }); } export function upload(path, zimOnly = false) { const storage = multer.diskStorage({ destination: async (req, file, cb) => { const uploadPath = req.body.uploadPath || path; try { await fs.mkdir(uploadPath, { recursive: true }); cb(null, uploadPath); } catch (err) { cb(err); } }, filename: (req, file, cb) => { let finalFilename = file.originalname; if(req.body.filename && req.body.filename.trim() !== '') { finalFilename = req.body.filename.trim(); if(!finalFilename.toLowerCase().endsWith('.zim')) { finalFilename += '.zim'; } } cb(null, finalFilename); } }); return multer({ storage, fileFilter: (req, file, cb) => { if(!zimOnly) cb(null, true); else if(/\.zim$/i.test(file.originalname)) cb(null, true); else cb(new Error('Only ZIM files allowed')); } }); } const pipelineAsync = promisify(pipeline); export async function downloadFile(url, destination) { if(!url.endsWith('.zim')) throw new Error('Only ZIM files allowed'); if(!destination.endsWith('.zim')) destination += '.zim'; const response = await fetch(url); if (!response.ok) throw new Error(`HTTP error! ${response.status}`); const writer = createWriteStream(destination); await pipelineAsync(response.body, writer); return destination; } export function parseSize(sizeStr) { if (sizeStr.toLowerCase() === 'infinity') return Infinity; const units = { B: 1, KB: 1024, MB: 1024 ** 2, GB: 1024 ** 3 }; const match = sizeStr.match(/^(\d+)(B|KB|MB|GB)$/i); if (!match) return 100 * 1024 * 1024; // default 100MB return parseInt(match[1]) * units[match[2].toUpperCase()]; } export async function ensureDirectoryExists(dirPath) { try { await fs.mkdir(dirPath, { recursive: true }); } catch (err) { if (err.code !== 'EEXIST') throw err; } } export function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } export async function getZimFiles(archiveDir) { await ensureDirectoryExists(archiveDir); const items = await fs.readdir(archiveDir, { withFileTypes: true }); const zimFiles = []; for (const item of items) { if (item.isFile() && path.extname(item.name).toLowerCase() === '.zim') { const fullPath = path.join(archiveDir, item.name); const stats = await fs.stat(fullPath); zimFiles.push({ name: item.name, path: fullPath, size: stats.size, modified: stats.mtime.toISOString() }); } } return zimFiles.sort((a, b) => new Date(b.modified) - new Date(a.modified)); } export async function deleteFile(filePath) { await fs.unlink(filePath); }