UNPKG

u-he-preset-randomizer

Version:

Create random u-he synth presets through randomization and merging of your existing presets.

191 lines 8.03 kB
/** * @file Preset library management and loading. * Handles discovery, loading, filtering, and writing of u-he preset libraries. */ import path from 'node:path'; import chalk from 'chalk'; import fg from 'fast-glob'; import fs from 'fs-extra'; import { isValidPreset, parseUhePreset, serializePresetToFile, } from './parser.js'; import { detectPresetLibraryLocations, } from './utils/detector.js'; export function loadPresetLibrary(synth, config, preDetectedLocation) { let pattern = config.pattern ?? '**/*'; // Detect correct Preset Library Location const location = preDetectedLocation ?? detectPresetLibraryLocations(config).find((el) => el.synthName.toLowerCase() === synth.toLowerCase()); if (!location) { throw new Error(`Could not find preset library location for synth: ${synth}`); } const presetLibrary = { synth: location.synthName, rootFolder: location.root, userPresetsFolder: location.userPresets, presetsFolder: location.presets, presets: [], favorites: [], }; let librarySelector; if (pattern.startsWith('/User/')) { librarySelector = 'User'; pattern = pattern.replace('/User/', ''); } else if (pattern.startsWith('/Local/')) { librarySelector = 'Local'; pattern = pattern.replace('/Local/', ''); } if (pattern === '/**/*') { pattern = '**/*'; } pattern = pattern.split('//').join('/'); // Load preset library const libraryPresets = fg .sync([`${pattern}.h2p`], { cwd: presetLibrary.presetsFolder ?? '' }) .map((el) => { return `/Local/${el}`; }); if (librarySelector !== 'User' && presetLibrary.presetsFolder) { if (libraryPresets.length > 0) { for (const presetPath of libraryPresets) { try { const presetString = fs .readFileSync(path.join(presetLibrary.presetsFolder, presetPath.replace('/Local/', ''))) .toString(); const parsedPreset = parseUhePreset(presetString, presetPath, config.binary ?? false); if (isValidPreset(parsedPreset)) { presetLibrary.presets.push(parsedPreset); } } catch (err) { console.warn(chalk.yellow(`Could not load and parse preset: ${presetPath}`, err)); } } } else { console.log(''); console.warn(chalk.yellow(`Could not find presets with glob pattern: ${pattern}.h2p in library: ${presetLibrary.presetsFolder}`)); } } // Load user preset library const userPresets = fg .sync([`${pattern}.h2p`], { cwd: presetLibrary.userPresetsFolder, ignore: ['RANDOM/**/*'], }) .map((el) => { return `/User/${el}`; }); if (librarySelector !== 'Local') { if (userPresets.length > 0) { for (const presetPath of userPresets) { try { const presetString = fs .readFileSync(path.join(presetLibrary.userPresetsFolder, presetPath.replace('/User/', ''))) .toString(); const parsedPreset = parseUhePreset(presetString, presetPath, config.binary ?? false); if (isValidPreset(parsedPreset)) { presetLibrary.presets.push(parsedPreset); } } catch (err) { console.warn(chalk.yellow(`Could not load and parse preset: ${presetPath}`), err); } } } else { console.log(''); console.warn(chalk.yellow(`Could not find presets with glob pattern: ${pattern}.h2p in user library: ${presetLibrary.userPresetsFolder}`)); } } if (presetLibrary.presets.length === 0) { throw new Error(`No presets found with glob pattern: ${pattern}.h2p in ${presetLibrary.presetsFolder}`); } // Search and load .uhe-fav files const favorites = fg .sync([`**/*.uhe-fav`], { cwd: presetLibrary.rootFolder }) .sort(); for (const favoriteFile of favorites) { const path = `${presetLibrary.rootFolder}/${favoriteFile}`; try { const favJson = fs.readJSONSync(path); for (const favCategory in favJson['tag-category-fav']) { const categoryPresets = favJson['tag-category-fav'][favCategory]; if (categoryPresets) { presetLibrary.favorites.push({ fileName: favoriteFile, presets: categoryPresets.map((el) => { return { name: el.name, path: el.db_path, }; }), }); } } } catch (err) { console.warn(chalk.yellow(`Could not read / parse: ${path}`), err); } } // Debug: Log memory usage after loading all presets if (config.debug) { const memUsage = process.memoryUsage(); console.log(chalk.gray('━'.repeat(60))); console.log(chalk.cyan('Memory Usage After Loading Presets:')); console.log(chalk.gray(` Heap Used: ${(memUsage.heapUsed / 1024 / 1024).toFixed(2)} MB`)); console.log(chalk.gray(` Heap Total: ${(memUsage.heapTotal / 1024 / 1024).toFixed(2)} MB`)); console.log(chalk.gray(` RSS: ${(memUsage.rss / 1024 / 1024).toFixed(2)} MB`)); console.log(chalk.gray(` Presets Loaded: ${presetLibrary.presets.length.toLocaleString()}`)); console.log(chalk.gray('━'.repeat(60))); } return presetLibrary; } /** * Validates that a file path is safe and stays within the target directory. * Prevents path traversal attacks using ../ or absolute paths. */ function validateSafePath(targetDir, filePath) { // Remove leading slash if present (these are treated as relative paths in this codebase) const normalizedFilePath = filePath.startsWith('/') ? filePath.slice(1) : filePath; // Resolve both paths to absolute paths to handle relative paths and symlinks const resolvedTargetDir = path.resolve(targetDir); const resolvedFilePath = path.resolve(targetDir, normalizedFilePath); // Ensure the resolved file path starts with the target directory if (!resolvedFilePath.startsWith(resolvedTargetDir + path.sep)) { throw new Error(`Path traversal detected: "${filePath}" attempts to write outside "${targetDir}"`); } return resolvedFilePath; } export function writePresetLibrary(presetLibrary) { const writtenFiles = []; for (const preset of presetLibrary.presets) { // Validate the file path to prevent path traversal attacks const filePath = validateSafePath(presetLibrary.userPresetsFolder, preset.filePath); const fileContent = serializePresetToFile(preset); // Explicitly ensure directory exists first (helps with cloud/network filesystems) const dirPath = path.dirname(filePath); try { fs.ensureDirSync(dirPath); } catch (dirError) { // Retry once after a brief delay (cloud filesystems may need time) const err = dirError; if (err.code === 'ENOENT') { // Wait briefly and retry const startTime = Date.now(); while (Date.now() - startTime < 100) { // Busy wait for ~100ms } fs.ensureDirSync(dirPath); } else { throw dirError; } } fs.outputFileSync(filePath, fileContent); writtenFiles.push(path.normalize(filePath)); } return writtenFiles; } //# sourceMappingURL=presetLibrary.js.map