From 991515e9e5f650046d14bf8752698eabc338cada Mon Sep 17 00:00:00 2001 From: Varun Patil Date: Fri, 7 Oct 2022 19:00:55 -0700 Subject: [PATCH] Get face previews in single query --- appinfo/routes.php | 1 - lib/Controller/ApiController.php | 53 ++++++++++++-------------------- lib/Db/TimelineQueryFaces.php | 33 +++++++++++++++----- src/components/Tag.vue | 11 +------ 4 files changed, 47 insertions(+), 51 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index 1bb59e98..0e44e4a3 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -26,7 +26,6 @@ return [ ['name' => 'api#day', 'url' => '/api/days/{id}', 'verb' => 'GET'], ['name' => 'api#tags', 'url' => '/api/tags', 'verb' => 'GET'], ['name' => 'api#faces', 'url' => '/api/faces', 'verb' => 'GET'], - ['name' => 'api#facePreviews', 'url' => '/api/face-previews/{id}', 'verb' => 'GET'], ['name' => 'api#imageInfo', 'url' => '/api/info/{id}', 'verb' => 'GET'], ['name' => 'api#imageEdit', 'url' => '/api/edit/{id}', 'verb' => 'PATCH'], ['name' => 'api#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'], diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index 884ce18c..d93a8325 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -359,45 +359,32 @@ class ApiController extends Controller { ); // Preload all face previews + $previews = $this->timelineQuery->getFacePreviews($folder); + + // Convert to map with key as cluster_id + $previews_map = []; + foreach ($previews as &$preview) { + $key = $preview["cluster_id"]; + if (!array_key_exists($key, $previews_map)) { + $previews_map[$key] = []; + } + unset($preview["cluster_id"]); + $previews_map[$key][] = $preview; + } + + // Add all previews to list foreach ($list as &$face) { - $face["previews"] = $this->timelineQuery->getFacePreviews( - $folder, $face["id"], - ); + $key = $face["id"]; + if (array_key_exists($key, $previews_map)) { + $face["previews"] = $previews_map[$key]; + } else { + $face["previews"] = []; + } } return new JSONResponse($list, Http::STATUS_OK); } - /** - * @NoAdminRequired - * - * Get preview objects for a face ID - * @return JSONResponse - */ - public function facePreviews(string $id): JSONResponse { - $user = $this->userSession->getUser(); - if (is_null($user)) { - return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED); - } - - // Check faces enabled for this user - if (!$this->recognizeIsEnabled()) { - return new JSONResponse(["message" => "Recognize app not enabled"], Http::STATUS_PRECONDITION_FAILED); - } - - // If this isn't the timeline folder then things aren't going to work - $folder = $this->getRequestFolder(); - if (is_null($folder)) { - return new JSONResponse([], Http::STATUS_NOT_FOUND); - } - - // Run actual query - $list = $this->timelineQuery->getFacePreviews( - $folder, intval($id), - ); - return new JSONResponse($list, Http::STATUS_OK); - } - /** * @NoAdminRequired * diff --git a/lib/Db/TimelineQueryFaces.php b/lib/Db/TimelineQueryFaces.php index 7dfa86c7..67761890 100644 --- a/lib/Db/TimelineQueryFaces.php +++ b/lib/Db/TimelineQueryFaces.php @@ -53,12 +53,20 @@ trait TimelineQueryFaces { return $faces; } - public function getFacePreviews(Folder $folder, int $faceId) { + public function getFacePreviews(Folder $folder) { $query = $this->connection->getQueryBuilder(); + $rowNumber = $query->createFunction('ROW_NUMBER() OVER (PARTITION BY rfd.cluster_id) as n'); + // SELECT face detections for ID - $query->select('rfd.file_id', 'rfd.x', 'rfd.y', 'rfd.width', 'rfd.height', 'f.etag')->from('recognize_face_detections', 'rfd'); - $query->where($query->expr()->eq('rfd.cluster_id', $query->createNamedParameter($faceId))); + $query->select( + 'rfd.cluster_id', + 'rfd.file_id', + 'rfd.x', 'rfd.y', 'rfd.width', 'rfd.height', + 'f.etag', + $rowNumber, + )->from('recognize_face_detections', 'rfd'); + $query->where($query->expr()->isNotNull('rfd.cluster_id')); // WHERE these photos are memories indexed $query->innerJoin('rfd', 'memories', 'm', $query->expr()->eq('m.fileid', 'rfd.file_id')); @@ -66,20 +74,31 @@ trait TimelineQueryFaces { // WHERE these photos are in the user's requested folder recursively $query->innerJoin('m', 'filecache', 'f', $this->getFilecacheJoinQuery($query, $folder, true, false)); - // MAX 4 results - $query->setMaxResults(4); + // Make this a sub query + $fun = $query->createFunction('(' . $query->getSQL() . ')'); + + // Create outer query + $outerQuery = $this->connection->getQueryBuilder(); + $outerQuery->setParameters($query->getParameters()); + $outerQuery->select('*')->from($fun, 't'); + $outerQuery->where($query->expr()->lte('t.n', $outerQuery->createParameter('nc'))); + $outerQuery->setParameter('nc', 4, IQueryBuilder::PARAM_INT); // FETCH all face detections - $previews = $query->executeQuery()->fetchAll(); + $previews = $outerQuery->executeQuery()->fetchAll(); // Post-process, everthing is a number foreach($previews as &$row) { + $row["cluster_id"] = intval($row["cluster_id"]); $row["fileid"] = intval($row["file_id"]); - unset($row["file_id"]); $row["x"] = floatval($row["x"]); $row["y"] = floatval($row["y"]); $row["width"] = floatval($row["width"]); $row["height"] = floatval($row["height"]); + + // remove stale + unset($row["file_id"]); + unset($row["n"]); } return $previews; diff --git a/src/components/Tag.vue b/src/components/Tag.vue index d0fa8380..e1270dcf 100644 --- a/src/components/Tag.vue +++ b/src/components/Tag.vue @@ -96,9 +96,7 @@ export default class Tag extends Mixins(GlobalMixin) { } try { - if (this.isFace) { - await this.refreshPreviewsFace(); - } else { + if (!this.isFace) { await this.refreshPreviewsTag(); } this.processPreviews(); @@ -115,13 +113,6 @@ export default class Tag extends Mixins(GlobalMixin) { this.data.previews = res.data; } - /** Refresh previews for face */ - async refreshPreviewsFace() { - const url = `/apps/memories/api/face-previews/${this.data.faceid}`; - const res = await axios.get(generateUrl(url)); - this.data.previews = res.data; - } - /** Process previews */ processPreviews() { this.data.previews.forEach((p) => p.flag = 0);