diff --git a/web/apps/photos/src/components/FixCreationTime.tsx b/web/apps/photos/src/components/FixCreationTime.tsx index 986f0a106b..bee5e78621 100644 --- a/web/apps/photos/src/components/FixCreationTime.tsx +++ b/web/apps/photos/src/components/FixCreationTime.tsx @@ -18,7 +18,6 @@ import { downloadManager } from "ente-gallery/services/download"; import { extractExifDates } from "ente-gallery/services/exif"; import { fileLogID, type EnteFile } from "ente-media/file"; import { - decryptPublicMagicMetadata, fileCreationPhotoDate, fileFileName, type ParsedMetadataDate, @@ -343,10 +342,7 @@ const updateFileDate = async ( if (!newDate) return; - const existingDate = fileCreationPhotoDate( - file, - await decryptPublicMagicMetadata(file), - ); + const existingDate = fileCreationPhotoDate(file); if (newDate.timestamp == existingDate.getTime()) return; await updateFilePublicMagicMetadata(file, { diff --git a/web/packages/gallery/components/FileInfo.tsx b/web/packages/gallery/components/FileInfo.tsx index 1d891e0b81..5d272738bb 100644 --- a/web/packages/gallery/components/FileInfo.tsx +++ b/web/packages/gallery/components/FileInfo.tsx @@ -58,11 +58,9 @@ import { tagNumericValue, type RawExifTags } from "ente-gallery/services/exif"; import { formattedByteSize } from "ente-gallery/utils/units"; import { type EnteFile } from "ente-media/file"; import { - fileCaption, fileCreationPhotoDate, fileFileName, fileLocation, - filePublicMagicMetadata, type ParsedMetadata, type ParsedMetadataDate, } from "ente-media/file-metadata"; @@ -260,7 +258,7 @@ export const FileInfo: React.FC = ({ onSelectPerson?.(personID); }; - const uploaderName = filePublicMagicMetadata(file)?.uploaderName; + const uploaderName = file.pubMagicMetadata?.data.uploaderName; return ( @@ -582,7 +580,7 @@ const Caption: React.FC = ({ }) => { const [isSaving, setIsSaving] = useState(false); - const caption = fileCaption(file) ?? ""; + const caption = file.pubMagicMetadata?.data.caption ?? ""; const formik = useFormik<{ caption: string }>({ initialValues: { caption }, @@ -675,10 +673,7 @@ const CreationTime: React.FC = ({ const [isEditing, setIsEditing] = useState(false); const [isSaving, setIsSaving] = useState(false); - const originalDate = fileCreationPhotoDate( - file, - filePublicMagicMetadata(file), - ); + const originalDate = fileCreationPhotoDate(file); const saveEdits = async (pickedTime: ParsedMetadataDate) => { setIsEditing(false); diff --git a/web/packages/gallery/components/viewer/FileViewer.tsx b/web/packages/gallery/components/viewer/FileViewer.tsx index baa10f24d5..90323618fc 100644 --- a/web/packages/gallery/components/viewer/FileViewer.tsx +++ b/web/packages/gallery/components/viewer/FileViewer.tsx @@ -37,11 +37,7 @@ import { type FileInfoProps, } from "ente-gallery/components/FileInfo"; import type { Collection } from "ente-media/collection"; -import { - fileFileName, - fileVisibility, - ItemVisibility, -} from "ente-media/file-metadata"; +import { fileFileName, ItemVisibility } from "ente-media/file-metadata"; import { FileType } from "ente-media/file-type"; import type { EnteFile } from "ente-media/file.js"; import { isHEICExtension, needsJPEGConversion } from "ente-media/formats"; @@ -670,7 +666,7 @@ export const FileViewer: React.FC = ({ file && activeAnnotatedFile.annotation.showArchive ) { - switch (fileVisibility(file)) { + switch (file.magicMetadata?.data.visibility) { case undefined: case ItemVisibility.visible: isArchived = false; diff --git a/web/packages/gallery/components/viewer/data-source.ts b/web/packages/gallery/components/viewer/data-source.ts index 1611c9e41f..f13df294b4 100644 --- a/web/packages/gallery/components/viewer/data-source.ts +++ b/web/packages/gallery/components/viewer/data-source.ts @@ -7,7 +7,6 @@ import { type HLSPlaylistDataForFile, } from "ente-gallery/services/video"; import type { EnteFile } from "ente-media/file"; -import { fileCaption, filePublicMagicMetadata } from "ente-media/file-metadata"; import { FileType } from "ente-media/file-type"; import { ensureString } from "ente-utils/ensure"; @@ -439,7 +438,7 @@ const enqueueUpdates = async ( const update = (itemData: Partial, validTill?: Date) => { // Use the file's caption as its alt text (in addition to using it as // the visible caption). - const alt = fileCaption(file); + const alt = file.pubMagicMetadata?.data.caption; _state.itemDataByFileID.set(file.id, { ...itemData, @@ -647,8 +646,7 @@ const thumbnailDimensions = ( { width: thumbnailWidth, height: thumbnailHeight }: Partial, file: EnteFile, ) => { - const { w: imageWidth, h: imageHeight } = - filePublicMagicMetadata(file) ?? {}; + const { w: imageWidth, h: imageHeight } = file.pubMagicMetadata?.data ?? {}; if (thumbnailWidth && thumbnailHeight && imageWidth && imageHeight) { const arThumb = thumbnailWidth / thumbnailHeight; const arImage = imageWidth / imageHeight; diff --git a/web/packages/gallery/services/upload/upload-service.ts b/web/packages/gallery/services/upload/upload-service.ts index 6a0cb9ba31..b6c89e9a58 100644 --- a/web/packages/gallery/services/upload/upload-service.ts +++ b/web/packages/gallery/services/upload/upload-service.ts @@ -33,14 +33,13 @@ import type { EncryptedMagicMetadata, EnteFile, FilePublicMagicMetadata, - FilePublicMagicMetadataProps, } from "ente-media/file"; import { fileFileName, metadataHash, type FileMetadata, + type FilePublicMagicMetadataData, type ParsedMetadata, - type PublicMagicMetadata, } from "ente-media/file-metadata"; import { FileType, type FileTypeInfo } from "ente-media/file-type"; import { encodeLivePhoto } from "ente-media/live-photo"; @@ -1047,7 +1046,7 @@ const readEntireStream = async (stream: ReadableStream) => interface ExtractAssetMetadataResult { metadata: FileMetadata; - publicMagicMetadata: FilePublicMagicMetadataProps; + publicMagicMetadata: FilePublicMagicMetadataData; } /** @@ -1171,7 +1170,7 @@ const extractImageOrVideoMetadata = async ( parsedMetadataJSONMap, ); - const publicMagicMetadata: PublicMagicMetadata = {}; + const publicMagicMetadata: FilePublicMagicMetadataData = {}; const modificationTime = parsedMetadataJSON?.modificationTime ?? lastModifiedMs * 1000; @@ -1490,7 +1489,7 @@ const augmentWithThumbnail = async ( }; const constructPublicMagicMetadata = async ( - publicMagicMetadataProps: FilePublicMagicMetadataProps, + publicMagicMetadataProps: FilePublicMagicMetadataData, ): Promise => { const nonEmptyPublicMagicMetadataProps = getNonEmptyMagicMetadataProps( publicMagicMetadataProps, diff --git a/web/packages/gallery/services/video.ts b/web/packages/gallery/services/video.ts index ebdfb362f2..02a0b93c36 100644 --- a/web/packages/gallery/services/video.ts +++ b/web/packages/gallery/services/video.ts @@ -10,7 +10,6 @@ import log from "ente-base/log"; import { apiURL } from "ente-base/origins"; import { ensureAuthToken } from "ente-base/token"; import { fileLogID, type EnteFile } from "ente-media/file"; -import { filePublicMagicMetadata } from "ente-media/file-metadata"; import { FileType } from "ente-media/file-type"; import { updateFilePublicMagicMetadata } from "ente-new/photos/services/file"; import { @@ -336,7 +335,7 @@ export const hlsPlaylistDataForFile = async ( ): Promise => { ensurePrecondition(file.metadata.fileType == FileType.video); - if (filePublicMagicMetadata(file)?.sv == 1) { + if (file.pubMagicMetadata?.data.sv == 1) { return "skip"; } @@ -901,7 +900,7 @@ const backfillQueue = async ( // Not in trash. !localTrashFileIDs.has(f.id) && // See: [Note: Marking files which do not need video processing] - filePublicMagicMetadata(f)?.sv != 1, + f.pubMagicMetadata?.data.sv != 1, ), ); diff --git a/web/packages/media/file-metadata.ts b/web/packages/media/file-metadata.ts index e75e6be8e2..73540f7ea8 100644 --- a/web/packages/media/file-metadata.ts +++ b/web/packages/media/file-metadata.ts @@ -1,18 +1,15 @@ -import { decryptMetadataJSON, encryptMetadataJSON } from "ente-base/crypto"; +import { encryptMetadataJSON } from "ente-base/crypto"; import { authenticatedRequestHeaders, ensureOk } from "ente-base/http"; import { apiURL } from "ente-base/origins"; import { type Location } from "ente-base/types"; import { - fileLogID, type EnteFile, type EnteFile2, type FileMagicMetadata, type FilePrivateMagicMetadata, - type FilePublicMagicMetadata, } from "ente-media/file"; import { nullToUndefined } from "ente-utils/transform"; import { z } from "zod/v4"; -import { mergeMetadata1 } from "./file"; import { FileType } from "./file-type"; import type { RemoteMagicMetadata } from "./magic-metadata"; @@ -293,7 +290,7 @@ export type ItemVisibility = * * Also see: [Note: Zod doesn't work with `exactOptionalPropertyTypes` yet]. */ -export interface PublicMagicMetadata { +export interface FilePublicMagicMetadataData { /** * A ISO 8601 date time string without a timezone, indicating the local time * where the photo (or video) was taken. @@ -358,6 +355,20 @@ export interface PublicMagicMetadata { * (The owner of such files will be the owner of the collection) */ uploaderName?: string; + /** + * Edited latitude of the file + * + * If the user edits the location (latitude and longitude) of a file within + * Ente, then the edits will be stored as the {@link lat} and {@link long} + * properties in the file's public magic metadata. + */ + lat?: number; + /** + * Edited longitude of the file. + * + * See {@link long}. + */ + long?: number; /** * An arbitrary integer set to indicate that this file should be skipped for * the purpose of HLS generation. @@ -402,7 +413,8 @@ export interface PublicMagicMetadata { * might be other, newer, clients out there adding fields that the current * client might not we aware of, and we don't want to overwrite them. */ -const PublicMagicMetadata = z.looseObject({ +// TODO(RE): Use me +export const PublicMagicMetadata = z.looseObject({ // [Note: Zod doesn't work with `exactOptionalPropertyTypes` yet] // // Using `optional` is not accurate here. The key is optional, but the @@ -416,46 +428,6 @@ const PublicMagicMetadata = z.looseObject({ editedTime: z.number().optional(), }); -/** - * Return the private magic metadata for an {@link EnteFile}. - * - * We are not expected to be in a scenario where the file gets to the UI without - * having its private magic metadata decrypted, so this function is a sanity - * check and should be a no-op in usually. It'll throw if it finds its - * assumptions broken. Once the types have been refactored this entire - * check/cast shouldn't be needed, and this should become a trivial accessor. - */ -export const filePrivateMagicMetadata = (file: EnteFile) => { - if (!file.magicMetadata) return undefined; - if (typeof file.magicMetadata.data == "string") { - throw new Error( - `Private magic metadata for ${fileLogID(file)} had not been decrypted even when the file reached the UI layer`, - ); - } - return file.magicMetadata.data; -}; - -/** - * Return the public magic metadata for an {@link EnteFile}. - * - * We are not expected to be in a scenario where the file gets to the UI without - * having its public magic metadata decrypted, so this function is a sanity - * check and should be a no-op in usually. It'll throw if it finds its - * assumptions broken. Once the types have been refactored this entire - * check/cast shouldn't be needed, and this should become a trivial accessor. - */ -export const filePublicMagicMetadata = (file: EnteFile) => { - if (!file.pubMagicMetadata) return undefined; - if (typeof file.pubMagicMetadata.data == "string") { - throw new Error( - `Public magic metadata for ${fileLogID(file)} had not been decrypted even when the file reached the UI layer`, - ); - } - // This cast is unavoidable in the current setup. We need to refactor the - // types so that this cast in not needed. - return file.pubMagicMetadata.data as PublicMagicMetadata; -}; - /** * Return the hash of the file by reading it from its metadata. * @@ -483,59 +455,11 @@ export const metadataHash = (metadata: FileMetadata) => { }; /** - * Return the public magic metadata for the given {@link file}. - * - * The file we persist in our local db has the metadata in the encrypted form - * that we get it from remote. We decrypt when we read it, and also hang the - * decrypted version to the in-memory {@link EnteFile} as a cache. - * - * If the file doesn't have any public magic metadata attached to it, return - * `undefined`. + * Return `true` if the {@link ItemVisibility} of the given {@link file} is + * archived. */ -export const decryptPublicMagicMetadata = async ( - file: EnteFile, -): Promise => { - const envelope = file.pubMagicMetadata; - if (!envelope) return undefined; - - // TODO: This function can be optimized to directly return the cached value - // instead of reparsing it using Zod. But that requires us (a) first fix the - // types, and (b) guarantee that we're the only ones putting that parsed - // data there, so that it is in a known good state (currently we exist in - // parallel with other functions that do the similar things). - - const jsonValue = - typeof envelope.data == "string" - ? await decryptMetadataJSON( - { - encryptedData: envelope.data, - decryptionHeader: envelope.header, - }, - file.key, - ) - : envelope.data; - const result = PublicMagicMetadata.parse( - // TODO: Can we avoid this cast? - withoutNullAndUndefinedValues(jsonValue as object), - ); - - // -@ts-expect-error [Note: Zod doesn't work with `exactOptionalPropertyTypes` yet] - // We can't use -@ts-expect-error since this code is also included in the - // packages which don't have strict mode enabled (and thus don't error). - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - envelope.data = result; - - // -@ts-expect-error [Note: Zod doesn't work with `exactOptionalPropertyTypes` yet] - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return result; -}; - -const withoutNullAndUndefinedValues = (o: object) => - Object.fromEntries( - Object.entries(o).filter(([, v]) => v !== null && v !== undefined), - ); +export const isArchivedFile = (file: EnteFile) => + file.magicMetadata?.data.visibility == ItemVisibility.archived; /** * Return the file name of the file (including both the name and the extension). @@ -555,15 +479,13 @@ export const fileFileName = (file: EnteFile | EnteFile2) => * Return the file's creation date as a Date in the hypothetical "timezone of * the photo". * - * For all the details and nuance, see {@link createPhotoDate}. + * This function handles files with edited dates. For all the details and + * nuance, see {@link createPhotoDate}. */ -export const fileCreationPhotoDate = ( - file: EnteFile, - publicMagicMetadata: PublicMagicMetadata | undefined, -) => +export const fileCreationPhotoDate = (file: EnteFile) => createPhotoDate( - publicMagicMetadata?.dateTime ?? - publicMagicMetadata?.editedTime ?? + file.pubMagicMetadata?.data.dateTime ?? + file.pubMagicMetadata?.data.editedTime ?? file.metadata.creationTime, ); @@ -617,7 +539,7 @@ export const updateRemotePrivateMagicMetadata = async ( file: EnteFile, metadataUpdates: Partial, ): Promise => { - const existingMetadata = filePrivateMagicMetadata(file); + const existingMetadata = file.magicMetadata?.data; const updatedMetadata = { ...(existingMetadata ?? {}), ...metadataUpdates }; @@ -647,50 +569,6 @@ export const updateRemotePrivateMagicMetadata = async ( return updatedMagicMetadata; }; -/** - * Update the public magic metadata associated with a file on remote. - * - * See: [Note: Interactive updates to file metadata] - * - * @param file The {@link EnteFile} whose public magic metadata we want to - * update. - * - * @param metadataUpdates A subset of {@link PublicMagicMetadata} containing the - * fields that we want to add or update. - */ -export const updateRemotePublicMagicMetadata = async ( - file: EnteFile, - metadataUpdates: Partial, -) => { - const existingMetadata = await decryptPublicMagicMetadata(file); - - const updatedMetadata = { ...(existingMetadata ?? {}), ...metadataUpdates }; - - const metadataVersion = file.pubMagicMetadata?.version ?? 1; - - const updateRequest = await updateMagicMetadataRequest( - file, - updatedMetadata, - metadataVersion, - ); - - const updatedEnvelope = updateRequest.metadataList[0]!.magicMetadata; - - await putFilesPublicMagicMetadata(updateRequest); - - // Modify the in-memory object to use the updated envelope. This steps are - // quite ad-hoc, as is the concept of updating the object in place. - file.pubMagicMetadata = updatedEnvelope as FilePublicMagicMetadata; - // The correct version will come in the updated EnteFile we get in the - // response of the /diff. Temporarily bump it for the in place edits. - file.pubMagicMetadata.version = file.pubMagicMetadata.version + 1; - // Re-read the data. - await decryptPublicMagicMetadata(file); - // Re-jig the other bits of EnteFile that depend on its public magic - // metadata. - mergeMetadata1(file); -}; - /** * The shape of the JSON body payload expected by the APIs that update the * public and private magic metadata fields associated with a file. @@ -712,7 +590,7 @@ interface UpdateMagicMetadataRequest { */ const updateMagicMetadataRequest = async ( file: EnteFile, - metadata: FilePrivateMagicMetadataData | PublicMagicMetadata, + metadata: FilePrivateMagicMetadataData | FilePublicMagicMetadataData, metadataVersion: number, ): Promise => { // Drop all null or undefined values to obtain the syncable entries. @@ -760,36 +638,6 @@ const putFilesPrivateMagicMetadata = async ( }), ); -/** - * Update the public magic metadata for a list of files. - * - * @param request The list of file ids and the updated encrypted magic metadata - * associated with each of them. - */ -const putFilesPublicMagicMetadata = async ( - request: UpdateMagicMetadataRequest, -) => - ensureOk( - await fetch(await apiURL("/files/public-magic-metadata"), { - method: "PUT", - headers: await authenticatedRequestHeaders(), - body: JSON.stringify(request), - }), - ); - -/** - * Return the {@link ItemVisibility} for the given {@link file}. - */ -export const fileVisibility = (file: EnteFile) => - filePrivateMagicMetadata(file)?.visibility; - -/** - * Return `true` if the {@link ItemVisibility} of the given {@link file} is - * archived. - */ -export const isArchivedFile = (item: EnteFile) => - fileVisibility(item) === ItemVisibility.archived; - /** * Return the GPS coordinates (if any) present in the given {@link EnteFile}. */ @@ -848,13 +696,6 @@ export const fileDurationString = (file: EnteFile): string | undefined => { } }; -/** - * Return the caption, aka "description", (if any) attached to the given - * {@link EnteFile}. - */ -export const fileCaption = (file: EnteFile): string | undefined => - filePublicMagicMetadata(file)?.caption; - /** * Metadata about a file extracted from various sources (like Exif) when * uploading it into Ente. diff --git a/web/packages/media/file.ts b/web/packages/media/file.ts index e07c2645ea..11938fc429 100644 --- a/web/packages/media/file.ts +++ b/web/packages/media/file.ts @@ -12,6 +12,7 @@ import { fileFileName, FileMetadata, type FilePrivateMagicMetadataData, + type FilePublicMagicMetadataData, } from "./file-metadata"; import { FileType } from "./file-type"; import { decryptMagicMetadata, RemoteMagicMetadata } from "./magic-metadata"; @@ -306,7 +307,7 @@ export interface EnteFile * * See: [Note: Metadatum] */ - pubMagicMetadata?: FilePublicMagicMetadata; + pubMagicMetadata?: MagicMetadataCore; /** * `true` if this file is in trash (i.e. it has been deleted by the user, * and will be permanently deleted after 30 days of being moved to trash). @@ -492,56 +493,8 @@ export type FileMagicMetadata = MagicMetadataCore; export type FilePrivateMagicMetadata = MagicMetadataCore; -export interface FilePublicMagicMetadataProps { - /** - * Modified value of the date time associated with an {@link EnteFile}. - * - * Epoch microseconds. - */ - editedTime?: number; - /** See {@link PublicMagicMetadata} in file-metadata.ts */ - dateTime?: string; - /** See {@link PublicMagicMetadata} in file-metadata.ts */ - offsetTime?: string; - /** - * Edited name of the {@link EnteFile}. - * - * If the user edits the name of the file within Ente, then the edits are - * saved in this field. - */ - editedName?: string; - /** - * A arbitrary textual caption / description that the user has attached to - * the {@link EnteFile}. - */ - caption?: string; - uploaderName?: string; - /** - * Width of the image / video, in pixels. - */ - w?: number; - /** - * Height of the image / video, in pixels. - */ - h?: number; - /** - * Edited latitude for the {@link EnteFile}. - * - * If the user edits the location (latitude and longitude) of a file within - * Ente, then the edits will be stored as the {@link lat} and {@link long} - * properties in the file's public magic metadata. - */ - lat?: number; - /** - * Edited longitude for the {@link EnteFile}. - * - * See {@link long}. - */ - long?: number; -} - export type FilePublicMagicMetadata = - MagicMetadataCore; + MagicMetadataCore; export interface TrashItem extends Omit { file: EnteFile; @@ -741,7 +694,7 @@ export const decryptRemoteFile = async ( key, ); // TODO(RE): - const data = genericMM.data as FilePublicMagicMetadataProps; + const data = genericMM.data as FilePublicMagicMetadataData; // TODO(RE): pubMagicMetadata = { ...genericMM, header: "", data }; } diff --git a/web/packages/new/photos/services/dedup.ts b/web/packages/new/photos/services/dedup.ts index d86c9abaf7..0965c184b6 100644 --- a/web/packages/new/photos/services/dedup.ts +++ b/web/packages/new/photos/services/dedup.ts @@ -2,10 +2,7 @@ import { ensureLocalUser } from "ente-accounts/services/user"; import { assertionFailed } from "ente-base/assert"; import { newID } from "ente-base/id"; import type { EnteFile } from "ente-media/file"; -import { - filePublicMagicMetadata, - metadataHash, -} from "ente-media/file-metadata"; +import { metadataHash } from "ente-media/file-metadata"; import { addToCollection, createCollectionNameByID, @@ -312,7 +309,7 @@ const duplicateGroupItemToRetain = (duplicateGroup: DuplicateGroup) => { const itemsWithCaption: DuplicateGroup["items"] = []; const itemsWithOtherEdits: DuplicateGroup["items"] = []; for (const item of duplicateGroup.items) { - const pubMM = filePublicMagicMetadata(item.file); + const pubMM = item.file.pubMagicMetadata?.data; if (!pubMM) continue; if (pubMM.caption) itemsWithCaption.push(item); if (pubMM.editedName ?? pubMM.editedTime) diff --git a/web/packages/new/photos/services/file.ts b/web/packages/new/photos/services/file.ts index 4716054e5c..e187d5c709 100644 --- a/web/packages/new/photos/services/file.ts +++ b/web/packages/new/photos/services/file.ts @@ -3,8 +3,8 @@ import { apiURL } from "ente-base/origins"; import type { EnteFile, EnteFile2 } from "ente-media/file"; import type { FilePrivateMagicMetadataData, + FilePublicMagicMetadataData, ItemVisibility, - PublicMagicMetadata, } from "ente-media/file-metadata"; import { createMagicMetadata, @@ -201,7 +201,7 @@ export const updateFileCaption = (file: EnteFile2, caption: string) => */ export const updateFilePublicMagicMetadata = async ( file: EnteFile2, - updates: PublicMagicMetadata, + updates: FilePublicMagicMetadataData, ) => updateFilesPublicMagicMetadata([file], updates); /** @@ -214,7 +214,7 @@ export const updateFilePublicMagicMetadata = async ( */ const updateFilesPublicMagicMetadata = async ( files: EnteFile2[], - updates: PublicMagicMetadata, + updates: FilePublicMagicMetadataData, ) => putFilesPublicMagicMetadata({ metadataList: await Promise.all( diff --git a/web/packages/new/photos/services/search/worker.ts b/web/packages/new/photos/services/search/worker.ts index 5223e0b547..b088eef50f 100644 --- a/web/packages/new/photos/services/search/worker.ts +++ b/web/packages/new/photos/services/search/worker.ts @@ -10,7 +10,6 @@ import { fileCreationPhotoDate, fileFileName, fileLocation, - filePublicMagicMetadata, } from "ente-media/file-metadata"; import { nullToUndefined } from "ente-utils/transform"; import { z } from "zod/v4"; @@ -385,7 +384,7 @@ const isMatchingFile = (file: EnteFile, suggestion: SearchSuggestion) => { case "date": return isDateComponentsMatch( suggestion.dateComponents, - fileCreationPhotoDate(file, filePublicMagicMetadata(file)), + fileCreationPhotoDate(file), ); case "location": {