From 9c5adfe7cbbd4361d094ddb735a1723514d50b36 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 3 Mar 2025 16:38:39 +0530 Subject: [PATCH] Reset cache --- web/apps/photos/src/pages/gallery.tsx | 8 +- .../gallery/components/viewer/FileViewer.tsx | 1 - .../gallery/components/viewer/data-source.ts | 91 ++++++++++++++++--- .../gallery/components/viewer/photoswipe.ts | 14 ++- .../components/FileViewerComponents-temp.ts | 7 ++ .../components/FileViewerComponents.tsx | 1 + web/packages/new/photos/services/sync.ts | 11 ++- 7 files changed, 107 insertions(+), 26 deletions(-) create mode 100644 web/packages/new/photos/components/FileViewerComponents-temp.ts diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index cdacf81631..79de4d7458 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -18,6 +18,7 @@ import { CollectionSelector, type CollectionSelectorAttributes, } from "@/new/photos/components/CollectionSelector"; +import { resetFileViewerDataSourceOnClose } from "@/new/photos/components/FileViewerComponents-temp"; import { PlanSelector } from "@/new/photos/components/PlanSelector"; import { SearchBar, @@ -564,11 +565,14 @@ const Page: React.FC = () => { (hiddenFiles) => dispatch({ type: "fetchHiddenFiles", hiddenFiles }), ); - if (didUpdateNormalFiles || didUpdateHiddenFiles) - exportService.onLocalFilesUpdated(); await syncTrash(allCollections, (trashedFiles: EnteFile[]) => dispatch({ type: "setTrashedFiles", trashedFiles }), ); + if (didUpdateNormalFiles || didUpdateHiddenFiles) { + exportService.onLocalFilesUpdated(); + // TODO(PS): Use direct one + resetFileViewerDataSourceOnClose(); + } // syncWithRemote is called with the force flag set to true before // doing an upload. So it is possible, say when resuming a pending // upload, that we get two syncWithRemotes happening in parallel. diff --git a/web/packages/gallery/components/viewer/FileViewer.tsx b/web/packages/gallery/components/viewer/FileViewer.tsx index 25e6d254fe..400bef2b36 100644 --- a/web/packages/gallery/components/viewer/FileViewer.tsx +++ b/web/packages/gallery/components/viewer/FileViewer.tsx @@ -213,7 +213,6 @@ const FileViewer: React.FC = ({ const handleClose = useCallback(() => { setNeedsSync((needSync) => { - console.log("needs sync", needSync); if (needSync) onTriggerSyncWithRemote?.(); return false; }); diff --git a/web/packages/gallery/components/viewer/data-source.ts b/web/packages/gallery/components/viewer/data-source.ts index 5c6d4ef7f5..ad82bfbbfa 100644 --- a/web/packages/gallery/components/viewer/data-source.ts +++ b/web/packages/gallery/components/viewer/data-source.ts @@ -38,6 +38,15 @@ interface PhotoSwipeSlideData { * The height (in pixels) of the {@link src} image. */ height?: number | undefined; + /** + * The alt text associated with the file. + * + * This will be set to the file's caption. PhotoSwipe will use it as the alt + * text when constructing img elements (if any) for this item. We will also + * use this for displaying the visible "caption" element atop the file (both + * images and video). + */ + alt?: string; } /** @@ -98,15 +107,6 @@ export type ItemData = PhotoSwipeSlideData & { * It is set while the thumbnail is loaded. */ isContentZoomable?: boolean; - /** - * The alt text associated with the file. - * - * This will be set to the file's caption. PhotoSwipe will use it as the alt - * text when constructing img elements (if any) for this item. We will also - * use this for displaying the visible "caption" element atop the file (both - * images and video). - */ - alt?: string; /** * This will be `true` if the fetch for the file's data has failed. * @@ -127,6 +127,19 @@ export type ItemData = PhotoSwipeSlideData & { * This will be cleared on logout. */ class FileViewerDataSourceState { + /** + * Non-zero if a file viewer is currently open. + * + * This is a counter, but the file viewer data source has other many + * assumptions about only a single instance of PhotoSwipe being active at a + * time, so this could've been a boolean as well. + */ + viewerCount = 0; + /** + * True if our state needs to be cleared the next time the file viewer is + * closed. + */ + needsReset = false; /** * The best data we have for a particular file (ID). */ @@ -154,12 +167,59 @@ class FileViewerDataSourceState { */ let _state = new FileViewerDataSourceState(); +const resetState = () => { + _state = new FileViewerDataSourceState(); +}; + /** * Clear any internal state maintained by the file viewer data source. */ // TODO(PS): Call me during logout sequence once this is integrated. -export const logoutFileViewerDataSource = () => { - _state = new FileViewerDataSourceState(); +export const logoutFileViewerDataSource = resetState; + +/** + * Clear any internal state if possible. This is invoked when files have been + * updated on remote, and those changes synced locally. + * + * Because we also retain callbacks, clearing existing item data when the file + * viewer is open can lead to problematic edge cases. Thus, this function + * behaves in two different ways: + * + * - If the file viewer is already open, then we enqueue a reset for when it is + * closed the next time. + * + * - Otherwise we immediately reset our state. + * + * See: [Note: Changes to underlying files when file viewer is open] + */ +export const resetFileViewerDataSourceOnClose = () => { + if (_state.viewerCount) { + _state.needsReset = true; + } else { + resetState(); + } +}; + +/** + * Called by the file viewer whenever it is opened. + */ +export const fileViewerWillOpen = () => { + _state.viewerCount++; +}; + +/** + * Called by the file viewer whenever it has been closed. + */ +export const fileViewerDidClose = () => { + _state.viewerCount--; + if (_state.needsReset && _state.viewerCount == 0) { + // Reset everything. + resetState(); + } else { + // Selectively clear. + forgetFailedItems(); + forgetExif(); + } }; /** @@ -246,7 +306,7 @@ export const forgetFailedItemDataForFileID = (fileID: number) => { * This is called when the user closes the file viewer so that we attempt a full * retry when they reopen the viewer the next time. */ -export const forgetFailedItems = () => +const forgetFailedItems = () => [..._state.itemDataByFileID.keys()].forEach(forgetFailedItemDataForFileID); const enqueueUpdates = async (file: EnteFile) => { @@ -258,7 +318,12 @@ const enqueueUpdates = async (file: EnteFile) => { // the visible caption). const alt = fileCaption(file); - _state.itemDataByFileID.set(file.id, { ...itemData, fileType, fileID, alt }); + _state.itemDataByFileID.set(file.id, { + ...itemData, + fileType, + fileID, + alt, + }); _state.needsRefreshByFileID.get(file.id)?.(); }; diff --git a/web/packages/gallery/components/viewer/photoswipe.ts b/web/packages/gallery/components/viewer/photoswipe.ts index 73c60bf916..384ad01d0a 100644 --- a/web/packages/gallery/components/viewer/photoswipe.ts +++ b/web/packages/gallery/components/viewer/photoswipe.ts @@ -7,10 +7,10 @@ import type { EnteFile } from "@/media/file"; import { FileType } from "@/media/file-type"; import { t } from "i18next"; import { - forgetExif, + fileViewerDidClose, + fileViewerWillOpen, forgetExifForItemData, forgetFailedItemDataForFileID, - forgetFailedItems, itemDataForFile, updateFileInfoExifIfNeeded, } from "./data-source"; @@ -58,10 +58,6 @@ export interface FileViewerFileAnnotation { * and the edit action should therefore be shown for this file. */ isEditableImage: boolean; - /** - * The caption ("description") of the file (if any). - */ - caption: string | undefined; } export interface FileViewerPhotoSwipeDelegate { @@ -464,8 +460,7 @@ export class FileViewerPhotoSwipe { // completed. pswp.on("destroy", () => { this.clearAutoHideIntervalIfNeeded(); - forgetFailedItems(); - forgetExif(); + fileViewerDidClose(); // Let our parent know that we have been closed. onClose(); }); @@ -654,6 +649,9 @@ export class FileViewerPhotoSwipe { return element; }); + // Let our data source know. + fileViewerWillOpen(); + // Initializing PhotoSwipe adds it to the DOM as a dialog-like div with // the class "pswp". pswp.init(); diff --git a/web/packages/new/photos/components/FileViewerComponents-temp.ts b/web/packages/new/photos/components/FileViewerComponents-temp.ts new file mode 100644 index 0000000000..834bc63d6c --- /dev/null +++ b/web/packages/new/photos/components/FileViewerComponents-temp.ts @@ -0,0 +1,7 @@ +// TODO(PS): Temporary trampoline +export const resetFileViewerDataSourceOnClose = async () => { + if (!process.env.NEXT_PUBLIC_ENTE_WIP_PS5) return; + ( + await import("@/gallery/components/viewer/data-source") + ).resetFileViewerDataSourceOnClose(); +}; diff --git a/web/packages/new/photos/components/FileViewerComponents.tsx b/web/packages/new/photos/components/FileViewerComponents.tsx index 120b3a4c2c..c65c74e52f 100644 --- a/web/packages/new/photos/components/FileViewerComponents.tsx +++ b/web/packages/new/photos/components/FileViewerComponents.tsx @@ -16,6 +16,7 @@ import dynamic from "next/dynamic"; const FV5 = dynamic(() => import("@/gallery/components/viewer/FileViewer"), { ssr: false, }); + const FVD = () => <>; export const FileViewer: React.FC = (props) => { diff --git a/web/packages/new/photos/services/sync.ts b/web/packages/new/photos/services/sync.ts index 906eaad9b5..6bd072e71e 100644 --- a/web/packages/new/photos/services/sync.ts +++ b/web/packages/new/photos/services/sync.ts @@ -9,6 +9,7 @@ import { isMLSupported, mlStatusSync, mlSync } from "@/new/photos/services/ml"; import { searchDataSync } from "@/new/photos/services/search"; import { syncSettings } from "@/new/photos/services/settings"; import { splitByPredicate } from "@/utils/array"; +import { resetFileViewerDataSourceOnClose } from "../components/FileViewerComponents-temp"; /** * Part 1 of {@link sync}. See TODO below for why this is split. @@ -62,17 +63,23 @@ export const syncFilesAndCollections = async () => { allCollections, isHiddenCollection, ); - await syncFiles( + const didUpdateNormalFiles = await syncFiles( "normal", normalCollections, () => {}, () => {}, ); - await syncFiles( + const didUpdateHiddenFiles = await syncFiles( "hidden", hiddenCollections, () => {}, () => {}, ); await syncTrash(allCollections, () => {}); + if (didUpdateNormalFiles || didUpdateHiddenFiles) { + // TODO: + // exportService.onLocalFilesUpdated(); + // TODO(PS): Use direct one + await resetFileViewerDataSourceOnClose(); + } };