commit
9c8a5f9440
|
@ -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<void>;
|
||||
favItemIds?: Set<number>;
|
||||
archivedCollections?: Set<number>;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<void>;
|
||||
|
@ -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}
|
||||
</MenuLink>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<MenuLink onClick={archiveCollectionHelper}>
|
||||
{IsArchived(
|
||||
getSelectedCollection(
|
||||
props.selectedCollectionID,
|
||||
props.collections
|
||||
)
|
||||
)
|
||||
? constants.UNARCHIVE
|
||||
: constants.ARCHIVE}
|
||||
</MenuLink>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<MenuLink
|
||||
variant={ButtonVariant.danger}
|
||||
|
|
|
@ -24,6 +24,9 @@ import {
|
|||
COLLECTION_SORT_BY,
|
||||
TRASH_SECTION,
|
||||
} from 'constants/collection';
|
||||
import { IsArchived } from 'utils/magicMetadata';
|
||||
import Archive from 'components/icons/Archive';
|
||||
import { IconWithMessage } from 'components/IconWithMessage';
|
||||
|
||||
interface CollectionProps {
|
||||
collections: Collection[];
|
||||
|
@ -69,10 +72,11 @@ const CollectionBar = styled.div`
|
|||
justify-content: flex-start;
|
||||
`;
|
||||
|
||||
const Chip = styled.button<{ active: boolean }>`
|
||||
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) {
|
|||
<Chip
|
||||
ref={collectionChipsRef[item.id]}
|
||||
active={activeCollection === item.id}
|
||||
onClick={clickHandler(item.id)}>
|
||||
onClick={clickHandler(item.id)}
|
||||
archived={IsArchived(item)}>
|
||||
{IsArchived(item) && (
|
||||
<IconWithMessage
|
||||
message={constants.ARCHIVED_ALBUM}>
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
marginRight: '5px',
|
||||
}}>
|
||||
<Archive />
|
||||
</div>
|
||||
</IconWithMessage>
|
||||
)}
|
||||
{item.name}
|
||||
{item.type !== CollectionType.favorites &&
|
||||
item.owner.id === user?.id ? (
|
||||
|
|
|
@ -16,8 +16,3 @@ export enum FILE_TYPE {
|
|||
LIVE_PHOTO,
|
||||
OTHERS,
|
||||
}
|
||||
|
||||
export enum VISIBILITY_STATE {
|
||||
VISIBLE,
|
||||
ARCHIVED,
|
||||
}
|
||||
|
|
|
@ -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<NotificationAttributes>(null);
|
||||
|
||||
const [archivedCollections, setArchivedCollections] =
|
||||
useState<Set<number>>();
|
||||
|
||||
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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<MagicMetadataCore, 'data'> {
|
||||
data: CollectionMagicMetadataProps;
|
||||
}
|
||||
|
|
|
@ -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<string, any>;
|
||||
}
|
||||
|
||||
export interface EncryptedMagicMetadataCore
|
||||
extends Omit<MagicMetadataCore, 'data'> {
|
||||
data: string;
|
||||
}
|
||||
|
||||
export interface MagicMetadataProps {
|
||||
export interface FileMagicMetadataProps {
|
||||
visibility?: VISIBILITY_STATE;
|
||||
}
|
||||
|
||||
export interface MagicMetadata extends Omit<MagicMetadataCore, 'data'> {
|
||||
data: MagicMetadataProps;
|
||||
export interface FileMagicMetadata extends Omit<MagicMetadataCore, 'data'> {
|
||||
data: FileMagicMetadataProps;
|
||||
}
|
||||
|
||||
export interface PublicMagicMetadataProps {
|
||||
export interface FilePublicMagicMetadataProps {
|
||||
editedTime?: number;
|
||||
editedName?: string;
|
||||
}
|
||||
|
||||
export interface PublicMagicMetadata extends Omit<MagicMetadataCore, 'data'> {
|
||||
data: PublicMagicMetadataProps;
|
||||
export interface FilePublicMagicMetadata
|
||||
extends Omit<MagicMetadataCore, 'data'> {
|
||||
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[];
|
||||
}
|
||||
|
|
39
src/types/magicMetadata/index.ts
Normal file
39
src/types/magicMetadata/index.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
export interface MagicMetadataCore {
|
||||
version: number;
|
||||
count: number;
|
||||
header: string;
|
||||
data: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface EncryptedMagicMetadataCore
|
||||
extends Omit<MagicMetadataCore, 'data'> {
|
||||
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;
|
||||
}
|
|
@ -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<void>
|
||||
) => {
|
||||
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);
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
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,
|
||||
return {
|
||||
...file,
|
||||
publicMagicMetadata: await updateMagicMetadataProps(
|
||||
file.pubMagicMetadata ?? NEW_FILE_MAGIC_METADATA,
|
||||
file.key,
|
||||
updatedPublicMagicMetadataProps
|
||||
);
|
||||
),
|
||||
} as EnteFile;
|
||||
}
|
||||
|
||||
export function isSharedFile(file: EnteFile) {
|
||||
|
|
52
src/utils/magicMetadata/index.ts
Normal file
52
src/utils/magicMetadata/index.ts
Normal file
|
@ -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<string, any>
|
||||
) {
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -380,8 +380,10 @@ const englishConstants = {
|
|||
<p>all files will be queued for download sequentially</p>
|
||||
</>
|
||||
),
|
||||
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: () => (
|
||||
<span>try searching for New York, April 14, Christmas...</span>
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue