This commit is contained in:
Manav Rathi
2025-06-17 07:45:57 +05:30
parent 41102dab4b
commit dabdb01ce6
6 changed files with 59 additions and 170 deletions

View File

@@ -40,6 +40,10 @@ import {
HIDDEN_ITEMS_SECTION,
isHiddenCollection,
leaveSharedCollection,
renameCollection,
updateCollectionOrder,
updateCollectionSortOrder,
updateCollectionVisibility,
} from "ente-new/photos/services/collection";
import type {
CollectionSummary,
@@ -57,9 +61,6 @@ import { Trans } from "react-i18next";
import * as CollectionAPI from "services/collectionService";
import { SetFilesDownloadProgressAttributesCreator } from "types/gallery";
import {
changeCollectionOrder,
changeCollectionSortOrder,
changeCollectionVisibility,
downloadCollectionHelper,
downloadDefaultHiddenCollectionHelper,
} from "utils/collection";
@@ -166,10 +167,10 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
[showLoadingBar, hideLoadingBar, onGenericError, syncWithRemote],
);
const renameCollection = useCallback(
const handleRenameCollection = useCallback(
async (newName: string) => {
if (activeCollection.name !== newName) {
await CollectionAPI.renameCollection(activeCollection, newName);
await renameCollection(activeCollection, newName);
void syncWithRemote(false, true);
}
},
@@ -254,11 +255,11 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
void _downloadCollection().catch(onGenericError);
const archiveAlbum = wrap(() =>
changeCollectionVisibility(activeCollection, ItemVisibility.archived),
updateCollectionVisibility(activeCollection, ItemVisibility.archived),
);
const unarchiveAlbum = wrap(() =>
changeCollectionVisibility(activeCollection, ItemVisibility.visible),
updateCollectionVisibility(activeCollection, ItemVisibility.visible),
);
const confirmLeaveSharedAlbum = () =>
@@ -278,15 +279,15 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
});
const pinAlbum = wrap(() =>
changeCollectionOrder(activeCollection, CollectionOrder.pinned),
updateCollectionOrder(activeCollection, CollectionOrder.pinned),
);
const unpinAlbum = wrap(() =>
changeCollectionOrder(activeCollection, CollectionOrder.default),
updateCollectionOrder(activeCollection, CollectionOrder.default),
);
const hideAlbum = wrap(async () => {
await changeCollectionVisibility(
await updateCollectionVisibility(
activeCollection,
ItemVisibility.hidden,
);
@@ -294,7 +295,7 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
});
const unhideAlbum = wrap(async () => {
await changeCollectionVisibility(
await updateCollectionVisibility(
activeCollection,
ItemVisibility.visible,
);
@@ -302,11 +303,11 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
});
const changeSortOrderAsc = wrap(() =>
changeCollectionSortOrder(activeCollection, true),
updateCollectionSortOrder(activeCollection, true),
);
const changeSortOrderDesc = wrap(() =>
changeCollectionSortOrder(activeCollection, false),
updateCollectionSortOrder(activeCollection, false),
);
let menuOptions: React.ReactNode[] = [];
@@ -527,7 +528,7 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
initialValue={activeCollection?.name}
submitButtonColor="primary"
submitButtonTitle={t("rename")}
onSubmit={renameCollection}
onSubmit={handleRenameCollection}
/>
</Box>
);

View File

@@ -12,11 +12,9 @@ import { EnteFile } from "ente-media/file";
import { ItemVisibility } from "ente-media/file-metadata";
import {
addToCollection,
collection1To2,
createCollection2,
isDefaultHiddenCollection,
moveToCollection,
renameCollection2,
} from "ente-new/photos/services/collection";
import type { CollectionSummary } from "ente-new/photos/services/collection/ui";
import {
@@ -252,11 +250,6 @@ export const deleteCollection = async (
}
};
export const renameCollection = async (
collection: Collection,
newCollectionName: string,
) => renameCollection2(await collection1To2(collection), newCollectionName);
/**
* Return the user's own favorites collection, if any.
*/

View File

@@ -2,26 +2,17 @@ import type { User } from "ente-accounts/services/user";
import { ensureElectron } from "ente-base/electron";
import { joinPath } from "ente-base/file-name";
import log from "ente-base/log";
import {
type Collection,
type CollectionOrder,
CollectionSubType,
} from "ente-media/collection";
import { type Collection, CollectionSubType } from "ente-media/collection";
import { EnteFile } from "ente-media/file";
import { ItemVisibility } from "ente-media/file-metadata";
import {
DEFAULT_HIDDEN_COLLECTION_USER_FACING_NAME,
HIDDEN_ITEMS_SECTION,
addToCollection,
collection1To2,
findDefaultHiddenCollectionIDs,
isHiddenCollection,
isIncomingShare,
moveToCollection,
restoreToCollection,
updateCollectionOrder,
updateCollectionSortOrder,
updateCollectionVisibility,
} from "ente-new/photos/services/collection";
import {
getAllLocalCollections,
@@ -181,21 +172,6 @@ async function createCollectionDownloadFolder(
return collectionDownloadPath;
}
export const changeCollectionVisibility = async (
collection: Collection,
visibility: ItemVisibility,
) => updateCollectionVisibility(await collection1To2(collection), visibility);
export const changeCollectionOrder = async (
collection: Collection,
order: CollectionOrder,
) => updateCollectionOrder(await collection1To2(collection), order);
export const changeCollectionSortOrder = async (
collection: Collection,
asc: boolean,
) => updateCollectionSortOrder(await collection1To2(collection), asc);
export const getUserOwnedCollections = (collections: Collection[]) => {
const user: User = getData("user");
if (!user?.id) {

View File

@@ -1,5 +1,4 @@
import { decryptBoxBytes } from "ente-base/crypto";
import { type MagicMetadataCore } from "ente-media/file";
import {
nullishToEmpty,
nullishToFalse,
@@ -23,11 +22,8 @@ import {
*
* A collection can be owned by the user (in whose context this code is
* running), or might be a collection that is shared with them.
*
* TODO: This type supercedes {@link Collection}. Once migration is done, rename
* this to drop the "2" suffix.
*/
export interface Collection2 {
export interface Collection {
/**
* The collection's globally unique ID.
*
@@ -431,22 +427,6 @@ export const RemoteCollection = z.looseObject({
export type RemoteCollection = z.infer<typeof RemoteCollection>;
export interface Collection {
id: number;
owner: CollectionUser;
type: CollectionType;
sharees: CollectionUser[];
publicURLs?: PublicURL[];
updationTime: number;
key: string;
name: string;
magicMetadata: MagicMetadataCore<CollectionPrivateMagicMetadataData>;
pubMagicMetadata: MagicMetadataCore<CollectionPublicMagicMetadataData>;
sharedMagicMetadata: MagicMetadataCore<CollectionShareeMagicMetadataData>;
// TODO(C2): Gradual conversion to new structure.
c2?: Collection2;
}
/**
* Decrypt a remote collection using the provided {@link collectionKey}.
*
@@ -456,8 +436,6 @@ export interface Collection {
* encrypted fields in {@link collection}.
*
* @returns A decrypted collection.
*
* TODO(C2): For legacy compat, it returns the older structure.
*/
export const decryptRemoteCollection = async (
collection: RemoteCollection,
@@ -495,7 +473,7 @@ export const decryptRemoteCollection = async (
),
);
let magicMetadata: Collection2["magicMetadata"];
let magicMetadata: Collection["magicMetadata"];
if (encryptedMagicMetadata) {
const genericMM = await decryptMagicMetadata(
encryptedMagicMetadata,
@@ -505,7 +483,7 @@ export const decryptRemoteCollection = async (
magicMetadata = { ...genericMM, data };
}
let pubMagicMetadata: Collection2["pubMagicMetadata"];
let pubMagicMetadata: Collection["pubMagicMetadata"];
if (encryptedPubMagicMetadata) {
const genericMM = await decryptMagicMetadata(
encryptedPubMagicMetadata,
@@ -515,7 +493,7 @@ export const decryptRemoteCollection = async (
pubMagicMetadata = { ...genericMM, data };
}
let sharedMagicMetadata: Collection2["sharedMagicMetadata"];
let sharedMagicMetadata: Collection["sharedMagicMetadata"];
if (encryptedSharedMagicMetadata) {
const genericMM = await decryptMagicMetadata(
encryptedSharedMagicMetadata,
@@ -525,8 +503,7 @@ export const decryptRemoteCollection = async (
sharedMagicMetadata = { ...genericMM, data };
}
// return {
const c2 = {
return {
...rest,
key: collectionKey,
owner: parseRemoteCollectionUser(owner),
@@ -536,36 +513,6 @@ export const decryptRemoteCollection = async (
pubMagicMetadata,
sharedMagicMetadata,
};
// Temporary scaffolding for the migration.
return {
...collection,
// See: [Note: strict mode migration]
c2: c2 as Collection2,
key: collectionKey,
name,
type: collection.type as CollectionType,
// Some temporary scaffolding to impersonate types.
//
// See: [Note: strict mode migration]
sharees: sharees as CollectionUser[],
publicURLs: collection.publicURLs as PublicURL[],
magicMetadata: (magicMetadata
? { ...magicMetadata, header: collection.magicMetadata!.header }
: undefined)!,
pubMagicMetadata: (pubMagicMetadata
? {
...pubMagicMetadata,
header: collection.pubMagicMetadata!.header,
}
: undefined)!,
sharedMagicMetadata: (sharedMagicMetadata
? {
...sharedMagicMetadata,
header: collection.sharedMagicMetadata!.header,
}
: undefined)!,
};
};
/**

View File

@@ -15,7 +15,6 @@ import {
RemoteCollection,
RemotePublicURL,
type Collection,
type Collection2,
type CollectionNewParticipantRole,
type CollectionOrder,
type CollectionPrivateMagicMetadataData,
@@ -58,8 +57,6 @@ export const ALL_SECTION = 0;
* See also: [Note: Multiple "default" hidden collections].
*/
export const isDefaultHiddenCollection = (collection: Collection) =>
// TODO: Need to audit the types
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
collection.magicMetadata?.data.subType == CollectionSubType.defaultHidden;
/**
@@ -85,8 +82,6 @@ export const isIncomingShare = (collection: Collection, user: User) =>
collection.owner.id !== user.id;
export const isHiddenCollection = (collection: Collection) =>
// TODO: Need to audit the types
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
collection.magicMetadata?.data.visibility === ItemVisibility.hidden;
export const DEFAULT_HIDDEN_COLLECTION_USER_FACING_NAME = "Hidden";
@@ -152,26 +147,6 @@ export const createCollection2 = async (
const decryptRemoteKeyAndCollection = async (collection: RemoteCollection) =>
decryptRemoteCollection(collection, await decryptCollectionKey(collection));
// TODO(C2): Temporary method to convert to the newer type.
export const collection1To2 = async (c1: Collection): Promise<Collection2> => {
const collection = RemoteCollection.parse({
...c1,
magicMetadata: undefined,
pubMagicMetadata: undefined,
sharedMagicMetadata: undefined,
});
const c2 = await decryptRemoteCollection(
collection,
await decryptCollectionKey(collection),
);
return {
...c2,
magicMetadata: c1.magicMetadata,
pubMagicMetadata: c1.pubMagicMetadata,
sharedMagicMetadata: c1.sharedMagicMetadata,
};
};
/**
* Return the decrypted collection key (as a base64 string) for the given
* {@link RemoteCollection}.
@@ -505,8 +480,8 @@ export const deleteFromTrash = async (fileIDs: number[]) => {
*
* @param newName The new name of the collection
*/
export const renameCollection2 = async (
collection: Collection2,
export const renameCollection = async (
collection: Collection,
newName: string,
) => {
if (collection.magicMetadata?.data.subType == CollectionSubType.quicklink) {
@@ -552,7 +527,7 @@ const postCollectionsRename = async (renameRequest: RenameRequest) =>
* @param visibility The new visibility (normal, archived, hidden).
*/
export const updateCollectionVisibility = async (
collection: Collection2,
collection: Collection,
visibility: ItemVisibility,
) =>
collection.owner.id == ensureLocalUser().id
@@ -571,7 +546,7 @@ export const updateCollectionVisibility = async (
* @param order Whether on not the collection is pinned.
*/
export const updateCollectionOrder = async (
collection: Collection2,
collection: Collection,
order: CollectionOrder,
) => updateCollectionPrivateMagicMetadata(collection, { order });
@@ -588,7 +563,7 @@ export const updateCollectionOrder = async (
* Otherwise they are sorted descending (newest first).
*/
export const updateCollectionSortOrder = async (
collection: Collection2,
collection: Collection,
asc: boolean,
) => updateCollectionPublicMagicMetadata(collection, { asc });
@@ -610,7 +585,7 @@ export const updateCollectionSortOrder = async (
* See: [Note: Magic metadata data cannot have nullish values]
*/
export const updateCollectionPrivateMagicMetadata = async (
{ id, key, magicMetadata }: Collection2,
{ id, key, magicMetadata }: Collection,
updates: CollectionPrivateMagicMetadataData,
) =>
putCollectionsMagicMetadata({
@@ -668,7 +643,7 @@ const putCollectionsMagicMetadata = async (
* with the {@link pubMagicMetadata} of a collection.
*/
const updateCollectionPublicMagicMetadata = async (
{ id, key, pubMagicMetadata }: Collection2,
{ id, key, pubMagicMetadata }: Collection,
updates: CollectionPublicMagicMetadataData,
) =>
putCollectionsPublicMagicMetadata({
@@ -705,7 +680,7 @@ const putCollectionsPublicMagicMetadata = async (
* with the {@link sharedMagicMetadata} of a collection.
*/
const updateCollectionShareeMagicMetadata = async (
{ id, key, sharedMagicMetadata }: Collection2,
{ id, key, sharedMagicMetadata }: Collection,
updates: CollectionShareeMagicMetadataData,
) =>
putCollectionsShareeMagicMetadata({

View File

@@ -24,23 +24,20 @@ import {
CollectionPrivateMagicMetadataData,
CollectionPublicMagicMetadataData,
CollectionShareeMagicMetadataData,
ignore,
RemoteCollectionUser,
RemotePublicURL,
type Collection2,
type Collection,
} from "ente-media/collection";
import { RemoteMagicMetadata } from "ente-media/magic-metadata";
import localForage from "ente-shared/storage/localForage";
import { nullishToEmpty } from "ente-utils/transform";
import { nullishToEmpty, nullToUndefined } from "ente-utils/transform";
import { z } from "zod/v4";
/**
* Zod schema for a {@link Collection} saved in our local persistence.
*
* This is similar to {@link RemoteCollection}, but has significant differences
* too in that it contains the decrypted fields, and some minor refinements.
* This is similar to {@link RemoteCollection}, but has both significant
* differences in that it contains the decrypted fields, and some minor tweaks.
*/
// TODO(C2): Use me
const LocalCollection = z.looseObject({
id: z.number(),
owner: RemoteCollectionUser,
@@ -50,30 +47,30 @@ const LocalCollection = z.looseObject({
sharees: z.array(RemoteCollectionUser).nullish().transform(nullishToEmpty),
publicURLs: z.array(RemotePublicURL).nullish().transform(nullishToEmpty),
updationTime: z.number(),
magicMetadata: RemoteMagicMetadata.nullish().transform((mm) => {
if (!mm) return undefined;
// Old code used to save the header, however it's unnecessary so we drop
// it on the next read. New code will not save it, so eventually this
// special case can be removed. Note added Jun 2025 (tag: Migration).
const { header, ...rest } = mm;
ignore(header);
const data = CollectionPrivateMagicMetadataData.parse(rest.data);
return { ...rest, data };
}),
pubMagicMetadata: RemoteMagicMetadata.nullish().transform((mm) => {
if (!mm) return undefined;
const { header, ...rest } = mm;
ignore(header);
const data = CollectionPublicMagicMetadataData.parse(rest.data);
return { ...rest, data };
}),
sharedMagicMetadata: RemoteMagicMetadata.nullish().transform((mm) => {
if (!mm) return undefined;
const { header, ...rest } = mm;
ignore(header);
const data = CollectionShareeMagicMetadataData.parse(rest.data);
return { ...rest, data };
}),
magicMetadata: z
.object({
version: z.number(),
count: z.number(),
data: CollectionPrivateMagicMetadataData,
})
.nullish()
.transform(nullToUndefined),
pubMagicMetadata: z
.object({
version: z.number(),
count: z.number(),
data: CollectionPublicMagicMetadataData,
})
.nullish()
.transform(nullToUndefined),
sharedMagicMetadata: z
.object({
version: z.number(),
count: z.number(),
data: CollectionShareeMagicMetadataData,
})
.nullish()
.transform(nullToUndefined),
});
const LocalCollections = z.array(LocalCollection);
@@ -85,7 +82,7 @@ const LocalCollections = z.array(LocalCollection);
*
* Use {@link saveCollections} to update the database.
*/
export const savedCollections = async (): Promise<Collection2[]> =>
export const savedCollections = async (): Promise<Collection[]> =>
LocalCollections.parse(await localForage.getItem("collections"));
/**
@@ -95,5 +92,5 @@ export const savedCollections = async (): Promise<Collection2[]> =>
* collections (the split between normal and hidden is not at the database level
* but is a filter when they are accessed).
*/
export const saveCollections = (collections: Collection2[]) =>
export const saveCollections = (collections: Collection[]) =>
localForage.setItem("collections", collections);