Browse Source

fix(server): transcodes failing due to storage migration happening simultaneously (#3071)

Mert 2 years ago
parent
commit
71a2914f3e

+ 17 - 1
server/src/domain/job/job.service.spec.ts

@@ -1,6 +1,7 @@
 import { SystemConfig } from '@app/infra/entities';
 import { SystemConfig } from '@app/infra/entities';
 import { BadRequestException } from '@nestjs/common';
 import { BadRequestException } from '@nestjs/common';
 import {
 import {
+  assetEntityStub,
   asyncTick,
   asyncTick,
   newAssetRepositoryMock,
   newAssetRepositoryMock,
   newCommunicationRepositoryMock,
   newCommunicationRepositoryMock,
@@ -271,6 +272,17 @@ describe(JobService.name, () => {
           JobName.GENERATE_THUMBHASH_THUMBNAIL,
           JobName.GENERATE_THUMBHASH_THUMBNAIL,
         ],
         ],
       },
       },
+      {
+        item: { name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: 'asset-1', source: 'upload' } },
+        jobs: [
+          JobName.GENERATE_WEBP_THUMBNAIL,
+          JobName.CLASSIFY_IMAGE,
+          JobName.ENCODE_CLIP,
+          JobName.RECOGNIZE_FACES,
+          JobName.GENERATE_THUMBHASH_THUMBNAIL,
+          JobName.VIDEO_CONVERSION,
+        ],
+      },
       {
       {
         item: { name: JobName.CLASSIFY_IMAGE, data: { id: 'asset-1' } },
         item: { name: JobName.CLASSIFY_IMAGE, data: { id: 'asset-1' } },
         jobs: [JobName.SEARCH_INDEX_ASSET],
         jobs: [JobName.SEARCH_INDEX_ASSET],
@@ -287,7 +299,11 @@ describe(JobService.name, () => {
 
 
     for (const { item, jobs } of tests) {
     for (const { item, jobs } of tests) {
       it(`should queue ${jobs.length} jobs when a ${item.name} job finishes successfully`, async () => {
       it(`should queue ${jobs.length} jobs when a ${item.name} job finishes successfully`, async () => {
-        assetMock.getByIds.mockResolvedValue([]);
+        if (item.name === JobName.GENERATE_JPEG_THUMBNAIL && item.data.source === 'upload') {
+          assetMock.getByIds.mockResolvedValue([assetEntityStub.livePhotoMotionAsset]);
+        } else {
+          assetMock.getByIds.mockResolvedValue([]);
+        }
 
 
         await sut.registerHandlers(makeMockHandlers(true));
         await sut.registerHandlers(makeMockHandlers(true));
         await jobMock.addHandler.mock.calls[0][2](item);
         await jobMock.addHandler.mock.calls[0][2](item);

+ 7 - 0
server/src/domain/job/job.service.ts

@@ -1,3 +1,4 @@
+import { AssetType } from '@app/infra/entities';
 import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common';
 import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common';
 import { IAssetRepository, mapAsset } from '../asset';
 import { IAssetRepository, mapAsset } from '../asset';
 import { CommunicationEvent, ICommunicationRepository } from '../communication';
 import { CommunicationEvent, ICommunicationRepository } from '../communication';
@@ -163,9 +164,15 @@ export class JobService {
         await this.jobRepository.queue({ name: JobName.CLASSIFY_IMAGE, data: item.data });
         await this.jobRepository.queue({ name: JobName.CLASSIFY_IMAGE, data: item.data });
         await this.jobRepository.queue({ name: JobName.ENCODE_CLIP, data: item.data });
         await this.jobRepository.queue({ name: JobName.ENCODE_CLIP, data: item.data });
         await this.jobRepository.queue({ name: JobName.RECOGNIZE_FACES, data: item.data });
         await this.jobRepository.queue({ name: JobName.RECOGNIZE_FACES, data: item.data });
+        if (item.data.source !== 'upload') {
+          break;
+        }
 
 
         const [asset] = await this.assetRepository.getByIds([item.data.id]);
         const [asset] = await this.assetRepository.getByIds([item.data.id]);
         if (asset) {
         if (asset) {
+          if (asset.type === AssetType.VIDEO) {
+            await this.jobRepository.queue({ name: JobName.VIDEO_CONVERSION, data: item.data });
+          }
           this.communicationRepository.send(CommunicationEvent.UPLOAD_SUCCESS, asset.ownerId, mapAsset(asset));
           this.communicationRepository.send(CommunicationEvent.UPLOAD_SUCCESS, asset.ownerId, mapAsset(asset));
         }
         }
         break;
         break;

+ 1 - 1
server/src/domain/media/media.service.ts

@@ -128,7 +128,7 @@ export class MediaService {
 
 
   async handleVideoConversion({ id }: IEntityJob) {
   async handleVideoConversion({ id }: IEntityJob) {
     const [asset] = await this.assetRepository.getByIds([id]);
     const [asset] = await this.assetRepository.getByIds([id]);
-    if (!asset) {
+    if (!asset || asset.type !== AssetType.VIDEO) {
       return false;
       return false;
     }
     }
 
 

+ 1 - 4
server/src/immich/api-v1/asset/asset.core.ts

@@ -1,5 +1,5 @@
 import { AuthUserDto, IJobRepository, JobName } from '@app/domain';
 import { AuthUserDto, IJobRepository, JobName } from '@app/domain';
-import { AssetEntity, AssetType, UserEntity } from '@app/infra/entities';
+import { AssetEntity, UserEntity } from '@app/infra/entities';
 import { parse } from 'node:path';
 import { parse } from 'node:path';
 import { IAssetRepository } from './asset-repository';
 import { IAssetRepository } from './asset-repository';
 import { CreateAssetDto, ImportAssetDto, UploadFile } from './dto/create-asset.dto';
 import { CreateAssetDto, ImportAssetDto, UploadFile } from './dto/create-asset.dto';
@@ -46,9 +46,6 @@ export class AssetCore {
     });
     });
 
 
     await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id: asset.id, source: 'upload' } });
     await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id: asset.id, source: 'upload' } });
-    if (asset.type === AssetType.VIDEO) {
-      await this.jobRepository.queue({ name: JobName.VIDEO_CONVERSION, data: { id: asset.id } });
-    }
 
 
     return asset;
     return asset;
   }
   }

+ 0 - 1
server/src/immich/api-v1/asset/asset.service.spec.ts

@@ -228,7 +228,6 @@ describe('AssetService', () => {
             data: { id: assetEntityStub.livePhotoMotionAsset.id, source: 'upload' },
             data: { id: assetEntityStub.livePhotoMotionAsset.id, source: 'upload' },
           },
           },
         ],
         ],
-        [{ name: JobName.VIDEO_CONVERSION, data: { id: assetEntityStub.livePhotoMotionAsset.id } }],
         [{ name: JobName.METADATA_EXTRACTION, data: { id: assetEntityStub.livePhotoStillAsset.id, source: 'upload' } }],
         [{ name: JobName.METADATA_EXTRACTION, data: { id: assetEntityStub.livePhotoStillAsset.id, source: 'upload' } }],
       ]);
       ]);
     });
     });