Skip to content

Commit

Permalink
Merge pull request #73 from raydak-labs/fix/69
Browse files Browse the repository at this point in the history
fix: correctly handle assign_scores_to config
  • Loading branch information
BlackDark authored Sep 30, 2024
2 parents 1c6c7a4 + 0c4066d commit ff064a1
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 80 deletions.
8 changes: 4 additions & 4 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ import {
} from "./src/quality-profiles";
import { cloneRecyclarrTemplateRepo, loadRecyclarrTemplates } from "./src/recyclarr-importer";
import { cloneTrashRepo, loadQualityDefinitionSonarrFromTrash, loadSonarrTrashCFs } from "./src/trash-guide";
import { ArrType, MappedMergedTemplates, TrashQualityDefintion, YamlConfigInstance, YamlConfigQualityProfile } from "./src/types";
import { ArrType, ConfigArrInstance, ConfigQualityProfile, MappedMergedTemplates, TrashQualityDefintion } from "./src/types";
import { DEBUG_CREATE_FILES, IS_DRY_RUN } from "./src/util";

const pipeline = async (value: YamlConfigInstance, arrType: ArrType) => {
const pipeline = async (value: ConfigArrInstance, arrType: ArrType) => {
const api = getArrApi();
const recyclarrTemplateMap = loadRecyclarrTemplates(arrType);

Expand Down Expand Up @@ -75,7 +75,7 @@ const pipeline = async (value: YamlConfigInstance, arrType: ArrType) => {
recylarrMergedTemplates.quality_profiles.push(...value.quality_profiles);
}

const recyclarrProfilesMerged = recylarrMergedTemplates.quality_profiles.reduce<Map<string, YamlConfigQualityProfile>>((p, c) => {
const recyclarrProfilesMerged = recylarrMergedTemplates.quality_profiles.reduce<Map<string, ConfigQualityProfile>>((p, c) => {
const profile = p.get(c.name);

if (profile == null) {
Expand Down Expand Up @@ -187,7 +187,7 @@ const pipeline = async (value: YamlConfigInstance, arrType: ArrType) => {
}

return p;
}, new Map<string, YamlConfigQualityProfile>());
}, new Map<string, ConfigQualityProfile>());

// calculate diff from server <-> what we want to be there

Expand Down
54 changes: 54 additions & 0 deletions src/config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe, expect, test } from "vitest";
import yaml from "yaml";
import { transformConfig } from "./config";
import { InputConfigSchema } from "./types";
import { cloneWithJSON } from "./util";

describe("transformConfig", async () => {
const config: InputConfigSchema = yaml.parse(`
sonarr:
instance1:
base_url: http://sonarr:8989
api_key: test
quality_profiles:
- name: Remux-2160p - Anime
quality_sort: top
score_set: anime-sonarr
upgrade:
allowed: true
until_quality: SINGLE_STAGE
until_score: 5000000
qualities:
- name: SINGLE_STAGE
enabled: true
qualities:
- Bluray-2160p
- SDTV
- name: Unknown
enabled: false
custom_formats:
- trash_ids:
- custom-german-dl
- custom-german-dl-2
assign_scores_to:
- name: Remux-2160p - Anime
radarr: {}
`);

test("should transform without error", async () => {
const transformed = transformConfig(config);
expect(transformed).not.toBeNull();
});

test("should transform without error - quality_profiles instead of assign_scores_to", async () => {
const cloned = cloneWithJSON(config);
const instance = cloned.sonarr["instance1"];
const { assign_scores_to, quality_profiles, ...rest } = instance.custom_formats[0];

instance.custom_formats[0] = { ...rest, quality_profiles: assign_scores_to ?? quality_profiles };

const transformed = transformConfig(config);
expect(transformed).not.toBeNull();
});
});
52 changes: 47 additions & 5 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { existsSync, readFileSync } from "fs";
import yaml from "yaml";
import { logger } from "./logger";
import { YamlConfig } from "./types";
import { ConfigArrInstance, ConfigCustomFormat, ConfigSchema, InputConfigArrInstance, InputConfigSchema } from "./types";
import { ROOT_PATH } from "./util";

const CONFIG_LOCATION = process.env.CONFIG_LOCATION ?? `${ROOT_PATH}/config.yml`;
const SECRETS_LOCATION = process.env.SECRETS_LOCATION ?? `${ROOT_PATH}/secrets.yml`;
export const LOG_LEVEL = process.env.LOG_LEVEL ?? `info`;

let config: YamlConfig;
let config: ConfigSchema;
let secrets: any;

const secretsTag = {
Expand Down Expand Up @@ -36,7 +35,7 @@ const envTag = {
};

// TODO some schema validation. For now only check if something can be imported
export const getConfig = (): YamlConfig => {
export const getConfig = (): ConfigSchema => {
if (config) {
return config;
}
Expand All @@ -47,7 +46,11 @@ export const getConfig = (): YamlConfig => {
}

const file = readFileSync(CONFIG_LOCATION, "utf8");
config = yaml.parse(file, { customTags: [secretsTag, envTag] }) as YamlConfig;

const inputConfig = yaml.parse(file, { customTags: [secretsTag, envTag] }) as InputConfigSchema;

config = transformConfig(inputConfig);

return config;
};

Expand All @@ -65,3 +68,42 @@ export const getSecrets = () => {
config = yaml.parse(file);
return config;
};

// 2024-09-30: Recyclarr assign_scores_to adjustments
export const transformConfig = (input: InputConfigSchema): ConfigSchema => {
const mappedCustomFormats = (arrInput: Record<string, InputConfigArrInstance>): Record<string, ConfigArrInstance> => {
return Object.entries(arrInput).reduce(
(p, [key, value]) => {
const mappedCustomFormats = value.custom_formats.map<ConfigCustomFormat>((cf) => {
const { assign_scores_to, quality_profiles, ...rest } = cf;

if (quality_profiles) {
logger.warn(
`Deprecated: (Instance '${key}') For custom_formats please rename 'quality_profiles' to 'assign_scores_to'. See recyclarr v7.2.0`,
);
}

const mapped_assign_scores = quality_profiles ?? assign_scores_to;

if (!mapped_assign_scores) {
throw new Error(
`Mapping failed for profile ${key} -> custom format mapping (assign_scores_to or quality_profiles is missing. Use assign_scores_to)`,
);
}

return { ...rest, assign_scores_to: mapped_assign_scores };
});

p[key] = { ...value, custom_formats: mappedCustomFormats };
return p;
},
{} as Record<string, ConfigArrInstance>,
);
};

return {
...input,
radarr: mappedCustomFormats(input.radarr),
sonarr: mappedCustomFormats(input.sonarr),
};
};
4 changes: 2 additions & 2 deletions src/custom-formats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CustomFormatResource } from "./__generated__/generated-sonarr-api";
import { getArrApi } from "./api";
import { getConfig } from "./config";
import { logger } from "./logger";
import { CFProcessing, ConfigarrCF, DynamicImportType, TrashCF, YamlInput } from "./types";
import { CFProcessing, ConfigCustomFormatList, ConfigarrCF, DynamicImportType, TrashCF } from "./types";
import { IS_DRY_RUN, IS_LOCAL_SAMPLE_MODE, compareObjectsCarr, mapImportCfToRequestCf, toCarrCF } from "./util";

export const deleteAllCustomFormats = async () => {
Expand Down Expand Up @@ -169,7 +169,7 @@ export const loadCFFromConfig = (): CFProcessing | null => {
};
};

export const calculateCFsToManage = (yaml: YamlInput) => {
export const calculateCFsToManage = (yaml: ConfigCustomFormatList) => {
const cfTrashToManage: Set<string> = new Set();

yaml.custom_formats.map((cf) => {
Expand Down
3 changes: 2 additions & 1 deletion src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pino from "pino";
import { LOG_LEVEL } from "./config";

export const LOG_LEVEL = process.env.LOG_LEVEL ?? `info`;

export const logger = pino({
level: LOG_LEVEL,
Expand Down
18 changes: 9 additions & 9 deletions src/quality-profiles.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { describe, expect, test } from "vitest";
import { doAllQualitiesExist, isOrderOfQualitiesEqual } from "./quality-profiles";
import { YamlConfigQualityProfileItems } from "./types";
import { ConfigQualityProfileItem } from "./types";

describe("QualityProfiles", async () => {
test("doAllQualitiesExist - all exist", async ({}) => {
const fromConfig: YamlConfigQualityProfileItems[] = [
const fromConfig: ConfigQualityProfileItem[] = [
{ name: "WEB 1080p", qualities: ["WEBDL-1080p", "WEBRip-1080p"] },
{ name: "HDTV-1080p" },
{ name: "Bluray-1080p" },
{ name: "Remux-1080p" },
{ name: "WEB 720p", qualities: ["WEBDL-720p", "WEBRip-720p"] },
{ name: "HDTV-720p" },
];
const fromServer: YamlConfigQualityProfileItems[] = [
const fromServer: ConfigQualityProfileItem[] = [
{ name: "Bluray-1080p", qualities: [] },
{ name: "HDTV-720p", qualities: [] },
{ name: "WEB 720p", qualities: ["WEBDL-720p", "WEBRip-720p"] },
Expand All @@ -26,15 +26,15 @@ describe("QualityProfiles", async () => {
});

test("doAllQualitiesExist - missing", async ({}) => {
const fromConfig: YamlConfigQualityProfileItems[] = [
const fromConfig: ConfigQualityProfileItem[] = [
{ name: "WEB 1080p", qualities: ["WEBDL-1080p", "WEBRip-1080p"] },
{ name: "HDTV-1080p" },
{ name: "Bluray-1080p" },
{ name: "Remux-1080p" },
{ name: "WEB 720p", qualities: ["WEBDL-720p", "WEBRip-720p"] },
{ name: "HDTV-720p" },
];
const fromServer: YamlConfigQualityProfileItems[] = [
const fromServer: ConfigQualityProfileItem[] = [
{ name: "Bluray-1080p", qualities: [] },
{ name: "WEB 720p", qualities: ["WEBDL-720p", "WEBRip-720p"] },
{ name: "HDTV-1080p", qualities: [] },
Expand All @@ -47,15 +47,15 @@ describe("QualityProfiles", async () => {
});

test("isOrderOfQualitiesEqual - should match", async ({}) => {
const fromConfig: YamlConfigQualityProfileItems[] = [
const fromConfig: ConfigQualityProfileItem[] = [
{ name: "WEB 1080p", qualities: ["WEBDL-1080p", "WEBRip-1080p"] },
{ name: "HDTV-1080p" },
{ name: "Bluray-1080p" },
{ name: "Remux-1080p" },
{ name: "WEB 720p", qualities: ["WEBDL-720p", "WEBRip-720p"] },
{ name: "HDTV-720p" },
];
const fromServer: YamlConfigQualityProfileItems[] = [
const fromServer: ConfigQualityProfileItem[] = [
{ name: "WEB 1080p", qualities: ["WEBDL-1080p", "WEBRip-1080p"] },
{ name: "HDTV-1080p" },
{ name: "Bluray-1080p" },
Expand All @@ -69,15 +69,15 @@ describe("QualityProfiles", async () => {
});

test("isOrderOfQualitiesEqual - different order", async ({}) => {
const fromConfig: YamlConfigQualityProfileItems[] = [
const fromConfig: ConfigQualityProfileItem[] = [
{ name: "WEB 1080p", qualities: ["WEBDL-1080p", "WEBRip-1080p"] },
{ name: "HDTV-1080p" },
{ name: "Bluray-1080p" },
{ name: "Remux-1080p" },
{ name: "WEB 720p", qualities: ["WEBDL-720p", "WEBRip-720p"] },
{ name: "HDTV-720p" },
];
const fromServer: YamlConfigQualityProfileItems[] = [
const fromServer: ConfigQualityProfileItem[] = [
{ name: "Bluray-1080p", qualities: [] },
{ name: "HDTV-720p", qualities: [] },
{ name: "WEB 720p", qualities: ["WEBDL-720p", "WEBRip-720p"] },
Expand Down
28 changes: 15 additions & 13 deletions src/quality-profiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,25 @@ import { getArrApi } from "./api";
import { loadServerCustomFormats } from "./custom-formats";
import { logger } from "./logger";
import { loadQualityDefinitionFromServer } from "./quality-definitions";
import { CFProcessing, RecyclarrMergedTemplates, YamlConfigQualityProfile, YamlConfigQualityProfileItems, YamlList } from "./types";
import { CFProcessing, ConfigCustomFormat, ConfigQualityProfile, ConfigQualityProfileItem, RecyclarrMergedTemplates } from "./types";
import { IS_LOCAL_SAMPLE_MODE, cloneWithJSON, notEmpty } from "./util";

export const mapQualityProfiles = ({ carrIdMapping }: CFProcessing, customFormats: YamlList[], config: RecyclarrMergedTemplates) => {
export const mapQualityProfiles = (
{ carrIdMapping }: CFProcessing,
customFormats: ConfigCustomFormat[],
config: RecyclarrMergedTemplates,
) => {
// QualityProfile -> (CF Name -> Scoring)
const profileScores = new Map<string, Map<string, ProfileFormatItemResource>>();

const defaultScoringMap = new Map(config.quality_profiles.map((obj) => [obj.name, obj]));

for (const { trash_ids, quality_profiles } of customFormats) {
for (const { trash_ids, assign_scores_to } of customFormats) {
if (!trash_ids) {
continue;
}

//logger.info(customFormats);
//logger.info(quality_profiles);
for (const profile of quality_profiles) {
for (const profile of assign_scores_to) {
for (const trashId of trash_ids) {
const carr = carrIdMapping.get(trashId);

Expand Down Expand Up @@ -81,7 +83,7 @@ export const loadQualityProfilesFromServer = async (): Promise<QualityProfileRes
};

// TODO should we use clones or not?
const mapQualities = (qd_source: QualityDefinitionResource[], value_source: YamlConfigQualityProfile) => {
const mapQualities = (qd_source: QualityDefinitionResource[], value_source: ConfigQualityProfile) => {
const qd = cloneWithJSON(qd_source);
const value = cloneWithJSON(value_source);

Expand Down Expand Up @@ -157,7 +159,7 @@ const mapQualities = (qd_source: QualityDefinitionResource[], value_source: Yaml
}
};

export const doAllQualitiesExist = (obj1_source: YamlConfigQualityProfileItems[], obj2_source: YamlConfigQualityProfileItems[]) => {
export const doAllQualitiesExist = (obj1_source: ConfigQualityProfileItem[], obj2_source: ConfigQualityProfileItem[]) => {
const obj1 = cloneWithJSON(obj1_source);
const obj2 = cloneWithJSON(obj2_source);

Expand Down Expand Up @@ -201,7 +203,7 @@ export const doAllQualitiesExist = (obj1_source: YamlConfigQualityProfileItems[]
return true;
};

export const isOrderOfQualitiesEqual = (obj1: YamlConfigQualityProfileItems[], obj2: YamlConfigQualityProfileItems[]) => {
export const isOrderOfQualitiesEqual = (obj1: ConfigQualityProfileItem[], obj2: ConfigQualityProfileItem[]) => {
if (obj1.length !== obj2.length) {
return false;
}
Expand All @@ -213,7 +215,7 @@ export const isOrderOfQualitiesEqual = (obj1: YamlConfigQualityProfileItems[], o

export const calculateQualityProfilesDiff = async (
cfManaged: CFProcessing,
qpMerged: Map<string, YamlConfigQualityProfile>,
qpMerged: Map<string, ConfigQualityProfile>,
scoring: Map<string, Map<string, ProfileFormatItemResource>>,
serverQP: QualityProfileResource[],
): Promise<{
Expand Down Expand Up @@ -303,8 +305,8 @@ export const calculateQualityProfilesDiff = async (
const valueQualityMap = new Map(value.qualities.map((obj) => [obj.name, obj]));

// TODO need to better validate if this quality transforming works as expected in different cases
const serverQualitiesMapped: YamlConfigQualityProfileItems[] = (serverMatch.items || [])
.map((obj): YamlConfigQualityProfileItems | null => {
const serverQualitiesMapped: ConfigQualityProfileItem[] = (serverMatch.items || [])
.map((obj): ConfigQualityProfileItem | null => {
let qualityName: string;

if (obj.id) {
Expand Down Expand Up @@ -474,7 +476,7 @@ export const calculateQualityProfilesDiff = async (
return { create: createQPs, changedQPs: changedQPs, noChanges: noChangedQPs };
};

export const filterInvalidQualityProfiles = (profiles: YamlConfigQualityProfile[]): YamlConfigQualityProfile[] => {
export const filterInvalidQualityProfiles = (profiles: ConfigQualityProfile[]): ConfigQualityProfile[] => {
return profiles.filter((p) => {
if (p.name == null) {
logger.info(p, `QualityProfile filtered because no name provided`);
Expand Down
8 changes: 7 additions & 1 deletion src/recyclarr-importer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,13 @@ export const loadRecyclarrTemplates = (arrType: ArrType): Map<string, MappedTemp
if (cf.assign_scores_to == null && cf.quality_profiles == null) {
logger.warn(`Recyclarr Template "${k}" does not provide correct profile for custom format. Ignoring.`);
}
return { ...cf, quality_profiles: cf.assign_scores_to ?? cf.quality_profiles ?? [] };

if (cf.quality_profiles) {
logger.warn(
`Deprecated: (Recyclarr Template '${k}') For custom_formats please rename 'quality_profiles' to 'assign_scores_to'. See recyclarr v7.2.0`,
);
}
return { ...cf, assign_scores_to: cf.assign_scores_to ?? cf.quality_profiles ?? [] };
});

return [
Expand Down
Loading

0 comments on commit ff064a1

Please sign in to comment.