const SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB']; /** Locale-aware number formatting (decimal separator); cached per locale. */ const NUM_FORMATS = new Map(); function num(value: number, locale: string | undefined): string { const key = locale ?? ''; let fmt = NUM_FORMATS.get(key); if (!fmt) { fmt = new Intl.NumberFormat(locale, { maximumFractionDigits: 1 }); NUM_FORMATS.set(key, fmt); } return fmt.format(value); } export function formatSize(bytes: number, locale?: string): string { let value = bytes; let unit = 0; while (value >= 1024 && unit < SIZE_UNITS.length - 1) { value /= 1024; unit++; } return `${unit === 0 ? num(value, locale) : num(Number(value.toFixed(1)), locale)} ${SIZE_UNITS[unit]}`; } const DATE_FORMATS = new Map(); export function formatDate(epochMs: number, locale?: string): string { if (!epochMs) return ''; const key = locale ?? ''; let fmt = DATE_FORMATS.get(key); if (!fmt) { fmt = new Intl.DateTimeFormat(locale, { year: '2-digit', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }); DATE_FORMATS.set(key, fmt); } return fmt.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', locale?: string): string { if (unit === 'Mbps') { return `${num(Number(((bytesPerSec * 8) / 1_000_000).toFixed(1)), locale)} Mbps`; } return `${formatSize(bytesPerSec, locale)}/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; }