diff --git a/web/apps/photos/src/services/face/indexer.ts b/web/apps/photos/src/services/face/indexer.ts index ba06e6f4a4..e629d4533b 100644 --- a/web/apps/photos/src/services/face/indexer.ts +++ b/web/apps/photos/src/services/face/indexer.ts @@ -4,6 +4,88 @@ import mlWorkManager from "services/machineLearning/mlWorkManager"; import type { EnteFile } from "types/file"; import { FaceIndexerWorker } from "./indexer.worker"; +import log from "@/next/log"; +import { wait } from "@/utils/promise"; +import type { EnteFile } from "types/file"; +import { markIndexingFailed } from "./db"; +import { indexFaces } from "./f-index"; + +/** + * Face indexing orchestrator. + * + * This is class that drives the face indexing process across all files that + * need to still be indexed. It runs in a Web Worker so as to not get in the way + * of the main thread. + * + * It operates in two modes - live indexing and backfill. + * + * In live indexing, any files that are being uploaded from the current client + * are provided to the indexer, which puts them in a queue and indexes them one + * by one. This is more efficient since we already have the file's content at + * hand and do not have to download and decrypt it. + * + * In backfill, the indexer figures out if any of the user's files (irrespective + * of where they were uploaded from) still need to be indexed, and if so, + * downloads, decrypts and indexes them. + * + * Live indexing has higher priority, backfill runs otherwise. + * + * If nothing needs to be indexed, the indexer goes to sleep for a while. + */ +export class FaceIndexerWorker { + /** Live indexing queue. */ + private liveItems: { file: File; enteFile: EnteFile }[]; + /** Timeout for when the next time we will wake up. */ + private wakeTimeout: ReturnType | undefined; + + /** + * Add {@link file} associated with {@link enteFile} to the live indexing + * queue. + */ + enqueueFile(file: File, enteFile: EnteFile) { + this.liveItems.push({ file, enteFile }); + this.wakeUpIfNeeded(); + } + + private wakeUpIfNeeded() { + // Already awake. + if (!this.wakeTimeout) return; + // Cancel the alarm, wake up now. + clearTimeout(this.wakeTimeout); + this.wakeTimeout = undefined; + // Get to work. + this.tick(); + } + + private async tick() { + console.log("tick"); + + const item = this.liveItems.pop(); + if (!item) { + // TODO-ML: backfill instead if needed here. + this.wakeTimeout = setTimeout(() => { + this.wakeTimeout = undefined; + this.wakeUpIfNeeded(); + }, 30 * 1000); + return; + } + + const fileID = item.enteFile.id; + try { + const faceIndex = await indexFaces(item.enteFile, item.file); + log.info(`faces in file ${fileID}`, faceIndex); + } catch (e) { + log.error(`Failed to index faces in file ${fileID}`, e); + markIndexingFailed(item.enteFile.id); + } + + // Let the runloop drain. + await wait(0); + // Run again. + this.tick(); + } +} + /** * A promise for the lazily created singleton {@link FaceIndexerWorker} remote * exposed by this module. diff --git a/web/apps/photos/src/services/face/indexer.worker.ts b/web/apps/photos/src/services/face/indexer.worker.ts index 3f7ee822d0..61c62b4029 100644 --- a/web/apps/photos/src/services/face/indexer.worker.ts +++ b/web/apps/photos/src/services/face/indexer.worker.ts @@ -1,81 +1,26 @@ import log from "@/next/log"; -import { wait } from "@/utils/promise"; import type { EnteFile } from "types/file"; import { markIndexingFailed } from "./db"; import { indexFaces } from "./f-index"; /** - * Face indexing orchestrator. + * Index faces in a file, save the persist the results locally, and put them on + * remote. * - * This is class that drives the face indexing process across all files that - * need to still be indexed. It runs in a Web Worker so as to not get in the way - * of the main thread. - * - * It operates in two modes - live indexing and backfill. - * - * In live indexing, any files that are being uploaded from the current client - * are provided to the indexer, which puts them in a queue and indexes them one - * by one. This is more efficient since we already have the file's content at - * hand and do not have to download and decrypt it. - * - * In backfill, the indexer figures out if any of the user's files (irrespective - * of where they were uploaded from) still need to be indexed, and if so, - * downloads, decrypts and indexes them. - * - * Live indexing has higher priority, backfill runs otherwise. - * - * If nothing needs to be indexed, the indexer goes to sleep for a while. + * This class is instantiated in a Web Worker so as to not get in the way of the + * main thread. It could've been a bunch of free standing functions too, it is + * just a class for convenience of compatibility with how the rest of our + * comlink workers are structured. */ export class FaceIndexerWorker { - /** Live indexing queue. */ - private liveItems: { file: File; enteFile: EnteFile }[]; - /** Timeout for when the next time we will wake up. */ - private wakeTimeout: ReturnType | undefined; - - /** - * Add {@link file} associated with {@link enteFile} to the live indexing - * queue. - */ - enqueueFile(file: File, enteFile: EnteFile) { - this.liveItems.push({ file, enteFile }); - this.wakeUpIfNeeded(); - } - - private wakeUpIfNeeded() { - // Already awake. - if (!this.wakeTimeout) return; - // Cancel the alarm, wake up now. - clearTimeout(this.wakeTimeout); - this.wakeTimeout = undefined; - // Get to work. - this.tick(); - } - - private async tick() { - console.log("tick"); - - const item = this.liveItems.pop(); - if (!item) { - // TODO-ML: backfill instead if needed here. - this.wakeTimeout = setTimeout(() => { - this.wakeTimeout = undefined; - this.wakeUpIfNeeded(); - }, 30 * 1000); - return; - } - - const fileID = item.enteFile.id; + async index(enteFile: EnteFile, file: File | undefined) { + const fileID = enteFile.id; try { - const faceIndex = await indexFaces(item.enteFile, item.file); + const faceIndex = await indexFaces(enteFile, file); log.info(`faces in file ${fileID}`, faceIndex); } catch (e) { log.error(`Failed to index faces in file ${fileID}`, e); - markIndexingFailed(item.enteFile.id); + markIndexingFailed(enteFile.id); } - - // Let the runloop drain. - await wait(0); - // Run again. - this.tick(); } }