forked from Cutlery/immich
feat(server): generate checksum for previous uploaded assets (#558)
* feat(server): generate checksum for previous uploaded assets * fix(server): typo
This commit is contained in:
parent
0799aa2c72
commit
c76f7804ab
@ -6,6 +6,7 @@ import { SmartInfoEntity } from '@app/database/entities/smart-info.entity';
|
|||||||
import { UserEntity } from '@app/database/entities/user.entity';
|
import { UserEntity } from '@app/database/entities/user.entity';
|
||||||
import {
|
import {
|
||||||
assetUploadedQueueName,
|
assetUploadedQueueName,
|
||||||
|
generateChecksumQueueName,
|
||||||
metadataExtractionQueueName,
|
metadataExtractionQueueName,
|
||||||
thumbnailGeneratorQueueName,
|
thumbnailGeneratorQueueName,
|
||||||
videoConversionQueueName,
|
videoConversionQueueName,
|
||||||
@ -17,6 +18,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
|||||||
import { CommunicationModule } from '../../immich/src/api-v1/communication/communication.module';
|
import { CommunicationModule } from '../../immich/src/api-v1/communication/communication.module';
|
||||||
import { MicroservicesService } from './microservices.service';
|
import { MicroservicesService } from './microservices.service';
|
||||||
import { AssetUploadedProcessor } from './processors/asset-uploaded.processor';
|
import { AssetUploadedProcessor } from './processors/asset-uploaded.processor';
|
||||||
|
import { GenerateChecksumProcessor } from './processors/generate-checksum.processor';
|
||||||
import { MetadataExtractionProcessor } from './processors/metadata-extraction.processor';
|
import { MetadataExtractionProcessor } from './processors/metadata-extraction.processor';
|
||||||
import { ThumbnailGeneratorProcessor } from './processors/thumbnail.processor';
|
import { ThumbnailGeneratorProcessor } from './processors/thumbnail.processor';
|
||||||
import { VideoTranscodeProcessor } from './processors/video-transcode.processor';
|
import { VideoTranscodeProcessor } from './processors/video-transcode.processor';
|
||||||
@ -45,30 +47,34 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor'
|
|||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
removeOnFail: false,
|
removeOnFail: false,
|
||||||
},
|
},
|
||||||
}),
|
}, {
|
||||||
BullModule.registerQueue({
|
|
||||||
name: assetUploadedQueueName,
|
name: assetUploadedQueueName,
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
removeOnFail: false,
|
removeOnFail: false,
|
||||||
},
|
},
|
||||||
}),
|
}, {
|
||||||
BullModule.registerQueue({
|
|
||||||
name: metadataExtractionQueueName,
|
name: metadataExtractionQueueName,
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
removeOnFail: false,
|
removeOnFail: false,
|
||||||
},
|
},
|
||||||
}),
|
}, {
|
||||||
BullModule.registerQueue({
|
|
||||||
name: videoConversionQueueName,
|
name: videoConversionQueueName,
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
removeOnFail: false,
|
removeOnFail: false,
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
name: generateChecksumQueueName,
|
||||||
|
defaultJobOptions: {
|
||||||
|
attempts: 3,
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: false,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
CommunicationModule,
|
CommunicationModule,
|
||||||
],
|
],
|
||||||
@ -79,6 +85,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor'
|
|||||||
ThumbnailGeneratorProcessor,
|
ThumbnailGeneratorProcessor,
|
||||||
MetadataExtractionProcessor,
|
MetadataExtractionProcessor,
|
||||||
VideoTranscodeProcessor,
|
VideoTranscodeProcessor,
|
||||||
|
GenerateChecksumProcessor,
|
||||||
],
|
],
|
||||||
exports: [],
|
exports: [],
|
||||||
})
|
})
|
||||||
|
@ -1,8 +1,17 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { generateChecksumQueueName } from '@app/job';
|
||||||
|
import { InjectQueue } from '@nestjs/bull';
|
||||||
|
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||||
|
import { Queue } from 'bull';
|
||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MicroservicesService {
|
export class MicroservicesService implements OnModuleInit {
|
||||||
getHello(): string {
|
constructor (
|
||||||
return 'Hello World 123!';
|
@InjectQueue(generateChecksumQueueName)
|
||||||
|
private generateChecksumQueue: Queue,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async onModuleInit() {
|
||||||
|
await this.generateChecksumQueue.add({}, { jobId: randomUUID() },);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
import { AssetEntity } from '@app/database/entities/asset.entity';
|
||||||
|
import { generateChecksumQueueName } from '@app/job';
|
||||||
|
import { Process, Processor } from '@nestjs/bull';
|
||||||
|
import { Logger } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { createHash } from 'node:crypto';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import { IsNull, Repository } from 'typeorm';
|
||||||
|
|
||||||
|
// TODO: just temporary task to generate previous uploaded assets.
|
||||||
|
@Processor(generateChecksumQueueName)
|
||||||
|
export class GenerateChecksumProcessor {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(AssetEntity)
|
||||||
|
private assetRepository: Repository<AssetEntity>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Process()
|
||||||
|
async generateChecksum() {
|
||||||
|
let hasNext = true;
|
||||||
|
let pageSize = 200;
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
while (hasNext) {
|
||||||
|
const assets = await this.assetRepository.find({
|
||||||
|
where: {
|
||||||
|
checksum: IsNull()
|
||||||
|
},
|
||||||
|
skip: offset,
|
||||||
|
take: pageSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!assets?.length) {
|
||||||
|
hasNext = false; // avoid using break
|
||||||
|
} else {
|
||||||
|
for (const asset of assets) {
|
||||||
|
try {
|
||||||
|
await this.generateAssetChecksum(asset);
|
||||||
|
} catch (err: any) {
|
||||||
|
Logger.error(`Error generate checksum ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assets.length < pageSize) {
|
||||||
|
hasNext = false;
|
||||||
|
} else {
|
||||||
|
offset += pageSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async generateAssetChecksum(asset: AssetEntity) {
|
||||||
|
if (!asset.originalPath) return;
|
||||||
|
if (!fs.existsSync(asset.originalPath)) return;
|
||||||
|
|
||||||
|
const fileReadStream = fs.createReadStream(asset.originalPath);
|
||||||
|
const sha1Hash = createHash('sha1');
|
||||||
|
const deferred = new Promise<Buffer>((resolve, reject) => {
|
||||||
|
sha1Hash.once('error', (err) => reject(err));
|
||||||
|
sha1Hash.once('finish', () => resolve(sha1Hash.read()));
|
||||||
|
});
|
||||||
|
|
||||||
|
fileReadStream.pipe(sha1Hash);
|
||||||
|
const checksum = await deferred;
|
||||||
|
|
||||||
|
await this.assetRepository.update(asset.id, { checksum });
|
||||||
|
}
|
||||||
|
}
|
@ -2,3 +2,4 @@ export const thumbnailGeneratorQueueName = 'thumbnail-generator-queue';
|
|||||||
export const assetUploadedQueueName = 'asset-uploaded-queue';
|
export const assetUploadedQueueName = 'asset-uploaded-queue';
|
||||||
export const metadataExtractionQueueName = 'metadata-extraction-queue';
|
export const metadataExtractionQueueName = 'metadata-extraction-queue';
|
||||||
export const videoConversionQueueName = 'video-conversion-queue';
|
export const videoConversionQueueName = 'video-conversion-queue';
|
||||||
|
export const generateChecksumQueueName = 'generate-checksum-queue';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user