import { APP_UPLOAD_LOCATION } from '@app/common'; import { AssetEntity } from '@app/infra'; import { ImmichConfigService } from '@app/immich-config'; import { QueueName, JobName } from '@app/job'; import { StorageService } from '@app/storage'; import { Process, Processor } from '@nestjs/bull'; import { Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @Processor(QueueName.CONFIG) export class StorageMigrationProcessor { readonly logger: Logger = new Logger(StorageMigrationProcessor.name); constructor( private storageService: StorageService, private immichConfigService: ImmichConfigService, @InjectRepository(AssetEntity) private assetRepository: Repository, ) {} /** * Migration process when a new user set a new storage template. * @param job */ @Process({ name: JobName.TEMPLATE_MIGRATION, concurrency: 100 }) async templateMigration() { console.time('migrating-time'); const assets = await this.assetRepository.find({ relations: ['exifInfo'], }); const livePhotoMap: Record = {}; for (const asset of assets) { if (asset.livePhotoVideoId) { livePhotoMap[asset.livePhotoVideoId] = asset; } } for (const asset of assets) { const livePhotoParentAsset = livePhotoMap[asset.id]; const filename = asset.exifInfo?.imageName || livePhotoParentAsset?.exifInfo?.imageName || asset.id; await this.storageService.moveAsset(asset, filename); } await this.storageService.removeEmptyDirectories(APP_UPLOAD_LOCATION); console.timeEnd('migrating-time'); } /** * Update config when a new storage template is set. * This is to ensure the synchronization between processes. * @param job */ @Process({ name: JobName.CONFIG_CHANGE, concurrency: 1 }) async updateTemplate() { await this.immichConfigService.refreshConfig(); } }