Files
chatpapers/src/modules/lecture/infrastructure/platform/paths.ts
T
2026-08-30 09:02:11 +08:00

81 lines
2.5 KiB
TypeScript

import { isWindows } from "./os";
/**
* Normalize user-provided executable path for the current OS.
* - Expands ~ on macOS/Linux via PathUtils (when available)
* - Ensures .exe suffix on Windows when missing
*/
export function normalizeExecutablePath(input: string): string {
const trimmed = (input || "").trim();
if (!trimmed) return "";
const path = trimmed;
if (isWindows() && !/\.(exe|cmd|bat)$/i.test(path)) {
return `${path}.exe`;
}
return path;
}
/** Join path segments using Zotero PathUtils (cross-platform). */
export function joinPath(...parts: string[]): string {
const filtered = parts.filter(Boolean);
if (!filtered.length) return "";
try {
return PathUtils.join(...filtered);
} catch (e) {
ztoolkit.log("[ChatPapers] PathUtils.join failed", filtered, e);
throw e;
}
}
/** Normalize user-provided storage directory paths (prefs). */
export function normalizeStoragePath(input: string): string {
const trimmed = (input || "").trim();
if (!trimmed) return "";
const unified = trimmed.replace(/\\/g, "/").replace(/\/+/g, "/");
const winMatch = unified.match(/^([A-Za-z]:)(\/.*)?$/);
if (winMatch) {
const drive = winMatch[1];
const rest = (winMatch[2] || "")
.split("/")
.filter((part) => part && part !== ".")
.join("\\");
return rest ? `${drive}\\${rest}` : drive;
}
const absolute = unified.startsWith("/");
const parts = unified.split("/").filter((part) => part && part !== ".");
let result = parts.join("/");
if (absolute) result = `/${result}`;
return result.replace(/\/+$/, "") || (absolute ? "/" : "");
}
/** Safe single path segment for file or directory names (cross-platform). */
export function sanitizePathSegment(input: string, maxLen = 120): string {
const cleaned = String(input || "")
.replace(/[<>:"/\\|?*\x00-\x1f]/g, "_")
.replace(/[.\s]+$/, "")
.trim();
if (!cleaned) return "_";
if (cleaned.length <= maxLen) return cleaned;
let hash = 0;
for (let i = 0; i < input.length; i++) {
hash = (hash * 31 + input.charCodeAt(i)) | 0;
}
const tail = Math.abs(hash).toString(36);
return `${cleaned.slice(0, Math.max(1, maxLen - tail.length - 1))}_${tail}`;
}
/** Convert local file path to file:// URL for <audio src>. */
export function pathToFileUrl(filePath: string): string {
const normalized = filePath.replace(/\\/g, "/");
if (normalized.startsWith("file://")) return normalized;
if (/^[A-Za-z]:\//.test(normalized)) {
return `file:///${encodeURI(normalized)}`;
}
return `file://${encodeURI(normalized)}`;
}