Skip to content

Commit

Permalink
feat: add configuration options for media management tab
Browse files Browse the repository at this point in the history
  • Loading branch information
BlackDark committed Dec 17, 2024
1 parent 4d613e5 commit bc7a03a
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 1 deletion.
16 changes: 16 additions & 0 deletions src/clients/radarr-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,22 @@ export class RadarrClient implements IArrClient<QualityProfileResource, QualityD
return this.api.v3CustomformatDelete(+id);
}

async getNaming() {
return await this.api.v3ConfigNamingList();
}

async updateNaming(id: string, data: any) {
return await this.api.v3ConfigNamingUpdate(id, data);
}

async getMediamanagement() {
return await this.api.v3ConfigMediamanagementList();
}

async updateMediamanagement(id: string, data: any) {
return await this.api.v3ConfigMediamanagementUpdate(id, data);
}

// System/Health Check
getSystemStatus() {
return this.api.v3SystemStatusList();
Expand Down
16 changes: 16 additions & 0 deletions src/clients/readarr-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,22 @@ export class ReadarrClient
return this.api.v1MetadataprofileUpdate(id.toString(), profile);
}

async getNaming() {
return await this.api.v1ConfigNamingList();
}

async updateNaming(id: string, data: any) {
return await this.api.v1ConfigNamingUpdate(id, data);
}

async getMediamanagement() {
return await this.api.v1ConfigMediamanagementList();
}

async updateMediamanagement(id: string, data: any) {
return await this.api.v1ConfigMediamanagementUpdate(id, data);
}

// System/Health Check
getSystemStatus() {
return this.api.v1SystemStatusList();
Expand Down
16 changes: 16 additions & 0 deletions src/clients/sonarr-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,22 @@ export class SonarrClient implements IArrClient<QualityProfileResource, QualityD
return this.api.v3CustomformatDelete(+id);
}

async getNaming() {
return await this.api.v3ConfigNamingList();
}

async updateNaming(id: string, data: any) {
return await this.api.v3ConfigNamingUpdate(id, data);
}

async getMediamanagement() {
return await this.api.v3ConfigMediamanagementList();
}

async updateMediamanagement(id: string, data: any) {
return await this.api.v3ConfigMediamanagementUpdate(id, data);
}

// System/Health Check
getSystemStatus() {
return this.api.v3SystemStatusList();
Expand Down
22 changes: 22 additions & 0 deletions src/clients/unified-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ export interface IArrClient<
updateCustomFormat(id: string, format: CF): Promise<CF>;
deleteCustomFormat(id: string): Promise<void>;

getNaming(): Promise<any>;
updateNaming(id: string, data: any): Promise<any>;

getMediamanagement(): Promise<any>;
updateMediamanagement(id: string, data: any): Promise<any>;

getLanguages(): Promise<L[]>;

// System/Health Check
Expand Down Expand Up @@ -187,6 +193,22 @@ export class UnifiedClient implements IArrClient {
return await this.api.deleteCustomFormat(id);
}

async getNaming() {
return await this.api.getNaming();
}

async updateNaming(id: string, data: any) {
return await this.api.updateNaming(id, data);
}

async getMediamanagement() {
return await this.api.getMediamanagement();
}

async updateMediamanagement(id: string, data: any) {
return await this.api.updateMediamanagement(id, data);
}

async getSystemStatus() {
return await this.api.getSystemStatus();
}
Expand Down
16 changes: 16 additions & 0 deletions src/clients/whisparr-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,22 @@ export class WhisparrClient
return this.api.v3CustomformatDelete(+id);
}

async getNaming() {
return await this.api.v3ConfigNamingList();
}

async updateNaming(id: string, data: any) {
return await this.api.v3ConfigNamingUpdate(id, data);
}

async getMediamanagement() {
return await this.api.v3ConfigMediamanagementList();
}

async updateMediamanagement(id: string, data: any) {
return await this.api.v3ConfigMediamanagementUpdate(id, data);
}

