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(); $this->tq->allowEmptyRoot();
} }
public function getClusters(int $fileid = 0): array protected function getClustersI(int $fileid = 0): array
{ {
// Run actual queries // Run actual queries
$list = []; $list = [];

View File

@ -61,9 +61,12 @@ abstract class Backend
/** /**
* Get the cluster list for the current user. * 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) * @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. * Get a cluster ID for the given cluster.
@ -125,10 +128,27 @@ abstract class Backend
return (int) $photo['fileid']; 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. * Register the backend. Do not override.
*/ */
public static function register(): void public static final function register(): void
{ {
Manager::register(static::clusterType(), static::class); 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']); 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) { if ($fileid) {
throw new \Exception('FaceRecognitionBackend: fileid filter not implemented'); 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) { if ($fileid) {
throw new \Exception('PlacesBackend: fileid filter not implemented'); 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']); 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(); $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) { if ($fileid) {
throw new \Exception('TagsBackend: fileid filter not implemented'); throw new \Exception('TagsBackend: fileid filter not implemented');

View File

@ -47,12 +47,6 @@ class ClustersController extends GenericApiController
$list = $this->backend->getClusters($fileid); $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); return new JSONResponse($list, Http::STATUS_OK);
}); });
} }

View File

@ -180,31 +180,19 @@ class ImageController extends GenericApiController
int $id, int $id,
bool $basic = false, bool $basic = false,
bool $current = false, bool $current = false,
bool $tags = false bool $tags = false,
string $clusters = ''
): Http\Response { ): 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); $file = $this->fs->getUserFile($id);
// Get the image info // Get the image info
$info = $this->timelineQuery->getInfoById($file->getId(), $basic); $info = $this->timelineQuery->getInfoById($id, $basic);
// Add fileid and etag // Add fileid and etag
$info['fileid'] = $file->getId(); $info['fileid'] = $file->getId();
$info['etag'] = $file->getEtag(); $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 // Inject permissions and convert to string
$info['permissions'] = \OCA\Memories\Util::permissionsToStr($file->getPermissions()); $info['permissions'] = \OCA\Memories\Util::permissionsToStr($file->getPermissions());
@ -213,6 +201,31 @@ class ImageController extends GenericApiController
$info['size'] = $file->getSize(); $info['size'] = $file->getSize();
$info['basename'] = $file->getName(); $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); return new JSONResponse($info, Http::STATUS_OK);
}); });
} }

View File

@ -110,28 +110,23 @@ export default defineComponent({
mixins: [UserConfig], mixins: [UserConfig],
data: () => ({ data: () => ({
// Basic info and metadata
fileid: null as number | null, fileid: null as number | null,
filename: '', filename: '',
exif: {} as { [prop: string]: any }, exif: {} as { [prop: string]: any },
baseInfo: {} as IImageInfo, baseInfo: {} as IImageInfo,
// Cluster lists
albums: [] as IAlbum[],
people: [] as IFace[],
loading: 0, loading: 0,
state: 0, state: 0,
}), }),
mounted() { mounted() {
subscribe('files:file:updated', this.handleFileUpdated); subscribe('files:file:updated', this.handleFileUpdated);
subscribe('memories:albums:update', this.refreshAlbums); subscribe('memories:albums:update', this.refresh);
}, },
beforeDestroy() { beforeDestroy() {
unsubscribe('files:file:updated', this.handleFileUpdated); unsubscribe('files:file:updated', this.handleFileUpdated);
unsubscribe('memories:albums:update', this.refreshAlbums); unsubscribe('memories:albums:update', this.refresh);
}, },
computed: { computed: {
@ -351,6 +346,14 @@ export default defineComponent({
mapFullUrl(): string { mapFullUrl(): string {
return `https://www.openstreetmap.org/?mlat=${this.lat}&mlon=${this.lon}#map=18/${this.lat}/${this.lon}`; 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: { methods: {
@ -359,35 +362,34 @@ export default defineComponent({
this.loading = 0; this.loading = 0;
this.fileid = null; this.fileid = null;
this.exif = {}; 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)); const res = await this.guardState(axios.get<IImageInfo>(url));
if (!res) return null; if (!res) return null;
// unwrap basic info
this.fileid = res.data.fileid; this.fileid = res.data.fileid;
this.filename = res.data.basename; this.filename = res.data.basename;
this.exif = res.data.exif || {}; this.exif = res.data.exif || {};
this.baseInfo = res.data; this.baseInfo = res.data;
// trigger other refreshes
if (!utils.isLocalPhoto(photo)) {
this.refreshAlbums();
this.refreshPeople();
}
return this.baseInfo; return this.baseInfo;
}, },
async refreshAlbums(): Promise<void> { async refresh() {
if (!this.config.albums_enabled) return; if (this.fileid) await this.update(this.fileid);
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 guardState<T>(promise: Promise<T>): Promise<T | null> { async guardState<T>(promise: Promise<T>): Promise<T | null> {
@ -406,7 +408,7 @@ export default defineComponent({
handleFileUpdated({ fileid }: { fileid: number }) { handleFileUpdated({ fileid }: { fileid: number }) {
if (fileid && this.fileid === fileid) { 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`)); return tok(gen(`${BASE}/folders/sub`));
} }
static ALBUM_LIST(fileid?: number) { static ALBUM_LIST() {
return API.Q(gen(`${BASE}/clusters/albums`), { fileid }); return gen(`${BASE}/clusters/albums`);
} }
static ALBUM_DOWNLOAD(user: string, name: string) { static ALBUM_DOWNLOAD(user: string, name: string) {
@ -106,8 +106,8 @@ export class API {
return gen(`${BASE}/tags/set/{fileid}`, { fileid }); return gen(`${BASE}/tags/set/{fileid}`, { fileid });
} }
static FACE_LIST(app: 'recognize' | 'facerecognition', fileid?: number) { static FACE_LIST(app: 'recognize' | 'facerecognition') {
return API.Q(gen(`${BASE}/clusters/${app}`), { fileid }); return gen(`${BASE}/clusters/${app}`);
} }
static CLUSTER_PREVIEW(backend: ClusterTypes, name: string | number) { 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 * @param fileid Optional file ID to get albums for
*/ */
export async function getAlbums(sort: 1 | 2 = 1, fileid?: number) { 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 // Sort the response
switch (sort) { switch (sort) {

View File

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

View File

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