/** * Pfade der Dateiansicht (quick-261008-mzu). Pfade beginnen immer mit "/"; * die Wurzel ist "/". Die API prueft jeden Pfad noch einmal selbst (D-H) — * diese Pruefungen dienen nur der verstaendlichen Rueckmeldung im Dialog. */ export const ROOT = '/'; const MAX_NAME_LENGTH = 250; export function join(dir: string, name: string): string { const base = dir.endsWith('/') ? dir.slice(0, -1) : dir; return `${base}/${name}`; } export function parent(path: string): string { if (path === ROOT || path === '') return ROOT; const trimmed = path.endsWith('/') ? path.slice(0, -1) : path; const i = trimmed.lastIndexOf('/'); return i <= 0 ? ROOT : trimmed.slice(0, i); } export function basename(path: string): string { if (path === ROOT) return ''; const trimmed = path.endsWith('/') ? path.slice(0, -1) : path; return trimmed.slice(trimmed.lastIndexOf('/') + 1); } /** Bestandteile eines Pfads: "/a/b" → ["a", "b"]; Wurzel → []. */ export function segments(path: string): string[] { return path.split('/').filter((s) => s !== ''); } /** Pfad aus den ersten `count` Bestandteilen. */ export function pathOf(parts: readonly string[], count = parts.length): string { return count <= 0 ? ROOT : `/${parts.slice(0, count).join('/')}`; } export type NameProblem = 'empty' | 'dots' | 'slash' | 'part' | 'tooLong'; /** null = gueltiger Name, sonst der Grund. */ export function nameProblem(name: string): NameProblem | null { if (name.trim() === '') return 'empty'; if (name === '.' || name === '..') return 'dots'; if (name.includes('/') || name.includes('\\')) return 'slash'; if (name.toLowerCase().endsWith('.part')) return 'part'; if (name.length > MAX_NAME_LENGTH) return 'tooLong'; return null; } export function validName(name: string): boolean { return nameProblem(name) === null; } const DOUBLE_EXTENSIONS = ['.tar.gz', '.tar.bz2', '.tar.xz', '.tar.zst']; /** Name ohne und mit Endung trennen; "Archiv.tar.gz" → ["Archiv", ".tar.gz"]. */ export function splitExtension(name: string): [string, string] { const lower = name.toLowerCase(); for (const ext of DOUBLE_EXTENSIONS) { if (lower.endsWith(ext) && name.length > ext.length) { return [name.slice(0, -ext.length), name.slice(-ext.length)]; } } const dot = name.lastIndexOf('.'); if (dot <= 0) return [name, '']; return [name.slice(0, dot), name.slice(dot)]; } /** Freier Name nach dem Muster "Bericht (2).pdf", "Bericht (3).pdf" … */ export function freeName(name: string, taken: readonly string[]): string { const used = new Set(taken.map((n) => n.toLowerCase())); if (!used.has(name.toLowerCase())) return name; const [base, ext] = splitExtension(name); for (let i = 2; ; i++) { const candidate = `${base} (${i})${ext}`; if (!used.has(candidate.toLowerCase())) return candidate; } } /** Liegt `path` innerhalb von `folder` (echt darunter)? "/a" und "/ab" gelten nicht. */ export function isInside(folder: string, path: string): boolean { if (folder === ROOT) return path !== ROOT; return path.startsWith(`${folder}/`); }