Skip to content

Commit 388875d

Browse files
committed
refactor: move merge config to config.ts
1 parent 0a8a732 commit 388875d

File tree

2 files changed

+224
-223
lines changed

2 files changed

+224
-223
lines changed

src/config.ts

+218
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import { existsSync, readFileSync } from "node:fs";
22
import yaml from "yaml";
33
import { getHelpers } from "./env";
4+
import { loadLocalRecyclarrTemplate } from "./local-importer";
45
import { logger } from "./logger";
6+
import { filterInvalidQualityProfiles } from "./quality-profiles";
7+
import { loadRecyclarrTemplates } from "./recyclarr-importer";
8+
import { loadQPFromTrash, transformTrashQPCFs, transformTrashQPToTemplate } from "./trash-guide";
9+
import { ArrType, MappedMergedTemplates } from "./types/common.types";
510
import {
611
ConfigArrInstance,
712
ConfigCustomFormat,
813
ConfigIncludeItem,
14+
ConfigQualityProfile,
915
ConfigSchema,
1016
InputConfigArrInstance,
1117
InputConfigIncludeItem,
@@ -151,3 +157,215 @@ export const validateConfig = (input: InputConfigInstance): MergedConfigInstance
151157
})),
152158
};
153159
};
160+
161+
/**
162+
* Load data from trash, recyclarr, custom configs and merge.
163+
* Afterwards do sanitize and check against required configuration.
164+
* @param value
165+
* @param arrType
166+
*/
167+
export const mergeConfigsAndTemplates = async (
168+
value: InputConfigArrInstance,
169+
arrType: ArrType,
170+
): Promise<{ config: MergedConfigInstance }> => {
171+
const recyclarrTemplateMap = loadRecyclarrTemplates(arrType);
172+
const localTemplateMap = loadLocalRecyclarrTemplate(arrType);
173+
const trashTemplates = await loadQPFromTrash(arrType);
174+
175+
logger.debug(
176+
`Loaded ${recyclarrTemplateMap.size} Recyclarr templates, ${localTemplateMap.size} local templates and ${trashTemplates.size} trash templates.`,
177+
);
178+
179+
const recyclarrMergedTemplates: MappedMergedTemplates = {
180+
custom_formats: [],
181+
quality_profiles: [],
182+
};
183+
184+
// HINT: we assume customFormatDefinitions only exist in RECYCLARR
185+
if (value.include) {
186+
const mappedIncludes = value.include.reduce<{ recyclarr: InputConfigIncludeItem[]; trash: InputConfigIncludeItem[] }>(
187+
(previous, current) => {
188+
switch (current.source) {
189+
case "TRASH":
190+
previous.trash.push(current);
191+
break;
192+
case "RECYCLARR":
193+
previous.recyclarr.push(current);
194+
break;
195+
default:
196+
logger.warn(`Unknown type for template requested: ${(current as any).type}. Ignoring.`);
197+
}
198+
199+
return previous;
200+
},
201+
{ recyclarr: [], trash: [] },
202+
);
203+
204+
logger.info(
205+
`Found ${value.include.length} templates to include. Mapped to [recyclarr]=${mappedIncludes.recyclarr.length}, [trash]=${mappedIncludes.trash.length} ...`,
206+
);
207+
208+
mappedIncludes.recyclarr.forEach((e) => {
209+
const template = recyclarrTemplateMap.get(e.template) ?? localTemplateMap.get(e.template);
210+
211+
if (!template) {
212+
logger.warn(`Unknown recyclarr template requested: ${e.template}`);
213+
return;
214+
}
215+
216+
if (template.custom_formats) {
217+
recyclarrMergedTemplates.custom_formats?.push(...template.custom_formats);
218+
}
219+
220+
if (template.quality_definition) {
221+
recyclarrMergedTemplates.quality_definition = template.quality_definition;
222+
}
223+
224+
if (template.quality_profiles) {
225+
for (const qp of template.quality_profiles) {
226+
recyclarrMergedTemplates.quality_profiles.push(qp);
227+
}
228+
}
229+
230+
if (template.media_management) {
231+
recyclarrMergedTemplates.media_management = { ...recyclarrMergedTemplates.media_management, ...template.media_management };
232+
}
233+
234+
if (template.media_naming) {
235+
recyclarrMergedTemplates.media_naming = { ...recyclarrMergedTemplates.media_naming, ...template.media_naming };
236+
}
237+
238+
if (template.customFormatDefinitions) {
239+
if (Array.isArray(template.customFormatDefinitions)) {
240+
recyclarrMergedTemplates.customFormatDefinitions = [
241+
...(recyclarrMergedTemplates.customFormatDefinitions || []),
242+
...template.customFormatDefinitions,
243+
];
244+
} else {
245+
logger.warn(`CustomFormatDefinitions in template must be an array. Ignoring.`);
246+
}
247+
}
248+
249+
// TODO Ignore recursive include for now
250+
if (template.include) {
251+
logger.warn(`Recursive includes not supported at the moment. Ignoring.`);
252+
}
253+
});
254+
255+
// TODO: local TRaSH-Guides QP templates do not work yet
256+
mappedIncludes.trash.forEach((e) => {
257+
const template = trashTemplates.get(e.template);
258+
259+
if (!template) {
260+
logger.warn(`Unknown trash template requested: ${e.template}`);
261+
return;
262+
}
263+
264+
recyclarrMergedTemplates.quality_profiles.push(transformTrashQPToTemplate(template));
265+
recyclarrMergedTemplates.custom_formats.push(transformTrashQPCFs(template));
266+
});
267+
}
268+
269+
// Config values overwrite template values
270+
if (value.custom_formats) {
271+
recyclarrMergedTemplates.custom_formats.push(...value.custom_formats);
272+
}
273+
274+
if (value.quality_profiles) {
275+
recyclarrMergedTemplates.quality_profiles.push(...value.quality_profiles);
276+
}
277+
278+
if (value.media_management) {
279+
recyclarrMergedTemplates.media_management = { ...recyclarrMergedTemplates.media_management, ...value.media_management };
280+
}
281+
282+
if (value.media_naming) {
283+
recyclarrMergedTemplates.media_naming = { ...recyclarrMergedTemplates.media_naming, ...value.media_naming };
284+
}
285+
286+
if (value.quality_definition) {
287+
recyclarrMergedTemplates.quality_definition = { ...recyclarrMergedTemplates.quality_definition, ...value.quality_definition };
288+
}
289+
290+
if (value.customFormatDefinitions) {
291+
if (Array.isArray(value.customFormatDefinitions)) {
292+
recyclarrMergedTemplates.customFormatDefinitions = [
293+
...(recyclarrMergedTemplates.customFormatDefinitions || []),
294+
...value.customFormatDefinitions,
295+
];
296+
} else {
297+
logger.warn(`CustomFormatDefinitions in config file must be an array. Ignoring.`);
298+
}
299+
}
300+
301+
const recyclarrProfilesMerged = recyclarrMergedTemplates.quality_profiles.reduce<Map<string, ConfigQualityProfile>>((p, c) => {
302+
const profile = p.get(c.name);
303+
304+
if (profile == null) {
305+
p.set(c.name, c);
306+
} else {
307+
p.set(c.name, {
308+
...profile,
309+
...c,
310+
reset_unmatched_scores: {
311+
enabled: c.reset_unmatched_scores?.enabled ?? profile.reset_unmatched_scores?.enabled ?? true,
312+
except: c.reset_unmatched_scores?.except ?? profile.reset_unmatched_scores?.except,
313+
},
314+
upgrade: {
315+
...profile.upgrade,
316+
...c.upgrade,
317+
},
318+
});
319+
}
320+
321+
return p;
322+
}, new Map());
323+
324+
recyclarrMergedTemplates.quality_profiles = Array.from(recyclarrProfilesMerged.values());
325+
326+
recyclarrMergedTemplates.quality_profiles = filterInvalidQualityProfiles(recyclarrMergedTemplates.quality_profiles);
327+
328+
// merge profiles from recyclarr templates into one
329+
const qualityProfilesMerged = recyclarrMergedTemplates.quality_profiles.reduce((p, c) => {
330+
let existingQp = p.get(c.name);
331+
332+
if (!existingQp) {
333+
p.set(c.name, { ...c });
334+
} else {
335+
existingQp = {
336+
...existingQp,
337+
...c,
338+
// Overwriting qualities array for now
339+
upgrade: { ...existingQp.upgrade, ...c.upgrade },
340+
reset_unmatched_scores: {
341+
...existingQp.reset_unmatched_scores,
342+
...c.reset_unmatched_scores,
343+
enabled: (c.reset_unmatched_scores?.enabled ?? existingQp.reset_unmatched_scores?.enabled) || false,
344+
},
345+
};
346+
p.set(c.name, existingQp);
347+
}
348+
349+
return p;
350+
}, new Map<string, ConfigQualityProfile>());
351+
352+
recyclarrMergedTemplates.quality_profiles = Array.from(qualityProfilesMerged.values());
353+
354+
const validatedConfig = validateConfig(recyclarrMergedTemplates);
355+
logger.debug(`Merged config: '${JSON.stringify(validatedConfig)}'`);
356+
357+
/*
358+
TODO: do we want to load all available local templates or only the included ones in the instance?
359+
Example: we have a local template folder which we can always traverse. So we could load every CF defined there.
360+
But then we could also have in theory conflicted CF IDs if user want to define same CF in different templates.
361+
How to handle overwrite? Maybe also support overriding CFs defined in Trash or something?
362+
*/
363+
// const localTemplateCFDs = Array.from(localTemplateMap.values()).reduce((p, c) => {
364+
// if (c.customFormatDefinitions) {
365+
// p.push(...c.customFormatDefinitions);
366+
// }
367+
// return p;
368+
// }, [] as CustomFormatDefinitions);
369+
370+
return { config: validatedConfig };
371+
};

0 commit comments

Comments
 (0)