From ef4b9ebc42e1898e6aaf6ab5cc3957e6461da9ff Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 26 Jun 2025 07:45:08 +0530 Subject: [PATCH 1/4] Rename --- web/apps/photos/src/components/Sidebar.tsx | 4 ++-- web/apps/photos/src/pages/gallery.tsx | 4 ++-- web/apps/photos/tests/upload.test.ts | 4 ++-- web/packages/new/photos/services/ml/index.ts | 2 +- .../new/photos/services/user-details.ts | 21 ++++++++++--------- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index 9e799df3ca..aa586955ab 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -102,8 +102,8 @@ import { isSubscriptionPastDue, isSubscriptionStripe, leaveFamily, + pullUserDetails, redirectToCustomerPortal, - syncUserDetails, userDetailsAddOnBonuses, type UserDetails, } from "ente-new/photos/services/user-details"; @@ -230,7 +230,7 @@ const UserDetailsSection: React.FC = ({ } = useModalVisibility(); useEffect(() => { - if (sidebarOpen) void syncUserDetails(); + if (sidebarOpen) void pullUserDetails(); }, [sidebarOpen]); const isNonAdminFamilyMember = useMemo( diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index b41eb83304..9fa258405f 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -91,7 +91,7 @@ import { import type { SearchOption } from "ente-new/photos/services/search/types"; import { initSettings } from "ente-new/photos/services/settings"; import { - initUserDetailsOrTriggerSync, + initUserDetailsOrTriggerPull, redirectToCustomerPortal, userDetailsSnapshot, verifyStripeSubscription, @@ -296,7 +296,7 @@ const Page: React.FC = () => { return; } initSettings(); - await initUserDetailsOrTriggerSync(); + await initUserDetailsOrTriggerPull(); setupSelectAllKeyBoardShortcutHandler(); dispatch({ type: "showAll" }); setIsFirstLoad(isFirstLogin()); diff --git a/web/apps/photos/tests/upload.test.ts b/web/apps/photos/tests/upload.test.ts index 3a8f30889c..00da88aea2 100644 --- a/web/apps/photos/tests/upload.test.ts +++ b/web/apps/photos/tests/upload.test.ts @@ -18,7 +18,7 @@ import { savedCollectionFiles, savedCollections, } from "ente-new/photos/services/photos-fdb"; -import { getUserDetailsV2 } from "ente-new/photos/services/user-details"; +import { userDetailsSnapshot } from "ente-new/photos/services/user-details"; const DATE_TIME_PARSING_TEST_FILE_NAMES = [ { @@ -170,7 +170,7 @@ export async function testUpload() { } async function totalFileCountCheck(expectedState) { - const userDetails = await getUserDetailsV2(); + const userDetails = userDetailsSnapshot(); if (expectedState.total_file_count === userDetails.fileCount) { console.log("file count check passed ✅"); } else { diff --git a/web/packages/new/photos/services/ml/index.ts b/web/packages/new/photos/services/ml/index.ts index 3ec6c44e26..aa73cfa22e 100644 --- a/web/packages/new/photos/services/ml/index.ts +++ b/web/packages/new/photos/services/ml/index.ts @@ -56,7 +56,7 @@ class MLState { * * - On app start, this is read from local storage during {@link initML}. * - * - It gets updated when we sync with remote (so if the user + * - It gets updated when we pull from remote (so if the user * enables/disables ML on a different device, this local value will also * become true/false). * diff --git a/web/packages/new/photos/services/user-details.ts b/web/packages/new/photos/services/user-details.ts index e5839edd3d..a8b2392438 100644 --- a/web/packages/new/photos/services/user-details.ts +++ b/web/packages/new/photos/services/user-details.ts @@ -183,12 +183,12 @@ export const logoutUserDetails = () => { * * This assumes that the user is already logged in. */ -export const initUserDetailsOrTriggerSync = async () => { +export const initUserDetailsOrTriggerPull = async () => { const saved = await getKV("userDetails"); if (saved) { setUserDetailsSnapshot(UserDetails.parse(saved)); } else { - void syncUserDetails(); + void pullUserDetails(); } }; @@ -231,8 +231,8 @@ const setUserDetailsSnapshot = (snapshot: UserDetails) => { * Fetch the user's details from remote and save them in local storage for * subsequent lookup, and also update our in-memory snapshots. */ -export const syncUserDetails = async () => { - const userDetails = await getUserDetailsV2(); +export const pullUserDetails = async () => { + const userDetails = await getUserDetails(); await setKV("userDetails", userDetails); setUserDetailsSnapshot(userDetails); @@ -248,7 +248,7 @@ export const syncUserDetails = async () => { /** * Fetch the user details for the currently logged in user from remote. */ -export const getUserDetailsV2 = async () => { +const getUserDetails = async () => { const res = await fetch(await apiURL("/users/details/v2"), { headers: await authenticatedRequestHeaders(), }); @@ -326,7 +326,7 @@ export const verifyStripeSubscription = async ( }), }), ); - await syncUserDetails(); + await pullUserDetails(); return userDetailsSnapshot()!.subscription; }; @@ -342,7 +342,7 @@ export const activateStripeSubscription = async () => { headers: await authenticatedRequestHeaders(), }), ); - return syncUserDetails(); + return pullUserDetails(); }; /** @@ -356,7 +356,7 @@ export const cancelStripeSubscription = async () => { headers: await authenticatedRequestHeaders(), }), ); - return syncUserDetails(); + return pullUserDetails(); }; const paymentsAppOrigin = "https://payments.ente.io"; @@ -539,7 +539,8 @@ const getFamiliesTokenAndURL = async () => { /** * Update remote to indicate that the user wants to leave the family plan that - * they are part of, then our local sync user details with remote. + * they are part of, then update our local user details by pulling the latest + * ones from remote. */ export const leaveFamily = async () => { ensureOk( @@ -548,7 +549,7 @@ export const leaveFamily = async () => { headers: await authenticatedRequestHeaders(), }), ); - return syncUserDetails(); + return pullUserDetails(); }; /** From 2c2b8f77cb00a6468b449ee0c92c0d5d54a7a76c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 26 Jun 2025 07:50:17 +0530 Subject: [PATCH 2/4] sync => pull or other apropos --- web/apps/photos/src/pages/gallery.tsx | 2 +- web/apps/photos/src/pages/shared-albums.tsx | 2 +- web/packages/gallery/services/file-data.ts | 4 ++-- .../new/photos/components/gallery/reducer.ts | 14 +++++++------- web/packages/new/photos/services/pull.ts | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 9fa258405f..77174822c9 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -821,7 +821,7 @@ const Page: React.FC = () => { // 2. Construct a fake a metadata object with the updates // reflected in it. // - // 3. The caller (eventually) triggers a remote sync in the + // 3. The caller (eventually) triggers a remote pull in the // background, but meanwhile uses this updated metadata. // // TODO(RE): Replace with file fetch? diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index b6b5ce3c8c..8daa9c897e 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -362,7 +362,7 @@ export default function PublicCollectionGallery() { setPublicCollection(null); setPublicFiles(null); } else { - log.error("failed to sync public album with remote", e); + log.error("Public album remote pull failed", e); } } finally { hideLoadingBar(); diff --git a/web/packages/gallery/services/file-data.ts b/web/packages/gallery/services/file-data.ts index 6e456fa122..f9434b5389 100644 --- a/web/packages/gallery/services/file-data.ts +++ b/web/packages/gallery/services/file-data.ts @@ -196,8 +196,8 @@ export interface UpdatedFileDataFileIDsPage { fileIDs: Set; /** * The latest updatedAt (epoch microseconds) time obtained from remote in - * this batch of sync (from amongst all of the files in the batch, not just - * those that were filtered to be part of {@link fileIDs}). + * this batch being fetched (from amongst all of the files in the batch, not + * just those that were filtered to be part of {@link fileIDs}). */ lastUpdatedAt: number; } diff --git a/web/packages/new/photos/components/gallery/reducer.ts b/web/packages/new/photos/components/gallery/reducer.ts index c06edaa990..6febb458ea 100644 --- a/web/packages/new/photos/components/gallery/reducer.ts +++ b/web/packages/new/photos/components/gallery/reducer.ts @@ -163,7 +163,7 @@ export interface GalleryState { * Unsynced modifications are those whose effects have already been made on * remote (so thus they have been "saved", so to say), but we still haven't * yet refreshed our local state to incorporate them. The refresh will - * happen on the next "file sync", until then they remain as in-memory state + * happen on the next files pull, until then they remain as in-memory state * in the reducer. */ collectionFiles: EnteFile[]; @@ -318,9 +318,9 @@ export interface GalleryState { * the user's favorites, false otherwise) which should be used for that file * instead of what we get from our local DB. * - * The next time a sync with remote completes, we clear this map since - * thereafter just deriving {@link favoriteFileIDs} from our local files - * would reflect the correct state on remote too. + * The next time a remote pull completes, we clear this map since thereafter + * just deriving {@link favoriteFileIDs} from our local files would reflect + * the correct state on remote too. */ unsyncedFavoriteUpdates: Map; /** @@ -338,9 +338,9 @@ export interface GalleryState { * Each entry from a file ID to the magic metadata that should be used for * that file instead of what we get from our local DB. * - * The next time a sync with remote completes, we clear this map since - * thereafter the synced files themselves will reflect the latest private - * magic metadata. + * The next time a remote pull completes, we clear this map since thereafter + * the synced files themselves will reflect the latest private magic + * metadata. */ unsyncedPrivateMagicMetadataUpdates: Map< number, diff --git a/web/packages/new/photos/services/pull.ts b/web/packages/new/photos/services/pull.ts index 7061791bc5..0e8bb9491c 100644 --- a/web/packages/new/photos/services/pull.ts +++ b/web/packages/new/photos/services/pull.ts @@ -79,7 +79,7 @@ interface PullFilesOpts { * This is a subset of a full remote pull, independently exposed for use at * times when we only want to pull the file related information (e.g. we just * made some API request that modified collections or files, and so now want to - * sync our local changes to match remote). + * update our local changes to match remote). * * See also: [Note: Remote pull] * From 63c1e630350c76cd8fdb74ff137e92bf7711a6fb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 26 Jun 2025 08:09:52 +0530 Subject: [PATCH 3/4] Prune --- web/apps/photos/src/components/FileList.tsx | 9 +++++++-- web/apps/photos/src/components/FileListWithViewer.tsx | 3 +++ web/apps/photos/src/pages/gallery.tsx | 9 +++------ web/apps/photos/src/types/gallery/index.ts | 1 - 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index 705677778c..1ca3140377 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -123,6 +123,10 @@ export interface FileListProps { */ modePlus?: GalleryBarMode | "search"; showAppDownloadBanner?: boolean; + /** + * If `true`, then the current listing is showing magic search results. + */ + isMagicSearchResult?: boolean; selectable?: boolean; setSelected: ( selected: SelectedState | ((selected: SelectedState) => SelectedState), @@ -167,6 +171,7 @@ export const FileList: React.FC = ({ modePlus, annotatedFiles, showAppDownloadBanner, + isMagicSearchResult, selectable, selected, setSelected, @@ -235,7 +240,7 @@ export const FileList: React.FC = ({ ), ); } - if (galleryContext.isClipSearchResult) { + if (isMagicSearchResult) { noGrouping(timeStampList); } else { groupByTime(timeStampList); @@ -277,7 +282,7 @@ export const FileList: React.FC = ({ annotatedFiles, galleryContext.photoListHeader, publicCollectionGalleryContext.photoListHeader, - galleryContext.isClipSearchResult, + isMagicSearchResult, ]); useEffect(() => { diff --git a/web/apps/photos/src/components/FileListWithViewer.tsx b/web/apps/photos/src/components/FileListWithViewer.tsx index 6c512e75f2..d2c22ae352 100644 --- a/web/apps/photos/src/components/FileListWithViewer.tsx +++ b/web/apps/photos/src/components/FileListWithViewer.tsx @@ -53,6 +53,7 @@ export type FileListWithViewerProps = { | "mode" | "modePlus" | "showAppDownloadBanner" + | "isMagicSearchResult" | "selectable" | "selected" | "setSelected" @@ -89,6 +90,7 @@ export const FileListWithViewer: React.FC = ({ files, enableDownload, showAppDownloadBanner, + isMagicSearchResult, selectable, selected, setSelected, @@ -175,6 +177,7 @@ export const FileListWithViewer: React.FC = ({ mode, modePlus, showAppDownloadBanner, + isMagicSearchResult, selectable, selected, setSelected, diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 77174822c9..8420be8e92 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -137,7 +137,6 @@ const defaultGalleryContext: GalleryContextType = { userIDToEmailMap: null, emailList: null, openHiddenSection: () => null, - isClipSearchResult: null, selectedFile: null, setSelectedFiles: () => null, }; @@ -202,9 +201,6 @@ const Page: React.FC = () => { const peopleState = usePeopleStateSnapshot(); - const [isClipSearchResult, setIsClipSearchResult] = - useState(false); - // The (non-sticky) header shown at the top of the gallery items. const [photoListHeader, setPhotoListHeader] = useState(null); @@ -758,7 +754,6 @@ const Page: React.FC = () => { } else { dispatch({ type: "exitSearch" }); } - setIsClipSearchResult(type == "clip"); }; const openUploader = (intent?: UploadTypeSelectorIntent) => { @@ -895,7 +890,6 @@ const Page: React.FC = () => { userIDToEmailMap: state.emailByUserID, emailList: state.shareSuggestionEmails, openHiddenSection, - isClipSearchResult, selectedFile: selected, setSelectedFiles: setSelected, }} @@ -1094,6 +1088,9 @@ const Page: React.FC = () => { showAppDownloadBanner={ state.collectionFiles.length < 30 && !isInSearchMode } + isMagicSearchResult={ + state.searchSuggestion?.type == "clip" + } selectable={true} selected={selected} setSelected={setSelected} diff --git a/web/apps/photos/src/types/gallery/index.ts b/web/apps/photos/src/types/gallery/index.ts index a717d31295..012df9a13c 100644 --- a/web/apps/photos/src/types/gallery/index.ts +++ b/web/apps/photos/src/types/gallery/index.ts @@ -46,7 +46,6 @@ export interface GalleryContextType { userIDToEmailMap: Map; emailList: string[]; openHiddenSection: (callback?: () => void) => void; - isClipSearchResult: boolean; setSelectedFiles: (value) => void; selectedFile: SelectedState; } From b981152deed031bb0f667bf7c851754c0768eb2d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 26 Jun 2025 08:17:20 +0530 Subject: [PATCH 4/4] Keep some leeway for fs.stat --- desktop/src/main/services/ml-worker.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/desktop/src/main/services/ml-worker.ts b/desktop/src/main/services/ml-worker.ts index 35e342b029..f700d84477 100644 --- a/desktop/src/main/services/ml-worker.ts +++ b/desktop/src/main/services/ml-worker.ts @@ -184,14 +184,13 @@ const downloadModel = async (saveLocation: string, name: string) => { /** * Create an ONNX {@link InferenceSession} with some defaults. */ -const createInferenceSession = async (modelPath: string) => { - return await ort.InferenceSession.create(modelPath, { +const createInferenceSession = async (modelPath: string) => + ort.InferenceSession.create(modelPath, { // Restrict the number of threads to 1. intraOpNumThreads: 1, // Be more conservative with RAM usage. enableCpuMemArena: false, }); -}; const cachedCLIPImageSession = makeCachedInferenceSession( "mobileclip_s2_image_opset18_rgba_opt.onnx", @@ -233,9 +232,11 @@ const getTokenizer = () => (_tokenizer ??= new Tokenizer()); export const computeCLIPTextEmbeddingIfAvailable = async (text: string) => { const sessionOrSkip = await Promise.race([ cachedCLIPTextSession(), - // Wait for a tick to get the session promise to resolved the first time - // this code runs on each app start (and the model has been downloaded). - wait(0).then(() => 1), + // Wait a bit to get the session promise to resolved the first time this + // code runs on each app start (in these cases the model will already be + // downloaded, so session creation should take only a 1 or 2 ticks: file + // system stat, and ort.InferenceSession.create). + wait(50).then(() => 1), ]); // Don't wait for the download to complete.