// System/Health Check
getSystemStatus() {
return this.api.v3SystemStatusList();
Expand Down
41 changes: 41 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getConfig, validateConfig } from "./config";
import { calculateCFsToManage, loadCustomFormatDefinitions, loadServerCustomFormats, manageCf } from "./custom-formats";
import { loadLocalRecyclarrTemplate } from "./local-importer";
import { logHeading, logger } from "./logger";
import { calculateMediamanagementDiff, calculateNamingDiff } from "./media-management";
import { calculateQualityDefinitionDiff, loadQualityDefinitionFromServer } from "./quality-definitions";
import { calculateQualityProfilesDiff, filterInvalidQualityProfiles, loadQualityProfilesFromServer } from "./quality-profiles";
import { cloneRecyclarrTemplateRepo, loadRecyclarrTemplates } from "./recyclarr-importer";
Expand Down Expand Up @@ -99,6 +100,14 @@ const mergeConfigsAndTemplates = async (
}
}

if (template.media_management) {
recylarrMergedTemplates.media_management = { ...recylarrMergedTemplates.media_management, ...template.media_management };
}

if (template.media_naming) {
recylarrMergedTemplates.media_naming = { ...recylarrMergedTemplates.media_naming, ...template.media_naming };
}

// TODO Ignore recursive include for now
if (template.include) {
logger.warn(`Recursive includes not supported at the moment. Ignoring.`);
Expand Down Expand Up @@ -127,6 +136,14 @@ const mergeConfigsAndTemplates = async (
recylarrMergedTemplates.quality_profiles.push(...value.quality_profiles);
}

if (value.media_management) {
recylarrMergedTemplates.media_management = { ...recylarrMergedTemplates.media_management, ...value.media_management };
}

if (value.media_naming) {
recylarrMergedTemplates.media_naming = { ...recylarrMergedTemplates.media_naming, ...value.media_naming };
}

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

Expand Down Expand Up @@ -270,6 +287,30 @@ const pipeline = async (value: InputConfigArrInstance, arrType: ArrType) => {
}
}

const namingDiff = await calculateNamingDiff(config.media_naming);

if (namingDiff) {
if (getEnvs().DRY_RUN) {
logger.info("DryRun: Would update MediaNaming.");
} else {
// TODO this will need a radarr/sonarr separation for sure to have good and correct typings
await api.updateNaming(namingDiff.updatedData.id! + "", namingDiff.updatedData as any); // Ignore types
logger.info(`Updated MediaNaming`);
}
}

const managementDiff = await calculateMediamanagementDiff(config.media_management);

if (managementDiff) {
if (getEnvs().DRY_RUN) {
logger.info("DryRun: Would update MediaManagement.");
} else {
// TODO this will need a radarr/sonarr separation for sure to have good and correct typings
await api.updateMediamanagement(managementDiff.updatedData.id! + "", managementDiff.updatedData as any); // Ignore types
logger.info(`Updated MediaManagement`);
}
}

// calculate diff from server <-> what we want to be there
const serverQP = await loadQualityProfilesFromServer();
const { changedQPs, create, noChanges } = await calculateQualityProfilesDiff(mergedCFs, config, serverQP, serverQD, serverCFs);
Expand Down
70 changes: 70 additions & 0 deletions src/media-management.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { getUnifiedClient } from "./clients/unified-client";
import { logger } from "./logger";
import { MediaManagementType, MediaNamingType } from "./types/config.types";
import { compareMediamanagement, compareNaming } from "./util";

const loadNamingFromServer = async () => {
const api = getUnifiedClient();
const result = await api.getNaming();
return result.data;
};

const loadMediamanagementConfigFromServer = async () => {
const api = getUnifiedClient();
const result = await api.getMediamanagement();
return result.data;
};

export const calculateNamingDiff = async (mediaNaming?: MediaNamingType) => {
if (mediaNaming == null) {
logger.debug(`Config 'media_naming' not specified. Ignoring.`);
return null;
}

const serverData = await loadNamingFromServer();

const { changes, equal } = compareNaming(serverData, mediaNaming);

if (equal) {
logger.debug(`Media naming settings are in sync`);
return null;
}

logger.info(`Found ${changes.length} differences for media naming.`);
logger.debug(changes, `Found following changes for media naming`);

return {
changes,
updatedData: {
...serverData,
...mediaNaming,
},
};
};

