albums: refactor backend

Signed-off-by: Varun Patil <radialapps@gmail.com>
pull/767/head
Varun Patil 2023-08-02 15:22:06 -07:00
parent 912e05fae8
commit 4da3bd938a
8 changed files with 57 additions and 51 deletions

View File

@ -94,14 +94,12 @@ class AlbumsBackend extends Backend
// Run actual query // Run actual query
$list = []; $list = [];
$t = (int) $request->getParam('t', 0); $fileid = (int) $request->getParam('fileid', -1);
$fileid = (int) $request->getParam('fid', -1);
if ($t & 1) { // personal // personal albums
$list = array_merge($list, $this->albumsQuery->getList(Util::getUID(), $fileid)); $list = array_merge($list, $this->albumsQuery->getList(Util::getUID(), false, $fileid));
} // shared albums
if ($t & 2) { // shared $list = array_merge($list, $this->albumsQuery->getList(Util::getUID(), true, $fileid));
$list = array_merge($list, $this->albumsQuery->getList(Util::getUID(), $fileid, true));
}
// Remove elements with duplicate album_id // Remove elements with duplicate album_id
$seenIds = []; $seenIds = [];

View File

@ -16,17 +16,17 @@ class AlbumsQuery
$this->connection = $connection; $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(); $query = $this->connection->getQueryBuilder();
$allPhotosQuery = $this->connection->getQueryBuilder();
// SELECT everything from albums // 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'); $count = $query->func()->count($query->createFunction('DISTINCT m.fileid'), 'count');
$query->select( $query->select(
'pa.album_id', 'pa.album_id',
@ -67,14 +67,22 @@ class AlbumsQuery
$query->orderBy('pa.created', 'DESC'); $query->orderBy('pa.created', 'DESC');
$query->addOrderBy('pa.album_id', 'DESC'); // tie-breaker $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 // FETCH all albums
$albums = $query->executeQuery()->fetchAll(); $albums = $query->executeQuery()->fetchAll();
$allPhotos = $allPhotosQuery->executeQuery()->fetchAll();
$albumIds = array();
foreach ($allPhotos as &$album) {
$albumIds[$album['album_id']] = true;
}
// Post process // Post process
foreach ($albums as &$row) { foreach ($albums as &$row) {
@ -83,7 +91,6 @@ class AlbumsQuery
$row['album_id'] = $albumId; $row['album_id'] = $albumId;
$row['created'] = (int) $row['created']; $row['created'] = (int) $row['created'];
$row['last_added_photo'] = (int) $row['last_added_photo']; $row['last_added_photo'] = (int) $row['last_added_photo'];
$row['has_file'] = !!$albumIds[$albumId];
} }
return $albums; return $albums;

View File

@ -89,7 +89,7 @@ export default defineComponent({
await this.$refs.dtm?.refresh?.(); await this.$refs.dtm?.refresh?.();
if (this.routeIsAlbums) { 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) { } else if (this.routeIsTags) {
this.items = await dav.getTags(); this.items = await dav.getTags();
} else if (this.routeIsPeople) { } else if (this.routeIsPeople) {

View File

@ -73,8 +73,8 @@ import LocationIcon from 'vue-material-design-icons/MapMarker.vue';
import TagIcon from 'vue-material-design-icons/Tag.vue'; import TagIcon from 'vue-material-design-icons/Tag.vue';
import * as utils from '../services/Utils'; import * as utils from '../services/Utils';
import * as dav from '../services/DavRequests';
import { API } from '../services/API'; import { API } from '../services/API';
import router from '../router';
import type { IAlbum, IImageInfo, IPhoto } from '../types'; import type { IAlbum, IImageInfo, IPhoto } from '../types';
@ -359,17 +359,13 @@ export default defineComponent({
async refreshAlbums(): Promise<IAlbum[]> { async refreshAlbums(): Promise<IAlbum[]> {
const state = this.state; const state = this.state;
// get album list
let list: IAlbum[] = []; let list: IAlbum[] = [];
try { try {
list = (await axios.get<IAlbum[]>(API.ALBUM_LIST(3, this.fileid!))).data; list = await dav.getAlbums(1, this.fileid!);
} catch (e) { } catch (e) {
console.error('metadata: failed to load albums', 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; if (state !== this.state) return list;
return (this.albums = list); return (this.albums = list);
}, },

View File

@ -67,13 +67,11 @@ import { defineComponent, PropType } from 'vue';
import AlbumForm from './AlbumForm.vue'; import AlbumForm from './AlbumForm.vue';
import AlbumsList from './AlbumsList.vue'; import AlbumsList from './AlbumsList.vue';
import axios from '@nextcloud/axios';
import NcButton from '@nextcloud/vue/dist/Components/NcButton'; import NcButton from '@nextcloud/vue/dist/Components/NcButton';
const NcListItem = () => import('@nextcloud/vue/dist/Components/NcListItem'); const NcListItem = () => import('@nextcloud/vue/dist/Components/NcListItem');
import * as dav from '../../services/DavRequests';
import { IAlbum, IPhoto } from '../../types'; import { IAlbum, IPhoto } from '../../types';
import { API } from '../../services/API';
import PlusIcon from 'vue-material-design-icons/Plus.vue'; import PlusIcon from 'vue-material-design-icons/Plus.vue';
import CheckIcon from 'vue-material-design-icons/Check.vue'; import CheckIcon from 'vue-material-design-icons/Check.vue';
@ -108,9 +106,11 @@ export default defineComponent({
loadingAlbums: true, loadingAlbums: true,
/** List of all albums */ /** List of all albums */
albums: [] as IAlbum[], albums: [] as IAlbum[],
/** All selected albums */ /** Initial selection */
initSelection: new Set<IAlbum>(),
/** Selected albums */
selection: new Set<IAlbum>(), selection: new Set<IAlbum>(),
/** Deselected albums that were previously selected */ /** Deselected albums that were initially selected */
deselection: new Set<IAlbum>(), deselection: new Set<IAlbum>(),
}), }),
@ -128,14 +128,21 @@ export default defineComponent({
try { try {
this.loadingAlbums = true; this.loadingAlbums = true;
// this only makes sense when we try to add single photo to albums // get all albums
const fileid = this.photos.length === 1 ? this.photos[0].fileid : -1; this.albums = await dav.getAlbums();
// get albums, possibly for one photo // reset selection
const res = await axios.get<IAlbum[]>(API.ALBUM_LIST(3, fileid)); this.initSelection = new Set();
this.albums = res.data; this.selection = new Set();
this.selection = new Set(this.albums.filter((album) => album.has_file));
this.deselection = 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) { } catch (e) {
console.error(e); console.error(e);
} finally { } finally {
@ -150,7 +157,7 @@ export default defineComponent({
this.selection.delete(album); this.selection.delete(album);
// deselection only if originally selected // deselection only if originally selected
if (album.has_file) { if (this.initSelection.has(album)) {
this.deselection.add(album); this.deselection.add(album);
} }
} else { } else {

View File

@ -37,7 +37,7 @@ export enum DaysFilterType {
} }
export class API { 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 (!query) return url;
if (typeof query === 'object') { if (typeof query === 'object') {
@ -84,8 +84,8 @@ export class API {
return tok(gen(`${BASE}/folders/sub`)); return tok(gen(`${BASE}/folders/sub`));
} }
static ALBUM_LIST(t: 1 | 2 | 3 = 3, photoId: number = -1) { static ALBUM_LIST(fileid?: number) {
return gen(`${BASE}/clusters/albums?t=${t}&fid=${photoId}`); return API.Q(gen(`${BASE}/clusters/albums`), { fileid });
} }
static ALBUM_DOWNLOAD(user: string, name: string) { static ALBUM_DOWNLOAD(user: string, name: string) {

View File

@ -22,14 +22,14 @@ export function getAlbumPath(user: string, name: string) {
/** /**
* Get list of albums. * Get list of albums.
* @param type Type of albums to get; 1 = personal, 2 = shared, 3 = all * @param sort Sort order; 1 = by date, 2 = by name
* @param sortOrder 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) { export async function getAlbums(sort: 1 | 2 = 1, fileid?: number) {
const data = (await axios.get<IAlbum[]>(API.ALBUM_LIST(type))).data; const data = (await axios.get<IAlbum[]>(API.ALBUM_LIST(fileid))).data;
// Sort the response // Sort the response
switch (sortOrder) { switch (sort) {
case 2: case 2:
data.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); data.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }));
break; break;

View File

@ -140,8 +140,6 @@ export interface IAlbum extends ICluster {
location: string; location: string;
/** File ID of last added photo */ /** File ID of last added photo */
last_added_photo: number; last_added_photo: number;
/** Whether an album contains the file */
has_file: boolean;
} }
export interface IFace extends ICluster { export interface IFace extends ICluster {