const SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB']; export function formatSize(bytes: number): string { let value = bytes; let unit = 0; while (value >= 1024 && unit < SIZE_UNITS.length - 1) { value /= 1024; unit++; } return `${unit === 0 ? value : value.toFixed(1)} ${SIZE_UNITS[unit]}`; } const DATE_FORMAT = new Intl.DateTimeFormat(undefined, { year: '2-digit', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }); export function formatDate(epochMs: number): string { if (!epochMs) return ''; return DATE_FORMAT.format(new Date(epochMs)); } /** bytes/sec -> "14.2 MB/s" or "113.8 Mbps" depending on the configured unit. */ export function formatSpeed(bytesPerSec: number, unit: 'MBps' | 'Mbps'): string { if (unit === 'Mbps') { return `${((bytesPerSec * 8) / 1_000_000).toFixed(1)} Mbps`; } return `${formatSize(bytesPerSec)}/s`; } /** Seconds -> "0:42" / "3:07" / "1:02:15". */ export function formatEta(seconds: number): string { if (!Number.isFinite(seconds) || seconds < 0) return ''; const s = Math.round(seconds); const h = Math.floor(s / 3600); const m = Math.floor((s % 3600) / 60); const sec = s % 60; const mmss = `${m}:${String(sec).padStart(2, '0')}`; return h ? `${h}:${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}` : mmss; } /** * Separator-aware join for UI-composed paths: remote and POSIX-local dirs use * "/", a Windows-local dir ("C:\Users\x") keeps "\" so paths stay consistent. */ export function joinPath(dir: string, name: string): string { const sep = dir.includes('\\') && !dir.includes('/') ? '\\' : '/'; return dir.endsWith(sep) ? dir + name : `${dir}${sep}${name}`; } /** "/" (POSIX) or a drive root like "C:\" — nothing above to navigate to. */ export function isRootPath(p: string): boolean { return p === '/' || /^[A-Za-z]:[\\/]?$/.test(p); } /** 0o755 -> "rwxr-xr-x" */ export function formatMode(mode: number): string { const flags = 'rwxrwxrwx'; let out = ''; for (let i = 0; i < 9; i++) { out += mode & (0o400 >> i) ? flags[i] : '-'; } return out; }