export const calculateMediamanagementDiff = async (mediaManagement?: MediaManagementType) => {
if (mediaManagement == null) {
logger.debug(`Config 'media_management' not specified. Ignoring.`);
return null;
}

const serverData = await loadMediamanagementConfigFromServer();

const { changes, equal } = compareMediamanagement(serverData, mediaManagement);

if (equal) {
logger.debug(`Media management settings are in sync`);
return null;
}

logger.info(`Found ${changes.length} differences for media management.`);
logger.debug(changes, `Found following changes for media management`);

return {
changes,
updatedData: {
...serverData,
...mediaManagement,
},
};
};
11 changes: 10 additions & 1 deletion src/types/common.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,16 @@ export type CFProcessing = {
};

export type MappedTemplates = Partial<
Pick<InputConfigArrInstance, "quality_definition" | "custom_formats" | "include" | "quality_profiles" | "customFormatDefinitions">
Pick<
InputConfigArrInstance,
| "quality_definition"
| "custom_formats"
| "include"
| "quality_profiles"
| "customFormatDefinitions"
| "media_management"
| "media_naming"
>
>;

export type MappedMergedTemplates = MappedTemplates & Required<Pick<MappedTemplates, "custom_formats" | "quality_profiles">>;
Expand Down
39 changes: 39 additions & 0 deletions src/types/config.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,47 @@ export type InputConfigArrInstance = {
custom_formats?: InputConfigCustomFormat[];
// TODO this is not correct. The profile can be added partly -> InputConfigQualityProfile
quality_profiles: ConfigQualityProfile[];
media_management?: MediaManagementType;
media_naming?: MediaNamingType;
} & Pick<InputConfigSchema, "customFormatDefinitions">;

// TODO configure all configs in recyclarr?
export type MediaManagementType = {
autoUnmonitorPreviouslyDownloadedEpisodes?: boolean;
recycleBin?: string;
recycleBinCleanupDays?: number;
downloadPropersAndRepacks?: string;
createEmptySeriesFolders?: boolean;
deleteEmptyFolders?: boolean;
fileDate?: string;
rescanAfterRefresh?: string;
setPermissionsLinux?: boolean;
chmodFolder?: string;
chownGroup?: string;
episodeTitleRequired?: string;
skipFreeSpaceCheckWhenImporting?: boolean;
minimumFreeSpaceWhenImporting?: number;
copyUsingHardlinks?: boolean;
useScriptImport?: boolean;
scriptImportPath?: string;
importExtraFiles?: boolean;
extraFileExtensions?: string;
enableMediaInfo?: boolean;
};

export type MediaNamingType = {
renameEpisodes?: boolean;
replaceIllegalCharacters?: boolean;
colonReplacementFormat?: number;
multiEpisodeStyle?: number;
standardEpisodeFormat?: string;
dailyEpisodeFormat?: string;
animeEpisodeFormat?: string;
seriesFolderFormat?: string;
seasonFolderFormat?: string;
specialsFolderFormat?: string;
};

export type InputConfigQualityProfile = {
name: string;
reset_unmatched_scores?: {
Expand Down
8 changes: 8 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ export function compareCustomFormats(
return compareObjectsCarr(serverObject, localObject);
}

export function compareNaming(serverObject: any, localObject: any): ReturnType<typeof compareObjectsCarr> {
return compareObjectsCarr(serverObject, localObject);
}

export function compareMediamanagement(serverObject: any, localObject: any): ReturnType<typeof compareObjectsCarr> {
return compareObjectsCarr(serverObject, localObject);
}

function compareObjectsCarr(serverObject: any, localObject: any, parent?: string): { equal: boolean; changes: string[] } {
const changes: string[] = [];

Expand Down

0 comments on commit bc7a03a

Please sign in to comment.