u-he-preset-randomizer
Version:
Create random u-he synth presets through randomization and merging of your existing presets.
191 lines • 8.03 kB
JavaScript
/**
* @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