refactor: rework controller (1)

Signed-off-by: Varun Patil <varunpatil@ucla.edu>
pull/563/head
Varun Patil 2023-03-23 13:32:23 -07:00
parent bd6aaeee3a
commit 0e385d2283
17 changed files with 781 additions and 783 deletions

View File

@ -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

View File

@ -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);
});
}
}

View File

@ -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');
}
}

View File

@ -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);
});
}
/**

View File

@ -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');

View File

@ -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 [

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
});
}
}

View File

@ -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);
});
}
/**

View File

@ -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;

View File

@ -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);
});
}
}

View File

@ -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;
});
}
/**

View File

@ -1,74 +0,0 @@
<?php
declare(strict_types=1);
namespace OCA\Memories;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
class Errors
{
public static function Generic(\Exception $e, int $status = Http::STATUS_INTERNAL_SERVER_ERROR): Http\Response
{
return new DataResponse([
'message' => $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);
}
}

81
lib/Exceptions.php 100644
View File

@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace OCA\Memories;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
class Exceptions
{
public static function Generic(\Exception $e, int $status = Http::STATUS_INTERNAL_SERVER_ERROR): HttpResponseException
{
return new HttpResponseException(new DataResponse([
'message' => $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));
}
}

View File

@ -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;

View File

@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace OCA\Memories;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
trait UtilController
{
/**
* Run a function and catch exceptions to return HTTP response.
*
* @param Function $function
*/
public static function guardEx($function): Http\Response
{
try {
return $function();
} catch (\OCA\Memories\HttpResponseException $e) {
return $e->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);
}
}