mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:39:37 -05:00 
			
		
		
		
	feature(server): compute sha1 during upload (#1424)
* feature(server): compute sha1 during upload * fix: clean up stream on error
This commit is contained in:
		
							parent
							
								
									7e53e33e0f
								
							
						
					
					
						commit
						c4e1bc35b4
					
				@ -19,7 +19,7 @@ import {
 | 
				
			|||||||
import { Authenticated } from '../../decorators/authenticated.decorator';
 | 
					import { Authenticated } from '../../decorators/authenticated.decorator';
 | 
				
			||||||
import { AssetService } from './asset.service';
 | 
					import { AssetService } from './asset.service';
 | 
				
			||||||
import { FileFieldsInterceptor } from '@nestjs/platform-express';
 | 
					import { FileFieldsInterceptor } from '@nestjs/platform-express';
 | 
				
			||||||
import { assetUploadOption } from '../../config/asset-upload.config';
 | 
					import { assetUploadOption, ImmichFile } from '../../config/asset-upload.config';
 | 
				
			||||||
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
 | 
					import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
 | 
				
			||||||
import { ServeFileDto } from './dto/serve-file.dto';
 | 
					import { ServeFileDto } from './dto/serve-file.dto';
 | 
				
			||||||
import { Response as Res } from 'express';
 | 
					import { Response as Res } from 'express';
 | 
				
			||||||
@ -80,7 +80,7 @@ export class AssetController {
 | 
				
			|||||||
  })
 | 
					  })
 | 
				
			||||||
  async uploadFile(
 | 
					  async uploadFile(
 | 
				
			||||||
    @GetAuthUser() authUser: AuthUserDto,
 | 
					    @GetAuthUser() authUser: AuthUserDto,
 | 
				
			||||||
    @UploadedFiles() files: { assetData: Express.Multer.File[]; livePhotoData?: Express.Multer.File[] },
 | 
					    @UploadedFiles() files: { assetData: ImmichFile[]; livePhotoData?: ImmichFile[] },
 | 
				
			||||||
    @Body(ValidationPipe) createAssetDto: CreateAssetDto,
 | 
					    @Body(ValidationPipe) createAssetDto: CreateAssetDto,
 | 
				
			||||||
    @Response({ passthrough: true }) res: Res,
 | 
					    @Response({ passthrough: true }) res: Res,
 | 
				
			||||||
  ): Promise<AssetFileUploadResponseDto> {
 | 
					  ): Promise<AssetFileUploadResponseDto> {
 | 
				
			||||||
 | 
				
			|||||||
@ -55,6 +55,7 @@ import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
 | 
				
			|||||||
import { mapSharedLink, SharedLinkResponseDto } from '@app/domain';
 | 
					import { mapSharedLink, SharedLinkResponseDto } from '@app/domain';
 | 
				
			||||||
import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto';
 | 
					import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto';
 | 
				
			||||||
import { AssetSearchDto } from './dto/asset-search.dto';
 | 
					import { AssetSearchDto } from './dto/asset-search.dto';
 | 
				
			||||||
 | 
					import { ImmichFile } from '../../config/asset-upload.config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fileInfo = promisify(stat);
 | 
					const fileInfo = promisify(stat);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -82,16 +83,16 @@ export class AssetService {
 | 
				
			|||||||
    authUser: AuthUserDto,
 | 
					    authUser: AuthUserDto,
 | 
				
			||||||
    createAssetDto: CreateAssetDto,
 | 
					    createAssetDto: CreateAssetDto,
 | 
				
			||||||
    res: Res,
 | 
					    res: Res,
 | 
				
			||||||
    originalAssetData: Express.Multer.File,
 | 
					    originalAssetData: ImmichFile,
 | 
				
			||||||
    livePhotoAssetData?: Express.Multer.File,
 | 
					    livePhotoAssetData?: ImmichFile,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    const checksum = await this.calculateChecksum(originalAssetData.path);
 | 
					    const checksum = originalAssetData.checksum;
 | 
				
			||||||
    const isLivePhoto = livePhotoAssetData !== undefined;
 | 
					    const isLivePhoto = livePhotoAssetData !== undefined;
 | 
				
			||||||
    let livePhotoAssetEntity: AssetEntity | undefined;
 | 
					    let livePhotoAssetEntity: AssetEntity | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      if (isLivePhoto) {
 | 
					      if (isLivePhoto) {
 | 
				
			||||||
        const livePhotoChecksum = await this.calculateChecksum(livePhotoAssetData.path);
 | 
					        const livePhotoChecksum = livePhotoAssetData.checksum;
 | 
				
			||||||
        livePhotoAssetEntity = await this.createUserAsset(
 | 
					        livePhotoAssetEntity = await this.createUserAsset(
 | 
				
			||||||
          authUser,
 | 
					          authUser,
 | 
				
			||||||
          createAssetDto,
 | 
					          createAssetDto,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,10 @@
 | 
				
			|||||||
import { APP_UPLOAD_LOCATION } from '@app/common/constants';
 | 
					import { APP_UPLOAD_LOCATION } from '@app/common/constants';
 | 
				
			||||||
import { BadRequestException, Logger, UnauthorizedException } from '@nestjs/common';
 | 
					import { BadRequestException, Logger, UnauthorizedException } from '@nestjs/common';
 | 
				
			||||||
import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
 | 
					import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
 | 
				
			||||||
import { randomUUID } from 'crypto';
 | 
					import { createHash, randomUUID } from 'crypto';
 | 
				
			||||||
import { Request } from 'express';
 | 
					import { Request } from 'express';
 | 
				
			||||||
import { existsSync, mkdirSync } from 'fs';
 | 
					import { existsSync, mkdirSync } from 'fs';
 | 
				
			||||||
import { diskStorage } from 'multer';
 | 
					import { diskStorage, StorageEngine } from 'multer';
 | 
				
			||||||
import { extname, join } from 'path';
 | 
					import { extname, join } from 'path';
 | 
				
			||||||
import sanitize from 'sanitize-filename';
 | 
					import sanitize from 'sanitize-filename';
 | 
				
			||||||
import { AuthUserDto } from '../decorators/auth-user.decorator';
 | 
					import { AuthUserDto } from '../decorators/auth-user.decorator';
 | 
				
			||||||
@ -12,14 +12,40 @@ import { patchFormData } from '../utils/path-form-data.util';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const logger = new Logger('AssetUploadConfig');
 | 
					const logger = new Logger('AssetUploadConfig');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ImmichFile extends Express.Multer.File {
 | 
				
			||||||
 | 
					  /** sha1 hash of file */
 | 
				
			||||||
 | 
					  checksum: Buffer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const assetUploadOption: MulterOptions = {
 | 
					export const assetUploadOption: MulterOptions = {
 | 
				
			||||||
  fileFilter,
 | 
					  fileFilter,
 | 
				
			||||||
  storage: diskStorage({
 | 
					  storage: customStorage(),
 | 
				
			||||||
    destination,
 | 
					 | 
				
			||||||
    filename,
 | 
					 | 
				
			||||||
  }),
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function customStorage(): StorageEngine {
 | 
				
			||||||
 | 
					  const storage = diskStorage({ destination, filename });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    _handleFile(req, file, callback) {
 | 
				
			||||||
 | 
					      const hash = createHash('sha1');
 | 
				
			||||||
 | 
					      file.stream.on('data', (chunk) => hash.update(chunk));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      storage._handleFile(req, file, (error, response) => {
 | 
				
			||||||
 | 
					        if (error) {
 | 
				
			||||||
 | 
					          hash.destroy();
 | 
				
			||||||
 | 
					          callback(error);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          callback(null, { ...response, checksum: hash.digest() } as ImmichFile);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _removeFile(req, file, callback) {
 | 
				
			||||||
 | 
					      storage._removeFile(req, file, callback);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const multerUtils = { fileFilter, filename, destination };
 | 
					export const multerUtils = { fileFilter, filename, destination };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function fileFilter(req: Request, file: any, cb: any) {
 | 
					function fileFilter(req: Request, file: any, cb: any) {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user