diff --git a/apps/photos/src/components/Collections/CollectionListBar/index.tsx b/apps/photos/src/components/Collections/CollectionListBar/index.tsx index 8eb23ffe9..ed2e4f245 100644 --- a/apps/photos/src/components/Collections/CollectionListBar/index.tsx +++ b/apps/photos/src/components/Collections/CollectionListBar/index.tsx @@ -75,6 +75,10 @@ const CollectionCardContainer = React.memo( areEqual ); +const getItemKey = (index: number, data: ItemData) => { + return `${data.collectionSummaries[index].id}-${data.collectionSummaries[index].latestFile?.id}`; +}; + const CollectionListBar = (props: IProps) => { const { activeCollection, @@ -153,6 +157,7 @@ const CollectionListBar = (props: IProps) => { layout="horizontal" width={width} height={110} + itemKey={getItemKey} itemCount={collectionSummaries.length} itemSize={CollectionListBarCardWidth} useIsScrolling> diff --git a/apps/photos/src/components/PhotoViewer/FileInfo/RenderCaption.tsx b/apps/photos/src/components/PhotoViewer/FileInfo/RenderCaption.tsx index 0cd38c5b5..a0c9ba842 100644 --- a/apps/photos/src/components/PhotoViewer/FileInfo/RenderCaption.tsx +++ b/apps/photos/src/components/PhotoViewer/FileInfo/RenderCaption.tsx @@ -1,5 +1,4 @@ import React, { useState } from 'react'; -import { updateFilePublicMagicMetadata } from 'services/fileService'; import { EnteFile } from 'types/file'; import { changeCaption, updateExistingFilePubMetadata } from 'utils/file'; import { logError } from 'utils/sentry'; @@ -41,10 +40,7 @@ export function RenderCaption({ } setCaption(newCaption); - let updatedFile = await changeCaption(file, newCaption); - updatedFile = ( - await updateFilePublicMagicMetadata([updatedFile]) - )[0]; + const updatedFile = await changeCaption(file, newCaption); updateExistingFilePubMetadata(file, updatedFile); file.title = file.pubMagicMetadata.data.caption; refreshPhotoswipe(); diff --git a/apps/photos/src/components/PhotoViewer/FileInfo/RenderCreationTime.tsx b/apps/photos/src/components/PhotoViewer/FileInfo/RenderCreationTime.tsx index 5a0c6662b..47910988b 100644 --- a/apps/photos/src/components/PhotoViewer/FileInfo/RenderCreationTime.tsx +++ b/apps/photos/src/components/PhotoViewer/FileInfo/RenderCreationTime.tsx @@ -1,5 +1,4 @@ import React, { useState } from 'react'; -import { updateFilePublicMagicMetadata } from 'services/fileService'; import { EnteFile } from 'types/file'; import CalendarTodayIcon from '@mui/icons-material/CalendarToday'; import { @@ -37,13 +36,10 @@ export function RenderCreationTime({ closeEditMode(); return; } - let updatedFile = await changeFileCreationTime( + const updatedFile = await changeFileCreationTime( file, unixTimeInMicroSec ); - updatedFile = ( - await updateFilePublicMagicMetadata([updatedFile]) - )[0]; updateExistingFilePubMetadata(file, updatedFile); scheduleUpdate(); } diff --git a/apps/photos/src/components/PhotoViewer/FileInfo/RenderFileName.tsx b/apps/photos/src/components/PhotoViewer/FileInfo/RenderFileName.tsx index f0d9303e7..a7d58a524 100644 --- a/apps/photos/src/components/PhotoViewer/FileInfo/RenderFileName.tsx +++ b/apps/photos/src/components/PhotoViewer/FileInfo/RenderFileName.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useState } from 'react'; -import { updateFilePublicMagicMetadata } from 'services/fileService'; import { EnteFile } from 'types/file'; import { changeFileName, @@ -82,10 +81,7 @@ export function RenderFileName({ } setFilename(newFilename); const newTitle = getFileTitle(newFilename, extension); - let updatedFile = await changeFileName(file, newTitle); - updatedFile = ( - await updateFilePublicMagicMetadata([updatedFile]) - )[0]; + const updatedFile = await changeFileName(file, newTitle); updateExistingFilePubMetadata(file, updatedFile); scheduleUpdate(); } diff --git a/apps/photos/src/pages/gallery/index.tsx b/apps/photos/src/pages/gallery/index.tsx index 49df1cab4..dd3a54a65 100644 --- a/apps/photos/src/pages/gallery/index.tsx +++ b/apps/photos/src/pages/gallery/index.tsx @@ -11,7 +11,6 @@ import { clearKeys, getKey, SESSION_KEYS } from 'utils/storage/sessionStorage'; import { getLocalFiles, syncFiles, - updateFileMagicMetadata, trashFiles, deleteFromTrash, getLocalHiddenFiles, @@ -664,11 +663,7 @@ export default function Gallery() { startLoading(); try { const selectedFiles = getSelectedFiles(selected, filteredData); - const updatedFiles = await changeFilesVisibility( - selectedFiles, - visibility - ); - await updateFileMagicMetadata(updatedFiles); + await changeFilesVisibility(selectedFiles, visibility); clearSelection(); } catch (e) { logError(e, 'change file visibility failed'); diff --git a/apps/photos/src/services/collectionService.ts b/apps/photos/src/services/collectionService.ts index a0f8db96a..01890b23b 100644 --- a/apps/photos/src/services/collectionService.ts +++ b/apps/photos/src/services/collectionService.ts @@ -47,7 +47,7 @@ import { SUB_TYPE, UpdateMagicMetadataRequest, } from 'types/magicMetadata'; -import { IsArchived, updateMagicMetadataProps } from 'utils/magicMetadata'; +import { IsArchived, updateMagicMetadata } from 'utils/magicMetadata'; import { User } from 'types/user'; import { isQuickLinkCollection, @@ -58,6 +58,7 @@ import { isHiddenCollection, isValidReplacementAlbum, getNonHiddenCollections, + changeCollectionSubType, } from 'utils/collection'; import ComlinkCryptoWorker from 'utils/comlink/ComlinkCryptoWorker'; import { getLocalFiles } from './fileService'; @@ -345,7 +346,7 @@ const createCollection = async ( await cryptoWorker.encryptUTF8(collectionName, collectionKey); let encryptedMagicMetadata: EncryptedMagicMetadata; if (magicMetadataProps) { - const magicMetadata = await updateMagicMetadataProps( + const magicMetadata = await updateMagicMetadata( NEW_COLLECTION_MAGIC_METADATA, null, magicMetadataProps @@ -711,7 +712,10 @@ export const leaveSharedAlbum = async (collectionID: number) => { } }; -export const updateCollectionMagicMetadata = async (collection: Collection) => { +export const updateCollectionMagicMetadata = async ( + collection: Collection, + updatedMagicMetadata: CollectionMagicMetadata +) => { const token = getToken(); if (!token) { return; @@ -720,15 +724,15 @@ export const updateCollectionMagicMetadata = async (collection: Collection) => { const cryptoWorker = await ComlinkCryptoWorker.getInstance(); const { file: encryptedMagicMetadata } = await cryptoWorker.encryptMetadata( - collection.magicMetadata.data, + updatedMagicMetadata.data, collection.key ); const reqBody: UpdateMagicMetadataRequest = { id: collection.id, magicMetadata: { - version: collection.magicMetadata.version, - count: collection.magicMetadata.count, + version: updatedMagicMetadata.version, + count: updatedMagicMetadata.count, data: encryptedMagicMetadata.encryptedData, header: encryptedMagicMetadata.decryptionHeader, }, @@ -745,8 +749,8 @@ export const updateCollectionMagicMetadata = async (collection: Collection) => { const updatedCollection: Collection = { ...collection, magicMetadata: { - ...collection.magicMetadata, - version: collection.magicMetadata.version + 1, + ...updatedMagicMetadata, + version: updatedMagicMetadata.version + 1, }, }; return updatedCollection; @@ -758,7 +762,7 @@ export const renameCollection = async ( ) => { if (isQuickLinkCollection(collection)) { // Convert quick link collection to normal collection on rename - await updateCollectionSubType(collection, SUB_TYPE.DEFAULT); + await changeCollectionSubType(collection, SUB_TYPE.DEFAULT); } const token = getToken(); const cryptoWorker = await ComlinkCryptoWorker.getInstance(); @@ -779,24 +783,6 @@ export const renameCollection = async ( ); }; -const updateCollectionSubType = async ( - collection: Collection, - subType: SUB_TYPE -) => { - const updatedMagicMetadataProps: CollectionMagicMetadataProps = { - subType: subType, - }; - const updatedCollection = { - ...collection, - magicMetadata: await updateMagicMetadataProps( - collection.magicMetadata ?? NEW_COLLECTION_MAGIC_METADATA, - collection.key, - updatedMagicMetadataProps - ), - } as Collection; - await updateCollectionMagicMetadata(updatedCollection); -}; - export const shareCollection = async ( collection: Collection, withUserEmail: string diff --git a/apps/photos/src/services/fileService.ts b/apps/photos/src/services/fileService.ts index f9c07d39b..d50ff6bb3 100644 --- a/apps/photos/src/services/fileService.ts +++ b/apps/photos/src/services/fileService.ts @@ -12,7 +12,13 @@ import { sortFiles, } from 'utils/file'; import { eventBus, Events } from './events'; -import { EnteFile, EncryptedEnteFile, TrashRequest } from 'types/file'; +import { + EnteFile, + EncryptedEnteFile, + TrashRequest, + FileWithUpdatedMagicMetadata, + FileWithUpdatedPublicMagicMetadata, +} from 'types/file'; import { SetFiles } from 'types/gallery'; import { BulkUpdateMagicMetadataRequest } from 'types/magicMetadata'; import { addLogLine } from 'utils/logging'; @@ -250,24 +256,29 @@ export const deleteFromTrash = async (filesToDelete: number[]) => { } }; -export const updateFileMagicMetadata = async (files: EnteFile[]) => { +export const updateFileMagicMetadata = async ( + fileWithUpdatedMagicMetadataList: FileWithUpdatedMagicMetadata[] +) => { const token = getToken(); if (!token) { return; } const reqBody: BulkUpdateMagicMetadataRequest = { metadataList: [] }; const cryptoWorker = await ComlinkCryptoWorker.getInstance(); - for (const file of files) { + for (const { + file, + updatedMagicMetadata, + } of fileWithUpdatedMagicMetadataList) { const { file: encryptedMagicMetadata } = await cryptoWorker.encryptMetadata( - file.magicMetadata.data, + updatedMagicMetadata.data, file.key ); reqBody.metadataList.push({ id: file.id, magicMetadata: { - version: file.magicMetadata.version, - count: file.magicMetadata.count, + version: updatedMagicMetadata.version, + count: updatedMagicMetadata.count, data: encryptedMagicMetadata.encryptedData, header: encryptedMagicMetadata.decryptionHeader, }, @@ -276,35 +287,40 @@ export const updateFileMagicMetadata = async (files: EnteFile[]) => { await HTTPService.put(`${ENDPOINT}/files/magic-metadata`, reqBody, null, { 'X-Auth-Token': token, }); - return files.map( - (file): EnteFile => ({ + return fileWithUpdatedMagicMetadataList.map( + ({ file, updatedMagicMetadata }): EnteFile => ({ ...file, magicMetadata: { - ...file.magicMetadata, - version: file.magicMetadata.version + 1, + ...updatedMagicMetadata, + version: updatedMagicMetadata.version + 1, }, }) ); }; -export const updateFilePublicMagicMetadata = async (files: EnteFile[]) => { +export const updateFilePublicMagicMetadata = async ( + fileWithUpdatedPublicMagicMetadataList: FileWithUpdatedPublicMagicMetadata[] +): Promise => { const token = getToken(); if (!token) { return; } const reqBody: BulkUpdateMagicMetadataRequest = { metadataList: [] }; const cryptoWorker = await ComlinkCryptoWorker.getInstance(); - for (const file of files) { + for (const { + file, + updatedPublicMagicMetadata: updatePublicMagicMetadata, + } of fileWithUpdatedPublicMagicMetadataList) { const { file: encryptedPubMagicMetadata } = await cryptoWorker.encryptMetadata( - file.pubMagicMetadata.data, + updatePublicMagicMetadata.data, file.key ); reqBody.metadataList.push({ id: file.id, magicMetadata: { - version: file.pubMagicMetadata.version, - count: file.pubMagicMetadata.count, + version: updatePublicMagicMetadata.version, + count: updatePublicMagicMetadata.count, data: encryptedPubMagicMetadata.encryptedData, header: encryptedPubMagicMetadata.decryptionHeader, }, @@ -318,12 +334,12 @@ export const updateFilePublicMagicMetadata = async (files: EnteFile[]) => { 'X-Auth-Token': token, } ); - return files.map( - (file): EnteFile => ({ + return fileWithUpdatedPublicMagicMetadataList.map( + ({ file, updatedPublicMagicMetadata }): EnteFile => ({ ...file, pubMagicMetadata: { - ...file.pubMagicMetadata, - version: file.pubMagicMetadata.version + 1, + ...updatedPublicMagicMetadata, + version: updatedPublicMagicMetadata.version + 1, }, }) ); diff --git a/apps/photos/src/services/updateCreationTimeWithExif.ts b/apps/photos/src/services/updateCreationTimeWithExif.ts index 52de86cb9..19a352827 100644 --- a/apps/photos/src/services/updateCreationTimeWithExif.ts +++ b/apps/photos/src/services/updateCreationTimeWithExif.ts @@ -7,7 +7,6 @@ import { } from 'utils/file'; import { logError } from 'utils/sentry'; import downloadManager from './downloadManager'; -import { updateFilePublicMagicMetadata } from './fileService'; import { EnteFile } from 'types/file'; import { getParsedExifData } from './upload/exifService'; @@ -66,13 +65,10 @@ export async function updateCreationTimeWithExif( correctCreationTime && correctCreationTime !== file.metadata.creationTime ) { - let updatedFile = await changeFileCreationTime( + const updatedFile = await changeFileCreationTime( file, correctCreationTime ); - updatedFile = ( - await updateFilePublicMagicMetadata([updatedFile]) - )[0]; updateExistingFilePubMetadata(file, updatedFile); } } catch (e) { diff --git a/apps/photos/src/services/upload/magicMetadataService.ts b/apps/photos/src/services/upload/magicMetadataService.ts index cb286b281..c52bde6d5 100644 --- a/apps/photos/src/services/upload/magicMetadataService.ts +++ b/apps/photos/src/services/upload/magicMetadataService.ts @@ -3,12 +3,12 @@ import { FilePublicMagicMetadataProps, FilePublicMagicMetadata, } from 'types/magicMetadata'; -import { updateMagicMetadataProps } from 'utils/magicMetadata'; +import { updateMagicMetadata } from 'utils/magicMetadata'; export async function constructPublicMagicMetadata( publicMagicMetadataProps: FilePublicMagicMetadataProps ): Promise { - const pubMagicMetadata = await updateMagicMetadataProps( + const pubMagicMetadata = await updateMagicMetadata( NEW_FILE_MAGIC_METADATA, null, publicMagicMetadataProps diff --git a/apps/photos/src/types/file/index.ts b/apps/photos/src/types/file/index.ts index ffcf99587..5b4e3c37f 100644 --- a/apps/photos/src/types/file/index.ts +++ b/apps/photos/src/types/file/index.ts @@ -70,3 +70,13 @@ export interface TrashRequestItems { fileID: number; collectionID: number; } + +export interface FileWithUpdatedMagicMetadata { + file: EnteFile; + updatedMagicMetadata: FileMagicMetadata; +} + +export interface FileWithUpdatedPublicMagicMetadata { + file: EnteFile; + updatedPublicMagicMetadata: FilePublicMagicMetadata; +} diff --git a/apps/photos/src/utils/collection/index.ts b/apps/photos/src/utils/collection/index.ts index 0aa411937..44840bd25 100644 --- a/apps/photos/src/utils/collection/index.ts +++ b/apps/photos/src/utils/collection/index.ts @@ -10,7 +10,7 @@ import { import { downloadFiles } from 'utils/file'; import { getLocalFiles, getLocalHiddenFiles } from 'services/fileService'; import { EnteFile } from 'types/file'; -import { CustomError, ServerErrorCodes } from 'utils/error'; +import { CustomError } from 'utils/error'; import { User } from 'types/user'; import { getData, LS_KEYS } from 'utils/storage/localStorage'; import { logError } from 'utils/sentry'; @@ -33,7 +33,7 @@ import { SUB_TYPE, VISIBILITY_STATE, } from 'types/magicMetadata'; -import { IsArchived, updateMagicMetadataProps } from 'utils/magicMetadata'; +import { IsArchived, updateMagicMetadata } from 'utils/magicMetadata'; import { getAlbumsURL } from 'utils/common/apiUtil'; import bs58 from 'bs58'; import { t } from 'i18next'; @@ -165,22 +165,35 @@ export const changeCollectionVisibility = async ( visibility, }; - const updatedCollection = { - ...collection, - magicMetadata: await updateMagicMetadataProps( - collection.magicMetadata ?? NEW_COLLECTION_MAGIC_METADATA, - collection.key, - updatedMagicMetadataProps - ), - } as Collection; - - await updateCollectionMagicMetadata(updatedCollection); + const updatedMagicMetadata = await updateMagicMetadata( + collection.magicMetadata ?? NEW_COLLECTION_MAGIC_METADATA, + collection.key, + updatedMagicMetadataProps + ); + await updateCollectionMagicMetadata(collection, updatedMagicMetadata); } catch (e) { - logError(e, 'change file visibility failed'); - switch (e.status?.toString()) { - case ServerErrorCodes.FORBIDDEN: - throw Error(CustomError.NOT_FILE_OWNER); - } + logError(e, 'change collection visibility failed'); + throw e; + } +}; + +export const changeCollectionSubType = async ( + collection: Collection, + subType: SUB_TYPE +) => { + try { + const updatedMagicMetadataProps: CollectionMagicMetadataProps = { + subType: subType, + }; + + const updatedMagicMetadata = await updateMagicMetadata( + collection.magicMetadata ?? NEW_COLLECTION_MAGIC_METADATA, + collection.key, + updatedMagicMetadataProps + ); + await updateCollectionMagicMetadata(collection, updatedMagicMetadata); + } catch (e) { + logError(e, 'change collection subType failed'); throw e; } }; diff --git a/apps/photos/src/utils/file/index.ts b/apps/photos/src/utils/file/index.ts index 060aa18a4..ca7a5d9f7 100644 --- a/apps/photos/src/utils/file/index.ts +++ b/apps/photos/src/utils/file/index.ts @@ -1,5 +1,9 @@ import { SelectedState } from 'types/gallery'; -import { EnteFile, EncryptedEnteFile } from 'types/file'; +import { + EnteFile, + EncryptedEnteFile, + FileWithUpdatedMagicMetadata, +} from 'types/file'; import { decodeLivePhoto } from 'services/livePhotoService'; import { getFileType } from 'services/typeDetectionService'; import DownloadManager from 'services/downloadManager'; @@ -25,12 +29,16 @@ import { NEW_FILE_MAGIC_METADATA, VISIBILITY_STATE, } from 'types/magicMetadata'; -import { IsArchived, updateMagicMetadataProps } from 'utils/magicMetadata'; +import { IsArchived, updateMagicMetadata } from 'utils/magicMetadata'; import { addLogLine } from 'utils/logging'; import { CustomError } from 'utils/error'; import { convertBytesToHumanReadable } from './size'; import ComlinkCryptoWorker from 'utils/comlink/ComlinkCryptoWorker'; +import { + updateFileMagicMetadata, + updateFilePublicMagicMetadata, +} from 'services/fileService'; const WAIT_TIME_IMAGE_CONVERSION = 30 * 1000; @@ -351,64 +359,82 @@ export function isExactTypeHEIC(exactType: string) { export async function changeFilesVisibility( files: EnteFile[], visibility: VISIBILITY_STATE -) { - const updatedFiles: EnteFile[] = []; +): Promise { + const fileWithUpdatedMagicMetadataList: FileWithUpdatedMagicMetadata[] = []; for (const file of files) { const updatedMagicMetadataProps: FileMagicMetadataProps = { visibility, }; - updatedFiles.push({ - ...file, - magicMetadata: await updateMagicMetadataProps( + fileWithUpdatedMagicMetadataList.push({ + file, + updatedMagicMetadata: await updateMagicMetadata( file.magicMetadata ?? NEW_FILE_MAGIC_METADATA, file.key, updatedMagicMetadataProps ), }); } - return updatedFiles; + return await updateFileMagicMetadata(fileWithUpdatedMagicMetadataList); } export async function changeFileCreationTime( file: EnteFile, editedTime: number -) { +): Promise { const updatedPublicMagicMetadataProps: FilePublicMagicMetadataProps = { editedTime, }; - file.pubMagicMetadata = await updateMagicMetadataProps( - file.pubMagicMetadata ?? NEW_FILE_MAGIC_METADATA, - file.key, - updatedPublicMagicMetadataProps - ); - return file; + const updatedPublicMagicMetadata: FilePublicMagicMetadata = + await updateMagicMetadata( + file.pubMagicMetadata ?? NEW_FILE_MAGIC_METADATA, + file.key, + updatedPublicMagicMetadataProps + ); + const updateResult = await updateFilePublicMagicMetadata([ + { file, updatedPublicMagicMetadata }, + ]); + return updateResult[0]; } -export async function changeFileName(file: EnteFile, editedName: string) { +export async function changeFileName( + file: EnteFile, + editedName: string +): Promise { const updatedPublicMagicMetadataProps: FilePublicMagicMetadataProps = { editedName, }; - file.pubMagicMetadata = await updateMagicMetadataProps( - file.pubMagicMetadata ?? NEW_FILE_MAGIC_METADATA, - file.key, - updatedPublicMagicMetadataProps - ); - return file; + const updatedPublicMagicMetadata: FilePublicMagicMetadata = + await updateMagicMetadata( + file.pubMagicMetadata ?? NEW_FILE_MAGIC_METADATA, + file.key, + updatedPublicMagicMetadataProps + ); + const updateResult = await updateFilePublicMagicMetadata([ + { file, updatedPublicMagicMetadata }, + ]); + return updateResult[0]; } -export async function changeCaption(file: EnteFile, caption: string) { +export async function changeCaption( + file: EnteFile, + caption: string +): Promise { const updatedPublicMagicMetadataProps: FilePublicMagicMetadataProps = { caption, }; - file.pubMagicMetadata = await updateMagicMetadataProps( - file.pubMagicMetadata ?? NEW_FILE_MAGIC_METADATA, - file.key, - updatedPublicMagicMetadataProps - ); - return file; + const updatedPublicMagicMetadata: FilePublicMagicMetadata = + await updateMagicMetadata( + file.pubMagicMetadata ?? NEW_FILE_MAGIC_METADATA, + file.key, + updatedPublicMagicMetadataProps + ); + const updateResult = await updateFilePublicMagicMetadata([ + { file, updatedPublicMagicMetadata }, + ]); + return updateResult[0]; } export function isSharedFile(user: User, file: EnteFile) { diff --git a/apps/photos/src/utils/magicMetadata/index.ts b/apps/photos/src/utils/magicMetadata/index.ts index e8c9d0f52..3883330ca 100644 --- a/apps/photos/src/utils/magicMetadata/index.ts +++ b/apps/photos/src/utils/magicMetadata/index.ts @@ -20,7 +20,7 @@ export function IsArchived(item: Collection | EnteFile) { return item.magicMetadata.data.visibility === VISIBILITY_STATE.ARCHIVED; } -export async function updateMagicMetadataProps( +export async function updateMagicMetadata( originalMagicMetadata: MagicMetadataCore, decryptionKey: string, magicMetadataUpdates: Record