refactor: cluster backends
Signed-off-by: Varun Patil <varunpatil@ucla.edu>pull/563/head
parent
da7ab397ae
commit
f284085a1a
|
@ -52,22 +52,12 @@ return [
|
||||||
['name' => 'Days#day', 'url' => '/api/days/{id}', 'verb' => 'GET'],
|
['name' => 'Days#day', 'url' => '/api/days/{id}', 'verb' => 'GET'],
|
||||||
['name' => 'Days#dayPost', 'url' => '/api/days', 'verb' => 'POST'],
|
['name' => 'Days#dayPost', 'url' => '/api/days', 'verb' => 'POST'],
|
||||||
|
|
||||||
['name' => 'Albums#list', 'url' => '/api/albums', 'verb' => 'GET'],
|
['name' => 'Clusters#list', 'url' => '/api/clusters/{backend}', 'verb' => 'GET'],
|
||||||
['name' => 'Albums#download', 'url' => '/api/albums/download', 'verb' => 'POST'],
|
['name' => 'Clusters#preview', 'url' => '/api/clusters/{backend}/preview/{name}', 'verb' => 'GET'],
|
||||||
|
['name' => 'Clusters#download', 'url' => '/api/clusters/{backend}/download', 'verb' => 'POST'],
|
||||||
|
|
||||||
['name' => 'Tags#list', 'url' => '/api/tags', 'verb' => 'GET'],
|
|
||||||
['name' => 'Tags#preview', 'url' => '/api/tags/preview/{name}', 'verb' => 'GET'],
|
|
||||||
['name' => 'Tags#set', 'url' => '/api/tags/set/{id}', 'verb' => 'PATCH'],
|
['name' => 'Tags#set', 'url' => '/api/tags/set/{id}', 'verb' => 'PATCH'],
|
||||||
|
|
||||||
['name' => 'Places#list', 'url' => '/api/places', 'verb' => 'GET'],
|
|
||||||
['name' => 'Places#preview', 'url' => '/api/places/preview/{name}', 'verb' => 'GET'],
|
|
||||||
|
|
||||||
['name' => 'PeopleRecognize#list', 'url' => '/api/recognize/people', 'verb' => 'GET'],
|
|
||||||
['name' => 'PeopleRecognize#preview', 'url' => '/api/recognize/people/preview/{name}', 'verb' => 'GET'],
|
|
||||||
|
|
||||||
['name' => 'PeopleFaceRecognition#list', 'url' => '/api/facerecognition/people', 'verb' => 'GET'],
|
|
||||||
['name' => 'PeopleFaceRecognition#preview', 'url' => '/api/facerecognition/people/preview/{name}', 'verb' => 'GET'],
|
|
||||||
|
|
||||||
['name' => 'Map#clusters', 'url' => '/api/map/clusters', 'verb' => 'GET'],
|
['name' => 'Map#clusters', 'url' => '/api/map/clusters', 'verb' => 'GET'],
|
||||||
|
|
||||||
['name' => 'Archive#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'],
|
['name' => 'Archive#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'],
|
||||||
|
|
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace OCA\Memories\AppInfo;
|
namespace OCA\Memories\AppInfo;
|
||||||
|
|
||||||
|
use OCA\Memories\ClustersBackend;
|
||||||
use OCA\Memories\Listeners\PostDeleteListener;
|
use OCA\Memories\Listeners\PostDeleteListener;
|
||||||
use OCA\Memories\Listeners\PostWriteListener;
|
use OCA\Memories\Listeners\PostWriteListener;
|
||||||
use OCP\AppFramework\App;
|
use OCP\AppFramework\App;
|
||||||
|
@ -68,9 +69,17 @@ class Application extends App implements IBootstrap
|
||||||
|
|
||||||
public function register(IRegistrationContext $context): void
|
public function register(IRegistrationContext $context): void
|
||||||
{
|
{
|
||||||
|
// Register file hooks
|
||||||
$context->registerEventListener(NodeWrittenEvent::class, PostWriteListener::class);
|
$context->registerEventListener(NodeWrittenEvent::class, PostWriteListener::class);
|
||||||
$context->registerEventListener(NodeTouchedEvent::class, PostWriteListener::class);
|
$context->registerEventListener(NodeTouchedEvent::class, PostWriteListener::class);
|
||||||
$context->registerEventListener(NodeDeletedEvent::class, PostDeleteListener::class);
|
$context->registerEventListener(NodeDeletedEvent::class, PostDeleteListener::class);
|
||||||
|
|
||||||
|
// Register clusters backends
|
||||||
|
ClustersBackend\Backend::register('albums', ClustersBackend\AlbumsBackend::class);
|
||||||
|
ClustersBackend\Backend::register('tags', ClustersBackend\TagsBackend::class);
|
||||||
|
ClustersBackend\Backend::register('places', ClustersBackend\PlacesBackend::class);
|
||||||
|
ClustersBackend\Backend::register('recognize', ClustersBackend\RecognizeBackend::class);
|
||||||
|
ClustersBackend\Backend::register('facerecognition', ClustersBackend\FaceRecognitionBackend::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function boot(IBootContext $context): void
|
public function boot(IBootContext $context): void
|
||||||
|
|
|
@ -21,43 +21,58 @@ declare(strict_types=1);
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace OCA\Memories\Controller;
|
namespace OCA\Memories\ClustersBackend;
|
||||||
|
|
||||||
|
use OCA\Memories\Db\TimelineQuery;
|
||||||
use OCA\Memories\Errors;
|
use OCA\Memories\Errors;
|
||||||
use OCA\Memories\HttpResponseException;
|
use OCA\Memories\HttpResponseException;
|
||||||
|
use OCP\App\IAppManager;
|
||||||
|
use OCP\IUserSession;
|
||||||
|
|
||||||
class AlbumsController extends GenericClusterController
|
class AlbumsBackend extends Backend
|
||||||
{
|
{
|
||||||
protected function appName(): string
|
protected TimelineQuery $timelineQuery;
|
||||||
|
protected string $userId;
|
||||||
|
protected IAppManager $appManager;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
TimelineQuery $timelineQuery,
|
||||||
|
IUserSession $userSession,
|
||||||
|
IAppManager $appManager
|
||||||
|
) {
|
||||||
|
$this->timelineQuery = $timelineQuery;
|
||||||
|
$this->userId = $userSession->getUser()->getUID();
|
||||||
|
$this->appManager = $appManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function appName(): string
|
||||||
{
|
{
|
||||||
return 'Albums';
|
return 'Albums';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function isEnabled(): bool
|
public function isEnabled(): bool
|
||||||
{
|
{
|
||||||
return $this->albumsIsEnabled();
|
return \OCA\Memories\Util::albumsIsEnabled($this->appManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function useTimelineRoot(): bool
|
public function clusterName(string $name)
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function clusterName(string $name)
|
|
||||||
{
|
{
|
||||||
return explode('/', $name)[1];
|
return explode('/', $name)[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getClusters(): array
|
public function getClusters(): array
|
||||||
{
|
{
|
||||||
|
/** @var \OCP\IRequest $request */
|
||||||
|
$request = \OC::$server->get(\OCP\IRequest::class);
|
||||||
|
|
||||||
// Run actual query
|
// Run actual query
|
||||||
$list = [];
|
$list = [];
|
||||||
$t = (int) $this->request->getParam('t', 0);
|
$t = (int) $request->getParam('t', 0);
|
||||||
if ($t & 1) { // personal
|
if ($t & 1) { // personal
|
||||||
$list = array_merge($list, $this->timelineQuery->getAlbums($this->getUID()));
|
$list = array_merge($list, $this->timelineQuery->getAlbums($this->userId));
|
||||||
}
|
}
|
||||||
if ($t & 2) { // shared
|
if ($t & 2) { // shared
|
||||||
$list = array_merge($list, $this->timelineQuery->getAlbums($this->getUID(), true));
|
$list = array_merge($list, $this->timelineQuery->getAlbums($this->userId, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove elements with duplicate album_id
|
// Remove elements with duplicate album_id
|
||||||
|
@ -75,10 +90,10 @@ class AlbumsController extends GenericClusterController
|
||||||
return array_values($list);
|
return array_values($list);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPhotos(string $name, ?int $limit = null): array
|
public function getPhotos(string $name, ?int $limit = null): array
|
||||||
{
|
{
|
||||||
// Get album
|
// Get album
|
||||||
$album = $this->timelineQuery->getAlbumIfAllowed($this->getUID(), $name);
|
$album = $this->timelineQuery->getAlbumIfAllowed($this->userId, $name);
|
||||||
if (null === $album) {
|
if (null === $album) {
|
||||||
throw new HttpResponseException(Errors::NotFound("album {$name}"));
|
throw new HttpResponseException(Errors::NotFound("album {$name}"));
|
||||||
}
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022 Varun Patil <radialapps@gmail.com>
|
||||||
|
* @author Varun Patil <radialapps@gmail.com>
|
||||||
|
* @license AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Memories\ClustersBackend;
|
||||||
|
|
||||||
|
abstract class Backend
|
||||||
|
{
|
||||||
|
/** Mapping of backend name to className */
|
||||||
|
public static array $backends = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A human-readable name for the app.
|
||||||
|
* Used for error messages.
|
||||||
|
*/
|
||||||
|
abstract public function appName(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the app is enabled for the current user.
|
||||||
|
*/
|
||||||
|
abstract public function isEnabled(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cluster list for the current user.
|
||||||
|
*/
|
||||||
|
abstract public function getClusters(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of photos with any extra parameters for the given cluster
|
||||||
|
* Used for preview generation and download.
|
||||||
|
*
|
||||||
|
* @param string $name Identifier for the cluster
|
||||||
|
* @param int $limit Maximum number of photos to return
|
||||||
|
*/
|
||||||
|
abstract public function getPhotos(string $name, ?int $limit = null): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new backend.
|
||||||
|
*
|
||||||
|
* @param mixed $name
|
||||||
|
* @param mixed $className
|
||||||
|
*/
|
||||||
|
public static function register($name, $className): void
|
||||||
|
{
|
||||||
|
self::$backends[$name] = $className;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Human readable name for the cluster.
|
||||||
|
*/
|
||||||
|
public function clusterName(string $name)
|
||||||
|
{
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put the photo objects in priority list.
|
||||||
|
* Works on the array in place.
|
||||||
|
*/
|
||||||
|
public function sortPhotosForPreview(array &$photos)
|
||||||
|
{
|
||||||
|
shuffle($photos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quality to use for the preview file.
|
||||||
|
*/
|
||||||
|
public function getPreviewQuality(): int
|
||||||
|
{
|
||||||
|
return 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any post processing and get the blob from the preview file.
|
||||||
|
*
|
||||||
|
* @param \OCP\Files\SimpleFS\ISimpleFile $file Preview file
|
||||||
|
* @param array $photo Photo object
|
||||||
|
*
|
||||||
|
* @return [Blob, mimetype] of data
|
||||||
|
*/
|
||||||
|
public function getPreviewBlob($file, $photo): array
|
||||||
|
{
|
||||||
|
return [$file->getContent(), $file->getMimeType()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file ID for a photo object.
|
||||||
|
*/
|
||||||
|
public function getFileId(array $photo): int
|
||||||
|
{
|
||||||
|
return (int) $photo['fileid'];
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,23 +21,48 @@ declare(strict_types=1);
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace OCA\Memories\Controller;
|
namespace OCA\Memories\ClustersBackend;
|
||||||
|
|
||||||
class PeopleFaceRecognitionController extends GenericClusterController
|
use OCA\Memories\Db\TimelineQuery;
|
||||||
|
use OCA\Memories\Db\TimelineRoot;
|
||||||
|
use OCP\App\IAppManager;
|
||||||
|
use OCP\IConfig;
|
||||||
|
use OCP\IUserSession;
|
||||||
|
|
||||||
|
class FaceRecognitionBackend extends Backend
|
||||||
{
|
{
|
||||||
use PeopleControllerUtils;
|
use PeopleBackendUtils;
|
||||||
|
|
||||||
protected function appName(): string
|
public TimelineRoot $root;
|
||||||
|
protected TimelineQuery $timelineQuery;
|
||||||
|
protected string $userId;
|
||||||
|
protected IAppManager $appManager;
|
||||||
|
protected IConfig $config;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
TimelineQuery $timelineQuery,
|
||||||
|
IUserSession $userSession,
|
||||||
|
IAppManager $appManager,
|
||||||
|
IConfig $config
|
||||||
|
) {
|
||||||
|
$this->timelineQuery = $timelineQuery;
|
||||||
|
$this->userId = $userSession->getUser()->getUID();
|
||||||
|
$this->appManager = $appManager;
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function appName(): string
|
||||||
{
|
{
|
||||||
return 'Face Recognition';
|
return 'Face Recognition';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function isEnabled(): bool
|
public function isEnabled(): bool
|
||||||
{
|
{
|
||||||
return $this->recognizeIsEnabled();
|
return \OCA\Memories\Util::facerecognitionIsInstalled($this->appManager)
|
||||||
|
&& \OCA\Memories\Util::facerecognitionIsEnabled($this->config, $this->userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getClusters(): array
|
public function getClusters(): array
|
||||||
{
|
{
|
||||||
return array_merge(
|
return array_merge(
|
||||||
$this->timelineQuery->getFaceRecognitionPersons($this->root, $this->model()),
|
$this->timelineQuery->getFaceRecognitionPersons($this->root, $this->model()),
|
||||||
|
@ -45,12 +70,12 @@ class PeopleFaceRecognitionController extends GenericClusterController
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPhotos(string $name, ?int $limit = null): array
|
public function getPhotos(string $name, ?int $limit = null): array
|
||||||
{
|
{
|
||||||
return $this->timelineQuery->getFaceRecognitionPhotos($name, $this->model(), $this->root, $limit) ?? [];
|
return $this->timelineQuery->getFaceRecognitionPhotos($name, $this->model(), $this->root, $limit) ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function sortPhotosForPreview(array &$photos)
|
public function sortPhotosForPreview(array &$photos)
|
||||||
{
|
{
|
||||||
// Convert to recognize format (percentage position-size)
|
// Convert to recognize format (percentage position-size)
|
||||||
foreach ($photos as &$p) {
|
foreach ($photos as &$p) {
|
||||||
|
@ -63,11 +88,16 @@ class PeopleFaceRecognitionController extends GenericClusterController
|
||||||
$this->sortByScores($photos);
|
$this->sortByScores($photos);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPreviewBlob($file, $photo): array
|
public function getPreviewBlob($file, $photo): array
|
||||||
{
|
{
|
||||||
return $this->cropFace($file, $photo, 1.8);
|
return $this->cropFace($file, $photo, 1.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPreviewQuality(): int
|
||||||
|
{
|
||||||
|
return 2048;
|
||||||
|
}
|
||||||
|
|
||||||
private function model(): int
|
private function model(): int
|
||||||
{
|
{
|
||||||
return (int) $this->config->getAppValue('facerecognition', 'model', -1);
|
return (int) $this->config->getAppValue('facerecognition', 'model', -1);
|
|
@ -21,9 +21,9 @@ declare(strict_types=1);
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace OCA\Memories\Controller;
|
namespace OCA\Memories\ClustersBackend;
|
||||||
|
|
||||||
trait PeopleControllerUtils
|
trait PeopleBackendUtils
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Sort a list of faces by the score.
|
* Sort a list of faces by the score.
|
|
@ -21,26 +21,42 @@ declare(strict_types=1);
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace OCA\Memories\Controller;
|
namespace OCA\Memories\ClustersBackend;
|
||||||
|
|
||||||
class PlacesController extends GenericClusterController
|
use OCA\Memories\Db\TimelineQuery;
|
||||||
|
use OCA\Memories\Db\TimelineRoot;
|
||||||
|
use OCP\IUserSession;
|
||||||
|
|
||||||
|
class PlacesBackend extends Backend
|
||||||
{
|
{
|
||||||
protected function appName(): string
|
public TimelineRoot $root;
|
||||||
|
protected TimelineQuery $timelineQuery;
|
||||||
|
protected string $userId;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
TimelineQuery $timelineQuery,
|
||||||
|
IUserSession $userSession
|
||||||
|
) {
|
||||||
|
$this->timelineQuery = $timelineQuery;
|
||||||
|
$this->userId = $userSession->getUser()->getUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function appName(): string
|
||||||
{
|
{
|
||||||
return 'Places';
|
return 'Places';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function isEnabled(): bool
|
public function isEnabled(): bool
|
||||||
{
|
{
|
||||||
return $this->placesIsEnabled();
|
return \OCA\Memories\Util::placesGISType() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getClusters(): array
|
public function getClusters(): array
|
||||||
{
|
{
|
||||||
return $this->timelineQuery->getPlaces($this->root);
|
return $this->timelineQuery->getPlaces($this->root);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPhotos(string $name, ?int $limit = null): array
|
public function getPhotos(string $name, ?int $limit = null): array
|
||||||
{
|
{
|
||||||
return $this->timelineQuery->getPlacePhotos((int) $name, $this->root, $limit) ?? [];
|
return $this->timelineQuery->getPlacePhotos((int) $name, $this->root, $limit) ?? [];
|
||||||
}
|
}
|
|
@ -21,39 +21,64 @@ declare(strict_types=1);
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace OCA\Memories\Controller;
|
namespace OCA\Memories\ClustersBackend;
|
||||||
|
|
||||||
class PeopleRecognizeController extends GenericClusterController
|
use OCA\Memories\Db\TimelineQuery;
|
||||||
|
use OCA\Memories\Db\TimelineRoot;
|
||||||
|
use OCP\App\IAppManager;
|
||||||
|
use OCP\IUserSession;
|
||||||
|
|
||||||
|
class RecognizeBackend extends Backend
|
||||||
{
|
{
|
||||||
use PeopleControllerUtils;
|
use PeopleBackendUtils;
|
||||||
|
|
||||||
protected function appName(): string
|
public TimelineRoot $root;
|
||||||
|
protected TimelineQuery $timelineQuery;
|
||||||
|
protected string $userId;
|
||||||
|
protected IAppManager $appManager;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
TimelineQuery $timelineQuery,
|
||||||
|
IUserSession $userSession,
|
||||||
|
IAppManager $appManager
|
||||||
|
) {
|
||||||
|
$this->timelineQuery = $timelineQuery;
|
||||||
|
$this->userId = $userSession->getUser()->getUID();
|
||||||
|
$this->appManager = $appManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function appName(): string
|
||||||
{
|
{
|
||||||
return 'Recognize';
|
return 'Recognize';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function isEnabled(): bool
|
public function isEnabled(): bool
|
||||||
{
|
{
|
||||||
return $this->recognizeIsEnabled();
|
return \OCA\Memories\Util::recognizeIsEnabled($this->appManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getClusters(): array
|
public function getClusters(): array
|
||||||
{
|
{
|
||||||
return $this->timelineQuery->getPeopleRecognize($this->root, $this->getUID());
|
return $this->timelineQuery->getPeopleRecognize($this->root, $this->userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPhotos(string $name, ?int $limit = null): array
|
public function getPhotos(string $name, ?int $limit = null): array
|
||||||
{
|
{
|
||||||
return $this->timelineQuery->getPeopleRecognizePhotos((int) $name, $this->root, $limit) ?? [];
|
return $this->timelineQuery->getPeopleRecognizePhotos((int) $name, $this->root, $limit) ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function sortPhotosForPreview(array &$photos)
|
public function sortPhotosForPreview(array &$photos)
|
||||||
{
|
{
|
||||||
$this->sortByScores($photos);
|
$this->sortByScores($photos);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPreviewBlob($file, $photo): array
|
public function getPreviewBlob($file, $photo): array
|
||||||
{
|
{
|
||||||
return $this->cropFace($file, $photo, 1.5);
|
return $this->cropFace($file, $photo, 1.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPreviewQuality(): int
|
||||||
|
{
|
||||||
|
return 2048;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022 Varun Patil <radialapps@gmail.com>
|
||||||
|
* @author Varun Patil <radialapps@gmail.com>
|
||||||
|
* @license AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Memories\ClustersBackend;
|
||||||
|
|
||||||
|
use OCA\Memories\Db\TimelineQuery;
|
||||||
|
use OCA\Memories\Db\TimelineRoot;
|
||||||
|
use OCP\App\IAppManager;
|
||||||
|
use OCP\IUserSession;
|
||||||
|
|
||||||
|
class TagsBackend extends Backend
|
||||||
|
{
|
||||||
|
public TimelineRoot $root;
|
||||||
|
protected TimelineQuery $timelineQuery;
|
||||||
|
protected string $userId;
|
||||||
|
protected IAppManager $appManager;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
TimelineQuery $timelineQuery,
|
||||||
|
IUserSession $userSession,
|
||||||
|
IAppManager $appManager
|
||||||
|
) {
|
||||||
|
$this->timelineQuery = $timelineQuery;
|
||||||
|
$this->userId = $userSession->getUser()->getUID();
|
||||||
|
$this->appManager = $appManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function appName(): string
|
||||||
|
{
|
||||||
|
return 'Tags';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isEnabled(): bool
|
||||||
|
{
|
||||||
|
return \OCA\Memories\Util::tagsIsEnabled($this->appManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClusters(): array
|
||||||
|
{
|
||||||
|
return $this->timelineQuery->getTags($this->root);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPhotos(string $name, ?int $limit = null): array
|
||||||
|
{
|
||||||
|
return $this->timelineQuery->getTagPhotos($name, $this->root, $limit) ?? [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,28 +23,29 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace OCA\Memories\Controller;
|
namespace OCA\Memories\Controller;
|
||||||
|
|
||||||
use OCA\Memories\Db\TimelineRoot;
|
use OCA\Memories\ClustersBackend\Backend;
|
||||||
use OCA\Memories\Errors;
|
use OCA\Memories\Errors;
|
||||||
use OCA\Memories\HttpResponseException;
|
use OCA\Memories\HttpResponseException;
|
||||||
use OCP\AppFramework\Http;
|
use OCP\AppFramework\Http;
|
||||||
use OCP\AppFramework\Http\DataDisplayResponse;
|
use OCP\AppFramework\Http\DataDisplayResponse;
|
||||||
use OCP\AppFramework\Http\JSONResponse;
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
|
||||||
abstract class GenericClusterController extends GenericApiController
|
class ClustersController extends GenericApiController
|
||||||
{
|
{
|
||||||
protected ?TimelineRoot $root;
|
/** Current backend for this instance */
|
||||||
|
protected Backend $backend;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
*
|
*
|
||||||
* Get list of clusters
|
* Get list of clusters
|
||||||
*/
|
*/
|
||||||
public function list(): Http\Response
|
public function list(string $backend): Http\Response
|
||||||
{
|
{
|
||||||
return $this->guardEx(function () {
|
return $this->guardEx(function () use ($backend) {
|
||||||
$this->init();
|
$this->init($backend);
|
||||||
|
|
||||||
$list = $this->getClusters();
|
$list = $this->backend->getClusters();
|
||||||
|
|
||||||
return new JSONResponse($list, Http::STATUS_OK);
|
return new JSONResponse($list, Http::STATUS_OK);
|
||||||
});
|
});
|
||||||
|
@ -57,13 +58,13 @@ abstract class GenericClusterController extends GenericApiController
|
||||||
*
|
*
|
||||||
* Get preview for a cluster
|
* Get preview for a cluster
|
||||||
*/
|
*/
|
||||||
public function preview(string $name): Http\Response
|
public function preview(string $backend, string $name): Http\Response
|
||||||
{
|
{
|
||||||
return $this->guardEx(function () use ($name) {
|
return $this->guardEx(function () use ($backend, $name) {
|
||||||
$this->init();
|
$this->init($backend);
|
||||||
|
|
||||||
// Get list of some photos in this cluster
|
// Get list of some photos in this cluster
|
||||||
$photos = $this->getPhotos($name, 8);
|
$photos = $this->backend->getPhotos($name, 8);
|
||||||
|
|
||||||
// If no photos found then return 404
|
// If no photos found then return 404
|
||||||
if (0 === \count($photos)) {
|
if (0 === \count($photos)) {
|
||||||
|
@ -71,7 +72,7 @@ abstract class GenericClusterController extends GenericApiController
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put the photos in the correct order
|
// Put the photos in the correct order
|
||||||
$this->sortPhotosForPreview($photos);
|
$this->backend->sortPhotosForPreview($photos);
|
||||||
|
|
||||||
// Get preview from image list
|
// Get preview from image list
|
||||||
return $this->getPreviewFromPhotoList($photos);
|
return $this->getPreviewFromPhotoList($photos);
|
||||||
|
@ -85,121 +86,47 @@ abstract class GenericClusterController extends GenericApiController
|
||||||
*
|
*
|
||||||
* Download a cluster as a zip file
|
* Download a cluster as a zip file
|
||||||
*/
|
*/
|
||||||
public function download(string $name): Http\Response
|
public function download(string $backend, string $name): Http\Response
|
||||||
{
|
{
|
||||||
return $this->guardEx(function () use ($name) {
|
return $this->guardEx(function () use ($backend, $name) {
|
||||||
$this->init();
|
$this->init($backend);
|
||||||
|
|
||||||
// Get list of all files in this cluster
|
// Get list of all files in this cluster
|
||||||
$photos = $this->getPhotos($name);
|
$photos = $this->backend->getPhotos($name);
|
||||||
$fileIds = array_map([$this, 'getFileId'], $photos);
|
$fileIds = array_map(fn ($p) => $this->backend->getFileId($p), $photos);
|
||||||
|
|
||||||
// Get download handle
|
// Get download handle
|
||||||
$filename = $this->clusterName($name);
|
$filename = $this->backend->clusterName($name);
|
||||||
$handle = \OCA\Memories\Controller\DownloadController::createHandle($filename, $fileIds);
|
$handle = \OCA\Memories\Controller\DownloadController::createHandle($filename, $fileIds);
|
||||||
|
|
||||||
return new JSONResponse(['handle' => $handle], Http::STATUS_OK);
|
return new JSONResponse(['handle' => $handle], Http::STATUS_OK);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A human-readable name for the app.
|
|
||||||
* Used for error messages.
|
|
||||||
*/
|
|
||||||
abstract protected function appName(): string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the app is enabled for the current user.
|
|
||||||
*/
|
|
||||||
abstract protected function isEnabled(): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the cluster list for the current user.
|
|
||||||
*/
|
|
||||||
abstract protected function getClusters(): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of photos with any extra parameters for the given cluster
|
|
||||||
* Used for preview generation and download.
|
|
||||||
*
|
|
||||||
* @param string $name Identifier for the cluster
|
|
||||||
* @param int $limit Maximum number of photos to return
|
|
||||||
*/
|
|
||||||
abstract protected function getPhotos(string $name, ?int $limit = null): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Human readable name for the cluster.
|
|
||||||
*/
|
|
||||||
protected function clusterName(string $name)
|
|
||||||
{
|
|
||||||
return $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put the photo objects in priority list.
|
|
||||||
* Works on the array in place.
|
|
||||||
*/
|
|
||||||
protected function sortPhotosForPreview(array &$photos)
|
|
||||||
{
|
|
||||||
shuffle($photos);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Quality to use for the preview file.
|
|
||||||
*/
|
|
||||||
protected function getPreviewQuality(): int
|
|
||||||
{
|
|
||||||
return 512;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform any post processing and get the blob from the preview file.
|
|
||||||
*
|
|
||||||
* @param \OCP\Files\SimpleFS\ISimpleFile $file Preview file
|
|
||||||
* @param array $photo Photo object
|
|
||||||
*
|
|
||||||
* @return [Blob, mimetype] of data
|
|
||||||
*/
|
|
||||||
protected function getPreviewBlob($file, $photo): array
|
|
||||||
{
|
|
||||||
return [$file->getContent(), $file->getMimeType()];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the file ID for a photo object.
|
|
||||||
*/
|
|
||||||
protected function getFileId(array $photo): int
|
|
||||||
{
|
|
||||||
return (int) $photo['fileid'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should the timeline root be queried?
|
|
||||||
*/
|
|
||||||
protected function useTimelineRoot(): bool
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize and check if the app is enabled.
|
* Initialize and check if the app is enabled.
|
||||||
* Gets the root node if required.
|
* Gets the root node if required.
|
||||||
*/
|
*/
|
||||||
protected function init(): void
|
protected function init(string $backend): void
|
||||||
{
|
{
|
||||||
$user = $this->userSession->getUser();
|
$user = $this->userSession->getUser();
|
||||||
if (null === $user) {
|
if (null === $user) {
|
||||||
throw new HttpResponseException(Errors::NotLoggedIn());
|
throw new HttpResponseException(Errors::NotLoggedIn());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->isEnabled()) {
|
if (\array_key_exists($backend, Backend::$backends)) {
|
||||||
throw new HttpResponseException(Errors::NotEnabled($this->appName()));
|
$this->backend = \OC::$server->get(Backend::$backends[$backend]);
|
||||||
|
} else {
|
||||||
|
throw new \Exception("Invalid clusters backend '{$backend}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->root = null;
|
if (!$this->backend->isEnabled()) {
|
||||||
if ($this->useTimelineRoot()) {
|
throw new HttpResponseException(Errors::NotEnabled($this->backend->appName()));
|
||||||
$this->root = $this->getRequestRoot();
|
}
|
||||||
if ($this->root->isEmpty()) {
|
|
||||||
|
if (property_exists($this->backend, 'root')) {
|
||||||
|
$this->backend->root = $this->getRequestRoot();
|
||||||
|
if ($this->backend->root->isEmpty()) {
|
||||||
throw new HttpResponseException(Errors::NoRequestRoot());
|
throw new HttpResponseException(Errors::NoRequestRoot());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,7 +144,7 @@ abstract class GenericClusterController extends GenericApiController
|
||||||
$userFolder = $this->rootFolder->getUserFolder($this->getUID());
|
$userFolder = $this->rootFolder->getUserFolder($this->getUID());
|
||||||
foreach ($photos as $img) {
|
foreach ($photos as $img) {
|
||||||
// Get the file
|
// Get the file
|
||||||
$files = $userFolder->getById($this->getFileId($img));
|
$files = $userFolder->getById($this->backend->getFileId($img));
|
||||||
if (0 === \count($files)) {
|
if (0 === \count($files)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -229,10 +156,10 @@ abstract class GenericClusterController extends GenericApiController
|
||||||
|
|
||||||
// Get preview image
|
// Get preview image
|
||||||
try {
|
try {
|
||||||
$quality = $this->getPreviewQuality();
|
$quality = $this->backend->getPreviewQuality();
|
||||||
$file = $previewManager->getPreview($files[0], $quality, $quality, false);
|
$file = $previewManager->getPreview($files[0], $quality, $quality, false);
|
||||||
|
|
||||||
[$blob, $mimetype] = $this->getPreviewBlob($file, $img);
|
[$blob, $mimetype] = $this->backend->getPreviewBlob($file, $img);
|
||||||
|
|
||||||
$response = new DataDisplayResponse($blob, Http::STATUS_OK, [
|
$response = new DataDisplayResponse($blob, Http::STATUS_OK, [
|
||||||
'Content-Type' => $mimetype,
|
'Content-Type' => $mimetype,
|
|
@ -27,7 +27,7 @@ use OCA\Memories\Errors;
|
||||||
use OCP\AppFramework\Http;
|
use OCP\AppFramework\Http;
|
||||||
use OCP\AppFramework\Http\JSONResponse;
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
|
||||||
class TagsController extends GenericClusterController
|
class TagsController extends GenericApiController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
|
@ -38,7 +38,7 @@ class TagsController extends GenericClusterController
|
||||||
{
|
{
|
||||||
// Check tags enabled for this user
|
// Check tags enabled for this user
|
||||||
if (!$this->tagsIsEnabled()) {
|
if (!$this->tagsIsEnabled()) {
|
||||||
return Errors::NotEnabled($this->appName());
|
return Errors::NotEnabled('Tags');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the user is allowed to edit the file
|
// Check the user is allowed to edit the file
|
||||||
|
@ -61,24 +61,4 @@ class TagsController extends GenericClusterController
|
||||||
|
|
||||||
return new JSONResponse([], Http::STATUS_OK);
|
return new JSONResponse([], Http::STATUS_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function appName(): string
|
|
||||||
{
|
|
||||||
return 'Tags';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function isEnabled(): bool
|
|
||||||
{
|
|
||||||
return $this->tagsIsEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getClusters(): array
|
|
||||||
{
|
|
||||||
return $this->timelineQuery->getTags($this->root);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getPhotos(string $name, ?int $limit = null): array
|
|
||||||
{
|
|
||||||
return $this->timelineQuery->getTagPhotos($name, $this->root, $limit) ?? [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,27 +48,27 @@ export class API {
|
||||||
}
|
}
|
||||||
|
|
||||||
static ALBUM_LIST(t: 1 | 2 | 3 = 3) {
|
static ALBUM_LIST(t: 1 | 2 | 3 = 3) {
|
||||||
return gen(`${BASE}/albums?t=${t}`);
|
return gen(`${BASE}/clusters/albums?t=${t}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ALBUM_DOWNLOAD(user: string, name: string) {
|
static ALBUM_DOWNLOAD(user: string, name: string) {
|
||||||
return gen(`${BASE}/albums/download?name={user}/{name}`, { user, name });
|
return gen(`${BASE}/clusters/albums/download?name={user}/{name}`, { user, name });
|
||||||
}
|
}
|
||||||
|
|
||||||
static PLACE_LIST() {
|
static PLACE_LIST() {
|
||||||
return gen(`${BASE}/places`);
|
return gen(`${BASE}/clusters/places`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PLACE_PREVIEW(place: number | string) {
|
static PLACE_PREVIEW(place: number | string) {
|
||||||
return gen(`${BASE}/places/preview/{place}`, { place });
|
return gen(`${BASE}/clusters/places/preview/{place}`, { place });
|
||||||
}
|
}
|
||||||
|
|
||||||
static TAG_LIST() {
|
static TAG_LIST() {
|
||||||
return gen(`${BASE}/tags`);
|
return gen(`${BASE}/clusters/tags`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static TAG_PREVIEW(tag: string) {
|
static TAG_PREVIEW(tag: string) {
|
||||||
return gen(`${BASE}/tags/preview/{tag}`, { tag });
|
return gen(`${BASE}/clusters/tags/preview/{tag}`, { tag });
|
||||||
}
|
}
|
||||||
|
|
||||||
static TAG_SET(fileid: string | number) {
|
static TAG_SET(fileid: string | number) {
|
||||||
|
@ -76,14 +76,14 @@ export class API {
|
||||||
}
|
}
|
||||||
|
|
||||||
static FACE_LIST(app: "recognize" | "facerecognition") {
|
static FACE_LIST(app: "recognize" | "facerecognition") {
|
||||||
return gen(`${BASE}/${app}/people`);
|
return gen(`${BASE}/clusters/${app}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static FACE_PREVIEW(
|
static FACE_PREVIEW(
|
||||||
app: "recognize" | "facerecognition",
|
app: "recognize" | "facerecognition",
|
||||||
face: string | number
|
face: string | number
|
||||||
) {
|
) {
|
||||||
return gen(`${BASE}/${app}/people/preview/{face}`, { face });
|
return gen(`${BASE}/clusters/${app}/preview/{face}`, { face });
|
||||||
}
|
}
|
||||||
|
|
||||||
static ARCHIVE(fileid: number) {
|
static ARCHIVE(fileid: number) {
|
||||||
|
|
Loading…
Reference in New Issue