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\Db\TimelineQuery;
use OCA\Memories\Db\TimelineRoot;
use OCA\Memories\Db\TimelineWrite;
use OCA\Memories\Exif;
use OCP\App\IAppManager;
@ -88,12 +89,14 @@ class ApiBase extends Controller
return $user ? $user->getUID() : '';
}
/** Get the Folder object relevant to the request */
protected function getRequestFolder()
/** Get the TimelineRoot object relevant to the request */
protected function getRequestRoot()
{
$root = new TimelineRoot();
// Albums have no folder
if ($this->request->getParam('album')) {
return null;
return $root;
}
// Public shared folder
@ -103,7 +106,9 @@ class ApiBase extends Controller
throw new \Exception('Share not found or invalid');
}
return $share;
$root->addFolder($share);
return $root;
}
// Anything else needs a user
@ -135,7 +140,13 @@ class ApiBase extends Controller
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;
use OCA\Memories\Db\TimelineRoot;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\Files\Folder;
class DaysController extends ApiBase
{
@ -42,10 +42,10 @@ class DaysController extends ApiBase
$uid = $this->getUid();
// Get the folder to show
$folder = null;
$root = null;
try {
$folder = $this->getRequestFolder();
$root = $this->getRequestRoot();
} catch (\Exception $e) {
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND);
}
@ -53,7 +53,7 @@ class DaysController extends ApiBase
// Run actual query
try {
$list = $this->timelineQuery->getDays(
$folder,
$root,
$uid,
$this->isRecursive(),
$this->isArchive(),
@ -65,7 +65,7 @@ class DaysController extends ApiBase
$list = $this->timelineQuery->daysToMonths($list);
} else {
// Preload some day responses
$this->preloadDays($list, $uid, $folder);
$this->preloadDays($list, $uid, $root);
}
// Reverse response if requested. Folders still stay at top.
@ -75,7 +75,7 @@ class DaysController extends ApiBase
// Add subfolder info if querying non-recursively
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);
@ -111,10 +111,10 @@ class DaysController extends ApiBase
}
// Get the folder to show
$folder = null;
$root = null;
try {
$folder = $this->getRequestFolder();
$root = $this->getRequestRoot();
} catch (\Exception $e) {
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND);
}
@ -127,7 +127,7 @@ class DaysController extends ApiBase
// Run actual query
try {
$list = $this->timelineQuery->getDay(
$folder,
$root,
$uid,
$dayIds,
$this->isRecursive(),
@ -237,11 +237,11 @@ class DaysController extends ApiBase
/**
* Preload a few "day" at the start of "days" response.
*
* @param array $days the days array
* @param string $uid User ID or blank for public shares
* @param null|Folder $folder the folder to search in
* @param array $days the days array
* @param string $uid User ID or blank for public shares
* @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);
$preloaded = 0;
@ -263,7 +263,7 @@ class DaysController extends ApiBase
if (\count($preloadDayIds) > 0) {
$allDetails = $this->timelineQuery->getDay(
$folder,
$root,
$uid,
$preloadDayIds,
$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
$folder = $this->getRequestFolder();
if (null === $folder) {
$root = $this->getRequestRoot();
if ($root->isEmpty()) {
return new JSONResponse([], Http::STATUS_NOT_FOUND);
}
// Run actual query
$list = $this->timelineQuery->getFaces(
$folder,
$root,
);
return new JSONResponse($list, Http::STATUS_OK);
@ -84,22 +84,23 @@ class FacesController extends ApiBase
}
// Get folder to search for
$folder = $this->getRequestFolder();
if (null === $folder) {
$root = $this->getRequestRoot();
if ($root->isEmpty()) {
return new JSONResponse([], Http::STATUS_NOT_FOUND);
}
// Run actual query
$detections = $this->timelineQuery->getFacePreviewDetection($folder, (int) $id);
$detections = $this->timelineQuery->getFacePreviewDetection($root, (int) $id);
if (null === $detections || 0 === \count($detections)) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
// Find the first detection that has a preview
$preview = null;
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
foreach ($detections as &$detection) {
// 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()) {
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
$folder = $this->getRequestFolder();
if (null === $folder) {
$root = $this->getRequestRoot();
if ($root->isEmpty()) {
return new JSONResponse([], Http::STATUS_NOT_FOUND);
}
// Run actual query
$list = $this->timelineQuery->getTags(
$folder,
$root,
);
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
$folder = $this->getRequestFolder();
if (null === $folder) {
$root = $this->getRequestRoot();
if ($root->isEmpty()) {
return new JSONResponse([], Http::STATUS_NOT_FOUND);
}
@ -88,7 +88,7 @@ class TagsController extends ApiBase
// Run actual query
$list = $this->timelineQuery->getTagPreviews(
$tagName,
$folder,
$root,
);
return new JSONResponse($list, Http::STATUS_OK);

View File

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace OCA\Memories\Db;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\Folder;
use OCP\IDBConnection;
const CTE_FOLDERS = // CTE to get all folders recursively in the given top folders excluding archive
@ -73,21 +72,18 @@ trait TimelineQueryDays
{
protected IDBConnection $connection;
/** Map of rootid => mount point */
private $topFolderPaths = [];
/**
* Get the days response from the database for the timeline.
*
* @param null|Folder $folder The folder to get the days from
* @param bool $recursive Whether to get the days recursively
* @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 TimelineRoot $root The root to get the days from
* @param bool $recursive Whether to get the days recursively
* @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
*
* @return array The days response
*/
public function getDays(
&$folder,
TimelineRoot &$root,
string $uid,
bool $recursive,
bool $archive,
@ -100,7 +96,7 @@ trait TimelineQueryDays
$query->select('m.dayid', $count)
->from('memories', 'm')
;
$query = $this->joinFilecache($query, $folder, $recursive, $archive);
$query = $this->joinFilecache($query, $root, $recursive, $archive);
// Group and sort by dayid
$query->groupBy('m.dayid')
@ -120,18 +116,18 @@ trait TimelineQueryDays
/**
* Get the day response from the database for the timeline.
*
* @param null|Folder $folder The folder to get the day from
* @param string $uid The user id
* @param int[] $day_ids The day ids to fetch
* @param bool $recursive If the query should be recursive
* @param bool $archive If the query should include only the archive folder
* @param array $queryTransforms The query transformations to apply
* @param mixed $day_ids
* @param TimelineRoot $root The root to get the day from
* @param string $uid The user id
* @param int[] $day_ids The day ids to fetch
* @param bool $recursive If the query should be recursive
* @param bool $archive If the query should include only the archive folder
* @param array $queryTransforms The query transformations to apply
* @param mixed $day_ids
*
* @return array An array of day responses
*/
public function getDay(
&$folder,
TimelineRoot &$root,
string $uid,
$day_ids,
bool $recursive,
@ -151,7 +147,7 @@ trait TimelineQueryDays
;
// 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');
// JOIN with mimetypes to get the mimetype
@ -180,7 +176,7 @@ trait TimelineQueryDays
$rows = $cursor->fetchAll();
$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.
*
* @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.
@ -225,15 +217,15 @@ trait TimelineQueryDays
*/
$defaultRootId = 0;
if (null !== $folder) {
if (!$root->isEmpty()) {
// Get root id of the top folder
$defaultRootId = $folder->getId();
$defaultRootId = $root->getOneId();
// No way to get the internal path from the folder
$query = $this->connection->getQueryBuilder();
$query->select('fileid', 'path')
->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();
foreach ($paths as &$path) {
@ -244,7 +236,7 @@ trait TimelineQueryDays
// getPath looks like /user/files/... but we want /files/user/...
// Split at / and swap these
// 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);
if (\count($actualPath) >= 3) {
$tmp = $actualPath[1];
@ -317,10 +309,11 @@ trait TimelineQueryDays
*/
private function addSubfolderJoinParams(
IQueryBuilder &$query,
TimelineRoot &$root,
bool $archive
) {
// 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);
}
@ -328,44 +321,32 @@ trait TimelineQueryDays
* Inner join with oc_filecache.
*
* @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 $archive Whether to get the days only from the archive folder
*/
private function joinFilecache(
IQueryBuilder &$query,
&$folder,
TimelineRoot &$root,
bool $recursive,
bool $archive
) {
// Join with memories
$baseOp = $query->expr()->eq('f.fileid', 'm.fileid');
if (null === $folder) {
if ($root->isEmpty()) {
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)
$pathOp = null;
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
$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->addSelect('cte_f.rootid');
} else {
// 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(

View File

@ -47,7 +47,7 @@ trait TimelineQueryFaces
);
}
public function getFaces(Folder $folder)
public function getFaces(TimelineRoot &$root)
{
$query = $this->connection->getQueryBuilder();
@ -62,7 +62,7 @@ trait TimelineQueryFaces
$query->innerJoin('rfd', 'memories', 'm', $query->expr()->eq('m.fileid', 'rfd.file_id'));
// 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
$query->groupBy('rfc.id');
@ -87,7 +87,7 @@ trait TimelineQueryFaces
return $faces;
}
public function getFacePreviewDetection(Folder &$folder, int $id)
public function getFacePreviewDetection(TimelineRoot &$root, int $id)
{
$query = $this->connection->getQueryBuilder();
@ -109,7 +109,7 @@ trait TimelineQueryFaces
$query->innerJoin('rfd', 'memories', 'm', $query->expr()->eq('m.fileid', 'rfd.file_id'));
// 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
$query->setMaxResults(15);

View File

@ -19,7 +19,9 @@ trait TimelineQueryFolders
$query->select('f.fileid', 'f.etag')->from('memories', 'm');
// 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
$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();
@ -58,7 +58,7 @@ trait TimelineQueryTags
$query->innerJoin('stom', 'memories', 'm', $query->expr()->eq('m.objectid', 'stom.objectid'));
// 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
$query->groupBy('st.id');
@ -78,7 +78,7 @@ trait TimelineQueryTags
return $tags;
}
public function getTagPreviews(string $tagName, Folder &$folder)
public function getTagPreviews(string $tagName, TimelineRoot &$root)
{
$query = $this->connection->getQueryBuilder();
$tagId = $this->getSystemTagId($query, $tagName);
@ -99,7 +99,7 @@ trait TimelineQueryTags
$query->innerJoin('stom', 'memories', 'm', $query->expr()->eq('m.objectid', 'stom.objectid'));
// 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
$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);
}
}