perf: reduce metadata queries

Signed-off-by: Varun Patil <radialapps@gmail.com>
pull/767/head
Varun Patil 2023-08-03 19:43:57 -07:00
parent d00411670e
commit 66f9616942
13 changed files with 97 additions and 62 deletions

View File

@ -87,7 +87,7 @@ class AlbumsBackend extends Backend
$this->tq->allowEmptyRoot();
}
public function getClusters(int $fileid = 0): array
protected function getClustersI(int $fileid = 0): array
{
// Run actual queries
$list = [];

View File

@ -61,9 +61,12 @@ abstract class Backend
/**
* Get the cluster list for the current user.
*
* If the signature of this function changes, the
* getClusters function must be updated to match.
*
* @param int $fileid Filter clusters by file ID (optional)
*/
abstract public function getClusters(int $fileid = 0): array;
abstract protected function getClustersI(int $fileid = 0): array;
/**
* Get a cluster ID for the given cluster.
@ -125,10 +128,27 @@ abstract class Backend
return (int) $photo['fileid'];
}
/**
* Calls the getClusters implementation and appends the
* result with the cluster_id and cluster_type values.
*
* @param int $fileid Filter clusters by file ID (optional)
*/
public final function getClusters(int $fileid): array {
$list = $this->getClustersI($fileid);
foreach ($list as &$cluster) {
$cluster['cluster_id'] = $this->getClusterId($cluster);
$cluster['cluster_type'] = $this->clusterType();
}
return $list;
}
/**
* Register the backend. Do not override.
*/
public static function register(): void
public static final function register(): void
{
Manager::register(static::clusterType(), static::class);
}

View File

@ -123,7 +123,7 @@ class FaceRecognitionBackend extends Backend
unset($row['face_x'], $row['face_y'], $row['face_w'], $row['face_h'], $row['image_height'], $row['image_width']);
}
public function getClusters(int $fileid = 0): array
protected function getClustersI(int $fileid = 0): array
{
if ($fileid) {
throw new \Exception('FaceRecognitionBackend: fileid filter not implemented');

View File

@ -63,7 +63,7 @@ class PlacesBackend extends Backend
));
}
public function getClusters(int $fileid = 0): array
protected function getClustersI(int $fileid = 0): array
{
if ($fileid) {
throw new \Exception('PlacesBackend: fileid filter not implemented');

View File

@ -129,7 +129,7 @@ class RecognizeBackend extends Backend
unset($row['face_w'], $row['face_h'], $row['face_x'], $row['face_y']);
}
public function getClusters(int $fileid = 0): array
protected function getClustersI(int $fileid = 0): array
{
$query = $this->tq->getBuilder();

View File

@ -67,7 +67,7 @@ class TagsBackend extends Backend
));
}
public function getClusters(int $fileid = 0): array
protected function getClustersI(int $fileid = 0): array
{
if ($fileid) {
throw new \Exception('TagsBackend: fileid filter not implemented');

View File

@ -47,12 +47,6 @@ class ClustersController extends GenericApiController
$list = $this->backend->getClusters($fileid);
// Set cluster_id and cluster_type for each cluster
foreach ($list as &$cluster) {
$cluster['cluster_id'] = $this->backend->getClusterId($cluster);
$cluster['cluster_type'] = $this->backend->clusterType();
}
return new JSONResponse($list, Http::STATUS_OK);
});
}

View File

