Refactor all calls to use TimelineRoot

pull/221/head
Varun Patil 2022-11-15 23:45:01 -08:00
parent 4a5408b846
commit d9afbbe710
9 changed files with 150 additions and 88 deletions

View File

@ -25,6 +25,7 @@ namespace OCA\Memories\Controller;
use OCA\Memories\AppInfo\Application; use OCA\Memories\AppInfo\Application;
use OCA\Memories\Db\TimelineQuery; use OCA\Memories\Db\TimelineQuery;
use OCA\Memories\Db\TimelineRoot;
use OCA\Memories\Db\TimelineWrite; use OCA\Memories\Db\TimelineWrite;
use OCA\Memories\Exif; use OCA\Memories\Exif;
use OCP\App\IAppManager; use OCP\App\IAppManager;
@ -88,12 +89,14 @@ class ApiBase extends Controller
return $user ? $user->getUID() : ''; return $user ? $user->getUID() : '';
} }
/** Get the Folder object relevant to the request */ /** Get the TimelineRoot object relevant to the request */
protected function getRequestFolder() protected function getRequestRoot()
{ {
$root = new TimelineRoot();
// Albums have no folder // Albums have no folder
if ($this->request->getParam('album')) { if ($this->request->getParam('album')) {
return null; return $root;
} }
// Public shared folder // Public shared folder
@ -103,7 +106,9 @@ class ApiBase extends Controller
throw new \Exception('Share not found or invalid'); throw new \Exception('Share not found or invalid');
} }
return $share; $root->addFolder($share);
return $root;
} }
// Anything else needs a user // Anything else needs a user
@ -135,7 +140,13 @@ class ApiBase extends Controller
throw new \Exception('Folder not readable'); throw new \Exception('Folder not readable');
} }
return $folder; // Don't add mount points for folder view
$root->addFolder($folder);
if (null === $folderPath) {
$root->addMountPoints();
}
return $root;
} }
/** /**

View File

@ -23,9 +23,9 @@ declare(strict_types=1);
namespace OCA\Memories\Controller; namespace OCA\Memories\Controller;
use OCA\Memories\Db\TimelineRoot;
use OCP\AppFramework\Http; use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\JSONResponse;
use OCP\Files\Folder;
class DaysController extends ApiBase class DaysController extends ApiBase
{ {
@ -42,10 +42,10 @@ class DaysController extends ApiBase
$uid = $this->getUid(); $uid = $this->getUid();
// Get the folder to show // Get the folder to show
$folder = null; $root = null;
try { try {
$folder = $this->getRequestFolder(); $root = $this->getRequestRoot();
} catch (\Exception $e) { } catch (\Exception $e) {
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND); return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND);
} }
@ -53,7 +53,7 @@ class DaysController extends ApiBase
// Run actual query // Run actual query
try { try {
$list = $this->timelineQuery->getDays( $list = $this->timelineQuery->getDays(
$folder, $root,
$uid, $uid,
$this->isRecursive(), $this->isRecursive(),
$this->isArchive(), $this->isArchive(),
@ -65,7 +65,7 @@ class DaysController extends ApiBase
$list = $this->timelineQuery->daysToMonths($list); $list = $this->timelineQuery->daysToMonths($list);
} else { } else {
// Preload some day responses // Preload some day responses
$this->preloadDays($list, $uid, $folder); $this->preloadDays($list, $uid, $root);
} }
// Reverse response if requested. Folders still stay at top. // Reverse response if requested. Folders still stay at top.
@ -75,7 +75,7 @@ class DaysController extends ApiBase
// Add subfolder info if querying non-recursively // Add subfolder info if querying non-recursively
if (!$this->isRecursive()) { if (!$this->isRecursive()) {
array_unshift($list, $this->getSubfoldersEntry($folder)); array_unshift($list, $this->getSubfoldersEntry($root->getFolder($root->getOneId())));
} }
return new JSONResponse($list, Http::STATUS_OK); return new JSONResponse($list, Http::STATUS_OK);
@ -111,10 +111,10 @@ class DaysController extends ApiBase
} }
// Get the folder to show // Get the folder to show
$folder = null; $root = null;
try { try {
$folder = $this->getRequestFolder(); $root = $this->getRequestRoot();
} catch (\Exception $e) { } catch (\Exception $e) {
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND); return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND);
} }
@ -127,7 +127,7 @@ class DaysController extends ApiBase
// Run actual query // Run actual query
try { try {
$list = $this->timelineQuery->getDay( $list = $this->timelineQuery->getDay(
$folder, $root,
$uid, $uid,
$dayIds, $dayIds,
$this->isRecursive(), $this->isRecursive(),
@ -239,9 +239,9 @@ class DaysController extends ApiBase
* *
* @param array $days the days array * @param array $days the days array
* @param string $uid User ID or blank for public shares * @param string $uid User ID or blank for public shares
* @param null|Folder $folder the folder to search in * @param TimelineRoot $root the root folder
*/ */
private function preloadDays(array &$days, string $uid, &$folder) private function preloadDays(array &$days, string $uid, TimelineRoot &$root)
{ {
$transforms = $this->getTransformations(false); $transforms = $this->getTransformations(false);
$preloaded = 0; $preloaded = 0;
@ -263,7 +263,7 @@ class DaysController extends ApiBase
if (\count($preloadDayIds) > 0) { if (\count($preloadDayIds) > 0) {
$allDetails = $this->timelineQuery->getDay( $allDetails = $this->timelineQuery->getDay(
$folder, $root,
$uid, $uid,
$preloadDayIds, $preloadDayIds,
$this->isRecursive(), $this->isRecursive(),

View File

@ -49,14 +49,14 @@ class FacesController extends ApiBase
} }
// If this isn't the timeline folder then things aren't going to work // If this isn't the timeline folder then things aren't going to work
$folder = $this->getRequestFolder(); $root = $this->getRequestRoot();
if (null === $folder) { if ($root->isEmpty()) {
return new JSONResponse([], Http::STATUS_NOT_FOUND); return new JSONResponse([], Http::STATUS_NOT_FOUND);
} }
// Run actual query // Run actual query
$list = $this->timelineQuery->getFaces( $list = $this->timelineQuery->getFaces(
$folder, $root,
); );
return new JSONResponse($list, Http::STATUS_OK); return new JSONResponse($list, Http::STATUS_OK);
@ -84,22 +84,23 @@ class FacesController extends ApiBase
} }
// Get folder to search for // Get folder to search for
$folder = $this->getRequestFolder(); $root = $this->getRequestRoot();
if (null === $folder) { if ($root->isEmpty()) {
return new JSONResponse([], Http::STATUS_NOT_FOUND); return new JSONResponse([], Http::STATUS_NOT_FOUND);
} }
// Run actual query // Run actual query
$detections = $this->timelineQuery->getFacePreviewDetection($folder, (int) $id); $detections = $this->timelineQuery->getFacePreviewDetection($root, (int) $id);
if (null === $detections || 0 === \count($detections)) { if (null === $detections || 0 === \count($detections)) {
return new DataResponse([], Http::STATUS_NOT_FOUND); return new DataResponse([], Http::STATUS_NOT_FOUND);
} }
// Find the first detection that has a preview // Find the first detection that has a preview
$preview = null; $preview = null;
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
foreach ($detections as &$detection) { foreach ($detections as &$detection) {
// Get the file (also checks permissions) // Get the file (also checks permissions)
$files = $folder->getById($detection['file_id']); $files = $userFolder->getById($detection['file_id']);
if (0 === \count($files) || FileInfo::TYPE_FILE !== $files[0]->getType()) { if (0 === \count($files) || FileInfo::TYPE_FILE !== $files[0]->getType()) {
continue; continue;
} }

View File

@ -46,14 +46,14 @@ class TagsController extends ApiBase
} }
// If this isn't the timeline folder then things aren't going to work // If this isn't the timeline folder then things aren't going to work
$folder = $this->getRequestFolder(); $root = $this->getRequestRoot();
if (null === $folder) { if ($root->isEmpty()) {
return new JSONResponse([], Http::STATUS_NOT_FOUND); return new JSONResponse([], Http::STATUS_NOT_FOUND);
} }
// Run actual query // Run actual query
$list = $this->timelineQuery->getTags( $list = $this->timelineQuery->getTags(
$folder, $root,
); );
return new JSONResponse($list, Http::STATUS_OK); return new JSONResponse($list, Http::STATUS_OK);
@ -77,8 +77,8 @@ class TagsController extends ApiBase
} }
// If this isn't the timeline folder then things aren't going to work // If this isn't the timeline folder then things aren't going to work
$folder = $this->getRequestFolder(); $root = $this->getRequestRoot();
if (null === $folder) { if ($root->isEmpty()) {
return new JSONResponse([], Http::STATUS_NOT_FOUND); return new JSONResponse([], Http::STATUS_NOT_FOUND);
} }
@ -88,7 +88,7 @@ class TagsController extends ApiBase
// Run actual query // Run actual query
$list = $this->timelineQuery->getTagPreviews( $list = $this->timelineQuery->getTagPreviews(
$tagName, $tagName,
$folder, $root,
); );
return new JSONResponse($list, Http::STATUS_OK); return new JSONResponse($list, Http::STATUS_OK);

