diff --git a/web/apps/photos/src/services/logout.ts b/web/apps/photos/src/services/logout.ts index 2013553fe7..90cd73f110 100644 --- a/web/apps/photos/src/services/logout.ts +++ b/web/apps/photos/src/services/logout.ts @@ -1,6 +1,6 @@ import DownloadManager from "@/new/photos/services/download"; import { clearFeatureFlagSessionState } from "@/new/photos/services/feature-flags"; -import { terminateFaceWorker } from "@/new/photos/services/ml"; +import { terminateMLWorker } from "@/new/photos/services/ml"; import { clearFaceData } from "@/new/photos/services/ml/db"; import mlWorkManager from "@/new/photos/services/ml/mlWorkManager"; import log from "@/next/log"; @@ -19,11 +19,23 @@ export const photosLogout = async () => { const ignoreError = (label: string, e: unknown) => log.error(`Ignoring error during logout (${label})`, e); + // - Workers + // Terminate any workers before clearing persistent state. // See: [Note: Caching IDB instances in separate execution contexts]. + try { + terminateMLWorker(); + } catch (e) { + ignoreError("face", e); + } + + // - Remote logout and clear state + await accountLogout(); + // - Photos specific logout + try { clearFeatureFlagSessionState(); } catch (e) { @@ -42,11 +54,7 @@ export const photosLogout = async () => { ignoreError("CLIP", e); } - try { - terminateFaceWorker(); - } catch (e) { - ignoreError("face", e); - } + // - Desktop const electron = globalThis.electron; if (electron) { @@ -69,7 +77,7 @@ export const photosLogout = async () => { } try { - await electron?.logout(); + await electron.logout(); } catch (e) { ignoreError("electron", e); } diff --git a/web/packages/new/photos/services/ml/index.ts b/web/packages/new/photos/services/ml/index.ts index 743c1dbd61..ffcedc7317 100644 --- a/web/packages/new/photos/services/ml/index.ts +++ b/web/packages/new/photos/services/ml/index.ts @@ -1,31 +1,31 @@ /** - * @file Main thread interface to {@link FaceWorker}. + * @file Main thread interface to {@link MLWorker}. */ import { ComlinkWorker } from "@/next/worker/comlink-worker"; -import { FaceWorker } from "./worker"; +import { MLWorker } from "./worker"; /** Cached instance of the {@link ComlinkWorker} that wraps our web worker. */ -let _comlinkWorker: ComlinkWorker | undefined; +let _comlinkWorker: ComlinkWorker | undefined; -/** Lazily created, cached, instance of {@link FaceWorker}. */ -export const faceWorker = async () => +/** Lazily created, cached, instance of {@link MLWorker}. */ +export const worker = async () => (_comlinkWorker ??= createComlinkWorker()).remote; const createComlinkWorker = () => - new ComlinkWorker( - "face", + new ComlinkWorker( + "ml", new Worker(new URL("worker.ts", import.meta.url)), ); /** - * Terminate {@link faceWorker} (if any). + * Terminate {@link worker} (if any). * - * This is useful during logout to immediately stop any background face related - * operations that are in-flight for the current user. After the user logs in - * again, a new {@link faceWorker} will be created on demand. + * This is useful during logout to immediately stop any background ML operations + * that are in-flight for the current user. After the user logs in again, a new + * {@link worker} will be created on demand for subsequent usage. */ -export const terminateFaceWorker = () => { +export const terminateMLWorker = () => { if (_comlinkWorker) { _comlinkWorker.terminate(); _comlinkWorker = undefined; diff --git a/web/packages/new/photos/services/ml/worker.ts b/web/packages/new/photos/services/ml/worker.ts index 40c4cafec7..723ff14618 100644 --- a/web/packages/new/photos/services/ml/worker.ts +++ b/web/packages/new/photos/services/ml/worker.ts @@ -1,14 +1,20 @@ +import { markIndexingFailed, saveFaceIndex } from "@/new/photos/services/ml/db"; +import type { FaceIndex } from "@/new/photos/services/ml/types"; +import type { EnteFile } from "@/new/photos/types/file"; +import log from "@/next/log"; import { expose } from "comlink"; -import { pullFaceEmbeddings } from "./embedding"; +import { fileLogID } from "../../utils/file"; +import { pullFaceEmbeddings, putFaceIndex } from "./embedding"; +import { indexFaces } from "./f-index"; /** - * Run operations related to face indexing and search in a Web Worker. + * Run operations related to machine learning (e.g. indexing) in a Web Worker. * * This is a normal class that is however exposed (via comlink) as a proxy * running inside a Web Worker. This way, we do not bother the main thread with * tasks that might degrade interactivity. */ -export class FaceWorker { +export class MLWorker { private isSyncing = false; /** @@ -22,4 +28,55 @@ export class FaceWorker { } } -expose(FaceWorker); +expose(MLWorker); + +/** + * Index faces in a file, save the persist the results locally, and put them + * on remote. + * + * @param enteFile The {@link EnteFile} to index. + * + * @param file If the file is one which is being uploaded from the current + * client, then we will also have access to the file's content. In such + * cases, pass a web {@link File} object to use that its data directly for + * face indexing. If this is not provided, then the file's contents will be + * downloaded and decrypted from remote. + * + * @param userAgent The UA of the client that is doing the indexing (us). + */ +export const index = async ( + enteFile: EnteFile, + file: File | undefined, + userAgent: string, +) => { + const f = fileLogID(enteFile); + const startTime = Date.now(); + + let faceIndex: FaceIndex; + try { + faceIndex = await indexFaces(enteFile, file, userAgent); + } catch (e) { + // Mark indexing as having failed only if the indexing itself + // failed, not if there were subsequent failures (like when trying + // to put the result to remote or save it to the local face DB). + log.error(`Failed to index faces in ${f}`, e); + await markIndexingFailed(enteFile.id); + throw e; + } + + try { + await putFaceIndex(enteFile, faceIndex); + await saveFaceIndex(faceIndex); + } catch (e) { + log.error(`Failed to put/save face index for ${f}`, e); + throw e; + } + + log.debug(() => { + const nf = faceIndex.faceEmbedding.faces.length; + const ms = Date.now() - startTime; + return `Indexed ${nf} faces in ${f} (${ms} ms)`; + }); + + return faceIndex; +};