@ -180,31 +180,19 @@ class ImageController extends GenericApiController
int $id,
bool $basic = false,
bool $current = false,
bool $tags = false
bool $tags = false,
string $clusters = ''
): Http\Response {
return Util::guardEx(function () use ($id, $basic, $current, $tags) {
return Util::guardEx(function () use ($id, $basic, $current, $tags, $clusters) {
$file = $this->fs->getUserFile($id);
// Get the image info
$info = $this->timelineQuery->getInfoById($file->getId(), $basic);
$info = $this->timelineQuery->getInfoById($id, $basic);
// Add fileid and etag
$info['fileid'] = $file->getId();
$info['etag'] = $file->getEtag();
// Allow these ony for logged in users
if (null !== $this->userSession->getUser()) {
// Get list of tags for this file
if ($tags) {
$info['tags'] = $this->getTags($file->getId());
}
// Get latest exif data if requested
if ($current) {
$info['current'] = Exif::getExifFromFile($file);
}
}
// Inject permissions and convert to string
$info['permissions'] = \OCA\Memories\Util::permissionsToStr($file->getPermissions());
@ -213,6 +201,31 @@ class ImageController extends GenericApiController
$info['size'] = $file->getSize();
$info['basename'] = $file->getName();
// Allow these ony for logged in users
if (null !== $this->userSession->getUser()) {
// Get list of tags for this file
if ($tags) {
$info['tags'] = $this->getTags($id);
}
// Get latest exif data if requested
if ($current) {
$info['current'] = Exif::getExifFromFile($file);
}
// Get clusters for this file
if ($clusters) {
$clist = [];
foreach (explode(',', $clusters) as $type) {
$backend = \OC::$server->get(\OCA\Memories\ClustersBackend\Manager::class)->get($type);
if ($backend->isEnabled()) {
$clist[$type] = $backend->getClusters($id);
}
}
$info['clusters'] = $clist;
}
}
return new JSONResponse($info, Http::STATUS_OK);
});
}

View File

@ -110,28 +110,23 @@ export default defineComponent({
mixins: [UserConfig],
data: () => ({
// Basic info and metadata
fileid: null as number | null,
filename: '',
exif: {} as { [prop: string]: any },
baseInfo: {} as IImageInfo,
// Cluster lists
albums: [] as IAlbum[],
people: [] as IFace[],
loading: 0,
state: 0,
}),
mounted() {
subscribe('files:file:updated', this.handleFileUpdated);
subscribe('memories:albums:update', this.refreshAlbums);
subscribe('memories:albums:update', this.refresh);
},
beforeDestroy() {
unsubscribe('files:file:updated', this.handleFileUpdated);
unsubscribe('memories:albums:update', this.refreshAlbums);
unsubscribe('memories:albums:update', this.refresh);
},
computed: {
@ -351,6 +346,14 @@ export default defineComponent({
mapFullUrl(): string {
return `https://www.openstreetmap.org/?mlat=${this.lat}&mlon=${this.lon}#map=18/${this.lat}/${this.lon}`;
},
albums(): IAlbum[] {
return this.baseInfo?.clusters?.albums ?? [];
},
people(): IFace[] {
return this.baseInfo?.clusters?.recognize ?? [];
},
},
methods: {
@ -359,35 +362,34 @@ export default defineComponent({
this.loading = 0;
this.fileid = null;
this.exif = {};
this.albums = [];
this.people = [];
const url = API.Q(utils.getImageInfoUrl(photo), { tags: 1 });
// which clusters to get
const clusters = [
this.config.albums_enabled ? 'albums' : null,
this.config.recognize_enabled ? 'recognize' : null,
]
.filter((c) => c)
.join(',');
// get tags if enabled
const tags = this.config.systemtags_enabled ? 1 : 0;
// get image info
const url = API.Q(utils.getImageInfoUrl(photo), { tags, clusters });
const res = await this.guardState(axios.get<IImageInfo>(url));
if (!res) return null;
// unwrap basic info
this.fileid = res.data.fileid;
this.filename = res.data.basename;
this.exif = res.data.exif || {};
this.baseInfo = res.data;
// trigger other refreshes
if (!utils.isLocalPhoto(photo)) {
this.refreshAlbums();
this.refreshPeople();
}
return this.baseInfo;
},
async refreshAlbums(): Promise<void> {
if (!this.config.albums_enabled) return;
this.albums = (await this.guardState(dav.getAlbums(1, this.fileid!))) ?? this.albums;
},
async refreshPeople(): Promise<void> {
if (!this.config.recognize_enabled) return;
this.people = (await this.guardState(dav.getFaceList('recognize', this.fileid!))) ?? this.people;
async refresh() {
if (this.fileid) await this.update(this.fileid);
},
async guardState<T>(promise: Promise<T>): Promise<T | null> {
@ -406,7 +408,7 @@ export default defineComponent({
handleFileUpdated({ fileid }: { fileid: number }) {
if (fileid && this.fileid === fileid) {
this.update(this.fileid);
this.refresh();
}
},
},

View File

@ -84,8 +84,8 @@ export class API {
return tok(gen(`${BASE}/folders/sub`));
}
static ALBUM_LIST(fileid?: number) {
return API.Q(gen(`${BASE}/clusters/albums`), { fileid });
static ALBUM_LIST() {
return gen(`${BASE}/clusters/albums`);
}
static ALBUM_DOWNLOAD(user: string, name: string) {
@ -106,8 +106,8 @@ export class API {
return gen(`${BASE}/tags/set/{fileid}`, { fileid });
}
static FACE_LIST(app: 'recognize' | 'facerecognition', fileid?: number) {
return API.Q(gen(`${BASE}/clusters/${app}`), { fileid });
static FACE_LIST(app: 'recognize' | 'facerecognition') {
return gen(`${BASE}/clusters/${app}`);
}
static CLUSTER_PREVIEW(backend: ClusterTypes, name: string | number) {

View File

@ -26,7 +26,9 @@ export function getAlbumPath(user: string, name: string) {
* @param fileid Optional file ID to get albums for
*/
export async function getAlbums(sort: 1 | 2 = 1, fileid?: number) {
const data = (await axios.get<IAlbum[]>(API.ALBUM_LIST(fileid))).data;
const url = API.Q(API.ALBUM_LIST(), { fileid });
const res = await axios.get<IAlbum[]>(url);
const data = res.data;
// Sort the response
switch (sort) {

View File

@ -11,10 +11,9 @@ import * as base from './base';
/**
* Get list of faces
* @param app Backend app to use
* @param fileid File to filter by (optional)
*/
export async function getFaceList(app: 'recognize' | 'facerecognition', fileid?: number) {
return (await axios.get<IFace[]>(API.FACE_LIST(app, fileid))).data;
export async function getFaceList(app: 'recognize' | 'facerecognition') {
return (await axios.get<IFace[]>(API.FACE_LIST(app))).data;
}
/**

View File

@ -105,6 +105,11 @@ export interface IImageInfo {
Description?: string;
[other: string]: unknown;
};
clusters?: {
albums?: IAlbum[];
recognize?: IFace[];
};
}
export interface IFolder extends IPhoto {