Browse Source

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
Thanh Pham 2 years ago
parent
commit
7f6837c751

+ 14 - 4
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<AssetFileUploadResponseDto> {
+    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}`);
     }
   }
 

+ 15 - 25
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<AssetEntity> {
-    const checksum = await this.calculateChecksum(originalPath);
-
-    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;
-      }
+    const assetEntity = await this._assetRepository.create(
+      createAssetDto,
+      authUser.id,
+      originalPath,
+      mimeType,
+      checksum,
+    );
 
-      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<Buffer> {
+  getAssetByChecksum(userId: string, checksum: Buffer) {
+    return this._assetRepository.getAssetByChecksum(userId, checksum);
+  }
+
+  calculateChecksum(filePath: string): Promise<Buffer> {
     const fileReadStream = createReadStream(filePath);
     const sha1Hash = createHash('sha1');
     const deferred = new Promise<Buffer>((resolve, reject) => {