perf: reduce metadata queries
Signed-off-by: Varun Patil <radialapps@gmail.com>pull/767/head
parent
d00411670e
commit
66f9616942
|
@ -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 = [];
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue