[web] Rework the face DB schema
These changes were in main only overnight and were not released anywhere, so I will take the liberty of modifying the schema without bumping the version.
This commit is contained in:
@@ -30,7 +30,7 @@ interface FaceDBSchema extends DBSchema {
|
||||
"file-status": {
|
||||
key: number;
|
||||
value: FileStatus;
|
||||
indexes: { isIndexable: number };
|
||||
indexes: { status: FileStatus["status"] };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,27 +38,32 @@ interface FileStatus {
|
||||
/** The ID of the {@link EnteFile} whose indexing status we represent. */
|
||||
fileID: number;
|
||||
/**
|
||||
* `1` if this file needs to be indexed, `0` otherwise.
|
||||
* The status of the file.
|
||||
*
|
||||
* > Somewhat confusingly, we also have a (IndexedDB) "index" on this field.
|
||||
* That (IDB) index allows us to efficiently select {@link fileIDs} that
|
||||
* still need indexing (i.e. entries where {@link isIndexed} is `1`).
|
||||
* - "indexable" - This file is something that we can index, but it is yet
|
||||
* to be indexed.
|
||||
*
|
||||
* [Note: Boolean IndexedDB indexes].
|
||||
* - "indexed" - We have a corresponding entry for this file in the
|
||||
* "face-index" object (either indexed locally or fetched from remote).
|
||||
*
|
||||
* IndexedDB does not (currently) supported indexes on boolean fields.
|
||||
* https://github.com/w3c/IndexedDB/issues/76
|
||||
* - "ignored" - Ignore this file when considering indexes. Some reasons
|
||||
* include:
|
||||
*
|
||||
* As a workaround, we use numeric fields where `0` denotes `false` and `1`
|
||||
* denotes `true`.
|
||||
* - Indexeing might've failed (in which case there won't even be a
|
||||
* corresponding entry for this file in "face-index").
|
||||
*
|
||||
* - We might have a "face-index" for this file, but we should not use it
|
||||
* because, say, the file is currently hidden.
|
||||
*
|
||||
* We also have a (IndexedDB) "index" on this field to allow us to
|
||||
* efficiently select or count {@link fileIDs} that fall into various
|
||||
* buckets.
|
||||
*/
|
||||
isIndexable: number;
|
||||
status: "indexable" | "indexed" | "ignored";
|
||||
/**
|
||||
* The number of times attempts to index this file failed.
|
||||
*
|
||||
* This is guaranteed to be `0` for files which have already been
|
||||
* sucessfully indexed (i.e. files for which `isIndexable` is 0 and which
|
||||
* have a corresponding entry in the "face-index" object store).
|
||||
* This is guaranteed to be `0` for files with status "indexed".
|
||||
*/
|
||||
failureCount: number;
|
||||
}
|
||||
@@ -91,7 +96,7 @@ const openFaceDB = async () => {
|
||||
db.createObjectStore("face-index", { keyPath: "fileID" });
|
||||
db.createObjectStore("file-status", {
|
||||
keyPath: "fileID",
|
||||
}).createIndex("isIndexable", "isIndexable");
|
||||
}).createIndex("status", "status");
|
||||
}
|
||||
},
|
||||
blocking() {
|
||||
@@ -176,7 +181,7 @@ export const saveFaceIndex = async (faceIndex: FaceIndex) => {
|
||||
indexStore.put(faceIndex),
|
||||
statusStore.put({
|
||||
fileID: faceIndex.fileID,
|
||||
isIndexable: 0,
|
||||
status: "indexed",
|
||||
failureCount: 0,
|
||||
}),
|
||||
tx.done,
|
||||
@@ -208,7 +213,7 @@ export const addFileEntry = async (fileID: number) => {
|
||||
if ((await tx.store.getKey(fileID)) === undefined) {
|
||||
await tx.store.put({
|
||||
fileID,
|
||||
isIndexable: 1,
|
||||
status: "indexable",
|
||||
failureCount: 0,
|
||||
});
|
||||
}
|
||||
@@ -248,7 +253,7 @@ export const syncWithLocalIndexableFileIDs = async (localFileIDs: number[]) => {
|
||||
newFileIDs.map((id) =>
|
||||
tx.objectStore("file-status").put({
|
||||
fileID: id,
|
||||
isIndexable: 1,
|
||||
status: "indexable",
|
||||
failureCount: 0,
|
||||
}),
|
||||
),
|
||||
@@ -263,16 +268,20 @@ export const syncWithLocalIndexableFileIDs = async (localFileIDs: number[]) => {
|
||||
|
||||
/**
|
||||
* Return the count of files that can be, and that have been, indexed.
|
||||
*
|
||||
* These counts are mutually exclusive. The total number of files that fall
|
||||
* within the purview of the indexer is thus indexable + indexed.
|
||||
*/
|
||||
export const indexedAndIndexableCounts = async () => {
|
||||
const db = await faceDB();
|
||||
const tx = db.transaction(["face-index", "file-status"], "readwrite");
|
||||
const indexedCount = await tx.objectStore("face-index").count();
|
||||
const indexableCount = await tx
|
||||
.objectStore("file-status")
|
||||
.index("isIndexable")
|
||||
.count(IDBKeyRange.only(1));
|
||||
return { indexedCount, indexableCount };
|
||||
const tx = db.transaction("file-status", "readwrite");
|
||||
const indexableCount = await tx.store
|
||||
.index("status")
|
||||
.count(IDBKeyRange.only("indexable"));
|
||||
const indexedCount = await tx.store
|
||||
.index("status")
|
||||
.count(IDBKeyRange.only("indexed"));
|
||||
return { indexableCount, indexedCount };
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -281,14 +290,16 @@ export const indexedAndIndexableCounts = async () => {
|
||||
* This list is from the universe of the file IDs that the face DB knows about
|
||||
* (can use {@link addFileEntry} to inform it about new files). From this
|
||||
* universe, we filter out fileIDs the files corresponding to which have already
|
||||
* been indexed, or for which we attempted indexing but failed.
|
||||
* been indexed, or which should be ignored.
|
||||
*
|
||||
* @param count Limit the result to up to {@link count} items.
|
||||
*/
|
||||
export const unindexedFileIDs = async (count?: number) => {
|
||||
export const indexableFileIDs = async (count?: number) => {
|
||||
const db = await faceDB();
|
||||
const tx = db.transaction("file-status", "readonly");
|
||||
return tx.store.index("isIndexable").getAllKeys(IDBKeyRange.only(1), count);
|
||||
return tx.store
|
||||
.index("status")
|
||||
.getAllKeys(IDBKeyRange.only("indexable"), count);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -306,7 +317,7 @@ export const markIndexingFailed = async (fileID: number) => {
|
||||
const failureCount = ((await tx.store.get(fileID)).failureCount ?? 0) + 1;
|
||||
await tx.store.put({
|
||||
fileID,
|
||||
isIndexable: 0,
|
||||
status: "ignored",
|
||||
failureCount,
|
||||
});
|
||||
return tx.done;
|
||||
|
||||
@@ -10,9 +10,9 @@ import type { EnteFile } from "types/file";
|
||||
import { isInternalUserForML } from "utils/user";
|
||||
import {
|
||||
faceIndex,
|
||||
indexableFileIDs,
|
||||
indexedAndIndexableCounts,
|
||||
syncWithLocalIndexableFileIDs,
|
||||
unindexedFileIDs,
|
||||
} from "./db";
|
||||
import { FaceIndexerWorker } from "./indexer.worker";
|
||||
|
||||
@@ -166,7 +166,7 @@ export const faceIndexingStatus = async (): Promise<FaceIndexingStatus> => {
|
||||
const { indexedCount, indexableCount } = await indexedAndIndexableCounts();
|
||||
|
||||
let phase: FaceIndexingStatus["phase"];
|
||||
if (indexedCount < indexableCount) {
|
||||
if (indexableCount > 0) {
|
||||
if (!isSyncing) {
|
||||
phase = "scheduled";
|
||||
} else {
|
||||
@@ -179,7 +179,7 @@ export const faceIndexingStatus = async (): Promise<FaceIndexingStatus> => {
|
||||
return {
|
||||
phase,
|
||||
nSyncedFiles: indexedCount,
|
||||
nTotalFiles: indexableCount,
|
||||
nTotalFiles: indexableCount + indexedCount,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -243,6 +243,6 @@ export const getFilesToIndex = async (userID: number, count: number) => {
|
||||
|
||||
await syncWithLocalIndexableFileIDs([...filesByID.keys()]);
|
||||
|
||||
const fileIDsToIndex = await unindexedFileIDs(count);
|
||||
const fileIDsToIndex = await indexableFileIDs(count);
|
||||
return fileIDsToIndex.map((id) => ensure(filesByID.get(id)));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user