diff --git a/src/components/PhotoFrame.tsx b/src/components/PhotoFrame.tsx index a0d5fd829..8447e40ad 100644 --- a/src/components/PhotoFrame.tsx +++ b/src/components/PhotoFrame.tsx @@ -8,7 +8,7 @@ import constants from 'utils/strings/constants'; import AutoSizer from 'react-virtualized-auto-sizer'; import PhotoSwipe from 'components/PhotoSwipe/PhotoSwipe'; import { isInsideBox, isSameDay as isSameDayAnyYear } from 'utils/search'; -import { fileIsArchived, formatDateRelative } from 'utils/file'; +import { formatDateRelative } from 'utils/file'; import { ALL_SECTION, ARCHIVE_SECTION, @@ -25,6 +25,7 @@ import { useRouter } from 'next/router'; import EmptyScreen from './EmptyScreen'; import { AppContext } from 'pages/_app'; import { DeduplicateContext } from 'pages/deduplicate'; +import { IsArchived } from 'utils/magicMetadata'; const Container = styled.div` display: block; @@ -46,6 +47,7 @@ interface Props { setFiles: SetFiles; syncWithRemote: () => Promise; favItemIds?: Set; + archivedCollections?: Set; setSelected: ( selected: SelectedState | ((selected: SelectedState) => SelectedState) ) => void; @@ -71,6 +73,7 @@ const PhotoFrame = ({ setFiles, syncWithRemote, favItemIds, + archivedCollections, setSelected, selected, isFirstLoad, @@ -196,13 +199,14 @@ const PhotoFrame = ({ ) { return false; } - if (activeCollection === ALL_SECTION && fileIsArchived(item)) { + if ( + activeCollection === ALL_SECTION && + (IsArchived(item) || + archivedCollections?.has(item.collectionID)) + ) { return false; } - if ( - activeCollection === ARCHIVE_SECTION && - !fileIsArchived(item) - ) { + if (activeCollection === ARCHIVE_SECTION && !IsArchived(item)) { return false; } diff --git a/src/components/PhotoSwipe/PhotoSwipe.tsx b/src/components/PhotoSwipe/PhotoSwipe.tsx index 6f1ae962e..d677d4e3a 100644 --- a/src/components/PhotoSwipe/PhotoSwipe.tsx +++ b/src/components/PhotoSwipe/PhotoSwipe.tsx @@ -7,7 +7,7 @@ import { addToFavorites, removeFromFavorites, } from 'services/collectionService'; -import { updatePublicMagicMetadata } from 'services/fileService'; +import { updateFilePublicMagicMetadata } from 'services/fileService'; import { EnteFile } from 'types/file'; import constants from 'utils/strings/constants'; import exifr from 'exifr'; @@ -151,7 +151,7 @@ function RenderCreationTime({ unixTimeInMicroSec ); updatedFile = ( - await updatePublicMagicMetadata([updatedFile]) + await updateFilePublicMagicMetadata([updatedFile]) )[0]; updateExistingFilePubMetadata(file, updatedFile); scheduleUpdate(); @@ -342,7 +342,7 @@ function RenderFileName({ const newTitle = getFileTitle(newFilename, extension); let updatedFile = await changeFileName(file, newTitle); updatedFile = ( - await updatePublicMagicMetadata([updatedFile]) + await updateFilePublicMagicMetadata([updatedFile]) )[0]; updateExistingFilePubMetadata(file, updatedFile); scheduleUpdate(); diff --git a/src/components/pages/gallery/CollectionOptions.tsx b/src/components/pages/gallery/CollectionOptions.tsx index dd9ce740a..d54a236da 100644 --- a/src/components/pages/gallery/CollectionOptions.tsx +++ b/src/components/pages/gallery/CollectionOptions.tsx @@ -2,12 +2,17 @@ import React from 'react'; import { SetDialogMessage } from 'components/MessageDialog'; import { ListGroup, Popover } from 'react-bootstrap'; import { deleteCollection, renameCollection } from 'services/collectionService'; -import { downloadCollection, getSelectedCollection } from 'utils/collection'; +import { + changeCollectionVisibilityHelper, + downloadCollection, + getSelectedCollection, +} from 'utils/collection'; import constants from 'utils/strings/constants'; import { SetCollectionNamerAttributes } from './CollectionNamer'; import LinkButton, { ButtonVariant, LinkButtonProps } from './LinkButton'; import { sleep } from 'utils/common'; import { Collection } from 'types/collection'; +import { IsArchived } from 'utils/magicMetadata'; interface CollectionOptionsProps { syncWithRemote: () => Promise; @@ -96,6 +101,19 @@ const CollectionOptions = (props: CollectionOptionsProps) => { }); }; + const archiveCollectionHelper = () => { + changeCollectionVisibilityHelper( + getSelectedCollection( + props.selectedCollectionID, + props.collections + ), + props.startLoading, + props.finishLoading, + props.setDialogMessage, + props.syncWithRemote + ); + }; + const confirmDownloadCollection = () => { props.setDialogMessage({ title: constants.CONFIRM_DOWNLOAD_COLLECTION, @@ -141,6 +159,18 @@ const CollectionOptions = (props: CollectionOptionsProps) => { {constants.DOWNLOAD} + + + {IsArchived( + getSelectedCollection( + props.selectedCollectionID, + props.collections + ) + ) + ? constants.UNARCHIVE + : constants.ARCHIVE} + + ` +const Chip = styled.button<{ active: boolean; archived?: boolean }>` border-radius: 8px; padding: 4px; - padding-left: 24px; + padding-left: 15px; + ${({ archived }) => !archived && 'padding-left: 24px;'} margin: 3px; border: none; background-color: ${(props) => @@ -227,7 +231,20 @@ export default function Collections(props: CollectionProps) { + onClick={clickHandler(item.id)} + archived={IsArchived(item)}> + {IsArchived(item) && ( + +
+ +
+
+ )} {item.name} {item.type !== CollectionType.favorites && item.owner.id === user?.id ? ( diff --git a/src/constants/file/index.ts b/src/constants/file/index.ts index 742265218..a7aa4121a 100644 --- a/src/constants/file/index.ts +++ b/src/constants/file/index.ts @@ -16,8 +16,3 @@ export enum FILE_TYPE { LIVE_PHOTO, OTHERS, } - -export enum VISIBILITY_STATE { - VISIBLE, - ARCHIVED, -} diff --git a/src/pages/gallery/index.tsx b/src/pages/gallery/index.tsx index 230dfab7e..901f691b1 100644 --- a/src/pages/gallery/index.tsx +++ b/src/pages/gallery/index.tsx @@ -10,7 +10,7 @@ import { clearKeys, getKey, SESSION_KEYS } from 'utils/storage/sessionStorage'; import { getLocalFiles, syncFiles, - updateMagicMetadata, + updateFileMagicMetadata, trashFiles, deleteFromTrash, } from 'services/fileService'; @@ -77,6 +77,7 @@ import { handleCollectionOps, getSelectedCollection, isFavoriteCollection, + getArchivedCollections, } from 'utils/collection'; import { logError } from 'utils/sentry'; import { @@ -101,7 +102,7 @@ import { NotificationAttributes, } from 'types/gallery'; import Collections from 'components/pages/gallery/Collections'; -import { VISIBILITY_STATE } from 'constants/file'; +import { VISIBILITY_STATE } from 'types/magicMetadata'; import ToastNotification from 'components/ToastNotification'; export const DeadCenter = styled.div` @@ -193,6 +194,9 @@ export default function Gallery() { const [notificationAttributes, setNotificationAttributes] = useState(null); + const [archivedCollections, setArchivedCollections] = + useState>(); + const showPlanSelectorModal = () => setPlanModalView(true); const clearNotificationAttributes = () => setNotificationAttributes(null); @@ -342,6 +346,9 @@ export default function Gallery() { collectionFilesCount.set(id, files.length); } setCollectionFilesCount(collectionFilesCount); + + const archivedCollections = getArchivedCollections(collections); + setArchivedCollections(new Set(archivedCollections)); }; const clearSelection = function () { @@ -388,7 +395,7 @@ export default function Gallery() { selected, visibility ); - await updateMagicMetadata(updatedFiles); + await updateFileMagicMetadata(updatedFiles); clearSelection(); } catch (e) { logError(e, 'change file visibility failed'); @@ -653,6 +660,7 @@ export default function Gallery() { setFiles={setFiles} syncWithRemote={syncWithRemote} favItemIds={favItemIds} + archivedCollections={archivedCollections} setSelected={setSelected} selected={selected} isFirstLoad={isFirstLoad} diff --git a/src/services/collectionService.ts b/src/services/collectionService.ts index 1dbc0f674..a7712a933 100644 --- a/src/services/collectionService.ts +++ b/src/services/collectionService.ts @@ -25,6 +25,8 @@ import { UpdatePublicURL, } from 'types/collection'; import { COLLECTION_SORT_BY, CollectionType } from 'constants/collection'; +import { UpdateMagicMetadataRequest } from 'types/magicMetadata'; +import { EncryptionResult } from 'types/upload'; const ENDPOINT = getEndpoint(); const COLLECTION_TABLE = 'collections'; @@ -63,6 +65,14 @@ const getCollectionWithSecrets = async ( collection.nameDecryptionNonce, decryptedKey )); + + if (collection.magicMetadata?.data) { + collection.magicMetadata.data = await worker.decryptMetadata( + collection.magicMetadata.data, + collection.magicMetadata.header, + decryptedKey + ); + } return { ...collection, key: decryptedKey, @@ -275,6 +285,7 @@ export const createCollection = async ( sharees: null, updationTime: null, isDeleted: false, + magicMetadata: null, }; let createdCollection: Collection = await postCollection( newCollection, @@ -494,6 +505,48 @@ export const deleteCollection = async ( } }; +export const updateCollectionMagicMetadata = async (collection: Collection) => { + const token = getToken(); + if (!token) { + return; + } + + const worker = await new CryptoWorker(); + + const { file: encryptedMagicMetadata }: EncryptionResult = + await worker.encryptMetadata( + collection.magicMetadata.data, + collection.key + ); + + const reqBody: UpdateMagicMetadataRequest = { + id: collection.id, + magicMetadata: { + version: collection.magicMetadata.version, + count: collection.magicMetadata.count, + data: encryptedMagicMetadata.encryptedData as unknown as string, + header: encryptedMagicMetadata.decryptionHeader, + }, + }; + + await HTTPService.put( + `${ENDPOINT}/collections/magic-metadata`, + reqBody, + null, + { + 'X-Auth-Token': token, + } + ); + const updatedCollection: Collection = { + ...collection, + magicMetadata: { + ...collection.magicMetadata, + version: collection.magicMetadata.version + 1, + }, + }; + return updatedCollection; +}; + export const renameCollection = async ( collection: Collection, newCollectionName: string diff --git a/src/services/fileService.ts b/src/services/fileService.ts index d1b625b3b..ad06867b1 100644 --- a/src/services/fileService.ts +++ b/src/services/fileService.ts @@ -13,9 +13,10 @@ import { sortFiles, } from 'utils/file'; import CryptoWorker from 'utils/crypto'; -import { EnteFile, TrashRequest, UpdateMagicMetadataRequest } from 'types/file'; +import { EnteFile, TrashRequest } from 'types/file'; import { SetFiles } from 'types/gallery'; import { MAX_TRASH_BATCH_SIZE } from 'constants/file'; +import { BulkUpdateMagicMetadataRequest } from 'types/magicMetadata'; const ENDPOINT = getEndpoint(); const FILES_TABLE = 'files'; @@ -203,12 +204,12 @@ export const deleteFromTrash = async (filesToDelete: number[]) => { } }; -export const updateMagicMetadata = async (files: EnteFile[]) => { +export const updateFileMagicMetadata = async (files: EnteFile[]) => { const token = getToken(); if (!token) { return; } - const reqBody: UpdateMagicMetadataRequest = { metadataList: [] }; + const reqBody: BulkUpdateMagicMetadataRequest = { metadataList: [] }; const worker = await new CryptoWorker(); for (const file of files) { const { file: encryptedMagicMetadata }: EncryptionResult = @@ -237,12 +238,12 @@ export const updateMagicMetadata = async (files: EnteFile[]) => { ); }; -export const updatePublicMagicMetadata = async (files: EnteFile[]) => { +export const updateFilePublicMagicMetadata = async (files: EnteFile[]) => { const token = getToken(); if (!token) { return; } - const reqBody: UpdateMagicMetadataRequest = { metadataList: [] }; + const reqBody: BulkUpdateMagicMetadataRequest = { metadataList: [] }; const worker = await new CryptoWorker(); for (const file of files) { const { file: encryptedPubMagicMetadata }: EncryptionResult = diff --git a/src/services/updateCreationTimeWithExif.ts b/src/services/updateCreationTimeWithExif.ts index ab83c8b63..2fa78d141 100644 --- a/src/services/updateCreationTimeWithExif.ts +++ b/src/services/updateCreationTimeWithExif.ts @@ -7,7 +7,7 @@ import { } from 'utils/file'; import { logError } from 'utils/sentry'; import downloadManager from './downloadManager'; -import { updatePublicMagicMetadata } from './fileService'; +import { updateFilePublicMagicMetadata } from './fileService'; import { EnteFile } from 'types/file'; import { getRawExif } from './upload/exifService'; @@ -60,7 +60,7 @@ export async function updateCreationTimeWithExif( correctCreationTime ); updatedFile = ( - await updatePublicMagicMetadata([updatedFile]) + await updateFilePublicMagicMetadata([updatedFile]) )[0]; updateExistingFilePubMetadata(file, updatedFile); } diff --git a/src/types/collection/index.ts b/src/types/collection/index.ts index f865e191b..2307b1153 100644 --- a/src/types/collection/index.ts +++ b/src/types/collection/index.ts @@ -1,6 +1,7 @@ import { User } from 'types/user'; import { EnteFile } from 'types/file'; import { CollectionType } from 'constants/collection'; +import { MagicMetadataCore, VISIBILITY_STATE } from 'types/magicMetadata'; export interface Collection { id: number; @@ -18,6 +19,7 @@ export interface Collection { isDeleted: boolean; isSharedCollection?: boolean; publicURLs?: PublicURL[]; + magicMetadata?: CollectionMagicMetadata; } export interface PublicURL { @@ -80,3 +82,12 @@ export interface RemoveFromCollectionRequest { collectionID: number; fileIDs: number[]; } + +export interface CollectionMagicMetadataProps { + visibility?: VISIBILITY_STATE; +} + +export interface CollectionMagicMetadata + extends Omit { + data: CollectionMagicMetadataProps; +} diff --git a/src/types/file/index.ts b/src/types/file/index.ts index ab0b1a566..b55afbbbf 100644 --- a/src/types/file/index.ts +++ b/src/types/file/index.ts @@ -1,4 +1,4 @@ -import { VISIBILITY_STATE } from 'constants/file'; +import { MagicMetadataCore, VISIBILITY_STATE } from 'types/magicMetadata'; import { DataStream, Metadata } from 'types/upload'; export interface fileAttribute { @@ -7,33 +7,22 @@ export interface fileAttribute { decryptionHeader: string; } -export interface MagicMetadataCore { - version: number; - count: number; - header: string; - data: Record; -} - -export interface EncryptedMagicMetadataCore - extends Omit { - data: string; -} - -export interface MagicMetadataProps { +export interface FileMagicMetadataProps { visibility?: VISIBILITY_STATE; } -export interface MagicMetadata extends Omit { - data: MagicMetadataProps; +export interface FileMagicMetadata extends Omit { + data: FileMagicMetadataProps; } -export interface PublicMagicMetadataProps { +export interface FilePublicMagicMetadataProps { editedTime?: number; editedName?: string; } -export interface PublicMagicMetadata extends Omit { - data: PublicMagicMetadataProps; +export interface FilePublicMagicMetadata + extends Omit { + data: FilePublicMagicMetadataProps; } export interface EnteFile { @@ -43,8 +32,8 @@ export interface EnteFile { file: fileAttribute; thumbnail: fileAttribute; metadata: Metadata; - magicMetadata: MagicMetadata; - pubMagicMetadata: PublicMagicMetadata; + magicMetadata: FileMagicMetadata; + pubMagicMetadata: FilePublicMagicMetadata; encryptedKey: string; keyDecryptionNonce: string; key: string; @@ -60,22 +49,6 @@ export interface EnteFile { updationTime: number; } -export interface UpdateMagicMetadataRequest { - metadataList: UpdateMagicMetadata[]; -} - -export interface UpdateMagicMetadata { - id: number; - magicMetadata: EncryptedMagicMetadataCore; -} - -export const NEW_MAGIC_METADATA: MagicMetadataCore = { - version: 0, - data: {}, - header: null, - count: 0, -}; - export interface TrashRequest { items: TrashRequestItems[]; } diff --git a/src/types/magicMetadata/index.ts b/src/types/magicMetadata/index.ts new file mode 100644 index 000000000..f16d54b9c --- /dev/null +++ b/src/types/magicMetadata/index.ts @@ -0,0 +1,39 @@ +export interface MagicMetadataCore { + version: number; + count: number; + header: string; + data: Record; +} + +export interface EncryptedMagicMetadataCore + extends Omit { + data: string; +} + +export enum VISIBILITY_STATE { + VISIBLE, + ARCHIVED, +} + +export const NEW_FILE_MAGIC_METADATA: MagicMetadataCore = { + version: 0, + data: {}, + header: null, + count: 0, +}; + +export const NEW_COLLECTION_MAGIC_METADATA: MagicMetadataCore = { + version: 1, + data: {}, + header: null, + count: 0, +}; + +export interface BulkUpdateMagicMetadataRequest { + metadataList: UpdateMagicMetadataRequest[]; +} + +export interface UpdateMagicMetadataRequest { + id: number; + magicMetadata: EncryptedMagicMetadataCore; +} diff --git a/src/utils/collection/index.ts b/src/utils/collection/index.ts index c2a02ba96..4f58d4da9 100644 --- a/src/utils/collection/index.ts +++ b/src/utils/collection/index.ts @@ -3,21 +3,27 @@ import { moveToCollection, removeFromCollection, restoreToCollection, + updateCollectionMagicMetadata, } from 'services/collectionService'; import { downloadFiles, getSelectedFiles } from 'utils/file'; import { getLocalFiles } from 'services/fileService'; import { EnteFile } from 'types/file'; -import { CustomError } from 'utils/error'; +import { CustomError, ServerErrorCodes } from 'utils/error'; import { SelectedState } from 'types/gallery'; import { User } from 'types/user'; import { getData, LS_KEYS } from 'utils/storage/localStorage'; import { SetDialogMessage } from 'components/MessageDialog'; import { logError } from 'utils/sentry'; import constants from 'utils/strings/constants'; -import { Collection } from 'types/collection'; +import { Collection, CollectionMagicMetadataProps } from 'types/collection'; import { CollectionType } from 'constants/collection'; import { getAlbumSiteHost } from 'constants/pages'; import { getUnixTimeInMicroSecondsWithDelta } from 'utils/time'; +import { + NEW_COLLECTION_MAGIC_METADATA, + VISIBILITY_STATE, +} from 'types/magicMetadata'; +import { IsArchived, updateMagicMetadataProps } from 'utils/magicMetadata'; export enum COLLECTION_OPS_TYPE { ADD, @@ -161,3 +167,56 @@ export const shareExpiryOptions = [ value: () => getUnixTimeInMicroSecondsWithDelta({ years: 1 }), }, ]; + +export const changeCollectionVisibilityHelper = async ( + collection: Collection, + startLoading: () => void, + finishLoading: () => void, + setDialogMessage: SetDialogMessage, + syncWithRemote: () => Promise +) => { + startLoading(); + try { + const updatedMagicMetadataProps: CollectionMagicMetadataProps = { + visibility: collection.magicMetadata?.data.visibility + ? VISIBILITY_STATE.VISIBLE + : VISIBILITY_STATE.ARCHIVED, + }; + + const updatedCollection = { + ...collection, + magicMetadata: await updateMagicMetadataProps( + collection.magicMetadata ?? NEW_COLLECTION_MAGIC_METADATA, + collection.key, + updatedMagicMetadataProps + ), + } as Collection; + + await updateCollectionMagicMetadata(updatedCollection); + } catch (e) { + logError(e, 'change file visibility failed'); + switch (e.status?.toString()) { + case ServerErrorCodes.FORBIDDEN: + setDialogMessage({ + title: constants.ERROR, + staticBackdrop: true, + close: { variant: 'danger' }, + content: constants.NOT_FILE_OWNER, + }); + return; + } + setDialogMessage({ + title: constants.ERROR, + staticBackdrop: true, + close: { variant: 'danger' }, + content: constants.UNKNOWN_ERROR, + }); + } finally { + await syncWithRemote(); + finishLoading(); + } +}; + +export const getArchivedCollections = (collections: Collection[]) => { + return collections.filter(IsArchived).map((collection) => collection.id); +}; diff --git a/src/utils/file/index.ts b/src/utils/file/index.ts index 2975c25f1..e8b21b6a5 100644 --- a/src/utils/file/index.ts +++ b/src/utils/file/index.ts @@ -2,9 +2,8 @@ import { SelectedState } from 'types/gallery'; import { EnteFile, fileAttribute, - MagicMetadataProps, - NEW_MAGIC_METADATA, - PublicMagicMetadataProps, + FileMagicMetadataProps, + FilePublicMagicMetadataProps, } from 'types/file'; import { decodeMotionPhoto } from 'services/motionPhotoService'; import { getFileType } from 'services/typeDetectionService'; @@ -20,11 +19,12 @@ import { TYPE_HEIC, TYPE_HEIF, FILE_TYPE, - VISIBILITY_STATE, } from 'constants/file'; import PublicCollectionDownloadManager from 'services/publicCollectionDownloadManager'; import HEICConverter from 'services/heicConverter/heicConverterService'; import ffmpegService from 'services/ffmpeg/ffmpegService'; +import { NEW_FILE_MAGIC_METADATA, VISIBILITY_STATE } from 'types/magicMetadata'; +import { updateMagicMetadataProps } from 'utils/magicMetadata'; export function downloadAsFile(filename: string, content: string) { const file = new Blob([content], { type: 'text/plain', @@ -370,90 +370,6 @@ export async function convertForPreview( return [fileBlob]; } -export function fileIsArchived(file: EnteFile) { - if ( - !file || - !file.magicMetadata || - !file.magicMetadata.data || - typeof file.magicMetadata.data === 'string' || - typeof file.magicMetadata.data.visibility === 'undefined' - ) { - return false; - } - return file.magicMetadata.data.visibility === VISIBILITY_STATE.ARCHIVED; -} - -export async function updateMagicMetadataProps( - file: EnteFile, - magicMetadataUpdates: MagicMetadataProps -) { - const worker = await new CryptoWorker(); - - if (!file.magicMetadata) { - file.magicMetadata = NEW_MAGIC_METADATA; - } - if (typeof file.magicMetadata.data === 'string') { - file.magicMetadata.data = (await worker.decryptMetadata( - file.magicMetadata.data, - file.magicMetadata.header, - file.key - )) as MagicMetadataProps; - } - if (magicMetadataUpdates) { - // copies the existing magic metadata properties of the files and updates the visibility value - // The expected behaviour while updating magic metadata is to let the existing property as it is and update/add the property you want - const magicMetadataProps: MagicMetadataProps = { - ...file.magicMetadata.data, - ...magicMetadataUpdates, - }; - - return { - ...file, - magicMetadata: { - ...file.magicMetadata, - data: magicMetadataProps, - count: Object.keys(file.magicMetadata.data).length, - }, - }; - } else { - return file; - } -} -export async function updatePublicMagicMetadataProps( - file: EnteFile, - publicMetadataUpdates: PublicMagicMetadataProps -) { - const worker = await new CryptoWorker(); - - if (!file.pubMagicMetadata) { - file.pubMagicMetadata = NEW_MAGIC_METADATA; - } - if (typeof file.pubMagicMetadata.data === 'string') { - file.pubMagicMetadata.data = (await worker.decryptMetadata( - file.pubMagicMetadata.data, - file.pubMagicMetadata.header, - file.key - )) as PublicMagicMetadataProps; - } - - if (publicMetadataUpdates) { - const publicMetadataProps = { - ...file.pubMagicMetadata.data, - ...publicMetadataUpdates, - }; - return { - ...file, - pubMagicMetadata: { - ...file.pubMagicMetadata, - data: publicMetadataProps, - count: Object.keys(file.pubMagicMetadata.data).length, - }, - }; - } else { - return file; - } -} - export async function changeFilesVisibility( files: EnteFile[], selected: SelectedState, @@ -462,13 +378,18 @@ export async function changeFilesVisibility( const selectedFiles = getSelectedFiles(selected, files); const updatedFiles: EnteFile[] = []; for (const file of selectedFiles) { - const updatedMagicMetadataProps: MagicMetadataProps = { + const updatedMagicMetadataProps: FileMagicMetadataProps = { visibility, }; - updatedFiles.push( - await updateMagicMetadataProps(file, updatedMagicMetadataProps) - ); + updatedFiles.push({ + ...file, + magicMetadata: await updateMagicMetadataProps( + file.magicMetadata ?? NEW_FILE_MAGIC_METADATA, + file.key, + updatedMagicMetadataProps + ), + }); } return updatedFiles; } @@ -477,25 +398,33 @@ export async function changeFileCreationTime( file: EnteFile, editedTime: number ) { - const updatedPublicMagicMetadataProps: PublicMagicMetadataProps = { + const updatedPublicMagicMetadataProps: FilePublicMagicMetadataProps = { editedTime, }; - return await updatePublicMagicMetadataProps( - file, - updatedPublicMagicMetadataProps - ); + return { + ...file, + publicMagicMetadata: await updateMagicMetadataProps( + file.pubMagicMetadata ?? NEW_FILE_MAGIC_METADATA, + file.key, + updatedPublicMagicMetadataProps + ), + } as EnteFile; } export async function changeFileName(file: EnteFile, editedName: string) { - const updatedPublicMagicMetadataProps: PublicMagicMetadataProps = { + const updatedPublicMagicMetadataProps: FilePublicMagicMetadataProps = { editedName, }; - return await updatePublicMagicMetadataProps( - file, - updatedPublicMagicMetadataProps - ); + return { + ...file, + publicMagicMetadata: await updateMagicMetadataProps( + file.pubMagicMetadata ?? NEW_FILE_MAGIC_METADATA, + file.key, + updatedPublicMagicMetadataProps + ), + } as EnteFile; } export function isSharedFile(file: EnteFile) { @@ -590,4 +519,4 @@ 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]; + [FILE_TYPE.IMAGE, FILE_TYPE.VIDEO].includes(fileType); diff --git a/src/utils/magicMetadata/index.ts b/src/utils/magicMetadata/index.ts new file mode 100644 index 000000000..2a1171ff3 --- /dev/null +++ b/src/utils/magicMetadata/index.ts @@ -0,0 +1,52 @@ +import { Collection } from 'types/collection'; +import { EnteFile, FileMagicMetadataProps } from 'types/file'; +import { MagicMetadataCore, VISIBILITY_STATE } from 'types/magicMetadata'; +import CryptoWorker from 'utils/crypto'; + +export function IsArchived(item: Collection | EnteFile) { + if ( + !item || + !item.magicMetadata || + !item.magicMetadata.data || + typeof item.magicMetadata.data === 'string' || + typeof item.magicMetadata.data.visibility === 'undefined' + ) { + return false; + } + return item.magicMetadata.data.visibility === VISIBILITY_STATE.ARCHIVED; +} + +export async function updateMagicMetadataProps( + originalMagicMetadata: MagicMetadataCore, + decryptionKey: string, + magicMetadataUpdates: Record +) { + const worker = await new CryptoWorker(); + + if (!originalMagicMetadata) { + throw Error('invalid originalMagicMetadata '); + } + if (typeof originalMagicMetadata.data === 'string') { + originalMagicMetadata.data = (await worker.decryptMetadata( + originalMagicMetadata.data, + originalMagicMetadata.header, + decryptionKey + )) as FileMagicMetadataProps; + } + if (magicMetadataUpdates) { + // copies the existing magic metadata properties of the files and updates the visibility value + // The expected behavior while updating magic metadata is to let the existing property as it is and update/add the property you want + const magicMetadataProps: FileMagicMetadataProps = { + ...originalMagicMetadata.data, + ...magicMetadataUpdates, + }; + + return { + ...originalMagicMetadata, + data: magicMetadataProps, + count: Object.keys(magicMetadataProps).length, + }; + } else { + return originalMagicMetadata; + } +} diff --git a/src/utils/strings/englishConstants.tsx b/src/utils/strings/englishConstants.tsx index d1cd4405d..bcf3d2fea 100644 --- a/src/utils/strings/englishConstants.tsx +++ b/src/utils/strings/englishConstants.tsx @@ -380,8 +380,10 @@ const englishConstants = {

all files will be queued for download sequentially

), + ARCHIVED_ALBUM: 'archived album', DOWNLOAD_COLLECTION_FAILED: 'album downloading failed, please try again', CREATE_ALBUM_FAILED: 'failed to create album , please try again', + SEARCH_HINT: () => ( try searching for New York, April 14, Christmas... ),