diff --git a/server/src/repositories/metadata.repository.ts b/server/src/repositories/metadata.repository.ts index 3f297d709b..5df37a5ea7 100644 --- a/server/src/repositories/metadata.repository.ts +++ b/server/src/repositories/metadata.repository.ts @@ -85,6 +85,10 @@ export class MetadataRepository { this.logger.setContext(MetadataRepository.name); } + setMaxConcurrency(concurrency: number) { + this.exiftool.batchCluster.setMaxProcs(concurrency); + } + async teardown() { await this.exiftool.end(); } diff --git a/server/src/services/metadata.service.spec.ts b/server/src/services/metadata.service.spec.ts index f5b10aa379..74f0231f5d 100644 --- a/server/src/services/metadata.service.spec.ts +++ b/server/src/services/metadata.service.spec.ts @@ -2,6 +2,7 @@ import { BinaryField, ExifDateTime } from 'exiftool-vendored'; import { randomBytes } from 'node:crypto'; import { Stats } from 'node:fs'; import { constants } from 'node:fs/promises'; +import { defaults } from 'src/config'; import { AssetEntity } from 'src/entities/asset.entity'; import { ExifEntity } from 'src/entities/exif.entity'; import { AssetType, ExifOrientation, ImmichWorker, JobName, JobStatus, SourceType } from 'src/enum'; @@ -54,6 +55,27 @@ describe(MetadataService.name, () => { }); }); + describe('onConfigInit', () => { + it('should update metadata processing concurrency', () => { + sut.onConfigInit({ newConfig: defaults }); + + expect(mocks.metadata.setMaxConcurrency).toHaveBeenCalledWith(defaults.job.metadataExtraction.concurrency); + expect(mocks.metadata.setMaxConcurrency).toHaveBeenCalledTimes(1); + }); + }); + + describe('onConfigUpdate', () => { + it('should update metadata processing concurrency', () => { + const newConfig = structuredClone(defaults); + newConfig.job.metadataExtraction.concurrency = 10; + + sut.onConfigUpdate({ oldConfig: defaults, newConfig }); + + expect(mocks.metadata.setMaxConcurrency).toHaveBeenCalledWith(newConfig.job.metadataExtraction.concurrency); + expect(mocks.metadata.setMaxConcurrency).toHaveBeenCalledTimes(1); + }); + }); + describe('handleLivePhotoLinking', () => { it('should handle an asset that could not be found', async () => { await expect(sut.handleLivePhotoLinking({ id: assetStub.image.id })).resolves.toBe(JobStatus.FAILED); diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index 78ea8089e6..592e0b836d 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -89,6 +89,16 @@ export class MetadataService extends BaseService { await this.metadataRepository.teardown(); } + @OnEvent({ name: 'config.init', workers: [ImmichWorker.MICROSERVICES] }) + onConfigInit({ newConfig }: ArgOf<'config.init'>) { + this.metadataRepository.setMaxConcurrency(newConfig.job.metadataExtraction.concurrency); + } + + @OnEvent({ name: 'config.update', workers: [ImmichWorker.MICROSERVICES], server: true }) + onConfigUpdate({ newConfig }: ArgOf<'config.update'>) { + this.metadataRepository.setMaxConcurrency(newConfig.job.metadataExtraction.concurrency); + } + private async init() { this.logger.log('Initializing metadata service'); diff --git a/server/test/repositories/metadata.repository.mock.ts b/server/test/repositories/metadata.repository.mock.ts index 47a0471b22..854f13b841 100644 --- a/server/test/repositories/metadata.repository.mock.ts +++ b/server/test/repositories/metadata.repository.mock.ts @@ -4,6 +4,7 @@ import { Mocked, vitest } from 'vitest'; export const newMetadataRepositoryMock = (): Mocked> => { return { + setMaxConcurrency: vitest.fn(), teardown: vitest.fn(), readTags: vitest.fn(), writeTags: vitest.fn(),