From 7c102be619dcfa35f8babc36e6d15f071e1b1ddc Mon Sep 17 00:00:00 2001 From: "immich-push-o-matic[bot]" <179150890+immich-push-o-matic[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 09:44:01 +0000 Subject: [PATCH] fix(server): dont queue unneeded jobs when no xmp file exists (#31936) fix(server): dont queue unneeded jobs when no xmp file exists (#31927) (cherry picked from commit 32e3239ba32f3f89b1b0b996c103333927b3324a) Co-authored-by: Jonathan Jogenfors --- server/src/services/job.service.spec.ts | 12 ++++++++++++ server/src/services/metadata.service.spec.ts | 12 ++++++++++++ server/src/services/metadata.service.ts | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/server/src/services/job.service.spec.ts b/server/src/services/job.service.spec.ts index baaf97cac..9f90b9140 100644 --- a/server/src/services/job.service.spec.ts +++ b/server/src/services/job.service.spec.ts @@ -20,6 +20,18 @@ describe(JobService.name, () => { }); describe('onJobRun', () => { + it('should queue metadata extraction when sidecar discovery is skipped', async () => { + const job: JobItem = { name: JobName.SidecarCheck, data: { id: 'asset-1', source: 'upload' } }; + mocks.job.run.mockResolvedValue(JobStatus.Skipped); + + await sut.onJobRun(QueueName.Sidecar, job); + + expect(mocks.job.queue).toHaveBeenCalledExactlyOnceWith({ + name: JobName.AssetExtractMetadata, + data: job.data, + }); + }); + it('should process a successful job', async () => { mocks.job.run.mockResolvedValue(JobStatus.Success); diff --git a/server/src/services/metadata.service.spec.ts b/server/src/services/metadata.service.spec.ts index f8a280742..65ab6dc37 100644 --- a/server/src/services/metadata.service.spec.ts +++ b/server/src/services/metadata.service.spec.ts @@ -1907,6 +1907,18 @@ describe(MetadataService.name, () => { expect(mocks.asset.update).not.toHaveBeenCalled(); }); + it('should skip database writes if no sidecar exists or was previously recorded', async () => { + const asset = forSidecarJob(); + + mocks.assetJob.getForSidecarCheckJob.mockResolvedValue(asset); + mocks.storage.checkFileExists.mockResolvedValue(false); + + await expect(sut.handleSidecarCheck({ id: asset.id })).resolves.toBe(JobStatus.Skipped); + + expect(mocks.asset.upsertFile).not.toHaveBeenCalled(); + expect(mocks.asset.deleteFile).not.toHaveBeenCalled(); + }); + it('should detect a new sidecar at .jpg.xmp', async () => { const asset = forSidecarJob({ originalPath: '/path/to/IMG_123.jpg', files: [] }); diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index 84c5d41e4..be314651f 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -448,7 +448,7 @@ export class MetadataService extends BaseService { const { sidecarFile } = getAssetFiles(asset.files); - const isChanged = sidecarPath !== sidecarFile?.path; + const isChanged = sidecarPath !== (sidecarFile?.path ?? null); if (sidecarFile?.path || sidecarPath) { this.logger.debug(