Reorganize API controller
parent
7edb28fe97
commit
f0e89f4056
|
@ -25,30 +25,31 @@ return [
|
||||||
],
|
],
|
||||||
|
|
||||||
// Public pages
|
// Public pages
|
||||||
['name' => 'page#sharedFolder', 'url' => '/s/{token}', 'verb' => 'GET'],
|
['name' => 'page#sharedfolder', 'url' => '/s/{token}', 'verb' => 'GET'],
|
||||||
|
|
||||||
// API
|
// API Routes
|
||||||
['name' => 'api#days', 'url' => '/api/days', 'verb' => 'GET'],
|
|
||||||
['name' => 'api#dayPost', 'url' => '/api/days', 'verb' => 'POST'],
|
|
||||||
['name' => 'api#day', 'url' => '/api/days/{id}', 'verb' => 'GET'],
|
|
||||||
|
|
||||||
['name' => 'api#tags', 'url' => '/api/tags', 'verb' => 'GET'],
|
['name' => 'days#days', 'url' => '/api/days', 'verb' => 'GET'],
|
||||||
['name' => 'api#tagPreviews', 'url' => '/api/tag-previews', 'verb' => 'GET'],
|
['name' => 'days#dayPost', 'url' => '/api/days', 'verb' => 'POST'],
|
||||||
|
['name' => 'days#day', 'url' => '/api/days/{id}', 'verb' => 'GET'],
|
||||||
|
|
||||||
['name' => 'api#albums', 'url' => '/api/albums', 'verb' => 'GET'],
|
['name' => 'tags#tags', 'url' => '/api/tags', 'verb' => 'GET'],
|
||||||
|
['name' => 'tags#previews', 'url' => '/api/tag-previews', 'verb' => 'GET'],
|
||||||
|
|
||||||
['name' => 'api#faces', 'url' => '/api/faces', 'verb' => 'GET'],
|
['name' => 'albums#albums', 'url' => '/api/albums', 'verb' => 'GET'],
|
||||||
['name' => 'api#facePreview', 'url' => '/api/faces/preview/{id}', 'verb' => 'GET'],
|
|
||||||
|
|
||||||
['name' => 'api#imageInfo', 'url' => '/api/info/{id}', 'verb' => 'GET'],
|
['name' => 'faces#faces', 'url' => '/api/faces', 'verb' => 'GET'],
|
||||||
['name' => 'api#imageEdit', 'url' => '/api/edit/{id}', 'verb' => 'PATCH'],
|
['name' => 'faces#preview', 'url' => '/api/faces/preview/{id}', 'verb' => 'GET'],
|
||||||
|
|
||||||
['name' => 'api#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'],
|
['name' => 'image#info', 'url' => '/api/info/{id}', 'verb' => 'GET'],
|
||||||
|
['name' => 'image#edit', 'url' => '/api/edit/{id}', 'verb' => 'PATCH'],
|
||||||
|
|
||||||
|
['name' => 'archive#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'],
|
||||||
|
|
||||||
// Config API
|
// Config API
|
||||||
['name' => 'api#setUserConfig', 'url' => '/api/config/{key}', 'verb' => 'PUT'],
|
['name' => 'other#setUserConfig', 'url' => '/api/config/{key}', 'verb' => 'PUT'],
|
||||||
|
|
||||||
// Service worker
|
// Service worker
|
||||||
['name' => 'api#serviceWorker', 'url' => '/service-worker.js', 'verb' => 'GET'],
|
['name' => 'other#serviceWorker', 'url' => '/service-worker.js', 'verb' => 'GET'],
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
|
||||||
|
class AlbumsController extends ApiBase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* Get list of albums with counts of images
|
||||||
|
*/
|
||||||
|
public function albums(): JSONResponse
|
||||||
|
{
|
||||||
|
$user = $this->userSession->getUser();
|
||||||
|
if (null === $user) {
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check tags enabled for this user
|
||||||
|
if (!$this->albumsIsEnabled()) {
|
||||||
|
return new JSONResponse(['message' => 'Albums not enabled for user'], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run actual query
|
||||||
|
$list = [];
|
||||||
|
$t = (int) $this->request->getParam('t');
|
||||||
|
if ($t & 1) { // personal
|
||||||
|
$list = array_merge($list, $this->timelineQuery->getAlbums($user->getUID()));
|
||||||
|
}
|
||||||
|
if ($t & 2) { // shared
|
||||||
|
$list = array_merge($list, $this->timelineQuery->getAlbums($user->getUID(), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JSONResponse($list, Http::STATUS_OK);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use OCA\Memories\AppInfo\Application;
|
||||||
|
use OCA\Memories\Db\TimelineQuery;
|
||||||
|
use OCA\Memories\Db\TimelineWrite;
|
||||||
|
use OCA\Memories\Exif;
|
||||||
|
use OCP\App\IAppManager;
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
use OCP\Files\Folder;
|
||||||
|
use OCP\Files\IRootFolder;
|
||||||
|
use OCP\IConfig;
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
use OCP\IPreview;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\IUserSession;
|
||||||
|
use OCP\Share\IManager as IShareManager;
|
||||||
|
|
||||||
|
class ApiBase extends Controller
|
||||||
|
{
|
||||||
|
protected IConfig $config;
|
||||||
|
protected IUserSession $userSession;
|
||||||
|
protected IRootFolder $rootFolder;
|
||||||
|
protected IAppManager $appManager;
|
||||||
|
protected TimelineQuery $timelineQuery;
|
||||||
|
protected TimelineWrite $timelineWrite;
|
||||||
|
protected IShareManager $shareManager;
|
||||||
|
protected IPreview $previewManager;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
IRequest $request,
|
||||||
|
IConfig $config,
|
||||||
|
IUserSession $userSession,
|
||||||
|
IDBConnection $connection,
|
||||||
|
IRootFolder $rootFolder,
|
||||||
|
IAppManager $appManager,
|
||||||
|
IShareManager $shareManager,
|
||||||
|
IPreview $preview
|
||||||
|
) {
|
||||||
|
parent::__construct(Application::APPNAME, $request);
|
||||||
|
|
||||||
|
$this->config = $config;
|
||||||
|
$this->userSession = $userSession;
|
||||||
|
$this->connection = $connection;
|
||||||
|
$this->rootFolder = $rootFolder;
|
||||||
|
$this->appManager = $appManager;
|
||||||
|
$this->shareManager = $shareManager;
|
||||||
|
$this->previewManager = $preview;
|
||||||
|
$this->timelineQuery = new TimelineQuery($connection);
|
||||||
|
$this->timelineWrite = new TimelineWrite($connection, $preview);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get logged in user's UID or throw HTTP error */
|
||||||
|
protected function getUid(): string
|
||||||
|
{
|
||||||
|
$user = $this->userSession->getUser();
|
||||||
|
if ($this->getShareToken()) {
|
||||||
|
$user = null;
|
||||||
|
} elseif (null === $user) {
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user ? $user->getUID() : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the Folder object relevant to the request */
|
||||||
|
protected function getRequestFolder()
|
||||||
|
{
|
||||||
|
// Albums have no folder
|
||||||
|
if ($this->request->getParam('album')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public shared folder
|
||||||
|
if ($token = $this->getShareToken()) {
|
||||||
|
$share = $this->shareManager->getShareByToken($token)->getNode(); // throws exception if not found
|
||||||
|
if (!$share instanceof Folder) {
|
||||||
|
throw new \Exception('Share not found or invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $share;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anything else needs a user
|
||||||
|
$user = $this->userSession->getUser();
|
||||||
|
if (null === $user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$uid = $user->getUID();
|
||||||
|
|
||||||
|
$folder = null;
|
||||||
|
$folderPath = $this->request->getParam('folder');
|
||||||
|
$forcedTimelinePath = $this->request->getParam('timelinePath');
|
||||||
|
$userFolder = $this->rootFolder->getUserFolder($uid);
|
||||||
|
|
||||||
|
if (null !== $folderPath) {
|
||||||
|
$folder = $userFolder->get($folderPath);
|
||||||
|
} elseif (null !== $forcedTimelinePath) {
|
||||||
|
$folder = $userFolder->get($forcedTimelinePath);
|
||||||
|
} else {
|
||||||
|
$configPath = Exif::removeExtraSlash(Exif::getPhotosPath($this->config, $uid));
|
||||||
|
$folder = $userFolder->get($configPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$folder instanceof Folder) {
|
||||||
|
throw new \Exception('Folder not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getShareToken()
|
||||||
|
{
|
||||||
|
return $this->request->getParam('folder_share');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if albums are enabled for this user.
|
||||||
|
*/
|
||||||
|
protected function albumsIsEnabled(): bool
|
||||||
|
{
|
||||||
|
return \OCA\Memories\Util::albumsIsEnabled($this->appManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if tags is enabled for this user.
|
||||||
|
*/
|
||||||
|
protected function tagsIsEnabled(): bool
|
||||||
|
{
|
||||||
|
return \OCA\Memories\Util::tagsIsEnabled($this->appManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if recognize is enabled for this user.
|
||||||
|
*/
|
||||||
|
protected function recognizeIsEnabled(): bool
|
||||||
|
{
|
||||||
|
return \OCA\Memories\Util::recognizeIsEnabled($this->appManager);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,901 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
|
|
||||||
* @author John Molakvoæ <skjnldsv@protonmail.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\Controller;
|
|
||||||
|
|
||||||
use OCA\Memories\AppInfo\Application;
|
|
||||||
use OCA\Memories\Db\TimelineQuery;
|
|
||||||
use OCA\Memories\Db\TimelineWrite;
|
|
||||||
use OCA\Memories\Exif;
|
|
||||||
use OCP\App\IAppManager;
|
|
||||||
use OCP\AppFramework\Controller;
|
|
||||||
use OCP\AppFramework\Http;
|
|
||||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
|
||||||
use OCP\AppFramework\Http\DataDisplayResponse;
|
|
||||||
use OCP\AppFramework\Http\DataResponse;
|
|
||||||
use OCP\AppFramework\Http\JSONResponse;
|
|
||||||
use OCP\AppFramework\Http\StreamResponse;
|
|
||||||
use OCP\Files\FileInfo;
|
|
||||||
use OCP\Files\Folder;
|
|
||||||
use OCP\Files\IRootFolder;
|
|
||||||
use OCP\IConfig;
|
|
||||||
use OCP\IDBConnection;
|
|
||||||
use OCP\IPreview;
|
|
||||||
use OCP\IRequest;
|
|
||||||
use OCP\IUserSession;
|
|
||||||
use OCP\Share\IManager as IShareManager;
|
|
||||||
|
|
||||||
class ApiController extends Controller
|
|
||||||
{
|
|
||||||
private IConfig $config;
|
|
||||||
private IUserSession $userSession;
|
|
||||||
private IDBConnection $connection;
|
|
||||||
private IRootFolder $rootFolder;
|
|
||||||
private IAppManager $appManager;
|
|
||||||
private TimelineQuery $timelineQuery;
|
|
||||||
private TimelineWrite $timelineWrite;
|
|
||||||
private IShareManager $shareManager;
|
|
||||||
private IPreview $preview;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
IRequest $request,
|
|
||||||
IConfig $config,
|
|
||||||
IUserSession $userSession,
|
|
||||||
IDBConnection $connection,
|
|
||||||
IRootFolder $rootFolder,
|
|
||||||
IAppManager $appManager,
|
|
||||||
IShareManager $shareManager,
|
|
||||||
IPreview $preview
|
|
||||||
) {
|
|
||||||
parent::__construct(Application::APPNAME, $request);
|
|
||||||
|
|
||||||
$this->config = $config;
|
|
||||||
$this->userSession = $userSession;
|
|
||||||
$this->connection = $connection;
|
|
||||||
$this->rootFolder = $rootFolder;
|
|
||||||
$this->appManager = $appManager;
|
|
||||||
$this->shareManager = $shareManager;
|
|
||||||
$this->previewManager = $preview;
|
|
||||||
$this->timelineQuery = new TimelineQuery($this->connection);
|
|
||||||
$this->timelineWrite = new TimelineWrite($connection, $preview);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*
|
|
||||||
* @PublicPage
|
|
||||||
*/
|
|
||||||
public function days(): JSONResponse
|
|
||||||
{
|
|
||||||
// Get the folder to show
|
|
||||||
$uid = $this->getUid();
|
|
||||||
|
|
||||||
// Get the folder to show
|
|
||||||
$folder = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
$folder = $this->getRequestFolder();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Params
|
|
||||||
$recursive = null === $this->request->getParam('folder');
|
|
||||||
$archive = null !== $this->request->getParam('archive');
|
|
||||||
|
|
||||||
// Run actual query
|
|
||||||
try {
|
|
||||||
$list = $this->timelineQuery->getDays(
|
|
||||||
$folder,
|
|
||||||
$uid,
|
|
||||||
$recursive,
|
|
||||||
$archive,
|
|
||||||
$this->getTransformations(true),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Preload some day responses
|
|
||||||
$this->preloadDays($list, $uid, $folder, $recursive, $archive);
|
|
||||||
|
|
||||||
// Add subfolder info if querying non-recursively
|
|
||||||
if (!$recursive) {
|
|
||||||
array_unshift($list, $this->getSubfoldersEntry($folder));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JSONResponse($list, Http::STATUS_OK);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*
|
|
||||||
* @PublicPage
|
|
||||||
*/
|
|
||||||
public function dayPost(): JSONResponse
|
|
||||||
{
|
|
||||||
$id = $this->request->getParam('body_ids');
|
|
||||||
if (null === $id) {
|
|
||||||
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->day($id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*
|
|
||||||
* @PublicPage
|
|
||||||
*/
|
|
||||||
public function day(string $id): JSONResponse
|
|
||||||
{
|
|
||||||
// Get user
|
|
||||||
$uid = $this->getUid();
|
|
||||||
|
|
||||||
// Check for wildcard
|
|
||||||
$day_ids = [];
|
|
||||||
if ('*' === $id) {
|
|
||||||
$day_ids = null;
|
|
||||||
} else {
|
|
||||||
// Split at commas and convert all parts to int
|
|
||||||
$day_ids = array_map(function ($part) {
|
|
||||||
return (int) $part;
|
|
||||||
}, explode(',', $id));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if $day_ids is empty
|
|
||||||
if (null !== $day_ids && 0 === \count($day_ids)) {
|
|
||||||
return new JSONResponse([], Http::STATUS_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the folder to show
|
|
||||||
$folder = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
$folder = $this->getRequestFolder();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Params
|
|
||||||
$recursive = null === $this->request->getParam('folder');
|
|
||||||
$archive = null !== $this->request->getParam('archive');
|
|
||||||
|
|
||||||
// Run actual query
|
|
||||||
try {
|
|
||||||
$list = $this->timelineQuery->getDay(
|
|
||||||
$folder,
|
|
||||||
$uid,
|
|
||||||
$day_ids,
|
|
||||||
$recursive,
|
|
||||||
$archive,
|
|
||||||
$this->getTransformations(false),
|
|
||||||
);
|
|
||||||
|
|
||||||
return new JSONResponse($list, Http::STATUS_OK);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get subfolders entry for days response.
|
|
||||||
*/
|
|
||||||
public function getSubfoldersEntry(Folder &$folder)
|
|
||||||
{
|
|
||||||
// Ugly: get the view of the folder with reflection
|
|
||||||
// This is unfortunately the only way to get the contents of a folder
|
|
||||||
// matching a MIME type without using SEARCH, which is deep
|
|
||||||
$rp = new \ReflectionProperty('\OC\Files\Node\Node', 'view');
|
|
||||||
$rp->setAccessible(true);
|
|
||||||
$view = $rp->getValue($folder);
|
|
||||||
|
|
||||||
// Get the subfolders
|
|
||||||
$folders = $view->getDirectoryContent($folder->getPath(), FileInfo::MIMETYPE_FOLDER, $folder);
|
|
||||||
|
|
||||||
// Sort by name
|
|
||||||
usort($folders, function ($a, $b) {
|
|
||||||
return strnatcmp($a->getName(), $b->getName());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Process to response type
|
|
||||||
return [
|
|
||||||
'dayid' => \OCA\Memories\Util::$TAG_DAYID_FOLDERS,
|
|
||||||
'count' => \count($folders),
|
|
||||||
'detail' => array_map(function ($node) {
|
|
||||||
return [
|
|
||||||
'fileid' => $node->getId(),
|
|
||||||
'name' => $node->getName(),
|
|
||||||
'isfolder' => 1,
|
|
||||||
'path' => $node->getPath(),
|
|
||||||
];
|
|
||||||
}, $folders, []),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*
|
|
||||||
* Get list of tags with counts of images
|
|
||||||
*/
|
|
||||||
public function tags(): JSONResponse
|
|
||||||
{
|
|
||||||
$user = $this->userSession->getUser();
|
|
||||||
if (null === $user) {
|
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check tags enabled for this user
|
|
||||||
if (!$this->tagsIsEnabled()) {
|
|
||||||
return new JSONResponse(['message' => 'Tags not enabled for user'], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this isn't the timeline folder then things aren't going to work
|
|
||||||
$folder = $this->getRequestFolder();
|
|
||||||
if (null === $folder) {
|
|
||||||
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run actual query
|
|
||||||
$list = $this->timelineQuery->getTags(
|
|
||||||
$folder,
|
|
||||||
);
|
|
||||||
|
|
||||||
return new JSONResponse($list, Http::STATUS_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*
|
|
||||||
* Get previews for a tag
|
|
||||||
*/
|
|
||||||
public function tagPreviews(): JSONResponse
|
|
||||||
{
|
|
||||||
$user = $this->userSession->getUser();
|
|
||||||
if (null === $user) {
|
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check tags enabled for this user
|
|
||||||
if (!$this->tagsIsEnabled()) {
|
|
||||||
return new JSONResponse(['message' => 'Tags not enabled for user'], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this isn't the timeline folder then things aren't going to work
|
|
||||||
$folder = $this->getRequestFolder();
|
|
||||||
if (null === $folder) {
|
|
||||||
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the tag
|
|
||||||
$tagName = $this->request->getParam('tag');
|
|
||||||
|
|
||||||
// Run actual query
|
|
||||||
$list = $this->timelineQuery->getTagPreviews(
|
|
||||||
$tagName,
|
|
||||||
$folder,
|
|
||||||
);
|
|
||||||
|
|
||||||
return new JSONResponse($list, Http::STATUS_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*
|
|
||||||
* Get list of albums with counts of images
|
|
||||||
*/
|
|
||||||
public function albums(): JSONResponse
|
|
||||||
{
|
|
||||||
$user = $this->userSession->getUser();
|
|
||||||
if (null === $user) {
|
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check tags enabled for this user
|
|
||||||
if (!$this->albumsIsEnabled()) {
|
|
||||||
return new JSONResponse(['message' => 'Albums not enabled for user'], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run actual query
|
|
||||||
$list = [];
|
|
||||||
$t = (int) $this->request->getParam('t');
|
|
||||||
if ($t & 1) { // personal
|
|
||||||
$list = array_merge($list, $this->timelineQuery->getAlbums($user->getUID()));
|
|
||||||
}
|
|
||||||
if ($t & 2) { // shared
|
|
||||||
$list = array_merge($list, $this->timelineQuery->getAlbums($user->getUID(), true));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JSONResponse($list, Http::STATUS_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*
|
|
||||||
* Get list of faces with counts of images
|
|
||||||
*/
|
|
||||||
public function faces(): JSONResponse
|
|
||||||
{
|
|
||||||
$user = $this->userSession->getUser();
|
|
||||||
if (null === $user) {
|
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check faces enabled for this user
|
|
||||||
if (!$this->recognizeIsEnabled()) {
|
|
||||||
return new JSONResponse(['message' => 'Recognize app not enabled or not v3+'], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this isn't the timeline folder then things aren't going to work
|
|
||||||
$folder = $this->getRequestFolder();
|
|
||||||
if (null === $folder) {
|
|
||||||
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run actual query
|
|
||||||
$list = $this->timelineQuery->getFaces(
|
|
||||||
$folder,
|
|
||||||
);
|
|
||||||
|
|
||||||
return new JSONResponse($list, Http::STATUS_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*
|
|
||||||
* @NoCSRFRequired
|
|
||||||
*
|
|
||||||
* Get face preview image cropped with imagick
|
|
||||||
*
|
|
||||||
* @return DataResponse
|
|
||||||
*/
|
|
||||||
public function facePreview(string $id): Http\Response
|
|
||||||
{
|
|
||||||
$user = $this->userSession->getUser();
|
|
||||||
if (null === $user) {
|
|
||||||
return new DataResponse([], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check faces enabled for this user
|
|
||||||
if (!$this->recognizeIsEnabled()) {
|
|
||||||
return new DataResponse([], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get folder to search for
|
|
||||||
$folder = $this->getRequestFolder();
|
|
||||||
if (null === $folder) {
|
|
||||||
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run actual query
|
|
||||||
$detections = $this->timelineQuery->getFacePreviewDetection($folder, (int) $id);
|
|
||||||
if (null === $detections || 0 === \count($detections)) {
|
|
||||||
return new DataResponse([], Http::STATUS_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the first detection that has a preview
|
|
||||||
$preview = null;
|
|
||||||
foreach ($detections as &$detection) {
|
|
||||||
// Get the file (also checks permissions)
|
|
||||||
$files = $folder->getById($detection['file_id']);
|
|
||||||
if (0 === \count($files) || FileInfo::TYPE_FILE !== $files[0]->getType()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get (hopefully cached) preview image
|
|
||||||
try {
|
|
||||||
$preview = $this->previewManager->getPreview($files[0], 2048, 2048, false);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Got the preview
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the preview is valid
|
|
||||||
if (null === $preview) {
|
|
||||||
return new DataResponse([], Http::STATUS_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crop image
|
|
||||||
$image = new \Imagick();
|
|
||||||
$image->readImageBlob($preview->getContent());
|
|
||||||
$iw = $image->getImageWidth();
|
|
||||||
$ih = $image->getImageHeight();
|
|
||||||
$dw = (float) $detection['width'];
|
|
||||||
$dh = (float) $detection['height'];
|
|
||||||
$dcx = (float) $detection['x'] + (float) $detection['width'] / 2;
|
|
||||||
$dcy = (float) $detection['y'] + (float) $detection['height'] / 2;
|
|
||||||
$faceDim = max($dw * $iw, $dh * $ih) * 1.5;
|
|
||||||
$image->cropImage(
|
|
||||||
(int) $faceDim,
|
|
||||||
(int) $faceDim,
|
|
||||||
(int) ($dcx * $iw - $faceDim / 2),
|
|
||||||
(int) ($dcy * $ih - $faceDim / 2),
|
|
||||||
);
|
|
||||||
$image->scaleImage(256, 256, true);
|
|
||||||
$blob = $image->getImageBlob();
|
|
||||||
|
|
||||||
// Create and send response
|
|
||||||
$response = new DataDisplayResponse($blob, Http::STATUS_OK, [
|
|
||||||
'Content-Type' => $image->getImageMimeType(),
|
|
||||||
]);
|
|
||||||
$response->cacheFor(3600 * 24, false, false);
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*
|
|
||||||
* Get image info for one file
|
|
||||||
*
|
|
||||||
* @param string fileid
|
|
||||||
*/
|
|
||||||
public function imageInfo(string $id): JSONResponse
|
|
||||||
{
|
|
||||||
$user = $this->userSession->getUser();
|
|
||||||
if (null === $user) {
|
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
|
|
||||||
|
|
||||||
// Check for permissions and get numeric Id
|
|
||||||
$file = $userFolder->getById((int) $id);
|
|
||||||
if (0 === \count($file)) {
|
|
||||||
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
|
||||||
}
|
|
||||||
$file = $file[0];
|
|
||||||
|
|
||||||
// Get the image info
|
|
||||||
$info = $this->timelineQuery->getInfoById($file->getId());
|
|
||||||
|
|
||||||
return new JSONResponse($info, Http::STATUS_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*
|
|
||||||
* Change exif data for one file
|
|
||||||
*
|
|
||||||
* @param string fileid
|
|
||||||
*/
|
|
||||||
public function imageEdit(string $id): JSONResponse
|
|
||||||
{
|
|
||||||
$user = $this->userSession->getUser();
|
|
||||||
if (null === $user) {
|
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
|
|
||||||
|
|
||||||
// Check for permissions and get numeric Id
|
|
||||||
$file = $userFolder->getById((int) $id);
|
|
||||||
if (0 === \count($file)) {
|
|
||||||
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
|
||||||
}
|
|
||||||
$file = $file[0];
|
|
||||||
|
|
||||||
// Check if user has permissions
|
|
||||||
if (!$file->isUpdateable()) {
|
|
||||||
return new JSONResponse([], Http::STATUS_FORBIDDEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get new date from body
|
|
||||||
$body = $this->request->getParams();
|
|
||||||
if (!isset($body['date'])) {
|
|
||||||
return new JSONResponse(['message' => 'Missing date'], Http::STATUS_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the date is valid
|
|
||||||
try {
|
|
||||||
Exif::parseExifDate($body['date']);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update date
|
|
||||||
try {
|
|
||||||
$res = Exif::updateExifDate($file, $body['date']);
|
|
||||||
if (false === $res) {
|
|
||||||
return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reprocess the file
|
|
||||||
$this->timelineWrite->processFile($file, true);
|
|
||||||
|
|
||||||
return $this->imageInfo($id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*
|
|
||||||
* Move one file to the archive folder
|
|
||||||
*
|
|
||||||
* @param string fileid
|
|
||||||
*/
|
|
||||||
public function archive(string $id): JSONResponse
|
|
||||||
{
|
|
||||||
$user = $this->userSession->getUser();
|
|
||||||
if (null === $user) {
|
|
||||||
return new JSONResponse(['message' => 'Not logged in'], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
$uid = $user->getUID();
|
|
||||||
$userFolder = $this->rootFolder->getUserFolder($uid);
|
|
||||||
|
|
||||||
// Check for permissions and get numeric Id
|
|
||||||
$file = $userFolder->getById((int) $id);
|
|
||||||
if (0 === \count($file)) {
|
|
||||||
return new JSONResponse(['message' => 'No such file'], Http::STATUS_NOT_FOUND);
|
|
||||||
}
|
|
||||||
$file = $file[0];
|
|
||||||
|
|
||||||
// Check if user has permissions
|
|
||||||
if (!$file->isUpdateable()) {
|
|
||||||
return new JSONResponse(['message' => 'Cannot update this file'], Http::STATUS_FORBIDDEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create archive folder in the root of the user's configured timeline
|
|
||||||
$timelinePath = Exif::removeExtraSlash(Exif::getPhotosPath($this->config, $uid));
|
|
||||||
$timelineFolder = $userFolder->get($timelinePath);
|
|
||||||
if (null === $timelineFolder || !$timelineFolder instanceof Folder) {
|
|
||||||
return new JSONResponse(['message' => 'Cannot get timeline'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
if (!$timelineFolder->isCreatable()) {
|
|
||||||
return new JSONResponse(['message' => 'Cannot create archive folder'], Http::STATUS_FORBIDDEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get path of current file relative to the timeline folder
|
|
||||||
// remove timelineFolder path from start of file path
|
|
||||||
$timelinePath = $timelineFolder->getPath(); // no trailing slash
|
|
||||||
if (substr($file->getPath(), 0, \strlen($timelinePath)) !== $timelinePath) {
|
|
||||||
return new JSONResponse(['message' => 'Files outside timeline cannot be archived'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
$relativePath = substr($file->getPath(), \strlen($timelinePath)); // has a leading slash
|
|
||||||
|
|
||||||
// Final path of the file including the file name
|
|
||||||
$destinationPath = '';
|
|
||||||
|
|
||||||
// Check if we want to archive or unarchive
|
|
||||||
$body = $this->request->getParams();
|
|
||||||
$unarchive = isset($body['archive']) && false === $body['archive'];
|
|
||||||
|
|
||||||
// Get if the file is already in the archive (relativePath starts with archive)
|
|
||||||
$archiveFolderWithLeadingSlash = '/'.\OCA\Memories\Util::$ARCHIVE_FOLDER;
|
|
||||||
if (substr($relativePath, 0, \strlen($archiveFolderWithLeadingSlash)) === $archiveFolderWithLeadingSlash) {
|
|
||||||
// file already in archive, remove it instead
|
|
||||||
$destinationPath = substr($relativePath, \strlen($archiveFolderWithLeadingSlash));
|
|
||||||
if (!$unarchive) {
|
|
||||||
return new JSONResponse(['message' => 'File already archived'], Http::STATUS_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// file not in archive, put it in there
|
|
||||||
$destinationPath = Exif::removeExtraSlash(\OCA\Memories\Util::$ARCHIVE_FOLDER.$relativePath);
|
|
||||||
if ($unarchive) {
|
|
||||||
return new JSONResponse(['message' => 'File not archived'], Http::STATUS_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the filename
|
|
||||||
$destinationFolders = explode('/', $destinationPath);
|
|
||||||
array_pop($destinationFolders);
|
|
||||||
|
|
||||||
// Create folder tree
|
|
||||||
$folder = $timelineFolder;
|
|
||||||
foreach ($destinationFolders as $folderName) {
|
|
||||||
if ('' === $folderName) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$existingFolder = $folder->get($folderName.'/');
|
|
||||||
if (!$existingFolder instanceof Folder) {
|
|
||||||
throw new \OCP\Files\NotFoundException('Not a folder');
|
|
||||||
}
|
|
||||||
$folder = $existingFolder;
|
|
||||||
} catch (\OCP\Files\NotFoundException $e) {
|
|
||||||
try {
|
|
||||||
$folder = $folder->newFolder($folderName);
|
|
||||||
} catch (\OCP\Files\NotPermittedException $e) {
|
|
||||||
return new JSONResponse(['message' => 'Failed to create folder'], Http::STATUS_FORBIDDEN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move file to archive folder
|
|
||||||
try {
|
|
||||||
$file->move($folder->getPath().'/'.$file->getName());
|
|
||||||
} catch (\OCP\Files\NotPermittedException $e) {
|
|
||||||
return new JSONResponse(['message' => 'Failed to move file'], Http::STATUS_FORBIDDEN);
|
|
||||||
} catch (\OCP\Files\NotFoundException $e) {
|
|
||||||
return new JSONResponse(['message' => 'File not found'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
|
||||||
} catch (\OCP\Files\InvalidPathException $e) {
|
|
||||||
return new JSONResponse(['message' => 'Invalid path'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
|
||||||
} catch (\OCP\Lock\LockedException $e) {
|
|
||||||
return new JSONResponse(['message' => 'File is locked'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JSONResponse([], Http::STATUS_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*
|
|
||||||
* update preferences (user setting)
|
|
||||||
*
|
|
||||||
* @param string key the identifier to change
|
|
||||||
* @param string value the value to set
|
|
||||||
*
|
|
||||||
* @return JSONResponse an empty JSONResponse with respective http status code
|
|
||||||
*/
|
|
||||||
public function setUserConfig(string $key, string $value): JSONResponse
|
|
||||||
{
|
|
||||||
$user = $this->userSession->getUser();
|
|
||||||
if (null === $user) {
|
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure not running in read-only mode
|
|
||||||
if ($this->config->getSystemValue('memories.readonly', false)) {
|
|
||||||
return new JSONResponse(['message' => 'Cannot change settings in readonly mode'], Http::STATUS_FORBIDDEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
$userId = $user->getUid();
|
|
||||||
$this->config->setUserValue($userId, Application::APPNAME, $key, $value);
|
|
||||||
|
|
||||||
return new JSONResponse([], Http::STATUS_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*
|
|
||||||
* @PublicPage
|
|
||||||
*
|
|
||||||
* @NoCSRFRequired
|
|
||||||
*/
|
|
||||||
public function serviceWorker(): StreamResponse
|
|
||||||
{
|
|
||||||
$response = new StreamResponse(__DIR__.'/../../js/memories-service-worker.js');
|
|
||||||
$response->setHeaders([
|
|
||||||
'Content-Type' => 'application/javascript',
|
|
||||||
'Service-Worker-Allowed' => '/',
|
|
||||||
]);
|
|
||||||
$policy = new ContentSecurityPolicy();
|
|
||||||
$policy->addAllowedWorkerSrcDomain("'self'");
|
|
||||||
$policy->addAllowedScriptDomain("'self'");
|
|
||||||
$policy->addAllowedConnectDomain("'self'");
|
|
||||||
$response->setContentSecurityPolicy($policy);
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get logged in user's UID or throw HTTP error */
|
|
||||||
private function getUid(): string
|
|
||||||
{
|
|
||||||
$user = $this->userSession->getUser();
|
|
||||||
if ($this->getShareToken()) {
|
|
||||||
$user = null;
|
|
||||||
} elseif (null === $user) {
|
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $user ? $user->getUID() : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get transformations depending on the request.
|
|
||||||
*
|
|
||||||
* @param bool $aggregateOnly Only apply transformations for aggregation (days call)
|
|
||||||
*/
|
|
||||||
private function getTransformations(bool $aggregateOnly)
|
|
||||||
{
|
|
||||||
$transforms = [];
|
|
||||||
|
|
||||||
// Add extra information, basename and mimetype
|
|
||||||
if (!$aggregateOnly && ($fields = $this->request->getParam('fields'))) {
|
|
||||||
$fields = explode(',', $fields);
|
|
||||||
$transforms[] = [$this->timelineQuery, 'transformExtraFields', $fields];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Other transforms not allowed for public shares
|
|
||||||
if (null === $this->userSession->getUser()) {
|
|
||||||
return $transforms;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter only favorites
|
|
||||||
if ($this->request->getParam('fav')) {
|
|
||||||
$transforms[] = [$this->timelineQuery, 'transformFavoriteFilter'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter only videos
|
|
||||||
if ($this->request->getParam('vid')) {
|
|
||||||
$transforms[] = [$this->timelineQuery, 'transformVideoFilter'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter only for one face
|
|
||||||
if ($this->recognizeIsEnabled()) {
|
|
||||||
$face = $this->request->getParam('face');
|
|
||||||
if ($face) {
|
|
||||||
$transforms[] = [$this->timelineQuery, 'transformFaceFilter', $face];
|
|
||||||
}
|
|
||||||
|
|
||||||
$faceRect = $this->request->getParam('facerect');
|
|
||||||
if ($faceRect && !$aggregateOnly) {
|
|
||||||
$transforms[] = [$this->timelineQuery, 'transformFaceRect', $face];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter only for one tag
|
|
||||||
if ($this->tagsIsEnabled()) {
|
|
||||||
if ($tagName = $this->request->getParam('tag')) {
|
|
||||||
$transforms[] = [$this->timelineQuery, 'transformTagFilter', $tagName];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter for one album
|
|
||||||
if ($this->albumsIsEnabled()) {
|
|
||||||
if ($albumId = $this->request->getParam('album')) {
|
|
||||||
$transforms[] = [$this->timelineQuery, 'transformAlbumFilter', $albumId];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limit number of responses for day query
|
|
||||||
$limit = $this->request->getParam('limit');
|
|
||||||
if ($limit) {
|
|
||||||
$transforms[] = [$this->timelineQuery, 'transformLimitDay', (int) $limit];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $transforms;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Preload a few "day" at the start of "days" response.
|
|
||||||
*
|
|
||||||
* @param array $days the days array
|
|
||||||
* @param string $uid User ID or blank for public shares
|
|
||||||
* @param null|Folder $folder the folder to search in
|
|
||||||
* @param bool $recursive search in subfolders
|
|
||||||
* @param bool $archive search in archive folder only
|
|
||||||
*/
|
|
||||||
private function preloadDays(array &$days, string $uid, &$folder, bool $recursive, bool $archive)
|
|
||||||
{
|
|
||||||
$transforms = $this->getTransformations(false);
|
|
||||||
$preloaded = 0;
|
|
||||||
$preloadDayIds = [];
|
|
||||||
$preloadDays = [];
|
|
||||||
foreach ($days as &$day) {
|
|
||||||
if ($day['count'] <= 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$preloaded += $day['count'];
|
|
||||||
$preloadDayIds[] = $day['dayid'];
|
|
||||||
$preloadDays[] = &$day;
|
|
||||||
|
|
||||||
if ($preloaded >= 50 || \count($preloadDayIds) > 5) { // should be enough
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (\count($preloadDayIds) > 0) {
|
|
||||||
$allDetails = $this->timelineQuery->getDay(
|
|
||||||
$folder,
|
|
||||||
$uid,
|
|
||||||
$preloadDayIds,
|
|
||||||
$recursive,
|
|
||||||
$archive,
|
|
||||||
$transforms,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Group into dayid
|
|
||||||
$detailMap = [];
|
|
||||||
foreach ($allDetails as &$detail) {
|
|
||||||
$detailMap[$detail['dayid']][] = &$detail;
|
|
||||||
}
|
|
||||||
foreach ($preloadDays as &$day) {
|
|
||||||
$m = $detailMap[$day['dayid']];
|
|
||||||
if (isset($m) && null !== $m && \count($m) > 0) {
|
|
||||||
$day['detail'] = $m;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the Folder object relevant to the request */
|
|
||||||
private function getRequestFolder()
|
|
||||||
{
|
|
||||||
// Albums have no folder
|
|
||||||
if ($this->request->getParam('album')) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public shared folder
|
|
||||||
if ($token = $this->getShareToken()) {
|
|
||||||
$share = $this->shareManager->getShareByToken($token)->getNode(); // throws exception if not found
|
|
||||||
if (!$share instanceof Folder) {
|
|
||||||
throw new \Exception('Share not found or invalid');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $share;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Anything else needs a user
|
|
||||||
$user = $this->userSession->getUser();
|
|
||||||
if (null === $user) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
$uid = $user->getUID();
|
|
||||||
|
|
||||||
$folder = null;
|
|
||||||
$folderPath = $this->request->getParam('folder');
|
|
||||||
$forcedTimelinePath = $this->request->getParam('timelinePath');
|
|
||||||
$userFolder = $this->rootFolder->getUserFolder($uid);
|
|
||||||
|
|
||||||
if (null !== $folderPath) {
|
|
||||||
$folder = $userFolder->get($folderPath);
|
|
||||||
} elseif (null !== $forcedTimelinePath) {
|
|
||||||
$folder = $userFolder->get($forcedTimelinePath);
|
|
||||||
} else {
|
|
||||||
$configPath = Exif::removeExtraSlash(Exif::getPhotosPath($this->config, $uid));
|
|
||||||
$folder = $userFolder->get($configPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$folder instanceof Folder) {
|
|
||||||
throw new \Exception('Folder not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $folder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getShareToken()
|
|
||||||
{
|
|
||||||
return $this->request->getParam('folder_share');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if albums are enabled for this user.
|
|
||||||
*/
|
|
||||||
private function albumsIsEnabled(): bool
|
|
||||||
{
|
|
||||||
return \OCA\Memories\Util::albumsIsEnabled($this->appManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if tags is enabled for this user.
|
|
||||||
*/
|
|
||||||
private function tagsIsEnabled(): bool
|
|
||||||
{
|
|
||||||
return \OCA\Memories\Util::tagsIsEnabled($this->appManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if recognize is enabled for this user.
|
|
||||||
*/
|
|
||||||
private function recognizeIsEnabled(): bool
|
|
||||||
{
|
|
||||||
return \OCA\Memories\Util::recognizeIsEnabled($this->appManager);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use OCA\Memories\Exif;
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
use OCP\Files\Folder;
|
||||||
|
|
||||||
|
class ArchiveController extends ApiBase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* Move one file to the archive folder
|
||||||
|
*
|
||||||
|
* @param string fileid
|
||||||
|
*/
|
||||||
|
public function archive(string $id): JSONResponse
|
||||||
|
{
|
||||||
|
$user = $this->userSession->getUser();
|
||||||
|
if (null === $user) {
|
||||||
|
return new JSONResponse(['message' => 'Not logged in'], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
$uid = $user->getUID();
|
||||||
|
$userFolder = $this->rootFolder->getUserFolder($uid);
|
||||||
|
|
||||||
|
// Check for permissions and get numeric Id
|
||||||
|
$file = $userFolder->getById((int) $id);
|
||||||
|
if (0 === \count($file)) {
|
||||||
|
return new JSONResponse(['message' => 'No such file'], Http::STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
$file = $file[0];
|
||||||
|
|
||||||
|
// Check if user has permissions
|
||||||
|
if (!$file->isUpdateable()) {
|
||||||
|
return new JSONResponse(['message' => 'Cannot update this file'], Http::STATUS_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create archive folder in the root of the user's configured timeline
|
||||||
|
$timelinePath = Exif::removeExtraSlash(Exif::getPhotosPath($this->config, $uid));
|
||||||
|
$timelineFolder = $userFolder->get($timelinePath);
|
||||||
|
if (null === $timelineFolder || !$timelineFolder instanceof Folder) {
|
||||||
|
return new JSONResponse(['message' => 'Cannot get timeline'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
if (!$timelineFolder->isCreatable()) {
|
||||||
|
return new JSONResponse(['message' => 'Cannot create archive folder'], Http::STATUS_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get path of current file relative to the timeline folder
|
||||||
|
// remove timelineFolder path from start of file path
|
||||||
|
$timelinePath = $timelineFolder->getPath(); // no trailing slash
|
||||||
|
if (substr($file->getPath(), 0, \strlen($timelinePath)) !== $timelinePath) {
|
||||||
|
return new JSONResponse(['message' => 'Files outside timeline cannot be archived'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
$relativePath = substr($file->getPath(), \strlen($timelinePath)); // has a leading slash
|
||||||
|
|
||||||
|
// Final path of the file including the file name
|
||||||
|
$destinationPath = '';
|
||||||
|
|
||||||
|
// Check if we want to archive or unarchive
|
||||||
|
$body = $this->request->getParams();
|
||||||
|
$unarchive = isset($body['archive']) && false === $body['archive'];
|
||||||
|
|
||||||
|
// Get if the file is already in the archive (relativePath starts with archive)
|
||||||
|
$archiveFolderWithLeadingSlash = '/'.\OCA\Memories\Util::$ARCHIVE_FOLDER;
|
||||||
|
if (substr($relativePath, 0, \strlen($archiveFolderWithLeadingSlash)) === $archiveFolderWithLeadingSlash) {
|
||||||
|
// file already in archive, remove it instead
|
||||||
|
$destinationPath = substr($relativePath, \strlen($archiveFolderWithLeadingSlash));
|
||||||
|
if (!$unarchive) {
|
||||||
|
return new JSONResponse(['message' => 'File already archived'], Http::STATUS_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// file not in archive, put it in there
|
||||||
|
$destinationPath = Exif::removeExtraSlash(\OCA\Memories\Util::$ARCHIVE_FOLDER.$relativePath);
|
||||||
|
if ($unarchive) {
|
||||||
|
return new JSONResponse(['message' => 'File not archived'], Http::STATUS_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the filename
|
||||||
|
$destinationFolders = explode('/', $destinationPath);
|
||||||
|
array_pop($destinationFolders);
|
||||||
|
|
||||||
|
// Create folder tree
|
||||||
|
$folder = $timelineFolder;
|
||||||
|
foreach ($destinationFolders as $folderName) {
|
||||||
|
if ('' === $folderName) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$existingFolder = $folder->get($folderName.'/');
|
||||||
|
if (!$existingFolder instanceof Folder) {
|
||||||
|
throw new \OCP\Files\NotFoundException('Not a folder');
|
||||||
|
}
|
||||||
|
$folder = $existingFolder;
|
||||||
|
} catch (\OCP\Files\NotFoundException $e) {
|
||||||
|
try {
|
||||||
|
$folder = $folder->newFolder($folderName);
|
||||||
|
} catch (\OCP\Files\NotPermittedException $e) {
|
||||||
|
return new JSONResponse(['message' => 'Failed to create folder'], Http::STATUS_FORBIDDEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move file to archive folder
|
||||||
|
try {
|
||||||
|
$file->move($folder->getPath().'/'.$file->getName());
|
||||||
|
} catch (\OCP\Files\NotPermittedException $e) {
|
||||||
|
return new JSONResponse(['message' => 'Failed to move file'], Http::STATUS_FORBIDDEN);
|
||||||
|
} catch (\OCP\Files\NotFoundException $e) {
|
||||||
|
return new JSONResponse(['message' => 'File not found'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||||
|
} catch (\OCP\Files\InvalidPathException $e) {
|
||||||
|
return new JSONResponse(['message' => 'Invalid path'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||||
|
} catch (\OCP\Lock\LockedException $e) {
|
||||||
|
return new JSONResponse(['message' => 'File is locked'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JSONResponse([], Http::STATUS_OK);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,304 @@
|
||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
use OCP\Files\FileInfo;
|
||||||
|
use OCP\Files\Folder;
|
||||||
|
|
||||||
|
class DaysController extends ApiBase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @PublicPage
|
||||||
|
*/
|
||||||
|
public function days(): JSONResponse
|
||||||
|
{
|
||||||
|
// Get the folder to show
|
||||||
|
$uid = $this->getUid();
|
||||||
|
|
||||||
|
// Get the folder to show
|
||||||
|
$folder = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$folder = $this->getRequestFolder();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Params
|
||||||
|
$recursive = null === $this->request->getParam('folder');
|
||||||
|
$archive = null !== $this->request->getParam('archive');
|
||||||
|
|
||||||
|
// Run actual query
|
||||||
|
try {
|
||||||
|
$list = $this->timelineQuery->getDays(
|
||||||
|
$folder,
|
||||||
|
$uid,
|
||||||
|
$recursive,
|
||||||
|
$archive,
|
||||||
|
$this->getTransformations(true),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Preload some day responses
|
||||||
|
$this->preloadDays($list, $uid, $folder, $recursive, $archive);
|
||||||
|
|
||||||
|
// Add subfolder info if querying non-recursively
|
||||||
|
if (!$recursive) {
|
||||||
|
array_unshift($list, $this->getSubfoldersEntry($folder));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JSONResponse($list, Http::STATUS_OK);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @PublicPage
|
||||||
|
*/
|
||||||
|
public function dayPost(): JSONResponse
|
||||||
|
{
|
||||||
|
$id = $this->request->getParam('body_ids');
|
||||||
|
if (null === $id) {
|
||||||
|
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->day($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @PublicPage
|
||||||
|
*/
|
||||||
|
public function day(string $id): JSONResponse
|
||||||
|
{
|
||||||
|
// Get user
|
||||||
|
$uid = $this->getUid();
|
||||||
|
|
||||||
|
// Check for wildcard
|
||||||
|
$day_ids = [];
|
||||||
|
if ('*' === $id) {
|
||||||
|
$day_ids = null;
|
||||||
|
} else {
|
||||||
|
// Split at commas and convert all parts to int
|
||||||
|
$day_ids = array_map(function ($part) {
|
||||||
|
return (int) $part;
|
||||||
|
}, explode(',', $id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if $day_ids is empty
|
||||||
|
if (null !== $day_ids && 0 === \count($day_ids)) {
|
||||||
|
return new JSONResponse([], Http::STATUS_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the folder to show
|
||||||
|
$folder = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$folder = $this->getRequestFolder();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Params
|
||||||
|
$recursive = null === $this->request->getParam('folder');
|
||||||
|
$archive = null !== $this->request->getParam('archive');
|
||||||
|
|
||||||
|
// Run actual query
|
||||||
|
try {
|
||||||
|
$list = $this->timelineQuery->getDay(
|
||||||
|
$folder,
|
||||||
|
$uid,
|
||||||
|
$day_ids,
|
||||||
|
$recursive,
|
||||||
|
$archive,
|
||||||
|
$this->getTransformations(false),
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JSONResponse($list, Http::STATUS_OK);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get subfolders entry for days response.
|
||||||
|
*/
|
||||||
|
public function getSubfoldersEntry(Folder &$folder)
|
||||||
|
{
|
||||||
|
// Ugly: get the view of the folder with reflection
|
||||||
|
// This is unfortunately the only way to get the contents of a folder
|
||||||
|
// matching a MIME type without using SEARCH, which is deep
|
||||||
|
$rp = new \ReflectionProperty('\OC\Files\Node\Node', 'view');
|
||||||
|
$rp->setAccessible(true);
|
||||||
|
$view = $rp->getValue($folder);
|
||||||
|
|
||||||
|
// Get the subfolders
|
||||||
|
$folders = $view->getDirectoryContent($folder->getPath(), FileInfo::MIMETYPE_FOLDER, $folder);
|
||||||
|
|
||||||
|
// Sort by name
|
||||||
|
usort($folders, function ($a, $b) {
|
||||||
|
return strnatcmp($a->getName(), $b->getName());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process to response type
|
||||||
|
return [
|
||||||
|
'dayid' => \OCA\Memories\Util::$TAG_DAYID_FOLDERS,
|
||||||
|
'count' => \count($folders),
|
||||||
|
'detail' => array_map(function ($node) {
|
||||||
|
return [
|
||||||
|
'fileid' => $node->getId(),
|
||||||
|
'name' => $node->getName(),
|
||||||
|
'isfolder' => 1,
|
||||||
|
'path' => $node->getPath(),
|
||||||
|
];
|
||||||
|
}, $folders, []),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get transformations depending on the request.
|
||||||
|
*
|
||||||
|
* @param bool $aggregateOnly Only apply transformations for aggregation (days call)
|
||||||
|
*/
|
||||||
|
private function getTransformations(bool $aggregateOnly)
|
||||||
|
{
|
||||||
|
$transforms = [];
|
||||||
|
|
||||||
|
// Add extra information, basename and mimetype
|
||||||
|
if (!$aggregateOnly && ($fields = $this->request->getParam('fields'))) {
|
||||||
|
$fields = explode(',', $fields);
|
||||||
|
$transforms[] = [$this->timelineQuery, 'transformExtraFields', $fields];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other transforms not allowed for public shares
|
||||||
|
if (null === $this->userSession->getUser()) {
|
||||||
|
return $transforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter only favorites
|
||||||
|
if ($this->request->getParam('fav')) {
|
||||||
|
$transforms[] = [$this->timelineQuery, 'transformFavoriteFilter'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter only videos
|
||||||
|
if ($this->request->getParam('vid')) {
|
||||||
|
$transforms[] = [$this->timelineQuery, 'transformVideoFilter'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter only for one face
|
||||||
|
if ($this->recognizeIsEnabled()) {
|
||||||
|
$face = $this->request->getParam('face');
|
||||||
|
if ($face) {
|
||||||
|
$transforms[] = [$this->timelineQuery, 'transformFaceFilter', $face];
|
||||||
|
}
|
||||||
|
|
||||||
|
$faceRect = $this->request->getParam('facerect');
|
||||||
|
if ($faceRect && !$aggregateOnly) {
|
||||||
|
$transforms[] = [$this->timelineQuery, 'transformFaceRect', $face];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter only for one tag
|
||||||
|
if ($this->tagsIsEnabled()) {
|
||||||
|
if ($tagName = $this->request->getParam('tag')) {
|
||||||
|
$transforms[] = [$this->timelineQuery, 'transformTagFilter', $tagName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter for one album
|
||||||
|
if ($this->albumsIsEnabled()) {
|
||||||
|
if ($albumId = $this->request->getParam('album')) {
|
||||||
|
$transforms[] = [$this->timelineQuery, 'transformAlbumFilter', $albumId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit number of responses for day query
|
||||||
|
$limit = $this->request->getParam('limit');
|
||||||
|
if ($limit) {
|
||||||
|
$transforms[] = [$this->timelineQuery, 'transformLimitDay', (int) $limit];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preload a few "day" at the start of "days" response.
|
||||||
|
*
|
||||||
|
* @param array $days the days array
|
||||||
|
* @param string $uid User ID or blank for public shares
|
||||||
|
* @param null|Folder $folder the folder to search in
|
||||||
|
* @param bool $recursive search in subfolders
|
||||||
|
* @param bool $archive search in archive folder only
|
||||||
|
*/
|
||||||
|
private function preloadDays(array &$days, string $uid, &$folder, bool $recursive, bool $archive)
|
||||||
|
{
|
||||||
|
$transforms = $this->getTransformations(false);
|
||||||
|
$preloaded = 0;
|
||||||
|
$preloadDayIds = [];
|
||||||
|
$preloadDays = [];
|
||||||
|
foreach ($days as &$day) {
|
||||||
|
if ($day['count'] <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$preloaded += $day['count'];
|
||||||
|
$preloadDayIds[] = $day['dayid'];
|
||||||
|
$preloadDays[] = &$day;
|
||||||
|
|
||||||
|
if ($preloaded >= 50 || \count($preloadDayIds) > 5) { // should be enough
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\count($preloadDayIds) > 0) {
|
||||||
|
$allDetails = $this->timelineQuery->getDay(
|
||||||
|
$folder,
|
||||||
|
$uid,
|
||||||
|
$preloadDayIds,
|
||||||
|
$recursive,
|
||||||
|
$archive,
|
||||||
|
$transforms,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Group into dayid
|
||||||
|
$detailMap = [];
|
||||||
|
foreach ($allDetails as &$detail) {
|
||||||
|
$detailMap[$detail['dayid']][] = &$detail;
|
||||||
|
}
|
||||||
|
foreach ($preloadDays as &$day) {
|
||||||
|
$m = $detailMap[$day['dayid']];
|
||||||
|
if (isset($m) && null !== $m && \count($m) > 0) {
|
||||||
|
$day['detail'] = $m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\DataDisplayResponse;
|
||||||
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
use OCP\Files\FileInfo;
|
||||||
|
|
||||||
|
class FacesController extends ApiBase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* Get list of faces with counts of images
|
||||||
|
*/
|
||||||
|
public function faces(): JSONResponse
|
||||||
|
{
|
||||||
|
$user = $this->userSession->getUser();
|
||||||
|
if (null === $user) {
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check faces enabled for this user
|
||||||
|
if (!$this->recognizeIsEnabled()) {
|
||||||
|
return new JSONResponse(['message' => 'Recognize app not enabled or not v3+'], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this isn't the timeline folder then things aren't going to work
|
||||||
|
$folder = $this->getRequestFolder();
|
||||||
|
if (null === $folder) {
|
||||||
|
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run actual query
|
||||||
|
$list = $this->timelineQuery->getFaces(
|
||||||
|
$folder,
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JSONResponse($list, Http::STATUS_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* Get face preview image cropped with imagick
|
||||||
|
*
|
||||||
|
* @return DataResponse
|
||||||
|
*/
|
||||||
|
public function preview(string $id): Http\Response
|
||||||
|
{
|
||||||
|
$user = $this->userSession->getUser();
|
||||||
|
if (null === $user) {
|
||||||
|
return new DataResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check faces enabled for this user
|
||||||
|
if (!$this->recognizeIsEnabled()) {
|
||||||
|
return new DataResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get folder to search for
|
||||||
|
$folder = $this->getRequestFolder();
|
||||||
|
if (null === $folder) {
|
||||||
|
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run actual query
|
||||||
|
$detections = $this->timelineQuery->getFacePreviewDetection($folder, (int) $id);
|
||||||
|
if (null === $detections || 0 === \count($detections)) {
|
||||||
|
return new DataResponse([], Http::STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first detection that has a preview
|
||||||
|
$preview = null;
|
||||||
|
foreach ($detections as &$detection) {
|
||||||
|
// Get the file (also checks permissions)
|
||||||
|
$files = $folder->getById($detection['file_id']);
|
||||||
|
if (0 === \count($files) || FileInfo::TYPE_FILE !== $files[0]->getType()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get (hopefully cached) preview image
|
||||||
|
try {
|
||||||
|
$preview = $this->previewManager->getPreview($files[0], 2048, 2048, false);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Got the preview
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the preview is valid
|
||||||
|
if (null === $preview) {
|
||||||
|
return new DataResponse([], Http::STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crop image
|
||||||
|
$image = new \Imagick();
|
||||||
|
$image->readImageBlob($preview->getContent());
|
||||||
|
$iw = $image->getImageWidth();
|
||||||
|
$ih = $image->getImageHeight();
|
||||||
|
$dw = (float) $detection['width'];
|
||||||
|
$dh = (float) $detection['height'];
|
||||||
|
$dcx = (float) $detection['x'] + (float) $detection['width'] / 2;
|
||||||
|
$dcy = (float) $detection['y'] + (float) $detection['height'] / 2;
|
||||||
|
$faceDim = max($dw * $iw, $dh * $ih) * 1.5;
|
||||||
|
$image->cropImage(
|
||||||
|
(int) $faceDim,
|
||||||
|
(int) $faceDim,
|
||||||
|
(int) ($dcx * $iw - $faceDim / 2),
|
||||||
|
(int) ($dcy * $ih - $faceDim / 2),
|
||||||
|
);
|
||||||
|
$image->scaleImage(256, 256, true);
|
||||||
|
$blob = $image->getImageBlob();
|
||||||
|
|
||||||
|
// Create and send response
|
||||||
|
$response = new DataDisplayResponse($blob, Http::STATUS_OK, [
|
||||||
|
'Content-Type' => $image->getImageMimeType(),
|
||||||
|
]);
|
||||||
|
$response->cacheFor(3600 * 24, false, false);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use OCA\Memories\Exif;
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
|
||||||
|
class ImageController extends ApiBase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* Get image info for one file
|
||||||
|
*
|
||||||
|
* @param string fileid
|
||||||
|
*/
|
||||||
|
public function info(string $id): JSONResponse
|
||||||
|
{
|
||||||
|
$user = $this->userSession->getUser();
|
||||||
|
if (null === $user) {
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
|
||||||
|
|
||||||
|
// Check for permissions and get numeric Id
|
||||||
|
$file = $userFolder->getById((int) $id);
|
||||||
|
if (0 === \count($file)) {
|
||||||
|
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
$file = $file[0];
|
||||||
|
|
||||||
|
// Get the image info
|
||||||
|
$info = $this->timelineQuery->getInfoById($file->getId());
|
||||||
|
|
||||||
|
return new JSONResponse($info, Http::STATUS_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* Change exif data for one file
|
||||||
|
*
|
||||||
|
* @param string fileid
|
||||||
|
*/
|
||||||
|
public function edit(string $id): JSONResponse
|
||||||
|
{
|
||||||
|
$user = $this->userSession->getUser();
|
||||||
|
if (null === $user) {
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
|
||||||
|
|
||||||
|
// Check for permissions and get numeric Id
|
||||||
|
$file = $userFolder->getById((int) $id);
|
||||||
|
if (0 === \count($file)) {
|
||||||
|
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
$file = $file[0];
|
||||||
|
|
||||||
|
// Check if user has permissions
|
||||||
|
if (!$file->isUpdateable()) {
|
||||||
|
return new JSONResponse([], Http::STATUS_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get new date from body
|
||||||
|
$body = $this->request->getParams();
|
||||||
|
if (!isset($body['date'])) {
|
||||||
|
return new JSONResponse(['message' => 'Missing date'], Http::STATUS_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the date is valid
|
||||||
|
try {
|
||||||
|
Exif::parseExifDate($body['date']);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update date
|
||||||
|
try {
|
||||||
|
$res = Exif::updateExifDate($file, $body['date']);
|
||||||
|
if (false === $res) {
|
||||||
|
return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reprocess the file
|
||||||
|
$this->timelineWrite->processFile($file, true);
|
||||||
|
|
||||||
|
return $this->info($id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use OCA\Memories\AppInfo\Application;
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
use OCP\AppFramework\Http\StreamResponse;
|
||||||
|
|
||||||
|
class OtherController extends ApiBase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* update preferences (user setting)
|
||||||
|
*
|
||||||
|
* @param string key the identifier to change
|
||||||
|
* @param string value the value to set
|
||||||
|
*
|
||||||
|
* @return JSONResponse an empty JSONResponse with respective http status code
|
||||||
|
*/
|
||||||
|
public function setUserConfig(string $key, string $value): JSONResponse
|
||||||
|
{
|
||||||
|
$user = $this->userSession->getUser();
|
||||||
|
if (null === $user) {
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure not running in read-only mode
|
||||||
|
if ($this->config->getSystemValue('memories.readonly', false)) {
|
||||||
|
return new JSONResponse(['message' => 'Cannot change settings in readonly mode'], Http::STATUS_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
$userId = $user->getUid();
|
||||||
|
$this->config->setUserValue($userId, Application::APPNAME, $key, $value);
|
||||||
|
|
||||||
|
return new JSONResponse([], Http::STATUS_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @PublicPage
|
||||||
|
*
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*/
|
||||||
|
public function serviceWorker(): StreamResponse
|
||||||
|
{
|
||||||
|
$response = new StreamResponse(__DIR__.'/../../js/memories-service-worker.js');
|
||||||
|
$response->setHeaders([
|
||||||
|
'Content-Type' => 'application/javascript',
|
||||||
|
'Service-Worker-Allowed' => '/',
|
||||||
|
]);
|
||||||
|
$policy = new ContentSecurityPolicy();
|
||||||
|
$policy->addAllowedWorkerSrcDomain("'self'");
|
||||||
|
$policy->addAllowedScriptDomain("'self'");
|
||||||
|
$policy->addAllowedConnectDomain("'self'");
|
||||||
|
$response->setContentSecurityPolicy($policy);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
|
@ -109,7 +109,7 @@ class PageController extends Controller
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function sharedFolder(string $token)
|
public function sharedfolder(string $token)
|
||||||
{
|
{
|
||||||
// Scripts
|
// Scripts
|
||||||
Util::addScript($this->appName, 'memories-main');
|
Util::addScript($this->appName, 'memories-main');
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
|
||||||
|
class TagsController extends ApiBase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* Get list of tags with counts of images
|
||||||
|
*/
|
||||||
|
public function tags(): JSONResponse
|
||||||
|
{
|
||||||
|
$user = $this->userSession->getUser();
|
||||||
|
if (null === $user) {
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check tags enabled for this user
|
||||||
|
if (!$this->tagsIsEnabled()) {
|
||||||
|
return new JSONResponse(['message' => 'Tags not enabled for user'], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this isn't the timeline folder then things aren't going to work
|
||||||
|
$folder = $this->getRequestFolder();
|
||||||
|
if (null === $folder) {
|
||||||
|
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run actual query
|
||||||
|
$list = $this->timelineQuery->getTags(
|
||||||
|
$folder,
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JSONResponse($list, Http::STATUS_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* Get previews for a tag
|
||||||
|
*/
|
||||||
|
public function previews(): JSONResponse
|
||||||
|
{
|
||||||
|
$user = $this->userSession->getUser();
|
||||||
|
if (null === $user) {
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check tags enabled for this user
|
||||||
|
if (!$this->tagsIsEnabled()) {
|
||||||
|
return new JSONResponse(['message' => 'Tags not enabled for user'], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this isn't the timeline folder then things aren't going to work
|
||||||
|
$folder = $this->getRequestFolder();
|
||||||
|
if (null === $folder) {
|
||||||
|
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the tag
|
||||||
|
$tagName = $this->request->getParam('tag');
|
||||||
|
|
||||||
|
// Run actual query
|
||||||
|
$list = $this->timelineQuery->getTagPreviews(
|
||||||
|
$tagName,
|
||||||
|
$folder,
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JSONResponse($list, Http::STATUS_OK);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue