From 7f6837c751ed7c35a28c6d3a4fac7e3922ed4061 Mon Sep 17 00:00:00 2001 From: Thanh Pham Date: Tue, 6 Sep 2022 08:02:50 +0700 Subject: [PATCH] hotfix(server): skip exif extraction on duplicate file (#590) * fix(server): skip exif extraction on duplicate file * fix(server): typo * chore(server): remvoe un-use code --- .../src/api-v1/asset/asset.controller.ts | 18 +++++++-- .../immich/src/api-v1/asset/asset.service.ts | 40 +++++++------------ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/server/apps/immich/src/api-v1/asset/asset.controller.ts b/server/apps/immich/src/api-v1/asset/asset.controller.ts index cdf73a819c..0df6c3093e 100644 --- a/server/apps/immich/src/api-v1/asset/asset.controller.ts +++ b/server/apps/immich/src/api-v1/asset/asset.controller.ts @@ -47,6 +47,7 @@ import { GetAssetThumbnailDto } from './dto/get-asset-thumbnail.dto'; import { AssetCountByTimeBucketResponseDto } from './response-dto/asset-count-by-time-group-response.dto'; import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-bucket.dto'; import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto'; +import { QueryFailedError } from 'typeorm'; @UseGuards(JwtAuthGuard) @ApiBearerAuth() @@ -74,8 +75,11 @@ export class AssetController { @UploadedFile() file: Express.Multer.File, @Body(ValidationPipe) assetInfo: CreateAssetDto, ): Promise { + const checksum = await this.assetService.calculateChecksum(file.path); + try { - const savedAsset = await this.assetService.createUserAsset(authUser, assetInfo, file.path, file.mimetype); + const savedAsset = await this.assetService.createUserAsset(authUser, assetInfo, file.path, file.mimetype, checksum); + if (!savedAsset) { await this.backgroundTaskService.deleteFileOnDisk([ { @@ -92,14 +96,20 @@ export class AssetController { ); return new AssetFileUploadResponseDto(savedAsset.id); - } catch (e) { - Logger.error(`Error uploading file ${e}`); + } catch (err) { await this.backgroundTaskService.deleteFileOnDisk([ { originalPath: file.path, } as any, ]); // simulate asset to make use of delete queue (or use fs.unlink instead) - throw new BadRequestException(`Error uploading file`, `${e}`); + + if (err instanceof QueryFailedError && (err as any).constraint === 'UQ_userid_checksum') { + const existedAsset = await this.assetService.getAssetByChecksum(authUser.id, checksum) + return new AssetFileUploadResponseDto(existedAsset.id); + } + + Logger.error(`Error uploading file ${err}`); + throw new BadRequestException(`Error uploading file`, `${err}`); } } diff --git a/server/apps/immich/src/api-v1/asset/asset.service.ts b/server/apps/immich/src/api-v1/asset/asset.service.ts index 7d06a9ba47..baff968d93 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.ts @@ -10,7 +10,7 @@ import { } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { createHash } from 'node:crypto'; -import { QueryFailedError, Repository } from 'typeorm'; +import { Repository } from 'typeorm'; import { AuthUserDto } from '../../decorators/auth-user.decorator'; import { AssetEntity, AssetType } from '@app/database/entities/asset.entity'; import { constants, createReadStream, ReadStream, stat } from 'fs'; @@ -53,31 +53,17 @@ export class AssetService { createAssetDto: CreateAssetDto, originalPath: string, mimeType: string, + checksum: Buffer, ): Promise { - const checksum = await this.calculateChecksum(originalPath); + const assetEntity = await this._assetRepository.create( + createAssetDto, + authUser.id, + originalPath, + mimeType, + checksum, + ); - try { - const assetEntity = await this._assetRepository.create( - createAssetDto, - authUser.id, - originalPath, - mimeType, - checksum, - ); - - return assetEntity; - } catch (err) { - if (err instanceof QueryFailedError && (err as any).constraint === 'UQ_userid_checksum') { - const [assetEntity, _] = await Promise.all([ - this._assetRepository.getAssetByChecksum(authUser.id, checksum), - fs.unlink(originalPath) - ]); - - return assetEntity; - } - - throw err; - } + return assetEntity; } public async getUserAssetsByDeviceId(authUser: AuthUserDto, deviceId: string) { @@ -478,7 +464,11 @@ export class AssetService { return mapAssetCountByTimeBucket(result); } - private calculateChecksum(filePath: string): Promise { + getAssetByChecksum(userId: string, checksum: Buffer) { + return this._assetRepository.getAssetByChecksum(userId, checksum); + } + + calculateChecksum(filePath: string): Promise { const fileReadStream = createReadStream(filePath); const sha1Hash = createHash('sha1'); const deferred = new Promise((resolve, reject) => {