2022-09-11 02:05:04 +00:00
|
|
|
<?php
|
2022-10-19 17:10:36 +00:00
|
|
|
|
2022-09-11 02:05:04 +00:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace OCA\Memories\Db;
|
|
|
|
|
2023-03-24 00:56:41 +00:00
|
|
|
use OCA\Memories\Manager\ClustersBackendManager;
|
2022-09-11 02:05:04 +00:00
|
|
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
2022-10-19 17:10:36 +00:00
|
|
|
use OCP\IDBConnection;
|
2022-09-11 02:05:04 +00:00
|
|
|
|
2022-11-15 15:12:27 +00:00
|
|
|
const CTE_FOLDERS = // CTE to get all folders recursively in the given top folders excluding archive
|
2023-03-16 16:58:43 +00:00
|
|
|
'WITH RECURSIVE *PREFIX*cte_folders_all(fileid) AS (
|
2022-10-27 16:19:25 +00:00
|
|
|
SELECT
|
2023-03-16 16:58:43 +00:00
|
|
|
f.fileid
|
2022-10-27 16:19:25 +00:00
|
|
|
FROM
|
|
|
|
*PREFIX*filecache f
|
|
|
|
WHERE
|
2022-11-15 15:12:27 +00:00
|
|
|
f.fileid IN (:topFolderIds)
|
2022-10-27 16:19:25 +00:00
|
|
|
UNION ALL
|
|
|
|
SELECT
|
2023-03-16 16:58:43 +00:00
|
|
|
f.fileid
|
2022-10-27 16:19:25 +00:00
|
|
|
FROM
|
|
|
|
*PREFIX*filecache f
|
2022-11-16 09:49:03 +00:00
|
|
|
INNER JOIN *PREFIX*cte_folders_all c
|
2022-10-27 16:19:25 +00:00
|
|
|
ON (f.parent = c.fileid
|
2022-11-01 05:59:14 +00:00
|
|
|
AND f.mimetype = (SELECT `id` FROM `*PREFIX*mimetypes` WHERE `mimetype` = \'httpd/unix-directory\')
|
2022-11-15 15:12:27 +00:00
|
|
|
AND f.name <> \'.archive\'
|
2022-10-27 16:19:25 +00:00
|
|
|
)
|
2022-11-16 09:49:03 +00:00
|
|
|
), *PREFIX*cte_folders AS (
|
|
|
|
SELECT
|
2023-03-16 16:58:43 +00:00
|
|
|
fileid
|
2022-11-16 09:49:03 +00:00
|
|
|
FROM
|
|
|
|
*PREFIX*cte_folders_all
|
|
|
|
GROUP BY
|
2023-01-20 18:50:48 +00:00
|
|
|
fileid
|
2022-10-27 16:19:25 +00:00
|
|
|
)';
|
|
|
|
|
2022-11-15 15:12:27 +00:00
|
|
|
const CTE_FOLDERS_ARCHIVE = // CTE to get all archive folders recursively in the given top folders
|
2023-03-16 16:58:43 +00:00
|
|
|
'WITH RECURSIVE *PREFIX*cte_folders_all(fileid, name) AS (
|
2022-11-15 15:12:27 +00:00
|
|
|
SELECT
|
2022-11-16 05:22:40 +00:00
|
|
|
f.fileid,
|
2023-03-16 16:58:43 +00:00
|
|
|
f.name
|
2022-11-15 15:12:27 +00:00
|
|
|
FROM
|
|
|
|
*PREFIX*filecache f
|
|
|
|
WHERE
|
|
|
|
f.fileid IN (:topFolderIds)
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
2022-11-16 05:22:40 +00:00
|
|
|
f.fileid,
|
2023-03-16 16:58:43 +00:00
|
|
|
f.name
|
2022-11-15 15:12:27 +00:00
|
|
|
FROM
|
|
|
|
*PREFIX*filecache f
|
|
|
|
INNER JOIN *PREFIX*cte_folders_all c
|
|
|
|
ON (f.parent = c.fileid
|
|
|
|
AND f.mimetype = (SELECT `id` FROM `*PREFIX*mimetypes` WHERE `mimetype` = \'httpd/unix-directory\')
|
|
|
|
)
|
2023-03-16 16:58:43 +00:00
|
|
|
), *PREFIX*cte_folders(fileid) AS (
|
2022-11-15 15:12:27 +00:00
|
|
|
SELECT
|
2023-03-16 16:58:43 +00:00
|
|
|
cfa.fileid
|
2022-11-15 15:12:27 +00:00
|
|
|
FROM
|
2022-11-16 05:22:40 +00:00
|
|
|
*PREFIX*cte_folders_all cfa
|
2022-11-15 15:12:27 +00:00
|
|
|
WHERE
|
2022-11-16 05:22:40 +00:00
|
|
|
cfa.name = \'.archive\'
|
2022-11-16 09:49:03 +00:00
|
|
|
GROUP BY
|
|
|
|
cfa.fileid
|
2022-11-15 15:12:27 +00:00
|
|
|
UNION ALL
|
|
|
|
SELECT
|
2023-03-16 16:58:43 +00:00
|
|
|
f.fileid
|
2022-11-15 15:12:27 +00:00
|
|
|
FROM
|
|
|
|
*PREFIX*filecache f
|
|
|
|
INNER JOIN *PREFIX*cte_folders c
|
|
|
|
ON (f.parent = c.fileid)
|
|
|
|
)';
|
|
|
|
|
2022-10-19 17:10:36 +00:00
|
|
|
trait TimelineQueryDays
|
|
|
|
{
|
2022-09-11 02:05:04 +00:00
|
|
|
protected IDBConnection $connection;
|
|
|
|
|
|
|
|
/**
|
2022-10-19 17:10:36 +00:00
|
|
|
* Get the days response from the database for the timeline.
|
|
|
|
*
|
2023-03-23 21:45:56 +00:00
|
|
|
* @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
|
2022-10-06 19:24:45 +00:00
|
|
|
*
|
|
|
|
* @return array The days response
|
2022-09-11 02:05:04 +00:00
|
|
|
*/
|
|
|
|
public function getDays(
|
2022-09-24 01:54:14 +00:00
|
|
|
bool $recursive,
|
2022-09-25 23:02:26 +00:00
|
|
|
bool $archive,
|
2022-09-12 01:33:38 +00:00
|
|
|
array $queryTransforms = []
|
2022-09-12 01:06:16 +00:00
|
|
|
): array {
|
2022-09-11 02:05:04 +00:00
|
|
|
$query = $this->connection->getQueryBuilder();
|
2022-09-12 01:03:40 +00:00
|
|
|
|
2022-09-24 01:54:14 +00:00
|
|
|
// Get all entries also present in filecache
|
|
|
|
$count = $query->func()->count($query->createFunction('DISTINCT m.fileid'), 'count');
|
|
|
|
$query->select('m.dayid', $count)
|
|
|
|
->from('memories', 'm')
|
2022-10-19 17:10:36 +00:00
|
|
|
;
|
2023-03-23 21:45:56 +00:00
|
|
|
$query = $this->joinFilecache($query, null, $recursive, $archive);
|
2022-09-24 01:54:14 +00:00
|
|
|
|
|
|
|
// Group and sort by dayid
|
|
|
|
$query->groupBy('m.dayid')
|
2022-10-19 17:10:36 +00:00
|
|
|
->orderBy('m.dayid', 'DESC')
|
|
|
|
;
|
2022-09-11 02:05:04 +00:00
|
|
|
|
2022-09-12 01:33:38 +00:00
|
|
|
// Apply all transformations
|
2023-03-23 23:58:49 +00:00
|
|
|
$this->applyAllTransforms($queryTransforms, $query, true);
|
2022-09-12 01:33:38 +00:00
|
|
|
|
2022-10-27 16:19:25 +00:00
|
|
|
$cursor = $this->executeQueryWithCTEs($query);
|
2022-09-25 11:30:28 +00:00
|
|
|
$rows = $cursor->fetchAll();
|
|
|
|
$cursor->closeCursor();
|
2022-10-19 17:10:36 +00:00
|
|
|
|
2022-09-11 02:05:04 +00:00
|
|
|
return $this->processDays($rows);
|
|
|
|
}
|
|
|
|
|
2022-10-06 19:24:45 +00:00
|
|
|
/**
|
2022-10-19 17:10:36 +00:00
|
|
|
* Get the day response from the database for the timeline.
|
|
|
|
*
|
2023-03-23 21:45:56 +00:00
|
|
|
* @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
|
2022-10-19 17:10:36 +00:00
|
|
|
*
|
2022-10-06 19:24:45 +00:00
|
|
|
* @return array An array of day responses
|
2022-09-11 02:05:04 +00:00
|
|
|
*/
|
2022-09-24 01:54:14 +00:00
|
|
|
public function getDay(
|
2022-11-24 02:28:34 +00:00
|
|
|
?array $day_ids,
|
2022-09-24 01:54:14 +00:00
|
|
|
bool $recursive,
|
2022-09-25 23:02:26 +00:00
|
|
|
bool $archive,
|
2022-09-24 01:54:14 +00:00
|
|
|
array $queryTransforms = []
|
|
|
|
): array {
|
2022-09-11 02:05:04 +00:00
|
|
|
$query = $this->connection->getQueryBuilder();
|
2022-09-24 01:54:14 +00:00
|
|
|
|
|
|
|
// Get all entries also present in filecache
|
|
|
|
$fileid = $query->createFunction('DISTINCT m.fileid');
|
|
|
|
|
|
|
|
// We don't actually use m.datetaken here, but postgres
|
|
|
|
// needs that all fields in ORDER BY are also in SELECT
|
|
|
|
// when using DISTINCT on selected fields
|
2023-03-10 17:30:56 +00:00
|
|
|
$query->select($fileid, ...TimelineQuery::TIMELINE_SELECT)
|
2022-09-24 01:54:14 +00:00
|
|
|
->from('memories', 'm')
|
2022-10-19 17:10:36 +00:00
|
|
|
;
|
2022-10-29 01:11:58 +00:00
|
|
|
|
|
|
|
// JOIN with filecache for existing files
|
2023-03-23 21:45:56 +00:00
|
|
|
$query = $this->joinFilecache($query, null, $recursive, $archive);
|
2022-10-29 01:11:58 +00:00
|
|
|
|
|
|
|
// JOIN with mimetypes to get the mimetype
|
|
|
|
$query->join('f', 'mimetypes', 'mimetypes', $query->expr()->eq('f.mimetype', 'mimetypes.id'));
|
2022-10-06 22:01:28 +00:00
|
|
|
|
|
|
|
// Filter by dayid unless wildcard
|
2022-10-19 17:10:36 +00:00
|
|
|
if (null !== $day_ids) {
|
2022-10-06 22:01:28 +00:00
|
|
|
$query->andWhere($query->expr()->in('m.dayid', $query->createNamedParameter($day_ids, IQueryBuilder::PARAM_INT_ARRAY)));
|
|
|
|
} else {
|
|
|
|
// Limit wildcard to 100 results
|
|
|
|
$query->setMaxResults(100);
|
|
|
|
}
|
2022-09-24 01:54:14 +00:00
|
|
|
|
|
|
|
// Add favorite field
|
2023-03-23 23:58:49 +00:00
|
|
|
$this->addFavoriteTag($query);
|
2022-09-24 01:54:14 +00:00
|
|
|
|
|
|
|
// Group and sort by date taken
|
|
|
|
$query->orderBy('m.datetaken', 'DESC');
|
2022-10-16 19:18:31 +00:00
|
|
|
$query->addOrderBy('m.fileid', 'DESC'); // tie-breaker
|
2022-09-24 01:54:14 +00:00
|
|
|
|
|
|
|
// Apply all transformations
|
2023-03-23 23:58:49 +00:00
|
|
|
$this->applyAllTransforms($queryTransforms, $query, false);
|
2022-09-11 02:05:04 +00:00
|
|
|
|
2022-10-27 16:19:25 +00:00
|
|
|
$cursor = $this->executeQueryWithCTEs($query);
|
2022-09-25 11:30:28 +00:00
|
|
|
$rows = $cursor->fetchAll();
|
|
|
|
$cursor->closeCursor();
|
2022-10-19 17:10:36 +00:00
|
|
|
|
2023-03-23 00:45:45 +00:00
|
|
|
return $this->processDay($rows);
|
2022-09-11 02:05:04 +00:00
|
|
|
}
|
2022-10-19 17:10:36 +00:00
|
|
|
|
2023-03-23 23:58:49 +00:00
|
|
|
public function executeQueryWithCTEs(IQueryBuilder $query, string $psql = '')
|
|
|
|
{
|
|
|
|
$sql = empty($psql) ? $query->getSQL() : $psql;
|
|
|
|
$params = $query->getParameters();
|
|
|
|
$types = $query->getParameterTypes();
|
|
|
|
|
|
|
|
// Get SQL
|
|
|
|
$CTE_SQL = \array_key_exists('cteFoldersArchive', $params) && $params['cteFoldersArchive']
|
|
|
|
? CTE_FOLDERS_ARCHIVE
|
|
|
|
: CTE_FOLDERS;
|
|
|
|
|
|
|
|
// Add WITH clause if needed
|
|
|
|
if (false !== strpos($sql, 'cte_folders')) {
|
|
|
|
$sql = $CTE_SQL.' '.$sql;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->connection->executeQuery($sql, $params, $types);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Inner join with oc_filecache.
|
|
|
|
*
|
|
|
|
* @param IQueryBuilder $query Query builder
|
|
|
|
* @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
|
|
|
|
*/
|
|
|
|
public function joinFilecache(
|
|
|
|
IQueryBuilder $query,
|
|
|
|
?TimelineRoot $root = null,
|
|
|
|
bool $recursive = true,
|
|
|
|
bool $archive = false
|
|
|
|
) {
|
|
|
|
if (null === $root) {
|
|
|
|
$root = $this->root();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Join with memories
|
|
|
|
$baseOp = $query->expr()->eq('f.fileid', 'm.fileid');
|
|
|
|
if ($root->isEmpty()) {
|
|
|
|
return $query->innerJoin('m', 'filecache', 'f', $baseOp);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filter by folder (recursive or otherwise)
|
|
|
|
$pathOp = null;
|
|
|
|
if ($recursive) {
|
|
|
|
// Join with folders CTE
|
|
|
|
$this->addSubfolderJoinParams($query, $root, $archive);
|
|
|
|
$query->innerJoin('f', 'cte_folders', 'cte_f', $query->expr()->eq('f.parent', 'cte_f.fileid'));
|
|
|
|
} else {
|
|
|
|
// If getting non-recursively folder only check for parent
|
|
|
|
$pathOp = $query->expr()->eq('f.parent', $query->createNamedParameter($root->getOneId(), IQueryBuilder::PARAM_INT));
|
|
|
|
}
|
|
|
|
|
|
|
|
return $query->innerJoin('m', 'filecache', 'f', $query->expr()->andX(
|
|
|
|
$baseOp,
|
|
|
|
$pathOp,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2022-10-19 17:10:36 +00:00
|
|
|
/**
|
|
|
|
* Process the days response.
|
|
|
|
*
|
|
|
|
* @param array $days
|
|
|
|
*/
|
2023-03-23 00:45:45 +00:00
|
|
|
private function processDays($days)
|
2022-10-19 17:10:36 +00:00
|
|
|
{
|
|
|
|
foreach ($days as &$row) {
|
2022-10-19 17:15:14 +00:00
|
|
|
$row['dayid'] = (int) $row['dayid'];
|
|
|
|
$row['count'] = (int) $row['count'];
|
2022-10-19 17:10:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $days;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process the single day response.
|
|
|
|
*/
|
2023-03-23 00:45:45 +00:00
|
|
|
private function processDay(array $day)
|
2022-10-19 17:10:36 +00:00
|
|
|
{
|
|
|
|
foreach ($day as &$row) {
|
|
|
|
// Convert field types
|
2022-10-19 17:15:14 +00:00
|
|
|
$row['fileid'] = (int) $row['fileid'];
|
|
|
|
$row['isvideo'] = (int) $row['isvideo'];
|
2022-11-10 03:48:03 +00:00
|
|
|
$row['video_duration'] = (int) $row['video_duration'];
|
2022-10-19 17:15:14 +00:00
|
|
|
$row['dayid'] = (int) $row['dayid'];
|
|
|
|
$row['w'] = (int) $row['w'];
|
|
|
|
$row['h'] = (int) $row['h'];
|
2022-10-19 17:10:36 +00:00
|
|
|
if (!$row['isvideo']) {
|
2022-11-10 06:19:44 +00:00
|
|
|
unset($row['isvideo'], $row['video_duration']);
|
2022-10-19 17:10:36 +00:00
|
|
|
}
|
|
|
|
if ($row['categoryid']) {
|
|
|
|
$row['isfavorite'] = 1;
|
|
|
|
}
|
|
|
|
unset($row['categoryid']);
|
2022-11-22 11:31:31 +00:00
|
|
|
if (!$row['liveid']) {
|
|
|
|
unset($row['liveid']);
|
|
|
|
}
|
2022-10-19 17:10:36 +00:00
|
|
|
|
2023-03-23 23:58:49 +00:00
|
|
|
// All cluster transformations
|
2023-03-24 00:56:41 +00:00
|
|
|
ClustersBackendManager::applyDayPostTransforms($this->request, $row);
|
2022-12-05 04:04:48 +00:00
|
|
|
|
|
|
|
// We don't need these fields
|
2023-03-16 16:58:43 +00:00
|
|
|
unset($row['datetaken']);
|
2022-10-19 17:10:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $day;
|
|
|
|
}
|
|
|
|
|
2022-10-26 16:29:32 +00:00
|
|
|
/**
|
2022-10-26 17:06:45 +00:00
|
|
|
* Get all folders inside a top folder.
|
2022-10-26 16:29:32 +00:00
|
|
|
*/
|
2022-10-27 19:54:51 +00:00
|
|
|
private function addSubfolderJoinParams(
|
2022-10-27 16:19:25 +00:00
|
|
|
IQueryBuilder &$query,
|
2022-11-16 07:45:01 +00:00
|
|
|
TimelineRoot &$root,
|
2022-10-19 17:10:36 +00:00
|
|
|
bool $archive
|
|
|
|
) {
|
2022-10-27 16:19:25 +00:00
|
|
|
// Add query parameters
|
2022-11-16 07:45:01 +00:00
|
|
|
$query->setParameter('topFolderIds', $root->getIds(), IQueryBuilder::PARAM_INT_ARRAY);
|
2022-11-15 15:12:27 +00:00
|
|
|
$query->setParameter('cteFoldersArchive', $archive, IQueryBuilder::PARAM_BOOL);
|
2022-10-26 16:29:32 +00:00
|
|
|
}
|
2022-09-11 02:05:04 +00:00
|
|
|
}
|