diff --git a/src/services/upload/fileService.ts b/src/services/upload/fileService.ts index 939e769e65..815df69643 100644 --- a/src/services/upload/fileService.ts +++ b/src/services/upload/fileService.ts @@ -69,9 +69,9 @@ export async function readFile( export async function extractFileMetadata( parsedMetadataJSONMap: ParsedMetadataJSONMap, - rawFile: File | ElectronFile, collectionID: number, - fileTypeInfo: FileTypeInfo + fileTypeInfo: FileTypeInfo, + rawFile: File | ElectronFile ) { const originalName = getFileOriginalName(rawFile); const googleMetadata = diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index 1b2000cc1b..d6dcdb8891 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -1,20 +1,20 @@ import { FILE_TYPE } from 'constants/file'; import { LIVE_PHOTO_ASSET_SIZE_LIMIT } from 'constants/upload'; import { encodeMotionPhoto } from 'services/motionPhotoService'; +import { getFileType } from 'services/typeDetectionService'; import { ElectronFile, FileTypeInfo, FileWithCollection, LivePhotoAssets, - Metadata, + ParsedMetadataJSONMap, } from 'types/upload'; import { CustomError } from 'utils/error'; import { isImageOrVideo, splitFilenameAndExtension } from 'utils/file'; import { logError } from 'utils/sentry'; import { getUint8ArrayView } from '../readerService'; +import { extractFileMetadata } from './fileService'; import { generateThumbnail } from './thumbnailService'; -import uploadService from './uploadService'; -import UploadService from './uploadService'; interface LivePhotoIdentifier { collectionID: number; @@ -23,37 +23,54 @@ interface LivePhotoIdentifier { size: number; } -interface Asset { - file: File | ElectronFile; - metadata: Metadata; - fileTypeInfo: FileTypeInfo; -} - const ENTE_LIVE_PHOTO_FORMAT = 'elp'; const UNDERSCORE_THREE = '_3'; const UNDERSCORE = '_'; -export function getLivePhotoFileType( - imageFileTypeInfo: FileTypeInfo, - videoTypeInfo: FileTypeInfo -): FileTypeInfo { +export async function getLivePhotoFileType( + livePhotoAssets: LivePhotoAssets +): Promise { + const imageFileTypeInfo = await getFileType(livePhotoAssets.image); + const videoFileTypeInfo = await getFileType(livePhotoAssets.video); return { fileType: FILE_TYPE.LIVE_PHOTO, - exactType: `${imageFileTypeInfo.exactType}+${videoTypeInfo.exactType}`, + exactType: `${imageFileTypeInfo.exactType}+${videoFileTypeInfo.exactType}`, imageType: imageFileTypeInfo.exactType, - videoType: videoTypeInfo.exactType, + videoType: videoFileTypeInfo.exactType, }; } -export function getLivePhotoMetadata( - imageMetadata: Metadata, - videoMetadata: Metadata +export async function extractLivePhotoMetadata( + parsedMetadataJSONMap: ParsedMetadataJSONMap, + collectionID: number, + fileTypeInfo: FileTypeInfo, + livePhotoAssets: LivePhotoAssets ) { + const imageFileTypeInfo: FileTypeInfo = { + fileType: FILE_TYPE.IMAGE, + exactType: fileTypeInfo.imageType, + }; + const videoFileTypeInfo: FileTypeInfo = { + fileType: FILE_TYPE.VIDEO, + exactType: fileTypeInfo.videoType, + }; + const imageMetadata = await extractFileMetadata( + parsedMetadataJSONMap, + collectionID, + imageFileTypeInfo, + livePhotoAssets.image + ); + const videoMetadata = await extractFileMetadata( + parsedMetadataJSONMap, + collectionID, + videoFileTypeInfo, + livePhotoAssets.video + ); return { ...imageMetadata, - title: getLivePhotoName(imageMetadata.title), + title: getLivePhotoName(livePhotoAssets), fileType: FILE_TYPE.LIVE_PHOTO, imageHash: imageMetadata.hash, videoHash: videoMetadata.hash, @@ -61,17 +78,13 @@ export function getLivePhotoMetadata( }; } -export function getLivePhotoFilePath(imageAsset: Asset): string { - return getLivePhotoName((imageAsset.file as any).path); -} - export function getLivePhotoSize(livePhotoAssets: LivePhotoAssets) { return livePhotoAssets.image.size + livePhotoAssets.video.size; } -export function getLivePhotoName(imageTitle: string) { +export function getLivePhotoName(livePhotoAssets: LivePhotoAssets) { return `${ - splitFilenameAndExtension(imageTitle)[0] + splitFilenameAndExtension(livePhotoAssets.image.name)[0] }.${ENTE_LIVE_PHOTO_FORMAT}`; } @@ -103,7 +116,7 @@ export async function readLivePhoto( }; } -export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { +export async function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { try { const analysedMediaFiles: FileWithCollection[] = []; mediaFiles @@ -122,18 +135,8 @@ export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { while (index < mediaFiles.length - 1) { const firstMediaFile = mediaFiles[index]; const secondMediaFile = mediaFiles[index + 1]; - const { - fileTypeInfo: firstFileTypeInfo, - metadata: firstFileMetadata, - } = UploadService.getFileMetadataAndFileTypeInfo( - firstMediaFile.localID - ); - const { - fileTypeInfo: secondFileFileInfo, - metadata: secondFileMetadata, - } = UploadService.getFileMetadataAndFileTypeInfo( - secondMediaFile.localID - ); + const firstFileTypeInfo = await getFileType(firstMediaFile.file); + const secondFileFileInfo = await getFileType(secondMediaFile.file); const firstFileIdentifier: LivePhotoIdentifier = { collectionID: firstMediaFile.collectionID, fileType: firstFileTypeInfo.fileType, @@ -146,33 +149,23 @@ export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { name: secondMediaFile.file.name, size: secondMediaFile.file.size, }; - const firstAsset = { - file: firstMediaFile.file, - metadata: firstFileMetadata, - fileTypeInfo: firstFileTypeInfo, - }; - const secondAsset = { - file: secondMediaFile.file, - metadata: secondFileMetadata, - fileTypeInfo: secondFileFileInfo, - }; if ( areFilesLivePhotoAssets( firstFileIdentifier, secondFileIdentifier ) ) { - let imageAsset: Asset; - let videoAsset: Asset; + let imageFile: File | ElectronFile; + let videoFile: File | ElectronFile; if ( firstFileTypeInfo.fileType === FILE_TYPE.IMAGE && secondFileFileInfo.fileType === FILE_TYPE.VIDEO ) { - imageAsset = firstAsset; - videoAsset = secondAsset; + imageFile = firstMediaFile.file; + videoFile = secondMediaFile.file; } else { - videoAsset = firstAsset; - imageAsset = secondAsset; + videoFile = firstMediaFile.file; + imageFile = secondMediaFile.file; } const livePhotoLocalID = firstMediaFile.localID; analysedMediaFiles.push({ @@ -180,25 +173,10 @@ export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { collectionID: firstMediaFile.collectionID, isLivePhoto: true, livePhotoAssets: { - image: imageAsset.file, - video: videoAsset.file, + image: imageFile, + video: videoFile, }, }); - const livePhotoFileTypeInfo: FileTypeInfo = - getLivePhotoFileType( - imageAsset.fileTypeInfo, - videoAsset.fileTypeInfo - ); - const livePhotoMetadata: Metadata = getLivePhotoMetadata( - imageAsset.metadata, - videoAsset.metadata - ); - const livePhotoPath = getLivePhotoFilePath(imageAsset); - uploadService.setFileMetadataAndFileTypeInfo(livePhotoLocalID, { - fileTypeInfo: { ...livePhotoFileTypeInfo }, - metadata: { ...livePhotoMetadata }, - filePath: livePhotoPath, - }); index += 2; } else { analysedMediaFiles.push({ diff --git a/src/services/upload/uploadManager.ts b/src/services/upload/uploadManager.ts index 3e57aea30b..900169aa62 100644 --- a/src/services/upload/uploadManager.ts +++ b/src/services/upload/uploadManager.ts @@ -20,22 +20,13 @@ import { CustomError } from 'utils/error'; import { Collection } from 'types/collection'; import { EnteFile } from 'types/file'; import { - ElectronFile, FileWithCollection, - Metadata, - MetadataAndFileTypeInfo, - MetadataAndFileTypeInfoMap, ParsedMetadataJSON, ParsedMetadataJSONMap, PublicUploadProps, } from 'types/upload'; -import { - UPLOAD_RESULT, - MAX_FILE_SIZE_SUPPORTED, - UPLOAD_STAGES, -} from 'constants/upload'; +import { UPLOAD_RESULT, UPLOAD_STAGES } from 'constants/upload'; import { ComlinkWorker } from 'utils/comlink'; -import { FILE_TYPE } from 'constants/file'; import uiService from './uiService'; import { addLogLine, getFileNameSize } from 'utils/logging'; import isElectron from 'is-electron'; @@ -54,7 +45,6 @@ const FILE_UPLOAD_COMPLETED = 100; class UploadManager { private cryptoWorkers = new Array(MAX_CONCURRENT_UPLOADS); private parsedMetadataJSONMap: ParsedMetadataJSONMap; - private metadataAndFileTypeInfoMap: MetadataAndFileTypeInfoMap; private filesToBeUploaded: FileWithCollection[]; private remainingFiles: FileWithCollection[] = []; private failedFiles: FileWithCollection[]; @@ -85,10 +75,7 @@ class UploadManager { this.remainingFiles = []; this.failedFiles = []; this.parsedMetadataJSONMap = new Map(); - this.metadataAndFileTypeInfoMap = new Map< - number, - MetadataAndFileTypeInfo - >(); + this.uploaderName = null; } @@ -154,50 +141,18 @@ class UploadManager { ); } if (mediaFiles.length) { - UIService.setUploadStage(UPLOAD_STAGES.EXTRACTING_METADATA); - await this.extractMetadataFromFiles(mediaFiles); - - UploadService.setMetadataAndFileTypeInfoMap( - this.metadataAndFileTypeInfoMap - ); - - // filter out files whose metadata detection failed or those that have been skipped because the files are too large, - // as they will be rejected during upload and are not valid upload files which we need to clustering - const rejectedFileLocalIDs = new Set( - [...this.metadataAndFileTypeInfoMap.entries()].map( - ([localID, metadataAndFileTypeInfo]) => { - if ( - !metadataAndFileTypeInfo.metadata || - !metadataAndFileTypeInfo.fileTypeInfo - ) { - return localID; - } - } - ) - ); - const rejectedFiles = []; - const filesWithMetadata = []; - mediaFiles.forEach((m) => { - if (rejectedFileLocalIDs.has(m.localID)) { - rejectedFiles.push(m); - } else { - filesWithMetadata.push(m); - } - }); - addLogLine(`clusterLivePhotoFiles started`); - const analysedMediaFiles = - UploadService.clusterLivePhotoFiles(filesWithMetadata); - + await UploadService.clusterLivePhotoFiles(mediaFiles); addLogLine(`clusterLivePhotoFiles ended`); - const allFiles = [...rejectedFiles, ...analysedMediaFiles]; addLogLine( - `got live photos: ${mediaFiles.length !== allFiles.length}` + `got live photos: ${ + mediaFiles.length !== analysedMediaFiles.length + }` ); uiService.setFilenames( new Map( - allFiles.map((mediaFile) => [ + analysedMediaFiles.map((mediaFile) => [ mediaFile.localID, UploadService.getAssetName(mediaFile), ]) @@ -205,10 +160,10 @@ class UploadManager { ); UIService.setHasLivePhoto( - mediaFiles.length !== allFiles.length + mediaFiles.length !== analysedMediaFiles.length ); - await this.uploadMediaFiles(allFiles); + await this.uploadMediaFiles(analysedMediaFiles); } } catch (e) { if (e.message === CustomError.UPLOAD_CANCELLED) { @@ -293,104 +248,6 @@ class UploadManager { } } - private async extractMetadataFromFiles(mediaFiles: FileWithCollection[]) { - try { - addLogLine(`extractMetadataFromFiles executed`); - UIService.reset(mediaFiles.length); - for (const { file, localID, collectionID } of mediaFiles) { - UIService.setFileProgress(localID, 0); - if (uploadCancelService.isUploadCancelationRequested()) { - throw Error(CustomError.UPLOAD_CANCELLED); - } - let fileTypeInfo = null; - let metadata = null; - let filePath = null; - try { - addLogLine( - `metadata extraction started ${getFileNameSize(file)} ` - ); - const result = await this.extractFileTypeAndMetadata( - file, - collectionID, - this.publicUploadProps?.accessedThroughSharedURL - ); - fileTypeInfo = result.fileTypeInfo; - metadata = result.metadata; - filePath = result.filePath; - addLogLine( - `metadata extraction successful${getFileNameSize( - file - )} ` - ); - } catch (e) { - if (e.message === CustomError.UPLOAD_CANCELLED) { - throw e; - } else { - // and don't break for subsequent files just log and move on - logError(e, 'extractFileTypeAndMetadata failed'); - addLogLine( - `metadata extraction failed ${getFileNameSize( - file - )} error: ${e.message}` - ); - } - } - this.metadataAndFileTypeInfoMap.set(localID, { - fileTypeInfo: fileTypeInfo && { ...fileTypeInfo }, - metadata: metadata && { ...metadata }, - filePath: filePath, - }); - UIService.removeFromInProgressList(localID); - UIService.increaseFileUploaded(); - } - } catch (e) { - if (e.message !== CustomError.UPLOAD_CANCELLED) { - logError(e, 'error extracting metadata'); - } - throw e; - } - } - - private async extractFileTypeAndMetadata( - file: File | ElectronFile, - collectionID: number, - skipVideos: boolean - ) { - if (file.size >= MAX_FILE_SIZE_SUPPORTED) { - addLogLine( - `${getFileNameSize(file)} rejected because of large size` - ); - - return { fileTypeInfo: null, metadata: null }; - } - const fileTypeInfo = await UploadService.getFileType(file); - if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) { - addLogLine( - `${getFileNameSize( - file - )} rejected because of unknown file format` - ); - return { fileTypeInfo, metadata: null }; - } - if (skipVideos && fileTypeInfo.fileType === FILE_TYPE.VIDEO) { - return { fileTypeInfo, metadata: null }; - } - addLogLine(` extracting ${getFileNameSize(file)} metadata`); - let metadata: Metadata; - try { - metadata = await UploadService.extractFileMetadata( - file, - collectionID, - fileTypeInfo - ); - const filePath = (file as any).path as string; - return { fileTypeInfo, metadata, filePath }; - } catch (e) { - logError(e, 'failed to extract file metadata'); - return { fileTypeInfo, metadata: null, filePath: null }; - } - } - private async uploadMediaFiles(mediaFiles: FileWithCollection[]) { addLogLine(`uploadMediaFiles called`); this.filesToBeUploaded = [...this.filesToBeUploaded, ...mediaFiles]; diff --git a/src/services/upload/uploadService.ts b/src/services/upload/uploadService.ts index 2d852b3664..f9c61a95b1 100644 --- a/src/services/upload/uploadService.ts +++ b/src/services/upload/uploadService.ts @@ -7,15 +7,12 @@ import { CustomError, handleUploadError } from 'utils/error'; import { B64EncryptionResult, BackupedFile, - ElectronFile, EncryptedFile, FileTypeInfo, FileWithCollection, FileWithMetadata, isDataStream, Metadata, - MetadataAndFileTypeInfo, - MetadataAndFileTypeInfoMap, ParsedMetadataJSON, ParsedMetadataJSONMap, ProcessedFile, @@ -26,6 +23,8 @@ import { } from 'types/upload'; import { clusterLivePhotoFiles, + extractLivePhotoMetadata, + getLivePhotoFileType, getLivePhotoName, getLivePhotoSize, readLivePhoto, @@ -42,10 +41,6 @@ class UploadService { string, ParsedMetadataJSON >(); - private metadataAndFileTypeInfoMap: MetadataAndFileTypeInfoMap = new Map< - number, - MetadataAndFileTypeInfo - >(); private uploaderName: string; @@ -74,12 +69,6 @@ class UploadService { return this.uploaderName; } - setMetadataAndFileTypeInfoMap( - metadataAndFileTypeInfoMap: MetadataAndFileTypeInfoMap - ) { - this.metadataAndFileTypeInfoMap = metadataAndFileTypeInfoMap; - } - reducePendingUploadCount() { this.pendingUploadCount--; } @@ -90,14 +79,16 @@ class UploadService { : getFileSize(file); } - getAssetName({ isLivePhoto, file, livePhotoAssets }: FileWithCollection) { + getAssetName({ isLivePhoto, file, livePhotoAssets }: UploadAsset) { return isLivePhoto - ? getLivePhotoName(livePhotoAssets.image.name) + ? getLivePhotoName(livePhotoAssets) : getFilename(file); } - async getFileType(file: File | ElectronFile) { - return getFileType(file); + getAssetFileType({ isLivePhoto, file, livePhotoAssets }: UploadAsset) { + return isLivePhoto + ? getLivePhotoFileType(livePhotoAssets) + : getFileType(file); } async readAsset( @@ -109,31 +100,24 @@ class UploadService { : await readFile(fileTypeInfo, file); } - async extractFileMetadata( - file: File | ElectronFile, + async extractAssetMetadata( + { isLivePhoto, file, livePhotoAssets }: UploadAsset, collectionID: number, fileTypeInfo: FileTypeInfo ): Promise { - return extractFileMetadata( - this.parsedMetadataJSONMap, - file, - collectionID, - fileTypeInfo - ); - } - - getFileMetadataAndFileTypeInfo(localID: number) { - return this.metadataAndFileTypeInfoMap.get(localID); - } - - setFileMetadataAndFileTypeInfo( - localID: number, - metadataAndFileTypeInfo: MetadataAndFileTypeInfo - ) { - return this.metadataAndFileTypeInfoMap.set( - localID, - metadataAndFileTypeInfo - ); + return isLivePhoto + ? await extractFileMetadata( + this.parsedMetadataJSONMap, + collectionID, + fileTypeInfo, + file + ) + : extractLivePhotoMetadata( + this.parsedMetadataJSONMap, + collectionID, + fileTypeInfo, + livePhotoAssets + ); } clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { diff --git a/src/services/upload/uploader.ts b/src/services/upload/uploader.ts index 746674c883..af0016173b 100644 --- a/src/services/upload/uploader.ts +++ b/src/services/upload/uploader.ts @@ -6,7 +6,12 @@ import UIService from './uiService'; import UploadService from './uploadService'; import { FILE_TYPE } from 'constants/file'; import { UPLOAD_RESULT, MAX_FILE_SIZE_SUPPORTED } from 'constants/upload'; -import { FileWithCollection, BackupedFile, UploadFile } from 'types/upload'; +import { + FileWithCollection, + BackupedFile, + UploadFile, + FileTypeInfo, +} from 'types/upload'; import { addLocalLog, addLogLine } from 'utils/logging'; import { convertBytesToHumanReadable } from 'utils/file/size'; import { sleep } from 'utils/common'; @@ -33,22 +38,24 @@ export default async function uploader( addLogLine(`uploader called for ${fileNameSize}`); UIService.setFileProgress(localID, 0); await sleep(0); - const { fileTypeInfo, metadata } = - UploadService.getFileMetadataAndFileTypeInfo(localID); + let fileTypeInfo: FileTypeInfo; try { const fileSize = UploadService.getAssetSize(uploadAsset); if (fileSize >= MAX_FILE_SIZE_SUPPORTED) { return { fileUploadResult: UPLOAD_RESULT.TOO_LARGE }; } + fileTypeInfo = await UploadService.getAssetFileType(uploadAsset); if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) { throw Error(CustomError.UNSUPPORTED_FILE_FORMAT); } if (skipVideos && fileTypeInfo.fileType === FILE_TYPE.VIDEO) { return { fileUploadResult: UPLOAD_RESULT.SKIPPED_VIDEOS }; } - if (!metadata) { - throw Error(CustomError.NO_METADATA); - } + const metadata = await UploadService.extractAssetMetadata( + uploadAsset, + collection.id, + fileTypeInfo + ); const matchingExistingFiles = findMatchingExistingFiles( existingFiles, diff --git a/src/types/upload/index.ts b/src/types/upload/index.ts index aa56d50c48..ddb034eeac 100644 --- a/src/types/upload/index.ts +++ b/src/types/upload/index.ts @@ -90,13 +90,7 @@ export interface FileWithCollection extends UploadAsset { collection?: Collection; collectionID?: number; } -export interface MetadataAndFileTypeInfo { - metadata: Metadata; - fileTypeInfo: FileTypeInfo; - filePath: string; -} -export type MetadataAndFileTypeInfoMap = Map; export type ParsedMetadataJSONMap = Map; export interface UploadURL {