From 4da3bd938a2880dec6ca48931c23e167b94c9414 Mon Sep 17 00:00:00 2001 From: Varun Patil Date: Wed, 2 Aug 2023 15:22:06 -0700 Subject: [PATCH] albums: refactor backend Signed-off-by: Varun Patil --- lib/ClustersBackend/AlbumsBackend.php | 14 +++++------ lib/Db/AlbumsQuery.php | 35 ++++++++++++++++----------- src/components/ClusterView.vue | 2 +- src/components/Metadata.vue | 8 ++---- src/components/modal/AlbumPicker.vue | 31 +++++++++++++++--------- src/services/API.ts | 6 ++--- src/services/dav/albums.ts | 10 ++++---- src/types.ts | 2 -- 8 files changed, 57 insertions(+), 51 deletions(-) diff --git a/lib/ClustersBackend/AlbumsBackend.php b/lib/ClustersBackend/AlbumsBackend.php index e020e089..a0cc38e1 100644 --- a/lib/ClustersBackend/AlbumsBackend.php +++ b/lib/ClustersBackend/AlbumsBackend.php @@ -94,14 +94,12 @@ class AlbumsBackend extends Backend // Run actual query $list = []; - $t = (int) $request->getParam('t', 0); - $fileid = (int) $request->getParam('fid', -1); - if ($t & 1) { // personal - $list = array_merge($list, $this->albumsQuery->getList(Util::getUID(), $fileid)); - } - if ($t & 2) { // shared - $list = array_merge($list, $this->albumsQuery->getList(Util::getUID(), $fileid, true)); - } + $fileid = (int) $request->getParam('fileid', -1); + + // personal albums + $list = array_merge($list, $this->albumsQuery->getList(Util::getUID(), false, $fileid)); + // shared albums + $list = array_merge($list, $this->albumsQuery->getList(Util::getUID(), true, $fileid)); // Remove elements with duplicate album_id $seenIds = []; diff --git a/lib/Db/AlbumsQuery.php b/lib/Db/AlbumsQuery.php index dbcfec24..51683f2a 100644 --- a/lib/Db/AlbumsQuery.php +++ b/lib/Db/AlbumsQuery.php @@ -16,17 +16,17 @@ class AlbumsQuery $this->connection = $connection; } - /** Get list of albums */ - public function getList(string $uid, int $fileId, bool $shared = false) + /** + * Get list of albums. + * + * @param bool $shared Whether to get shared albums + * @param int $fileid File to filter by + */ + public function getList(string $uid, bool $shared = false, int $fileid = -1) { $query = $this->connection->getQueryBuilder(); - $allPhotosQuery = $this->connection->getQueryBuilder(); // SELECT everything from albums - $allPhotosQuery->select('album_id')->from('photos_albums_files'); - $allPhotosQuery->where( - $allPhotosQuery->expr()->eq('file_id', $allPhotosQuery->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)) - ); $count = $query->func()->count($query->createFunction('DISTINCT m.fileid'), 'count'); $query->select( 'pa.album_id', @@ -67,14 +67,22 @@ class AlbumsQuery $query->orderBy('pa.created', 'DESC'); $query->addOrderBy('pa.album_id', 'DESC'); // tie-breaker + // WHERE these albums contain fileid if specified + if ($fileid > 0) { + $fSq = $this->connection->getQueryBuilder() + ->select('paf.file_id') + ->from('photos_albums_files', 'paf') + ->where($query->expr()->andX( + $query->expr()->eq('paf.album_id', 'pa.album_id'), + $query->expr()->eq('paf.file_id', $query->createNamedParameter($fileid, IQueryBuilder::PARAM_INT)), + )) + ->getSQL() + ; + $query->andWhere($query->createFunction("EXISTS ({$fSq})")); + } + // FETCH all albums $albums = $query->executeQuery()->fetchAll(); - $allPhotos = $allPhotosQuery->executeQuery()->fetchAll(); - $albumIds = array(); - - foreach ($allPhotos as &$album) { - $albumIds[$album['album_id']] = true; - } // Post process foreach ($albums as &$row) { @@ -83,7 +91,6 @@ class AlbumsQuery $row['album_id'] = $albumId; $row['created'] = (int) $row['created']; $row['last_added_photo'] = (int) $row['last_added_photo']; - $row['has_file'] = !!$albumIds[$albumId]; } return $albums; diff --git a/src/components/ClusterView.vue b/src/components/ClusterView.vue index 1416dc6b..bebe1838 100644 --- a/src/components/ClusterView.vue +++ b/src/components/ClusterView.vue @@ -89,7 +89,7 @@ export default defineComponent({ await this.$refs.dtm?.refresh?.(); if (this.routeIsAlbums) { - this.items = await dav.getAlbums(3, this.config.album_list_sort); + this.items = await dav.getAlbums(this.config.album_list_sort); } else if (this.routeIsTags) { this.items = await dav.getTags(); } else if (this.routeIsPeople) { diff --git a/src/components/Metadata.vue b/src/components/Metadata.vue index ccdfb1e4..0f76145f 100644 --- a/src/components/Metadata.vue +++ b/src/components/Metadata.vue @@ -73,8 +73,8 @@ import LocationIcon from 'vue-material-design-icons/MapMarker.vue'; import TagIcon from 'vue-material-design-icons/Tag.vue'; import * as utils from '../services/Utils'; +import * as dav from '../services/DavRequests'; import { API } from '../services/API'; -import router from '../router'; import type { IAlbum, IImageInfo, IPhoto } from '../types'; @@ -359,17 +359,13 @@ export default defineComponent({ async refreshAlbums(): Promise { const state = this.state; - // get album list let list: IAlbum[] = []; try { - list = (await axios.get(API.ALBUM_LIST(3, this.fileid!))).data; + list = await dav.getAlbums(1, this.fileid!); } catch (e) { console.error('metadata: failed to load albums', e); } - // filter albums containing this file - list = list.filter((a) => a.has_file); - if (state !== this.state) return list; return (this.albums = list); }, diff --git a/src/components/modal/AlbumPicker.vue b/src/components/modal/AlbumPicker.vue index 1e6e3bb3..4903e35e 100644 --- a/src/components/modal/AlbumPicker.vue +++ b/src/components/modal/AlbumPicker.vue @@ -67,13 +67,11 @@ import { defineComponent, PropType } from 'vue'; import AlbumForm from './AlbumForm.vue'; import AlbumsList from './AlbumsList.vue'; -import axios from '@nextcloud/axios'; - import NcButton from '@nextcloud/vue/dist/Components/NcButton'; const NcListItem = () => import('@nextcloud/vue/dist/Components/NcListItem'); +import * as dav from '../../services/DavRequests'; import { IAlbum, IPhoto } from '../../types'; -import { API } from '../../services/API'; import PlusIcon from 'vue-material-design-icons/Plus.vue'; import CheckIcon from 'vue-material-design-icons/Check.vue'; @@ -108,9 +106,11 @@ export default defineComponent({ loadingAlbums: true, /** List of all albums */ albums: [] as IAlbum[], - /** All selected albums */ + /** Initial selection */ + initSelection: new Set(), + /** Selected albums */ selection: new Set(), - /** Deselected albums that were previously selected */ + /** Deselected albums that were initially selected */ deselection: new Set(), }), @@ -128,14 +128,21 @@ export default defineComponent({ try { this.loadingAlbums = true; - // this only makes sense when we try to add single photo to albums - const fileid = this.photos.length === 1 ? this.photos[0].fileid : -1; + // get all albums + this.albums = await dav.getAlbums(); - // get albums, possibly for one photo - const res = await axios.get(API.ALBUM_LIST(3, fileid)); - this.albums = res.data; - this.selection = new Set(this.albums.filter((album) => album.has_file)); + // reset selection + this.initSelection = new Set(); + this.selection = new Set(); this.deselection = new Set(); + + // if only one photo is selected, get the albums of that photo + const fileid = this.photos.length === 1 ? this.photos[0].fileid : 0; + if (fileid) { + const selIds = new Set((await dav.getAlbums(1, fileid)).map((a) => a.album_id)); + this.initSelection = new Set(this.albums.filter((a) => selIds.has(a.album_id))); + this.selection = new Set(this.initSelection); + } } catch (e) { console.error(e); } finally { @@ -150,7 +157,7 @@ export default defineComponent({ this.selection.delete(album); // deselection only if originally selected - if (album.has_file) { + if (this.initSelection.has(album)) { this.deselection.add(album); } } else { diff --git a/src/services/API.ts b/src/services/API.ts index bbaf011c..9a3a98f6 100644 --- a/src/services/API.ts +++ b/src/services/API.ts @@ -37,7 +37,7 @@ export enum DaysFilterType { } export class API { - static Q(url: string, query: string | URLSearchParams | Object | undefined | null) { + static Q(url: string, query: string | URLSearchParams | Object | undefined | null): string { if (!query) return url; if (typeof query === 'object') { @@ -84,8 +84,8 @@ export class API { return tok(gen(`${BASE}/folders/sub`)); } - static ALBUM_LIST(t: 1 | 2 | 3 = 3, photoId: number = -1) { - return gen(`${BASE}/clusters/albums?t=${t}&fid=${photoId}`); + static ALBUM_LIST(fileid?: number) { + return API.Q(gen(`${BASE}/clusters/albums`), { fileid }); } static ALBUM_DOWNLOAD(user: string, name: string) { diff --git a/src/services/dav/albums.ts b/src/services/dav/albums.ts index f04b7975..f3d842e2 100644 --- a/src/services/dav/albums.ts +++ b/src/services/dav/albums.ts @@ -22,14 +22,14 @@ export function getAlbumPath(user: string, name: string) { /** * Get list of albums. - * @param type Type of albums to get; 1 = personal, 2 = shared, 3 = all - * @param sortOrder Sort order; 1 = by date, 2 = by name + * @param sort Sort order; 1 = by date, 2 = by name + * @param fileid Optional file ID to get albums for */ -export async function getAlbums(type: 1 | 2 | 3, sortOrder: 1 | 2) { - const data = (await axios.get(API.ALBUM_LIST(type))).data; +export async function getAlbums(sort: 1 | 2 = 1, fileid?: number) { + const data = (await axios.get(API.ALBUM_LIST(fileid))).data; // Sort the response - switch (sortOrder) { + switch (sort) { case 2: data.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); break; diff --git a/src/types.ts b/src/types.ts index 6f3453e6..1c06098f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -140,8 +140,6 @@ export interface IAlbum extends ICluster { location: string; /** File ID of last added photo */ last_added_photo: number; - /** Whether an album contains the file */ - has_file: boolean; } export interface IFace extends ICluster {