From 2260374fa01b03bf37757711deb105f42ba9b16d Mon Sep 17 00:00:00 2001 From: Abhinav Date: Fri, 26 Nov 2021 16:12:05 +0530 Subject: [PATCH 01/60] add live photo upload support --- src/services/motionPhotoService.ts | 13 +++ src/services/upload/uploadManager.ts | 23 +++-- src/services/upload/uploader.ts | 145 ++++++++++++++++++++++----- src/utils/upload/index.ts | 50 ++++++++- 4 files changed, 196 insertions(+), 35 deletions(-) diff --git a/src/services/motionPhotoService.ts b/src/services/motionPhotoService.ts index 425a86fcd..7cca48d00 100644 --- a/src/services/motionPhotoService.ts +++ b/src/services/motionPhotoService.ts @@ -32,3 +32,16 @@ export const decodeMotionPhoto = async ( } return motionPhoto; }; + +export const encodeMotionPhoto = async (motionPhoto: MotionPhoto) => { + const zip = new JSZip(); + zip.file( + 'image' + fileExtensionWithDot(motionPhoto.imageNameTitle), + motionPhoto.image + ); + zip.file( + 'video' + fileExtensionWithDot(motionPhoto.videoNameTitle), + motionPhoto.video + ); + return await zip.generateAsync({ type: 'uint8array' }); +}; diff --git a/src/services/upload/uploadManager.ts b/src/services/upload/uploadManager.ts index 1c67cbb6d..01c8c0716 100644 --- a/src/services/upload/uploadManager.ts +++ b/src/services/upload/uploadManager.ts @@ -13,7 +13,7 @@ import { ParsedMetaDataJSON, parseMetadataJSON, } from './metadataService'; -import { segregateFiles } from 'utils/upload'; +import { addKeysToFilesToBeUploaded, segregateFiles } from 'utils/upload'; import { ProgressUpdater } from 'components/pages/gallery/Upload'; import uploader from './uploader'; import UIService from './uiService'; @@ -33,9 +33,12 @@ export enum FileUploadResults { } export interface FileWithCollection { - file: globalThis.File; - collectionID?: number; + key?: string; + isLivePhoto?: boolean; + file?: globalThis.File; + livePhotoAsset?: [globalThis.File, globalThis.File]; collection?: Collection; + collectionID?: number; } export enum UPLOAD_STAGES { @@ -93,9 +96,15 @@ class UploadManager { ); await this.seedMetadataMap(metadataFiles); } - if (mediaFiles.length) { + if ( + mediaFiles?.normalFiles.length + + mediaFiles?.livePhotoFiles.length + ) { UIService.setUploadStage(UPLOAD_STAGES.START); - await this.uploadMediaFiles(mediaFiles); + await this.uploadMediaFiles([ + ...mediaFiles.normalFiles, + ...mediaFiles.livePhotoFiles, + ]); } UIService.setUploadStage(UPLOAD_STAGES.FINISH); UIService.setPercentComplete(FILE_UPLOAD_COMPLETED); @@ -137,7 +146,7 @@ class UploadManager { } private async uploadMediaFiles(mediaFiles: FileWithCollection[]) { - this.filesToBeUploaded.push(...mediaFiles); + this.filesToBeUploaded.push(...addKeysToFilesToBeUploaded(mediaFiles)); UIService.reset(mediaFiles.length); await UploadService.init(mediaFiles.length, this.metadataMap); @@ -202,7 +211,7 @@ class UploadManager { this.failedFiles.push(fileWithCollection); } - UIService.moveFileToResultList(fileWithCollection.file.name); + UIService.moveFileToResultList(fileWithCollection.key); } } diff --git a/src/services/upload/uploader.ts b/src/services/upload/uploader.ts index aef8bd962..e740e3a74 100644 --- a/src/services/upload/uploader.ts +++ b/src/services/upload/uploader.ts @@ -1,7 +1,7 @@ import { File, FILE_TYPE } from 'services/fileService'; import { sleep } from 'utils/common'; import { handleUploadError, CustomError } from 'utils/common/errorUtil'; -import { decryptFile } from 'utils/file'; +import { decryptFile, splitFilenameAndExtension } from 'utils/file'; import { logError } from 'utils/sentry'; import { fileAlreadyInCollection } from 'utils/upload'; import UploadHttpClient from './uploadHttpClient'; @@ -12,11 +12,13 @@ import UploadService, { EncryptedFile, FileInMemory, FileWithMetadata, + isDataStream, MetadataObject, UploadFile, } from './uploadService'; import uploadService from './uploadService'; import { FileTypeInfo, getFileType } from './readFileService'; +import { encodeMotionPhoto } from 'services/motionPhotoService'; const TwoSecondInMillSeconds = 2000; const FIVE_GB_IN_BYTES = 5 * 1024 * 1024 * 1024; @@ -29,9 +31,13 @@ export default async function uploader( existingFilesInCollection: File[], fileWithCollection: FileWithCollection ): Promise { - const { file: rawFile, collection } = fileWithCollection; - - UIService.setFileProgress(rawFile.name, 0); + const { + file: rawFile, + isLivePhoto, + livePhotoAsset, + collection, + key: progressBarKey, + } = fileWithCollection; let file: FileInMemory = null; let encryptedFile: EncryptedFile = null; @@ -39,34 +45,119 @@ export default async function uploader( let fileTypeInfo: FileTypeInfo = null; let fileWithMetadata: FileWithMetadata = null; + UIService.setFileProgress(progressBarKey, 0); + const fileSize = isLivePhoto + ? livePhotoAsset[0].size + livePhotoAsset[1].size + : rawFile.size; try { - if (rawFile.size >= FIVE_GB_IN_BYTES) { + if (fileSize >= FIVE_GB_IN_BYTES) { UIService.setFileProgress( - rawFile.name, + progressBarKey, FileUploadResults.TOO_LARGE ); // wait two second before removing the file from the progress in file section await sleep(TwoSecondInMillSeconds); return { fileUploadResult: FileUploadResults.TOO_LARGE }; } - fileTypeInfo = await getFileType(worker, rawFile); - if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) { - throw Error(CustomError.UNSUPPORTED_FILE_FORMAT); - } - metadata = await uploadService.getFileMetadata( - rawFile, - collection, - fileTypeInfo - ); + if (isLivePhoto) { + const file1TypeInfo = await getFileType(worker, livePhotoAsset[0]); + const file2TypeInfo = await getFileType(worker, livePhotoAsset[1]); + fileTypeInfo = { + fileType: FILE_TYPE.LIVE_PHOTO, + exactType: `${file1TypeInfo.exactType}+${file2TypeInfo.exactType}`, + }; + let imageFile: globalThis.File; + let videoFile: globalThis.File; + if ( + file1TypeInfo.fileType === FILE_TYPE.IMAGE && + file2TypeInfo.fileType === FILE_TYPE.VIDEO + ) { + imageFile = livePhotoAsset[0]; + videoFile = livePhotoAsset[1]; + } else if ( + file1TypeInfo.fileType === FILE_TYPE.VIDEO && + file2TypeInfo.fileType === FILE_TYPE.IMAGE + ) { + imageFile = livePhotoAsset[1]; + videoFile = livePhotoAsset[0]; + } else { + throw Error(CustomError.UNSUPPORTED_FILE_FORMAT); + } + const imageMetadata = await uploadService.getFileMetadata( + imageFile, + collection, + fileTypeInfo + ); + const videoMetadata = await uploadService.getFileMetadata( + videoFile, + collection, + fileTypeInfo + ); + metadata = { + ...videoMetadata, + ...imageMetadata, + title: splitFilenameAndExtension(livePhotoAsset[0].name)[0], + }; + if (fileAlreadyInCollection(existingFilesInCollection, metadata)) { + UIService.setFileProgress( + progressBarKey, + FileUploadResults.SKIPPED + ); + // wait two second before removing the file from the progress in file section + await sleep(TwoSecondInMillSeconds); + return { fileUploadResult: FileUploadResults.SKIPPED }; + } + const image = await UploadService.readFile( + worker, + imageFile, + fileTypeInfo + ); + const video = await UploadService.readFile( + worker, + videoFile, + fileTypeInfo + ); - if (fileAlreadyInCollection(existingFilesInCollection, metadata)) { - UIService.setFileProgress(rawFile.name, FileUploadResults.SKIPPED); - // wait two second before removing the file from the progress in file section - await sleep(TwoSecondInMillSeconds); - return { fileUploadResult: FileUploadResults.SKIPPED }; - } + if (isDataStream(video.filedata) || isDataStream(image.filedata)) { + throw new Error('too large live photo assets'); + } + file = { + filedata: await encodeMotionPhoto({ + image: image.filedata as Uint8Array, + video: video.filedata as Uint8Array, + imageNameTitle: imageFile.name, + videoNameTitle: videoFile.name, + }), + thumbnail: video.hasStaticThumbnail + ? video.thumbnail + : image.thumbnail, + hasStaticThumbnail: !( + !video.hasStaticThumbnail || !image.hasStaticThumbnail + ), + }; + } else { + fileTypeInfo = await getFileType(worker, rawFile); + if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) { + throw Error(CustomError.UNSUPPORTED_FILE_FORMAT); + } + metadata = await uploadService.getFileMetadata( + rawFile, + collection, + fileTypeInfo + ); - file = await UploadService.readFile(worker, rawFile, fileTypeInfo); + if (fileAlreadyInCollection(existingFilesInCollection, metadata)) { + UIService.setFileProgress( + progressBarKey, + FileUploadResults.SKIPPED + ); + // wait two second before removing the file from the progress in file section + await sleep(TwoSecondInMillSeconds); + return { fileUploadResult: FileUploadResults.SKIPPED }; + } + + file = await UploadService.readFile(worker, rawFile, fileTypeInfo); + } if (file.hasStaticThumbnail) { metadata.hasStaticThumbnail = true; } @@ -95,7 +186,7 @@ export default async function uploader( const uploadedFile = await UploadHttpClient.uploadFile(uploadFile); const decryptedFile = await decryptFile(uploadedFile, collection); - UIService.setFileProgress(rawFile.name, FileUploadResults.UPLOADED); + UIService.setFileProgress(progressBarKey, FileUploadResults.UPLOADED); UIService.increaseFileUploaded(); return { fileUploadResult: FileUploadResults.UPLOADED, @@ -109,26 +200,26 @@ export default async function uploader( switch (error.message) { case CustomError.ETAG_MISSING: UIService.setFileProgress( - rawFile.name, + progressBarKey, FileUploadResults.BLOCKED ); return { fileUploadResult: FileUploadResults.BLOCKED }; case CustomError.UNSUPPORTED_FILE_FORMAT: UIService.setFileProgress( - rawFile.name, + progressBarKey, FileUploadResults.UNSUPPORTED ); return { fileUploadResult: FileUploadResults.UNSUPPORTED }; case CustomError.FILE_TOO_LARGE: UIService.setFileProgress( - rawFile.name, + progressBarKey, FileUploadResults.TOO_LARGE ); return { fileUploadResult: FileUploadResults.TOO_LARGE }; default: UIService.setFileProgress( - rawFile.name, + progressBarKey, FileUploadResults.FAILED ); return { fileUploadResult: FileUploadResults.FAILED }; diff --git a/src/utils/upload/index.ts b/src/utils/upload/index.ts index ded536051..1f8bb91e4 100644 --- a/src/utils/upload/index.ts +++ b/src/utils/upload/index.ts @@ -1,6 +1,7 @@ import { FileWithCollection } from 'services/upload/uploadManager'; import { MetadataObject } from 'services/upload/uploadService'; import { File } from 'services/fileService'; +import { splitFilenameAndExtension } from 'utils/file'; const TYPE_JSON = 'json'; export function fileAlreadyInCollection( @@ -33,6 +34,12 @@ export function areFilesSame( export function segregateFiles( filesWithCollectionToUpload: FileWithCollection[] ) { + filesWithCollectionToUpload = filesWithCollectionToUpload.sort( + (fileWithCollection1, fileWithCollection2) => + fileWithCollection1.file.name.localeCompare( + fileWithCollection2.file.name + ) + ); const metadataFiles: FileWithCollection[] = []; const mediaFiles: FileWithCollection[] = []; filesWithCollectionToUpload.forEach((fileWithCollection) => { @@ -47,5 +54,46 @@ export function segregateFiles( mediaFiles.push(fileWithCollection); } }); - return { mediaFiles, metadataFiles }; + const normalFiles: FileWithCollection[] = []; + const livePhotoFiles: FileWithCollection[] = []; + for (let i = 0; i < mediaFiles.length - 1; i++) { + const mediaFile1 = mediaFiles[i]; + const mediaFile2 = mediaFiles[i + 1]; + if (mediaFile1.collectionID === mediaFile2.collectionID) { + const collectionID = mediaFiles[i].collectionID; + const file1 = mediaFile1.file; + const file2 = mediaFile2.file; + if ( + splitFilenameAndExtension(file1.name)[0] === + splitFilenameAndExtension(file2.name)[0] + ) { + livePhotoFiles.push({ + collectionID: collectionID, + isLivePhoto: true, + livePhotoAsset: [file1, file2], + }); + } + } else { + normalFiles.push(mediaFile1); + normalFiles.push(mediaFile2); + } + } + return { mediaFiles: { normalFiles, livePhotoFiles }, metadataFiles }; +} + +export function addKeysToFilesToBeUploaded(files: FileWithCollection[]) { + console.log(files); + return files.map((file) => ({ + ...file, + key: getFileToBeUploadedKey(file), + })); +} + +function getFileToBeUploadedKey(fileWithCollection: FileWithCollection) { + const fileName = splitFilenameAndExtension( + fileWithCollection.isLivePhoto + ? fileWithCollection.livePhotoAsset[0].name + '-livePhoto' + : fileWithCollection.file.name + )[0]; + return fileName; } From e7c051ac537ab6daaf2ceb09692199f366bcb43f Mon Sep 17 00:00:00 2001 From: Abhinav Date: Thu, 3 Feb 2022 23:54:36 +0530 Subject: [PATCH 02/60] refactor metadata extraction --- src/constants/upload/index.ts | 2 + src/services/upload/uploadManager.ts | 93 +++++++++++++++++++++------- src/services/upload/uploadService.ts | 60 +++++++++++------- src/services/upload/uploader.ts | 46 ++++---------- src/types/upload/index.ts | 8 ++- src/utils/error/index.ts | 1 + src/utils/upload/index.ts | 6 +- 7 files changed, 134 insertions(+), 82 deletions(-) diff --git a/src/constants/upload/index.ts b/src/constants/upload/index.ts index b5d6bed9f..27ac07eb7 100644 --- a/src/constants/upload/index.ts +++ b/src/constants/upload/index.ts @@ -38,3 +38,5 @@ export enum FileUploadResults { LARGER_THAN_AVAILABLE_STORAGE, UPLOADED, } + +export const FIVE_GB_IN_BYTES = 5 * 1024 * 1024 * 1024; diff --git a/src/services/upload/uploadManager.ts b/src/services/upload/uploadManager.ts index 927086a8e..19acdb1a8 100644 --- a/src/services/upload/uploadManager.ts +++ b/src/services/upload/uploadManager.ts @@ -18,19 +18,30 @@ import { Collection } from 'types/collection'; import { EnteFile } from 'types/file'; import { FileWithCollection, - MetadataMap, + MetadataAndFileTypeInfo, + MetadataAndFileTypeInfoMap, ParsedMetadataJSON, + ParsedMetadataJSONMap, ProgressUpdater, } from 'types/upload'; -import { UPLOAD_STAGES, FileUploadResults } from 'constants/upload'; +import { + UPLOAD_STAGES, + FileUploadResults, + FIVE_GB_IN_BYTES, +} from 'constants/upload'; import { ComlinkWorker } from 'utils/comlink'; +import { getFileType } from './readFileService'; +import { FILE_TYPE } from 'constants/file'; +import { title } from 'process'; +import uploadService from './uploadService'; const MAX_CONCURRENT_UPLOADS = 4; const FILE_UPLOAD_COMPLETED = 100; class UploadManager { private cryptoWorkers = new Array(MAX_CONCURRENT_UPLOADS); - private metadataMap: MetadataMap; + private parsedMetadataJSONMap: ParsedMetadataJSONMap; + private metadataAndFileTypeInfoMap: MetadataAndFileTypeInfoMap; private filesToBeUploaded: FileWithCollection[]; private failedFiles: FileWithCollection[]; private existingFilesCollectionWise: Map; @@ -45,7 +56,11 @@ class UploadManager { private async init(newCollections?: Collection[]) { this.filesToBeUploaded = []; this.failedFiles = []; - this.metadataMap = new Map(); + this.parsedMetadataJSONMap = new Map(); + this.metadataAndFileTypeInfoMap = new Map< + string, + MetadataAndFileTypeInfo + >(); this.existingFiles = await getLocalFiles(); this.existingFilesCollectionWise = sortFilesIntoCollections( this.existingFiles @@ -65,17 +80,21 @@ class UploadManager { ) { try { await this.init(newCreatedCollections); - const { metadataFiles, mediaFiles } = segregateFiles( + const { metadataJSONFiles, mediaFiles } = segregateFiles( fileWithCollectionToBeUploaded ); - if (metadataFiles.length) { + if (metadataJSONFiles.length) { UIService.setUploadStage( UPLOAD_STAGES.READING_GOOGLE_METADATA_FILES ); - await this.seedMetadataMap(metadataFiles); + await this.parseMetadataJSONFiles(metadataJSONFiles); + uploadService.setParsedMetadataJSONMap( + this.parsedMetadataJSONMap + ); } if (mediaFiles.length) { UIService.setUploadStage(UPLOAD_STAGES.START); + await this.extractMetadataFromFiles(mediaFiles); await this.uploadMediaFiles(mediaFiles); } UIService.setUploadStage(UPLOAD_STAGES.FINISH); @@ -90,23 +109,20 @@ class UploadManager { } } - private async seedMetadataMap(metadataFiles: FileWithCollection[]) { + private async parseMetadataJSONFiles(metadataFiles: FileWithCollection[]) { try { UIService.reset(metadataFiles.length); const reader = new FileReader(); - for (const fileWithCollection of metadataFiles) { + for (const { file, collectionID } of metadataFiles) { const parsedMetadataJSONWithTitle = await parseMetadataJSON( reader, - fileWithCollection.file + file ); if (parsedMetadataJSONWithTitle) { const { title, parsedMetadataJSON } = parsedMetadataJSONWithTitle; - this.metadataMap.set( - getMetadataMapKey( - fileWithCollection.collectionID, - title - ), + this.parsedMetadataJSONMap.set( + getMetadataMapKey(collectionID, title), { ...parsedMetadataJSON } ); UIService.increaseFileUploaded(); @@ -118,11 +134,44 @@ class UploadManager { } } + private async extractMetadataFromFiles(mediaFiles: FileWithCollection[]) { + try { + UIService.reset(mediaFiles.length); + const reader = new FileReader(); + for (const { file, collectionID } of mediaFiles) { + const { fileTypeInfo, metadata } = await (async () => { + if (file.size >= FIVE_GB_IN_BYTES) { + return { fileTypeInfo: null, metadata: null }; + } + const fileTypeInfo = await getFileType(reader, file); + if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) { + return { fileTypeInfo, metadata: null }; + } + const metadata = + (await uploadService.extractMetadata( + file, + collectionID, + fileTypeInfo + )) || null; + return { fileTypeInfo, metadata }; + })(); + this.metadataAndFileTypeInfoMap.set( + getMetadataMapKey(collectionID, title), + { ...{ fileTypeInfo, metadata } } + ); + UIService.increaseFileUploaded(); + } + } catch (e) { + logError(e, 'error extracting metadata'); + // silently ignore the error + } + } + private async uploadMediaFiles(mediaFiles: FileWithCollection[]) { this.filesToBeUploaded.push(...mediaFiles); UIService.reset(mediaFiles.length); - await UploadService.init(mediaFiles.length, this.metadataMap); + await UploadService.setFileCount(mediaFiles.length); UIService.setUploadStage(UPLOAD_STAGES.UPLOADING); @@ -150,19 +199,15 @@ class UploadManager { private async uploadNextFileInQueue(worker: any, reader: FileReader) { while (this.filesToBeUploaded.length > 0) { const fileWithCollection = this.filesToBeUploaded.pop(); + const { file: rawFile, collectionID } = fileWithCollection; const existingFilesInCollection = - this.existingFilesCollectionWise.get( - fileWithCollection.collectionID - ) ?? []; - const collection = this.collections.get( - fileWithCollection.collectionID - ); - fileWithCollection.collection = collection; + this.existingFilesCollectionWise.get(collectionID) ?? []; + const collection = this.collections.get(collectionID); const { fileUploadResult, file } = await uploader( worker, reader, existingFilesInCollection, - fileWithCollection + { file: rawFile, collection } ); if (fileUploadResult === FileUploadResults.UPLOADED) { diff --git a/src/services/upload/uploadService.ts b/src/services/upload/uploadService.ts index 54b65b46c..98b4bb765 100644 --- a/src/services/upload/uploadService.ts +++ b/src/services/upload/uploadService.ts @@ -17,25 +17,35 @@ import { FileTypeInfo, FileWithMetadata, isDataStream, - MetadataMap, Metadata, - ParsedMetadataJSON, ProcessedFile, UploadFile, UploadURL, + MetadataAndFileTypeInfoMap, + ParsedMetadataJSONMap, } from 'types/upload'; class UploadService { private uploadURLs: UploadURL[] = []; - private metadataMap: Map; + private parsedMetadataJSONMap: ParsedMetadataJSONMap; + private metadataAndFileTypeInfoMap: MetadataAndFileTypeInfoMap; private pendingUploadCount: number = 0; - async init(fileCount: number, metadataMap: MetadataMap) { + async setFileCount(fileCount: number) { this.pendingUploadCount = fileCount; - this.metadataMap = metadataMap; await this.preFetchUploadURLs(); } + setParsedMetadataJSONMap(parsedMetadataJSONMap: ParsedMetadataJSONMap) { + this.parsedMetadataJSONMap = parsedMetadataJSONMap; + } + + setMetadataAndFileTypeInfoMap( + metadataAndFileTypeInfoMap: MetadataAndFileTypeInfoMap + ) { + this.metadataAndFileTypeInfoMap = metadataAndFileTypeInfoMap; + } + reducePendingUploadCount() { this.pendingUploadCount--; } @@ -62,28 +72,36 @@ class UploadService { }; } - async getFileMetadata( + async extractMetadata( rawFile: File, - collection: Collection, + collectionID: number, fileTypeInfo: FileTypeInfo ): Promise { - const originalName = getFileOriginalName(rawFile); - const googleMetadata = - this.metadataMap.get( - getMetadataMapKey(collection.id, originalName) - ) ?? {}; - const extractedMetadata: Metadata = await extractMetadata( - rawFile, - fileTypeInfo - ); + try { + const originalName = getFileOriginalName(rawFile); + const googleMetadata = + this.parsedMetadataJSONMap.get( + getMetadataMapKey(collectionID, originalName) + ) ?? {}; + const extractedMetadata: Metadata = await extractMetadata( + rawFile, + fileTypeInfo + ); - for (const [key, value] of Object.entries(googleMetadata)) { - if (!value) { - continue; + for (const [key, value] of Object.entries(googleMetadata)) { + if (!value) { + continue; + } + extractedMetadata[key] = value; } - extractedMetadata[key] = value; + return extractedMetadata; + } catch (e) { + logError(e, 'failed to extract file metadata'); } - return extractedMetadata; + } + + getFileMetadataAndFileTypeInfo(key: string) { + return this.getFileMetadataAndFileTypeInfo(key); } async encryptFile( diff --git a/src/services/upload/uploader.ts b/src/services/upload/uploader.ts index ca9215421..b26ab3239 100644 --- a/src/services/upload/uploader.ts +++ b/src/services/upload/uploader.ts @@ -7,21 +7,11 @@ import UploadHttpClient from './uploadHttpClient'; import UIService from './uiService'; import UploadService from './uploadService'; import uploadService from './uploadService'; -import { getFileType } from './readFileService'; -import { - BackupedFile, - EncryptedFile, - FileInMemory, - FileTypeInfo, - FileWithCollection, - FileWithMetadata, - Metadata, - UploadFile, -} from 'types/upload'; +import { BackupedFile, FileWithCollection, UploadFile } from 'types/upload'; import { FILE_TYPE } from 'constants/file'; -import { FileUploadResults } from 'constants/upload'; +import { FileUploadResults, FIVE_GB_IN_BYTES } from 'constants/upload'; +import { getMetadataMapKey } from './metadataService'; -const FIVE_GB_IN_BYTES = 5 * 1024 * 1024 * 1024; interface UploadResponse { fileUploadResult: FileUploadResults; file?: EnteFile; @@ -35,32 +25,26 @@ export default async function uploader( const { file: rawFile, collection } = fileWithCollection; UIService.setFileProgress(rawFile.name, 0); - - let file: FileInMemory = null; - let encryptedFile: EncryptedFile = null; - let metadata: Metadata = null; - let fileTypeInfo: FileTypeInfo = null; - let fileWithMetadata: FileWithMetadata = null; - + const { fileTypeInfo, metadata } = + uploadService.getFileMetadataAndFileTypeInfo( + getMetadataMapKey(collection.id, rawFile.name) + ); try { if (rawFile.size >= FIVE_GB_IN_BYTES) { return { fileUploadResult: FileUploadResults.TOO_LARGE }; } - fileTypeInfo = await getFileType(reader, rawFile); if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) { throw Error(CustomError.UNSUPPORTED_FILE_FORMAT); } - metadata = await uploadService.getFileMetadata( - rawFile, - collection, - fileTypeInfo - ); + if (!metadata) { + throw Error(CustomError.NO_METADATA); + } if (fileAlreadyInCollection(existingFilesInCollection, metadata)) { return { fileUploadResult: FileUploadResults.ALREADY_UPLOADED }; } - file = await UploadService.readFile( + const file = await UploadService.readFile( worker, reader, rawFile, @@ -69,13 +53,13 @@ export default async function uploader( if (file.hasStaticThumbnail) { metadata.hasStaticThumbnail = true; } - fileWithMetadata = { + const fileWithMetadata = { filedata: file.filedata, thumbnail: file.thumbnail, metadata, }; - encryptedFile = await UploadService.encryptFile( + const encryptedFile = await UploadService.encryptFile( worker, fileWithMetadata, collection.key @@ -117,9 +101,5 @@ export default async function uploader( default: return { fileUploadResult: FileUploadResults.FAILED }; } - } finally { - file = null; - fileWithMetadata = null; - encryptedFile = null; } } diff --git a/src/types/upload/index.ts b/src/types/upload/index.ts index 6e58177bc..ce28cbdb9 100644 --- a/src/types/upload/index.ts +++ b/src/types/upload/index.ts @@ -69,7 +69,13 @@ export interface FileWithCollection { collection?: Collection; } -export type MetadataMap = Map; +export interface MetadataAndFileTypeInfo { + metadata: Metadata; + fileTypeInfo: FileTypeInfo; +} + +export type MetadataAndFileTypeInfoMap = Map; +export type ParsedMetadataJSONMap = Map; export interface UploadURL { url: string; diff --git a/src/utils/error/index.ts b/src/utils/error/index.ts index 45d3e2f52..b8d498dce 100644 --- a/src/utils/error/index.ts +++ b/src/utils/error/index.ts @@ -37,6 +37,7 @@ export enum CustomError { BAD_REQUEST = 'bad request', SUBSCRIPTION_NEEDED = 'subscription not present', NOT_FOUND = 'not found ', + NO_METADATA = 'no metadata', } function parseUploadErrorCodes(error) { diff --git a/src/utils/upload/index.ts b/src/utils/upload/index.ts index ebf11f0a3..325088490 100644 --- a/src/utils/upload/index.ts +++ b/src/utils/upload/index.ts @@ -33,7 +33,7 @@ export function areFilesSame( export function segregateFiles( filesWithCollectionToUpload: FileWithCollection[] ) { - const metadataFiles: FileWithCollection[] = []; + const metadataJSONFiles: FileWithCollection[] = []; const mediaFiles: FileWithCollection[] = []; filesWithCollectionToUpload.forEach((fileWithCollection) => { const file = fileWithCollection.file; @@ -42,10 +42,10 @@ export function segregateFiles( return; } if (file.name.toLowerCase().endsWith(TYPE_JSON)) { - metadataFiles.push(fileWithCollection); + metadataJSONFiles.push(fileWithCollection); } else { mediaFiles.push(fileWithCollection); } }); - return { mediaFiles, metadataFiles }; + return { mediaFiles, metadataJSONFiles }; } From 21aab14380e670ed5eefc85e9df8a43d3ef5ecdd Mon Sep 17 00:00:00 2001 From: Abhinav Date: Fri, 4 Feb 2022 00:26:34 +0530 Subject: [PATCH 03/60] fix things --- src/services/upload/uploadManager.ts | 13 ++++++++++--- src/services/upload/uploadService.ts | 15 ++++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/services/upload/uploadManager.ts b/src/services/upload/uploadManager.ts index 19acdb1a8..bba3d041f 100644 --- a/src/services/upload/uploadManager.ts +++ b/src/services/upload/uploadManager.ts @@ -32,7 +32,6 @@ import { import { ComlinkWorker } from 'utils/comlink'; import { getFileType } from './readFileService'; import { FILE_TYPE } from 'constants/file'; -import { title } from 'process'; import uploadService from './uploadService'; const MAX_CONCURRENT_UPLOADS = 4; @@ -95,6 +94,9 @@ class UploadManager { if (mediaFiles.length) { UIService.setUploadStage(UPLOAD_STAGES.START); await this.extractMetadataFromFiles(mediaFiles); + uploadService.setMetadataAndFileTypeInfoMap( + this.metadataAndFileTypeInfoMap + ); await this.uploadMediaFiles(mediaFiles); } UIService.setUploadStage(UPLOAD_STAGES.FINISH); @@ -155,10 +157,15 @@ class UploadManager { )) || null; return { fileTypeInfo, metadata }; })(); + this.metadataAndFileTypeInfoMap.set( - getMetadataMapKey(collectionID, title), - { ...{ fileTypeInfo, metadata } } + getMetadataMapKey(collectionID, file.name), + { + fileTypeInfo: { ...fileTypeInfo }, + metadata: { ...metadata }, + } ); + console.log(this.metadataAndFileTypeInfoMap.keys()); UIService.increaseFileUploaded(); } } catch (e) { diff --git a/src/services/upload/uploadService.ts b/src/services/upload/uploadService.ts index 98b4bb765..0d77df4bf 100644 --- a/src/services/upload/uploadService.ts +++ b/src/services/upload/uploadService.ts @@ -23,12 +23,20 @@ import { UploadURL, MetadataAndFileTypeInfoMap, ParsedMetadataJSONMap, + ParsedMetadataJSON, + MetadataAndFileTypeInfo, } from 'types/upload'; class UploadService { private uploadURLs: UploadURL[] = []; - private parsedMetadataJSONMap: ParsedMetadataJSONMap; - private metadataAndFileTypeInfoMap: MetadataAndFileTypeInfoMap; + private parsedMetadataJSONMap: ParsedMetadataJSONMap = new Map< + string, + ParsedMetadataJSON + >(); + private metadataAndFileTypeInfoMap: MetadataAndFileTypeInfoMap = new Map< + string, + MetadataAndFileTypeInfo + >(); private pendingUploadCount: number = 0; async setFileCount(fileCount: number) { @@ -101,7 +109,8 @@ class UploadService { } getFileMetadataAndFileTypeInfo(key: string) { - return this.getFileMetadataAndFileTypeInfo(key); + console.log(this.metadataAndFileTypeInfoMap, key); + return this.metadataAndFileTypeInfoMap.get(key); } async encryptFile( From c9129ce11ce13193f2df36c9fae23d9139e71d20 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Sat, 5 Feb 2022 14:03:34 +0530 Subject: [PATCH 04/60] remove console logs --- src/services/upload/uploadManager.ts | 1 - src/services/upload/uploadService.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/services/upload/uploadManager.ts b/src/services/upload/uploadManager.ts index bba3d041f..e3b9bd5e2 100644 --- a/src/services/upload/uploadManager.ts +++ b/src/services/upload/uploadManager.ts @@ -165,7 +165,6 @@ class UploadManager { metadata: { ...metadata }, } ); - console.log(this.metadataAndFileTypeInfoMap.keys()); UIService.increaseFileUploaded(); } } catch (e) { diff --git a/src/services/upload/uploadService.ts b/src/services/upload/uploadService.ts index 0d77df4bf..a9d4ce7d9 100644 --- a/src/services/upload/uploadService.ts +++ b/src/services/upload/uploadService.ts @@ -109,7 +109,6 @@ class UploadService { } getFileMetadataAndFileTypeInfo(key: string) { - console.log(this.metadataAndFileTypeInfoMap, key); return this.metadataAndFileTypeInfoMap.get(key); } From 42670853e7cd074c206c0fcecd3bc1c339306663 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Sat, 5 Feb 2022 14:10:18 +0530 Subject: [PATCH 05/60] renamed 5GB to better name of max file size supported --- src/constants/upload/index.ts | 2 +- src/services/upload/uploadManager.ts | 4 ++-- src/services/upload/uploader.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/constants/upload/index.ts b/src/constants/upload/index.ts index 27ac07eb7..a34dbf6b3 100644 --- a/src/constants/upload/index.ts +++ b/src/constants/upload/index.ts @@ -39,4 +39,4 @@ export enum FileUploadResults { UPLOADED, } -export const FIVE_GB_IN_BYTES = 5 * 1024 * 1024 * 1024; +export const MAX_FILE_SIZE_SUPPORTED = 5 * 1024 * 1024 * 1024; // 5 GB diff --git a/src/services/upload/uploadManager.ts b/src/services/upload/uploadManager.ts index e3b9bd5e2..1ec3dc07d 100644 --- a/src/services/upload/uploadManager.ts +++ b/src/services/upload/uploadManager.ts @@ -27,7 +27,7 @@ import { import { UPLOAD_STAGES, FileUploadResults, - FIVE_GB_IN_BYTES, + MAX_FILE_SIZE_SUPPORTED, } from 'constants/upload'; import { ComlinkWorker } from 'utils/comlink'; import { getFileType } from './readFileService'; @@ -142,7 +142,7 @@ class UploadManager { const reader = new FileReader(); for (const { file, collectionID } of mediaFiles) { const { fileTypeInfo, metadata } = await (async () => { - if (file.size >= FIVE_GB_IN_BYTES) { + if (file.size >= MAX_FILE_SIZE_SUPPORTED) { return { fileTypeInfo: null, metadata: null }; } const fileTypeInfo = await getFileType(reader, file); diff --git a/src/services/upload/uploader.ts b/src/services/upload/uploader.ts index b26ab3239..a706ddba0 100644 --- a/src/services/upload/uploader.ts +++ b/src/services/upload/uploader.ts @@ -9,7 +9,7 @@ import UploadService from './uploadService'; import uploadService from './uploadService'; import { BackupedFile, FileWithCollection, UploadFile } from 'types/upload'; import { FILE_TYPE } from 'constants/file'; -import { FileUploadResults, FIVE_GB_IN_BYTES } from 'constants/upload'; +import { FileUploadResults, MAX_FILE_SIZE_SUPPORTED } from 'constants/upload'; import { getMetadataMapKey } from './metadataService'; interface UploadResponse { @@ -30,7 +30,7 @@ export default async function uploader( getMetadataMapKey(collection.id, rawFile.name) ); try { - if (rawFile.size >= FIVE_GB_IN_BYTES) { + if (rawFile.size >= MAX_FILE_SIZE_SUPPORTED) { return { fileUploadResult: FileUploadResults.TOO_LARGE }; } if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) { From 95a7b871f636482b4402d78278d315686903e136 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Sat, 5 Feb 2022 14:22:07 +0530 Subject: [PATCH 06/60] add check to only set data in map if not null --- src/services/upload/uploadManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/upload/uploadManager.ts b/src/services/upload/uploadManager.ts index 1ec3dc07d..7f52b4150 100644 --- a/src/services/upload/uploadManager.ts +++ b/src/services/upload/uploadManager.ts @@ -125,7 +125,7 @@ class UploadManager { parsedMetadataJSONWithTitle; this.parsedMetadataJSONMap.set( getMetadataMapKey(collectionID, title), - { ...parsedMetadataJSON } + parsedMetadataJSON && { ...parsedMetadataJSON } ); UIService.increaseFileUploaded(); } @@ -161,8 +161,8 @@ class UploadManager { this.metadataAndFileTypeInfoMap.set( getMetadataMapKey(collectionID, file.name), { - fileTypeInfo: { ...fileTypeInfo }, - metadata: { ...metadata }, + fileTypeInfo: fileTypeInfo && { ...fileTypeInfo }, + metadata: metadata && { ...metadata }, } ); UIService.increaseFileUploaded(); From 858315c89750c7174b2a443f5be7e687d90f7d08 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 9 Feb 2022 10:32:14 +0530 Subject: [PATCH 07/60] added stage for metadata extraction --- src/constants/upload/index.ts | 1 + src/services/upload/uploadManager.ts | 3 ++- src/utils/strings/englishConstants.tsx | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/constants/upload/index.ts b/src/constants/upload/index.ts index a34dbf6b3..19432605f 100644 --- a/src/constants/upload/index.ts +++ b/src/constants/upload/index.ts @@ -25,6 +25,7 @@ export const NULL_LOCATION: Location = { latitude: null, longitude: null }; export enum UPLOAD_STAGES { START, READING_GOOGLE_METADATA_FILES, + EXTRACTING_METADATA, UPLOADING, FINISH, } diff --git a/src/services/upload/uploadManager.ts b/src/services/upload/uploadManager.ts index 7f52b4150..e4642ede6 100644 --- a/src/services/upload/uploadManager.ts +++ b/src/services/upload/uploadManager.ts @@ -92,11 +92,12 @@ class UploadManager { ); } if (mediaFiles.length) { - UIService.setUploadStage(UPLOAD_STAGES.START); + UIService.setUploadStage(UPLOAD_STAGES.EXTRACTING_METADATA); await this.extractMetadataFromFiles(mediaFiles); uploadService.setMetadataAndFileTypeInfoMap( this.metadataAndFileTypeInfoMap ); + UIService.setUploadStage(UPLOAD_STAGES.START); await this.uploadMediaFiles(mediaFiles); } UIService.setUploadStage(UPLOAD_STAGES.FINISH); diff --git a/src/utils/strings/englishConstants.tsx b/src/utils/strings/englishConstants.tsx index cec9704f5..adfc149dd 100644 --- a/src/utils/strings/englishConstants.tsx +++ b/src/utils/strings/englishConstants.tsx @@ -103,9 +103,10 @@ const englishConstants = { UPLOAD: { 0: 'preparing to upload', 1: 'reading google metadata files', - 2: (fileCounter) => + 2: 'reading file metadata to organize file', + 3: (fileCounter) => `${fileCounter.finished} / ${fileCounter.total} files backed up`, - 3: 'backup complete', + 4: 'backup complete', }, UPLOADING_FILES: 'file upload', FILE_NOT_UPLOADED_LIST: 'the following files were not uploaded', From eb23444853d9487f275987528303a56b6d888128 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 9 Feb 2022 10:53:43 +0530 Subject: [PATCH 08/60] create a POC working model of live photo upload --- src/services/upload/readFileService.ts | 19 +++++++++++++ src/services/upload/uploadManager.ts | 2 +- src/services/upload/uploader.ts | 16 +---------- src/utils/upload/index.ts | 37 ++++++++++++++++++++------ 4 files changed, 50 insertions(+), 24 deletions(-) diff --git a/src/services/upload/readFileService.ts b/src/services/upload/readFileService.ts index 3342fd572..54e52dd07 100644 --- a/src/services/upload/readFileService.ts +++ b/src/services/upload/readFileService.ts @@ -151,3 +151,22 @@ export async function getUint8ArrayView( throw e; } } + +export function getFileTypeFromFileObject(file: globalThis.File) { + const typeParts = file.type.split('/'); + let fileType; + if (typeParts?.length !== 2) { + throw Error(CustomError.TYPE_DETECTION_FAILED); + } + switch (typeParts[0]) { + case TYPE_IMAGE: + fileType = FILE_TYPE.IMAGE; + break; + case TYPE_VIDEO: + fileType = FILE_TYPE.VIDEO; + break; + default: + fileType = FILE_TYPE.OTHERS; + } + return fileType; +} diff --git a/src/services/upload/uploadManager.ts b/src/services/upload/uploadManager.ts index 01c8c0716..98d84f605 100644 --- a/src/services/upload/uploadManager.ts +++ b/src/services/upload/uploadManager.ts @@ -36,7 +36,7 @@ export interface FileWithCollection { key?: string; isLivePhoto?: boolean; file?: globalThis.File; - livePhotoAsset?: [globalThis.File, globalThis.File]; + livePhotoAsset?: { image: globalThis.File; video: globalThis.File }; collection?: Collection; collectionID?: number; } diff --git a/src/services/upload/uploader.ts b/src/services/upload/uploader.ts index e740e3a74..60f2c23bc 100644 --- a/src/services/upload/uploader.ts +++ b/src/services/upload/uploader.ts @@ -68,21 +68,7 @@ export default async function uploader( }; let imageFile: globalThis.File; let videoFile: globalThis.File; - if ( - file1TypeInfo.fileType === FILE_TYPE.IMAGE && - file2TypeInfo.fileType === FILE_TYPE.VIDEO - ) { - imageFile = livePhotoAsset[0]; - videoFile = livePhotoAsset[1]; - } else if ( - file1TypeInfo.fileType === FILE_TYPE.VIDEO && - file2TypeInfo.fileType === FILE_TYPE.IMAGE - ) { - imageFile = livePhotoAsset[1]; - videoFile = livePhotoAsset[0]; - } else { - throw Error(CustomError.UNSUPPORTED_FILE_FORMAT); - } + const imageMetadata = await uploadService.getFileMetadata( imageFile, collection, diff --git a/src/utils/upload/index.ts b/src/utils/upload/index.ts index 1f8bb91e4..1db242d23 100644 --- a/src/utils/upload/index.ts +++ b/src/utils/upload/index.ts @@ -1,7 +1,8 @@ import { FileWithCollection } from 'services/upload/uploadManager'; import { MetadataObject } from 'services/upload/uploadService'; -import { File } from 'services/fileService'; +import { File, FILE_TYPE } from 'services/fileService'; import { splitFilenameAndExtension } from 'utils/file'; +import { getFileTypeFromFileObject } from 'services/upload/readFileService'; const TYPE_JSON = 'json'; export function fileAlreadyInCollection( @@ -63,15 +64,35 @@ export function segregateFiles( const collectionID = mediaFiles[i].collectionID; const file1 = mediaFile1.file; const file2 = mediaFile2.file; + const file1Type = getFileTypeFromFileObject(file1); + const file2Type = getFileTypeFromFileObject(file2); if ( - splitFilenameAndExtension(file1.name)[0] === - splitFilenameAndExtension(file2.name)[0] + file1Type !== FILE_TYPE.OTHERS && + file2Type !== FILE_TYPE.OTHERS ) { - livePhotoFiles.push({ - collectionID: collectionID, - isLivePhoto: true, - livePhotoAsset: [file1, file2], - }); + let imageFile; + let videoFile; + if ( + file1Type !== file2Type && + splitFilenameAndExtension(file1.name)[0] === + splitFilenameAndExtension(file2.name)[0] + ) { + if ( + file1Type === FILE_TYPE.IMAGE && + file2Type === FILE_TYPE.VIDEO + ) { + imageFile = file1; + videoFile = file2; + } else { + imageFile = file2; + videoFile = file1; + } + livePhotoFiles.push({ + collectionID: collectionID, + isLivePhoto: true, + livePhotoAsset: { image: imageFile, video: videoFile }, + }); + } } } else { normalFiles.push(mediaFile1); From 7b45aee757e7835f7c1d1a91b26cd0067dffc9f8 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Sun, 13 Feb 2022 09:43:25 +0530 Subject: [PATCH 09/60] refactor code --- src/services/upload/fileService.ts | 101 +++++++++++++++ src/services/upload/livePhotoService.ts | 81 +++++++++++++ src/services/upload/readFileService.ts | 15 +-- src/services/upload/uploadService.ts | 155 +++++++++++------------- src/services/upload/uploader.ts | 109 +++-------------- src/types/upload/index.ts | 15 ++- 6 files changed, 286 insertions(+), 190 deletions(-) create mode 100644 src/services/upload/fileService.ts create mode 100644 src/services/upload/livePhotoService.ts diff --git a/src/services/upload/fileService.ts b/src/services/upload/fileService.ts new file mode 100644 index 000000000..8aa62d0b6 --- /dev/null +++ b/src/services/upload/fileService.ts @@ -0,0 +1,101 @@ +import { Collection } from 'types/collection'; +import { + FileTypeInfo, + FileInMemory, + Metadata, + B64EncryptionResult, + EncryptedFile, + EncryptionResult, + FileWithMetadata, +} from 'types/upload'; +import { logError } from 'utils/sentry'; +import { encryptFiledata } from './encryptionService'; +import { getMetadataMapKey, extractMetadata } from './metadataService'; +import { getFileData, getFileOriginalName } from './readFileService'; +import { generateThumbnail } from './thumbnailService'; + +export function getFileSize(file: File) { + return file.size; +} + +export async function readFile( + worker: any, + reader: FileReader, + fileTypeInfo: FileTypeInfo, + rawFile: File +): Promise { + const { thumbnail, hasStaticThumbnail } = await generateThumbnail( + worker, + reader, + rawFile, + fileTypeInfo + ); + + const filedata = await getFileData(reader, rawFile); + + return { + filedata, + thumbnail, + hasStaticThumbnail, + }; +} + +export async function getFileMetadata( + rawFile: File, + collection: Collection, + fileTypeInfo: FileTypeInfo +) { + const originalName = getFileOriginalName(rawFile); + const googleMetadata = + this.metadataMap.get(getMetadataMapKey(collection.id, originalName)) ?? + {}; + const extractedMetadata: Metadata = await extractMetadata( + rawFile, + fileTypeInfo + ); + + for (const [key, value] of Object.entries(googleMetadata)) { + if (!value) { + continue; + } + extractedMetadata[key] = value; + } + return extractedMetadata; +} + +export async function encryptFile( + worker: any, + file: FileWithMetadata, + encryptionKey: string +): Promise { + try { + const { key: fileKey, file: encryptedFiledata } = await encryptFiledata( + worker, + file.filedata + ); + + const { file: encryptedThumbnail }: EncryptionResult = + await worker.encryptThumbnail(file.thumbnail, fileKey); + const { file: encryptedMetadata }: EncryptionResult = + await worker.encryptMetadata(file.metadata, fileKey); + + const encryptedKey: B64EncryptionResult = await worker.encryptToB64( + fileKey, + encryptionKey + ); + + const result: EncryptedFile = { + file: { + file: encryptedFiledata, + thumbnail: encryptedThumbnail, + metadata: encryptedMetadata, + filename: file.metadata.title, + }, + fileKey: encryptedKey, + }; + return result; + } catch (e) { + logError(e, 'Error encrypting files'); + throw e; + } +} diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts new file mode 100644 index 000000000..982e6f1e1 --- /dev/null +++ b/src/services/upload/livePhotoService.ts @@ -0,0 +1,81 @@ +import { FILE_TYPE } from 'constants/file'; +import { encodeMotionPhoto } from 'services/motionPhotoService'; +import { Collection } from 'types/collection'; +import { FileTypeInfo, isDataStream, LivePhotoAssets } from 'types/upload'; +import { splitFilenameAndExtension } from 'utils/file'; +import { getFileMetadata, readFile } from './fileService'; +import { getFileType } from './readFileService'; + +export async function getLivePhotoFileType( + worker, + livePhotoAssets: LivePhotoAssets +) { + const file1TypeInfo = await getFileType(worker, livePhotoAssets[0]); + const file2TypeInfo = await getFileType(worker, livePhotoAssets[1]); + return { + fileType: FILE_TYPE.LIVE_PHOTO, + exactType: `${file1TypeInfo.exactType}+${file2TypeInfo.exactType}`, + }; +} + +export function getLivePhotoSize(livePhotoAssets: LivePhotoAssets) { + return livePhotoAssets[0].size + livePhotoAssets[1].size; +} + +export async function getLivePhotoMetadata( + livePhotoAssets: LivePhotoAssets, + collection: Collection, + fileTypeInfo: FileTypeInfo +) { + const imageMetadata = await getFileMetadata( + livePhotoAssets.image, + collection, + fileTypeInfo + ); + const videoMetadata = await getFileMetadata( + livePhotoAssets.video, + collection, + fileTypeInfo + ); + return { + ...videoMetadata, + ...imageMetadata, + title: splitFilenameAndExtension(livePhotoAssets[0].name)[0], + }; +} + +export async function readLivePhoto( + worker, + reader: FileReader, + fileTypeInfo: FileTypeInfo, + livePhotoAssets: LivePhotoAssets +) { + const image = await readFile( + worker, + reader, + fileTypeInfo, + livePhotoAssets.image + ); + const video = await readFile( + worker, + reader, + fileTypeInfo, + livePhotoAssets.video + ); + + if (isDataStream(video.filedata) || isDataStream(image.filedata)) { + throw new Error('too large live photo assets'); + } + return { + filedata: await encodeMotionPhoto({ + image: image.filedata as Uint8Array, + video: video.filedata as Uint8Array, + imageNameTitle: livePhotoAssets.image.name, + videoNameTitle: livePhotoAssets.video.name, + }), + thumbnail: video.hasStaticThumbnail ? video.thumbnail : image.thumbnail, + hasStaticThumbnail: !( + !video.hasStaticThumbnail || !image.hasStaticThumbnail + ), + }; +} diff --git a/src/services/upload/readFileService.ts b/src/services/upload/readFileService.ts index 04668ab76..19d4d186d 100644 --- a/src/services/upload/readFileService.ts +++ b/src/services/upload/readFileService.ts @@ -1,10 +1,6 @@ import { FILE_TYPE } from 'constants/file'; import { logError } from 'utils/sentry'; -import { - FILE_READER_CHUNK_SIZE, - FORMAT_MISSED_BY_FILE_TYPE_LIB, - MULTIPART_PART_SIZE, -} from 'constants/upload'; +import { FILE_READER_CHUNK_SIZE, MULTIPART_PART_SIZE } from 'constants/upload'; import FileType from 'file-type/browser'; import { CustomError } from 'utils/error'; import { getFileExtension, splitFilenameAndExtension } from 'utils/file'; @@ -47,15 +43,6 @@ export async function getFileType( return { fileType, exactType: typeParts[1] }; } catch (e) { const fileFormat = getFileExtension(receivedFile.name); - const formatMissedByTypeDetection = FORMAT_MISSED_BY_FILE_TYPE_LIB.find( - (a) => a.exactType === fileFormat - ); - if (formatMissedByTypeDetection) { - return formatMissedByTypeDetection; - } - logError(e, CustomError.TYPE_DETECTION_FAILED, { - fileFormat, - }); return { fileType: FILE_TYPE.OTHERS, exactType: fileFormat }; } } diff --git a/src/services/upload/uploadService.ts b/src/services/upload/uploadService.ts index 54b65b46c..50ae91967 100644 --- a/src/services/upload/uploadService.ts +++ b/src/services/upload/uploadService.ts @@ -1,29 +1,35 @@ import { Collection } from 'types/collection'; import { logError } from 'utils/sentry'; import UploadHttpClient from './uploadHttpClient'; -import { extractMetadata, getMetadataMapKey } from './metadataService'; -import { generateThumbnail } from './thumbnailService'; -import { getFileOriginalName, getFileData } from './readFileService'; -import { encryptFiledata } from './encryptionService'; -import { uploadStreamUsingMultipart } from './multiPartUploadService'; -import UIService from './uiService'; -import { handleUploadError } from 'utils/error'; +import { getFileMetadata } from './fileService'; +import { getFileType } from './readFileService'; +import { CustomError, handleUploadError } from 'utils/error'; import { - B64EncryptionResult, - BackupedFile, - EncryptedFile, - EncryptionResult, - FileInMemory, FileTypeInfo, - FileWithMetadata, - isDataStream, MetadataMap, Metadata, ParsedMetadataJSON, + UploadURL, + UploadAsset, + B64EncryptionResult, + BackupedFile, + isDataStream, ProcessedFile, UploadFile, - UploadURL, + FileWithMetadata, + EncryptedFile, } from 'types/upload'; +import { FILE_TYPE } from 'constants/file'; +import { FORMAT_MISSED_BY_FILE_TYPE_LIB } from 'constants/upload'; +import { + getLivePhotoFileType, + getLivePhotoMetadata, + getLivePhotoSize, + readLivePhoto, +} from './livePhotoService'; +import { encryptFile, getFileSize, readFile } from './fileService'; +import { uploadStreamUsingMultipart } from './multiPartUploadService'; +import UIService from './uiService'; class UploadService { private uploadURLs: UploadURL[] = []; @@ -40,85 +46,68 @@ class UploadService { this.pendingUploadCount--; } - async readFile( - worker: any, - reader: FileReader, - rawFile: File, - fileTypeInfo: FileTypeInfo - ): Promise { - const { thumbnail, hasStaticThumbnail } = await generateThumbnail( - worker, - reader, - rawFile, - fileTypeInfo - ); - - const filedata = await getFileData(reader, rawFile); - - return { - filedata, - thumbnail, - hasStaticThumbnail, - }; + getAssetSize({ isLivePhoto, file, livePhotoAssets }: UploadAsset) { + return isLivePhoto + ? getLivePhotoSize(livePhotoAssets) + : getFileSize(file); } - async getFileMetadata( - rawFile: File, + async getAssetType( + worker, + { file, isLivePhoto, livePhotoAssets }: UploadAsset + ) { + const fileTypeInfo = isLivePhoto + ? await getLivePhotoFileType(worker, livePhotoAssets) + : await getFileType(worker, file); + if (fileTypeInfo.fileType !== FILE_TYPE.OTHERS) { + return fileTypeInfo; + } + try { + const formatMissedByTypeDetection = + FORMAT_MISSED_BY_FILE_TYPE_LIB.find( + (a) => a.exactType === fileTypeInfo.exactType + ); + if (formatMissedByTypeDetection) { + return formatMissedByTypeDetection; + } + throw Error(CustomError.UNSUPPORTED_FILE_FORMAT); + } catch (e) { + logError(e, CustomError.TYPE_DETECTION_FAILED, { + fileType: fileTypeInfo.exactType, + }); + } + } + async readAsset( + worker: any, + reader: FileReader, + fileTypeInfo: FileTypeInfo, + { isLivePhoto, file, livePhotoAssets }: UploadAsset + ) { + return isLivePhoto + ? await readLivePhoto(worker, reader, fileTypeInfo, livePhotoAssets) + : await readFile(worker, reader, fileTypeInfo, file); + } + + async getAssetMetadata( + { isLivePhoto, file, livePhotoAssets }: UploadAsset, collection: Collection, fileTypeInfo: FileTypeInfo ): Promise { - const originalName = getFileOriginalName(rawFile); - const googleMetadata = - this.metadataMap.get( - getMetadataMapKey(collection.id, originalName) - ) ?? {}; - const extractedMetadata: Metadata = await extractMetadata( - rawFile, - fileTypeInfo - ); - - for (const [key, value] of Object.entries(googleMetadata)) { - if (!value) { - continue; - } - extractedMetadata[key] = value; - } - return extractedMetadata; + return isLivePhoto + ? await getLivePhotoMetadata( + livePhotoAssets, + collection, + fileTypeInfo + ) + : await getFileMetadata(file, collection, fileTypeInfo); } - async encryptFile( + async encryptAsset( worker: any, file: FileWithMetadata, encryptionKey: string ): Promise { - try { - const { key: fileKey, file: encryptedFiledata } = - await encryptFiledata(worker, file.filedata); - - const { file: encryptedThumbnail }: EncryptionResult = - await worker.encryptThumbnail(file.thumbnail, fileKey); - const { file: encryptedMetadata }: EncryptionResult = - await worker.encryptMetadata(file.metadata, fileKey); - - const encryptedKey: B64EncryptionResult = await worker.encryptToB64( - fileKey, - encryptionKey - ); - - const result: EncryptedFile = { - file: { - file: encryptedFiledata, - thumbnail: encryptedThumbnail, - metadata: encryptedMetadata, - filename: file.metadata.title, - }, - fileKey: encryptedKey, - }; - return result; - } catch (e) { - logError(e, 'Error encrypting files'); - throw e; - } + return encryptFile(worker, file, encryptionKey); } async uploadToBucket(file: ProcessedFile): Promise { diff --git a/src/services/upload/uploader.ts b/src/services/upload/uploader.ts index 5b0a12b16..d7a4bdef9 100644 --- a/src/services/upload/uploader.ts +++ b/src/services/upload/uploader.ts @@ -1,13 +1,11 @@ import { EnteFile } from 'types/file'; import { handleUploadError, CustomError } from 'utils/error'; -import { decryptFile, splitFilenameAndExtension } from 'utils/file'; +import { decryptFile } from 'utils/file'; import { logError } from 'utils/sentry'; import { fileAlreadyInCollection } from 'utils/upload'; import UploadHttpClient from './uploadHttpClient'; import UIService from './uiService'; import UploadService from './uploadService'; -import uploadService from './uploadService'; -import { getFileType } from './readFileService'; import { BackupedFile, EncryptedFile, @@ -15,13 +13,10 @@ import { FileTypeInfo, FileWithCollection, FileWithMetadata, - isDataStream, Metadata, UploadFile, } from 'types/upload'; -import { FILE_TYPE } from 'constants/file'; import { FileUploadResults } from 'constants/upload'; -import { encodeMotionPhoto } from 'services/motionPhotoService'; const FIVE_GB_IN_BYTES = 5 * 1024 * 1024 * 1024; interface UploadResponse { @@ -35,11 +30,9 @@ export default async function uploader( fileWithCollection: FileWithCollection ): Promise { const { - file: rawFile, - isLivePhoto, - livePhotoAsset, collection, key: progressBarKey, + ...uploadAsset } = fileWithCollection; let file: FileInMemory = null; @@ -49,93 +42,31 @@ export default async function uploader( let fileWithMetadata: FileWithMetadata = null; UIService.setFileProgress(progressBarKey, 0); - const fileSize = isLivePhoto - ? livePhotoAsset[0].size + livePhotoAsset[1].size - : rawFile.size; + const fileSize = UploadService.getAssetSize(uploadAsset); try { if (fileSize >= FIVE_GB_IN_BYTES) { return { fileUploadResult: FileUploadResults.TOO_LARGE }; } - if (isLivePhoto) { - const file1TypeInfo = await getFileType(worker, livePhotoAsset[0]); - const file2TypeInfo = await getFileType(worker, livePhotoAsset[1]); - fileTypeInfo = { - fileType: FILE_TYPE.LIVE_PHOTO, - exactType: `${file1TypeInfo.exactType}+${file2TypeInfo.exactType}`, - }; - let imageFile: globalThis.File; - let videoFile: globalThis.File; - const imageMetadata = await uploadService.getFileMetadata( - imageFile, - collection, - fileTypeInfo - ); - const videoMetadata = await uploadService.getFileMetadata( - videoFile, - collection, - fileTypeInfo - ); - metadata = { - ...videoMetadata, - ...imageMetadata, - title: splitFilenameAndExtension(livePhotoAsset[0].name)[0], - }; - if (fileAlreadyInCollection(existingFilesInCollection, metadata)) { - return { fileUploadResult: FileUploadResults.ALREADY_UPLOADED }; - } - const image = await UploadService.readFile( - worker, - reader, - imageFile, - fileTypeInfo - ); - const video = await UploadService.readFile( - worker, - reader, - videoFile, - fileTypeInfo - ); + fileTypeInfo = await UploadService.getAssetType(worker, uploadAsset); - if (isDataStream(video.filedata) || isDataStream(image.filedata)) { - throw new Error('too large live photo assets'); - } - file = { - filedata: await encodeMotionPhoto({ - image: image.filedata as Uint8Array, - video: video.filedata as Uint8Array, - imageNameTitle: imageFile.name, - videoNameTitle: videoFile.name, - }), - thumbnail: video.hasStaticThumbnail - ? video.thumbnail - : image.thumbnail, - hasStaticThumbnail: !( - !video.hasStaticThumbnail || !image.hasStaticThumbnail - ), - }; - } else { - fileTypeInfo = await getFileType(worker, rawFile); - if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) { - throw Error(CustomError.UNSUPPORTED_FILE_FORMAT); - } - metadata = await uploadService.getFileMetadata( - rawFile, - collection, - fileTypeInfo - ); + metadata = await UploadService.getAssetMetadata( + uploadAsset, + collection, + fileTypeInfo + ); - if (fileAlreadyInCollection(existingFilesInCollection, metadata)) { - return { fileUploadResult: FileUploadResults.ALREADY_UPLOADED }; - } - - file = await UploadService.readFile( - worker, - reader, - rawFile, - fileTypeInfo - ); + if (fileAlreadyInCollection(existingFilesInCollection, metadata)) { + return { fileUploadResult: FileUploadResults.ALREADY_UPLOADED }; } + + file = await UploadService.readAsset( + worker, + reader, + fileTypeInfo, + uploadAsset + ); + if (file.hasStaticThumbnail) { metadata.hasStaticThumbnail = true; } @@ -145,7 +76,7 @@ export default async function uploader( metadata, }; - encryptedFile = await UploadService.encryptFile( + encryptedFile = await UploadService.encryptAsset( worker, fileWithMetadata, collection.key diff --git a/src/types/upload/index.ts b/src/types/upload/index.ts index 08d1ca17b..4553daf16 100644 --- a/src/types/upload/index.ts +++ b/src/types/upload/index.ts @@ -63,11 +63,18 @@ export interface ProgressUpdater { setUploadResult: React.Dispatch>>; } -export interface FileWithCollection { +export interface UploadAsset { + isLivePhoto: boolean; + file: File; + livePhotoAssets: LivePhotoAssets; +} +export interface LivePhotoAssets { + image: globalThis.File; + video: globalThis.File; +} + +export interface FileWithCollection extends UploadAsset { key?: string; - isLivePhoto?: boolean; - file?: globalThis.File; - livePhotoAsset?: { image: globalThis.File; video: globalThis.File }; collection?: Collection; collectionID?: number; } From 10be89e72c1aeded4e635ad44437e8feef472718 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Sun, 13 Feb 2022 09:52:59 +0530 Subject: [PATCH 10/60] fix minor issues --- src/types/upload/index.ts | 6 +++--- src/utils/upload/index.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/types/upload/index.ts b/src/types/upload/index.ts index 4553daf16..33310501b 100644 --- a/src/types/upload/index.ts +++ b/src/types/upload/index.ts @@ -64,9 +64,9 @@ export interface ProgressUpdater { } export interface UploadAsset { - isLivePhoto: boolean; - file: File; - livePhotoAssets: LivePhotoAssets; + isLivePhoto?: boolean; + file?: File; + livePhotoAssets?: LivePhotoAssets; } export interface LivePhotoAssets { image: globalThis.File; diff --git a/src/utils/upload/index.ts b/src/utils/upload/index.ts index 67008c73e..e78de1930 100644 --- a/src/utils/upload/index.ts +++ b/src/utils/upload/index.ts @@ -91,7 +91,7 @@ export function segregateFiles( livePhotoFiles.push({ collectionID: collectionID, isLivePhoto: true, - livePhotoAsset: { image: imageFile, video: videoFile }, + livePhotoAssets: { image: imageFile, video: videoFile }, }); } } @@ -114,7 +114,7 @@ export function addKeysToFilesToBeUploaded(files: FileWithCollection[]) { function getFileToBeUploadedKey(fileWithCollection: FileWithCollection) { const fileName = splitFilenameAndExtension( fileWithCollection.isLivePhoto - ? fileWithCollection.livePhotoAsset[0].name + '-livePhoto' + ? fileWithCollection.livePhotoAssets[0].name + '-livePhoto' : fileWithCollection.file.name )[0]; return fileName; From 0fff2ee6748bff25224d23770de9e03fec5ba17c Mon Sep 17 00:00:00 2001 From: Abhinav Date: Sun, 13 Feb 2022 13:43:14 +0530 Subject: [PATCH 11/60] fix minor bugs --- src/components/pages/gallery/Upload.tsx | 6 +- src/services/upload/fileService.ts | 4 +- src/services/upload/livePhotoService.ts | 74 +++++++++++++++++-------- src/services/upload/readFileService.ts | 15 ++++- src/services/upload/uiService.ts | 26 +++++---- src/services/upload/uploadManager.ts | 16 +++++- src/services/upload/uploadService.ts | 31 +++-------- src/services/upload/uploader.ts | 8 --- src/types/upload/index.ts | 2 +- 9 files changed, 107 insertions(+), 75 deletions(-) diff --git a/src/components/pages/gallery/Upload.tsx b/src/components/pages/gallery/Upload.tsx index d830ef2c2..e0fbc91b9 100644 --- a/src/components/pages/gallery/Upload.tsx +++ b/src/components/pages/gallery/Upload.tsx @@ -77,6 +77,7 @@ export default function Upload(props: Props) { setFileProgress, setUploadResult, setUploadStage, + setFilenames, }, props.setFiles ); @@ -121,11 +122,6 @@ export default function Upload(props: Props) { if (props.acceptedFiles.length === 0) { return null; } - setFilenames( - new Map( - props.acceptedFiles.map((file, index) => [index, file.name]) - ) - ); const paths: string[] = props.acceptedFiles.map((file) => file['path']); const getCharCount = (str: string) => (str.match(/\//g) ?? []).length; paths.sort((path1, path2) => getCharCount(path1) - getCharCount(path2)); diff --git a/src/services/upload/fileService.ts b/src/services/upload/fileService.ts index 968d4020a..3ad7dcced 100644 --- a/src/services/upload/fileService.ts +++ b/src/services/upload/fileService.ts @@ -6,6 +6,7 @@ import { EncryptedFile, EncryptionResult, FileWithMetadata, + ParsedMetadataJSONMap, } from 'types/upload'; import { logError } from 'utils/sentry'; import { encryptFiledata } from './encryptionService'; @@ -40,13 +41,14 @@ export async function readFile( } export async function getFileMetadata( + parsedMetadataJSONMap: ParsedMetadataJSONMap, rawFile: File, collectionID: number, fileTypeInfo: FileTypeInfo ) { const originalName = getFileOriginalName(rawFile); const googleMetadata = - this.metadataMap.get( + parsedMetadataJSONMap.get( getMetadataJSONMapKey(collectionID, originalName) ) ?? {}; const extractedMetadata: Metadata = await extractMetadata( diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index 4ba394426..4610fc305 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -9,23 +9,33 @@ import { } from 'types/upload'; import { splitFilenameAndExtension } from 'utils/file'; import { readFile } from './fileService'; -import { getFileType } from './readFileService'; import uploadService from './uploadService'; import UploadService from './uploadService'; -export async function getLivePhotoFileType( - worker, - livePhotoAssets: LivePhotoAssets + +export function getLivePhotoFileType( + file1TypeInfo: FileTypeInfo, + file2TypeInfo: FileTypeInfo ) { - const file1TypeInfo = await getFileType(worker, livePhotoAssets[0]); - const file2TypeInfo = await getFileType(worker, livePhotoAssets[1]); return { fileType: FILE_TYPE.LIVE_PHOTO, exactType: `${file1TypeInfo.exactType}+${file2TypeInfo.exactType}`, }; } +export function getLivePhotoMetadata( + file1Metadata: Metadata, + file2Metadata: Metadata +) { + return { + ...file1Metadata, + ...file2Metadata, + title: splitFilenameAndExtension(file1Metadata.title)[0], + fileType: FILE_TYPE.LIVE_PHOTO, + }; +} + export function getLivePhotoSize(livePhotoAssets: LivePhotoAssets) { - return livePhotoAssets[0].size + livePhotoAssets[1].size; + return livePhotoAssets.image.size + livePhotoAssets.video.size; } export async function readLivePhoto( @@ -37,13 +47,13 @@ export async function readLivePhoto( const image = await readFile( worker, reader, - fileTypeInfo, + { exactType: fileTypeInfo.exactType, fileType: FILE_TYPE.IMAGE }, livePhotoAssets.image ); const video = await readFile( worker, reader, - fileTypeInfo, + { exactType: fileTypeInfo.exactType, fileType: FILE_TYPE.VIDEO }, livePhotoAssets.video ); @@ -66,9 +76,14 @@ export async function readLivePhoto( export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { const analysedMediaFiles: FileWithCollection[] = []; - for (let i = 0; i < mediaFiles.length - 1; i++) { + mediaFiles.sort((media1Files, media2Files) => + splitFilenameAndExtension(media1Files.file.name)[0].localeCompare( + splitFilenameAndExtension(media2Files.file.name)[0] + ) + ); + for (let i = 0; i < mediaFiles.length - 1; i += 2) { const mediaFile1 = mediaFiles[i]; - const mediaFile2 = mediaFiles[i]; + const mediaFile2 = mediaFiles[i + 1]; const { fileTypeInfo: file1TypeInfo, metadata: file1Metadata } = UploadService.getFileMetadataAndFileTypeInfo(mediaFile1.localID); const { fileTypeInfo: file2TypeInfo, metadata: file2Metadata } = @@ -93,15 +108,14 @@ export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { isLivePhoto: true, livePhotoAssets: { image: imageFile, video: videoFile }, }); - const livePhotoFileTypeInfo: FileTypeInfo = { - fileType: FILE_TYPE.LIVE_PHOTO, - exactType: `${file1TypeInfo.exactType} - ${file2TypeInfo.exactType}`, - }; - const livePhotoMetadata: Metadata = { - ...file1Metadata, - ...file2Metadata, - title: splitFilenameAndExtension(file1Metadata.title)[0], - }; + const livePhotoFileTypeInfo: FileTypeInfo = getLivePhotoFileType( + file1TypeInfo, + file2TypeInfo + ); + const livePhotoMetadata: Metadata = getLivePhotoMetadata( + file1Metadata, + file2Metadata + ); uploadService.setFileMetadataAndFileTypeInfo(livePhotoLocalID, { fileTypeInfo: { ...livePhotoFileTypeInfo }, metadata: { ...livePhotoMetadata }, @@ -121,10 +135,22 @@ function areFilesLivePhotoAssets( mediaFile1: FileWithCollection, mediaFile2: FileWithCollection ) { - const { collectionID: file1collectionID, file: file1 } = mediaFile1; - const { collectionID: file2collectionID, file: file2 } = mediaFile2; - const file1Type = FILE_TYPE.OTHERS; - const file2Type = FILE_TYPE.OTHERS; + const { + collectionID: file1collectionID, + file: file1, + localID: localID1, + } = mediaFile1; + const { + collectionID: file2collectionID, + file: file2, + localID: localID2, + } = mediaFile2; + const { + fileTypeInfo: { fileType: file1Type }, + } = UploadService.getFileMetadataAndFileTypeInfo(localID1); + const { + fileTypeInfo: { fileType: file2Type }, + } = UploadService.getFileMetadataAndFileTypeInfo(localID2); return ( file1collectionID === file2collectionID && file1Type !== file2Type && diff --git a/src/services/upload/readFileService.ts b/src/services/upload/readFileService.ts index 0c6cab57d..2eb19a09d 100644 --- a/src/services/upload/readFileService.ts +++ b/src/services/upload/readFileService.ts @@ -1,6 +1,10 @@ import { FILE_TYPE } from 'constants/file'; import { logError } from 'utils/sentry'; -import { FILE_READER_CHUNK_SIZE, MULTIPART_PART_SIZE } from 'constants/upload'; +import { + FILE_READER_CHUNK_SIZE, + FORMAT_MISSED_BY_FILE_TYPE_LIB, + MULTIPART_PART_SIZE, +} from 'constants/upload'; import FileType from 'file-type/browser'; import { CustomError } from 'utils/error'; import { getFileExtension, splitFilenameAndExtension } from 'utils/file'; @@ -43,6 +47,15 @@ export async function getFileType( return { fileType, exactType: typeParts[1] }; } catch (e) { const fileFormat = getFileExtension(receivedFile.name); + const formatMissedByTypeDetection = FORMAT_MISSED_BY_FILE_TYPE_LIB.find( + (a) => a.exactType === fileFormat + ); + if (formatMissedByTypeDetection) { + return formatMissedByTypeDetection; + } + logError(e, CustomError.TYPE_DETECTION_FAILED, { + fileType: fileFormat, + }); return { fileType: FILE_TYPE.OTHERS, exactType: fileFormat }; } } diff --git a/src/services/upload/uiService.ts b/src/services/upload/uiService.ts index 953de78b4..5018b9d34 100644 --- a/src/services/upload/uiService.ts +++ b/src/services/upload/uiService.ts @@ -43,6 +43,10 @@ class UIService { this.progressUpdater.setPercentComplete(percent); } + setFilenames(filenames: Map) { + this.progressUpdater.setFilenames(filenames); + } + increaseFileUploaded() { this.filesUploaded++; this.updateProgressBarUI(); @@ -97,18 +101,16 @@ class UIService { return { cancel, onUploadProgress: (event) => { - fileLocalID && - this.fileProgress.set( - fileLocalID, - Math.min( - Math.round( - percentPerPart * index + - (percentPerPart * event.loaded) / - event.total - ), - 98 - ) - ); + this.fileProgress.set( + fileLocalID, + Math.min( + Math.round( + percentPerPart * index + + (percentPerPart * event.loaded) / event.total + ), + 98 + ) + ); this.updateProgressBarUI(); if (event.loaded === event.total) { clearTimeout(timeout); diff --git a/src/services/upload/uploadManager.ts b/src/services/upload/uploadManager.ts index ec8f4119d..5b8881af1 100644 --- a/src/services/upload/uploadManager.ts +++ b/src/services/upload/uploadManager.ts @@ -31,6 +31,7 @@ import { } from 'constants/upload'; import { ComlinkWorker } from 'utils/comlink'; import { FILE_TYPE } from 'constants/file'; +import uiService from './uiService'; const MAX_CONCURRENT_UPLOADS = 4; const FILE_UPLOAD_COMPLETED = 100; @@ -96,7 +97,19 @@ class UploadManager { ); UIService.setUploadStage(UPLOAD_STAGES.START); const analysedMediaFiles = - UploadService.clusterLivePhotoFiles(mediaFiles); + mediaFiles.length > 1 + ? UploadService.clusterLivePhotoFiles(mediaFiles) + : mediaFiles; + uiService.setFilenames( + new Map( + mediaFiles.map(({ localID }) => [ + localID, + UploadService.getFileMetadataAndFileTypeInfo( + localID + ).metadata.title, + ]) + ) + ); await this.uploadMediaFiles(analysedMediaFiles); } UIService.setUploadStage(UPLOAD_STAGES.FINISH); @@ -174,6 +187,7 @@ class UploadManager { } private async uploadMediaFiles(mediaFiles: FileWithCollection[]) { + this.filesToBeUploaded.push(...mediaFiles); UIService.reset(mediaFiles.length); await UploadService.setFileCount(mediaFiles.length); diff --git a/src/services/upload/uploadService.ts b/src/services/upload/uploadService.ts index 38875615a..097b748ac 100644 --- a/src/services/upload/uploadService.ts +++ b/src/services/upload/uploadService.ts @@ -3,7 +3,7 @@ import { logError } from 'utils/sentry'; import UploadHttpClient from './uploadHttpClient'; import { getFileMetadata } from './fileService'; import { getFileType } from './readFileService'; -import { CustomError, handleUploadError } from 'utils/error'; +import { handleUploadError } from 'utils/error'; import { B64EncryptionResult, BackupedFile, @@ -22,8 +22,6 @@ import { UploadFile, UploadURL, } from 'types/upload'; -import { FILE_TYPE } from 'constants/file'; -import { FORMAT_MISSED_BY_FILE_TYPE_LIB } from 'constants/upload'; import { clusterLivePhotoFiles, getLivePhotoSize, @@ -71,25 +69,9 @@ class UploadService { } async getFileType(reader: FileReader, file: File) { - const fileTypeInfo = await getFileType(reader, file); - if (fileTypeInfo.fileType !== FILE_TYPE.OTHERS) { - return fileTypeInfo; - } - try { - const formatMissedByTypeDetection = - FORMAT_MISSED_BY_FILE_TYPE_LIB.find( - (a) => a.exactType === fileTypeInfo.exactType - ); - if (formatMissedByTypeDetection) { - return formatMissedByTypeDetection; - } - throw Error(CustomError.UNSUPPORTED_FILE_FORMAT); - } catch (e) { - logError(e, CustomError.TYPE_DETECTION_FAILED, { - fileType: fileTypeInfo.exactType, - }); - } + return getFileType(reader, file); } + async readAsset( worker: any, reader: FileReader, @@ -106,7 +88,12 @@ class UploadService { collectionID: number, fileTypeInfo: FileTypeInfo ): Promise { - return getFileMetadata(file, collectionID, fileTypeInfo); + return getFileMetadata( + this.parsedMetadataJSONMap, + file, + collectionID, + fileTypeInfo + ); } getFileMetadataAndFileTypeInfo(localID: number) { diff --git a/src/services/upload/uploader.ts b/src/services/upload/uploader.ts index ae0eaf678..083ffb2c9 100644 --- a/src/services/upload/uploader.ts +++ b/src/services/upload/uploader.ts @@ -37,14 +37,6 @@ export default async function uploader( throw Error(CustomError.NO_METADATA); } - // fileTypeInfo = await UploadService.getAssetType(worker, uploadAsset); - - // metadata = await UploadService.getAssetMetadata( - // uploadAsset, - // collection, - // fileTypeInfo - // ); - if (fileAlreadyInCollection(existingFilesInCollection, metadata)) { return { fileUploadResult: FileUploadResults.ALREADY_UPLOADED }; } diff --git a/src/types/upload/index.ts b/src/types/upload/index.ts index 4bd152441..a83490144 100644 --- a/src/types/upload/index.ts +++ b/src/types/upload/index.ts @@ -61,6 +61,7 @@ export interface ProgressUpdater { setUploadStage: React.Dispatch>; setFileProgress: React.Dispatch>>; setUploadResult: React.Dispatch>>; + setFilenames: React.Dispatch>>; } export interface UploadAsset { @@ -78,7 +79,6 @@ export interface FileWithCollection extends UploadAsset { collection?: Collection; collectionID?: number; } -export type MetadataMap = Map; export interface MetadataAndFileTypeInfo { metadata: Metadata; fileTypeInfo: FileTypeInfo; From 86a9b86294ac77057ab85045ce758a65ebe08082 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Sun, 13 Feb 2022 16:26:46 +0530 Subject: [PATCH 12/60] handle metadata null case --- src/services/upload/uploadManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/upload/uploadManager.ts b/src/services/upload/uploadManager.ts index 5b8881af1..3031ca896 100644 --- a/src/services/upload/uploadManager.ts +++ b/src/services/upload/uploadManager.ts @@ -102,11 +102,11 @@ class UploadManager { : mediaFiles; uiService.setFilenames( new Map( - mediaFiles.map(({ localID }) => [ + analysedMediaFiles.map(({ localID, file }) => [ localID, UploadService.getFileMetadataAndFileTypeInfo( localID - ).metadata.title, + )?.metadata?.title ?? file.name, ]) ) ); From 1398ea22b3b50c2e49c96afefaafd85f7a4d9d4a Mon Sep 17 00:00:00 2001 From: Abhinav Date: Sun, 13 Feb 2022 16:34:08 +0530 Subject: [PATCH 13/60] set live photo extension as zip --- src/services/upload/livePhotoService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index 4610fc305..a7a057e18 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -29,7 +29,7 @@ export function getLivePhotoMetadata( return { ...file1Metadata, ...file2Metadata, - title: splitFilenameAndExtension(file1Metadata.title)[0], + title: `${splitFilenameAndExtension(file1Metadata.title)[0]}.zip`, fileType: FILE_TYPE.LIVE_PHOTO, }; } From ccd730cf9fff0985bca46570828902f4d07d5f99 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Sun, 13 Feb 2022 16:40:01 +0530 Subject: [PATCH 14/60] minor changes --- src/services/upload/readFileService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/upload/readFileService.ts b/src/services/upload/readFileService.ts index 2eb19a09d..b40ccfe38 100644 --- a/src/services/upload/readFileService.ts +++ b/src/services/upload/readFileService.ts @@ -54,7 +54,7 @@ export async function getFileType( return formatMissedByTypeDetection; } logError(e, CustomError.TYPE_DETECTION_FAILED, { - fileType: fileFormat, + fileFormat, }); return { fileType: FILE_TYPE.OTHERS, exactType: fileFormat }; } From 04f4fd14e33b87a50147c7dacdecb6bbdb501f5e Mon Sep 17 00:00:00 2001 From: Abhinav Date: Sun, 13 Feb 2022 16:48:42 +0530 Subject: [PATCH 15/60] uploadService getFileMetadata to better name extractFileMetadata to avoid confusion with getFileMetadataAndFileTypeInfo --- src/services/upload/fileService.ts | 2 +- src/services/upload/uploadManager.ts | 2 +- src/services/upload/uploadService.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/services/upload/fileService.ts b/src/services/upload/fileService.ts index 3ad7dcced..9f2d405ec 100644 --- a/src/services/upload/fileService.ts +++ b/src/services/upload/fileService.ts @@ -40,7 +40,7 @@ export async function readFile( }; } -export async function getFileMetadata( +export async function extractFileMetadata( parsedMetadataJSONMap: ParsedMetadataJSONMap, rawFile: File, collectionID: number, diff --git a/src/services/upload/uploadManager.ts b/src/services/upload/uploadManager.ts index 3031ca896..bc511068d 100644 --- a/src/services/upload/uploadManager.ts +++ b/src/services/upload/uploadManager.ts @@ -166,7 +166,7 @@ class UploadManager { return { fileTypeInfo, metadata: null }; } const metadata = - (await UploadService.getFileMetadata( + (await UploadService.extractFileMetadata( file, collectionID, fileTypeInfo diff --git a/src/services/upload/uploadService.ts b/src/services/upload/uploadService.ts index 097b748ac..5f934ceb5 100644 --- a/src/services/upload/uploadService.ts +++ b/src/services/upload/uploadService.ts @@ -1,7 +1,7 @@ import { Collection } from 'types/collection'; import { logError } from 'utils/sentry'; import UploadHttpClient from './uploadHttpClient'; -import { getFileMetadata } from './fileService'; +import { extractFileMetadata } from './fileService'; import { getFileType } from './readFileService'; import { handleUploadError } from 'utils/error'; import { @@ -83,12 +83,12 @@ class UploadService { : await readFile(worker, reader, fileTypeInfo, file); } - async getFileMetadata( + async extractFileMetadata( file: File, collectionID: number, fileTypeInfo: FileTypeInfo ): Promise { - return getFileMetadata( + return extractFileMetadata( this.parsedMetadataJSONMap, file, collectionID, From 2afa7407b9c97c24521a69484fdd0b5206147fa2 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Sun, 13 Feb 2022 16:51:39 +0530 Subject: [PATCH 16/60] fix type uploadResult --- src/components/pages/gallery/Upload.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/pages/gallery/Upload.tsx b/src/components/pages/gallery/Upload.tsx index e0fbc91b9..d85160794 100644 --- a/src/components/pages/gallery/Upload.tsx +++ b/src/components/pages/gallery/Upload.tsx @@ -58,7 +58,7 @@ export default function Upload(props: Props) { const [fileCounter, setFileCounter] = useState({ finished: 0, total: 0 }); const [fileProgress, setFileProgress] = useState(new Map()); const [uploadResult, setUploadResult] = useState( - new Map() + new Map() ); const [percentComplete, setPercentComplete] = useState(0); const [choiceModalView, setChoiceModalView] = useState(false); From c335bc21bea144ad58c86f2ac5aeeb874768e38b Mon Sep 17 00:00:00 2001 From: Abhinav Date: Sun, 13 Feb 2022 16:59:17 +0530 Subject: [PATCH 17/60] no need to sorting before segregateMetadataAndMediaFiles --- src/utils/upload/index.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/utils/upload/index.ts b/src/utils/upload/index.ts index 726a7a0a6..29ed9ecf2 100644 --- a/src/utils/upload/index.ts +++ b/src/utils/upload/index.ts @@ -33,12 +33,6 @@ export function areFilesSame( export function segregateMetadataAndMediaFiles( filesWithCollectionToUpload: FileWithCollection[] ) { - filesWithCollectionToUpload = filesWithCollectionToUpload.sort( - (fileWithCollection1, fileWithCollection2) => - fileWithCollection1.file.name.localeCompare( - fileWithCollection2.file.name - ) - ); const metadataJSONFiles: FileWithCollection[] = []; const mediaFiles: FileWithCollection[] = []; filesWithCollectionToUpload.forEach((fileWithCollection) => { From 941a2f9b76117c81e2ce016351ad1923ce307bb7 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sun, 13 Feb 2022 22:53:24 +0530 Subject: [PATCH 18/60] add dependency on bs58 --- package.json | 1 + yarn.lock | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 322c2ffab..1dae3df28 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "axios": "^0.21.3", "bip39": "^3.0.4", "bootstrap": "^4.5.2", + "bs58" : "^4.0.1", "chrono-node": "^2.2.6", "comlink": "^4.3.0", "debounce-promise": "^3.1.2", diff --git a/yarn.lock b/yarn.lock index 5fd937e4a..41ab0edb9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2079,6 +2079,13 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base-x@^3.0.2: + version "3.0.9" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + dependencies: + safe-buffer "^5.0.1" + base64-js@^1.0.2: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -2269,6 +2276,13 @@ browserslist@^4.16.6, browserslist@^4.17.0: escalade "^3.1.1" node-releases "^1.1.75" +bs58@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo= + dependencies: + base-x "^3.0.2" + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" @@ -5086,7 +5100,7 @@ peek-readable@^4.0.1: integrity sha512-7qmhptnR0WMSpxT5rMHG9bW/mYSR1uqaPFj2MHvT+y/aOUu6msJijpKt5SkTDKySwg65OWG2JwTMBlgcbwMHrQ== "photoswipe@file:./thirdparty/photoswipe": - version "4.1.4" + version "4.1.3" picomatch@^2.0.4, picomatch@^2.2.1: version "2.3.1" From 3fc214962f504eca025beb278f18f111f0fbef94 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sun, 13 Feb 2022 23:28:11 +0530 Subject: [PATCH 19/60] shared-albums: add support for handling both hex and base58 encoding for ck --- src/pages/shared-albums/index.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/shared-albums/index.tsx b/src/pages/shared-albums/index.tsx index 4f245dda3..bd320ba9d 100644 --- a/src/pages/shared-albums/index.tsx +++ b/src/pages/shared-albums/index.tsx @@ -36,6 +36,7 @@ const Loader = () => ( ); +const bs58 = require('bs58'); export default function PublicCollectionGallery() { const token = useRef(null); const collectionKey = useRef(null); @@ -92,10 +93,13 @@ export default function PublicCollectionGallery() { const currentURL = new URL(url.current); const t = currentURL.searchParams.get('t'); const ck = currentURL.hash.slice(1); - const dck = await worker.fromHex(ck); - if (!t || !dck) { + if (!t || !ck) { return; } + const dck = + ck.length < 50 + ? await worker.toB64(bs58.decode(ck)) + : await worker.fromHex(ck); token.current = t; collectionKey.current = dck; url.current = window.location.href; From 94adaaf020f7832f6412f2ba837b10ca2058857d Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sun, 13 Feb 2022 23:41:58 +0530 Subject: [PATCH 20/60] shared-url: use base58 encoding for ck --- src/utils/collection/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/collection/index.ts b/src/utils/collection/index.ts index d28b44678..9dd5c5cda 100644 --- a/src/utils/collection/index.ts +++ b/src/utils/collection/index.ts @@ -16,7 +16,6 @@ import { logError } from 'utils/sentry'; import constants from 'utils/strings/constants'; import { Collection } from 'types/collection'; import { CollectionType } from 'constants/collection'; -import CryptoWorker from 'utils/crypto'; import { getAlbumSiteHost } from 'constants/pages'; export enum COLLECTION_OPS_TYPE { @@ -114,15 +113,16 @@ export async function appendCollectionKeyToShareURL( url: string, collectionKey: string ) { - const worker = await new CryptoWorker(); if (!url) { return null; } + const bs58 = require('bs58'); const sharableURL = new URL(url); if (process.env.NODE_ENV === 'development') { sharableURL.host = getAlbumSiteHost(); sharableURL.protocol = 'http'; } - sharableURL.hash = await worker.toHex(collectionKey); + const bytes = Buffer.from(collectionKey, 'base64'); + sharableURL.hash = bs58.encode(bytes); return sharableURL.href; } From a0ec17490580985e44aded3179b83b6ef865c506 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 05:15:38 +0000 Subject: [PATCH 21/60] Bump follow-redirects from 1.14.7 to 1.14.8 Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.7 to 1.14.8. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.7...v1.14.8) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 41ab0edb9..eb2e9ab46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3472,9 +3472,9 @@ fn-name@~3.0.0: integrity sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA== follow-redirects@^1.0.0, follow-redirects@^1.14.0: - version "1.14.7" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" - integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== + version "1.14.8" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc" + integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA== foreach@^2.0.5: version "2.0.5" @@ -5100,7 +5100,7 @@ peek-readable@^4.0.1: integrity sha512-7qmhptnR0WMSpxT5rMHG9bW/mYSR1uqaPFj2MHvT+y/aOUu6msJijpKt5SkTDKySwg65OWG2JwTMBlgcbwMHrQ== "photoswipe@file:./thirdparty/photoswipe": - version "4.1.3" + version "4.1.4" picomatch@^2.0.4, picomatch@^2.2.1: version "2.3.1" From dab1844223577fc5da776d2f7f4e3441bc958f91 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Mon, 14 Feb 2022 12:44:42 +0530 Subject: [PATCH 22/60] reuse fileTypeInfo and metdata and fix case where live photos clustering --- src/services/upload/livePhotoService.ts | 109 +++++++++++++----------- src/services/upload/uploadManager.ts | 4 +- 2 files changed, 61 insertions(+), 52 deletions(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index a7a057e18..4ea8e447d 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -12,6 +12,12 @@ import { readFile } from './fileService'; import uploadService from './uploadService'; import UploadService from './uploadService'; +interface LivePhotoIdentifier { + collectionID: number; + fileType: FILE_TYPE; + name: string; +} + export function getLivePhotoFileType( file1TypeInfo: FileTypeInfo, file2TypeInfo: FileTypeInfo @@ -81,82 +87,87 @@ export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { splitFilenameAndExtension(media2Files.file.name)[0] ) ); - for (let i = 0; i < mediaFiles.length - 1; i += 2) { - const mediaFile1 = mediaFiles[i]; - const mediaFile2 = mediaFiles[i + 1]; - const { fileTypeInfo: file1TypeInfo, metadata: file1Metadata } = - UploadService.getFileMetadataAndFileTypeInfo(mediaFile1.localID); - const { fileTypeInfo: file2TypeInfo, metadata: file2Metadata } = - UploadService.getFileMetadataAndFileTypeInfo(mediaFile2.localID); - if (areFilesLivePhotoAssets(mediaFile1, mediaFile2)) { + let index = 0; + 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 firstFileIdentifier: LivePhotoIdentifier = { + collectionID: firstMediaFile.collectionID, + fileType: firstFileTypeInfo.fileType, + name: firstFileMetadata.title, + }; + const secondFileIdentifier: LivePhotoIdentifier = { + collectionID: secondMediaFile.collectionID, + fileType: secondFileFileInfo.fileType, + name: secondFileMetadata.title, + }; + if ( + areFilesLivePhotoAssets(firstFileIdentifier, secondFileIdentifier) + ) { let imageFile; let videoFile; if ( - file1TypeInfo.fileType === FILE_TYPE.IMAGE && - file2TypeInfo.fileType === FILE_TYPE.VIDEO + firstFileTypeInfo.fileType === FILE_TYPE.IMAGE && + secondFileFileInfo.fileType === FILE_TYPE.VIDEO ) { - imageFile = mediaFile1.file; - videoFile = mediaFile2.file; + imageFile = firstMediaFile.file; + videoFile = secondMediaFile.file; } else { - imageFile = mediaFile2.file; - videoFile = mediaFile1.file; + imageFile = secondMediaFile.file; + videoFile = firstMediaFile.file; } - const livePhotoLocalID = i; + const livePhotoLocalID = index; analysedMediaFiles.push({ localID: livePhotoLocalID, - collectionID: mediaFile1.collectionID, + collectionID: firstMediaFile.collectionID, isLivePhoto: true, livePhotoAssets: { image: imageFile, video: videoFile }, }); const livePhotoFileTypeInfo: FileTypeInfo = getLivePhotoFileType( - file1TypeInfo, - file2TypeInfo + firstFileTypeInfo, + secondFileFileInfo ); const livePhotoMetadata: Metadata = getLivePhotoMetadata( - file1Metadata, - file2Metadata + firstFileMetadata, + secondFileMetadata ); uploadService.setFileMetadataAndFileTypeInfo(livePhotoLocalID, { fileTypeInfo: { ...livePhotoFileTypeInfo }, metadata: { ...livePhotoMetadata }, }); + index += 2; } else { - analysedMediaFiles.push({ ...mediaFile1, isLivePhoto: false }); - analysedMediaFiles.push({ - ...mediaFile2, - isLivePhoto: false, - }); + analysedMediaFiles.push({ ...firstMediaFile, isLivePhoto: false }); + index += 1; } } + if (index === mediaFiles.length - 1) { + analysedMediaFiles.push({ ...mediaFiles[index], isLivePhoto: false }); + } return analysedMediaFiles; } function areFilesLivePhotoAssets( - mediaFile1: FileWithCollection, - mediaFile2: FileWithCollection + firstFileIdentifier: LivePhotoIdentifier, + secondFileIdentifier: LivePhotoIdentifier ) { - const { - collectionID: file1collectionID, - file: file1, - localID: localID1, - } = mediaFile1; - const { - collectionID: file2collectionID, - file: file2, - localID: localID2, - } = mediaFile2; - const { - fileTypeInfo: { fileType: file1Type }, - } = UploadService.getFileMetadataAndFileTypeInfo(localID1); - const { - fileTypeInfo: { fileType: file2Type }, - } = UploadService.getFileMetadataAndFileTypeInfo(localID2); return ( - file1collectionID === file2collectionID && - file1Type !== file2Type && - file1Type !== FILE_TYPE.OTHERS && - file2Type !== FILE_TYPE.OTHERS && - splitFilenameAndExtension(file1.name)[0] === - splitFilenameAndExtension(file2.name)[0] + firstFileIdentifier.collectionID === + secondFileIdentifier.collectionID && + firstFileIdentifier.fileType !== secondFileIdentifier.fileType && + firstFileIdentifier.fileType !== FILE_TYPE.OTHERS && + secondFileIdentifier.fileType !== FILE_TYPE.OTHERS && + splitFilenameAndExtension(firstFileIdentifier.name)[0] === + splitFilenameAndExtension(secondFileIdentifier.name)[0] ); } diff --git a/src/services/upload/uploadManager.ts b/src/services/upload/uploadManager.ts index bc511068d..0ced3f484 100644 --- a/src/services/upload/uploadManager.ts +++ b/src/services/upload/uploadManager.ts @@ -97,9 +97,7 @@ class UploadManager { ); UIService.setUploadStage(UPLOAD_STAGES.START); const analysedMediaFiles = - mediaFiles.length > 1 - ? UploadService.clusterLivePhotoFiles(mediaFiles) - : mediaFiles; + UploadService.clusterLivePhotoFiles(mediaFiles); uiService.setFilenames( new Map( analysedMediaFiles.map(({ localID, file }) => [ From ed06e1427e2e607c91617b880e838929e7847af3 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Mon, 14 Feb 2022 13:05:22 +0530 Subject: [PATCH 23/60] use video thumbnails for livePhotos --- src/services/upload/livePhotoService.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index 4ea8e447d..eb1de8ec8 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -9,6 +9,7 @@ import { } from 'types/upload'; import { splitFilenameAndExtension } from 'utils/file'; import { readFile } from './fileService'; +import { getFileData } from './readFileService'; import uploadService from './uploadService'; import UploadService from './uploadService'; @@ -50,12 +51,8 @@ export async function readLivePhoto( fileTypeInfo: FileTypeInfo, livePhotoAssets: LivePhotoAssets ) { - const image = await readFile( - worker, - reader, - { exactType: fileTypeInfo.exactType, fileType: FILE_TYPE.IMAGE }, - livePhotoAssets.image - ); + const image = await getFileData(reader, livePhotoAssets.image); + const video = await readFile( worker, reader, @@ -63,20 +60,18 @@ export async function readLivePhoto( livePhotoAssets.video ); - if (isDataStream(video.filedata) || isDataStream(image.filedata)) { + if (isDataStream(video.filedata) || isDataStream(image)) { throw new Error('too large live photo assets'); } return { filedata: await encodeMotionPhoto({ - image: image.filedata as Uint8Array, + image: image as Uint8Array, video: video.filedata as Uint8Array, imageNameTitle: livePhotoAssets.image.name, videoNameTitle: livePhotoAssets.video.name, }), - thumbnail: video.hasStaticThumbnail ? video.thumbnail : image.thumbnail, - hasStaticThumbnail: !( - !video.hasStaticThumbnail || !image.hasStaticThumbnail - ), + thumbnail: video.thumbnail, + hasStaticThumbnail: video.hasStaticThumbnail, }; } From 22a489be6e9946802d2dc55a2820db182de74acb Mon Sep 17 00:00:00 2001 From: Abhinav Date: Mon, 14 Feb 2022 13:17:10 +0530 Subject: [PATCH 24/60] only generate and use image thumbnail for livePhoto --- src/services/upload/livePhotoService.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index eb1de8ec8..de56e68c9 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -51,27 +51,27 @@ export async function readLivePhoto( fileTypeInfo: FileTypeInfo, livePhotoAssets: LivePhotoAssets ) { - const image = await getFileData(reader, livePhotoAssets.image); - - const video = await readFile( + const image = await readFile( worker, reader, - { exactType: fileTypeInfo.exactType, fileType: FILE_TYPE.VIDEO }, - livePhotoAssets.video + { exactType: fileTypeInfo.exactType, fileType: FILE_TYPE.IMAGE }, + livePhotoAssets.image ); - if (isDataStream(video.filedata) || isDataStream(image)) { + const video = await getFileData(reader, livePhotoAssets.video); + + if (isDataStream(video) || isDataStream(image.filedata)) { throw new Error('too large live photo assets'); } return { filedata: await encodeMotionPhoto({ - image: image as Uint8Array, - video: video.filedata as Uint8Array, + image: image.filedata as Uint8Array, + video: video as Uint8Array, imageNameTitle: livePhotoAssets.image.name, videoNameTitle: livePhotoAssets.video.name, }), - thumbnail: video.thumbnail, - hasStaticThumbnail: video.hasStaticThumbnail, + thumbnail: image.thumbnail, + hasStaticThumbnail: image.hasStaticThumbnail, }; } From d1da96653045c48e2b926db459729fcf9517e29e Mon Sep 17 00:00:00 2001 From: Abhinav Date: Mon, 14 Feb 2022 15:47:07 +0530 Subject: [PATCH 25/60] live photo indicator overlay --- .../icons/LivePhotoIndicatorOverlay.tsx | 31 +++++++++++++++++++ src/components/pages/gallery/PreviewCard.tsx | 2 ++ 2 files changed, 33 insertions(+) create mode 100644 src/components/icons/LivePhotoIndicatorOverlay.tsx diff --git a/src/components/icons/LivePhotoIndicatorOverlay.tsx b/src/components/icons/LivePhotoIndicatorOverlay.tsx new file mode 100644 index 000000000..9d5e26ea8 --- /dev/null +++ b/src/components/icons/LivePhotoIndicatorOverlay.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import styled from 'styled-components'; + +const Wrapper = styled.div` + font-size: 10px; + position: absolute; + padding: 2px; + right: 5px; + top: 5px; +`; +export default function LivePhotoIndicatorOverlay(props) { + return ( + + + + + + + ); +} + +LivePhotoIndicatorOverlay.defaultProps = { + height: 20, + width: 20, + viewBox: '0 0 24 24', +}; diff --git a/src/components/pages/gallery/PreviewCard.tsx b/src/components/pages/gallery/PreviewCard.tsx index 404c76749..6dcb7218c 100644 --- a/src/components/pages/gallery/PreviewCard.tsx +++ b/src/components/pages/gallery/PreviewCard.tsx @@ -11,6 +11,7 @@ import { PublicCollectionGalleryContext, } from 'utils/publicCollectionGallery'; import PublicCollectionDownloadManager from 'services/publicCollectionDownloadManager'; +import LivePhotoIndicatorOverlay from 'components/icons/LivePhotoIndicatorOverlay'; interface IProps { file: EnteFile; @@ -283,6 +284,7 @@ export default function PreviewCard(props: IProps) { + ); } From d764cecb4b22cb03c1ad8f23970d15cae6582b46 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Mon, 14 Feb 2022 16:41:03 +0530 Subject: [PATCH 26/60] add notification toast component --- src/components/Notification.tsx | 56 +++++++++++++++++++++++++++++++++ src/pages/_app.tsx | 16 +++++++--- src/pages/gallery/index.tsx | 19 ++++++++++- src/types/gallery/index.ts | 6 ++++ 4 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 src/components/Notification.tsx diff --git a/src/components/Notification.tsx b/src/components/Notification.tsx new file mode 100644 index 000000000..020f75a15 --- /dev/null +++ b/src/components/Notification.tsx @@ -0,0 +1,56 @@ +import React, { useEffect, useState } from 'react'; +import { Toast } from 'react-bootstrap'; +import styled from 'styled-components'; +import { NotificationAttributes } from 'types/gallery'; + +const Wrapper = styled.div` + position: absolute; + top: 60px; + right: 10px; + z-index: 1501; + min-height: 100px; +`; +const AUTO_HIDE_TIME_IN_MILLISECONDS = 3000; + +interface Iprops { + attributes: NotificationAttributes; + clearAttributes: () => void; +} + +export default function Notification({ attributes, clearAttributes }: Iprops) { + const [show, setShow] = useState(false); + const closeToast = () => { + setShow(false); + clearAttributes(); + }; + useEffect(() => { + if (!attributes) { + setShow(false); + } else { + setShow(true); + } + }, [attributes]); + return ( + + + {attributes?.title && ( + +
{attributes.title}
+
+ )} + {attributes?.message && ( + {attributes.message} + )} +
+
+ ); +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 8ba8808a3..7443e4b15 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -134,10 +134,10 @@ const GlobalStyles = createGlobalStyle` min-height: -moz-calc(80% - 3.5rem); min-height: calc(80% - 3.5rem); } - .modal .modal-header, .modal .modal-footer { + .modal .modal-header, .modal .modal-footer , .toast-header{ border-color: #444 !important; } - .modal .modal-header .close { + .modal .modal-header .close, .toast-header .close { color: #aaa; text-shadow: none; } @@ -145,7 +145,11 @@ const GlobalStyles = createGlobalStyle` z-index:2000; opacity:0.8 !important; } - .modal .card , .table { + + .toast-header{ + border-radius:0px !important; + } + .modal .card , .table , .toast { background-color: #202020; border: none; } @@ -154,7 +158,7 @@ const GlobalStyles = createGlobalStyle` overflow: hidden; margin: 0 0 5px 0; } - .modal-content { + .modal-content ,.toast-header{ border-radius:15px; background-color:#202020 !important; } @@ -485,6 +489,10 @@ const GlobalStyles = createGlobalStyle` .form-check-input:hover, .form-check-label :hover{ cursor:pointer; } + + .ente-toast{ + + } `; export const LogoImage = styled.img` diff --git a/src/pages/gallery/index.tsx b/src/pages/gallery/index.tsx index 18a3d2594..d58e21b3b 100644 --- a/src/pages/gallery/index.tsx +++ b/src/pages/gallery/index.tsx @@ -96,9 +96,15 @@ import FixCreationTime, { } from 'components/FixCreationTime'; import { Collection, CollectionAndItsLatestFile } from 'types/collection'; import { EnteFile } from 'types/file'; -import { GalleryContextType, SelectedState, Search } from 'types/gallery'; +import { + GalleryContextType, + SelectedState, + Search, + NotificationAttributes, +} from 'types/gallery'; import Collections from 'components/pages/gallery/Collections'; import { VISIBILITY_STATE } from 'constants/file'; +import Notification from 'components/Notification'; export const DeadCenter = styled.div` flex: 1; @@ -125,6 +131,7 @@ const defaultGalleryContext: GalleryContextType = { setDialogMessage: () => null, startLoading: () => null, finishLoading: () => null, + setNotificationAttributes: () => null, }; export const GalleryContext = createContext( @@ -191,9 +198,14 @@ export default function Gallery() { const [fixCreationTimeAttributes, setFixCreationTimeAttributes] = useState(null); + const [notificationAttributes, setNotificationAttributes] = + useState(null); + const showPlanSelectorModal = () => setPlanModalView(true); const closeMessageDialog = () => setMessageDialogView(false); + const clearNotificationAttributes = () => setNotificationAttributes(null); + useEffect(() => { const key = getKey(SESSION_KEYS.ENCRYPTION_KEY); if (!key) { @@ -555,6 +567,7 @@ export default function Gallery() { setDialogMessage, startLoading, finishLoading, + setNotificationAttributes, }}> + void; finishLoading: () => void; + setNotificationAttributes: (attributes: NotificationAttributes) => void; }; + +export interface NotificationAttributes { + message: string; + title: string; +} From 9ead332d1fe0fe59328e8bfb4dfab4bf6331e262 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Mon, 14 Feb 2022 16:41:16 +0530 Subject: [PATCH 27/60] show live support coming soon message when live photo opened --- src/components/PhotoSwipe/PhotoSwipe.tsx | 16 +++++++++++++++- src/utils/strings/englishConstants.tsx | 2 ++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/components/PhotoSwipe/PhotoSwipe.tsx b/src/components/PhotoSwipe/PhotoSwipe.tsx index 327644956..f7748cd1c 100644 --- a/src/components/PhotoSwipe/PhotoSwipe.tsx +++ b/src/components/PhotoSwipe/PhotoSwipe.tsx @@ -42,7 +42,7 @@ import { Formik } from 'formik'; import * as Yup from 'yup'; import EnteSpinner from 'components/EnteSpinner'; import EnteDateTimePicker from 'components/EnteDateTimePicker'; -import { MAX_EDITED_FILE_NAME_LENGTH } from 'constants/file'; +import { FILE_TYPE, MAX_EDITED_FILE_NAME_LENGTH } from 'constants/file'; import { sleep } from 'utils/common'; import { PublicCollectionGalleryContext } from 'utils/publicCollectionGallery'; import { GalleryContext } from 'pages/gallery'; @@ -525,6 +525,15 @@ function PhotoSwipe(props: Iprops) { setIsFav(isInFav(this?.currItem)); } + function handleLivePhotoNotification() { + if (checkIsLivePhoto(this?.currItem)) { + galleryContext.setNotificationAttributes({ + message: constants.PLAYBACK_SUPPORT_COMING, + title: constants.LIVE_PHOTO, + }); + } + } + const openPhotoSwipe = () => { const { items, currentIndex } = props; const options = { @@ -587,6 +596,7 @@ function PhotoSwipe(props: Iprops) { photoSwipe.listen('beforeChange', function () { updateInfo.call(this); updateFavButton.call(this); + handleLivePhotoNotification.call(this); }); photoSwipe.listen('resize', checkExifAvailable); photoSwipe.init(); @@ -617,6 +627,10 @@ function PhotoSwipe(props: Iprops) { return false; }; + const checkIsLivePhoto = (file: EnteFile) => { + return file.metadata.fileType === FILE_TYPE.LIVE_PHOTO; + }; + const onFavClick = async (file) => { const { favItemIds } = props; if (!isInFav(file)) { diff --git a/src/utils/strings/englishConstants.tsx b/src/utils/strings/englishConstants.tsx index 91d22536f..d5e12d90d 100644 --- a/src/utils/strings/englishConstants.tsx +++ b/src/utils/strings/englishConstants.tsx @@ -653,6 +653,8 @@ const englishConstants = { TERM_3: 'I acknowledge that any person who knowingly materially misrepresents that material or activity is infringing may be subject to liability for damages. ', PRESERVED_BY: 'preserved by', ENTE_IO: 'ente.io', + PLAYBACK_SUPPORT_COMING: 'playback support coming soon...', + LIVE_PHOTO: 'this is a live photo', }; export default englishConstants; From 08cd6e72ea1bf5e72fbb7597f321d4fc354ef3f7 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Mon, 14 Feb 2022 16:43:56 +0530 Subject: [PATCH 28/60] group same collection files together before live photo clustering --- src/services/upload/livePhotoService.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index de56e68c9..093ade7a3 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -77,11 +77,18 @@ export async function readLivePhoto( export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { const analysedMediaFiles: FileWithCollection[] = []; - mediaFiles.sort((media1Files, media2Files) => - splitFilenameAndExtension(media1Files.file.name)[0].localeCompare( - splitFilenameAndExtension(media2Files.file.name)[0] + mediaFiles + .sort((firstMediaFile, secondMediaFile) => + splitFilenameAndExtension( + firstMediaFile.file.name + )[0].localeCompare( + splitFilenameAndExtension(secondMediaFile.file.name)[0] + ) ) - ); + .sort( + (firstMediaFile, secondMediaFile) => + firstMediaFile.collectionID - secondMediaFile.collectionID + ); let index = 0; while (index < mediaFiles.length - 1) { const firstMediaFile = mediaFiles[index]; From 5ff8b857bcdf654d126df01fbff0c21f4c69edba Mon Sep 17 00:00:00 2001 From: Abhinav Date: Tue, 15 Feb 2022 08:12:38 +0530 Subject: [PATCH 29/60] add comment and check to avoid larger than multipart file size files to be clustered as livePhoto --- src/services/upload/livePhotoService.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index 093ade7a3..729063d22 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -1,4 +1,5 @@ import { FILE_TYPE } from 'constants/file'; +import { MULTIPART_PART_SIZE } from 'constants/upload'; import { encodeMotionPhoto } from 'services/motionPhotoService'; import { FileTypeInfo, @@ -17,6 +18,7 @@ interface LivePhotoIdentifier { collectionID: number; fileType: FILE_TYPE; name: string; + size: number; } export function getLivePhotoFileType( @@ -60,6 +62,10 @@ export async function readLivePhoto( const video = await getFileData(reader, livePhotoAssets.video); + /* + did it based on the assumption that live photo assets ideally would not be larger than MULTIPART_PART_SIZE and hence not require to be streamed + also, allowing that would require a small amount of code changes as the zipping library doesn't support stream as a input + */ if (isDataStream(video) || isDataStream(image.filedata)) { throw new Error('too large live photo assets'); } @@ -107,11 +113,13 @@ export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { collectionID: firstMediaFile.collectionID, fileType: firstFileTypeInfo.fileType, name: firstFileMetadata.title, + size: firstMediaFile.file.size, }; const secondFileIdentifier: LivePhotoIdentifier = { collectionID: secondMediaFile.collectionID, fileType: secondFileFileInfo.fileType, name: secondFileMetadata.title, + size: secondMediaFile.file.size, }; if ( areFilesLivePhotoAssets(firstFileIdentifier, secondFileIdentifier) @@ -170,6 +178,8 @@ function areFilesLivePhotoAssets( firstFileIdentifier.fileType !== FILE_TYPE.OTHERS && secondFileIdentifier.fileType !== FILE_TYPE.OTHERS && splitFilenameAndExtension(firstFileIdentifier.name)[0] === - splitFilenameAndExtension(secondFileIdentifier.name)[0] + splitFilenameAndExtension(secondFileIdentifier.name)[0] && + firstFileIdentifier.size <= MULTIPART_PART_SIZE && // so that they are small enough to be read and uploaded in single chunk + secondFileIdentifier.size <= MULTIPART_PART_SIZE ); } From 8b668b924a3f6835e30c2e17cf6dbab58261a30d Mon Sep 17 00:00:00 2001 From: Abhinav Date: Tue, 15 Feb 2022 09:01:19 +0530 Subject: [PATCH 30/60] use file name as metadata for some files may be empty --- src/services/upload/livePhotoService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index 729063d22..81b6ad557 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -112,13 +112,13 @@ export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { const firstFileIdentifier: LivePhotoIdentifier = { collectionID: firstMediaFile.collectionID, fileType: firstFileTypeInfo.fileType, - name: firstFileMetadata.title, + name: firstMediaFile.file.name, size: firstMediaFile.file.size, }; const secondFileIdentifier: LivePhotoIdentifier = { collectionID: secondMediaFile.collectionID, fileType: secondFileFileInfo.fileType, - name: secondFileMetadata.title, + name: secondMediaFile.file.name, size: secondMediaFile.file.size, }; if ( From 80db61133a162db750b98eaba824071247aa2d22 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Tue, 15 Feb 2022 09:20:41 +0530 Subject: [PATCH 31/60] refactored code to not create stream for livePhotodata assets as they zipping library doesn't support streams --- src/constants/upload/index.ts | 2 + src/services/upload/livePhotoService.ts | 64 +++++++++++++++---------- src/utils/error/index.ts | 1 + 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/constants/upload/index.ts b/src/constants/upload/index.ts index 19432605f..2109e566b 100644 --- a/src/constants/upload/index.ts +++ b/src/constants/upload/index.ts @@ -41,3 +41,5 @@ export enum FileUploadResults { } export const MAX_FILE_SIZE_SUPPORTED = 5 * 1024 * 1024 * 1024; // 5 GB + +export const LIVE_PHOTO_ASSET_SIZE_LIMIT = 20 * 1024 * 1024; // 20MB diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index 81b6ad557..eac3e2a79 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -1,16 +1,17 @@ import { FILE_TYPE } from 'constants/file'; -import { MULTIPART_PART_SIZE } from 'constants/upload'; +import { LIVE_PHOTO_ASSET_SIZE_LIMIT } from 'constants/upload'; import { encodeMotionPhoto } from 'services/motionPhotoService'; import { FileTypeInfo, FileWithCollection, - isDataStream, LivePhotoAssets, Metadata, } from 'types/upload'; +import { CustomError } from 'utils/error'; import { splitFilenameAndExtension } from 'utils/file'; -import { readFile } from './fileService'; -import { getFileData } from './readFileService'; +import { logError } from 'utils/sentry'; +import { getUint8ArrayView } from './readFileService'; +import { generateThumbnail } from './thumbnailService'; import uploadService from './uploadService'; import UploadService from './uploadService'; @@ -53,31 +54,26 @@ export async function readLivePhoto( fileTypeInfo: FileTypeInfo, livePhotoAssets: LivePhotoAssets ) { - const image = await readFile( + const { thumbnail, hasStaticThumbnail } = await generateThumbnail( worker, reader, - { exactType: fileTypeInfo.exactType, fileType: FILE_TYPE.IMAGE }, - livePhotoAssets.image + livePhotoAssets.image, + { exactType: fileTypeInfo.exactType, fileType: FILE_TYPE.IMAGE } ); - const video = await getFileData(reader, livePhotoAssets.video); + const image = await getUint8ArrayView(reader, livePhotoAssets.image); + + const video = await getUint8ArrayView(reader, livePhotoAssets.video); - /* - did it based on the assumption that live photo assets ideally would not be larger than MULTIPART_PART_SIZE and hence not require to be streamed - also, allowing that would require a small amount of code changes as the zipping library doesn't support stream as a input - */ - if (isDataStream(video) || isDataStream(image.filedata)) { - throw new Error('too large live photo assets'); - } return { filedata: await encodeMotionPhoto({ - image: image.filedata as Uint8Array, - video: video as Uint8Array, + image, + video, imageNameTitle: livePhotoAssets.image.name, videoNameTitle: livePhotoAssets.video.name, }), - thumbnail: image.thumbnail, - hasStaticThumbnail: image.hasStaticThumbnail, + thumbnail, + hasStaticThumbnail, }; } @@ -171,15 +167,35 @@ function areFilesLivePhotoAssets( firstFileIdentifier: LivePhotoIdentifier, secondFileIdentifier: LivePhotoIdentifier ) { - return ( + if ( firstFileIdentifier.collectionID === secondFileIdentifier.collectionID && firstFileIdentifier.fileType !== secondFileIdentifier.fileType && firstFileIdentifier.fileType !== FILE_TYPE.OTHERS && secondFileIdentifier.fileType !== FILE_TYPE.OTHERS && splitFilenameAndExtension(firstFileIdentifier.name)[0] === - splitFilenameAndExtension(secondFileIdentifier.name)[0] && - firstFileIdentifier.size <= MULTIPART_PART_SIZE && // so that they are small enough to be read and uploaded in single chunk - secondFileIdentifier.size <= MULTIPART_PART_SIZE - ); + splitFilenameAndExtension(secondFileIdentifier.name)[0] + ) { + // checks size of live Photo assets are less than allowed limit + // I did that based on the assumption that live photo assets ideally would not be larger than LIVE_PHOTO_ASSET_SIZE_LIMIT + // also zipping library doesn't support stream as a input + if ( + firstFileIdentifier.size <= LIVE_PHOTO_ASSET_SIZE_LIMIT && + secondFileIdentifier.size <= LIVE_PHOTO_ASSET_SIZE_LIMIT + ) { + return true; + } else { + logError( + new Error(CustomError.TOO_LARGE_LIVE_PHOTO_ASSETS), + CustomError.TOO_LARGE_LIVE_PHOTO_ASSETS, + { + fileSizes: [ + firstFileIdentifier.size, + secondFileIdentifier.size, + ], + } + ); + } + } + return false; } diff --git a/src/utils/error/index.ts b/src/utils/error/index.ts index 9680c7274..57fde1701 100644 --- a/src/utils/error/index.ts +++ b/src/utils/error/index.ts @@ -39,6 +39,7 @@ export enum CustomError { SUBSCRIPTION_NEEDED = 'subscription not present', NOT_FOUND = 'not found ', NO_METADATA = 'no metadata', + TOO_LARGE_LIVE_PHOTO_ASSETS = 'too large live photo assets', } function parseUploadErrorCodes(error) { From 93505f9bc42c5cc3b42ec3e5fb9d5b04efc2da5a Mon Sep 17 00:00:00 2001 From: Abhinav Date: Tue, 15 Feb 2022 09:26:36 +0530 Subject: [PATCH 32/60] show live photo overlay only on live photos --- src/components/pages/gallery/PreviewCard.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/pages/gallery/PreviewCard.tsx b/src/components/pages/gallery/PreviewCard.tsx index 6dcb7218c..2ccab2644 100644 --- a/src/components/pages/gallery/PreviewCard.tsx +++ b/src/components/pages/gallery/PreviewCard.tsx @@ -12,6 +12,7 @@ import { } from 'utils/publicCollectionGallery'; import PublicCollectionDownloadManager from 'services/publicCollectionDownloadManager'; import LivePhotoIndicatorOverlay from 'components/icons/LivePhotoIndicatorOverlay'; +import { FILE_TYPE } from 'constants/file'; interface IProps { file: EnteFile; @@ -284,7 +285,9 @@ export default function PreviewCard(props: IProps) { - + {file?.metadata.fileType === FILE_TYPE.LIVE_PHOTO && ( + + )} ); } From c04056569bf35182ce0284ae7ca138fe95d9742a Mon Sep 17 00:00:00 2001 From: Abhinav Date: Tue, 15 Feb 2022 09:48:38 +0530 Subject: [PATCH 33/60] use image metadata for live photo --- src/services/upload/livePhotoService.ts | 27 +++++++++++++------------ 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index eac3e2a79..b91defe39 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -22,6 +22,8 @@ interface LivePhotoIdentifier { size: number; } +const ENTE_LIVE_PHOTO_FORMAT = 'elp'; + export function getLivePhotoFileType( file1TypeInfo: FileTypeInfo, file2TypeInfo: FileTypeInfo @@ -32,14 +34,12 @@ export function getLivePhotoFileType( }; } -export function getLivePhotoMetadata( - file1Metadata: Metadata, - file2Metadata: Metadata -) { +export function getLivePhotoMetadata(imageMetadata: Metadata) { return { - ...file1Metadata, - ...file2Metadata, - title: `${splitFilenameAndExtension(file1Metadata.title)[0]}.zip`, + ...imageMetadata, + title: `${ + splitFilenameAndExtension(imageMetadata.title)[0] + }.${ENTE_LIVE_PHOTO_FORMAT}`, fileType: FILE_TYPE.LIVE_PHOTO, }; } @@ -120,17 +120,20 @@ export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { if ( areFilesLivePhotoAssets(firstFileIdentifier, secondFileIdentifier) ) { - let imageFile; - let videoFile; + let imageFile: File; + let videoFile: File; + let imageMetadata: Metadata; if ( firstFileTypeInfo.fileType === FILE_TYPE.IMAGE && secondFileFileInfo.fileType === FILE_TYPE.VIDEO ) { imageFile = firstMediaFile.file; videoFile = secondMediaFile.file; + imageMetadata = secondFileMetadata; } else { imageFile = secondMediaFile.file; videoFile = firstMediaFile.file; + imageMetadata = firstFileMetadata; } const livePhotoLocalID = index; analysedMediaFiles.push({ @@ -143,10 +146,8 @@ export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { firstFileTypeInfo, secondFileFileInfo ); - const livePhotoMetadata: Metadata = getLivePhotoMetadata( - firstFileMetadata, - secondFileMetadata - ); + const livePhotoMetadata: Metadata = + getLivePhotoMetadata(imageMetadata); uploadService.setFileMetadataAndFileTypeInfo(livePhotoLocalID, { fileTypeInfo: { ...livePhotoFileTypeInfo }, metadata: { ...livePhotoMetadata }, From 0ea457e5f8f6f7e787559b7f86d7e7fe310d4c44 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Tue, 15 Feb 2022 09:52:17 +0530 Subject: [PATCH 34/60] fix imageMetadata intialisation --- src/services/upload/livePhotoService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index b91defe39..a6db24552 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -128,12 +128,12 @@ export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { secondFileFileInfo.fileType === FILE_TYPE.VIDEO ) { imageFile = firstMediaFile.file; + imageMetadata = firstFileMetadata; videoFile = secondMediaFile.file; - imageMetadata = secondFileMetadata; } else { imageFile = secondMediaFile.file; + imageMetadata = secondFileMetadata; videoFile = firstMediaFile.file; - imageMetadata = firstFileMetadata; } const livePhotoLocalID = index; analysedMediaFiles.push({ From acbbfdf405b2be2d068ae2cd94b9123ccf7b38dc Mon Sep 17 00:00:00 2001 From: Abhinav Date: Tue, 15 Feb 2022 15:53:32 +0530 Subject: [PATCH 35/60] fix localID intialisation --- src/components/pages/gallery/Upload.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/pages/gallery/Upload.tsx b/src/components/pages/gallery/Upload.tsx index d85160794..921bc6c86 100644 --- a/src/components/pages/gallery/Upload.tsx +++ b/src/components/pages/gallery/Upload.tsx @@ -201,6 +201,7 @@ export default function Upload(props: Props) { } try { const existingCollection = await syncCollections(); + let index = 0; for (const [collectionName, files] of collectionWiseFiles) { const collection = await createAlbum( collectionName, @@ -209,8 +210,8 @@ export default function Upload(props: Props) { collections.push(collection); filesWithCollectionToUpload.push( - ...files.map((file, index) => ({ - localID: index, + ...files.map((file) => ({ + localID: index++, collectionID: collection.id, file, })) From f58108a55f5b529cec5a1c34eb024918c8a02b89 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Tue, 15 Feb 2022 16:32:42 +0530 Subject: [PATCH 36/60] fix type passed to thumbnail generation for heic detection --- src/services/upload/livePhotoService.ts | 66 ++++++++++++++++++------- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index a6db24552..f582b5e28 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -22,15 +22,21 @@ interface LivePhotoIdentifier { size: number; } +interface Asset { + file: File; + metadata: Metadata; + fileTypeInfo: FileTypeInfo; +} + const ENTE_LIVE_PHOTO_FORMAT = 'elp'; export function getLivePhotoFileType( - file1TypeInfo: FileTypeInfo, - file2TypeInfo: FileTypeInfo + imageFileTypeInfo: FileTypeInfo, + videoTypeInfo: FileTypeInfo ) { return { fileType: FILE_TYPE.LIVE_PHOTO, - exactType: `${file1TypeInfo.exactType}+${file2TypeInfo.exactType}`, + exactType: `${imageFileTypeInfo.exactType}+${videoTypeInfo.exactType}`, }; } @@ -54,11 +60,18 @@ export async function readLivePhoto( fileTypeInfo: FileTypeInfo, livePhotoAssets: LivePhotoAssets ) { + const imageType = fileTypeInfo.exactType.slice( + 0, + fileTypeInfo.exactType.indexOf('+') + ); const { thumbnail, hasStaticThumbnail } = await generateThumbnail( worker, reader, livePhotoAssets.image, - { exactType: fileTypeInfo.exactType, fileType: FILE_TYPE.IMAGE } + { + exactType: imageType, + fileType: FILE_TYPE.IMAGE, + } ); const image = await getUint8ArrayView(reader, livePhotoAssets.image); @@ -120,34 +133,51 @@ export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { if ( areFilesLivePhotoAssets(firstFileIdentifier, secondFileIdentifier) ) { - let imageFile: File; - let videoFile: File; - let imageMetadata: Metadata; + let imageAsset: Asset; + let videoAsset: Asset; if ( firstFileTypeInfo.fileType === FILE_TYPE.IMAGE && secondFileFileInfo.fileType === FILE_TYPE.VIDEO ) { - imageFile = firstMediaFile.file; - imageMetadata = firstFileMetadata; - videoFile = secondMediaFile.file; + imageAsset = { + file: firstMediaFile.file, + metadata: firstFileMetadata, + fileTypeInfo: firstFileTypeInfo, + }; + videoAsset = { + file: secondMediaFile.file, + metadata: secondFileMetadata, + fileTypeInfo: secondFileFileInfo, + }; } else { - imageFile = secondMediaFile.file; - imageMetadata = secondFileMetadata; - videoFile = firstMediaFile.file; + videoAsset = { + file: firstMediaFile.file, + metadata: firstFileMetadata, + fileTypeInfo: firstFileTypeInfo, + }; + imageAsset = { + file: secondMediaFile.file, + metadata: secondFileMetadata, + fileTypeInfo: secondFileFileInfo, + }; } const livePhotoLocalID = index; analysedMediaFiles.push({ localID: livePhotoLocalID, collectionID: firstMediaFile.collectionID, isLivePhoto: true, - livePhotoAssets: { image: imageFile, video: videoFile }, + livePhotoAssets: { + image: imageAsset.file, + video: videoAsset.file, + }, }); const livePhotoFileTypeInfo: FileTypeInfo = getLivePhotoFileType( - firstFileTypeInfo, - secondFileFileInfo + imageAsset.fileTypeInfo, + videoAsset.fileTypeInfo + ); + const livePhotoMetadata: Metadata = getLivePhotoMetadata( + imageAsset.metadata ); - const livePhotoMetadata: Metadata = - getLivePhotoMetadata(imageMetadata); uploadService.setFileMetadataAndFileTypeInfo(livePhotoLocalID, { fileTypeInfo: { ...livePhotoFileTypeInfo }, metadata: { ...livePhotoMetadata }, From 8a9bbf08f3024eb240a0367548e101366bf3b871 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Tue, 15 Feb 2022 16:51:41 +0530 Subject: [PATCH 37/60] close notification on photoswipe close --- src/components/PhotoSwipe/PhotoSwipe.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/PhotoSwipe/PhotoSwipe.tsx b/src/components/PhotoSwipe/PhotoSwipe.tsx index f7748cd1c..e17813be3 100644 --- a/src/components/PhotoSwipe/PhotoSwipe.tsx +++ b/src/components/PhotoSwipe/PhotoSwipe.tsx @@ -618,6 +618,7 @@ function PhotoSwipe(props: Iprops) { videoTag.pause(); } handleCloseInfo(); + galleryContext.setNotificationAttributes(null); }; const isInFav = (file) => { const { favItemIds } = props; From cf8d3eb72f97c7cfd61a731fe82cad4ecd60fb66 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Tue, 15 Feb 2022 16:52:02 +0530 Subject: [PATCH 38/60] show notification only 3 times --- src/components/PhotoSwipe/PhotoSwipe.tsx | 17 +++++++++++++---- src/utils/storage/localStorage.ts | 1 + 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/components/PhotoSwipe/PhotoSwipe.tsx b/src/components/PhotoSwipe/PhotoSwipe.tsx index e17813be3..765b01574 100644 --- a/src/components/PhotoSwipe/PhotoSwipe.tsx +++ b/src/components/PhotoSwipe/PhotoSwipe.tsx @@ -46,6 +46,7 @@ import { FILE_TYPE, MAX_EDITED_FILE_NAME_LENGTH } from 'constants/file'; import { sleep } from 'utils/common'; import { PublicCollectionGalleryContext } from 'utils/publicCollectionGallery'; import { GalleryContext } from 'pages/gallery'; +import { getData, LS_KEYS, setData } from 'utils/storage/localStorage'; const SmallLoadingSpinner = () => ( { From 5d7d32d5fcb647d05982c434ba09d0b12e7db336 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Tue, 15 Feb 2022 17:04:36 +0530 Subject: [PATCH 39/60] better names --- src/components/PhotoSwipe/PhotoSwipe.tsx | 13 ++++++------- src/utils/storage/index.ts | 8 ++++++++ src/utils/storage/localStorage.ts | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/components/PhotoSwipe/PhotoSwipe.tsx b/src/components/PhotoSwipe/PhotoSwipe.tsx index 765b01574..1f75d346d 100644 --- a/src/components/PhotoSwipe/PhotoSwipe.tsx +++ b/src/components/PhotoSwipe/PhotoSwipe.tsx @@ -46,7 +46,10 @@ import { FILE_TYPE, MAX_EDITED_FILE_NAME_LENGTH } from 'constants/file'; import { sleep } from 'utils/common'; import { PublicCollectionGalleryContext } from 'utils/publicCollectionGallery'; import { GalleryContext } from 'pages/gallery'; -import { getData, LS_KEYS, setData } from 'utils/storage/localStorage'; +import { + getLivePhotoInfoShownCount, + setLivePhotoInfoShownCount, +} from 'utils/storage'; const SmallLoadingSpinner = () => ( export function setJustSignedUp(status) { setData(LS_KEYS.JUST_SIGNED_UP, { status }); } + +export function getLivePhotoInfoShownCount() { + return getData(LS_KEYS.LIVE_PHOTO_INFO_SHOWN_COUNT)?.count ?? 0; +} + +export function setLivePhotoInfoShownCount(count) { + setData(LS_KEYS.LIVE_PHOTO_INFO_SHOWN_COUNT, { count }); +} diff --git a/src/utils/storage/localStorage.ts b/src/utils/storage/localStorage.ts index 6307d3db2..e8409eb70 100644 --- a/src/utils/storage/localStorage.ts +++ b/src/utils/storage/localStorage.ts @@ -13,7 +13,7 @@ export enum LS_KEYS { EXPORT = 'export', AnonymizeUserID = 'anonymizedUserID', THUMBNAIL_FIX_STATE = 'thumbnailFixState', - IS_LIVE_PHOTO_INFO_SHOWN_COUNT = 'isLivePhotoInfoShownCount', + LIVE_PHOTO_INFO_SHOWN_COUNT = 'livePhotoInfoShownCount', } export const setData = (key: LS_KEYS, value: object) => { From 0b34bcbba225cbe6e1d50fa1a49b73e3fd86deee Mon Sep 17 00:00:00 2001 From: Abhinav Date: Tue, 15 Feb 2022 19:21:53 +0530 Subject: [PATCH 40/60] set livePhotoLocalID correctly --- src/services/upload/livePhotoService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index f582b5e28..b1a2eb4cf 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -161,7 +161,7 @@ export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { fileTypeInfo: secondFileFileInfo, }; } - const livePhotoLocalID = index; + const livePhotoLocalID = firstMediaFile.localID; analysedMediaFiles.push({ localID: livePhotoLocalID, collectionID: firstMediaFile.collectionID, From 56a10cd80d82ba50692885b429730a3b16f25162 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 09:01:26 +0530 Subject: [PATCH 41/60] create util isLivePhoto --- src/components/PhotoSwipe/PhotoSwipe.tsx | 9 +++------ src/components/pages/gallery/PreviewCard.tsx | 6 ++---- src/utils/file/index.ts | 4 ++++ 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/components/PhotoSwipe/PhotoSwipe.tsx b/src/components/PhotoSwipe/PhotoSwipe.tsx index 1f75d346d..955fef9b5 100644 --- a/src/components/PhotoSwipe/PhotoSwipe.tsx +++ b/src/components/PhotoSwipe/PhotoSwipe.tsx @@ -20,6 +20,7 @@ import { changeFileName, downloadFile, formatDateTime, + isLivePhoto, splitFilenameAndExtension, updateExistingFilePubMetadata, } from 'utils/file'; @@ -42,7 +43,7 @@ import { Formik } from 'formik'; import * as Yup from 'yup'; import EnteSpinner from 'components/EnteSpinner'; import EnteDateTimePicker from 'components/EnteDateTimePicker'; -import { FILE_TYPE, MAX_EDITED_FILE_NAME_LENGTH } from 'constants/file'; +import { MAX_EDITED_FILE_NAME_LENGTH } from 'constants/file'; import { sleep } from 'utils/common'; import { PublicCollectionGalleryContext } from 'utils/publicCollectionGallery'; import { GalleryContext } from 'pages/gallery'; @@ -530,7 +531,7 @@ function PhotoSwipe(props: Iprops) { } function handleLivePhotoNotification() { - if (checkIsLivePhoto(this?.currItem)) { + if (isLivePhoto(this?.currItem)) { const infoShownCount = getLivePhotoInfoShownCount(); if (infoShownCount < 3) { galleryContext.setNotificationAttributes({ @@ -636,10 +637,6 @@ function PhotoSwipe(props: Iprops) { return false; }; - const checkIsLivePhoto = (file: EnteFile) => { - return file.metadata.fileType === FILE_TYPE.LIVE_PHOTO; - }; - const onFavClick = async (file) => { const { favItemIds } = props; if (!isInFav(file)) { diff --git a/src/components/pages/gallery/PreviewCard.tsx b/src/components/pages/gallery/PreviewCard.tsx index 2ccab2644..3eb8edfe3 100644 --- a/src/components/pages/gallery/PreviewCard.tsx +++ b/src/components/pages/gallery/PreviewCard.tsx @@ -12,7 +12,7 @@ import { } from 'utils/publicCollectionGallery'; import PublicCollectionDownloadManager from 'services/publicCollectionDownloadManager'; import LivePhotoIndicatorOverlay from 'components/icons/LivePhotoIndicatorOverlay'; -import { FILE_TYPE } from 'constants/file'; +import { isLivePhoto } from 'utils/file'; interface IProps { file: EnteFile; @@ -285,9 +285,7 @@ export default function PreviewCard(props: IProps) { - {file?.metadata.fileType === FILE_TYPE.LIVE_PHOTO && ( - - )} + {isLivePhoto(file) && } ); } diff --git a/src/utils/file/index.ts b/src/utils/file/index.ts index 42b71b8bd..1a4f56b27 100644 --- a/src/utils/file/index.ts +++ b/src/utils/file/index.ts @@ -548,3 +548,7 @@ export function needsConversionForPreview(file: EnteFile) { return false; } } + +export function isLivePhoto(file: EnteFile) { + return file.metadata.fileType === FILE_TYPE.LIVE_PHOTO; +} From 58dbac8ffd4ec45a5975e142bc889a2662ca6ce3 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 09:08:28 +0530 Subject: [PATCH 42/60] remove underscored suffix added to live photo name while comparing file names --- src/services/upload/livePhotoService.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index b1a2eb4cf..8d5f3e1d8 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -204,8 +204,12 @@ function areFilesLivePhotoAssets( firstFileIdentifier.fileType !== secondFileIdentifier.fileType && firstFileIdentifier.fileType !== FILE_TYPE.OTHERS && secondFileIdentifier.fileType !== FILE_TYPE.OTHERS && - splitFilenameAndExtension(firstFileIdentifier.name)[0] === - splitFilenameAndExtension(secondFileIdentifier.name)[0] + removeUnderscoreSuffix( + splitFilenameAndExtension(firstFileIdentifier.name)[0] + ) === + removeUnderscoreSuffix( + splitFilenameAndExtension(secondFileIdentifier.name)[0] + ) ) { // checks size of live Photo assets are less than allowed limit // I did that based on the assumption that live photo assets ideally would not be larger than LIVE_PHOTO_ASSET_SIZE_LIMIT @@ -230,3 +234,12 @@ function areFilesLivePhotoAssets( } return false; } + +function removeUnderscoreSuffix(filename: string) { + const indexOfUnderscore = filename.indexOf('_'); + if (indexOfUnderscore !== -1) { + return filename.slice(0, indexOfUnderscore); + } else { + return filename; + } +} From b7d63ee8335a06c9f539f46641d932ddeec381d3 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 09:15:19 +0530 Subject: [PATCH 43/60] better comments --- src/components/PhotoSwipe/PhotoSwipe.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/PhotoSwipe/PhotoSwipe.tsx b/src/components/PhotoSwipe/PhotoSwipe.tsx index 955fef9b5..eec1a7a22 100644 --- a/src/components/PhotoSwipe/PhotoSwipe.tsx +++ b/src/components/PhotoSwipe/PhotoSwipe.tsx @@ -627,6 +627,7 @@ function PhotoSwipe(props: Iprops) { videoTag.pause(); } handleCloseInfo(); + // BE_AWARE: this will clear any notification set, even if they were not set in/by the photoswipe component galleryContext.setNotificationAttributes(null); }; const isInFav = (file) => { From f9d38c8c6c1a67fdb9ad56b47b716a1008e12791 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 09:16:12 +0530 Subject: [PATCH 44/60] renamed Notification to ToastNotification --- src/components/{Notification.tsx => ToastNotification.tsx} | 5 ++++- src/pages/gallery/index.tsx | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) rename src/components/{Notification.tsx => ToastNotification.tsx} (94%) diff --git a/src/components/Notification.tsx b/src/components/ToastNotification.tsx similarity index 94% rename from src/components/Notification.tsx rename to src/components/ToastNotification.tsx index 020f75a15..a885fc4ab 100644 --- a/src/components/Notification.tsx +++ b/src/components/ToastNotification.tsx @@ -17,7 +17,10 @@ interface Iprops { clearAttributes: () => void; } -export default function Notification({ attributes, clearAttributes }: Iprops) { +export default function ToastNotification({ + attributes, + clearAttributes, +}: Iprops) { const [show, setShow] = useState(false); const closeToast = () => { setShow(false); diff --git a/src/pages/gallery/index.tsx b/src/pages/gallery/index.tsx index d58e21b3b..f6d005f8c 100644 --- a/src/pages/gallery/index.tsx +++ b/src/pages/gallery/index.tsx @@ -104,7 +104,7 @@ import { } from 'types/gallery'; import Collections from 'components/pages/gallery/Collections'; import { VISIBILITY_STATE } from 'constants/file'; -import Notification from 'components/Notification'; +import Notification from 'components/ToastNotification'; export const DeadCenter = styled.div` flex: 1; From c63e3eed999bfff73d32d4ff0274bd9a77b0947b Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 09:21:52 +0530 Subject: [PATCH 45/60] remove unused class --- src/pages/_app.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 7443e4b15..031791c39 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -490,9 +490,6 @@ const GlobalStyles = createGlobalStyle` cursor:pointer; } - .ente-toast{ - - } `; export const LogoImage = styled.img` From d972dad891715722e208a76e69d67bc5f12771d3 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 09:24:24 +0530 Subject: [PATCH 46/60] fix import name to ToastNotification --- src/pages/gallery/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/gallery/index.tsx b/src/pages/gallery/index.tsx index f6d005f8c..bd5b2e1af 100644 --- a/src/pages/gallery/index.tsx +++ b/src/pages/gallery/index.tsx @@ -104,7 +104,7 @@ import { } from 'types/gallery'; import Collections from 'components/pages/gallery/Collections'; import { VISIBILITY_STATE } from 'constants/file'; -import Notification from 'components/ToastNotification'; +import ToastNotification from 'components/ToastNotification'; export const DeadCenter = styled.div` flex: 1; @@ -590,7 +590,7 @@ export default function Gallery() { setLoading={setLoading} /> - From 044b2579723461e4908f463edffd5235f7467d9e Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 09:29:50 +0530 Subject: [PATCH 47/60] dont need cryptoWorker for thumbnail generation convertService handles it --- src/services/migrateThumbnailService.ts | 1 - src/services/upload/fileService.ts | 2 -- src/services/upload/livePhotoService.ts | 2 -- src/services/upload/thumbnailService.ts | 10 ++-------- src/services/upload/uploadService.ts | 5 ++--- src/services/upload/uploader.ts | 1 - 6 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/services/migrateThumbnailService.ts b/src/services/migrateThumbnailService.ts index a5c693e97..f1ded2766 100644 --- a/src/services/migrateThumbnailService.ts +++ b/src/services/migrateThumbnailService.ts @@ -79,7 +79,6 @@ export async function replaceThumbnail( ); const fileTypeInfo = await getFileType(reader, dummyImageFile); const { thumbnail: newThumbnail } = await generateThumbnail( - worker, reader, dummyImageFile, fileTypeInfo diff --git a/src/services/upload/fileService.ts b/src/services/upload/fileService.ts index 9f2d405ec..a9e4e880c 100644 --- a/src/services/upload/fileService.ts +++ b/src/services/upload/fileService.ts @@ -19,13 +19,11 @@ export function getFileSize(file: File) { } export async function readFile( - worker: any, reader: FileReader, fileTypeInfo: FileTypeInfo, rawFile: File ): Promise { const { thumbnail, hasStaticThumbnail } = await generateThumbnail( - worker, reader, rawFile, fileTypeInfo diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index 8d5f3e1d8..ce2d9ba00 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -55,7 +55,6 @@ export function getLivePhotoSize(livePhotoAssets: LivePhotoAssets) { } export async function readLivePhoto( - worker, reader: FileReader, fileTypeInfo: FileTypeInfo, livePhotoAssets: LivePhotoAssets @@ -65,7 +64,6 @@ export async function readLivePhoto( fileTypeInfo.exactType.indexOf('+') ); const { thumbnail, hasStaticThumbnail } = await generateThumbnail( - worker, reader, livePhotoAssets.image, { diff --git a/src/services/upload/thumbnailService.ts b/src/services/upload/thumbnailService.ts index 332570a97..b6f1d3903 100644 --- a/src/services/upload/thumbnailService.ts +++ b/src/services/upload/thumbnailService.ts @@ -23,7 +23,6 @@ interface Dimension { } export async function generateThumbnail( - worker, reader: FileReader, file: File, fileTypeInfo: FileTypeInfo @@ -35,13 +34,12 @@ export async function generateThumbnail( try { if (fileTypeInfo.fileType === FILE_TYPE.IMAGE) { const isHEIC = isFileHEIC(fileTypeInfo.exactType); - canvas = await generateImageThumbnail(worker, file, isHEIC); + canvas = await generateImageThumbnail(file, isHEIC); } else { try { const thumb = await FFmpegService.generateThumbnail(file); const dummyImageFile = new File([thumb], file.name); canvas = await generateImageThumbnail( - worker, dummyImageFile, false ); @@ -73,11 +71,7 @@ export async function generateThumbnail( } } -export async function generateImageThumbnail( - worker, - file: File, - isHEIC: boolean -) { +export async function generateImageThumbnail(file: File, isHEIC: boolean) { const canvas = document.createElement('canvas'); const canvasCTX = canvas.getContext('2d'); diff --git a/src/services/upload/uploadService.ts b/src/services/upload/uploadService.ts index 5f934ceb5..d99269cd8 100644 --- a/src/services/upload/uploadService.ts +++ b/src/services/upload/uploadService.ts @@ -73,14 +73,13 @@ class UploadService { } async readAsset( - worker: any, reader: FileReader, fileTypeInfo: FileTypeInfo, { isLivePhoto, file, livePhotoAssets }: UploadAsset ) { return isLivePhoto - ? await readLivePhoto(worker, reader, fileTypeInfo, livePhotoAssets) - : await readFile(worker, reader, fileTypeInfo, file); + ? await readLivePhoto(reader, fileTypeInfo, livePhotoAssets) + : await readFile(reader, fileTypeInfo, file); } async extractFileMetadata( diff --git a/src/services/upload/uploader.ts b/src/services/upload/uploader.ts index 083ffb2c9..5e9025678 100644 --- a/src/services/upload/uploader.ts +++ b/src/services/upload/uploader.ts @@ -42,7 +42,6 @@ export default async function uploader( } const file = await UploadService.readAsset( - worker, reader, fileTypeInfo, uploadAsset From 79455d22959be075baf9654cad53bc99b8b1f3ed Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 09:52:00 +0530 Subject: [PATCH 48/60] added imageType and videoType property to FleTypeInfo to handle livePhoto case --- src/services/upload/livePhotoService.ts | 10 ++++------ src/types/upload/index.ts | 2 ++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index ce2d9ba00..b5e4fadfc 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -33,10 +33,12 @@ const ENTE_LIVE_PHOTO_FORMAT = 'elp'; export function getLivePhotoFileType( imageFileTypeInfo: FileTypeInfo, videoTypeInfo: FileTypeInfo -) { +): FileTypeInfo { return { fileType: FILE_TYPE.LIVE_PHOTO, exactType: `${imageFileTypeInfo.exactType}+${videoTypeInfo.exactType}`, + imageType: imageFileTypeInfo.exactType, + videoType: videoTypeInfo.exactType, }; } @@ -59,15 +61,11 @@ export async function readLivePhoto( fileTypeInfo: FileTypeInfo, livePhotoAssets: LivePhotoAssets ) { - const imageType = fileTypeInfo.exactType.slice( - 0, - fileTypeInfo.exactType.indexOf('+') - ); const { thumbnail, hasStaticThumbnail } = await generateThumbnail( reader, livePhotoAssets.image, { - exactType: imageType, + exactType: fileTypeInfo.imageType, fileType: FILE_TYPE.IMAGE, } ); diff --git a/src/types/upload/index.ts b/src/types/upload/index.ts index a83490144..fd2319f1f 100644 --- a/src/types/upload/index.ts +++ b/src/types/upload/index.ts @@ -48,6 +48,8 @@ export interface MultipartUploadURLs { export interface FileTypeInfo { fileType: FILE_TYPE; exactType: string; + imageType?: string; + videoType?: string; } export interface ProgressUpdater { From 8534f69e969434d88ebf9c393eac1cf4d1c73bc7 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 09:57:33 +0530 Subject: [PATCH 49/60] refactor code --- src/services/upload/livePhotoService.ts | 34 ++++++++++--------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index b5e4fadfc..e9349bbef 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -126,6 +126,16 @@ 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) ) { @@ -135,27 +145,11 @@ export function clusterLivePhotoFiles(mediaFiles: FileWithCollection[]) { firstFileTypeInfo.fileType === FILE_TYPE.IMAGE && secondFileFileInfo.fileType === FILE_TYPE.VIDEO ) { - imageAsset = { - file: firstMediaFile.file, - metadata: firstFileMetadata, - fileTypeInfo: firstFileTypeInfo, - }; - videoAsset = { - file: secondMediaFile.file, - metadata: secondFileMetadata, - fileTypeInfo: secondFileFileInfo, - }; + imageAsset = firstAsset; + videoAsset = secondAsset; } else { - videoAsset = { - file: firstMediaFile.file, - metadata: firstFileMetadata, - fileTypeInfo: firstFileTypeInfo, - }; - imageAsset = { - file: secondMediaFile.file, - metadata: secondFileMetadata, - fileTypeInfo: secondFileFileInfo, - }; + videoAsset = firstAsset; + imageAsset = secondAsset; } const livePhotoLocalID = firstMediaFile.localID; analysedMediaFiles.push({ From 47c6fabc054b68b32a53dd943bdbbf2a604456ac Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 10:06:28 +0530 Subject: [PATCH 50/60] isImageOrVideo util function and updated the areFilesLivePhotoAssets to allow only image and video as allowed assets --- src/services/upload/livePhotoService.ts | 6 +++--- src/utils/file/index.ts | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index e9349bbef..5db0e1722 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -8,7 +8,7 @@ import { Metadata, } from 'types/upload'; import { CustomError } from 'utils/error'; -import { splitFilenameAndExtension } from 'utils/file'; +import { isImageOrVideo, splitFilenameAndExtension } from 'utils/file'; import { logError } from 'utils/sentry'; import { getUint8ArrayView } from './readFileService'; import { generateThumbnail } from './thumbnailService'; @@ -192,8 +192,8 @@ function areFilesLivePhotoAssets( firstFileIdentifier.collectionID === secondFileIdentifier.collectionID && firstFileIdentifier.fileType !== secondFileIdentifier.fileType && - firstFileIdentifier.fileType !== FILE_TYPE.OTHERS && - secondFileIdentifier.fileType !== FILE_TYPE.OTHERS && + isImageOrVideo(firstFileIdentifier.fileType) && + isImageOrVideo(secondFileIdentifier.fileType) && removeUnderscoreSuffix( splitFilenameAndExtension(firstFileIdentifier.name)[0] ) === diff --git a/src/utils/file/index.ts b/src/utils/file/index.ts index 1a4f56b27..41ca1997b 100644 --- a/src/utils/file/index.ts +++ b/src/utils/file/index.ts @@ -549,6 +549,8 @@ export function needsConversionForPreview(file: EnteFile) { } } -export function isLivePhoto(file: EnteFile) { - return file.metadata.fileType === FILE_TYPE.LIVE_PHOTO; -} +export const isLivePhoto = (file: EnteFile) => + file.metadata.fileType === FILE_TYPE.LIVE_PHOTO; + +export const isImageOrVideo = (fileType: FILE_TYPE) => + fileType in [FILE_TYPE.IMAGE, FILE_TYPE.VIDEO]; From 3b9841b9c46712ca12cf36f1e238615bd035bf86 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 10:13:18 +0530 Subject: [PATCH 51/60] use the last index of underscore --- src/services/upload/livePhotoService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index 5db0e1722..a6be8c5fe 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -226,7 +226,7 @@ function areFilesLivePhotoAssets( } function removeUnderscoreSuffix(filename: string) { - const indexOfUnderscore = filename.indexOf('_'); + const indexOfUnderscore = filename.lastIndexOf('_'); if (indexOfUnderscore !== -1) { return filename.slice(0, indexOfUnderscore); } else { From 4d74c62eb2defb90742fd41a4dc3f7ffdb59d705 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 10:44:32 +0530 Subject: [PATCH 52/60] add try catch wrapper over extractFileMetadata and parseMetadataJSON to prevent one file breaking to progagate cause all of them to fail --- src/services/upload/uploadManager.ts | 78 +++++++++++++++------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/src/services/upload/uploadManager.ts b/src/services/upload/uploadManager.ts index 0ced3f484..50ff17ca6 100644 --- a/src/services/upload/uploadManager.ts +++ b/src/services/upload/uploadManager.ts @@ -127,18 +127,22 @@ class UploadManager { UIService.reset(metadataFiles.length); const reader = new FileReader(); for (const { file, collectionID } of metadataFiles) { - const parsedMetadataJSONWithTitle = await parseMetadataJSON( - reader, - file - ); - if (parsedMetadataJSONWithTitle) { - const { title, parsedMetadataJSON } = - parsedMetadataJSONWithTitle; - this.parsedMetadataJSONMap.set( - getMetadataJSONMapKey(collectionID, title), - parsedMetadataJSON && { ...parsedMetadataJSON } + try { + const parsedMetadataJSONWithTitle = await parseMetadataJSON( + reader, + file ); - UIService.increaseFileUploaded(); + if (parsedMetadataJSONWithTitle) { + const { title, parsedMetadataJSON } = + parsedMetadataJSONWithTitle; + this.parsedMetadataJSONMap.set( + getMetadataJSONMapKey(collectionID, title), + parsedMetadataJSON && { ...parsedMetadataJSON } + ); + UIService.increaseFileUploaded(); + } + } catch (e) { + logError(e, 'parsing failed for a file'); } } } catch (e) { @@ -152,31 +156,35 @@ class UploadManager { UIService.reset(mediaFiles.length); const reader = new FileReader(); for (const { file, localID, collectionID } of mediaFiles) { - const { fileTypeInfo, metadata } = await (async () => { - if (file.size >= MAX_FILE_SIZE_SUPPORTED) { - return { fileTypeInfo: null, metadata: null }; - } - const fileTypeInfo = await UploadService.getFileType( - reader, - file - ); - if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) { - return { fileTypeInfo, metadata: null }; - } - const metadata = - (await UploadService.extractFileMetadata( - file, - collectionID, - fileTypeInfo - )) || null; - return { fileTypeInfo, metadata }; - })(); + try { + const { fileTypeInfo, metadata } = await (async () => { + if (file.size >= MAX_FILE_SIZE_SUPPORTED) { + return { fileTypeInfo: null, metadata: null }; + } + const fileTypeInfo = await UploadService.getFileType( + reader, + file + ); + if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) { + return { fileTypeInfo, metadata: null }; + } + const metadata = + (await UploadService.extractFileMetadata( + file, + collectionID, + fileTypeInfo + )) || null; + return { fileTypeInfo, metadata }; + })(); - this.metadataAndFileTypeInfoMap.set(localID, { - fileTypeInfo: fileTypeInfo && { ...fileTypeInfo }, - metadata: metadata && { ...metadata }, - }); - UIService.increaseFileUploaded(); + this.metadataAndFileTypeInfoMap.set(localID, { + fileTypeInfo: fileTypeInfo && { ...fileTypeInfo }, + metadata: metadata && { ...metadata }, + }); + UIService.increaseFileUploaded(); + } catch (e) { + logError(e, 'metadata extraction failed for a file'); + } } } catch (e) { logError(e, 'error extracting metadata'); From 0c8b36279f77d7456ea3939d0365358ca1584a90 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 11:23:55 +0530 Subject: [PATCH 53/60] set initialValues for collectionNamer to empty string to give good error message --- src/components/pages/gallery/CollectionNamer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/pages/gallery/CollectionNamer.tsx b/src/components/pages/gallery/CollectionNamer.tsx index a94b47b17..8295394e5 100644 --- a/src/components/pages/gallery/CollectionNamer.tsx +++ b/src/components/pages/gallery/CollectionNamer.tsx @@ -53,7 +53,7 @@ export default function CollectionNamer({ attributes, ...props }: Props) { title: attributes?.title, }}> - initialValues={{ albumName: attributes.autoFilledName }} + initialValues={{ albumName: attributes.autoFilledName ?? '' }} validationSchema={Yup.object().shape({ albumName: Yup.string().required(constants.REQUIRED), })} From 227a04a01f20c0905183c7621807296c69d505bb Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 12:04:05 +0530 Subject: [PATCH 54/60] update file type detection function --- src/services/upload/readFileService.ts | 19 +++++++++---------- src/utils/file/index.ts | 5 +++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/services/upload/readFileService.ts b/src/services/upload/readFileService.ts index b40ccfe38..65508afbc 100644 --- a/src/services/upload/readFileService.ts +++ b/src/services/upload/readFileService.ts @@ -29,12 +29,12 @@ export async function getFileType( ): Promise { try { let fileType: FILE_TYPE; - const mimeType = await getMimeType(reader, receivedFile); - const typeParts = mimeType?.split('/'); - if (typeParts?.length !== 2) { + const typeResult = await extractFileType(reader, receivedFile); + const mimTypeParts = typeResult.mime?.split('/'); + if (mimTypeParts?.length !== 2) { throw Error(CustomError.TYPE_DETECTION_FAILED); } - switch (typeParts[0]) { + switch (mimTypeParts[0]) { case TYPE_IMAGE: fileType = FILE_TYPE.IMAGE; break; @@ -44,7 +44,7 @@ export async function getFileType( default: fileType = FILE_TYPE.OTHERS; } - return { fileType, exactType: typeParts[1] }; + return { fileType, exactType: typeResult.ext }; } catch (e) { const fileFormat = getFileExtension(receivedFile.name); const formatMissedByTypeDetection = FORMAT_MISSED_BY_FILE_TYPE_LIB.find( @@ -85,16 +85,15 @@ export function getFileOriginalName(file: File) { return originalName; } -async function getMimeType(reader: FileReader, file: File) { +async function extractFileType(reader: FileReader, file: File) { const fileChunkBlob = file.slice(0, CHUNK_SIZE_FOR_TYPE_DETECTION); - return getMimeTypeFromBlob(reader, fileChunkBlob); + return getFileTypeFromBlob(reader, fileChunkBlob); } -export async function getMimeTypeFromBlob(reader: FileReader, fileBlob: Blob) { +export async function getFileTypeFromBlob(reader: FileReader, fileBlob: Blob) { try { const initialFiledata = await getUint8ArrayView(reader, fileBlob); - const result = await FileType.fromBuffer(initialFiledata); - return result.mime; + return await FileType.fromBuffer(initialFiledata); } catch (e) { throw Error(CustomError.TYPE_DETECTION_FAILED); } diff --git a/src/utils/file/index.ts b/src/utils/file/index.ts index 41ca1997b..ae105a4d1 100644 --- a/src/utils/file/index.ts +++ b/src/utils/file/index.ts @@ -7,7 +7,7 @@ import { PublicMagicMetadataProps, } from 'types/file'; import { decodeMotionPhoto } from 'services/motionPhotoService'; -import { getMimeTypeFromBlob } from 'services/upload/readFileService'; +import { getFileTypeFromBlob } from 'services/upload/readFileService'; import DownloadManager from 'services/downloadManager'; import { logError } from 'utils/sentry'; import { User } from 'types/user'; @@ -326,7 +326,8 @@ export async function convertForPreview(file: EnteFile, fileBlob: Blob) { const reader = new FileReader(); const mimeType = - (await getMimeTypeFromBlob(reader, fileBlob)) ?? typeFromExtension; + (await getFileTypeFromBlob(reader, fileBlob))?.mime ?? + typeFromExtension; if (isFileHEIC(mimeType)) { fileBlob = await HEICConverter.convert(fileBlob); } From 602f36ee3783b4595b7f939c42cae1162a49d216 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 12:04:16 +0530 Subject: [PATCH 55/60] use the ext returned from the fileType detection library to set the correct extension for the file --- src/services/upload/metadataService.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/services/upload/metadataService.ts b/src/services/upload/metadataService.ts index 3f2b00547..89564aee5 100644 --- a/src/services/upload/metadataService.ts +++ b/src/services/upload/metadataService.ts @@ -8,6 +8,7 @@ import { FileTypeInfo, } from 'types/upload'; import { NULL_LOCATION } from 'constants/upload'; +import { splitFilenameAndExtension } from 'utils/file'; interface ParsedMetadataJSONWithTitle { title: string; @@ -30,7 +31,9 @@ export async function extractMetadata( } const extractedMetadata: Metadata = { - title: receivedFile.name, + title: `${splitFilenameAndExtension(receivedFile.name)[0]}.${ + fileTypeInfo.exactType + }`, creationTime: exifData?.creationTime ?? receivedFile.lastModified * 1000, modificationTime: receivedFile.lastModified * 1000, From 5744def6f2bfadc1c085a26ccb84aeee8c702823 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 12:18:07 +0530 Subject: [PATCH 56/60] added logic to show filename in upload progressbar --- src/services/upload/fileService.ts | 4 ++++ src/services/upload/livePhotoService.ts | 10 +++++++--- src/services/upload/uploadManager.ts | 8 +++----- src/services/upload/uploadService.ts | 9 ++++++++- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/services/upload/fileService.ts b/src/services/upload/fileService.ts index a9e4e880c..a9c22425c 100644 --- a/src/services/upload/fileService.ts +++ b/src/services/upload/fileService.ts @@ -18,6 +18,10 @@ export function getFileSize(file: File) { return file.size; } +export function getFilename(file: File) { + return file.name; +} + export async function readFile( reader: FileReader, fileTypeInfo: FileTypeInfo, diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index a6be8c5fe..509573a7c 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -45,9 +45,7 @@ export function getLivePhotoFileType( export function getLivePhotoMetadata(imageMetadata: Metadata) { return { ...imageMetadata, - title: `${ - splitFilenameAndExtension(imageMetadata.title)[0] - }.${ENTE_LIVE_PHOTO_FORMAT}`, + title: getLivePhotoName(imageMetadata.title), fileType: FILE_TYPE.LIVE_PHOTO, }; } @@ -56,6 +54,12 @@ export function getLivePhotoSize(livePhotoAssets: LivePhotoAssets) { return livePhotoAssets.image.size + livePhotoAssets.video.size; } +export function getLivePhotoName(imageTitle: string) { + return `${ + splitFilenameAndExtension(imageTitle)[0] + }.${ENTE_LIVE_PHOTO_FORMAT}`; +} + export async function readLivePhoto( reader: FileReader, fileTypeInfo: FileTypeInfo, diff --git a/src/services/upload/uploadManager.ts b/src/services/upload/uploadManager.ts index 50ff17ca6..137300690 100644 --- a/src/services/upload/uploadManager.ts +++ b/src/services/upload/uploadManager.ts @@ -100,11 +100,9 @@ class UploadManager { UploadService.clusterLivePhotoFiles(mediaFiles); uiService.setFilenames( new Map( - analysedMediaFiles.map(({ localID, file }) => [ - localID, - UploadService.getFileMetadataAndFileTypeInfo( - localID - )?.metadata?.title ?? file.name, + analysedMediaFiles.map((mediaFile) => [ + mediaFile.localID, + UploadService.getAssetName(mediaFile), ]) ) ); diff --git a/src/services/upload/uploadService.ts b/src/services/upload/uploadService.ts index d99269cd8..988a02fd4 100644 --- a/src/services/upload/uploadService.ts +++ b/src/services/upload/uploadService.ts @@ -1,7 +1,7 @@ import { Collection } from 'types/collection'; import { logError } from 'utils/sentry'; import UploadHttpClient from './uploadHttpClient'; -import { extractFileMetadata } from './fileService'; +import { extractFileMetadata, getFilename } from './fileService'; import { getFileType } from './readFileService'; import { handleUploadError } from 'utils/error'; import { @@ -24,6 +24,7 @@ import { } from 'types/upload'; import { clusterLivePhotoFiles, + getLivePhotoName, getLivePhotoSize, readLivePhoto, } from './livePhotoService'; @@ -68,6 +69,12 @@ class UploadService { : getFileSize(file); } + getAssetName({ isLivePhoto, file, livePhotoAssets }: FileWithCollection) { + return isLivePhoto + ? getLivePhotoName(livePhotoAssets.image.name) + : getFilename(file); + } + async getFileType(reader: FileReader, file: File) { return getFileType(reader, file); } From 0474d28aec0e3aca0c31c3b4e967bf98f0f1ebb2 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 12:30:34 +0530 Subject: [PATCH 57/60] remove only underscore three from the end --- src/services/upload/livePhotoService.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/services/upload/livePhotoService.ts b/src/services/upload/livePhotoService.ts index 509573a7c..5ad9a2647 100644 --- a/src/services/upload/livePhotoService.ts +++ b/src/services/upload/livePhotoService.ts @@ -30,6 +30,10 @@ interface Asset { const ENTE_LIVE_PHOTO_FORMAT = 'elp'; +const UNDERSCORE_THREE = '_3'; + +const UNDERSCORE = '_'; + export function getLivePhotoFileType( imageFileTypeInfo: FileTypeInfo, videoTypeInfo: FileTypeInfo @@ -198,10 +202,10 @@ function areFilesLivePhotoAssets( firstFileIdentifier.fileType !== secondFileIdentifier.fileType && isImageOrVideo(firstFileIdentifier.fileType) && isImageOrVideo(secondFileIdentifier.fileType) && - removeUnderscoreSuffix( + removeUnderscoreThreeSuffix( splitFilenameAndExtension(firstFileIdentifier.name)[0] ) === - removeUnderscoreSuffix( + removeUnderscoreThreeSuffix( splitFilenameAndExtension(secondFileIdentifier.name)[0] ) ) { @@ -229,10 +233,9 @@ function areFilesLivePhotoAssets( return false; } -function removeUnderscoreSuffix(filename: string) { - const indexOfUnderscore = filename.lastIndexOf('_'); - if (indexOfUnderscore !== -1) { - return filename.slice(0, indexOfUnderscore); +function removeUnderscoreThreeSuffix(filename: string) { + if (filename.endsWith(UNDERSCORE_THREE)) { + return filename.slice(0, filename.lastIndexOf(UNDERSCORE)); } else { return filename; } From 671bf0ee2d475c754c40d11c7253f380fe858117 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 13:13:47 +0530 Subject: [PATCH 58/60] add live photo detection info section --- src/components/pages/gallery/Upload.tsx | 4 ++++ src/components/pages/gallery/UploadProgress.tsx | 11 ++++++++++- src/services/upload/uiService.ts | 4 ++++ src/services/upload/uploadManager.ts | 3 +++ src/types/upload/index.ts | 1 + src/utils/strings/englishConstants.tsx | 7 +++++++ 6 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/components/pages/gallery/Upload.tsx b/src/components/pages/gallery/Upload.tsx index 921bc6c86..c15725311 100644 --- a/src/components/pages/gallery/Upload.tsx +++ b/src/components/pages/gallery/Upload.tsx @@ -61,6 +61,8 @@ export default function Upload(props: Props) { new Map() ); const [percentComplete, setPercentComplete] = useState(0); + const [hasLivePhotos, setHasLivePhotos] = useState(false); + const [choiceModalView, setChoiceModalView] = useState(false); const [analysisResult, setAnalysisResult] = useState({ suggestedCollectionName: '', @@ -78,6 +80,7 @@ export default function Upload(props: Props) { setUploadResult, setUploadStage, setFilenames, + setHasLivePhotos, }, props.setFiles ); @@ -348,6 +351,7 @@ export default function Upload(props: Props) { fileCounter={fileCounter} uploadStage={uploadStage} fileProgress={fileProgress} + hasLivePhotos={hasLivePhotos} show={progressView} closeModal={() => setProgressView(false)} retryFailed={retryFailed} diff --git a/src/components/pages/gallery/UploadProgress.tsx b/src/components/pages/gallery/UploadProgress.tsx index 753f99883..9fc38a535 100644 --- a/src/components/pages/gallery/UploadProgress.tsx +++ b/src/components/pages/gallery/UploadProgress.tsx @@ -22,6 +22,7 @@ interface Props { show; fileRejections: FileRejection[]; uploadResult: Map; + hasLivePhotos: boolean; } interface FileProgresses { fileID: number; @@ -111,6 +112,7 @@ interface InProgressProps { filenames: Map; sectionTitle: string; fileProgressStatuses: FileProgresses[]; + sectionInfo?: any; } const InProgressSection = (props: InProgressProps) => { const [listView, setListView] = useState(true); @@ -129,6 +131,9 @@ const InProgressSection = (props: InProgressProps) => { + {props.sectionInfo && ( + {props.sectionInfo} + )} {fileList.map(({ fileID, progress }) => (
  • @@ -148,7 +153,7 @@ export default function UploadProgress(props: Props) { const fileProgressStatuses = [] as FileProgresses[]; const fileUploadResultMap = new Map(); let filesNotUploaded = false; - + let sectionInfo = null; if (props.fileProgress) { for (const [localID, progress] of props.fileProgress) { fileProgressStatuses.push({ @@ -170,6 +175,9 @@ export default function UploadProgress(props: Props) { fileUploadResultMap.set(progress, [...fileList, localID]); } } + if (props.hasLivePhotos) { + sectionInfo = constants.LIVE_PHOTOS_DETECTED(); + } return ( >>; setUploadResult: React.Dispatch>>; setFilenames: React.Dispatch>>; + setHasLivePhotos: React.Dispatch>; } export interface UploadAsset { diff --git a/src/utils/strings/englishConstants.tsx b/src/utils/strings/englishConstants.tsx index d5e12d90d..8b3deb00c 100644 --- a/src/utils/strings/englishConstants.tsx +++ b/src/utils/strings/englishConstants.tsx @@ -517,6 +517,13 @@ const englishConstants = { ), + LIVE_PHOTOS_DETECTED: () => ( +

    + the photo and video files from your Live Photos have been merged + into a single ELP file +

    + ), + RETRY_FAILED: 'retry failed uploads', FAILED_UPLOADS: 'failed uploads ', SKIPPED_FILES: 'ignored uploads', From a786b6c30933755f704474759459e2cc1881233a Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 16 Feb 2022 21:00:09 +0530 Subject: [PATCH 59/60] export live photo assets seperately --- src/utils/file/index.ts | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/utils/file/index.ts b/src/utils/file/index.ts index ae105a4d1..b56b519d8 100644 --- a/src/utils/file/index.ts +++ b/src/utils/file/index.ts @@ -48,8 +48,6 @@ export async function downloadFile( ) { let fileURL: string; let tempURL: string; - const a = document.createElement('a'); - a.style.display = 'none'; if (accessedThroughSharedURL) { fileURL = await PublicCollectionDownloadManager.getCachedOriginalFile( file @@ -95,19 +93,35 @@ export async function downloadFile( tempEditedFileURL = URL.createObjectURL(fileBlob); fileURL = tempEditedFileURL; } - - a.href = fileURL; + let tempImageURL: string; + let tempVideoURL: string; if (file.metadata.fileType === FILE_TYPE.LIVE_PHOTO) { - a.download = fileNameWithoutExtension(file.metadata.title) + '.zip'; + const fileBlob = await (await fetch(fileURL)).blob(); + const originalName = fileNameWithoutExtension(file.metadata.title); + const motionPhoto = await decodeMotionPhoto(fileBlob, originalName); + tempImageURL = URL.createObjectURL(new Blob([motionPhoto.image])); + tempVideoURL = URL.createObjectURL(new Blob([motionPhoto.video])); + downloadUsingAnchor(motionPhoto.imageNameTitle, tempImageURL); + downloadUsingAnchor(motionPhoto.videoNameTitle, tempVideoURL); } else { - a.download = file.metadata.title; + downloadUsingAnchor(file.metadata.title, fileURL); } + + tempURL && URL.revokeObjectURL(tempURL); + tempEditedFileURL && URL.revokeObjectURL(tempEditedFileURL); + tempImageURL && URL.revokeObjectURL(tempImageURL); + tempVideoURL && URL.revokeObjectURL(tempVideoURL); +} + +function downloadUsingAnchor(name: string, link: string) { + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = link; + a.download = name; document.body.appendChild(a); a.click(); a.remove(); - tempURL && URL.revokeObjectURL(tempURL); - tempEditedFileURL && URL.revokeObjectURL(tempEditedFileURL); } export function isFileHEIC(mimeType: string) { From 816960fbaff33c1370f9f5242645c2fe1890892d Mon Sep 17 00:00:00 2001 From: Vishnu Mohandas Date: Wed, 16 Feb 2022 21:18:53 +0530 Subject: [PATCH 60/60] Fix copy --- src/utils/strings/englishConstants.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/strings/englishConstants.tsx b/src/utils/strings/englishConstants.tsx index 8b3deb00c..b77d46490 100644 --- a/src/utils/strings/englishConstants.tsx +++ b/src/utils/strings/englishConstants.tsx @@ -99,7 +99,7 @@ const englishConstants = { ENTER_ALBUM_NAME: 'album name', CLOSE: 'close', NO: 'no', - NOTHING_HERE: 'nothing to see here eyes 👀', + NOTHING_HERE: 'nothing to see here yet 👀', UPLOAD: { 0: 'preparing to upload', 1: 'reading google metadata files',