1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-03-01 15:11:21 +01: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:
Jason Rasmussen 2023-01-26 14:29:44 -05:00 committed by GitHub
parent 7e53e33e0f
commit c4e1bc35b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 39 additions and 12 deletions

View file

@ -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> {

View file

@ -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,

View file

@ -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) {