View File

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace OCA\Memories\Db; namespace OCA\Memories\Db;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\Folder;
use OCP\IDBConnection; use OCP\IDBConnection;
const CTE_FOLDERS = // CTE to get all folders recursively in the given top folders excluding archive const CTE_FOLDERS = // CTE to get all folders recursively in the given top folders excluding archive
@ -73,13 +72,10 @@ trait TimelineQueryDays
{ {
protected IDBConnection $connection; protected IDBConnection $connection;
/** Map of rootid => mount point */
private $topFolderPaths = [];
/** /**
* Get the days response from the database for the timeline. * Get the days response from the database for the timeline.
* *
* @param null|Folder $folder The folder to get the days from * @param TimelineRoot $root The root to get the days from
* @param bool $recursive Whether to get the days recursively * @param bool $recursive Whether to get the days recursively
* @param bool $archive Whether to get the days only from the archive folder * @param bool $archive Whether to get the days only from the archive folder
* @param array $queryTransforms An array of query transforms to apply to the query * @param array $queryTransforms An array of query transforms to apply to the query
@ -87,7 +83,7 @@ trait TimelineQueryDays
* @return array The days response * @return array The days response
*/ */
public function getDays( public function getDays(
&$folder, TimelineRoot &$root,
string $uid, string $uid,
bool $recursive, bool $recursive,
bool $archive, bool $archive,
@ -100,7 +96,7 @@ trait TimelineQueryDays
$query->select('m.dayid', $count) $query->select('m.dayid', $count)
->from('memories', 'm') ->from('memories', 'm')
; ;
$query = $this->joinFilecache($query, $folder, $recursive, $archive); $query = $this->joinFilecache($query, $root, $recursive, $archive);
// Group and sort by dayid // Group and sort by dayid
$query->groupBy('m.dayid') $query->groupBy('m.dayid')
@ -120,7 +116,7 @@ trait TimelineQueryDays
/** /**
* Get the day response from the database for the timeline. * Get the day response from the database for the timeline.
* *
* @param null|Folder $folder The folder to get the day from * @param TimelineRoot $root The root to get the day from
* @param string $uid The user id * @param string $uid The user id
* @param int[] $day_ids The day ids to fetch * @param int[] $day_ids The day ids to fetch
* @param bool $recursive If the query should be recursive * @param bool $recursive If the query should be recursive
@ -131,7 +127,7 @@ trait TimelineQueryDays
* @return array An array of day responses * @return array An array of day responses
*/ */
public function getDay( public function getDay(
&$folder, TimelineRoot &$root,
string $uid, string $uid,
$day_ids, $day_ids,
bool $recursive, bool $recursive,
@ -151,7 +147,7 @@ trait TimelineQueryDays
; ;
// JOIN with filecache for existing files // JOIN with filecache for existing files
$query = $this->joinFilecache($query, $folder, $recursive, $archive); $query = $this->joinFilecache($query, $root, $recursive, $archive);
$query->addSelect('f.etag', 'f.path', 'f.name AS basename'); $query->addSelect('f.etag', 'f.path', 'f.name AS basename');
// JOIN with mimetypes to get the mimetype // JOIN with mimetypes to get the mimetype
@ -180,7 +176,7 @@ trait TimelineQueryDays
$rows = $cursor->fetchAll(); $rows = $cursor->fetchAll();
$cursor->closeCursor(); $cursor->closeCursor();
return $this->processDay($rows, $uid, $folder); return $this->processDay($rows, $uid, $root);
} }
/** /**
@ -200,12 +196,8 @@ trait TimelineQueryDays
/** /**
* Process the single day response. * Process the single day response.
*
* @param array $day
* @param string $uid User or blank if not logged in
* @param null|Folder $folder
*/ */
private function processDay(&$day, $uid, $folder) private function processDay(array &$day, string $uid, TimelineRoot &$root)
{ {
/** /**
* Path entry in database for folder. * Path entry in database for folder.
@ -225,15 +217,15 @@ trait TimelineQueryDays
*/ */
$defaultRootId = 0; $defaultRootId = 0;
if (null !== $folder) { if (!$root->isEmpty()) {
// Get root id of the top folder // Get root id of the top folder
$defaultRootId = $folder->getId(); $defaultRootId = $root->getOneId();
// No way to get the internal path from the folder // No way to get the internal path from the folder
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
$query->select('fileid', 'path') $query->select('fileid', 'path')
->from('filecache') ->from('filecache')
->where($query->expr()->in('fileid', $query->createNamedParameter(array_keys($this->topFolderPaths), IQueryBuilder::PARAM_INT_ARRAY))) ->where($query->expr()->in('fileid', $query->createNamedParameter($root->getIds(), IQueryBuilder::PARAM_INT_ARRAY)))
; ;
$paths = $query->executeQuery()->fetchAll(); $paths = $query->executeQuery()->fetchAll();
foreach ($paths as &$path) { foreach ($paths as &$path) {
@ -244,7 +236,7 @@ trait TimelineQueryDays
// getPath looks like /user/files/... but we want /files/user/... // getPath looks like /user/files/... but we want /files/user/...
// Split at / and swap these // Split at / and swap these
// For public shares, we just give the relative path // For public shares, we just give the relative path
if (!empty($uid) && ($actualPath = $this->topFolderPaths[$fileid])) { if (!empty($uid) && ($actualPath = $root->getFolderPath($fileid))) {
$actualPath = explode('/', $actualPath); $actualPath = explode('/', $actualPath);
if (\count($actualPath) >= 3) { if (\count($actualPath) >= 3) {
$tmp = $actualPath[1]; $tmp = $actualPath[1];
@ -317,10 +309,11 @@ trait TimelineQueryDays
*/ */
private function addSubfolderJoinParams( private function addSubfolderJoinParams(
IQueryBuilder &$query, IQueryBuilder &$query,
TimelineRoot &$root,
bool $archive bool $archive
) { ) {
// Add query parameters // Add query parameters
$query->setParameter('topFolderIds', array_keys($this->topFolderPaths), IQueryBuilder::PARAM_INT_ARRAY); $query->setParameter('topFolderIds', $root->getIds(), IQueryBuilder::PARAM_INT_ARRAY);
$query->setParameter('cteFoldersArchive', $archive, IQueryBuilder::PARAM_BOOL); $query->setParameter('cteFoldersArchive', $archive, IQueryBuilder::PARAM_BOOL);
} }
@ -328,44 +321,32 @@ trait TimelineQueryDays
* Inner join with oc_filecache. * Inner join with oc_filecache.
* *
* @param IQueryBuilder $query Query builder * @param IQueryBuilder $query Query builder
* @param null|Folder $folder Either the top folder or null for all * @param TimelineRoot $root Either the top folder or null for all
* @param bool $recursive Whether to get the days recursively * @param bool $recursive Whether to get the days recursively
* @param bool $archive Whether to get the days only from the archive folder * @param bool $archive Whether to get the days only from the archive folder
*/ */
private function joinFilecache( private function joinFilecache(
IQueryBuilder &$query, IQueryBuilder &$query,
&$folder, TimelineRoot &$root,
bool $recursive, bool $recursive,
bool $archive bool $archive
) { ) {
// Join with memories // Join with memories
$baseOp = $query->expr()->eq('f.fileid', 'm.fileid'); $baseOp = $query->expr()->eq('f.fileid', 'm.fileid');
if (null === $folder) { if ($root->isEmpty()) {
return $query->innerJoin('m', 'filecache', 'f', $baseOp); return $query->innerJoin('m', 'filecache', 'f', $baseOp);
} }
// Create top folders paths for later processing
$this->topFolderPaths = [];
$this->topFolderPaths[$folder->getId()] = $folder->getPath();
// Filter by folder (recursive or otherwise) // Filter by folder (recursive or otherwise)
$pathOp = null; $pathOp = null;
if ($recursive) { if ($recursive) {
// Add mountpoints recursively
$this->mounts = \OC\Files\Filesystem::getMountManager()->findIn($folder->getPath());
foreach ($this->mounts as &$mount) {
$id = $mount->getStorageRootId();
$path = $mount->getMountPoint();
$this->topFolderPaths[$id] = $path;
}
// Join with folders CTE // Join with folders CTE
$this->addSubfolderJoinParams($query, $archive); $this->addSubfolderJoinParams($query, $root, $archive);
$query->innerJoin('f', 'cte_folders', 'cte_f', $query->expr()->eq('f.parent', 'cte_f.fileid')); $query->innerJoin('f', 'cte_folders', 'cte_f', $query->expr()->eq('f.parent', 'cte_f.fileid'));
$query->addSelect('cte_f.rootid'); $query->addSelect('cte_f.rootid');
} else { } else {
// If getting non-recursively folder only check for parent // If getting non-recursively folder only check for parent
$pathOp = $query->expr()->eq('f.parent', $query->createNamedParameter($folder->getId(), IQueryBuilder::PARAM_INT)); $pathOp = $query->expr()->eq('f.parent', $query->createNamedParameter($root->getOneId(), IQueryBuilder::PARAM_INT));
} }
return $query->innerJoin('m', 'filecache', 'f', $query->expr()->andX( return $query->innerJoin('m', 'filecache', 'f', $query->expr()->andX(

View File

@ -47,7 +47,7 @@ trait TimelineQueryFaces
); );
} }
public function getFaces(Folder $folder) public function getFaces(TimelineRoot &$root)
{ {
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
@ -62,7 +62,7 @@ trait TimelineQueryFaces
$query->innerJoin('rfd', 'memories', 'm', $query->expr()->eq('m.fileid', 'rfd.file_id')); $query->innerJoin('rfd', 'memories', 'm', $query->expr()->eq('m.fileid', 'rfd.file_id'));
// WHERE these photos are in the user's requested folder recursively // WHERE these photos are in the user's requested folder recursively
$query = $this->joinFilecache($query, $folder, true, false); $query = $this->joinFilecache($query, $root, true, false);
// GROUP by ID of face cluster // GROUP by ID of face cluster
$query->groupBy('rfc.id'); $query->groupBy('rfc.id');
@ -87,7 +87,7 @@ trait TimelineQueryFaces
return $faces; return $faces;
} }
public function getFacePreviewDetection(Folder &$folder, int $id) public function getFacePreviewDetection(TimelineRoot &$root, int $id)
{ {
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
@ -109,7 +109,7 @@ trait TimelineQueryFaces
$query->innerJoin('rfd', 'memories', 'm', $query->expr()->eq('m.fileid', 'rfd.file_id')); $query->innerJoin('rfd', 'memories', 'm', $query->expr()->eq('m.fileid', 'rfd.file_id'));
// WHERE these photos are in the user's requested folder recursively // WHERE these photos are in the user's requested folder recursively
$query = $this->joinFilecache($query, $folder, true, false); $query = $this->joinFilecache($query, $root, true, false);
// LIMIT results // LIMIT results
$query->setMaxResults(15); $query->setMaxResults(15);

View File

@ -19,7 +19,9 @@ trait TimelineQueryFolders
$query->select('f.fileid', 'f.etag')->from('memories', 'm'); $query->select('f.fileid', 'f.etag')->from('memories', 'm');
// WHERE these photos are in the user's requested folder recursively // WHERE these photos are in the user's requested folder recursively
$query = $this->joinFilecache($query, $folder, true, false); $root = new TimelineRoot();
$root->addFolder($folder);
$query = $this->joinFilecache($query, $root, true, false);
// ORDER descending by fileid // ORDER descending by fileid
$query->orderBy('f.fileid', 'DESC'); $query->orderBy('f.fileid', 'DESC');

View File

@ -38,7 +38,7 @@ trait TimelineQueryTags
)); ));
} }
public function getTags(Folder $folder) public function getTags(TimelineRoot &$root)
{ {
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
@ -58,7 +58,7 @@ trait TimelineQueryTags
$query->innerJoin('stom', 'memories', 'm', $query->expr()->eq('m.objectid', 'stom.objectid')); $query->innerJoin('stom', 'memories', 'm', $query->expr()->eq('m.objectid', 'stom.objectid'));
// WHERE these photos are in the user's requested folder recursively // WHERE these photos are in the user's requested folder recursively
$query = $this->joinFilecache($query, $folder, true, false); $query = $this->joinFilecache($query, $root, true, false);
// GROUP and ORDER by tag name // GROUP and ORDER by tag name
$query->groupBy('st.id'); $query->groupBy('st.id');
@ -78,7 +78,7 @@ trait TimelineQueryTags
return $tags; return $tags;
} }
public function getTagPreviews(string $tagName, Folder &$folder) public function getTagPreviews(string $tagName, TimelineRoot &$root)
{ {
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
$tagId = $this->getSystemTagId($query, $tagName); $tagId = $this->getSystemTagId($query, $tagName);
@ -99,7 +99,7 @@ trait TimelineQueryTags
$query->innerJoin('stom', 'memories', 'm', $query->expr()->eq('m.objectid', 'stom.objectid')); $query->innerJoin('stom', 'memories', 'm', $query->expr()->eq('m.objectid', 'stom.objectid'));
// WHERE these photos are in the user's requested folder recursively // WHERE these photos are in the user's requested folder recursively
$query = $this->joinFilecache($query, $folder, true, false); $query = $this->joinFilecache($query, $root, true, false);
// MAX 4 // MAX 4
$query->setMaxResults(4); $query->setMaxResults(4);

View File

@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace OCA\Memories\Db;
use OCP\Files\Folder;
class TimelineRoot
{
protected array $folders;
protected array $folderPaths;
/** Initialize */
public function __construct()
{
}
public function addFolder(Folder &$folder)
{
// Add top level folder
$id = $folder->getId();
$folderPath = $folder->getPath();
$this->folders[$id] = $folder;
$this->folderPaths[$id] = $folderPath;
}
// Add mountpoints recursively
public function addMountPoints()
{
$mp = [];
foreach ($this->folderPaths as $id => $folderPath) {
$mounts = \OC\Files\Filesystem::getMountManager()->findIn($folderPath);
foreach ($mounts as &$mount) {
$id = $mount->getStorageRootId();
$path = $mount->getMountPoint();
$mp[$id] = $path;
}
}
$this->folderPaths += $mp;
}
public function getFolderPath(int $id)
{
return $this->folderPaths[$id];
}
public function getIds()
{
return array_keys($this->folderPaths);
}
public function getOneId()
{
return array_key_first($this->folders);
}
public function getFolder(int $id)
{
return $this->folders[$id];
}
public function isEmpty()
{
return empty($this->folderPaths);
}
}