diff --git a/appinfo/routes.php b/appinfo/routes.php index 72baca14..7f3c2321 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -30,6 +30,7 @@ return [ ['name' => 'api#day', 'url' => '/api/days/{id}', 'verb' => 'GET'], ['name' => 'api#tags', 'url' => '/api/tags', 'verb' => 'GET'], + ['name' => 'api#tagPreviews', 'url' => '/api/tag-previews', 'verb' => 'GET'], ['name' => 'api#albums', 'url' => '/api/albums', 'verb' => 'GET'], diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index 51b56dea..98c64db9 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -253,8 +253,40 @@ class ApiController extends Controller $folder, ); - // Preload all tag previews - $this->timelineQuery->getTagPreviews($list, $folder); + return new JSONResponse($list, Http::STATUS_OK); + } + + /** + * @NoAdminRequired + * + * Get previews for a tag + */ + public function tagPreviews(): JSONResponse + { + $user = $this->userSession->getUser(); + if (null === $user) { + return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED); + } + + // Check tags enabled for this user + if (!$this->tagsIsEnabled()) { + return new JSONResponse(['message' => 'Tags not enabled for user'], Http::STATUS_PRECONDITION_FAILED); + } + + // If this isn't the timeline folder then things aren't going to work + $folder = $this->getRequestFolder(); + if (null === $folder) { + return new JSONResponse([], Http::STATUS_NOT_FOUND); + } + + // Get the tag + $tagName = $this->request->getParam('tag'); + + // Run actual query + $list = $this->timelineQuery->getTagPreviews( + $tagName, + $folder, + ); return new JSONResponse($list, Http::STATUS_OK); } diff --git a/lib/Db/TimelineQueryTags.php b/lib/Db/TimelineQueryTags.php index f4b97e33..24cef447 100644 --- a/lib/Db/TimelineQueryTags.php +++ b/lib/Db/TimelineQueryTags.php @@ -88,66 +88,46 @@ trait TimelineQueryTags return $tags; } - public function getTagPreviews(array &$tags, Folder &$folder) + public function getTagPreviews(string $tagName, Folder &$folder) { - // This is really horrible but will have to do for now - $sql = ''; - foreach ($tags as &$tag) { - if (!empty($sql)) { - $sql .= ' UNION ALL '; - } - - $query = $this->connection->getQueryBuilder(); - - // SELECT all photos with this tag - $query->select('f.fileid', 'f.etag', 'stom.systemtagid')->from( - 'systemtag_object_mapping', - 'stom' - )->where( - $query->expr()->eq('stom.objecttype', $query->createNamedParameter('files')), - $query->expr()->eq('stom.systemtagid', $query->createNamedParameter($tag['id'])), - ); - - // WHERE these items are memories indexed photos - $query->innerJoin('stom', 'memories', 'm', $query->expr()->eq('m.fileid', 'stom.objectid')); - - // WHERE these photos are in the user's requested folder recursively - // See the function above for an explanation of this hack - $this->addSubfolderJoinParams($query, $folder, false); - $query->innerJoin('m', 'filecache', 'f', $query->expr()->andX( - $query->expr()->eq('f.fileid', 'm.fileid'), - $query->createFunction('EXISTS (SELECT 1 from *PREFIX*cte_folders WHERE *PREFIX*cte_folders.fileid = `f`.parent)') - )); - - // MAX 4 - $query->setMaxResults(4); - - // Replace parameters - $thisSql = self::replaceQueryParams($query, $query->getSQL()); - - // Add clause - $sql .= "({$thisSql})"; + $query = $this->connection->getQueryBuilder(); + $tagId = $this->getSystemTagId($query, $tagName); + if (false === $tagId) { + return []; } + // SELECT all photos with this tag + $query->select('f.fileid', 'f.etag', 'stom.systemtagid')->from( + 'systemtag_object_mapping', + 'stom' + )->where( + $query->expr()->eq('stom.objecttype', $query->createNamedParameter('files')), + $query->expr()->eq('stom.systemtagid', $query->createNamedParameter($tagId)), + ); + + // WHERE these items are memories indexed photos + $query->innerJoin('stom', 'memories', 'm', $query->expr()->eq('m.fileid', 'stom.objectid')); + + // WHERE these photos are in the user's requested folder recursively + // See the function above for an explanation of this hack + $this->addSubfolderJoinParams($query, $folder, false); + $query->innerJoin('m', 'filecache', 'f', $query->expr()->andX( + $query->expr()->eq('f.fileid', 'm.fileid'), + $query->createFunction('EXISTS (SELECT 1 from *PREFIX*cte_folders WHERE *PREFIX*cte_folders.fileid = `f`.parent)') + )); + + // MAX 4 + $query->setMaxResults(4); + // FETCH tag previews - $cursor = $this->executeQueryWithCTEs($query, $sql); + $cursor = $this->executeQueryWithCTEs($query); $ans = $cursor->fetchAll(); // Post-process - $previewMap = []; foreach ($ans as &$row) { $row['fileid'] = (int) $row['fileid']; - $key = (int) $row['systemtagid']; - unset($row['systemtagid']); - if (!isset($previewMap[$key])) { - $previewMap[$key] = []; - } - $previewMap[$key][] = $row; } - // Add previews to tags - foreach ($tags as &$tag) { - $tag['previews'] = $previewMap[$tag['id']] ?? []; - } + return $ans; } } diff --git a/src/components/frame/Tag.vue b/src/components/frame/Tag.vue index 0d297f3e..23aaba7b 100644 --- a/src/components/frame/Tag.vue +++ b/src/components/frame/Tag.vue @@ -34,7 +34,9 @@ import { generateUrl } from '@nextcloud/router' import { getPreviewUrl } from "../../services/FileUtils"; import { getCurrentUser } from '@nextcloud/auth'; -import { NcCounterBubble } from '@nextcloud/vue' +import { NcCounterBubble } from '@nextcloud/vue'; +import axios from '@nextcloud/axios'; +import * as utils from "../../services/Utils"; import GlobalMixin from '../../mixins/GlobalMixin'; import { constants } from '../../services/Utils'; @@ -112,7 +114,28 @@ export default class Tag extends Mixins(GlobalMixin) { } // Look for previews - if (!this.data.previews) return; + if (!this.data.previews) { + try { + const todayDayId = utils.dateToDayId(new Date()); + const url = generateUrl(`/apps/memories/api/tag-previews?tag=${this.data.name}`); + const cacheUrl = `${url}&today=${Math.floor(todayDayId / 10)}`; + const cache = await utils.getCachedData(cacheUrl); + if (cache) { + this.data.previews = cache as any; + } else { + const res = await axios.get(url); + this.data.previews = res.data; + + // Cache only if >= 4 previews + if (this.data.previews.length >= 4) { + utils.cacheData(cacheUrl, res.data); + } + } + } catch (e) { + this.error = true; + return; + } + } // Reset flag this.data.previews.forEach((p) => p.flag = 0);