Use storage for lookups
parent
36dc5abb8f
commit
f5eeb1ae9d
|
@ -17,8 +17,6 @@ return [
|
||||||
// API
|
// API
|
||||||
['name' => 'api#days', 'url' => '/api/days', 'verb' => 'GET'],
|
['name' => 'api#days', 'url' => '/api/days', 'verb' => 'GET'],
|
||||||
['name' => 'api#day', 'url' => '/api/days/{id}', 'verb' => 'GET'],
|
['name' => 'api#day', 'url' => '/api/days/{id}', 'verb' => 'GET'],
|
||||||
['name' => 'api#folder', 'url' => '/api/folder/{folder}', 'verb' => 'GET'],
|
|
||||||
['name' => 'api#folderDay', 'url' => '/api/folder/{folder}/{dayId}', 'verb' => 'GET'],
|
|
||||||
|
|
||||||
// Config API
|
// Config API
|
||||||
['name' => 'api#setUserConfig', 'url' => '/api/config/{key}', 'verb' => 'PUT'],
|
['name' => 'api#setUserConfig', 'url' => '/api/config/{key}', 'verb' => 'PUT'],
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -29,6 +29,7 @@ use OC\Files\Search\SearchComparison;
|
||||||
use OC\Files\Search\SearchQuery;
|
use OC\Files\Search\SearchQuery;
|
||||||
use OCA\Memories\AppInfo\Application;
|
use OCA\Memories\AppInfo\Application;
|
||||||
use OCA\Memories\Db\TimelineQuery;
|
use OCA\Memories\Db\TimelineQuery;
|
||||||
|
use OCA\Memories\Exif;
|
||||||
use OCP\AppFramework\Controller;
|
use OCP\AppFramework\Controller;
|
||||||
use OCP\AppFramework\Http;
|
use OCP\AppFramework\Http;
|
||||||
use OCP\AppFramework\Http\JSONResponse;
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
@ -38,6 +39,7 @@ use OCP\IDBConnection;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
use OCP\IUserSession;
|
use OCP\IUserSession;
|
||||||
use OCP\Files\FileInfo;
|
use OCP\Files\FileInfo;
|
||||||
|
use OCP\Files\Folder;
|
||||||
use OCP\Files\Search\ISearchComparison;
|
use OCP\Files\Search\ISearchComparison;
|
||||||
|
|
||||||
class ApiController extends Controller {
|
class ApiController extends Controller {
|
||||||
|
@ -83,15 +85,16 @@ class ApiController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Preload a few "day" at the start of "days" response */
|
/** Preload a few "day" at the start of "days" response */
|
||||||
private function preloadDays(array &$days) {
|
private function preloadDays(array &$days, Folder &$folder, bool $recursive) {
|
||||||
$uid = $this->userSession->getUser()->getUID();
|
$uid = $this->userSession->getUser()->getUID();
|
||||||
$transforms = $this->getTransformations();
|
$transforms = $this->getTransformations();
|
||||||
$preloaded = 0;
|
$preloaded = 0;
|
||||||
foreach ($days as &$day) {
|
foreach ($days as &$day) {
|
||||||
$day["detail"] = $this->timelineQuery->getDay(
|
$day["detail"] = $this->timelineQuery->getDay(
|
||||||
$this->config,
|
$folder,
|
||||||
$uid,
|
$uid,
|
||||||
$day["dayid"],
|
$day["dayid"],
|
||||||
|
$recursive,
|
||||||
$transforms,
|
$transforms,
|
||||||
);
|
);
|
||||||
$day["count"] = count($day["detail"]); // make sure count is accurate
|
$day["count"] = count($day["detail"]); // make sure count is accurate
|
||||||
|
@ -103,6 +106,30 @@ class ApiController extends Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Get the Folder object relevant to the request */
|
||||||
|
private function getRequestFolder() {
|
||||||
|
$uid = $this->userSession->getUser()->getUID();
|
||||||
|
try {
|
||||||
|
$folder = null;
|
||||||
|
$folderPath = $this->request->getParam('folder');
|
||||||
|
$userFolder = $this->rootFolder->getUserFolder($uid);
|
||||||
|
|
||||||
|
if (!is_null($folderPath)) {
|
||||||
|
$folder = $userFolder->get($folderPath);
|
||||||
|
} else {
|
||||||
|
$configPath = Exif::removeExtraSlash(Exif::getPhotosPath($this->config, $uid));
|
||||||
|
$folder = $userFolder->get($configPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$folder instanceof Folder) {
|
||||||
|
throw new \Exception("Folder not found");
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $folder;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
*
|
*
|
||||||
|
@ -113,13 +140,31 @@ class ApiController extends Controller {
|
||||||
if (is_null($user)) {
|
if (is_null($user)) {
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
}
|
}
|
||||||
|
$uid = $user->getUID();
|
||||||
|
|
||||||
|
// Get the folder to show
|
||||||
|
$folder = $this->getRequestFolder();
|
||||||
|
$recursive = is_null($this->request->getParam('folder'));
|
||||||
|
if (is_null($folder)) {
|
||||||
|
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run actual query
|
||||||
$list = $this->timelineQuery->getDays(
|
$list = $this->timelineQuery->getDays(
|
||||||
$this->config,
|
$folder,
|
||||||
$user->getUID(),
|
$uid,
|
||||||
|
$recursive,
|
||||||
$this->getTransformations(),
|
$this->getTransformations(),
|
||||||
);
|
);
|
||||||
$this->preloadDays($list);
|
|
||||||
|
// Preload some day responses
|
||||||
|
$this->preloadDays($list, $folder, $recursive);
|
||||||
|
|
||||||
|
// Add subfolder info if querying non-recursively
|
||||||
|
if (!$recursive) {
|
||||||
|
$this->addSubfolders($folder, $list, $user);
|
||||||
|
}
|
||||||
|
|
||||||
return new JSONResponse($list, Http::STATUS_OK);
|
return new JSONResponse($list, Http::STATUS_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,70 +175,39 @@ class ApiController extends Controller {
|
||||||
*/
|
*/
|
||||||
public function day(string $id): JSONResponse {
|
public function day(string $id): JSONResponse {
|
||||||
$user = $this->userSession->getUser();
|
$user = $this->userSession->getUser();
|
||||||
if (is_null($user) || !is_numeric($id)) {
|
if (is_null($user)) {
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
}
|
}
|
||||||
|
$uid = $user->getUID();
|
||||||
|
|
||||||
|
// Get the folder to show
|
||||||
|
$folder = $this->getRequestFolder();
|
||||||
|
$recursive = is_null($this->request->getParam('folder'));
|
||||||
|
if (is_null($folder)) {
|
||||||
|
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run actual query
|
||||||
$list = $this->timelineQuery->getDay(
|
$list = $this->timelineQuery->getDay(
|
||||||
$this->config,
|
$folder,
|
||||||
$user->getUID(),
|
$uid,
|
||||||
intval($id),
|
intval($id),
|
||||||
|
$recursive,
|
||||||
$this->getTransformations(),
|
$this->getTransformations(),
|
||||||
);
|
);
|
||||||
return new JSONResponse($list, Http::STATUS_OK);
|
return new JSONResponse($list, Http::STATUS_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if folder is allowed and get it if yes
|
|
||||||
*/
|
|
||||||
private function getAllowedFolder(int $folder, $user) {
|
|
||||||
// Get root if folder not specified
|
|
||||||
$root = $this->rootFolder->getUserFolder($user->getUID());
|
|
||||||
if ($folder === 0) {
|
|
||||||
$folder = $root->getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check access to folder
|
|
||||||
$nodes = $root->getById($folder);
|
|
||||||
if (empty($nodes)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check it is a folder
|
|
||||||
$node = $nodes[0];
|
|
||||||
if (!$node instanceof \OCP\Files\Folder) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
*
|
|
||||||
* @return JSONResponse
|
|
||||||
*/
|
*/
|
||||||
public function folder(string $folder): JSONResponse {
|
public function addSubfolders(Folder &$folder, &$list, &$user) {
|
||||||
$user = $this->userSession->getUser();
|
|
||||||
if (is_null($user) || !is_numeric($folder)) {
|
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check permissions
|
|
||||||
$node = $this->getAllowedFolder(intval($folder), $user);
|
|
||||||
if (is_null($node)) {
|
|
||||||
return new JSONResponse([], Http::STATUS_FORBIDDEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get response from db
|
|
||||||
$list = $this->timelineQuery->getDaysFolder($node->getId());
|
|
||||||
|
|
||||||
// Get subdirectories
|
// Get subdirectories
|
||||||
$sub = $node->search(new SearchQuery(
|
$sub = $folder->search(new SearchQuery(
|
||||||
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', FileInfo::MIMETYPE_FOLDER),
|
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', FileInfo::MIMETYPE_FOLDER),
|
||||||
0, 0, [], $user));
|
0, 0, [], $user));
|
||||||
$sub = array_filter($sub, function ($item) use ($node) {
|
$sub = array_filter($sub, function ($item) use (&$folder) {
|
||||||
return $item->getParent()->getId() === $node->getId();
|
return $item->getParent()->getId() === $folder->getId();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sort by name
|
// Sort by name
|
||||||
|
@ -204,7 +218,7 @@ class ApiController extends Controller {
|
||||||
// Map sub to JSON array
|
// Map sub to JSON array
|
||||||
$subdirArray = [
|
$subdirArray = [
|
||||||
"dayid" => \OCA\Memories\Util::$TAG_DAYID_FOLDERS,
|
"dayid" => \OCA\Memories\Util::$TAG_DAYID_FOLDERS,
|
||||||
"detail" => array_map(function ($node) {
|
"detail" => array_map(function (&$node) {
|
||||||
return [
|
return [
|
||||||
"fileid" => $node->getId(),
|
"fileid" => $node->getId(),
|
||||||
"name" => $node->getName(),
|
"name" => $node->getName(),
|
||||||
|
@ -215,28 +229,6 @@ class ApiController extends Controller {
|
||||||
];
|
];
|
||||||
$subdirArray["count"] = count($subdirArray["detail"]);
|
$subdirArray["count"] = count($subdirArray["detail"]);
|
||||||
array_unshift($list, $subdirArray);
|
array_unshift($list, $subdirArray);
|
||||||
|
|
||||||
return new JSONResponse($list, Http::STATUS_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*
|
|
||||||
* @return JSONResponse
|
|
||||||
*/
|
|
||||||
public function folderDay(string $folder, string $dayId): JSONResponse {
|
|
||||||
$user = $this->userSession->getUser();
|
|
||||||
if (is_null($user) || !is_numeric($folder) || !is_numeric($dayId)) {
|
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
$node = $this->getAllowedFolder(intval($folder), $user);
|
|
||||||
if ($node === NULL) {
|
|
||||||
return new JSONResponse([], Http::STATUS_FORBIDDEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
$list = $this->timelineQuery->getDayFolder($user->getUID(), $node->getId(), intval($dayId));
|
|
||||||
return new JSONResponse($list, Http::STATUS_OK);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,7 +7,6 @@ use OCP\IDBConnection;
|
||||||
|
|
||||||
class TimelineQuery {
|
class TimelineQuery {
|
||||||
use TimelineQueryDays;
|
use TimelineQueryDays;
|
||||||
use TimelineQueryDay;
|
|
||||||
use TimelineQueryFilters;
|
use TimelineQueryFilters;
|
||||||
|
|
||||||
protected IDBConnection $connection;
|
protected IDBConnection $connection;
|
||||||
|
|
|
@ -1,121 +0,0 @@
|
||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace OCA\Memories\Db;
|
|
||||||
|
|
||||||
use OCA\Memories\Exif;
|
|
||||||
use OCP\IConfig;
|
|
||||||
use OCP\IDBConnection;
|
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
|
||||||
|
|
||||||
trait TimelineQueryDay {
|
|
||||||
protected IDBConnection $connection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process the single day response
|
|
||||||
* @param array $day
|
|
||||||
*/
|
|
||||||
private function processDay(&$day) {
|
|
||||||
foreach($day as &$row) {
|
|
||||||
// We don't need date taken (see query builder)
|
|
||||||
unset($row['datetaken']);
|
|
||||||
|
|
||||||
// Convert field types
|
|
||||||
$row["fileid"] = intval($row["fileid"]);
|
|
||||||
$row["isvideo"] = intval($row["isvideo"]);
|
|
||||||
if (!$row["isvideo"]) {
|
|
||||||
unset($row["isvideo"]);
|
|
||||||
}
|
|
||||||
if ($row["categoryid"]) {
|
|
||||||
$row["isfavorite"] = 1;
|
|
||||||
}
|
|
||||||
unset($row["categoryid"]);
|
|
||||||
}
|
|
||||||
return $day;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the base query builder for day */
|
|
||||||
private function makeQueryDay(
|
|
||||||
IQueryBuilder &$query,
|
|
||||||
int $dayid,
|
|
||||||
string $user,
|
|
||||||
$whereFilecache
|
|
||||||
) {
|
|
||||||
// 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
|
|
||||||
$query->select($fileid, 'f.etag', 'm.isvideo', 'vco.categoryid', 'm.datetaken')
|
|
||||||
->from('memories', 'm')
|
|
||||||
->innerJoin('m', 'filecache', 'f',
|
|
||||||
$query->expr()->andX(
|
|
||||||
$query->expr()->eq('f.fileid', 'm.fileid'),
|
|
||||||
$whereFilecache
|
|
||||||
))
|
|
||||||
->andWhere($query->expr()->eq('m.dayid', $query->createNamedParameter($dayid, IQueryBuilder::PARAM_INT)));
|
|
||||||
|
|
||||||
// Add favorite field
|
|
||||||
$this->addFavoriteTag($query, $user);
|
|
||||||
|
|
||||||
// Group and sort by date taken
|
|
||||||
$query->orderBy('m.datetaken', 'DESC');
|
|
||||||
return $query;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a day response from the database for the timeline
|
|
||||||
* @param IConfig $config
|
|
||||||
* @param string $userId
|
|
||||||
* @param int $dayId
|
|
||||||
*/
|
|
||||||
public function getDay(
|
|
||||||
IConfig &$config,
|
|
||||||
string $user,
|
|
||||||
int $dayId,
|
|
||||||
array $queryTransforms = []
|
|
||||||
): array {
|
|
||||||
// Filter by path starting with timeline path
|
|
||||||
$configPath = Exif::getPhotosPath($config, $user);
|
|
||||||
$likeHome = Exif::removeExtraSlash("files/" . $configPath . "%");
|
|
||||||
$likeExt = Exif::removeLeadingSlash(Exif::removeExtraSlash($configPath . "%"));
|
|
||||||
|
|
||||||
$query = $this->connection->getQueryBuilder();
|
|
||||||
$this->makeQueryDay($query, $dayId, $user, $query->expr()->orX(
|
|
||||||
$query->expr()->like('f.path', $query->createNamedParameter($likeHome)),
|
|
||||||
$query->expr()->like('f.path', $query->createNamedParameter($likeExt)),
|
|
||||||
));
|
|
||||||
|
|
||||||
// Filter by UID
|
|
||||||
$query->andWhere($query->expr()->eq('m.uid', $query->createNamedParameter($user)));
|
|
||||||
|
|
||||||
// Apply all transformations
|
|
||||||
foreach ($queryTransforms as &$transform) {
|
|
||||||
$transform($query, $user);
|
|
||||||
}
|
|
||||||
|
|
||||||
$rows = $query->executeQuery()->fetchAll();
|
|
||||||
return $this->processDay($rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a day response from the database for one folder
|
|
||||||
* @param int $folderId
|
|
||||||
* @param int $dayId
|
|
||||||
*/
|
|
||||||
public function getDayFolder(
|
|
||||||
string $user,
|
|
||||||
int $folderId,
|
|
||||||
int $dayId
|
|
||||||
): array {
|
|
||||||
$query = $this->connection->getQueryBuilder();
|
|
||||||
$this->makeQueryDay($query, $dayId, $user, $query->expr()->orX(
|
|
||||||
$query->expr()->eq('f.parent', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT)),
|
|
||||||
$query->expr()->eq('f.fileid', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT)),
|
|
||||||
));
|
|
||||||
|
|
||||||
$rows = $query->executeQuery()->fetchAll();
|
|
||||||
return $this->processDay($rows);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,10 +3,9 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace OCA\Memories\Db;
|
namespace OCA\Memories\Db;
|
||||||
|
|
||||||
use OCA\Memories\Exif;
|
|
||||||
use OCP\IConfig;
|
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
use OCP\Files\Folder;
|
||||||
|
|
||||||
trait TimelineQueryDays {
|
trait TimelineQueryDays {
|
||||||
protected IDBConnection $connection;
|
protected IDBConnection $connection;
|
||||||
|
@ -23,55 +22,86 @@ trait TimelineQueryDays {
|
||||||
return $days;
|
return $days;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the base query builder for days */
|
/**
|
||||||
private function makeQueryDays(
|
* Process the single day response
|
||||||
IQueryBuilder &$query,
|
* @param array $day
|
||||||
$whereFilecache
|
*/
|
||||||
) {
|
private function processDay(&$day) {
|
||||||
// Get all entries also present in filecache
|
foreach($day as &$row) {
|
||||||
$count = $query->func()->count($query->createFunction('DISTINCT m.fileid'), 'count');
|
// We don't need date taken (see query builder)
|
||||||
$query->select('m.dayid', $count)
|
unset($row['datetaken']);
|
||||||
->from('memories', 'm')
|
|
||||||
->innerJoin('m', 'filecache', 'f',
|
|
||||||
$query->expr()->andX(
|
|
||||||
$query->expr()->eq('f.fileid', 'm.fileid'),
|
|
||||||
$whereFilecache
|
|
||||||
));
|
|
||||||
|
|
||||||
// Group and sort by dayid
|
// Convert field types
|
||||||
$query->groupBy('m.dayid')
|
$row["fileid"] = intval($row["fileid"]);
|
||||||
->orderBy('m.dayid', 'DESC');
|
$row["isvideo"] = intval($row["isvideo"]);
|
||||||
return $query;
|
if (!$row["isvideo"]) {
|
||||||
|
unset($row["isvideo"]);
|
||||||
|
}
|
||||||
|
if ($row["categoryid"]) {
|
||||||
|
$row["isfavorite"] = 1;
|
||||||
|
}
|
||||||
|
unset($row["categoryid"]);
|
||||||
|
}
|
||||||
|
return $day;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the query for oc_filecache join */
|
||||||
|
private function getFilecacheJoinQuery(IQueryBuilder &$query, Folder &$folder, bool $recursive) {
|
||||||
|
// Subquery to get storage and path
|
||||||
|
$subQuery = $query->getConnection()->getQueryBuilder();
|
||||||
|
$finfo = $subQuery->select('path', 'storage')->from('filecache')->where(
|
||||||
|
$subQuery->expr()->eq('fileid', $subQuery->createNamedParameter($folder->getId())),
|
||||||
|
)->executeQuery()->fetch();
|
||||||
|
if (empty($finfo)) {
|
||||||
|
throw new \Exception("Folder not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
$pathQuery = null;
|
||||||
|
if ($recursive) {
|
||||||
|
// Filter by path for recursive query
|
||||||
|
$likePath = $finfo["path"];
|
||||||
|
if (!empty($likePath)) {
|
||||||
|
$likePath .= '/';
|
||||||
|
}
|
||||||
|
$likePath = $likePath . '%';
|
||||||
|
$pathQuery = $query->expr()->like('f.path', $query->createNamedParameter($likePath));
|
||||||
|
} else {
|
||||||
|
// If getting non-recursively folder only check for parent
|
||||||
|
$pathQuery = $query->expr()->eq('f.parent', $query->createNamedParameter($folder->getId(), IQueryBuilder::PARAM_INT));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->expr()->andX(
|
||||||
|
$query->expr()->eq('f.fileid', 'm.fileid'),
|
||||||
|
$query->expr()->in('f.storage', $query->createNamedParameter($finfo["storage"])),
|
||||||
|
$pathQuery,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the days response from the database for the timeline
|
* Get the days response from the database for the timeline
|
||||||
* @param IConfig $config
|
|
||||||
* @param string $userId
|
* @param string $userId
|
||||||
*/
|
*/
|
||||||
public function getDays(
|
public function getDays(
|
||||||
IConfig &$config,
|
Folder &$folder,
|
||||||
string $user,
|
string $uid,
|
||||||
|
bool $recursive,
|
||||||
array $queryTransforms = []
|
array $queryTransforms = []
|
||||||
): array {
|
): array {
|
||||||
|
|
||||||
// Filter by path starting with timeline path
|
|
||||||
$configPath = Exif::getPhotosPath($config, $user);
|
|
||||||
$likeHome = Exif::removeExtraSlash("files/" . $configPath . "%");
|
|
||||||
$likeExt = Exif::removeLeadingSlash(Exif::removeExtraSlash($configPath . "%"));
|
|
||||||
|
|
||||||
$query = $this->connection->getQueryBuilder();
|
$query = $this->connection->getQueryBuilder();
|
||||||
$this->makeQueryDays($query, $query->expr()->orX(
|
|
||||||
$query->expr()->like('f.path', $query->createNamedParameter($likeHome)),
|
|
||||||
$query->expr()->like('f.path', $query->createNamedParameter($likeExt)),
|
|
||||||
));
|
|
||||||
|
|
||||||
// Filter by user
|
// Get all entries also present in filecache
|
||||||
$query->andWhere($query->expr()->eq('m.uid', $query->createNamedParameter($user)));
|
$count = $query->func()->count($query->createFunction('DISTINCT m.fileid'), 'count');
|
||||||
|
$query->select('m.dayid', $count)
|
||||||
|
->from('memories', 'm')
|
||||||
|
->innerJoin('m', 'filecache', 'f', $this->getFilecacheJoinQuery($query, $folder, $recursive));
|
||||||
|
|
||||||
|
// Group and sort by dayid
|
||||||
|
$query->groupBy('m.dayid')
|
||||||
|
->orderBy('m.dayid', 'DESC');
|
||||||
|
|
||||||
// Apply all transformations
|
// Apply all transformations
|
||||||
foreach ($queryTransforms as &$transform) {
|
foreach ($queryTransforms as &$transform) {
|
||||||
$transform($query, $user);
|
$transform($query, $uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
$rows = $query->executeQuery()->fetchAll();
|
$rows = $query->executeQuery()->fetchAll();
|
||||||
|
@ -79,17 +109,41 @@ trait TimelineQueryDays {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the days response from the database for one folder
|
* Get the days response from the database for the timeline
|
||||||
* @param int $folderId
|
* @param string $userId
|
||||||
*/
|
*/
|
||||||
public function getDaysFolder(int $folderId) {
|
public function getDay(
|
||||||
|
Folder &$folder,
|
||||||
|
string $uid,
|
||||||
|
int $dayid,
|
||||||
|
bool $recursive,
|
||||||
|
array $queryTransforms = []
|
||||||
|
): array {
|
||||||
$query = $this->connection->getQueryBuilder();
|
$query = $this->connection->getQueryBuilder();
|
||||||
$this->makeQueryDays($query, $query->expr()->orX(
|
|
||||||
$query->expr()->eq('f.parent', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT)),
|
// Get all entries also present in filecache
|
||||||
$query->expr()->eq('f.fileid', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT)),
|
$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
|
||||||
|
$query->select($fileid, 'f.etag', 'm.isvideo', 'vco.categoryid', 'm.datetaken')
|
||||||
|
->from('memories', 'm')
|
||||||
|
->innerJoin('m', 'filecache', 'f', $this->getFilecacheJoinQuery($query, $folder, $recursive))
|
||||||
|
->andWhere($query->expr()->eq('m.dayid', $query->createNamedParameter($dayid, IQueryBuilder::PARAM_INT)));
|
||||||
|
|
||||||
|
// Add favorite field
|
||||||
|
$this->addFavoriteTag($query, $uid);
|
||||||
|
|
||||||
|
// Group and sort by date taken
|
||||||
|
$query->orderBy('m.datetaken', 'DESC');
|
||||||
|
|
||||||
|
// Apply all transformations
|
||||||
|
foreach ($queryTransforms as &$transform) {
|
||||||
|
$transform($query, $uid);
|
||||||
|
}
|
||||||
|
|
||||||
$rows = $query->executeQuery()->fetchAll();
|
$rows = $query->executeQuery()->fetchAll();
|
||||||
return $this->processDays($rows);
|
return $this->processDay($rows);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
hasPreview: previewFileInfos.length > 0,
|
hasPreview: previewFileInfos.length > 0,
|
||||||
onePreview: previewFileInfos.length === 1,
|
onePreview: previewFileInfos.length === 1,
|
||||||
}"
|
}"
|
||||||
@click="openFolder(data.fileid)"
|
@click="openFolder(data)"
|
||||||
v-bind:style="{
|
v-bind:style="{
|
||||||
width: rowHeight + 'px',
|
width: rowHeight + 'px',
|
||||||
height: rowHeight + 'px',
|
height: rowHeight + 'px',
|
||||||
|
@ -88,9 +88,9 @@ export default class Folder extends Mixins(GlobalMixin) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Open folder */
|
/** Open folder */
|
||||||
openFolder(id: number) {
|
openFolder(folder: IFolder) {
|
||||||
this.$router.push({ name: 'folders', params: {
|
this.$router.push({ name: 'folders', params: {
|
||||||
id: id.toString(),
|
path: folder.path.split('/').slice(3).join('/'),
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,9 +141,6 @@ const MIN_COLS = 3; // Min number of columns (on phone, e.g.
|
||||||
const API_ROUTES = {
|
const API_ROUTES = {
|
||||||
DAYS: 'days',
|
DAYS: 'days',
|
||||||
DAY: 'days/{dayId}',
|
DAY: 'days/{dayId}',
|
||||||
|
|
||||||
FOLDER_DAYS: 'folder/{folderId}',
|
|
||||||
FOLDER_DAY: 'folder/{folderId}/{dayId}',
|
|
||||||
};
|
};
|
||||||
for (const [key, value] of Object.entries(API_ROUTES)) {
|
for (const [key, value] of Object.entries(API_ROUTES)) {
|
||||||
API_ROUTES[key] = '/apps/memories/api/' + value;
|
API_ROUTES[key] = '/apps/memories/api/' + value;
|
||||||
|
@ -415,6 +412,11 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
||||||
query.set('vid', '1');
|
query.set('vid', '1');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Folder
|
||||||
|
if (this.$route.name === 'folders') {
|
||||||
|
query.set('folder', this.$route.params.path || '/');
|
||||||
|
}
|
||||||
|
|
||||||
// Create query string and append to URL
|
// Create query string and append to URL
|
||||||
const queryStr = query.toString();
|
const queryStr = query.toString();
|
||||||
if (queryStr) {
|
if (queryStr) {
|
||||||
|
@ -452,11 +454,6 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
||||||
let url = API_ROUTES.DAYS;
|
let url = API_ROUTES.DAYS;
|
||||||
let params: any = {};
|
let params: any = {};
|
||||||
|
|
||||||
if (this.$route.name === 'folders') {
|
|
||||||
url = API_ROUTES.FOLDER_DAYS;
|
|
||||||
params.folderId = this.$route.params.id || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.loading++;
|
this.loading++;
|
||||||
const startState = this.state;
|
const startState = this.state;
|
||||||
|
@ -555,11 +552,6 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
||||||
let url = API_ROUTES.DAY;
|
let url = API_ROUTES.DAY;
|
||||||
const params: any = { dayId };
|
const params: any = { dayId };
|
||||||
|
|
||||||
if (this.$route.name === 'folders') {
|
|
||||||
url = API_ROUTES.FOLDER_DAY;
|
|
||||||
params.folderId = this.$route.params.id || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do this in advance to prevent duplicate requests
|
// Do this in advance to prevent duplicate requests
|
||||||
this.loadedDays.add(dayId);
|
this.loadedDays.add(dayId);
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/folders/:id*',
|
path: '/folders/:path*',
|
||||||
component: Timeline,
|
component: Timeline,
|
||||||
name: 'folders',
|
name: 'folders',
|
||||||
props: route => ({
|
props: route => ({
|
||||||
|
|
Loading…
Reference in New Issue