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
$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 = [];

View File

@ -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;

View File

@ -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) {

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 * 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<IAlbum[]> {
const state = this.state;
// get album list
let list: IAlbum[] = [];
try {
list = (await axios.get<IAlbum[]>(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);
},

View File

@ -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<IAlbum>(),
/** Selected albums */
selection: new Set<IAlbum>(),
/** Deselected albums that were previously selected */
/** Deselected albums that were initially selected */
deselection: new Set<IAlbum>(),
}),
@ -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<IAlbum[]>(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 {

View File

@ -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) {

View File

@ -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<IAlbum[]>(API.ALBUM_LIST(type))).data;
export async function getAlbums(sort: 1 | 2 = 1, fileid?: number) {
const data = (await axios.get<IAlbum[]>(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;

View File

@ -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 {