* @author Varun Patil * @license AGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ namespace OCA\Memories\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; use OCP\Files\FileInfo; use OCP\Files\Folder; class DaysController extends ApiBase { /** * @NoAdminRequired * * @PublicPage */ public function days(): JSONResponse { // Get the folder to show $uid = $this->getUid(); // Get the folder to show $folder = null; try { $folder = $this->getRequestFolder(); } catch (\Exception $e) { return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND); } // Run actual query try { $list = $this->timelineQuery->getDays( $folder, $uid, $this->isRecursive(), $this->isArchive(), $this->getTransformations(true), ); if ($this->isMonthView()) { // Group days together into months $list = $this->timelineQuery->daysToMonths($list); } else { // Preload some day responses $this->preloadDays($list, $uid, $folder); } // Add subfolder info if querying non-recursively if (!$this->isRecursive()) { array_unshift($list, $this->getSubfoldersEntry($folder)); } return new JSONResponse($list, Http::STATUS_OK); } catch (\Exception $e) { return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR); } } /** * @NoAdminRequired * * @PublicPage */ public function day(string $id): JSONResponse { // Get user $uid = $this->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 if $dayIds is empty if (null !== $dayIds && 0 === \count($dayIds)) { return new JSONResponse([], Http::STATUS_OK); } // Get the folder to show $folder = null; try { $folder = $this->getRequestFolder(); } catch (\Exception $e) { return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND); } // Convert to actual dayIds if month view if ($this->isMonthView()) { $dayIds = $this->timelineQuery->monthIdToDayIds($dayIds[0]); } // Run actual query try { $list = $this->timelineQuery->getDay( $folder, $uid, $dayIds, $this->isRecursive(), $this->isArchive(), $this->getTransformations(false), ); // Force month id for dayId for month view if ($this->isMonthView()) { foreach ($list as &$photo) { $photo['dayid'] = (int) $dayIds[0]; } } return new JSONResponse($list, Http::STATUS_OK); } catch (\Exception $e) { return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR); } } /** * @NoAdminRequired * * @PublicPage */ public function dayPost(): JSONResponse { $id = $this->request->getParam('body_ids'); if (null === $id) { return new JSONResponse([], Http::STATUS_BAD_REQUEST); } return $this->day($id); } /** * Get subfolders entry for days response. */ public function getSubfoldersEntry(Folder &$folder) { // Ugly: get the view of the folder with reflection // This is unfortunately the only way to get the contents of a folder // matching a MIME type without using SEARCH, which is deep $rp = new \ReflectionProperty('\OC\Files\Node\Node', 'view'); $rp->setAccessible(true); $view = $rp->getValue($folder); // Get the subfolders $folders = $view->getDirectoryContent($folder->getPath(), FileInfo::MIMETYPE_FOLDER, $folder); // Sort by name usort($folders, function ($a, $b) { return strnatcmp($a->getName(), $b->getName()); }); // Process to response type return [ 'dayid' => \OCA\Memories\Util::$TAG_DAYID_FOLDERS, 'count' => \count($folders), 'detail' => array_map(function ($node) { return [ 'fileid' => $node->getId(), 'name' => $node->getName(), 'isfolder' => 1, 'path' => $node->getPath(), ]; }, $folders, []), ]; } /** * Get transformations depending on the request. * * @param bool $aggregateOnly Only apply transformations for aggregation (days call) */ private function getTransformations(bool $aggregateOnly) { $transforms = []; // Add extra information, basename and mimetype if (!$aggregateOnly && ($fields = $this->request->getParam('fields'))) { $fields = explode(',', $fields); $transforms[] = [$this->timelineQuery, 'transformExtraFields', $fields]; } // Other transforms not allowed for public shares if (null === $this->userSession->getUser()) { return $transforms; } // Filter only favorites if ($this->request->getParam('fav')) { $transforms[] = [$this->timelineQuery, 'transformFavoriteFilter']; } // Filter only videos if ($this->request->getParam('vid')) { $transforms[] = [$this->timelineQuery, 'transformVideoFilter']; } // Filter only for one face if ($this->recognizeIsEnabled()) { $face = $this->request->getParam('face'); if ($face) { $transforms[] = [$this->timelineQuery, 'transformFaceFilter', $face]; } $faceRect = $this->request->getParam('facerect'); if ($faceRect && !$aggregateOnly) { $transforms[] = [$this->timelineQuery, 'transformFaceRect', $face]; } } // Filter only for one tag if ($this->tagsIsEnabled()) { if ($tagName = $this->request->getParam('tag')) { $transforms[] = [$this->timelineQuery, 'transformTagFilter', $tagName]; } } // Filter for one album if ($this->albumsIsEnabled()) { if ($albumId = $this->request->getParam('album')) { $transforms[] = [$this->timelineQuery, 'transformAlbumFilter', $albumId]; } } // Limit number of responses for day query $limit = $this->request->getParam('limit'); if ($limit) { $transforms[] = [$this->timelineQuery, 'transformLimitDay', (int) $limit]; } return $transforms; } /** * 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 */ private function preloadDays(array &$days, string $uid, &$folder) { $transforms = $this->getTransformations(false); $preloaded = 0; $preloadDayIds = []; $preloadDays = []; foreach ($days as &$day) { if ($day['count'] <= 0) { continue; } $preloaded += $day['count']; $preloadDayIds[] = $day['dayid']; $preloadDays[] = &$day; if ($preloaded >= 50 || \count($preloadDayIds) > 5) { // should be enough break; } } if (\count($preloadDayIds) > 0) { $allDetails = $this->timelineQuery->getDay( $folder, $uid, $preloadDayIds, $this->isRecursive(), $this->isArchive(), $transforms, ); // Group into dayid $detailMap = []; foreach ($allDetails as &$detail) { $detailMap[$detail['dayid']][] = &$detail; } foreach ($preloadDays as &$day) { $m = $detailMap[$day['dayid']]; if (isset($m) && null !== $m && \count($m) > 0) { $day['detail'] = $m; } } } } }