diff --git a/lib/ClustersBackend/AlbumsBackend.php b/lib/ClustersBackend/AlbumsBackend.php index b71aee32..2de3286e 100644 --- a/lib/ClustersBackend/AlbumsBackend.php +++ b/lib/ClustersBackend/AlbumsBackend.php @@ -24,8 +24,7 @@ declare(strict_types=1); namespace OCA\Memories\ClustersBackend; use OCA\Memories\Db\TimelineQuery; -use OCA\Memories\Errors; -use OCA\Memories\HttpResponseException; +use OCA\Memories\Exceptions; use OCP\App\IAppManager; use OCP\IUserSession; @@ -95,7 +94,7 @@ class AlbumsBackend extends Backend // Get album $album = $this->timelineQuery->getAlbumIfAllowed($this->userId, $name); if (null === $album) { - throw new HttpResponseException(Errors::NotFound("album {$name}")); + throw Exceptions::NotFound("album {$name}"); } // Get files diff --git a/lib/Controller/ArchiveController.php b/lib/Controller/ArchiveController.php index 23e22a75..b5c5f15d 100644 --- a/lib/Controller/ArchiveController.php +++ b/lib/Controller/ArchiveController.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace OCA\Memories\Controller; -use OCA\Memories\Errors; +use OCA\Memories\Exceptions; use OCA\Memories\Exif; +use OCA\Memories\Util; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; use OCP\Files\Folder; @@ -40,142 +41,130 @@ class ArchiveController extends GenericApiController */ public function archive(string $id): Http\Response { - $user = $this->userSession->getUser(); - if (null === $user) { - return Errors::NotLoggedIn(); - } - $uid = $user->getUID(); - $userFolder = $this->rootFolder->getUserFolder($uid); + return Util::guardEx(function () use ($id) { + $uid = Util::getUID(); + $userFolder = Util::getUserFolder(); - // Check for permissions and get numeric Id - $file = $userFolder->getById((int) $id); - if (0 === \count($file)) { - return Errors::NotFound("file id {$id}"); - } - $file = $file[0]; - - // Check if user has permissions - if (!$file->isUpdateable()) { - return Errors::ForbiddenFileUpdate($file->getName()); - } - - // Create archive folder in the root of the user's configured timeline - $configPath = Exif::removeExtraSlash(Exif::getPhotosPath($this->config, $uid)); - $configPaths = explode(';', $configPath); - $timelineFolders = []; - $timelinePaths = []; - - // Get all timeline paths - foreach ($configPaths as $path) { - try { - $f = $userFolder->get($path); - $timelineFolders[] = $f; - $timelinePaths[] = $f->getPath(); - } catch (\OCP\Files\NotFoundException $e) { - return new JSONResponse(['message' => 'Timeline folder not found'], Http::STATUS_NOT_FOUND); + // Check for permissions and get numeric Id + $file = $userFolder->getById((int) $id); + if (0 === \count($file)) { + throw Exceptions::NotFound("file id {$id}"); } - } + $file = $file[0]; - // Bubble up from file until we reach the correct folder - $fileStorageId = $file->getStorage()->getId(); - $parent = $file->getParent(); - $isArchived = false; - while (true) { - if (null === $parent) { - throw new \Exception('Cannot get correct parent of file'); + // Check if user has permissions + if (!$file->isUpdateable()) { + throw Exceptions::ForbiddenFileUpdate($file->getName()); } - // Hit a timeline folder - if (\in_array($parent->getPath(), $timelinePaths, true)) { - break; + // Create archive folder in the root of the user's configured timeline + $configPath = Exif::removeExtraSlash(Exif::getPhotosPath($this->config, $uid)); + $configPaths = explode(';', $configPath); + $timelineFolders = []; + $timelinePaths = []; + + // Get all timeline paths + foreach ($configPaths as $path) { + try { + $f = $userFolder->get($path); + $timelineFolders[] = $f; + $timelinePaths[] = $f->getPath(); + } catch (\OCP\Files\NotFoundException $e) { + throw Exceptions::NotFound("timeline folder {$path}"); + } } - // Hit the user's root folder - if ($parent->getPath() === $userFolder->getPath()) { - break; - } + // Bubble up from file until we reach the correct folder + $fileStorageId = $file->getStorage()->getId(); + $parent = $file->getParent(); + $isArchived = false; + while (true) { + if (null === $parent) { + throw new \Exception('Cannot get correct parent of file'); + } - // Hit a storage root - try { - if ($parent->getParent()->getStorage()->getId() !== $fileStorageId) { + // Hit a timeline folder + if (\in_array($parent->getPath(), $timelinePaths, true)) { break; } - } catch (\OCP\Files\NotFoundException $e) { - break; - } - // Hit an archive folder root - if ($parent->getName() === \OCA\Memories\Util::$ARCHIVE_FOLDER) { - $isArchived = true; - - break; - } - - $parent = $parent->getParent(); - } - - // Get path of current file relative to the parent folder - $relativeFilePath = $parent->getRelativePath($file->getPath()); - - // Check if we want to archive or unarchive - $body = $this->request->getParams(); - $unarchive = isset($body['archive']) && false === $body['archive']; - if ($isArchived && !$unarchive) { - return new JSONResponse(['message' => 'File already archived'], Http::STATUS_BAD_REQUEST); - } - if (!$isArchived && $unarchive) { - return new JSONResponse(['message' => 'File not archived'], Http::STATUS_BAD_REQUEST); - } - - // Final path of the file including the file name - $destinationPath = ''; - - // Get if the file is already in the archive (relativePath starts with archive) - if ($isArchived) { - // file already in archive, remove it - $destinationPath = $relativeFilePath; - $parent = $parent->getParent(); - } else { - // file not in archive, put it in there - $af = \OCA\Memories\Util::$ARCHIVE_FOLDER; - $destinationPath = Exif::removeExtraSlash($af.$relativeFilePath); - } - - // Remove the filename - $destinationFolders = array_filter(explode('/', $destinationPath)); - array_pop($destinationFolders); - - // Create folder tree - $folder = $parent; - foreach ($destinationFolders as $folderName) { - try { - $existingFolder = $folder->get($folderName.'/'); - if (!$existingFolder instanceof Folder) { - throw new \OCP\Files\NotFoundException('Not a folder'); + // Hit the user's root folder + if ($parent->getPath() === $userFolder->getPath()) { + break; } - $folder = $existingFolder; - } catch (\OCP\Files\NotFoundException $e) { + + // Hit a storage root try { - $folder = $folder->newFolder($folderName); - } catch (\OCP\Files\NotPermittedException $e) { - return new JSONResponse(['message' => 'Failed to create folder'], Http::STATUS_FORBIDDEN); + if ($parent->getParent()->getStorage()->getId() !== $fileStorageId) { + break; + } + } catch (\OCP\Files\NotFoundException $e) { + break; + } + + // Hit an archive folder root + if ($parent->getName() === \OCA\Memories\Util::$ARCHIVE_FOLDER) { + $isArchived = true; + + break; + } + + $parent = $parent->getParent(); + } + + // Get path of current file relative to the parent folder + $relativeFilePath = $parent->getRelativePath($file->getPath()); + + // Check if we want to archive or unarchive + $body = $this->request->getParams(); + $unarchive = isset($body['archive']) && false === $body['archive']; + if ($isArchived && !$unarchive) { + throw Exceptions::BadRequest('File already archived'); + } + if (!$isArchived && $unarchive) { + throw Exceptions::BadRequest('File not archived'); + } + + // Final path of the file including the file name + $destinationPath = ''; + + // Get if the file is already in the archive (relativePath starts with archive) + if ($isArchived) { + // file already in archive, remove it + $destinationPath = $relativeFilePath; + $parent = $parent->getParent(); + } else { + // file not in archive, put it in there + $af = \OCA\Memories\Util::$ARCHIVE_FOLDER; + $destinationPath = Exif::removeExtraSlash($af.$relativeFilePath); + } + + // Remove the filename + $destinationFolders = array_filter(explode('/', $destinationPath)); + array_pop($destinationFolders); + + // Create folder tree + $folder = $parent; + foreach ($destinationFolders as $folderName) { + try { + $existingFolder = $folder->get($folderName.'/'); + if (!$existingFolder instanceof Folder) { + throw Exceptions::NotFound('Not a folder: '.$existingFolder->getPath()); + } + $folder = $existingFolder; + } catch (\OCP\Files\NotFoundException $e) { + try { + $folder = $folder->newFolder($folderName); + } catch (\OCP\Files\NotPermittedException $e) { + throw Exceptions::ForbiddenFileUpdate($folder->getPath().' [create]'); + } } } - } - // Move file to archive folder - try { + // Move file to archive folder $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); + return new JSONResponse([], Http::STATUS_OK); + }); } } diff --git a/lib/Controller/ClustersController.php b/lib/Controller/ClustersController.php index 094d7d8d..73a1f2f6 100644 --- a/lib/Controller/ClustersController.php +++ b/lib/Controller/ClustersController.php @@ -24,8 +24,8 @@ declare(strict_types=1); namespace OCA\Memories\Controller; use OCA\Memories\ClustersBackend\Backend; -use OCA\Memories\Errors; -use OCA\Memories\HttpResponseException; +use OCA\Memories\Exceptions; +use OCA\Memories\Util; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataDisplayResponse; use OCP\AppFramework\Http\JSONResponse; @@ -42,7 +42,7 @@ class ClustersController extends GenericApiController */ public function list(string $backend): Http\Response { - return $this->guardEx(function () use ($backend) { + return Util::guardEx(function () use ($backend) { $this->init($backend); $list = $this->backend->getClusters(); @@ -60,7 +60,7 @@ class ClustersController extends GenericApiController */ public function preview(string $backend, string $name): Http\Response { - return $this->guardEx(function () use ($backend, $name) { + return Util::guardEx(function () use ($backend, $name) { $this->init($backend); // Get list of some photos in this cluster @@ -88,7 +88,7 @@ class ClustersController extends GenericApiController */ public function download(string $backend, string $name): Http\Response { - return $this->guardEx(function () use ($backend, $name) { + return Util::guardEx(function () use ($backend, $name) { $this->init($backend); // Get list of all files in this cluster @@ -111,7 +111,7 @@ class ClustersController extends GenericApiController { $user = $this->userSession->getUser(); if (null === $user) { - throw new HttpResponseException(Errors::NotLoggedIn()); + throw Exceptions::NotLoggedIn(); } if (\array_key_exists($backend, Backend::$backends)) { @@ -121,13 +121,13 @@ class ClustersController extends GenericApiController } if (!$this->backend->isEnabled()) { - throw new HttpResponseException(Errors::NotEnabled($this->backend->appName())); + throw Exceptions::NotEnabled($this->backend->appName()); } if (property_exists($this->backend, 'root')) { $this->backend->root = $this->getRequestRoot(); if ($this->backend->root->isEmpty()) { - throw new HttpResponseException(Errors::NoRequestRoot()); + throw Exceptions::NoRequestRoot(); } } } @@ -141,7 +141,7 @@ class ClustersController extends GenericApiController $previewManager = \OC::$server->get(\OCP\IPreview::class); // Try to get a preview - $userFolder = $this->rootFolder->getUserFolder($this->getUID()); + $userFolder = Util::getUserFolder(); foreach ($photos as $img) { // Get the file $files = $userFolder->getById($this->backend->getFileId($img)); @@ -172,6 +172,6 @@ class ClustersController extends GenericApiController } } - return Errors::NotFound('preview from photos list'); + throw Exceptions::NotFound('preview from photos list'); } } diff --git a/lib/Controller/DaysController.php b/lib/Controller/DaysController.php index 9a7344d5..e1105eda 100644 --- a/lib/Controller/DaysController.php +++ b/lib/Controller/DaysController.php @@ -24,7 +24,8 @@ declare(strict_types=1); namespace OCA\Memories\Controller; use OCA\Memories\Db\TimelineRoot; -use OCA\Memories\Errors; +use OCA\Memories\Exceptions; +use OCA\Memories\Util; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; @@ -39,24 +40,10 @@ class DaysController extends GenericApiController */ public function days(): Http\Response { - // Get the folder to show - try { - $uid = $this->getUID(); - } catch (\Exception $e) { - return Errors::NotLoggedIn(); - } - - // Get the folder to show - $root = null; - - try { + return Util::guardEx(function () { + $uid = $this->getShareToken() ? '' : Util::getUID(); $root = $this->getRequestRoot(); - } catch (\Exception $e) { - return Errors::Generic($e); - } - // Run actual query - try { $list = $this->timelineQuery->getDays( $root, $uid, @@ -84,9 +71,7 @@ class DaysController extends GenericApiController } return new JSONResponse($list, Http::STATUS_OK); - } catch (\Exception $e) { - return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR); - } + }); } /** @@ -96,41 +81,32 @@ class DaysController extends GenericApiController */ public function day(string $id): Http\Response { - // Get user - $uid = $this->getUID(); + return Util::guardEx(function () use ($id) { + $uid = $this->getShareToken() ? '' : Util::getUID(); - // Check for wildcard - $dayIds = []; - if ('*' === $id) { - $dayIds = null; - } else { - // Split at commas and convert all parts to int - $dayIds = array_map(function ($part) { - return (int) $part; - }, explode(',', $id)); - } + // Check for wildcard + $dayIds = []; + if ('*' === $id) { + $dayIds = null; + } else { + // Split at commas and convert all parts to int + $dayIds = array_map(fn ($p) => (int) $p, explode(',', $id)); + } - // Check if $dayIds is empty - if (null !== $dayIds && 0 === \count($dayIds)) { - return new JSONResponse([], Http::STATUS_OK); - } + // Check if $dayIds is empty + if (null !== $dayIds && 0 === \count($dayIds)) { + return new JSONResponse([], Http::STATUS_OK); + } - // Get the folder to show - $root = null; - - try { + // Get the folder to show $root = $this->getRequestRoot(); - } catch (\Exception $e) { - return Errors::Generic($e); - } - // Convert to actual dayIds if month view - if ($this->isMonthView()) { - $dayIds = $this->timelineQuery->monthIdToDayIds((int) $dayIds[0]); - } + // Convert to actual dayIds if month view + if ($this->isMonthView()) { + $dayIds = $this->timelineQuery->monthIdToDayIds((int) $dayIds[0]); + } - // Run actual query - try { + // Run actual query $list = $this->timelineQuery->getDay( $root, $uid, @@ -153,9 +129,7 @@ class DaysController extends GenericApiController } return new JSONResponse($list, Http::STATUS_OK); - } catch (\Exception $e) { - return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR); - } + }); } /** @@ -165,12 +139,14 @@ class DaysController extends GenericApiController */ public function dayPost(): Http\Response { - $id = $this->request->getParam('body_ids'); - if (null === $id) { - return Errors::MissingParameter('body_ids'); - } + return Util::guardEx(function () { + $id = $this->request->getParam('body_ids'); + if (null === $id) { + throw Exceptions::MissingParameter('body_ids'); + } - return $this->day($id); + return $this->day($id); + }); } /** diff --git a/lib/Controller/DownloadController.php b/lib/Controller/DownloadController.php index f554de2b..70418601 100644 --- a/lib/Controller/DownloadController.php +++ b/lib/Controller/DownloadController.php @@ -24,7 +24,8 @@ declare(strict_types=1); namespace OCA\Memories\Controller; use bantu\IniGetWrapper\IniGetWrapper; -use OCA\Memories\Errors; +use OCA\Memories\Exceptions; +use OCA\Memories\Util; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; use OCP\ISession; @@ -44,16 +45,18 @@ class DownloadController extends GenericApiController */ public function request(): Http\Response { - // Get ids from body - $files = $this->request->getParam('files'); - if (null === $files || !\is_array($files)) { - return Errors::MissingParameter('files'); - } + return Util::guardEx(function () { + // Get ids from body + $files = $this->request->getParam('files'); + if (null === $files || !\is_array($files)) { + throw Exceptions::MissingParameter('files'); + } - // Return id - $handle = self::createHandle('memories', $files); + // Return id + $handle = self::createHandle('memories', $files); - return new JSONResponse(['handle' => $handle]); + return new JSONResponse(['handle' => $handle]); + }); } /** @@ -83,36 +86,36 @@ class DownloadController extends GenericApiController */ public function file(string $handle): Http\Response { - // Get ids from request - $session = \OC::$server->get(ISession::class); - $key = "memories_download_{$handle}"; - $info = $session->get($key); - $session->remove($key); + return Util::guardEx(function () use ($handle) { + // Get ids from request + $session = \OC::$server->get(ISession::class); + $key = "memories_download_{$handle}"; + $info = $session->get($key); + $session->remove($key); - if (null === $info) { - return Errors::NotFound('handle'); - } + if (null === $info) { + return Exceptions::NotFound('handle'); + } - $name = $info[0].'-'.date('YmdHis'); - $fileIds = $info[1]; + $name = $info[0].'-'.date('YmdHis'); + $fileIds = $info[1]; - /** @var int[] $fileIds */ - $fileIds = array_filter(array_map('intval', $fileIds), function (int $id): bool { - return $id > 0; + /** @var int[] $fileIds */ + $fileIds = array_filter(array_map('intval', $fileIds), fn ($id) => $id > 0); + + // Check if we have any valid ids + if (0 === \count($fileIds)) { + return Exceptions::NotFound('file IDs'); + } + + // Download single file + if (1 === \count($fileIds)) { + return $this->one($fileIds[0]); + } + + // Download multiple files + $this->multiple($name, $fileIds); // exits }); - - // Check if we have any valid ids - if (0 === \count($fileIds)) { - return Errors::NotFound('file IDs'); - } - - // Download single file - if (1 === \count($fileIds)) { - return $this->one($fileIds[0]); - } - - // Download multiple files - $this->multiple($name, $fileIds); // exits } /** @@ -124,47 +127,45 @@ class DownloadController extends GenericApiController */ public function one(int $fileid): Http\Response { - $file = $this->getUserFile($fileid); - if (null === $file) { - return Errors::NotFoundFile($fileid); - } + return Util::guardEx(function () use ($fileid) { + $file = $this->getUserFile($fileid); + if (null === $file) { + return Exceptions::NotFoundFile($fileid); + } - // Get the owner's root folder - $owner = $file->getOwner()->getUID(); - $userFolder = $this->rootFolder->getUserFolder($owner); + // Get the owner's root folder + $owner = $file->getOwner()->getUID(); + $userFolder = Util::getUserFolder($owner); - // Get the file in the context of the owner - $ownerFile = $userFolder->getById($fileid); - if (0 === \count($ownerFile)) { - // This should never happen, since the file was already found earlier - // Except if it was deleted in the meantime ... - return new JSONResponse([ - 'message' => 'File not found in owner\'s root folder', - ], Http::STATUS_INTERNAL_SERVER_ERROR); - } + // Get the file in the context of the owner + $ownerFile = $userFolder->getById($fileid); + if (0 === \count($ownerFile)) { + // This should never happen, since the file was already found earlier + // Except if it was deleted in the meantime ... + throw new \Exception('File not found in owner\'s root folder'); + } - // Get DAV path of file relative to owner's root folder - $path = $userFolder->getRelativePath($ownerFile[0]->getPath()); - if (null === $path) { - return new JSONResponse([ - 'message' => 'File path not found in owner\'s root folder', - ], Http::STATUS_INTERNAL_SERVER_ERROR); - } + // Get DAV path of file relative to owner's root folder + $path = $userFolder->getRelativePath($ownerFile[0]->getPath()); + if (null === $path) { + throw new \Exception('File path not found in owner\'s root folder'); + } - // Setup filesystem for owner - \OC_Util::tearDownFS(); - \OC_Util::setupFS($owner); + // Setup filesystem for owner + \OC_Util::tearDownFS(); + \OC_Util::setupFS($owner); - // HEAD and RANGE support - $server_params = ['head' => 'HEAD' === $this->request->getMethod()]; - if (isset($_SERVER['HTTP_RANGE'])) { - $server_params['range'] = $this->request->getHeader('Range'); - } + // HEAD and RANGE support + $server_params = ['head' => 'HEAD' === $this->request->getMethod()]; + if (isset($_SERVER['HTTP_RANGE'])) { + $server_params['range'] = $this->request->getHeader('Range'); + } - // Write file to output and exit - \OC_Files::get(\dirname($path), basename($path), $server_params); + // Write file to output and exit + \OC_Files::get(\dirname($path), basename($path), $server_params); - exit; + exit; + }); } /** @@ -173,7 +174,7 @@ class DownloadController extends GenericApiController * @param string $name Name of zip file * @param int[] $fileIds */ - private function multiple(string $name, array &$fileIds) + private function multiple(string $name, array $fileIds) { // Disable time limit $executionTime = (int) \OC::$server->get(IniGetWrapper::class)->getNumeric('max_execution_time'); diff --git a/lib/Controller/FoldersTrait.php b/lib/Controller/FoldersTrait.php index c8a087b0..0bafbc55 100644 --- a/lib/Controller/FoldersTrait.php +++ b/lib/Controller/FoldersTrait.php @@ -26,9 +26,7 @@ trait FoldersTrait $folders = $view->getDirectoryContent($folder->getPath(), FileInfo::MIMETYPE_FOLDER, $folder); // Sort by name - usort($folders, function ($a, $b) { - return strnatcmp($a->getName(), $b->getName()); - }); + usort($folders, fn ($a, $b) => strnatcmp($a->getName(), $b->getName())); // Process to response type return [ diff --git a/lib/Controller/GenericApiControllerUtils.php b/lib/Controller/GenericApiControllerUtils.php index 79e76709..ff59156c 100644 --- a/lib/Controller/GenericApiControllerUtils.php +++ b/lib/Controller/GenericApiControllerUtils.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace OCA\Memories\Controller; +use OCA\Memories\Util; use OCP\App\IAppManager; use OCP\IConfig; @@ -31,41 +32,12 @@ trait GenericApiControllerUtils protected IAppManager $appManager; protected IConfig $config; - /** Get logged in user's UID or throw exception */ - protected function getUID(): string - { - $user = $this->userSession->getUser(); - if ($this->getShareToken()) { - $user = null; - } elseif (null === $user) { - throw new \Exception('User not logged in'); - } - - return $user ? $user->getUID() : ''; - } - - /** - * Runa function and catch exceptions to return HTTP response. - * - * @param mixed $function - */ - protected function guardEx($function): \OCP\AppFramework\Http\Response - { - try { - return $function(); - } catch (\OCA\Memories\HttpResponseException $e) { - return $e->response; - } catch (\Exception $e) { - return \OCA\Memories\Errors::Generic($e); - } - } - /** * Check if albums are enabled for this user. */ protected function albumsIsEnabled(): bool { - return \OCA\Memories\Util::albumsIsEnabled($this->appManager); + return Util::albumsIsEnabled($this->appManager); } /** @@ -73,7 +45,7 @@ trait GenericApiControllerUtils */ protected function tagsIsEnabled(): bool { - return \OCA\Memories\Util::tagsIsEnabled($this->appManager); + return Util::tagsIsEnabled($this->appManager); } /** @@ -81,13 +53,13 @@ trait GenericApiControllerUtils */ protected function recognizeIsEnabled(): bool { - return \OCA\Memories\Util::recognizeIsEnabled($this->appManager); + return Util::recognizeIsEnabled($this->appManager); } // Check if facerecognition is installed and enabled for this user. protected function facerecognitionIsInstalled(): bool { - return \OCA\Memories\Util::facerecognitionIsInstalled($this->appManager); + return Util::facerecognitionIsInstalled($this->appManager); } /** @@ -95,7 +67,7 @@ trait GenericApiControllerUtils */ protected function facerecognitionIsEnabled(): bool { - return \OCA\Memories\Util::facerecognitionIsEnabled($this->config, $this->getUID()); + return Util::facerecognitionIsEnabled($this->config, Util::getUID()); } /** @@ -103,6 +75,6 @@ trait GenericApiControllerUtils */ protected function placesIsEnabled(): bool { - return \OCA\Memories\Util::placesGISType() > 0; + return Util::placesGISType() > 0; } } diff --git a/lib/Controller/ImageController.php b/lib/Controller/ImageController.php index 80e5e7d6..28dc6b05 100644 --- a/lib/Controller/ImageController.php +++ b/lib/Controller/ImageController.php @@ -24,8 +24,9 @@ declare(strict_types=1); namespace OCA\Memories\Controller; use OCA\Memories\AppInfo\Application; -use OCA\Memories\Errors; +use OCA\Memories\Exceptions; use OCA\Memories\Exif; +use OCA\Memories\Util; use OCP\AppFramework\Http; use OCP\AppFramework\Http\FileDisplayResponse; use OCP\AppFramework\Http\JSONResponse; @@ -49,16 +50,16 @@ class ImageController extends GenericApiController bool $a = false, string $mode = 'fill' ) { - if (-1 === $id || 0 === $x || 0 === $y) { - return Errors::MissingParameter('id, x, y'); - } + return Util::guardEx(function () use ($id, $x, $y, $a, $mode) { + if (-1 === $id || 0 === $x || 0 === $y) { + throw Exceptions::MissingParameter('id, x, y'); + } - $file = $this->getUserFile($id); - if (!$file) { - return Errors::NotFoundFile($id); - } + $file = $this->getUserFile($id); + if (!$file) { + throw Exceptions::NotFoundFile($id); + } - try { $preview = \OC::$server->get(\OCP\IPreview::class)->getPreview($file, $x, $y, !$a, $mode); $response = new FileDisplayResponse($preview, Http::STATUS_OK, [ 'Content-Type' => $preview->getMimeType(), @@ -66,11 +67,7 @@ class ImageController extends GenericApiController $response->cacheFor(3600 * 24, false, true); return $response; - } catch (\OCP\Files\NotFoundException $e) { - return Errors::NotFound('preview'); - } catch (\InvalidArgumentException $e) { - return Errors::Generic($e); - } + }); } /** @@ -84,87 +81,85 @@ class ImageController extends GenericApiController */ public function multipreview() { - // read body to array - try { + return Util::guardEx(function () { + // read body to array $body = file_get_contents('php://input'); $files = json_decode($body, true); - } catch (\Exception $e) { - return new JSONResponse([], Http::STATUS_BAD_REQUEST); - } - /** @var \OCP\IPreview $previewManager */ - $previewManager = \OC::$server->get(\OCP\IPreview::class); + /** @var \OCP\IPreview $previewManager */ + $previewManager = \OC::$server->get(\OCP\IPreview::class); - // For checking max previews - $previewRoot = new \OC\Preview\Storage\Root( - \OC::$server->get(IRootFolder::class), - \OC::$server->get(\OC\SystemConfig::class), - ); + // For checking max previews + $previewRoot = new \OC\Preview\Storage\Root( + \OC::$server->get(IRootFolder::class), + \OC::$server->get(\OC\SystemConfig::class), + ); - // stream the response - header('Content-Type: application/octet-stream'); - header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T', time() + 7 * 3600 * 24)); - header('Cache-Control: max-age='. 7 * 3600 * 24 .', private'); + // stream the response + header('Content-Type: application/octet-stream'); + header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T', time() + 7 * 3600 * 24)); + header('Cache-Control: max-age='. 7 * 3600 * 24 .', private'); - foreach ($files as $bodyFile) { - if (!isset($bodyFile['reqid']) || !isset($bodyFile['fileid']) || !isset($bodyFile['x']) || !isset($bodyFile['y']) || !isset($bodyFile['a'])) { - continue; - } - $reqid = $bodyFile['reqid']; - $fileid = (int) $bodyFile['fileid']; - $x = (int) $bodyFile['x']; - $y = (int) $bodyFile['y']; - $a = '1' === $bodyFile['a']; - if ($fileid <= 0 || $x <= 0 || $y <= 0) { - continue; - } + foreach ($files as $bodyFile) { + if (!isset($bodyFile['reqid']) || !isset($bodyFile['fileid']) || !isset($bodyFile['x']) || !isset($bodyFile['y']) || !isset($bodyFile['a'])) { + continue; + } + $reqid = $bodyFile['reqid']; + $fileid = (int) $bodyFile['fileid']; + $x = (int) $bodyFile['x']; + $y = (int) $bodyFile['y']; + $a = '1' === $bodyFile['a']; + if ($fileid <= 0 || $x <= 0 || $y <= 0) { + continue; + } - $file = $this->getUserFile($fileid); - if (!$file) { - continue; - } + $file = $this->getUserFile($fileid); + if (!$file) { + continue; + } - try { - // Make sure max preview exists - $fileId = (string) $file->getId(); - $folder = $previewRoot->getFolder($fileId); - $hasMax = false; - foreach ($folder->getDirectoryListing() as $preview) { - $name = $preview->getName(); - if (str_contains($name, '-max')) { - $hasMax = true; + try { + // Make sure max preview exists + $fileId = (string) $file->getId(); + $folder = $previewRoot->getFolder($fileId); + $hasMax = false; + foreach ($folder->getDirectoryListing() as $preview) { + $name = $preview->getName(); + if (str_contains($name, '-max')) { + $hasMax = true; - break; + break; + } } - } - if (!$hasMax) { + if (!$hasMax) { + continue; + } + + // Add this preview to the response + $preview = $previewManager->getPreview($file, $x, $y, !$a, \OCP\IPreview::MODE_FILL); + $content = $preview->getContent(); + if (empty($content)) { + continue; + } + + ob_start(); + echo json_encode([ + 'reqid' => $reqid, + 'Content-Length' => \strlen($content), + 'Content-Type' => $preview->getMimeType(), + ]); + echo "\n"; + echo $content; + ob_end_flush(); + } catch (\OCP\Files\NotFoundException $e) { + continue; + } catch (\Exception $e) { continue; } - - // Add this preview to the response - $preview = $previewManager->getPreview($file, $x, $y, !$a, \OCP\IPreview::MODE_FILL); - $content = $preview->getContent(); - if (empty($content)) { - continue; - } - - ob_start(); - echo json_encode([ - 'reqid' => $reqid, - 'Content-Length' => \strlen($content), - 'Content-Type' => $preview->getMimeType(), - ]); - echo "\n"; - echo $content; - ob_end_flush(); - } catch (\OCP\Files\NotFoundException $e) { - continue; - } catch (\Exception $e) { - continue; } - } - exit; + exit; + }); } /** @@ -182,36 +177,38 @@ class ImageController extends GenericApiController bool $current = false, bool $tags = false ): Http\Response { - $file = $this->getUserFile((int) $id); - if (!$file) { - return Errors::NotFoundFile($id); - } - - // Get the image info - $info = $this->timelineQuery->getInfoById($file->getId(), $basic); - - // Allow these ony for logged in users - if (null !== $this->userSession->getUser()) { - // Get list of tags for this file - if ($tags) { - $info['tags'] = $this->getTags($file->getId()); + return Util::guardEx(function () use ($id, $basic, $current, $tags) { + $file = $this->getUserFile((int) $id); + if (!$file) { + throw Exceptions::NotFoundFile($id); } - // Get latest exif data if requested - if ($current) { - $info['current'] = Exif::getExifFromFile($file); + // Get the image info + $info = $this->timelineQuery->getInfoById($file->getId(), $basic); + + // Allow these ony for logged in users + if (null !== $this->userSession->getUser()) { + // Get list of tags for this file + if ($tags) { + $info['tags'] = $this->getTags($file->getId()); + } + + // Get latest exif data if requested + if ($current) { + $info['current'] = Exif::getExifFromFile($file); + } } - } - // Inject permissions and convert to string - $info['permissions'] = \OCA\Memories\Util::permissionsToStr($file->getPermissions()); + // Inject permissions and convert to string + $info['permissions'] = \OCA\Memories\Util::permissionsToStr($file->getPermissions()); - // Inject other file parameters that are cheap to get now - $info['mimetype'] = $file->getMimeType(); - $info['size'] = $file->getSize(); - $info['basename'] = $file->getName(); + // Inject other file parameters that are cheap to get now + $info['mimetype'] = $file->getMimeType(); + $info['size'] = $file->getSize(); + $info['basename'] = $file->getName(); - return new JSONResponse($info, Http::STATUS_OK); + return new JSONResponse($info, Http::STATUS_OK); + }); } /** @@ -224,36 +221,30 @@ class ImageController extends GenericApiController */ public function setExif(string $id, array $raw): Http\Response { - $file = $this->getUserFile((int) $id); - if (!$file) { - return Errors::NotFoundFile($id); - } + return Util::guardEx(function () use ($id, $raw) { + $file = $this->getUserFile((int) $id); + if (!$file) { + throw Exceptions::NotFoundFile($id); + } - // Check if user has permissions - if (!$file->isUpdateable()) { - return Errors::ForbiddenFileUpdate($file->getName()); - } + // Check if user has permissions + if (!$file->isUpdateable() || Util::isEncryptionEnabled()) { + throw Exceptions::ForbiddenFileUpdate($file->getName()); + } - // Check for end-to-end encryption - if (\OCA\Memories\Util::isEncryptionEnabled()) { - return new JSONResponse(['message' => 'Cannot change encrypted file'], Http::STATUS_PRECONDITION_FAILED); - } + // Check if allowed to edit file + $mime = $file->getMimeType(); + if (!\in_array($mime, Exif::allowedEditMimetypes(), true)) { + $name = $file->getName(); - // Check if allowed to edit file - $mime = $file->getMimeType(); - if (!\in_array($mime, Exif::allowedEditMimetypes(), true)) { - $name = $file->getName(); + throw Exceptions::Forbidden("Cannot edit file {$name} (blacklisted type {$mime})"); + } - return new JSONResponse(['message' => "Cannot edit file {$name} (blacklisted type {$mime})"], Http::STATUS_PRECONDITION_FAILED); - } - - try { + // Set the exif data Exif::setFileExif($file, $raw); - } catch (\Exception $e) { - return Errors::Generic($e); - } - return new JSONResponse([], Http::STATUS_OK); + return new JSONResponse([], Http::STATUS_OK); + }); } /** @@ -269,35 +260,37 @@ class ImageController extends GenericApiController */ public function decodable(string $id): Http\Response { - $file = $this->getUserFile((int) $id); - if (!$file) { - return Errors::NotFoundFile($id); - } + return Util::guardEx(function () use ($id) { + $file = $this->getUserFile((int) $id); + if (!$file) { + throw Exceptions::NotFoundFile($id); + } - // Check if valid image - $mimetype = $file->getMimeType(); - if (!\in_array($mimetype, Application::IMAGE_MIMES, true)) { - return Errors::ForbiddenFileUpdate($file->getName()); - } + // Check if valid image + $mimetype = $file->getMimeType(); + if (!\in_array($mimetype, Application::IMAGE_MIMES, true)) { + throw Exceptions::Forbidden('Not an image'); + } - /** @var string Blob of image */ - $blob = $file->getContent(); + /** @var string Blob of image */ + $blob = $file->getContent(); - // Convert image to JPEG if required - if (!\in_array($mimetype, ['image/png', 'image/webp', 'image/jpeg', 'image/gif'], true)) { - $image = new \Imagick(); - $image->readImageBlob($blob); - $image->setImageFormat('jpeg'); - $image->setImageCompressionQuality(95); - $blob = $image->getImageBlob(); - $mimetype = $image->getImageMimeType(); - } + // Convert image to JPEG if required + if (!\in_array($mimetype, ['image/png', 'image/webp', 'image/jpeg', 'image/gif'], true)) { + $image = new \Imagick(); + $image->readImageBlob($blob); + $image->setImageFormat('jpeg'); + $image->setImageCompressionQuality(95); + $blob = $image->getImageBlob(); + $mimetype = $image->getImageMimeType(); + } - // Return the image - $response = new Http\DataDisplayResponse($blob, Http::STATUS_OK, ['Content-Type' => $mimetype]); - $response->cacheFor(3600 * 24, false, false); + // Return the image + $response = new Http\DataDisplayResponse($blob, Http::STATUS_OK, ['Content-Type' => $mimetype]); + $response->cacheFor(3600 * 24, false, false); - return $response; + return $response; + }); } /** @@ -306,7 +299,7 @@ class ImageController extends GenericApiController private function getTags(int $fileId): array { // Make sure tags are enabled - if (!\OCA\Memories\Util::tagsIsEnabled($this->appManager)) { + if (!Util::tagsIsEnabled($this->appManager)) { return []; } @@ -320,10 +313,9 @@ class ImageController extends GenericApiController /** @var \OCP\SystemTag\ISystemTag[] */ $tags = $tagManager->getTagsByIds($tagIds); - return array_map(function ($tag) { - return $tag->getName(); - }, array_filter($tags, function ($tag) { - return $tag->isUserVisible(); - })); + $visible = array_filter($tags, fn ($t) => $t->isUserVisible()); + + // Get the tag names + return array_map(fn ($t) => $t->getName(), $visible); } } diff --git a/lib/Controller/MapController.php b/lib/Controller/MapController.php index fced3f01..480b4271 100644 --- a/lib/Controller/MapController.php +++ b/lib/Controller/MapController.php @@ -23,7 +23,8 @@ declare(strict_types=1); namespace OCA\Memories\Controller; -use OCA\Memories\Errors; +use OCA\Memories\Exceptions; +use OCA\Memories\Util; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; @@ -34,35 +35,27 @@ class MapController extends GenericApiController */ public function clusters(): Http\Response { - // Get the folder to show - $root = null; - - try { + return Util::guardEx(function () { + // Get the folder to show $root = $this->getRequestRoot(); - } catch (\Exception $e) { - return Errors::NoRequestRoot(); - } - // Make sure we have bounds and zoom level - // Zoom level is used to determine the grid length - $bounds = $this->request->getParam('bounds'); - $zoomLevel = $this->request->getParam('zoom'); - if (!$bounds || !$zoomLevel || !is_numeric($zoomLevel)) { - return Errors::MissingParameter('bounds or zoom'); - } + // Make sure we have bounds and zoom level + // Zoom level is used to determine the grid length + $bounds = $this->request->getParam('bounds'); + $zoomLevel = $this->request->getParam('zoom'); + if (!$bounds || !$zoomLevel || !is_numeric($zoomLevel)) { + throw Exceptions::MissingParameter('bounds or zoom'); + } - // A tweakable parameter to determine the number of boxes in the map - // Note: these parameters need to be changed in MapSplitMatter.vue as well - $clusterDensity = 1; - $gridLen = 180.0 / (2 ** $zoomLevel * $clusterDensity); + // A tweakable parameter to determine the number of boxes in the map + // Note: these parameters need to be changed in MapSplitMatter.vue as well + $clusterDensity = 1; + $gridLen = 180.0 / (2 ** $zoomLevel * $clusterDensity); - try { $clusters = $this->timelineQuery->getMapClusters($gridLen, $bounds, $root); // Get previews for each cluster - $clusterIds = array_map(function ($cluster) { - return (int) $cluster['id']; - }, $clusters); + $clusterIds = array_map(fn ($cluster) => (int) $cluster['id'], $clusters); $previews = $this->timelineQuery->getMapClusterPreviews($clusterIds, $root); // Merge the responses @@ -75,8 +68,6 @@ class MapController extends GenericApiController } return new JSONResponse($clusters); - } catch (\Exception $e) { - return Errors::Generic($e); - } + }); } } diff --git a/lib/Controller/OtherController.php b/lib/Controller/OtherController.php index 55018f67..5b284271 100644 --- a/lib/Controller/OtherController.php +++ b/lib/Controller/OtherController.php @@ -24,7 +24,8 @@ declare(strict_types=1); namespace OCA\Memories\Controller; use OCA\Memories\AppInfo\Application; -use OCA\Memories\Errors; +use OCA\Memories\Exceptions; +use OCA\Memories\Util; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\StreamResponse; @@ -43,20 +44,18 @@ class OtherController extends GenericApiController */ public function setUserConfig(string $key, string $value): Http\Response { - $user = $this->userSession->getUser(); - if (null === $user) { - return Errors::NotLoggedIn(); - } + return Util::guardEx(function () use ($key, $value) { + $uid = Util::getUID(); - // 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); - } + // Make sure not running in read-only mode + if ($this->config->getSystemValue('memories.readonly', false)) { + throw Exceptions::Forbidden('Cannot change settings in readonly mode'); + } - $userId = $user->getUID(); - $this->config->setUserValue($userId, Application::APPNAME, $key, $value); + $this->config->setUserValue($uid, Application::APPNAME, $key, $value); - return new JSONResponse([], Http::STATUS_OK); + return new JSONResponse([], Http::STATUS_OK); + }); } /** diff --git a/lib/Controller/ShareController.php b/lib/Controller/ShareController.php index b19dfc3b..c452d139 100644 --- a/lib/Controller/ShareController.php +++ b/lib/Controller/ShareController.php @@ -23,7 +23,8 @@ declare(strict_types=1); namespace OCA\Memories\Controller; -use OCA\Memories\Errors; +use OCA\Memories\Exceptions; +use OCA\Memories\Util; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; @@ -39,22 +40,21 @@ class ShareController extends GenericApiController */ public function links($id, $path): Http\Response { - $file = $this->getNodeByIdOrPath($id, $path); - if (!$file) { - return Errors::Forbidden('file'); - } + return Util::guardEx(function () use ($id, $path) { + $file = $this->getNodeByIdOrPath($id, $path); - /** @var \OCP\Share\IManager $shareManager */ - $shareManager = \OC::$server->get(\OCP\Share\IManager::class); + /** @var \OCP\Share\IManager $shareManager */ + $shareManager = \OC::$server->get(\OCP\Share\IManager::class); - $shares = $shareManager->getSharesBy($this->getUID(), \OCP\Share\IShare::TYPE_LINK, $file, true, 50, 0); - if (empty($shares)) { - return Errors::NotFound('external links'); - } + $shares = $shareManager->getSharesBy(Util::getUID(), \OCP\Share\IShare::TYPE_LINK, $file, true, 50, 0); + if (empty($shares)) { + throw Exceptions::NotFound('external links'); + } - $links = array_map([$this, 'makeShareResponse'], $shares); + $links = array_map(fn ($s) => $this->makeShareResponse($s), $shares); - return new JSONResponse($links, Http::STATUS_OK); + return new JSONResponse($links, Http::STATUS_OK); + }); } /** @@ -67,24 +67,23 @@ class ShareController extends GenericApiController */ public function createNode($id, $path): Http\Response { - $file = $this->getNodeByIdOrPath($id, $path); - if (!$file) { - return Errors::Forbidden('You are not allowed to share this file'); - } + return Util::guardEx(function () use ($id, $path) { + $file = $this->getNodeByIdOrPath($id, $path); - /** @var \OCP\Share\IManager $shareManager */ - $shareManager = \OC::$server->get(\OCP\Share\IManager::class); + /** @var \OCP\Share\IManager $shareManager */ + $shareManager = \OC::$server->get(\OCP\Share\IManager::class); - /** @var \OCP\Share\IShare $share */ - $share = $shareManager->newShare(); - $share->setNode($file); - $share->setShareType(\OCP\Share\IShare::TYPE_LINK); - $share->setSharedBy($this->userSession->getUser()->getUID()); - $share->setPermissions(\OCP\Constants::PERMISSION_READ); + /** @var \OCP\Share\IShare $share */ + $share = $shareManager->newShare(); + $share->setNode($file); + $share->setShareType(\OCP\Share\IShare::TYPE_LINK); + $share->setSharedBy($this->userSession->getUser()->getUID()); + $share->setPermissions(\OCP\Constants::PERMISSION_READ); - $share = $shareManager->createShare($share); + $share = $shareManager->createShare($share); - return new JSONResponse($this->makeShareResponse($share), Http::STATUS_OK); + return new JSONResponse($this->makeShareResponse($share), Http::STATUS_OK); + }); } /** @@ -94,46 +93,41 @@ class ShareController extends GenericApiController */ public function deleteShare(string $id): Http\Response { - $uid = $this->getUID(); - if (!$uid) { - return Errors::NotLoggedIn(); - } + return Util::guardEx(function () use ($id) { + $uid = Util::getUID(); - /** @var \OCP\Share\IManager $shareManager */ - $shareManager = \OC::$server->get(\OCP\Share\IManager::class); + /** @var \OCP\Share\IManager $shareManager */ + $shareManager = \OC::$server->get(\OCP\Share\IManager::class); - $share = $shareManager->getShareById($id); + $share = $shareManager->getShareById($id); - if ($share->getSharedBy() !== $uid) { - return Errors::Forbidden('You are not the owner of this share'); - } + if ($share->getSharedBy() !== $uid) { + throw Exceptions::Forbidden('You are not the owner of this share'); + } - $shareManager->deleteShare($share); + $shareManager->deleteShare($share); - return new JSONResponse([], Http::STATUS_OK); + return new JSONResponse([], Http::STATUS_OK); + }); } private function getNodeByIdOrPath($id, $path) { - $uid = $this->getUID(); - if (!$uid) { - return null; - } + $uid = Util::getUID(); - $file = null; - if ($id) { - $file = $this->getUserFile($id); - } elseif ($path) { - try { - $userFolder = $this->rootFolder->getUserFolder($uid); - $file = $userFolder->get($path); - } catch (\OCP\Files\NotFoundException $e) { - return null; + try { + $file = null; + if ($id) { + $file = $this->getUserFile($id); + } elseif ($path) { + $file = Util::getUserFolder($uid)->get($path); } + } catch (\OCP\Files\NotFoundException $e) { + throw Exceptions::NotFoundFile($path ?? $id); } if (!$file || !$file->isShareable()) { - return null; + throw Exceptions::Forbidden('File not sharable'); } return $file; diff --git a/lib/Controller/TagsController.php b/lib/Controller/TagsController.php index 07cd968d..67de2d3c 100644 --- a/lib/Controller/TagsController.php +++ b/lib/Controller/TagsController.php @@ -23,7 +23,8 @@ declare(strict_types=1); namespace OCA\Memories\Controller; -use OCA\Memories\Errors; +use OCA\Memories\Exceptions; +use OCA\Memories\Util; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; @@ -36,29 +37,31 @@ class TagsController extends GenericApiController */ public function set(int $id, array $add, array $remove): Http\Response { - // Check tags enabled for this user - if (!$this->tagsIsEnabled()) { - return Errors::NotEnabled('Tags'); - } + return Util::guardEx(function () use ($id, $add, $remove) { + // Check tags enabled for this user + if (!$this->tagsIsEnabled()) { + throw Exceptions::NotEnabled('Tags'); + } - // Check the user is allowed to edit the file - $file = $this->getUserFile($id); - if (null === $file) { - return Errors::NotFoundFile($id); - } + // Check the user is allowed to edit the file + $file = $this->getUserFile($id); + if (null === $file) { + throw Exceptions::NotFoundFile($id); + } - // Check the user is allowed to edit the file - if (!$file->isUpdateable() || !($file->getPermissions() & \OCP\Constants::PERMISSION_UPDATE)) { - return Errors::ForbiddenFileUpdate($file->getName()); - } + // Check the user is allowed to edit the file + if (!$file->isUpdateable() || !($file->getPermissions() & \OCP\Constants::PERMISSION_UPDATE)) { + throw Exceptions::ForbiddenFileUpdate($file->getName()); + } - // Get mapper from tags to objects - $om = \OC::$server->get(\OCP\SystemTag\ISystemTagObjectMapper::class); + // Get mapper from tags to objects + $om = \OC::$server->get(\OCP\SystemTag\ISystemTagObjectMapper::class); - // Add and remove tags - $om->assignTags((string) $id, 'files', $add); - $om->unassignTags((string) $id, 'files', $remove); + // Add and remove tags + $om->assignTags((string) $id, 'files', $add); + $om->unassignTags((string) $id, 'files', $remove); - return new JSONResponse([], Http::STATUS_OK); + return new JSONResponse([], Http::STATUS_OK); + }); } } diff --git a/lib/Controller/VideoController.php b/lib/Controller/VideoController.php index 716fdcdd..d77439a6 100644 --- a/lib/Controller/VideoController.php +++ b/lib/Controller/VideoController.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace OCA\Memories\Controller; -use OCA\Memories\Errors; +use OCA\Memories\Exceptions; use OCA\Memories\Exif; +use OCA\Memories\Util; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataDisplayResponse; use OCP\AppFramework\Http\JSONResponse; @@ -43,58 +44,60 @@ class VideoController extends GenericApiController */ public function transcode(string $client, int $fileid, string $profile): Http\Response { - // Make sure not running in read-only mode - if (false !== $this->config->getSystemValue('memories.vod.disable', 'UNSET')) { - return Errors::Forbidden('Transcoding disabled'); - } - - // Check client identifier is 8 characters or more - if (\strlen($client) < 8) { - return Errors::MissingParameter('client (invalid)'); - } - - // Get file - $file = $this->getUserFile($fileid); - if (!$file || !$file->isReadable()) { - return Errors::NotFoundFile($fileid); - } - - // Local files only for now - if (!$file->getStorage()->isLocal()) { - return Errors::Forbidden('External storage not supported'); - } - - // Get file path - $path = $file->getStorage()->getLocalFile($file->getInternalPath()); - if (!$path || !file_exists($path)) { - return Errors::NotFound('local file path'); - } - - // Check if file starts with temp dir - $tmpDir = sys_get_temp_dir(); - if (0 === strpos($path, $tmpDir)) { - return Errors::Forbidden('files in temp directory not supported'); - } - - // Request and check data was received - try { - $status = $this->getUpstream($client, $path, $profile); - if (409 === $status || -1 === $status) { - // Just a conflict (transcoding process changed) - return new JSONResponse(['message' => 'Conflict'], Http::STATUS_CONFLICT); + return Util::guardEx(function () use ($client, $fileid, $profile) { + // Make sure not running in read-only mode + if (false !== $this->config->getSystemValue('memories.vod.disable', 'UNSET')) { + throw Exceptions::Forbidden('Transcoding disabled'); } - if (200 !== $status) { - throw new \Exception("Transcoder returned {$status}"); + + // Check client identifier is 8 characters or more + if (\strlen($client) < 8) { + throw Exceptions::MissingParameter('client (invalid)'); } - } catch (\Exception $e) { - $msg = 'Transcode failed: '.$e->getMessage(); - $this->logger->error($msg, ['app' => 'memories']); - return Errors::Generic($e); - } + // Get file + $file = $this->getUserFile($fileid); + if (!$file || !$file->isReadable()) { + throw Exceptions::NotFoundFile($fileid); + } - // The response was already streamed, so we have nothing to do here - exit; + // Local files only for now + if (!$file->getStorage()->isLocal()) { + throw Exceptions::Forbidden('External storage not supported'); + } + + // Get file path + $path = $file->getStorage()->getLocalFile($file->getInternalPath()); + if (!$path || !file_exists($path)) { + throw Exceptions::NotFound('local file path'); + } + + // Check if file starts with temp dir + $tmpDir = sys_get_temp_dir(); + if (0 === strpos($path, $tmpDir)) { + throw Exceptions::Forbidden('files in temp directory not supported'); + } + + // Request and check data was received + try { + $status = $this->getUpstream($client, $path, $profile); + if (409 === $status || -1 === $status) { + // Just a conflict (transcoding process changed) + return new JSONResponse(['message' => 'Conflict'], Http::STATUS_CONFLICT); + } + if (200 !== $status) { + throw new \Exception("Transcoder returned {$status}"); + } + } catch (\Exception $e) { + $msg = 'Transcode failed: '.$e->getMessage(); + $this->logger->error($msg, ['app' => 'memories']); + + throw $e; + } + + // The response was already streamed, so we have nothing to do here + exit; + }); } /** @@ -112,111 +115,113 @@ class VideoController extends GenericApiController string $format = '', string $transcode = '' ) { - $file = $this->getUserFile($fileid); - if (null === $file) { - return Errors::NotFoundFile($fileid); - } - - // Check file liveid - if (!$liveid) { - return Errors::MissingParameter('liveid'); - } - - // Response data - $name = ''; - $mime = ''; - $blob = null; - $liveVideoPath = null; - - // Video is inside the file - $path = null; - if (str_starts_with($liveid, 'self__')) { - $path = $file->getStorage()->getLocalFile($file->getInternalPath()); - $mime = 'video/mp4'; - $name = $file->getName().'.mp4'; - } - - // Different manufacurers have different formats - if ('self__trailer' === $liveid) { - try { // Get trailer - $blob = Exif::getBinaryExifProp($path, '-trailer'); - } catch (\Exception $e) { - return Errors::NotFound('file trailer'); - } - } elseif ('self__embeddedvideo' === $liveid) { - try { // Get embedded video file - $blob = Exif::getBinaryExifProp($path, '-EmbeddedVideoFile'); - } catch (\Exception $e) { - return Errors::NotFound('embedded video'); - } - } elseif (str_starts_with($liveid, 'self__traileroffset=')) { - // Remove prefix - $offset = (int) substr($liveid, \strlen('self__traileroffset=')); - if ($offset <= 0) { - return new JSONResponse(['message' => 'Invalid offset'], Http::STATUS_BAD_REQUEST); + return Util::guardEx(function () use ($fileid, $liveid, $format, $transcode) { + $file = $this->getUserFile($fileid); + if (null === $file) { + throw Exceptions::NotFoundFile($fileid); } - // Read file from offset to end - $blob = file_get_contents($path, false, null, $offset); - } else { - // Get stored video file (Apple MOV) - $lp = $this->timelineQuery->getLivePhoto($fileid); - if (!$lp || $lp['liveid'] !== $liveid) { - return Errors::NotFound('live video entry'); + // Check file liveid + if (!$liveid) { + throw Exceptions::MissingParameter('liveid'); } - // Get and return file - $liveFileId = (int) $lp['fileid']; - $files = $this->rootFolder->getById($liveFileId); - if (0 === \count($files)) { - return Errors::NotFound('live video file'); - } - $liveFile = $files[0]; + // Response data + $name = ''; + $mime = ''; + $blob = null; + $liveVideoPath = null; - if ($liveFile instanceof File) { - // Requested only JSON info - if ('json' === $format) { - return new JSONResponse($lp); + // Video is inside the file + $path = null; + if (str_starts_with($liveid, 'self__')) { + $path = $file->getStorage()->getLocalFile($file->getInternalPath()); + $mime = 'video/mp4'; + $name = $file->getName().'.mp4'; + } + + // Different manufacurers have different formats + if ('self__trailer' === $liveid) { + try { // Get trailer + $blob = Exif::getBinaryExifProp($path, '-trailer'); + } catch (\Exception $e) { + throw Exceptions::NotFound('file trailer'); + } + } elseif ('self__embeddedvideo' === $liveid) { + try { // Get embedded video file + $blob = Exif::getBinaryExifProp($path, '-EmbeddedVideoFile'); + } catch (\Exception $e) { + throw Exceptions::NotFound('embedded video'); + } + } elseif (str_starts_with($liveid, 'self__traileroffset=')) { + // Remove prefix + $offset = (int) substr($liveid, \strlen('self__traileroffset=')); + if ($offset <= 0) { + throw Exceptions::BadRequest('Invalid offset'); } - $name = $liveFile->getName(); - $blob = $liveFile->getContent(); - $mime = $liveFile->getMimeType(); - $liveVideoPath = $liveFile->getStorage()->getLocalFile($liveFile->getInternalPath()); - } - } - - // Data not found - if (!$blob) { - return Errors::NotFound('live video data'); - } - - // Transcode video if allowed - if ($transcode && !$this->config->getSystemValue('memories.vod.disable', true)) { - try { - // If video path not given, write to temp file - if (!$liveVideoPath) { - $liveVideoPath = self::postFile($transcode, $blob)['path']; + // Read file from offset to end + $blob = file_get_contents($path, false, null, $offset); + } else { + // Get stored video file (Apple MOV) + $lp = $this->timelineQuery->getLivePhoto($fileid); + if (!$lp || $lp['liveid'] !== $liveid) { + throw Exceptions::NotFound('live video entry'); } - // If this is H.264 it won't get transcoded anyway - if ($liveVideoPath && 200 === $this->getUpstream($transcode, $liveVideoPath, 'max.mov')) { - exit; + // Get and return file + $liveFileId = (int) $lp['fileid']; + $files = $this->rootFolder->getById($liveFileId); + if (0 === \count($files)) { + throw Exceptions::NotFound('live video file'); + } + $liveFile = $files[0]; + + if ($liveFile instanceof File) { + // Requested only JSON info + if ('json' === $format) { + return new JSONResponse($lp); + } + + $name = $liveFile->getName(); + $blob = $liveFile->getContent(); + $mime = $liveFile->getMimeType(); + $liveVideoPath = $liveFile->getStorage()->getLocalFile($liveFile->getInternalPath()); } - } catch (\Exception $e) { - // Transcoding failed, just return the original video } - } - // Make and send response - $response = new DataDisplayResponse($blob, Http::STATUS_OK, []); - $response->setHeaders([ - 'Content-Type' => $mime, - 'Content-Disposition' => "attachment; filename=\"{$name}\"", - ]); - $response->cacheFor(3600 * 24, false, false); + // Data not found + if (!$blob) { + throw Exceptions::NotFound('live video data'); + } - return $response; + // Transcode video if allowed + if ($transcode && !$this->config->getSystemValue('memories.vod.disable', true)) { + try { + // If video path not given, write to temp file + if (!$liveVideoPath) { + $liveVideoPath = self::postFile($transcode, $blob)['path']; + } + + // If this is H.264 it won't get transcoded anyway + if ($liveVideoPath && 200 === $this->getUpstream($transcode, $liveVideoPath, 'max.mov')) { + exit; + } + } catch (\Exception $e) { + // Transcoding failed, just return the original video + } + } + + // Make and send response + $response = new DataDisplayResponse($blob, Http::STATUS_OK, []); + $response->setHeaders([ + 'Content-Type' => $mime, + 'Content-Disposition' => "attachment; filename=\"{$name}\"", + ]); + $response->cacheFor(3600 * 24, false, false); + + return $response; + }); } /** diff --git a/lib/Errors.php b/lib/Errors.php deleted file mode 100644 index 5b481511..00000000 --- a/lib/Errors.php +++ /dev/null @@ -1,74 +0,0 @@ - $e->getMessage(), - ], $status); - } - - public static function NotLoggedIn(): Http\Response - { - return new DataResponse([ - 'message' => 'User not logged in', - ], Http::STATUS_PRECONDITION_FAILED); - } - - public static function NotEnabled(string $app): Http\Response - { - return new DataResponse([ - 'message' => "{$app} app not enabled or not the required version.", - ], Http::STATUS_PRECONDITION_FAILED); - } - - public static function NoRequestRoot(): Http\Response - { - return new DataResponse([ - 'message' => 'Request root could not be determined', - ], Http::STATUS_NOT_FOUND); - } - - public static function NotFound(string $ctx): Http\Response - { - return new DataResponse([ - 'message' => "Not found ({$ctx})", - ], Http::STATUS_NOT_FOUND); - } - - public static function NotFoundFile($identifier): Http\Response - { - return new DataResponse([ - 'message' => "File not found ({$identifier})", - ], Http::STATUS_NOT_FOUND); - } - - public static function Forbidden(string $ctx): Http\Response - { - return new DataResponse([ - 'message' => "Forbidden ({$ctx})", - ], Http::STATUS_FORBIDDEN); - } - - public static function ForbiddenFileUpdate(string $name): Http\Response - { - return new DataResponse([ - 'message' => "Forbidden ({$name} cannot be updated)", - ], Http::STATUS_FORBIDDEN); - } - - public static function MissingParameter(string $name): Http\Response - { - return new DataResponse([ - 'message' => "Missing parameter ({$name})", - ], Http::STATUS_BAD_REQUEST); - } -} diff --git a/lib/Exceptions.php b/lib/Exceptions.php new file mode 100644 index 00000000..4dec524d --- /dev/null +++ b/lib/Exceptions.php @@ -0,0 +1,81 @@ + $e->getMessage(), + ], $status)); + } + + public static function NotLoggedIn(): HttpResponseException + { + return new HttpResponseException(new DataResponse([ + 'message' => 'User not logged in', + ], Http::STATUS_PRECONDITION_FAILED)); + } + + public static function NotEnabled(string $app): HttpResponseException + { + return new HttpResponseException(new DataResponse([ + 'message' => "{$app} app not enabled or not the required version.", + ], Http::STATUS_PRECONDITION_FAILED)); + } + + public static function NoRequestRoot(): HttpResponseException + { + return new HttpResponseException(new DataResponse([ + 'message' => 'Request root could not be determined', + ], Http::STATUS_NOT_FOUND)); + } + + public static function NotFound(string $ctx): HttpResponseException + { + return new HttpResponseException(new DataResponse([ + 'message' => "Not found ({$ctx})", + ], Http::STATUS_NOT_FOUND)); + } + + public static function NotFoundFile($identifier): HttpResponseException + { + return new HttpResponseException(new DataResponse([ + 'message' => "File not found ({$identifier})", + ], Http::STATUS_NOT_FOUND)); + } + + public static function Forbidden(string $ctx): HttpResponseException + { + return new HttpResponseException(new DataResponse([ + 'message' => "Forbidden ({$ctx})", + ], Http::STATUS_FORBIDDEN)); + } + + public static function ForbiddenFileUpdate(string $name): HttpResponseException + { + return new HttpResponseException(new DataResponse([ + 'message' => "Forbidden ({$name} cannot be updated)", + ], Http::STATUS_FORBIDDEN)); + } + + public static function MissingParameter(string $name): HttpResponseException + { + return new HttpResponseException(new DataResponse([ + 'message' => "Missing parameter ({$name})", + ], Http::STATUS_BAD_REQUEST)); + } + + public static function BadRequest(string $ctx): HttpResponseException + { + return new HttpResponseException(new DataResponse([ + 'message' => "Bad Request ({$ctx})", + ], Http::STATUS_BAD_REQUEST)); + } +} diff --git a/lib/Util.php b/lib/Util.php index 03c40aae..3ace47f0 100644 --- a/lib/Util.php +++ b/lib/Util.php @@ -16,6 +16,8 @@ use OCP\IConfig; class Util { + use UtilController; + public static $TAG_DAYID_START = -(1 << 30); // the world surely didn't exist public static $TAG_DAYID_FOLDERS = -(1 << 30) + 1; diff --git a/lib/UtilController.php b/lib/UtilController.php new file mode 100644 index 00000000..ef2795b9 --- /dev/null +++ b/lib/UtilController.php @@ -0,0 +1,70 @@ +response; + } catch (\Exception $e) { + return new DataResponse([ + 'message' => $e->getMessage(), + ], Http::STATUS_INTERNAL_SERVER_ERROR); + } + } + + /** + * Get the current user. + * + * @throws \OCA\Memories\HttpResponseException if the user is not logged in + */ + public static function getUser(): \OCP\IUser + { + $user = \OC::$server->get(\OCP\IUserSession::class)->getUser(); + if (null === $user) { + throw Exceptions::NotLoggedIn(); + } + + return $user; + } + + /** + * Get the current user ID. + * + * @throws \OCA\Memories\HttpResponseException if the user is not logged in + */ + public static function getUID(): string + { + return self::getUser()->getUID(); + } + + /** + * Get a user's home folder. + * + * @param null|string $uid User ID, or null for current user + * + * @throws \OCA\Memories\HttpResponseException if the user is not logged in + */ + public static function getUserFolder(?string $uid = null): \OCP\Files\Folder + { + if (null === $uid) { + $uid = self::getUID(); + } + + return \OC::$server->get(\OCP\Files\IRootFolder::class)->getUserFolder($uid); + } +}