Skip to content

Commit

Permalink
fix(rules): Addressed an issue where certain collection-related rules…
Browse files Browse the repository at this point in the history
… exhibited unexpected behavior when media was added to other groups in the same run
  • Loading branch information
jorenn92 committed Jan 26, 2024
1 parent 496401f commit 57f7712
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 52 deletions.
6 changes: 6 additions & 0 deletions server/src/modules/api/lib/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ class CacheManager {
public getAllCaches(): Record<string, Cache> {
return this.availableCaches;
}

public flushAll(): void {
for (const [key, value] of Object.entries(this.getAllCaches())) {
value.flush();
}
}
}

const cacheManager = new CacheManager();
Expand Down
91 changes: 49 additions & 42 deletions server/src/modules/collections/collections.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,21 +98,23 @@ export class CollectionsService {
const itemCount = await queryBuilder.getCount();
let { entities } = await queryBuilder.getRawAndEntities();

entities = await Promise.all(
entities.map(async (el) => {
el.plexData = await this.plexApi.getMetadata(el.plexId.toString());
if (el.plexData?.grandparentRatingKey) {
el.plexData.parentData = await this.plexApi.getMetadata(
el.plexData.grandparentRatingKey.toString(),
);
} else if (el.plexData?.parentRatingKey) {
el.plexData.parentData = await this.plexApi.getMetadata(
el.plexData.parentRatingKey.toString(),
);
}
return el;
}),
);
entities = (
await Promise.all(
entities.map(async (el) => {
el.plexData = await this.plexApi.getMetadata(el.plexId.toString());
if (el.plexData?.grandparentRatingKey) {
el.plexData.parentData = await this.plexApi.getMetadata(
el.plexData.grandparentRatingKey.toString(),
);
} else if (el.plexData?.parentRatingKey) {
el.plexData.parentData = await this.plexApi.getMetadata(
el.plexData.parentRatingKey.toString(),
);
}
return el;
}),
)
).filter((el) => el.plexData !== undefined);

return {
totalSize: itemCount,
Expand Down Expand Up @@ -152,21 +154,23 @@ export class CollectionsService {
const itemCount = await queryBuilder.getCount();
let { entities } = await queryBuilder.getRawAndEntities();

entities = await Promise.all(
entities.map(async (el) => {
el.plexData = await this.plexApi.getMetadata(el.plexId.toString());
if (el.plexData?.grandparentRatingKey) {
el.plexData.parentData = await this.plexApi.getMetadata(
el.plexData.grandparentRatingKey.toString(),
);
} else if (el.plexData?.parentRatingKey) {
el.plexData.parentData = await this.plexApi.getMetadata(
el.plexData.parentRatingKey.toString(),
);
}
return el;
}),
);
entities = (
await Promise.all(
entities.map(async (el) => {
el.plexData = await this.plexApi.getMetadata(el.plexId.toString());
if (el.plexData?.grandparentRatingKey) {
el.plexData.parentData = await this.plexApi.getMetadata(
el.plexData.grandparentRatingKey.toString(),
);
} else if (el.plexData?.parentRatingKey) {
el.plexData.parentData = await this.plexApi.getMetadata(
el.plexData.parentRatingKey.toString(),
);
}
return el;
}),
)
).filter((el) => el.plexData !== undefined);

return {
totalSize: itemCount,
Expand Down Expand Up @@ -832,18 +836,21 @@ export class CollectionsService {
) {
// log record
const plexData = await this.plexApi.getMetadata(plexId.toString()); // fetch data from cache

const subject =
plexData.type === 'episode'
? `${plexData.grandparentTitle} - season ${plexData.parentIndex} - episode ${plexData.index}`
: plexData.type === 'season'
? `${plexData.parentTitle} - season ${plexData.index}`
: plexData.title;
this.addLogRecord(
{ id: collectionId } as Collection,
`${type === 'add' ? 'Added' : type === 'handle' ? 'Successfully handled' : type === 'exclude' ? 'Added a specific exclusion for' : type === 'include' ? 'Removed specific exclusion of' : 'Removed'} "${subject}"`,
ECollectionLogType.MEDIA,
);
// if there's no data.. skip logging

if (plexData) {
const subject =
plexData.type === 'episode'
? `${plexData.grandparentTitle} - season ${plexData.parentIndex} - episode ${plexData.index}`
: plexData.type === 'season'
? `${plexData.parentTitle} - season ${plexData.index}`
: plexData.title;
this.addLogRecord(
{ id: collectionId } as Collection,
`${type === 'add' ? 'Added' : type === 'handle' ? 'Successfully handled' : type === 'exclude' ? 'Added a specific exclusion for' : type === 'include' ? 'Removed specific exclusion of' : 'Removed'} "${subject}"`,
ECollectionLogType.MEDIA,
);
}
}

private async removeChildFromCollection(
Expand Down
12 changes: 7 additions & 5 deletions server/src/modules/rules/constants/constants.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ export class RuleConstanstService {
}

public getValueHumanName(location: [number, number]) {
return `${this.ruleConstants.applications.find(
(el) => el.id === location[0],
)?.name} - ${this.ruleConstants.applications
.find((el) => el.id === location[0])
?.props.find((el) => el.id === location[1])?.humanName}`;
return `${
this.ruleConstants.applications.find((el) => el.id === location[0])?.name
} - ${
this.ruleConstants.applications
.find((el) => el.id === location[0])
?.props.find((el) => el.id === location[1])?.humanName
}`;
}

public getValueFromIdentifier(identifier: string): [number, number] {
Expand Down
3 changes: 3 additions & 0 deletions server/src/modules/rules/constants/rules.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export interface Property {
type: RuleType;
mediaType: MediaType;
humanName: string;
cacheReset?: boolean; // for properties that require a cache reset between group executions
}

export interface ApplicationProperties {
Expand Down Expand Up @@ -163,6 +164,7 @@ export class RuleConstants {
humanName: 'Present in amount of other collections',
mediaType: MediaType.BOTH,
type: RuleType.NUMBER,
cacheReset: true,
} as Property,
{
id: 7,
Expand Down Expand Up @@ -264,6 +266,7 @@ export class RuleConstants {
humanName: '[list] Collections media is present in (titles)',
mediaType: MediaType.BOTH,
type: RuleType.TEXT,
cacheReset: true,
} as Property,
{
id: 20,
Expand Down
55 changes: 55 additions & 0 deletions server/src/modules/rules/rules.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { RuleYamlService } from './helpers/yaml.service';
import { RuleComparatorService } from './helpers/rule.comparator.service';
import { PlexLibraryItem } from '../api/plex-api/interfaces/library.interfaces';
import { ECollectionLogType } from 'src/modules/collections/entities/collection_log.entities';
import cacheManager from 'src/modules/api/lib/cache';

export interface ReturnStatus {
code: 0 | 1;
Expand Down Expand Up @@ -951,4 +952,58 @@ export class RulesService {
}
return { code: 0, result: 'Invalid input' };
}

/**
* Reset the Plex cache if any rule in the rule group requires it.
*
* @param {RulesDto} rulegroup - The rule group to check for cache reset requirement.
* @return {Promise<boolean>} Whether the Plex cache was reset.
*/
public async resetPlexCacheIfgroupUsesRuleThatRequiresIt(
rulegroup: RulesDto,
): Promise<boolean> {
try {
let result = false;
const constant = await this.getRuleConstants();

// for all rules in group
for (const rule of rulegroup.rules) {
const parsedRule = JSON.parse((rule as RuleDbDto).ruleJson) as RuleDto;

//test first value
const first =
constant.applications[parsedRule.firstVal[0]].props[
parsedRule.firstVal[1]
];

result = first?.cacheReset ? true : result;

// test second value
const second = parsedRule.lastVal
? constant.applications[parsedRule.lastVal[0]].props[
parsedRule.lastVal[1]
]
: undefined;

result = second?.cacheReset ? true : result;
}

// if any rule requires a cache reset
if (result) {
cacheManager.getCache('plextv').flush();
cacheManager.getCache('plexguid').flush();
this.logger.log(
`Flushed Plex cache because a rule in the group required it`,
);
}

return result;
} catch (e) {
this.logger.warn(
`Couldn't determine if rulegroup with id ${rulegroup.id} requires a cache reset`,
);
this.logger.debug(e);
return false;
}
}
}
13 changes: 8 additions & 5 deletions server/src/modules/rules/tasks/rule-executor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class RuleExecutorService implements OnApplicationBootstrap {
this.onApplicationBootstrap();
}, 10000);
} else {
this.logger.error(`Creation of job Rule Handler failed.`);
this.logger.error(`Creation of job Rule Handler failed`);
}
}
}
Expand All @@ -77,13 +77,11 @@ export class RuleExecutorService implements OnApplicationBootstrap {
}

public async executeAllRules() {
this.logger.log('Starting Execution of all active rules.');
this.logger.log('Starting Execution of all active rules');
const appStatus = await this.settings.testConnections();

// reset API caches, make sure latest data is used
for (const [key, value] of Object.entries(cacheManager.getAllCaches())) {
value.flush();
}
cacheManager.flushAll();

if (appStatus) {
const ruleGroups = await this.getAllActiveRuleGroups();
Expand All @@ -93,6 +91,11 @@ export class RuleExecutorService implements OnApplicationBootstrap {
this.logger.log(`Executing rules for '${rulegroup.name}'`);
this.startTime = new Date();

// reset Plex cache if group uses a rule that requires it (collection rules for example)
this.rulesService.resetPlexCacheIfgroupUsesRuleThatRequiresIt(
rulegroup,
);

// prepare
this.workerData = [];
this.resultData = [];
Expand Down

0 comments on commit 57f7712

Please sign in to comment.