Add cross-platform path handling (#89)

Add cross-platform path handling support

- Add sanitizePath, joinPath, buildOutputPath utilities
- Add operatingSystem to Settings interface
- Replace hardcoded Windows paths with dynamic path handling
- Support Windows, Linux, and macOS
This commit is contained in:
Ahmed Alghafri
2025-11-21 20:36:25 -07:00
committed by GitHub
parent bb9e2dcbb6
commit a49bb560bd
3 changed files with 51 additions and 18 deletions
+13 -14
View File
@@ -36,6 +36,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Spinner } from "@/components/ui/spinner";
import { joinPath, sanitizePath } from "./lib/utils";
function App() {
const [spotifyUrl, setSpotifyUrl] = useState("");
@@ -125,32 +126,30 @@ function App() {
const query = trackName && artistName ? `${trackName} ${artistName}` : undefined;
// Build output directory based on settings
const os = settings.operatingSystem;
// Base download path
let outputDir = settings.downloadPath;
// For playlist or artist discography downloads
if (playlistName) {
const sanitizedPlaylist = playlistName.replace(/[<>:"/\\|?*]/g, '_').trim();
outputDir = `${settings.downloadPath}\\${sanitizedPlaylist}`;
// For artist discography: only use album subfolder (artist is redundant)
// Playlist or discography
if (playlistName) {
outputDir = joinPath(os, outputDir, sanitizePath(playlistName, os));
if (isArtistDiscography) {
// Only add album subfolder if enabled
// Only album subfolder
if (settings.albumSubfolder && albumName) {
const sanitizedAlbum = albumName.replace(/[<>:"/\\|?*]/g, '_').trim();
outputDir = `${outputDir}\\${sanitizedAlbum}`;
outputDir = joinPath(os, outputDir, sanitizePath(albumName, os));
}
} else {
// For playlist: use both artist and album subfolders if enabled
// Add artist subfolder if enabled
// Playlist rules:
if (settings.artistSubfolder && artistName) {
const sanitizedArtist = artistName.replace(/[<>:"/\\|?*]/g, '_').trim();
outputDir = `${outputDir}\\${sanitizedArtist}`;
outputDir = joinPath(os, outputDir, sanitizePath(artistName, os));
}
// Add album subfolder if enabled
if (settings.albumSubfolder && albumName) {
const sanitizedAlbum = albumName.replace(/[<>:"/\\|?*]/g, '_').trim();
outputDir = `${outputDir}\\${sanitizedAlbum}`;
outputDir = joinPath(os, outputDir, sanitizePath(albumName, os));
}
}
}
+7 -2
View File
@@ -9,6 +9,7 @@ export interface Settings {
artistSubfolder: boolean;
albumSubfolder: boolean;
trackNumber: boolean;
operatingSystem: "Windows" | "linux/MacOS"
}
export const DEFAULT_SETTINGS: Settings = {
@@ -20,16 +21,20 @@ export const DEFAULT_SETTINGS: Settings = {
artistSubfolder: false,
albumSubfolder: false,
trackNumber: false,
operatingSystem: "Windows"
};
// TODO: add mac/linux defaults
const DEFAULT_PATH : string = "C:\\Users\\Public\\Music";
async function fetchDefaultPath(): Promise<string> {
try {
const data = await GetDefaults();
return data.downloadPath || "C:\\Users\\Public\\Music";
return data.downloadPath || DEFAULT_PATH;
} catch (error) {
console.error("Failed to fetch default path:", error);
}
return "C:\\Users\\Public\\Music";
return DEFAULT_PATH;
}
const SETTINGS_KEY = "spotiflac-settings";
+29
View File
@@ -1,6 +1,35 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
import type { Settings } from "./settings";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function sanitizePath(input: string, os: string): string {
if (os === "Windows") {
return input.replace(/[<>:"/\\|?*]/g, "_");
}
// unix-based OS
return input.replace(/\//g, "_");
}
export function joinPath(os: string, ...parts: string[]): string {
const sep = os === "Windows" ? "\\" : "/";
return parts
.filter(Boolean)
.map(p => p.replace(/^[/\\]+|[/\\]+$/g, ""))
.join(sep);
}
export function buildOutputPath(settings: Settings, folder?: string) {
const os = settings.operatingSystem;
const base = settings.downloadPath || "";
const sanitized = folder ? sanitizePath(folder, os) : undefined;
return sanitized ? joinPath(os, base, sanitized) : base;
}