mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -05:00 
			
		
		
		
	fix(server): transcodes failing due to storage migration happening simultaneously (#3071)
This commit is contained in:
		
							parent
							
								
									0d30ceb284
								
							
						
					
					
						commit
						71a2914f3e
					
				@ -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 () => {
 | 
				
			||||||
 | 
					        if (item.name === JobName.GENERATE_JPEG_THUMBNAIL && item.data.source === 'upload') {
 | 
				
			||||||
 | 
					          assetMock.getByIds.mockResolvedValue([assetEntityStub.livePhotoMotionAsset]);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
          assetMock.getByIds.mockResolvedValue([]);
 | 
					          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);
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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,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;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -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' } }],
 | 
				
			||||||
      ]);
 | 
					      ]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user