mirror of
https://github.com/immich-app/immich.git
synced 2025-03-01 15:11:21 +01:00
refactor: job names (#1343)
* refactor: job names * refactor: remove jobId
This commit is contained in:
parent
adacfb1110
commit
693adf8488
19 changed files with 125 additions and 289 deletions
|
@ -10,7 +10,7 @@ import {
|
||||||
StreamableFile,
|
StreamableFile,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { createHash, randomUUID } from 'node:crypto';
|
import { createHash } from 'node:crypto';
|
||||||
import { QueryFailedError, Repository } from 'typeorm';
|
import { QueryFailedError, Repository } from 'typeorm';
|
||||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||||
import { AssetEntity, AssetType, SharedLinkType } from '@app/infra';
|
import { AssetEntity, AssetType, SharedLinkType } from '@app/infra';
|
||||||
|
@ -43,13 +43,7 @@ import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-as
|
||||||
import { UpdateAssetDto } from './dto/update-asset.dto';
|
import { UpdateAssetDto } from './dto/update-asset.dto';
|
||||||
import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto';
|
import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto';
|
||||||
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
|
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
|
||||||
import {
|
import { IAssetUploadedJob, IVideoTranscodeJob, QueueName, JobName } from '@app/job';
|
||||||
assetUploadedProcessorName,
|
|
||||||
IAssetUploadedJob,
|
|
||||||
IVideoTranscodeJob,
|
|
||||||
mp4ConversionProcessorName,
|
|
||||||
QueueNameEnum,
|
|
||||||
} from '@app/job';
|
|
||||||
import { InjectQueue } from '@nestjs/bull';
|
import { InjectQueue } from '@nestjs/bull';
|
||||||
import { Queue } from 'bull';
|
import { Queue } from 'bull';
|
||||||
import { DownloadService } from '../../modules/download/download.service';
|
import { DownloadService } from '../../modules/download/download.service';
|
||||||
|
@ -80,16 +74,16 @@ export class AssetService {
|
||||||
|
|
||||||
private backgroundTaskService: BackgroundTaskService,
|
private backgroundTaskService: BackgroundTaskService,
|
||||||
|
|
||||||
@InjectQueue(QueueNameEnum.ASSET_UPLOADED)
|
@InjectQueue(QueueName.ASSET_UPLOADED)
|
||||||
private assetUploadedQueue: Queue<IAssetUploadedJob>,
|
private assetUploadedQueue: Queue<IAssetUploadedJob>,
|
||||||
|
|
||||||
@InjectQueue(QueueNameEnum.VIDEO_CONVERSION)
|
@InjectQueue(QueueName.VIDEO_CONVERSION)
|
||||||
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
||||||
|
|
||||||
private downloadService: DownloadService,
|
private downloadService: DownloadService,
|
||||||
|
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
@Inject(ISharedLinkRepository) private sharedLinkRepository: ISharedLinkRepository,
|
@Inject(ISharedLinkRepository) sharedLinkRepository: ISharedLinkRepository,
|
||||||
) {
|
) {
|
||||||
this.shareCore = new ShareCore(sharedLinkRepository);
|
this.shareCore = new ShareCore(sharedLinkRepository);
|
||||||
}
|
}
|
||||||
|
@ -128,11 +122,7 @@ export class AssetService {
|
||||||
|
|
||||||
await this.storageService.moveAsset(livePhotoAssetEntity, originalAssetData.originalname);
|
await this.storageService.moveAsset(livePhotoAssetEntity, originalAssetData.originalname);
|
||||||
|
|
||||||
await this.videoConversionQueue.add(
|
await this.videoConversionQueue.add(JobName.MP4_CONVERSION, { asset: livePhotoAssetEntity });
|
||||||
mp4ConversionProcessorName,
|
|
||||||
{ asset: livePhotoAssetEntity },
|
|
||||||
{ jobId: randomUUID() },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const assetEntity = await this.createUserAsset(
|
const assetEntity = await this.createUserAsset(
|
||||||
|
@ -157,7 +147,7 @@ export class AssetService {
|
||||||
const movedAsset = await this.storageService.moveAsset(assetEntity, originalAssetData.originalname);
|
const movedAsset = await this.storageService.moveAsset(assetEntity, originalAssetData.originalname);
|
||||||
|
|
||||||
await this.assetUploadedQueue.add(
|
await this.assetUploadedQueue.add(
|
||||||
assetUploadedProcessorName,
|
JobName.ASSET_UPLOADED,
|
||||||
{ asset: movedAsset, fileName: originalAssetData.originalname },
|
{ asset: movedAsset, fileName: originalAssetData.originalname },
|
||||||
{ jobId: movedAsset.id },
|
{ jobId: movedAsset.id },
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,19 +1,8 @@
|
||||||
import {
|
import { IMetadataExtractionJob, IThumbnailGenerationJob, IVideoTranscodeJob, QueueName, JobName } from '@app/job';
|
||||||
exifExtractionProcessorName,
|
|
||||||
generateJPEGThumbnailProcessorName,
|
|
||||||
IMetadataExtractionJob,
|
|
||||||
IThumbnailGenerationJob,
|
|
||||||
IVideoTranscodeJob,
|
|
||||||
MachineLearningJobNameEnum,
|
|
||||||
QueueNameEnum,
|
|
||||||
templateMigrationProcessorName,
|
|
||||||
videoMetadataExtractionProcessorName,
|
|
||||||
} from '@app/job';
|
|
||||||
import { InjectQueue } from '@nestjs/bull';
|
import { InjectQueue } from '@nestjs/bull';
|
||||||
import { Queue } from 'bull';
|
import { Queue } from 'bull';
|
||||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||||
import { AllJobStatusResponseDto } from './response-dto/all-job-status-response.dto';
|
import { AllJobStatusResponseDto } from './response-dto/all-job-status-response.dto';
|
||||||
import { randomUUID } from 'crypto';
|
|
||||||
import { IAssetRepository } from '../asset/asset-repository';
|
import { IAssetRepository } from '../asset/asset-repository';
|
||||||
import { AssetType } from '@app/infra';
|
import { AssetType } from '@app/infra';
|
||||||
import { GetJobDto, JobId } from './dto/get-job.dto';
|
import { GetJobDto, JobId } from './dto/get-job.dto';
|
||||||
|
@ -24,20 +13,20 @@ import { StorageService } from '@app/storage';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JobService {
|
export class JobService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectQueue(QueueNameEnum.THUMBNAIL_GENERATION)
|
@InjectQueue(QueueName.THUMBNAIL_GENERATION)
|
||||||
private thumbnailGeneratorQueue: Queue<IThumbnailGenerationJob>,
|
private thumbnailGeneratorQueue: Queue<IThumbnailGenerationJob>,
|
||||||
|
|
||||||
@InjectQueue(QueueNameEnum.METADATA_EXTRACTION)
|
@InjectQueue(QueueName.METADATA_EXTRACTION)
|
||||||
private metadataExtractionQueue: Queue<IMetadataExtractionJob>,
|
private metadataExtractionQueue: Queue<IMetadataExtractionJob>,
|
||||||
|
|
||||||
@InjectQueue(QueueNameEnum.VIDEO_CONVERSION)
|
@InjectQueue(QueueName.VIDEO_CONVERSION)
|
||||||
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
||||||
|
|
||||||
@InjectQueue(QueueNameEnum.MACHINE_LEARNING)
|
@InjectQueue(QueueName.MACHINE_LEARNING)
|
||||||
private machineLearningQueue: Queue<IMachineLearningJob>,
|
private machineLearningQueue: Queue<IMachineLearningJob>,
|
||||||
|
|
||||||
@InjectQueue(QueueNameEnum.STORAGE_MIGRATION)
|
@InjectQueue(QueueName.CONFIG)
|
||||||
private storageMigrationQueue: Queue,
|
private configQueue: Queue,
|
||||||
|
|
||||||
@Inject(IAssetRepository)
|
@Inject(IAssetRepository)
|
||||||
private _assetRepository: IAssetRepository,
|
private _assetRepository: IAssetRepository,
|
||||||
|
@ -47,7 +36,7 @@ export class JobService {
|
||||||
this.thumbnailGeneratorQueue.empty();
|
this.thumbnailGeneratorQueue.empty();
|
||||||
this.metadataExtractionQueue.empty();
|
this.metadataExtractionQueue.empty();
|
||||||
this.videoConversionQueue.empty();
|
this.videoConversionQueue.empty();
|
||||||
this.storageMigrationQueue.empty();
|
this.configQueue.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
async startJob(jobDto: GetJobDto): Promise<number> {
|
async startJob(jobDto: GetJobDto): Promise<number> {
|
||||||
|
@ -72,7 +61,7 @@ export class JobService {
|
||||||
const metadataExtractionJobCount = await this.metadataExtractionQueue.getJobCounts();
|
const metadataExtractionJobCount = await this.metadataExtractionQueue.getJobCounts();
|
||||||
const videoConversionJobCount = await this.videoConversionQueue.getJobCounts();
|
const videoConversionJobCount = await this.videoConversionQueue.getJobCounts();
|
||||||
const machineLearningJobCount = await this.machineLearningQueue.getJobCounts();
|
const machineLearningJobCount = await this.machineLearningQueue.getJobCounts();
|
||||||
const storageMigrationJobCount = await this.storageMigrationQueue.getJobCounts();
|
const storageMigrationJobCount = await this.configQueue.getJobCounts();
|
||||||
|
|
||||||
const response = new AllJobStatusResponseDto();
|
const response = new AllJobStatusResponseDto();
|
||||||
response.isThumbnailGenerationActive = Boolean(thumbnailGeneratorJobCount.waiting);
|
response.isThumbnailGenerationActive = Boolean(thumbnailGeneratorJobCount.waiting);
|
||||||
|
@ -108,8 +97,8 @@ export class JobService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.jobId === JobId.STORAGE_TEMPLATE_MIGRATION) {
|
if (query.jobId === JobId.STORAGE_TEMPLATE_MIGRATION) {
|
||||||
response.isActive = Boolean((await this.storageMigrationQueue.getJobCounts()).waiting);
|
response.isActive = Boolean((await this.configQueue.getJobCounts()).waiting);
|
||||||
response.queueCount = await this.storageMigrationQueue.getJobCounts();
|
response.queueCount = await this.configQueue.getJobCounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
|
@ -130,7 +119,7 @@ export class JobService {
|
||||||
this.machineLearningQueue.empty();
|
this.machineLearningQueue.empty();
|
||||||
return 0;
|
return 0;
|
||||||
case JobId.STORAGE_TEMPLATE_MIGRATION:
|
case JobId.STORAGE_TEMPLATE_MIGRATION:
|
||||||
this.storageMigrationQueue.empty();
|
this.configQueue.empty();
|
||||||
return 0;
|
return 0;
|
||||||
default:
|
default:
|
||||||
throw new BadRequestException('Invalid job id');
|
throw new BadRequestException('Invalid job id');
|
||||||
|
@ -147,7 +136,7 @@ export class JobService {
|
||||||
const assetsWithNoThumbnail = await this._assetRepository.getAssetWithNoThumbnail();
|
const assetsWithNoThumbnail = await this._assetRepository.getAssetWithNoThumbnail();
|
||||||
|
|
||||||
for (const asset of assetsWithNoThumbnail) {
|
for (const asset of assetsWithNoThumbnail) {
|
||||||
await this.thumbnailGeneratorQueue.add(generateJPEGThumbnailProcessorName, { asset }, { jobId: randomUUID() });
|
await this.thumbnailGeneratorQueue.add(JobName.GENERATE_JPEG_THUMBNAIL, { asset });
|
||||||
}
|
}
|
||||||
|
|
||||||
return assetsWithNoThumbnail.length;
|
return assetsWithNoThumbnail.length;
|
||||||
|
@ -163,17 +152,9 @@ export class JobService {
|
||||||
const assetsWithNoExif = await this._assetRepository.getAssetWithNoEXIF();
|
const assetsWithNoExif = await this._assetRepository.getAssetWithNoEXIF();
|
||||||
for (const asset of assetsWithNoExif) {
|
for (const asset of assetsWithNoExif) {
|
||||||
if (asset.type === AssetType.VIDEO) {
|
if (asset.type === AssetType.VIDEO) {
|
||||||
await this.metadataExtractionQueue.add(
|
await this.metadataExtractionQueue.add(JobName.EXTRACT_VIDEO_METADATA, { asset, fileName: asset.id });
|
||||||
videoMetadataExtractionProcessorName,
|
|
||||||
{ asset, fileName: asset.id },
|
|
||||||
{ jobId: randomUUID() },
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
await this.metadataExtractionQueue.add(
|
await this.metadataExtractionQueue.add(JobName.EXIF_EXTRACTION, { asset, fileName: asset.id });
|
||||||
exifExtractionProcessorName,
|
|
||||||
{ asset, fileName: asset.id },
|
|
||||||
{ jobId: randomUUID() },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return assetsWithNoExif.length;
|
return assetsWithNoExif.length;
|
||||||
|
@ -189,25 +170,21 @@ export class JobService {
|
||||||
const assetWithNoSmartInfo = await this._assetRepository.getAssetWithNoSmartInfo();
|
const assetWithNoSmartInfo = await this._assetRepository.getAssetWithNoSmartInfo();
|
||||||
|
|
||||||
for (const asset of assetWithNoSmartInfo) {
|
for (const asset of assetWithNoSmartInfo) {
|
||||||
await this.machineLearningQueue.add(MachineLearningJobNameEnum.IMAGE_TAGGING, { asset }, { jobId: randomUUID() });
|
await this.machineLearningQueue.add(JobName.IMAGE_TAGGING, { asset });
|
||||||
await this.machineLearningQueue.add(
|
await this.machineLearningQueue.add(JobName.OBJECT_DETECTION, { asset });
|
||||||
MachineLearningJobNameEnum.OBJECT_DETECTION,
|
|
||||||
{ asset },
|
|
||||||
{ jobId: randomUUID() },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return assetWithNoSmartInfo.length;
|
return assetWithNoSmartInfo.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
async runStorageMigration() {
|
async runStorageMigration() {
|
||||||
const jobCount = await this.storageMigrationQueue.getJobCounts();
|
const jobCount = await this.configQueue.getJobCounts();
|
||||||
|
|
||||||
if (jobCount.active > 0) {
|
if (jobCount.active > 0) {
|
||||||
throw new BadRequestException('Storage migration job is already running');
|
throw new BadRequestException('Storage migration job is already running');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.storageMigrationQueue.add(templateMigrationProcessorName, {}, { jobId: randomUUID() });
|
await this.configQueue.add(JobName.TEMPLATE_MIGRATION, {});
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { QueueNameEnum, updateTemplateProcessorName } from '@app/job';
|
import { JobName, QueueName } from '@app/job';
|
||||||
import {
|
import {
|
||||||
supportedDayTokens,
|
supportedDayTokens,
|
||||||
supportedHourTokens,
|
supportedHourTokens,
|
||||||
|
@ -11,7 +11,6 @@ import {
|
||||||
import { InjectQueue } from '@nestjs/bull';
|
import { InjectQueue } from '@nestjs/bull';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Queue } from 'bull';
|
import { Queue } from 'bull';
|
||||||
import { randomUUID } from 'crypto';
|
|
||||||
import { ImmichConfigService } from 'libs/immich-config/src';
|
import { ImmichConfigService } from 'libs/immich-config/src';
|
||||||
import { mapConfig, SystemConfigDto } from './dto/system-config.dto';
|
import { mapConfig, SystemConfigDto } from './dto/system-config.dto';
|
||||||
import { SystemConfigTemplateStorageOptionDto } from './response-dto/system-config-template-storage-option.dto';
|
import { SystemConfigTemplateStorageOptionDto } from './response-dto/system-config-template-storage-option.dto';
|
||||||
|
@ -20,8 +19,7 @@ import { SystemConfigTemplateStorageOptionDto } from './response-dto/system-conf
|
||||||
export class SystemConfigService {
|
export class SystemConfigService {
|
||||||
constructor(
|
constructor(
|
||||||
private immichConfigService: ImmichConfigService,
|
private immichConfigService: ImmichConfigService,
|
||||||
@InjectQueue(QueueNameEnum.STORAGE_MIGRATION)
|
@InjectQueue(QueueName.CONFIG) private configQueue: Queue,
|
||||||
private storageMigrationQueue: Queue,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async getConfig(): Promise<SystemConfigDto> {
|
public async getConfig(): Promise<SystemConfigDto> {
|
||||||
|
@ -36,7 +34,7 @@ export class SystemConfigService {
|
||||||
|
|
||||||
public async updateConfig(dto: SystemConfigDto): Promise<SystemConfigDto> {
|
public async updateConfig(dto: SystemConfigDto): Promise<SystemConfigDto> {
|
||||||
const config = await this.immichConfigService.updateConfig(dto);
|
const config = await this.immichConfigService.updateConfig(dto);
|
||||||
this.storageMigrationQueue.add(updateTemplateProcessorName, {}, { jobId: randomUUID() });
|
this.configQueue.add(JobName.CONFIG_CHANGE, {});
|
||||||
return mapConfig(config);
|
return mapConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
import { BullModule } from '@nestjs/bull';
|
import { BullModule } from '@nestjs/bull';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { QueueName } from '@app/job';
|
||||||
import { AssetEntity, ExifEntity, SmartInfoEntity } from '@app/infra';
|
|
||||||
import { BackgroundTaskProcessor } from './background-task.processor';
|
import { BackgroundTaskProcessor } from './background-task.processor';
|
||||||
import { BackgroundTaskService } from './background-task.service';
|
import { BackgroundTaskService } from './background-task.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [BullModule.registerQueue({ name: QueueName.BACKGROUND_TASK })],
|
||||||
BullModule.registerQueue({
|
|
||||||
name: 'background-task',
|
|
||||||
}),
|
|
||||||
TypeOrmModule.forFeature([AssetEntity, ExifEntity, SmartInfoEntity]),
|
|
||||||
],
|
|
||||||
providers: [BackgroundTaskService, BackgroundTaskProcessor],
|
providers: [BackgroundTaskService, BackgroundTaskProcessor],
|
||||||
exports: [BackgroundTaskService, BullModule],
|
exports: [BackgroundTaskService, BullModule],
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,23 +1,12 @@
|
||||||
import { Process, Processor } from '@nestjs/bull';
|
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { AssetEntity, SmartInfoEntity } from '@app/infra';
|
|
||||||
import { Job } from 'bull';
|
|
||||||
import { AssetResponseDto } from '../../api-v1/asset/response-dto/asset-response.dto';
|
|
||||||
import { assetUtils } from '@app/common/utils';
|
import { assetUtils } from '@app/common/utils';
|
||||||
|
import { Process, Processor } from '@nestjs/bull';
|
||||||
|
import { Job } from 'bull';
|
||||||
|
import { JobName, QueueName } from '@app/job';
|
||||||
|
import { AssetResponseDto } from '../../api-v1/asset/response-dto/asset-response.dto';
|
||||||
|
|
||||||
@Processor('background-task')
|
@Processor(QueueName.BACKGROUND_TASK)
|
||||||
export class BackgroundTaskProcessor {
|
export class BackgroundTaskProcessor {
|
||||||
constructor(
|
@Process(JobName.DELETE_FILE_ON_DISK)
|
||||||
@InjectRepository(AssetEntity)
|
|
||||||
private assetRepository: Repository<AssetEntity>,
|
|
||||||
|
|
||||||
@InjectRepository(SmartInfoEntity)
|
|
||||||
private smartInfoRepository: Repository<SmartInfoEntity>,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
// TODO: Should probably use constants / Interfaces for Queue names / data
|
|
||||||
@Process('delete-file-on-disk')
|
|
||||||
async deleteFileOnDisk(job: Job<{ assets: AssetResponseDto[] }>) {
|
async deleteFileOnDisk(job: Job<{ assets: AssetResponseDto[] }>) {
|
||||||
const { assets } = job.data;
|
const { assets } = job.data;
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,17 @@
|
||||||
import { InjectQueue } from '@nestjs/bull/dist/decorators';
|
import { InjectQueue } from '@nestjs/bull/dist/decorators';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Queue } from 'bull';
|
import { Queue } from 'bull';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { JobName, QueueName } from '@app/job';
|
||||||
import { AssetResponseDto } from '../../api-v1/asset/response-dto/asset-response.dto';
|
import { AssetResponseDto } from '../../api-v1/asset/response-dto/asset-response.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BackgroundTaskService {
|
export class BackgroundTaskService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectQueue('background-task')
|
@InjectQueue(QueueName.BACKGROUND_TASK)
|
||||||
private backgroundTaskQueue: Queue,
|
private backgroundTaskQueue: Queue,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async deleteFileOnDisk(assets: AssetResponseDto[]) {
|
async deleteFileOnDisk(assets: AssetResponseDto[]) {
|
||||||
await this.backgroundTaskQueue.add(
|
await this.backgroundTaskQueue.add(JobName.DELETE_FILE_ON_DISK, { assets });
|
||||||
'delete-file-on-disk',
|
|
||||||
{
|
|
||||||
assets,
|
|
||||||
},
|
|
||||||
{ jobId: randomUUID() },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,7 @@ import { IsNull, Not, Repository } from 'typeorm';
|
||||||
import { AssetEntity, AssetType, ExifEntity, UserEntity } from '@app/infra';
|
import { AssetEntity, AssetType, ExifEntity, UserEntity } from '@app/infra';
|
||||||
import { InjectQueue } from '@nestjs/bull';
|
import { InjectQueue } from '@nestjs/bull';
|
||||||
import { Queue } from 'bull';
|
import { Queue } from 'bull';
|
||||||
import { randomUUID } from 'crypto';
|
import { IMetadataExtractionJob, IVideoTranscodeJob, QueueName, JobName } from '@app/job';
|
||||||
import {
|
|
||||||
userDeletionProcessorName,
|
|
||||||
exifExtractionProcessorName,
|
|
||||||
generateWEBPThumbnailProcessorName,
|
|
||||||
IMetadataExtractionJob,
|
|
||||||
IVideoTranscodeJob,
|
|
||||||
mp4ConversionProcessorName,
|
|
||||||
QueueNameEnum,
|
|
||||||
reverseGeocodingProcessorName,
|
|
||||||
videoMetadataExtractionProcessorName,
|
|
||||||
} from '@app/job';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { IUserDeletionJob } from '@app/job/interfaces/user-deletion.interface';
|
import { IUserDeletionJob } from '@app/job/interfaces/user-deletion.interface';
|
||||||
import { userUtils } from '@app/common';
|
import { userUtils } from '@app/common';
|
||||||
|
@ -33,16 +22,16 @@ export class ScheduleTasksService {
|
||||||
@InjectRepository(ExifEntity)
|
@InjectRepository(ExifEntity)
|
||||||
private exifRepository: Repository<ExifEntity>,
|
private exifRepository: Repository<ExifEntity>,
|
||||||
|
|
||||||
@InjectQueue(QueueNameEnum.THUMBNAIL_GENERATION)
|
@InjectQueue(QueueName.THUMBNAIL_GENERATION)
|
||||||
private thumbnailGeneratorQueue: Queue,
|
private thumbnailGeneratorQueue: Queue,
|
||||||
|
|
||||||
@InjectQueue(QueueNameEnum.VIDEO_CONVERSION)
|
@InjectQueue(QueueName.VIDEO_CONVERSION)
|
||||||
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
||||||
|
|
||||||
@InjectQueue(QueueNameEnum.METADATA_EXTRACTION)
|
@InjectQueue(QueueName.METADATA_EXTRACTION)
|
||||||
private metadataExtractionQueue: Queue<IMetadataExtractionJob>,
|
private metadataExtractionQueue: Queue<IMetadataExtractionJob>,
|
||||||
|
|
||||||
@InjectQueue(QueueNameEnum.USER_DELETION)
|
@InjectQueue(QueueName.USER_DELETION)
|
||||||
private userDeletionQueue: Queue<IUserDeletionJob>,
|
private userDeletionQueue: Queue<IUserDeletionJob>,
|
||||||
|
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
|
@ -62,11 +51,7 @@ export class ScheduleTasksService {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const asset of assets) {
|
for (const asset of assets) {
|
||||||
await this.thumbnailGeneratorQueue.add(
|
await this.thumbnailGeneratorQueue.add(JobName.GENERATE_WEBP_THUMBNAIL, { asset: asset });
|
||||||
generateWEBPThumbnailProcessorName,
|
|
||||||
{ asset: asset },
|
|
||||||
{ jobId: randomUUID() },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +69,7 @@ export class ScheduleTasksService {
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const asset of assets) {
|
for (const asset of assets) {
|
||||||
await this.videoConversionQueue.add(mp4ConversionProcessorName, { asset }, { jobId: randomUUID() });
|
await this.videoConversionQueue.add(JobName.MP4_CONVERSION, { asset });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,10 +88,9 @@ export class ScheduleTasksService {
|
||||||
|
|
||||||
for (const exif of exifInfo) {
|
for (const exif of exifInfo) {
|
||||||
await this.metadataExtractionQueue.add(
|
await this.metadataExtractionQueue.add(
|
||||||
reverseGeocodingProcessorName,
|
JobName.REVERSE_GEOCODING,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
{ exifId: exif.id, latitude: exif.latitude!, longitude: exif.longitude! },
|
{ exifId: exif.id, latitude: exif.latitude!, longitude: exif.longitude! },
|
||||||
{ jobId: randomUUID() },
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,17 +106,9 @@ export class ScheduleTasksService {
|
||||||
|
|
||||||
for (const asset of exifAssets) {
|
for (const asset of exifAssets) {
|
||||||
if (asset.type === AssetType.VIDEO) {
|
if (asset.type === AssetType.VIDEO) {
|
||||||
await this.metadataExtractionQueue.add(
|
await this.metadataExtractionQueue.add(JobName.EXTRACT_VIDEO_METADATA, { asset, fileName: asset.id });
|
||||||
videoMetadataExtractionProcessorName,
|
|
||||||
{ asset, fileName: asset.id },
|
|
||||||
{ jobId: randomUUID() },
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
await this.metadataExtractionQueue.add(
|
await this.metadataExtractionQueue.add(JobName.EXIF_EXTRACTION, { asset, fileName: asset.id });
|
||||||
exifExtractionProcessorName,
|
|
||||||
{ asset, fileName: asset.id },
|
|
||||||
{ jobId: randomUUID() },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,7 +118,7 @@ export class ScheduleTasksService {
|
||||||
const usersToDelete = await this.userRepository.find({ withDeleted: true, where: { deletedAt: Not(IsNull()) } });
|
const usersToDelete = await this.userRepository.find({ withDeleted: true, where: { deletedAt: Not(IsNull()) } });
|
||||||
for (const user of usersToDelete) {
|
for (const user of usersToDelete) {
|
||||||
if (userUtils.isReadyForDeletion(user)) {
|
if (userUtils.isReadyForDeletion(user)) {
|
||||||
await this.userDeletionQueue.add(userDeletionProcessorName, { user: user }, { jobId: randomUUID() });
|
await this.userDeletionQueue.add(JobName.USER_DELETION, { user });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,17 @@
|
||||||
import { QueueNameEnum } from '@app/job';
|
import { QueueName } from '@app/job';
|
||||||
import { InjectQueue } from '@nestjs/bull';
|
import { InjectQueue } from '@nestjs/bull';
|
||||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||||
import { Queue } from 'bull';
|
import { Queue } from 'bull';
|
||||||
import { randomUUID } from 'node:crypto';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MicroservicesService implements OnModuleInit {
|
export class MicroservicesService implements OnModuleInit {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectQueue(QueueNameEnum.CHECKSUM_GENERATION)
|
@InjectQueue(QueueName.CHECKSUM_GENERATION)
|
||||||
private generateChecksumQueue: Queue,
|
private generateChecksumQueue: Queue,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
await this.generateChecksumQueue.add(
|
// wait for migration
|
||||||
{},
|
await this.generateChecksumQueue.add({}, { delay: 10000 });
|
||||||
{
|
|
||||||
jobId: randomUUID(),
|
|
||||||
delay: 10000, // wait for migration
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,27 +4,22 @@ import {
|
||||||
IMetadataExtractionJob,
|
IMetadataExtractionJob,
|
||||||
IThumbnailGenerationJob,
|
IThumbnailGenerationJob,
|
||||||
IVideoTranscodeJob,
|
IVideoTranscodeJob,
|
||||||
assetUploadedProcessorName,
|
QueueName,
|
||||||
exifExtractionProcessorName,
|
JobName,
|
||||||
generateJPEGThumbnailProcessorName,
|
|
||||||
mp4ConversionProcessorName,
|
|
||||||
videoMetadataExtractionProcessorName,
|
|
||||||
QueueNameEnum,
|
|
||||||
} from '@app/job';
|
} from '@app/job';
|
||||||
import { InjectQueue, Process, Processor } from '@nestjs/bull';
|
import { InjectQueue, Process, Processor } from '@nestjs/bull';
|
||||||
import { Job, Queue } from 'bull';
|
import { Job, Queue } from 'bull';
|
||||||
import { randomUUID } from 'crypto';
|
|
||||||
|
|
||||||
@Processor(QueueNameEnum.ASSET_UPLOADED)
|
@Processor(QueueName.ASSET_UPLOADED)
|
||||||
export class AssetUploadedProcessor {
|
export class AssetUploadedProcessor {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectQueue(QueueNameEnum.THUMBNAIL_GENERATION)
|
@InjectQueue(QueueName.THUMBNAIL_GENERATION)
|
||||||
private thumbnailGeneratorQueue: Queue<IThumbnailGenerationJob>,
|
private thumbnailGeneratorQueue: Queue<IThumbnailGenerationJob>,
|
||||||
|
|
||||||
@InjectQueue(QueueNameEnum.METADATA_EXTRACTION)
|
@InjectQueue(QueueName.METADATA_EXTRACTION)
|
||||||
private metadataExtractionQueue: Queue<IMetadataExtractionJob>,
|
private metadataExtractionQueue: Queue<IMetadataExtractionJob>,
|
||||||
|
|
||||||
@InjectQueue(QueueNameEnum.VIDEO_CONVERSION)
|
@InjectQueue(QueueName.VIDEO_CONVERSION)
|
||||||
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@ -37,30 +32,19 @@ export class AssetUploadedProcessor {
|
||||||
*
|
*
|
||||||
* @param job asset-uploaded
|
* @param job asset-uploaded
|
||||||
*/
|
*/
|
||||||
@Process(assetUploadedProcessorName)
|
@Process(JobName.ASSET_UPLOADED)
|
||||||
async processUploadedVideo(job: Job<IAssetUploadedJob>) {
|
async processUploadedVideo(job: Job<IAssetUploadedJob>) {
|
||||||
const { asset, fileName } = job.data;
|
const { asset, fileName } = job.data;
|
||||||
|
|
||||||
await this.thumbnailGeneratorQueue.add(generateJPEGThumbnailProcessorName, { asset }, { jobId: randomUUID() });
|
await this.thumbnailGeneratorQueue.add(JobName.GENERATE_JPEG_THUMBNAIL, { asset });
|
||||||
|
|
||||||
// Video Conversion
|
// Video Conversion
|
||||||
if (asset.type == AssetType.VIDEO) {
|
if (asset.type == AssetType.VIDEO) {
|
||||||
await this.videoConversionQueue.add(mp4ConversionProcessorName, { asset }, { jobId: randomUUID() });
|
await this.videoConversionQueue.add(JobName.MP4_CONVERSION, { asset });
|
||||||
await this.metadataExtractionQueue.add(
|
await this.metadataExtractionQueue.add(JobName.EXTRACT_VIDEO_METADATA, { asset, fileName });
|
||||||
videoMetadataExtractionProcessorName,
|
|
||||||
{ asset, fileName },
|
|
||||||
{ jobId: randomUUID() },
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// Extract Metadata/Exif for Images - Currently the EXIF library on the web cannot extract EXIF for video yet
|
// Extract Metadata/Exif for Images - Currently the EXIF library on the web cannot extract EXIF for video yet
|
||||||
await this.metadataExtractionQueue.add(
|
await this.metadataExtractionQueue.add(JobName.EXIF_EXTRACTION, { asset, fileName });
|
||||||
exifExtractionProcessorName,
|
|
||||||
{
|
|
||||||
asset,
|
|
||||||
fileName,
|
|
||||||
},
|
|
||||||
{ jobId: randomUUID() },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { AssetEntity } from '@app/infra';
|
import { AssetEntity } from '@app/infra';
|
||||||
import { QueueNameEnum } from '@app/job';
|
import { QueueName } from '@app/job';
|
||||||
import { Process, Processor } from '@nestjs/bull';
|
import { Process, Processor } from '@nestjs/bull';
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
@ -8,7 +8,7 @@ import fs from 'node:fs';
|
||||||
import { FindOptionsWhere, IsNull, MoreThan, QueryFailedError, Repository } from 'typeorm';
|
import { FindOptionsWhere, IsNull, MoreThan, QueryFailedError, Repository } from 'typeorm';
|
||||||
|
|
||||||
// TODO: just temporary task to generate previous uploaded assets.
|
// TODO: just temporary task to generate previous uploaded assets.
|
||||||
@Processor(QueueNameEnum.CHECKSUM_GENERATION)
|
@Processor(QueueName.CHECKSUM_GENERATION)
|
||||||
export class GenerateChecksumProcessor {
|
export class GenerateChecksumProcessor {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(AssetEntity)
|
@InjectRepository(AssetEntity)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { AssetEntity } from '@app/infra';
|
import { AssetEntity } from '@app/infra';
|
||||||
import { SmartInfoEntity } from '@app/infra';
|
import { SmartInfoEntity } from '@app/infra';
|
||||||
import { MachineLearningJobNameEnum, QueueNameEnum } from '@app/job';
|
import { QueueName, JobName } from '@app/job';
|
||||||
import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface';
|
import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface';
|
||||||
import { Process, Processor } from '@nestjs/bull';
|
import { Process, Processor } from '@nestjs/bull';
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
|
@ -11,14 +11,14 @@ import { Repository } from 'typeorm';
|
||||||
|
|
||||||
const immich_machine_learning_url = process.env.IMMICH_MACHINE_LEARNING_URL || 'http://immich-machine-learning:3003';
|
const immich_machine_learning_url = process.env.IMMICH_MACHINE_LEARNING_URL || 'http://immich-machine-learning:3003';
|
||||||
|
|
||||||
@Processor(QueueNameEnum.MACHINE_LEARNING)
|
@Processor(QueueName.MACHINE_LEARNING)
|
||||||
export class MachineLearningProcessor {
|
export class MachineLearningProcessor {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(SmartInfoEntity)
|
@InjectRepository(SmartInfoEntity)
|
||||||
private smartInfoRepository: Repository<SmartInfoEntity>,
|
private smartInfoRepository: Repository<SmartInfoEntity>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Process({ name: MachineLearningJobNameEnum.IMAGE_TAGGING, concurrency: 2 })
|
@Process({ name: JobName.IMAGE_TAGGING, concurrency: 2 })
|
||||||
async tagImage(job: Job<IMachineLearningJob>) {
|
async tagImage(job: Job<IMachineLearningJob>) {
|
||||||
const { asset } = job.data;
|
const { asset } = job.data;
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ export class MachineLearningProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Process({ name: MachineLearningJobNameEnum.OBJECT_DETECTION, concurrency: 2 })
|
@Process({ name: JobName.OBJECT_DETECTION, concurrency: 2 })
|
||||||
async detectObject(job: Job<IMachineLearningJob>) {
|
async detectObject(job: Job<IMachineLearningJob>) {
|
||||||
try {
|
try {
|
||||||
const { asset }: { asset: AssetEntity } = job.data;
|
const { asset }: { asset: AssetEntity } = job.data;
|
||||||
|
|
|
@ -2,11 +2,9 @@ import { AssetEntity, ExifEntity } from '@app/infra';
|
||||||
import {
|
import {
|
||||||
IExifExtractionProcessor,
|
IExifExtractionProcessor,
|
||||||
IVideoLengthExtractionProcessor,
|
IVideoLengthExtractionProcessor,
|
||||||
exifExtractionProcessorName,
|
|
||||||
videoMetadataExtractionProcessorName,
|
|
||||||
reverseGeocodingProcessorName,
|
|
||||||
IReverseGeocodingProcessor,
|
IReverseGeocodingProcessor,
|
||||||
QueueNameEnum,
|
QueueName,
|
||||||
|
JobName,
|
||||||
} from '@app/job';
|
} from '@app/job';
|
||||||
import { Process, Processor } from '@nestjs/bull';
|
import { Process, Processor } from '@nestjs/bull';
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
|
@ -73,7 +71,7 @@ export type GeoData = {
|
||||||
distance: number;
|
distance: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Processor(QueueNameEnum.METADATA_EXTRACTION)
|
@Processor(QueueName.METADATA_EXTRACTION)
|
||||||
export class MetadataExtractionProcessor {
|
export class MetadataExtractionProcessor {
|
||||||
private logger = new Logger(MetadataExtractionProcessor.name);
|
private logger = new Logger(MetadataExtractionProcessor.name);
|
||||||
private isGeocodeInitialized = false;
|
private isGeocodeInitialized = false;
|
||||||
|
@ -140,7 +138,7 @@ export class MetadataExtractionProcessor {
|
||||||
return { country, state, city };
|
return { country, state, city };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Process(exifExtractionProcessorName)
|
@Process(JobName.EXIF_EXTRACTION)
|
||||||
async extractExifInfo(job: Job<IExifExtractionProcessor>) {
|
async extractExifInfo(job: Job<IExifExtractionProcessor>) {
|
||||||
try {
|
try {
|
||||||
const { asset, fileName }: { asset: AssetEntity; fileName: string } = job.data;
|
const { asset, fileName }: { asset: AssetEntity; fileName: string } = job.data;
|
||||||
|
@ -262,7 +260,7 @@ export class MetadataExtractionProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Process({ name: reverseGeocodingProcessorName })
|
@Process({ name: JobName.REVERSE_GEOCODING })
|
||||||
async reverseGeocoding(job: Job<IReverseGeocodingProcessor>) {
|
async reverseGeocoding(job: Job<IReverseGeocodingProcessor>) {
|
||||||
if (this.isGeocodeInitialized) {
|
if (this.isGeocodeInitialized) {
|
||||||
const { latitude, longitude } = job.data;
|
const { latitude, longitude } = job.data;
|
||||||
|
@ -271,7 +269,7 @@ export class MetadataExtractionProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Process({ name: videoMetadataExtractionProcessorName, concurrency: 2 })
|
@Process({ name: JobName.EXTRACT_VIDEO_METADATA, concurrency: 2 })
|
||||||
async extractVideoMetadata(job: Job<IVideoLengthExtractionProcessor>) {
|
async extractVideoMetadata(job: Job<IVideoLengthExtractionProcessor>) {
|
||||||
const { asset, fileName } = job.data;
|
const { asset, fileName } = job.data;
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { APP_UPLOAD_LOCATION } from '@app/common';
|
import { APP_UPLOAD_LOCATION } from '@app/common';
|
||||||
import { AssetEntity } from '@app/infra';
|
import { AssetEntity } from '@app/infra';
|
||||||
import { ImmichConfigService } from '@app/immich-config';
|
import { ImmichConfigService } from '@app/immich-config';
|
||||||
import { QueueNameEnum, templateMigrationProcessorName, updateTemplateProcessorName } from '@app/job';
|
import { QueueName, JobName } from '@app/job';
|
||||||
import { StorageService } from '@app/storage';
|
import { StorageService } from '@app/storage';
|
||||||
import { Process, Processor } from '@nestjs/bull';
|
import { Process, Processor } from '@nestjs/bull';
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
@Processor(QueueNameEnum.STORAGE_MIGRATION)
|
@Processor(QueueName.CONFIG)
|
||||||
export class StorageMigrationProcessor {
|
export class StorageMigrationProcessor {
|
||||||
readonly logger: Logger = new Logger(StorageMigrationProcessor.name);
|
readonly logger: Logger = new Logger(StorageMigrationProcessor.name);
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ export class StorageMigrationProcessor {
|
||||||
* Migration process when a new user set a new storage template.
|
* Migration process when a new user set a new storage template.
|
||||||
* @param job
|
* @param job
|
||||||
*/
|
*/
|
||||||
@Process({ name: templateMigrationProcessorName, concurrency: 100 })
|
@Process({ name: JobName.TEMPLATE_MIGRATION, concurrency: 100 })
|
||||||
async templateMigration() {
|
async templateMigration() {
|
||||||
console.time('migrating-time');
|
console.time('migrating-time');
|
||||||
const assets = await this.assetRepository.find({
|
const assets = await this.assetRepository.find({
|
||||||
|
@ -54,7 +54,7 @@ export class StorageMigrationProcessor {
|
||||||
* This is to ensure the synchronization between processes.
|
* This is to ensure the synchronization between processes.
|
||||||
* @param job
|
* @param job
|
||||||
*/
|
*/
|
||||||
@Process({ name: updateTemplateProcessorName, concurrency: 1 })
|
@Process({ name: JobName.CONFIG_CHANGE, concurrency: 1 })
|
||||||
async updateTemplate() {
|
async updateTemplate() {
|
||||||
await this.immichConfigService.refreshConfig();
|
await this.immichConfigService.refreshConfig();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,12 @@
|
||||||
import { APP_UPLOAD_LOCATION } from '@app/common';
|
import { APP_UPLOAD_LOCATION } from '@app/common';
|
||||||
import { AssetEntity, AssetType } from '@app/infra';
|
import { AssetEntity, AssetType } from '@app/infra';
|
||||||
import {
|
import { WebpGeneratorProcessor, JpegGeneratorProcessor, QueueName, JobName } from '@app/job';
|
||||||
WebpGeneratorProcessor,
|
|
||||||
generateJPEGThumbnailProcessorName,
|
|
||||||
generateWEBPThumbnailProcessorName,
|
|
||||||
JpegGeneratorProcessor,
|
|
||||||
QueueNameEnum,
|
|
||||||
MachineLearningJobNameEnum,
|
|
||||||
} from '@app/job';
|
|
||||||
import { InjectQueue, Process, Processor } from '@nestjs/bull';
|
import { InjectQueue, Process, Processor } from '@nestjs/bull';
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { mapAsset } from 'apps/immich/src/api-v1/asset/response-dto/asset-response.dto';
|
import { mapAsset } from 'apps/immich/src/api-v1/asset/response-dto/asset-response.dto';
|
||||||
import { Job, Queue } from 'bull';
|
import { Job, Queue } from 'bull';
|
||||||
import ffmpeg from 'fluent-ffmpeg';
|
import ffmpeg from 'fluent-ffmpeg';
|
||||||
import { randomUUID } from 'node:crypto';
|
|
||||||
import { existsSync, mkdirSync } from 'node:fs';
|
import { existsSync, mkdirSync } from 'node:fs';
|
||||||
import sanitize from 'sanitize-filename';
|
import sanitize from 'sanitize-filename';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
|
@ -23,7 +15,7 @@ import { join } from 'path';
|
||||||
import { CommunicationGateway } from 'apps/immich/src/api-v1/communication/communication.gateway';
|
import { CommunicationGateway } from 'apps/immich/src/api-v1/communication/communication.gateway';
|
||||||
import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface';
|
import { IMachineLearningJob } from '@app/job/interfaces/machine-learning.interface';
|
||||||
|
|
||||||
@Processor(QueueNameEnum.THUMBNAIL_GENERATION)
|
@Processor(QueueName.THUMBNAIL_GENERATION)
|
||||||
export class ThumbnailGeneratorProcessor {
|
export class ThumbnailGeneratorProcessor {
|
||||||
readonly logger: Logger = new Logger(ThumbnailGeneratorProcessor.name);
|
readonly logger: Logger = new Logger(ThumbnailGeneratorProcessor.name);
|
||||||
|
|
||||||
|
@ -31,16 +23,16 @@ export class ThumbnailGeneratorProcessor {
|
||||||
@InjectRepository(AssetEntity)
|
@InjectRepository(AssetEntity)
|
||||||
private assetRepository: Repository<AssetEntity>,
|
private assetRepository: Repository<AssetEntity>,
|
||||||
|
|
||||||
@InjectQueue(QueueNameEnum.THUMBNAIL_GENERATION)
|
@InjectQueue(QueueName.THUMBNAIL_GENERATION)
|
||||||
private thumbnailGeneratorQueue: Queue,
|
private thumbnailGeneratorQueue: Queue,
|
||||||
|
|
||||||
private wsCommunicationGateway: CommunicationGateway,
|
private wsCommunicationGateway: CommunicationGateway,
|
||||||
|
|
||||||
@InjectQueue(QueueNameEnum.MACHINE_LEARNING)
|
@InjectQueue(QueueName.MACHINE_LEARNING)
|
||||||
private machineLearningQueue: Queue<IMachineLearningJob>,
|
private machineLearningQueue: Queue<IMachineLearningJob>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Process({ name: generateJPEGThumbnailProcessorName, concurrency: 3 })
|
@Process({ name: JobName.GENERATE_JPEG_THUMBNAIL, concurrency: 3 })
|
||||||
async generateJPEGThumbnail(job: Job<JpegGeneratorProcessor>) {
|
async generateJPEGThumbnail(job: Job<JpegGeneratorProcessor>) {
|
||||||
const basePath = APP_UPLOAD_LOCATION;
|
const basePath = APP_UPLOAD_LOCATION;
|
||||||
|
|
||||||
|
@ -70,13 +62,10 @@ export class ThumbnailGeneratorProcessor {
|
||||||
// Update resize path to send to generate webp queue
|
// Update resize path to send to generate webp queue
|
||||||
asset.resizePath = jpegThumbnailPath;
|
asset.resizePath = jpegThumbnailPath;
|
||||||
|
|
||||||
await this.thumbnailGeneratorQueue.add(generateWEBPThumbnailProcessorName, { asset }, { jobId: randomUUID() });
|
await this.thumbnailGeneratorQueue.add(JobName.GENERATE_WEBP_THUMBNAIL, { asset });
|
||||||
await this.machineLearningQueue.add(MachineLearningJobNameEnum.IMAGE_TAGGING, { asset }, { jobId: randomUUID() });
|
await this.machineLearningQueue.add(JobName.IMAGE_TAGGING, { asset });
|
||||||
await this.machineLearningQueue.add(
|
await this.machineLearningQueue.add(JobName.OBJECT_DETECTION, { asset });
|
||||||
MachineLearningJobNameEnum.OBJECT_DETECTION,
|
|
||||||
{ asset },
|
|
||||||
{ jobId: randomUUID() },
|
|
||||||
);
|
|
||||||
this.wsCommunicationGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(mapAsset(asset)));
|
this.wsCommunicationGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(mapAsset(asset)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,19 +93,15 @@ export class ThumbnailGeneratorProcessor {
|
||||||
// Update resize path to send to generate webp queue
|
// Update resize path to send to generate webp queue
|
||||||
asset.resizePath = jpegThumbnailPath;
|
asset.resizePath = jpegThumbnailPath;
|
||||||
|
|
||||||
await this.thumbnailGeneratorQueue.add(generateWEBPThumbnailProcessorName, { asset }, { jobId: randomUUID() });
|
await this.thumbnailGeneratorQueue.add(JobName.GENERATE_WEBP_THUMBNAIL, { asset });
|
||||||
await this.machineLearningQueue.add(MachineLearningJobNameEnum.IMAGE_TAGGING, { asset }, { jobId: randomUUID() });
|
await this.machineLearningQueue.add(JobName.IMAGE_TAGGING, { asset });
|
||||||
await this.machineLearningQueue.add(
|
await this.machineLearningQueue.add(JobName.OBJECT_DETECTION, { asset });
|
||||||
MachineLearningJobNameEnum.OBJECT_DETECTION,
|
|
||||||
{ asset },
|
|
||||||
{ jobId: randomUUID() },
|
|
||||||
);
|
|
||||||
|
|
||||||
this.wsCommunicationGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(mapAsset(asset)));
|
this.wsCommunicationGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(mapAsset(asset)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Process({ name: generateWEBPThumbnailProcessorName, concurrency: 3 })
|
@Process({ name: JobName.GENERATE_WEBP_THUMBNAIL, concurrency: 3 })
|
||||||
async generateWepbThumbnail(job: Job<WebpGeneratorProcessor>) {
|
async generateWepbThumbnail(job: Job<WebpGeneratorProcessor>) {
|
||||||
const { asset } = job.data;
|
const { asset } = job.data;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { APP_UPLOAD_LOCATION, userUtils } from '@app/common';
|
import { APP_UPLOAD_LOCATION, userUtils } from '@app/common';
|
||||||
import { APIKeyEntity, AssetEntity, UserEntity } from '@app/infra';
|
import { APIKeyEntity, AssetEntity, UserEntity } from '@app/infra';
|
||||||
import { QueueNameEnum, userDeletionProcessorName } from '@app/job';
|
import { QueueName, JobName } from '@app/job';
|
||||||
import { IUserDeletionJob } from '@app/job/interfaces/user-deletion.interface';
|
import { IUserDeletionJob } from '@app/job/interfaces/user-deletion.interface';
|
||||||
import { Process, Processor } from '@nestjs/bull';
|
import { Process, Processor } from '@nestjs/bull';
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
|
@ -10,7 +10,7 @@ import { join } from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
@Processor(QueueNameEnum.USER_DELETION)
|
@Processor(QueueName.USER_DELETION)
|
||||||
export class UserDeletionProcessor {
|
export class UserDeletionProcessor {
|
||||||
private logger = new Logger(UserDeletionProcessor.name);
|
private logger = new Logger(UserDeletionProcessor.name);
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ export class UserDeletionProcessor {
|
||||||
private apiKeyRepository: Repository<APIKeyEntity>,
|
private apiKeyRepository: Repository<APIKeyEntity>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Process(userDeletionProcessorName)
|
@Process(JobName.USER_DELETION)
|
||||||
async processUserDeletion(job: Job<IUserDeletionJob>) {
|
async processUserDeletion(job: Job<IUserDeletionJob>) {
|
||||||
const { user } = job.data;
|
const { user } = job.data;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { APP_UPLOAD_LOCATION } from '@app/common/constants';
|
import { APP_UPLOAD_LOCATION } from '@app/common/constants';
|
||||||
import { AssetEntity } from '@app/infra';
|
import { AssetEntity } from '@app/infra';
|
||||||
import { QueueNameEnum } from '@app/job';
|
import { QueueName, JobName } from '@app/job';
|
||||||
import { mp4ConversionProcessorName } from '@app/job/constants/job-name.constant';
|
|
||||||
import { IMp4ConversionProcessor } from '@app/job/interfaces/video-transcode.interface';
|
import { IMp4ConversionProcessor } from '@app/job/interfaces/video-transcode.interface';
|
||||||
import { Process, Processor } from '@nestjs/bull';
|
import { Process, Processor } from '@nestjs/bull';
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
|
@ -12,7 +11,7 @@ import { existsSync, mkdirSync } from 'fs';
|
||||||
import { ImmichConfigService } from 'libs/immich-config/src';
|
import { ImmichConfigService } from 'libs/immich-config/src';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
@Processor(QueueNameEnum.VIDEO_CONVERSION)
|
@Processor(QueueName.VIDEO_CONVERSION)
|
||||||
export class VideoTranscodeProcessor {
|
export class VideoTranscodeProcessor {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(AssetEntity)
|
@InjectRepository(AssetEntity)
|
||||||
|
@ -20,7 +19,7 @@ export class VideoTranscodeProcessor {
|
||||||
private immichConfigService: ImmichConfigService,
|
private immichConfigService: ImmichConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Process({ name: mp4ConversionProcessorName, concurrency: 2 })
|
@Process({ name: JobName.MP4_CONVERSION, concurrency: 2 })
|
||||||
async mp4Conversion(job: Job<IMp4ConversionProcessor>) {
|
async mp4Conversion(job: Job<IMp4ConversionProcessor>) {
|
||||||
const { asset } = job.data;
|
const { asset } = job.data;
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,16 @@
|
||||||
import { BullModuleOptions } from '@nestjs/bull';
|
import { BullModuleOptions } from '@nestjs/bull';
|
||||||
import { QueueNameEnum } from './queue-name.constant';
|
import { QueueName } from './queue-name.constant';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared queues between apps and microservices
|
* Shared queues between apps and microservices
|
||||||
*/
|
*/
|
||||||
export const immichSharedQueues: BullModuleOptions[] = [
|
export const immichSharedQueues: BullModuleOptions[] = [
|
||||||
{
|
{ name: QueueName.USER_DELETION },
|
||||||
name: QueueNameEnum.USER_DELETION,
|
{ name: QueueName.THUMBNAIL_GENERATION },
|
||||||
},
|
{ name: QueueName.ASSET_UPLOADED },
|
||||||
{
|
{ name: QueueName.METADATA_EXTRACTION },
|
||||||
name: QueueNameEnum.THUMBNAIL_GENERATION,
|
{ name: QueueName.VIDEO_CONVERSION },
|
||||||
},
|
{ name: QueueName.CHECKSUM_GENERATION },
|
||||||
{
|
{ name: QueueName.MACHINE_LEARNING },
|
||||||
name: QueueNameEnum.ASSET_UPLOADED,
|
{ name: QueueName.CONFIG },
|
||||||
},
|
|
||||||
{
|
|
||||||
name: QueueNameEnum.METADATA_EXTRACTION,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: QueueNameEnum.VIDEO_CONVERSION,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: QueueNameEnum.CHECKSUM_GENERATION,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: QueueNameEnum.MACHINE_LEARNING,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: QueueNameEnum.STORAGE_MIGRATION,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,42 +1,15 @@
|
||||||
/**
|
export enum JobName {
|
||||||
* Asset Uploaded Queue Jobs
|
ASSET_UPLOADED = 'asset-uploaded',
|
||||||
*/
|
MP4_CONVERSION = 'mp4-conversion',
|
||||||
export const assetUploadedProcessorName = 'asset-uploaded';
|
GENERATE_JPEG_THUMBNAIL = 'generate-jpeg-thumbnail',
|
||||||
|
GENERATE_WEBP_THUMBNAIL = 'generate-webp-thumbnail',
|
||||||
/**
|
EXIF_EXTRACTION = 'exif-extraction',
|
||||||
* Video Conversion Queue Jobs
|
EXTRACT_VIDEO_METADATA = 'extract-video-metadata',
|
||||||
**/
|
REVERSE_GEOCODING = 'reverse-geocoding',
|
||||||
export const mp4ConversionProcessorName = 'mp4-conversion';
|
USER_DELETION = 'user-deletion',
|
||||||
|
TEMPLATE_MIGRATION = 'template-migration',
|
||||||
/**
|
CONFIG_CHANGE = 'config-change',
|
||||||
* Thumbnail Generator Queue Jobs
|
|
||||||
*/
|
|
||||||
export const generateJPEGThumbnailProcessorName = 'generate-jpeg-thumbnail';
|
|
||||||
export const generateWEBPThumbnailProcessorName = 'generate-webp-thumbnail';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Metadata Extraction Queue Jobs
|
|
||||||
*/
|
|
||||||
export const exifExtractionProcessorName = 'exif-extraction';
|
|
||||||
export const videoMetadataExtractionProcessorName = 'extract-video-metadata';
|
|
||||||
export const reverseGeocodingProcessorName = 'reverse-geocoding';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Machine learning Queue Jobs
|
|
||||||
*/
|
|
||||||
|
|
||||||
export enum MachineLearningJobNameEnum {
|
|
||||||
OBJECT_DETECTION = 'detect-object',
|
OBJECT_DETECTION = 'detect-object',
|
||||||
IMAGE_TAGGING = 'tag-image',
|
IMAGE_TAGGING = 'tag-image',
|
||||||
|
DELETE_FILE_ON_DISK = 'delete-file-on-disk',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* User deletion Queue Jobs
|
|
||||||
*/
|
|
||||||
export const userDeletionProcessorName = 'user-deletion';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Storage Template Migration Queue Jobs
|
|
||||||
*/
|
|
||||||
export const templateMigrationProcessorName = 'template-migration';
|
|
||||||
export const updateTemplateProcessorName = 'update-template';
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export enum QueueNameEnum {
|
export enum QueueName {
|
||||||
THUMBNAIL_GENERATION = 'thumbnail-generation-queue',
|
THUMBNAIL_GENERATION = 'thumbnail-generation-queue',
|
||||||
METADATA_EXTRACTION = 'metadata-extraction-queue',
|
METADATA_EXTRACTION = 'metadata-extraction-queue',
|
||||||
VIDEO_CONVERSION = 'video-conversion-queue',
|
VIDEO_CONVERSION = 'video-conversion-queue',
|
||||||
|
@ -6,5 +6,6 @@ export enum QueueNameEnum {
|
||||||
ASSET_UPLOADED = 'asset-uploaded-queue',
|
ASSET_UPLOADED = 'asset-uploaded-queue',
|
||||||
MACHINE_LEARNING = 'machine-learning-queue',
|
MACHINE_LEARNING = 'machine-learning-queue',
|
||||||
USER_DELETION = 'user-deletion-queue',
|
USER_DELETION = 'user-deletion-queue',
|
||||||
STORAGE_MIGRATION = 'storage-template-migration',
|
CONFIG = 'config-queue',
|
||||||
|
BACKGROUND_TASK = 'background-task',
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue