diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 3ca302e7..9bd25016 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -34,6 +34,7 @@ $config '@PhpCsFixer:risky' => true, 'general_phpdoc_annotation_remove' => ['annotations' => ['expectedDeprecation']], // one should use PHPUnit built-in method instead 'modernize_strpos' => false, // needs PHP 8+ or polyfill + 'phpdoc_to_comment' => ['ignored_tags' => ['psalm-suppress', 'template-implements']], ]) ->setFinder($finder) ; diff --git a/Makefile b/Makefile index 6038e93e..465536e0 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,20 @@ all: dev-setup lint build-js-production test # Dev env management -dev-setup: clean clean-dev npm-init exiftool php-cs-fixer +dev-setup: clean clean-dev npm-init exiftool install-tools exiftool: sh scripts/get-exiftool.sh -php-cs-fixer: - mkdir -p tools/php-cs-fixer - composer require --dev --working-dir=tools/php-cs-fixer friendsofphp/php-cs-fixer +install-tools: + mkdir -p tools + composer require --dev --working-dir=tools friendsofphp/php-cs-fixer vimeo/psalm php-lint: - tools/php-cs-fixer/vendor/bin/php-cs-fixer fix lib + tools/vendor/bin/php-cs-fixer fix lib + +psalm: + tools/vendor/bin/psalm npm-init: npm ci @@ -19,7 +22,7 @@ npm-init: npm-update: npm update -.PHONY: dev-setup exiftool php-cs-fixer php-lint npm-init npm-update +.PHONY: dev-setup exiftool install-tools php-lint psalm npm-init npm-update # Building build-js: diff --git a/composer.json b/composer.json index f57885d0..a9e85a31 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { - "name": "Memories", - "description": "p", + "name": "radialapps/memories", + "description": "Fast and advanced photo management for Nextcloud", "type": "project", "license": "AGPL", "authors": [ @@ -9,7 +9,5 @@ } ], "require": {}, - "require-dev": { - "phpunit/phpunit": "^5.4" - } + "require-dev": {} } diff --git a/lib/ClustersBackend/AlbumsBackend.php b/lib/ClustersBackend/AlbumsBackend.php index f12f0366..c0b6b4cd 100644 --- a/lib/ClustersBackend/AlbumsBackend.php +++ b/lib/ClustersBackend/AlbumsBackend.php @@ -60,7 +60,7 @@ class AlbumsBackend extends Backend return Util::albumsIsEnabled(); } - public function clusterName(string $name) + public function clusterName(string $name): string { return explode('/', $name)[1]; } @@ -138,7 +138,7 @@ class AlbumsBackend extends Backend return $this->albumsQuery->getAlbumPhotos($id, $limit) ?? []; } - public function sortPhotosForPreview(array &$photos) + public function sortPhotosForPreview(array &$photos): void { // Do nothing, the photos are already sorted by added date desc } diff --git a/lib/ClustersBackend/Backend.php b/lib/ClustersBackend/Backend.php index 3e28b7cd..e697bf9a 100644 --- a/lib/ClustersBackend/Backend.php +++ b/lib/ClustersBackend/Backend.php @@ -83,7 +83,7 @@ abstract class Backend /** * Human readable name for the cluster. */ - public function clusterName(string $name) + public function clusterName(string $name): string { return $name; } @@ -92,7 +92,7 @@ abstract class Backend * Put the photo objects in priority list. * Works on the array in place. */ - public function sortPhotosForPreview(array &$photos) + public function sortPhotosForPreview(array &$photos): void { shuffle($photos); } diff --git a/lib/ClustersBackend/FaceRecognitionBackend.php b/lib/ClustersBackend/FaceRecognitionBackend.php index b593d3aa..31937894 100644 --- a/lib/ClustersBackend/FaceRecognitionBackend.php +++ b/lib/ClustersBackend/FaceRecognitionBackend.php @@ -191,7 +191,7 @@ class FaceRecognitionBackend extends Backend return $this->tq->executeQueryWithCTEs($query)->fetchAll() ?: []; } - public function sortPhotosForPreview(array &$photos) + public function sortPhotosForPreview(array &$photos): void { // Convert to recognize format (percentage position-size) foreach ($photos as &$p) { @@ -216,15 +216,15 @@ class FaceRecognitionBackend extends Backend private function model(): int { - return (int) $this->config->getAppValue('facerecognition', 'model', -1); + return (int) $this->config->getAppValue('facerecognition', 'model', (string) -1); } private function minFaceInClusters(): int { - return (int) $this->config->getAppValue('facerecognition', 'min_faces_in_cluster', 5); + return (int) $this->config->getAppValue('facerecognition', 'min_faces_in_cluster', (string) 5); } - private function getFaceRecognitionClusters(int $fileid = 0) + private function getFaceRecognitionClusters(int $fileid = 0): array { $query = $this->tq->getBuilder(); @@ -276,7 +276,7 @@ class FaceRecognitionBackend extends Backend return $this->tq->executeQueryWithCTEs($query)->fetchAll() ?: []; } - private function getFaceRecognitionPersons(int $fileid = 0) + private function getFaceRecognitionPersons(int $fileid = 0): array { $query = $this->tq->getBuilder(); diff --git a/lib/ClustersBackend/PeopleBackendUtils.php b/lib/ClustersBackend/PeopleBackendUtils.php index d0637797..40c832c8 100644 --- a/lib/ClustersBackend/PeopleBackendUtils.php +++ b/lib/ClustersBackend/PeopleBackendUtils.php @@ -87,11 +87,13 @@ trait PeopleBackendUtils * @param array $photo The face object * @param float $padding The padding to add around the face * - * @return [Blob, mimetype] of resulting image + * @return string[] [Blob, mimetype] of resulting image * * @throws \Exception if file could not be used + * + * @psalm-return list{string, string} */ - private function cropFace($file, array $photo, float $padding) + private function cropFace($file, array $photo, float $padding): array { $img = new \OCP\Image(); $img->loadFromData($file->getContent()); @@ -121,6 +123,13 @@ trait PeopleBackendUtils // Max 512x512 $img->scaleDownToFit(512, 512); - return [$img->data(), $img->mimeType()]; + // Get blob and mimetype + $data = $img->data(); + $mime = $img->mimeType(); + if (null === $data || null === $mime) { + throw new \Exception('Could not get image data'); + } + + return [$data, $mime]; } } diff --git a/lib/ClustersBackend/RecognizeBackend.php b/lib/ClustersBackend/RecognizeBackend.php index a36607a7..fcb56c7b 100644 --- a/lib/ClustersBackend/RecognizeBackend.php +++ b/lib/ClustersBackend/RecognizeBackend.php @@ -231,7 +231,7 @@ class RecognizeBackend extends Backend return $this->tq->executeQueryWithCTEs($query)->fetchAll() ?: []; } - public function sortPhotosForPreview(array &$photos) + public function sortPhotosForPreview(array &$photos): void { $this->sortByScores($photos); } diff --git a/lib/Command/Index.php b/lib/Command/Index.php index eb998791..301a066d 100644 --- a/lib/Command/Index.php +++ b/lib/Command/Index.php @@ -203,7 +203,7 @@ class Index extends Command * * @param mixed $closure */ - private function runForUsers($closure) + private function runForUsers($closure): void { if ($uid = $this->opts->user) { if ($user = $this->userManager->get($uid)) { diff --git a/lib/Command/MigrateGoogleTakeout.php b/lib/Command/MigrateGoogleTakeout.php index c284d063..0c59d25a 100644 --- a/lib/Command/MigrateGoogleTakeout.php +++ b/lib/Command/MigrateGoogleTakeout.php @@ -296,7 +296,12 @@ class MigrateGoogleTakeout extends Command ++$this->nProcessed; } - protected function takeoutToExiftoolJson(array $json) + /** + * @return (float|mixed|string)[] + * + * @psalm-return array + */ + protected function takeoutToExiftoolJson(array $json): array { // Helper to get a value from nested JSON $get = static function (string $source) use ($json) { diff --git a/lib/Controller/AdminController.php b/lib/Controller/AdminController.php index a767853c..f2794153 100644 --- a/lib/Controller/AdminController.php +++ b/lib/Controller/AdminController.php @@ -109,9 +109,9 @@ class AdminController extends GenericApiController $status['indexed_count'] = $index->getIndexedCount(); // Automatic indexing stats - $jobStart = $config->getAppValue(Application::APPNAME, 'last_index_job_start', 0); + $jobStart = (int) $config->getAppValue(Application::APPNAME, 'last_index_job_start', (string) 0); $status['last_index_job_start'] = $jobStart ? time() - $jobStart : 0; // Seconds ago - $status['last_index_job_duration'] = $config->getAppValue(Application::APPNAME, 'last_index_job_duration', 0); + $status['last_index_job_duration'] = (float) $config->getAppValue(Application::APPNAME, 'last_index_job_duration', (string) 0); $status['last_index_job_status'] = $config->getAppValue(Application::APPNAME, 'last_index_job_status', 'Indexing has not been run yet'); $status['last_index_job_status_type'] = $config->getAppValue(Application::APPNAME, 'last_index_job_status_type', 'warning'); diff --git a/lib/Controller/DaysController.php b/lib/Controller/DaysController.php index ff81abdb..ef17ff51 100644 --- a/lib/Controller/DaysController.php +++ b/lib/Controller/DaysController.php @@ -134,7 +134,7 @@ class DaysController extends GenericApiController /** * Get transformations depending on the request. */ - private function getTransformations() + private function getTransformations(): array { $transforms = []; @@ -180,7 +180,7 @@ class DaysController extends GenericApiController * * @param array $days the days array */ - private function preloadDays(array &$days) + private function preloadDays(array &$days): void { // Do not preload anything for native clients. // Since the contents of preloads are trusted, clients will not load locals. @@ -234,7 +234,7 @@ class DaysController extends GenericApiController * Convert days response to months response. * The dayId is used to group the days into months. */ - private function daysToMonths(array $days) + private function daysToMonths(array $days): array { $months = []; foreach ($days as $day) { @@ -255,45 +255,49 @@ class DaysController extends GenericApiController return $months; } - /** Convert list of month IDs to list of dayIds */ - private function monthIdToDayIds(int $monthId) + /** + * Convert list of month IDs to list of dayIds. + * + * @return int[] The list of dayIds + */ + private function monthIdToDayIds(int $monthId): array { $dayIds = []; $firstDay = (int) $monthId; $lastDay = strtotime(date('Ymt', $firstDay * 86400)) / 86400; for ($i = $firstDay; $i <= $lastDay; ++$i) { - $dayIds[] = (string) $i; + $dayIds[] = $i; } return $dayIds; } - private function isRecursive() + private function isRecursive(): bool { return null === $this->request->getParam('folder') || $this->request->getParam('recursive'); } - private function isArchive() + private function isArchive(): bool { return null !== $this->request->getParam('archive'); } - private function isHidden() + private function isHidden(): bool { return null !== $this->request->getParam('hidden'); } - private function noPreload() + private function noPreload(): bool { return null !== $this->request->getParam('nopreload'); } - private function isMonthView() + private function isMonthView(): bool { return null !== $this->request->getParam('monthView'); } - private function isReverse() + private function isReverse(): bool { return null !== $this->request->getParam('reverse'); } diff --git a/lib/Controller/DownloadController.php b/lib/Controller/DownloadController.php index e48ad1ce..6141a9d3 100644 --- a/lib/Controller/DownloadController.php +++ b/lib/Controller/DownloadController.php @@ -282,7 +282,7 @@ class DownloadController extends GenericApiController /** @var bool|resource */ $handle = false; - /** @var ?File */ + /** @var ?\OCP\Files\File */ $file = null; /** @var ?string */ diff --git a/lib/Controller/ImageController.php b/lib/Controller/ImageController.php index abe91374..43b7c824 100644 --- a/lib/Controller/ImageController.php +++ b/lib/Controller/ImageController.php @@ -50,7 +50,7 @@ class ImageController extends GenericApiController int $y = 32, bool $a = false, string $mode = 'fill' - ) { + ): Http\Response { return Util::guardEx(function () use ($id, $x, $y, $a, $mode) { if (-1 === $id || 0 === $x || 0 === $y) { throw Exceptions::MissingParameter('id, x, y'); diff --git a/lib/Controller/OtherController.php b/lib/Controller/OtherController.php index 3f5328f5..db117580 100644 --- a/lib/Controller/OtherController.php +++ b/lib/Controller/OtherController.php @@ -77,7 +77,7 @@ class OtherController extends GenericApiController } // helper function to get user config values - $getAppConfig = function ($key, $default) use ($uid) { + $getAppConfig = function ($key, $default) use ($uid): string { return $this->config->getUserValue($uid, Application::APPNAME, $key, $default); }; diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index 78dd5372..5a9509ae 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -69,7 +69,7 @@ class PageController extends Controller { // Image domains MUST be added to the connect domain list // because of the service worker fetch() call - $addImageDomain = static function ($url) use (&$policy) { + $addImageDomain = static function ($url) use (&$policy): void { $policy->addAllowedImageDomain($url); $policy->addAllowedConnectDomain($url); }; @@ -108,7 +108,7 @@ class PageController extends Controller /** * Get params for main.php template. */ - public static function getMainParams() + public static function getMainParams(): array { return [ 'native' => Util::callerIsNative(), diff --git a/lib/Controller/PublicAlbumController.php b/lib/Controller/PublicAlbumController.php index 10dfeb1a..b33b6674 100644 --- a/lib/Controller/PublicAlbumController.php +++ b/lib/Controller/PublicAlbumController.php @@ -139,7 +139,7 @@ class PublicAlbumController extends Controller return $downloadController->file($handle); } - private function addOgMetadata(array $album, string $token) + private function addOgMetadata(array $album, string $token): void { $fileId = (int) $album['last_added_photo']; $albumId = (int) $album['album_id']; diff --git a/lib/Controller/ShareController.php b/lib/Controller/ShareController.php index ed118e53..60292c28 100644 --- a/lib/Controller/ShareController.php +++ b/lib/Controller/ShareController.php @@ -111,7 +111,7 @@ class ShareController extends GenericApiController }); } - private function getNodeByIdOrPath($id, $path) + private function getNodeByIdOrPath($id, $path): \OCP\Files\Node { $uid = Util::getUID(); @@ -133,7 +133,7 @@ class ShareController extends GenericApiController return $file; } - private function makeShareResponse(\OCP\Share\IShare $share) + private function makeShareResponse(\OCP\Share\IShare $share): array { /** @var \OCP\IURLGenerator $urlGenerator */ $urlGenerator = \OC::$server->get(\OCP\IURLGenerator::class); diff --git a/lib/Controller/VideoController.php b/lib/Controller/VideoController.php index 41d78bfb..13ea0a94 100644 --- a/lib/Controller/VideoController.php +++ b/lib/Controller/VideoController.php @@ -118,7 +118,7 @@ class VideoController extends GenericApiController string $liveid = '', string $format = '', string $transcode = '' - ) { + ): Http\Response { return Util::guardEx(function () use ($fileid, $liveid, $format, $transcode) { $file = $this->fs->getUserFile($fileid); @@ -230,7 +230,7 @@ class VideoController extends GenericApiController }); } - private function getUpstream(string $client, string $path, string $profile) + private function getUpstream(string $client, string $path, string $profile): int { $returnCode = $this->getUpstreamInternal($client, $path, $profile); @@ -251,7 +251,7 @@ class VideoController extends GenericApiController return $returnCode; } - private function getUpstreamInternal(string $client, string $path, string $profile) + private function getUpstreamInternal(string $client, string $path, string $profile): int { // Make sure query params are repeated // For example, in folder sharing, we need the params on every request diff --git a/lib/Cron/IndexJob.php b/lib/Cron/IndexJob.php index f0ccda79..9ea4f6ed 100644 --- a/lib/Cron/IndexJob.php +++ b/lib/Cron/IndexJob.php @@ -39,7 +39,7 @@ class IndexJob extends TimedJob $this->setInterval(INTERVAL); } - protected function run($arguments) + protected function run($argument) { // Check if indexing is enabled if ('0' === Util::getSystemConfig('memories.index.mode')) { @@ -47,12 +47,12 @@ class IndexJob extends TimedJob } // Store the last run time - $this->config->setAppValue(Application::APPNAME, 'last_index_job_start', time()); - $this->config->setAppValue(Application::APPNAME, 'last_index_job_duration', 0); + $this->config->setAppValue(Application::APPNAME, 'last_index_job_start', (string) time()); + $this->config->setAppValue(Application::APPNAME, 'last_index_job_duration', (string) 0); // Run for a maximum of 5 minutes $startTime = microtime(true); - $this->service->continueCheck = static function () use ($startTime) { + $this->service->continueCheck = static function () use ($startTime): bool { return (microtime(true) - $startTime) < MAX_RUN_TIME; }; @@ -81,7 +81,7 @@ class IndexJob extends TimedJob // Store the last run duration $duration = round(microtime(true) - $startTime, 2); - $this->config->setAppValue(Application::APPNAME, 'last_index_job_duration', $duration); + $this->config->setAppValue(Application::APPNAME, 'last_index_job_duration', (string) $duration); } /** diff --git a/lib/Db/AddMissingIndices.php b/lib/Db/AddMissingIndices.php index 977d70eb..dff0051f 100644 --- a/lib/Db/AddMissingIndices.php +++ b/lib/Db/AddMissingIndices.php @@ -10,7 +10,7 @@ class AddMissingIndices /** * Add missing indices to the database schema. */ - public static function run(IOutput $output) + public static function run(IOutput $output): SchemaWrapper { $connection = \OC::$server->get(\OC\DB\Connection::class); $schema = new SchemaWrapper($connection); diff --git a/lib/Db/AlbumsQuery.php b/lib/Db/AlbumsQuery.php index 8254d388..c025243f 100644 --- a/lib/Db/AlbumsQuery.php +++ b/lib/Db/AlbumsQuery.php @@ -22,7 +22,7 @@ class AlbumsQuery * @param bool $shared Whether to get shared albums * @param int $fileid File to filter by */ - public function getList(string $uid, bool $shared = false, int $fileid = 0) + public function getList(string $uid, bool $shared = false, int $fileid = 0): array { $query = $this->connection->getQueryBuilder(); @@ -234,7 +234,7 @@ class AlbumsQuery /** * Get list of photos in album. */ - public function getAlbumPhotos(int $albumId, ?int $limit) + public function getAlbumPhotos(int $albumId, ?int $limit): array { $query = $this->connection->getQueryBuilder(); @@ -283,8 +283,10 @@ class AlbumsQuery return $groups; } - /** Get the name of the collaborators table */ - private function collaboratorsTable() + /** + * Get the name of the collaborators table. + */ + private function collaboratorsTable(): string { // https://github.com/nextcloud/photos/commit/20e3e61ad577014e5f092a292c90a8476f630355 $appManager = \OC::$server->get(\OCP\App\IAppManager::class); diff --git a/lib/Db/FsManager.php b/lib/Db/FsManager.php index 0b3fa186..6c524dbc 100644 --- a/lib/Db/FsManager.php +++ b/lib/Db/FsManager.php @@ -74,7 +74,7 @@ class FsManager * @param TimelineRoot $root Root object to populate (by reference) * @param bool $recursive Whether to get the folders recursively */ - public function populateRoot(TimelineRoot &$root, bool $recursive = true) + public function populateRoot(TimelineRoot &$root, bool $recursive = true): TimelineRoot { $user = $this->userSession->getUser(); @@ -183,7 +183,7 @@ class FsManager /** * Get a file with ID for the current user. * - * @throws Exceptions\NotFoundFile + * @throws \OCA\Memories\HttpResponseException */ public function getUserFile(int $fileId): File { @@ -298,7 +298,7 @@ class FsManager return null; } - public function getShareObject() + public function getShareObject(): ?IShare { // Get token from request $token = $this->getShareToken(); diff --git a/lib/Db/LivePhoto.php b/lib/Db/LivePhoto.php index 842e1bb3..d101bd41 100644 --- a/lib/Db/LivePhoto.php +++ b/lib/Db/LivePhoto.php @@ -18,8 +18,10 @@ class LivePhoto $this->connection = $connection; } - /** Check if a given Exif data is the video part of a Live Photo */ - public function isVideoPart(array $exif) + /** + * Check if a given Exif data is the video part of a Live Photo. + */ + public function isVideoPart(array $exif): bool { return \array_key_exists('MIMEType', $exif) && 'video/quicktime' === $exif['MIMEType'] diff --git a/lib/Db/TimelineQuery.php b/lib/Db/TimelineQuery.php index 06242fc9..110de2b8 100644 --- a/lib/Db/TimelineQuery.php +++ b/lib/Db/TimelineQuery.php @@ -37,16 +37,19 @@ class TimelineQuery $this->request = $request; } - public function allowEmptyRoot(bool $value = true) + public function allowEmptyRoot(bool $value = true): void { $this->_rootEmptyAllowed = $value; } - public function getBuilder() + public function getBuilder(): IQueryBuilder { return $this->connection->getQueryBuilder(); } + /** + * @return never + */ public static function debugQuery(IQueryBuilder &$query, string $sql = '') { // Print the query and exit @@ -58,7 +61,7 @@ class TimelineQuery exit; // only for debugging, so this is okay } - public static function replaceQueryParams(IQueryBuilder &$query, string $sql) + public static function replaceQueryParams(IQueryBuilder &$query, string $sql): string { $params = $query->getParameters(); $platform = $query->getConnection()->getDatabasePlatform(); diff --git a/lib/Db/TimelineQueryDays.php b/lib/Db/TimelineQueryDays.php index 4f41d5cf..c7981c5f 100644 --- a/lib/Db/TimelineQueryDays.php +++ b/lib/Db/TimelineQueryDays.php @@ -57,11 +57,11 @@ trait TimelineQueryDays /** * Get the day response from the database for the timeline. * - * @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 bool $hidden If the query should include hidden files - * @param array $queryTransforms The query transformations to apply + * @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 bool $hidden If the query should include hidden files + * @param array $queryTransforms The query transformations to apply * * @return array An array of day responses */ @@ -124,7 +124,7 @@ trait TimelineQueryDays return $day; } - public function executeQueryWithCTEs(IQueryBuilder $query, string $psql = '') + public function executeQueryWithCTEs(IQueryBuilder $query, string $psql = ''): \OCP\DB\IResult { $sql = empty($psql) ? $query->getSQL() : $psql; $params = $query->getParameters(); @@ -211,7 +211,7 @@ trait TimelineQueryDays * * @param array $days */ - private function processDays($days) + private function processDays($days): array { foreach ($days as &$row) { $row['dayid'] = (int) $row['dayid']; @@ -224,7 +224,7 @@ trait TimelineQueryDays /** * Process the single day response. */ - private function processDayPhoto(array &$row) + private function processDayPhoto(array &$row): void { // Convert field types $row['fileid'] = (int) $row['fileid']; @@ -278,7 +278,7 @@ trait TimelineQueryDays TimelineRoot &$root, bool $archive, bool $hidden - ) { + ): void { // Add query parameters $query->setParameter('topFolderIds', $root->getIds(), IQueryBuilder::PARAM_INT_ARRAY); diff --git a/lib/Db/TimelineQueryFilters.php b/lib/Db/TimelineQueryFilters.php index 5c31aa18..c7eaa49d 100644 --- a/lib/Db/TimelineQueryFilters.php +++ b/lib/Db/TimelineQueryFilters.php @@ -10,7 +10,7 @@ use OCP\ITags; trait TimelineQueryFilters { - public function transformFavoriteFilter(IQueryBuilder &$query, bool $aggregate) + public function transformFavoriteFilter(IQueryBuilder &$query, bool $aggregate): void { if (Util::isLoggedIn()) { $query->innerJoin('m', 'vcategory_to_object', 'vcoi', $query->expr()->andX( @@ -20,7 +20,7 @@ trait TimelineQueryFilters } } - public function addFavoriteTag(IQueryBuilder &$query) + public function addFavoriteTag(IQueryBuilder &$query): void { if (Util::isLoggedIn()) { $query->leftJoin('m', 'vcategory_to_object', 'vco', $query->expr()->andX( @@ -31,12 +31,12 @@ trait TimelineQueryFilters } } - public function transformVideoFilter(IQueryBuilder &$query, bool $aggregate) + public function transformVideoFilter(IQueryBuilder &$query, bool $aggregate): void { $query->andWhere($query->expr()->eq('m.isvideo', $query->expr()->literal(1))); } - public function transformLimit(IQueryBuilder &$query, bool $aggregate, int $limit) + public function transformLimit(IQueryBuilder &$query, bool $aggregate, int $limit): void { if ($limit >= 1 || $limit <= 100) { $query->setMaxResults($limit); diff --git a/lib/Db/TimelineQueryFolders.php b/lib/Db/TimelineQueryFolders.php index 485fbece..a2d9bae2 100644 --- a/lib/Db/TimelineQueryFolders.php +++ b/lib/Db/TimelineQueryFolders.php @@ -17,7 +17,7 @@ trait TimelineQueryFolders * * @param TimelineRoot $root The root to use for the query */ - public function getRootPreviews(TimelineRoot $root) + public function getRootPreviews(TimelineRoot $root): array { $query = $this->connection->getQueryBuilder(); diff --git a/lib/Db/TimelineQueryLivePhoto.php b/lib/Db/TimelineQueryLivePhoto.php index e52cfa77..c216aff6 100644 --- a/lib/Db/TimelineQueryLivePhoto.php +++ b/lib/Db/TimelineQueryLivePhoto.php @@ -6,7 +6,7 @@ namespace OCA\Memories\Db; trait TimelineQueryLivePhoto { - public function getLivePhotos(int $fileid) + public function getLivePhotos(int $fileid): array { $qb = $this->connection->getQueryBuilder(); $qb->select('lp.fileid', 'lp.liveid') diff --git a/lib/Db/TimelineQueryMap.php b/lib/Db/TimelineQueryMap.php index 58a66bef..694c922c 100644 --- a/lib/Db/TimelineQueryMap.php +++ b/lib/Db/TimelineQueryMap.php @@ -13,7 +13,7 @@ trait TimelineQueryMap protected IDBConnection $connection; - public function transformMapBoundsFilter(IQueryBuilder &$query, bool $aggregate, string $bounds, string $table = 'm') + public function transformMapBoundsFilter(IQueryBuilder &$query, bool $aggregate, string $bounds, string $table = 'm'): void { $bounds = explode(',', $bounds); $bounds = array_map('floatval', $bounds); @@ -84,7 +84,7 @@ trait TimelineQueryMap return $clusters; } - public function getMapClusterPreviews(array $clusterIds) + public function getMapClusterPreviews(array $clusterIds): array { $query = $this->connection->getQueryBuilder(); diff --git a/lib/Db/TimelineQueryNativeX.php b/lib/Db/TimelineQueryNativeX.php index 8e355746..4eab3db9 100644 --- a/lib/Db/TimelineQueryNativeX.php +++ b/lib/Db/TimelineQueryNativeX.php @@ -8,7 +8,7 @@ use OCP\DB\QueryBuilder\IQueryBuilder; trait TimelineQueryNativeX { - public function transformNativeQuery(IQueryBuilder &$query, bool $aggregate) + public function transformNativeQuery(IQueryBuilder &$query, bool $aggregate): void { if (!$aggregate) { $query->addSelect('m.epoch', 'f.size', 'm.buid'); diff --git a/lib/Db/TimelineRoot.php b/lib/Db/TimelineRoot.php index 5544ce1d..501287f9 100644 --- a/lib/Db/TimelineRoot.php +++ b/lib/Db/TimelineRoot.php @@ -23,7 +23,7 @@ class TimelineRoot * * @throws \Exception if node is not valid readable folder */ - public function addFolder(FileInfo $info) + public function addFolder(FileInfo $info): void { $path = $info->getPath(); @@ -42,7 +42,7 @@ class TimelineRoot /** * Add mountpoints recursively. */ - public function addMountPoints() + public function addMountPoints(): void { $manager = \OC\Files\Filesystem::getMountManager(); foreach ($this->folderPaths as $id => $folderPath) { @@ -67,7 +67,7 @@ class TimelineRoot * * @param string[] $paths The paths to exclude */ - public function excludePaths(array $paths) + public function excludePaths(array $paths): void { foreach ($paths as $path) { foreach ($this->folderPaths as $id => $folderPath) { @@ -87,7 +87,7 @@ class TimelineRoot * * @param string $path The new base path */ - public function baseChange(string $path) + public function baseChange(string $path): void { foreach ($this->folderPaths as $id => $folderPath) { if (!str_starts_with($folderPath.'/', $path.'/')) { @@ -101,11 +101,17 @@ class TimelineRoot return $this->folderPaths[$id]; } - public function getIds() + /** + * @return int[] + */ + public function getIds(): array { return array_keys($this->folderPaths); } + /** + * @return null|int + */ public function getOneId() { return array_key_first($this->folders); @@ -116,12 +122,12 @@ class TimelineRoot return $this->folders[$id]; } - public function isEmpty() + public function isEmpty(): bool { return empty($this->folderPaths); } - private function setFolder(int $id, ?FileInfo $fileInfo, ?string $path) + private function setFolder(int $id, ?FileInfo $fileInfo, ?string $path): void { if (null !== $path) { $this->folderPaths[$id] = $path; diff --git a/lib/Db/TimelineWrite.php b/lib/Db/TimelineWrite.php index a7cbc6cd..7281a45f 100644 --- a/lib/Db/TimelineWrite.php +++ b/lib/Db/TimelineWrite.php @@ -192,7 +192,7 @@ class TimelineWrite /** * Remove a file from the exif database. */ - public function deleteFile(File &$file) + public function deleteFile(File &$file): void { // Get full record $query = $this->connection->getQueryBuilder(); @@ -255,7 +255,7 @@ class TimelineWrite /** * Clear the entire index. Does not need confirmation! */ - public function clear() + public function clear(): void { $p = $this->connection->getDatabasePlatform(); foreach (array_merge(DELETE_TABLES, TRUNCATE_TABLES) as $table) { diff --git a/lib/Db/TimelineWriteOrphans.php b/lib/Db/TimelineWriteOrphans.php index 6e842102..2554663a 100644 --- a/lib/Db/TimelineWriteOrphans.php +++ b/lib/Db/TimelineWriteOrphans.php @@ -22,7 +22,7 @@ trait TimelineWriteOrphans */ public function orphanAll(bool $value = true, ?array $fileIds = null, bool $onlyMain = false): int { - $do = function (string $table) use ($value, $fileIds) { + $do = function (string $table) use ($value, $fileIds): int { $query = $this->connection->getQueryBuilder(); $query->update($table) ->set('orphan', $query->createNamedParameter($value, IQueryBuilder::PARAM_BOOL)) @@ -51,7 +51,7 @@ trait TimelineWriteOrphans * @param int $txnSize number of rows to process in a single transaction * @param \Closure $callback will be passed each row */ - public function orphanAndRun(array $fields, int $txnSize, \Closure $callback) + public function orphanAndRun(array $fields, int $txnSize, \Closure $callback): void { // Orphan all files. This means if we are interrupted, // it will lead to a re-index of the whole library! diff --git a/lib/Db/TimelineWritePlaces.php b/lib/Db/TimelineWritePlaces.php index 57a7b24b..0546bf22 100644 --- a/lib/Db/TimelineWritePlaces.php +++ b/lib/Db/TimelineWritePlaces.php @@ -156,8 +156,12 @@ trait TimelineWritePlaces * Read coordinates from array and round to 6 decimal places. * * Modifies the array to remove invalid coordinates. + * + * @return (null|float)[] + * + * @psalm-return list{float|null, float|null} */ - private static function readCoord(array &$exif) + private static function readCoord(array &$exif): array { $lat = \array_key_exists(LAT_KEY, $exif) ? round((float) $exif[LAT_KEY], 6) : null; $lon = \array_key_exists(LON_KEY, $exif) ? round((float) $exif[LON_KEY], 6) : null; diff --git a/lib/Exif.php b/lib/Exif.php index e96222d6..d1334df1 100644 --- a/lib/Exif.php +++ b/lib/Exif.php @@ -19,7 +19,7 @@ class Exif private static $staticPipes; private static $noStaticProc = false; - public static function closeStaticExiftoolProc() + public static function closeStaticExiftoolProc(): void { try { if (self::$staticProc) { @@ -35,13 +35,13 @@ class Exif } } - public static function restartStaticExiftoolProc() + public static function restartStaticExiftoolProc(): void { self::closeStaticExiftoolProc(); self::ensureStaticExiftoolProc(); } - public static function ensureStaticExiftoolProc() + public static function ensureStaticExiftoolProc(): void { if (self::$noStaticProc) { return; @@ -68,7 +68,7 @@ class Exif /** * Get exif data as a JSON object from a Nextcloud file. */ - public static function getExifFromFile(File $file) + public static function getExifFromFile(File $file): array { try { $path = $file->getStorage()->getLocalFile($file->getInternalPath()); @@ -106,8 +106,10 @@ class Exif return $exif; } - /** Get exif data as a JSON object from a local file path */ - public static function getExifFromLocalPath(string $path) + /** + * Get exif data as a JSON object from a local file path. + */ + public static function getExifFromLocalPath(string $path): array { if (null !== self::$staticProc) { self::ensureStaticExiftoolProc(); @@ -298,7 +300,7 @@ class Exif * * @throws \Exception on failure */ - public static function setExif(string $path, array $data) + public static function setExif(string $path, array $data): void { $data['SourceFile'] = $path; $raw = json_encode([$data], JSON_UNESCAPED_UNICODE); @@ -328,7 +330,7 @@ class Exif } } - public static function setFileExif(File $file, array $data) + public static function setFileExif(File $file, array $data): void { // Get path to local file so we can skip reading $path = $file->getStorage()->getLocalFile($file->getInternalPath()); @@ -345,7 +347,7 @@ class Exif $file->touch(); } - public static function getBinaryExifProp(string $path, string $prop) + public static function getBinaryExifProp(string $path, string $prop): string { $pipes = []; $proc = proc_open(array_merge(self::getExiftool(), [$prop, '-n', '-b', $path]), [ @@ -368,7 +370,7 @@ class Exif } } - public static function getExifWithDuplicates(string $path) + public static function getExifWithDuplicates(string $path): array { return self::getExifFromLocalPathWithSeparateProc($path, ['-U', '-G4']); } @@ -378,8 +380,10 @@ class Exif return BinExt::getExiftool(); } - /** Initialize static exiftool process for local reads */ - private static function initializeStaticExiftoolProc() + /** + * Initialize static exiftool process for local reads. + */ + private static function initializeStaticExiftoolProc(): void { self::closeStaticExiftoolProc(); self::$staticProc = proc_open(array_merge(self::getExiftool(), ['-stay_open', 'true', '-@', '-']), [ @@ -397,7 +401,7 @@ class Exif * @param int $timeout milliseconds * @param string $delimiter null for eof */ - private static function readOrTimeout($handle, int $timeout, ?string $delimiter = null) + private static function readOrTimeout($handle, int $timeout, ?string $delimiter = null): string { $buf = ''; $waitedMs = 0; @@ -420,7 +424,7 @@ class Exif return $buf; } - private static function getExifFromLocalPathWithStaticProc(string $path) + private static function getExifFromLocalPathWithStaticProc(string $path): array { $args = implode("\n", self::EXIFTOOL_ARGS); fwrite(self::$staticPipes[0], "{$path}\n{$args}\n-execute\n"); @@ -442,7 +446,7 @@ class Exif } } - private static function getExifFromLocalPathWithSeparateProc(string $path, array $extraArgs = []) + private static function getExifFromLocalPathWithSeparateProc(string $path, array $extraArgs = []): array { $pipes = []; $proc = proc_open(array_merge(self::getExiftool(), self::EXIFTOOL_ARGS, $extraArgs, [$path]), [ @@ -468,7 +472,7 @@ class Exif } /** Get json array from stdout of exiftool */ - private static function processStdout(string $stdout) + private static function processStdout(string $stdout): array { $json = json_decode($stdout, true); if (!$json) { diff --git a/lib/Listeners/BeforeTemplateListener.php b/lib/Listeners/BeforeTemplateListener.php index fd8f20b7..f1283b70 100644 --- a/lib/Listeners/BeforeTemplateListener.php +++ b/lib/Listeners/BeforeTemplateListener.php @@ -26,6 +26,9 @@ use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\ISession; +/** + * @template-implements IEventListener + */ class BeforeTemplateListener implements IEventListener { private ISession $session; diff --git a/lib/Listeners/PostDeleteListener.php b/lib/Listeners/PostDeleteListener.php index d2bf80e3..23d9db66 100644 --- a/lib/Listeners/PostDeleteListener.php +++ b/lib/Listeners/PostDeleteListener.php @@ -27,6 +27,9 @@ use OCP\EventDispatcher\IEventListener; use OCP\Files\Events\Node\NodeDeletedEvent; use OCP\Files\Folder; +/** + * @template-implements IEventListener + */ class PostDeleteListener implements IEventListener { private TimelineWrite $util; diff --git a/lib/Listeners/PostLogoutListener.php b/lib/Listeners/PostLogoutListener.php index a4d1d131..44dd8060 100644 --- a/lib/Listeners/PostLogoutListener.php +++ b/lib/Listeners/PostLogoutListener.php @@ -25,6 +25,9 @@ use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\ISession; +/** + * @template-implements IEventListener + */ class PostLogoutListener implements IEventListener { public const CLEAR_CACHE_KEY = 'memories_clear_cache'; diff --git a/lib/Listeners/PostWriteListener.php b/lib/Listeners/PostWriteListener.php index 9fca2e6e..937e11e2 100644 --- a/lib/Listeners/PostWriteListener.php +++ b/lib/Listeners/PostWriteListener.php @@ -29,6 +29,9 @@ use OCP\Files\Events\Node\NodeTouchedEvent; use OCP\Files\Events\Node\NodeWrittenEvent; use Psr\Log\LoggerInterface; +/** + * @template-implements IEventListener + */ class PostWriteListener implements IEventListener { private TimelineWrite $timelineWrite; diff --git a/lib/Service/BinExt.php b/lib/Service/BinExt.php index b6722898..bbbd406e 100644 --- a/lib/Service/BinExt.php +++ b/lib/Service/BinExt.php @@ -164,7 +164,7 @@ class BinExt return "http://{$connect}/{$client}{$path}/{$profile}"; } - public static function getGoVodConfig($local = false) + public static function getGoVodConfig(bool $local = false): array { // Get config from system values $env = [ @@ -205,7 +205,7 @@ class BinExt /** * Get temp binary for go-vod. */ - public static function getGoVodBin() + public static function getGoVodBin(): string { $path = Util::getSystemConfig('memories.vod.path'); @@ -332,8 +332,10 @@ class BinExt return $version; } - /** POST a new configuration to go-vod */ - public static function configureGoVod() + /** + * POST a new configuration to go-vod. + */ + public static function configureGoVod(): bool { // Get config $config = self::getGoVodConfig(); @@ -412,7 +414,7 @@ class BinExt return $ffmpegPath; } - public static function testFFmpeg(string $path, string $name) + public static function testFFmpeg(string $path, string $name): string { $version = shell_exec("{$path} -version"); if (!preg_match("/{$name} version \\S*/", $version, $matches)) { @@ -422,12 +424,12 @@ class BinExt return explode(' ', $matches[0])[2]; } - public static function testSystemPerl(string $path): string + public static function testSystemPerl(string $path): ?string { if (($out = shell_exec("{$path} -e 'print \"OK\";'")) !== 'OK') { throw new \Exception('Failed to run test perl script: '.$out); } - return shell_exec("{$path} -e 'print $^V;'"); + return shell_exec("{$path} -e 'print $^V;'") ?: null; } } diff --git a/lib/Service/FileRobotMagick.php b/lib/Service/FileRobotMagick.php index e247a39f..a764b491 100644 --- a/lib/Service/FileRobotMagick.php +++ b/lib/Service/FileRobotMagick.php @@ -123,7 +123,7 @@ class FileRobotImageState } } - private function _set(array $parent, string $key, string $ckey = null) + private function _set(array $parent, string $key, string $ckey = null): void { $ckey ??= $key; if (\array_key_exists($key, $parent)) { @@ -146,7 +146,7 @@ class FileRobotMagick $this->state = new FileRobotImageState($state); } - public function apply() + public function apply(): \Imagick { // Ensure the image is in the correct colorspace if (\Imagick::COLORSPACE_SRGB !== $this->image->getColorspace()) { @@ -179,7 +179,7 @@ class FileRobotMagick return $this->image; } - protected function applyCrop() + protected function applyCrop(): void { if ($this->state->cropX || $this->state->cropY || $this->state->cropWidth || $this->state->cropHeight) { $iw = $this->image->getImageWidth(); @@ -193,7 +193,7 @@ class FileRobotMagick } } - protected function applyFlipRotation() + protected function applyFlipRotation(): void { if ($this->state->isFlippedX) { $this->image->flopImage(); @@ -206,7 +206,7 @@ class FileRobotMagick } } - protected function applyResize() + protected function applyResize(): void { if ($this->state->resizeWidth || $this->state->resizeHeight) { $this->image->resizeImage( @@ -218,7 +218,7 @@ class FileRobotMagick } } - protected function applyBrighten(?float $value = null) + protected function applyBrighten(?float $value = null): void { $brightness = $value ?? $this->state->brightness ?? 0; if (0 === $brightness) { @@ -229,7 +229,7 @@ class FileRobotMagick $this->image->evaluateImage(\Imagick::EVALUATE_ADD, $brightness * 255 * 255, \Imagick::CHANNEL_ALL); } - protected function applyContrast(?float $value = null) + protected function applyContrast(?float $value = null): void { $contrast = $value ?? $this->state->contrast ?? 0; if (0 === $contrast) { @@ -246,7 +246,7 @@ class FileRobotMagick $this->image->functionImage(\Imagick::FUNCTION_POLYNOMIAL, [$m, $c], \Imagick::CHANNEL_ALL); } - protected function applyHSV(?float $hue = null, ?float $saturation = null, ?float $value = null) + protected function applyHSV(?float $hue = null, ?float $saturation = null, ?float $value = null): void { $hue ??= $this->state->hue ?? 0; $saturation ??= $this->state->saturation ?? 0; @@ -274,18 +274,17 @@ class FileRobotMagick $bg = 0.587 * $v - 0.586 * $vsu - 1.05 * $vsw; $bb = 0.114 * $v + 0.886 * $vsu - 0.2 * $vsw; - $colorMatrix = [ + /** @psalm-suppress InvalidArgument */ + $this->image->colorMatrixImage([ $rr, $rg, $rb, 0, 0, $gr, $gg, $gb, 0, 0, $br, $bg, $bb, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, - ]; - - $this->image->colorMatrixImage($colorMatrix); + ]); } - protected function applyBlur() + protected function applyBlur(): void { if ($this->state->blurRadius <= 0) { return; @@ -296,7 +295,7 @@ class FileRobotMagick $this->image->blurImage(0, $sigma); } - protected function applyWarmth() + protected function applyWarmth(): void { // https://github.com/scaleflex/filerobot-image-editor/blob/7113bf4968d97f41381f4a2965a59defd44562c8/packages/react-filerobot-image-editor/src/custom/finetunes/Warmth.js#L17-L28 $warmth = ($this->state->warmth ?? 0); @@ -310,20 +309,21 @@ class FileRobotMagick } // https://github.com/scaleflex/filerobot-image-editor/blob/7113bf4968d97f41381f4a2965a59defd44562c8/packages/react-filerobot-image-editor/src/components/tools/Filters/Filters.constants.js#L8 - protected function applyFilterInvert() + protected function applyFilterInvert(): void { $this->image->negateImage(false); } - protected function applyFilterBlackAndWhite() + protected function applyFilterBlackAndWhite(): void { // https://github.com/scaleflex/filerobot-image-editor/blob/7113bf4968d97f41381f4a2965a59defd44562c8/packages/react-filerobot-image-editor/src/custom/filters/BlackAndWhite.js $this->image->thresholdImage(100 * 255); } - protected function applyFilterSepia() + protected function applyFilterSepia(): void { // https://github.com/konvajs/konva/blob/master/src/filters/Sepia.ts + /** @psalm-suppress InvalidArgument */ $this->image->colorMatrixImage([ 0.393, 0.769, 0.189, 0, 0, 0.349, 0.686, 0.168, 0, 0, @@ -333,267 +333,267 @@ class FileRobotMagick ]); } - protected function applyFilterSolarize() + protected function applyFilterSolarize(): void { // https://github.com/konvajs/konva/blob/master/src/filters/Solarize.ts $this->image->solarizeImage(128 * 255); } - protected function applyFilterClarendon() + protected function applyFilterClarendon(): void { $this->applyBaseFilterBrightness(0.1); $this->applyBaseFilterContrast(0.1); $this->applyBaseFilterSaturation(0.15); } - protected function applyFilterGingham() + protected function applyFilterGingham(): void { $this->applyBaseFilterSepia(0.04); $this->applyBaseFilterContrast(-0.15); } - protected function applyFilterMoon() + protected function applyFilterMoon(): void { $this->applyBaseFilterGrayscale(); $this->applyBaseFilterBrightness(0.1); } - protected function applyFilterLark() + protected function applyFilterLark(): void { $this->applyBaseFilterBrightness(0.08); $this->applyBaseFilterAdjustRGB(1, 1.03, 1.05); $this->applyBaseFilterSaturation(0.12); } - protected function applyFilterReyes() + protected function applyFilterReyes(): void { $this->applyBaseFilterSepia(0.4); $this->applyBaseFilterBrightness(0.13); $this->applyBaseFilterContrast(-0.05); } - protected function applyFilterJuno() + protected function applyFilterJuno(): void { $this->applyBaseFilterAdjustRGB(1.01, 1.04, 1); $this->applyBaseFilterSaturation(0.3); } - protected function applyFilterSlumber() + protected function applyFilterSlumber(): void { $this->applyBaseFilterBrightness(0.1); $this->applyBaseFilterSaturation(-0.5); } - protected function applyFilterCrema() + protected function applyFilterCrema(): void { $this->applyBaseFilterAdjustRGB(1.04, 1, 1.02); $this->applyBaseFilterSaturation(-0.05); } - protected function applyFilterLudwig() + protected function applyFilterLudwig(): void { $this->applyBaseFilterBrightness(0.05); $this->applyBaseFilterSaturation(-0.03); } - protected function applyFilterAden() + protected function applyFilterAden(): void { $this->applyBaseFilterColorFilter(228, 130, 225, 0.13); $this->applyBaseFilterSaturation(-0.2); } - protected function applyFilterPerpetua() + protected function applyFilterPerpetua(): void { $this->applyBaseFilterAdjustRGB(1.05, 1.1, 1); } - protected function applyFilterAmaro() + protected function applyFilterAmaro(): void { $this->applyBaseFilterSaturation(0.3); $this->applyBaseFilterBrightness(0.15); } - protected function applyFilterMayfair() + protected function applyFilterMayfair(): void { $this->applyBaseFilterColorFilter(230, 115, 108, 0.05); $this->applyBaseFilterSaturation(0.15); } - protected function applyFilterRise() + protected function applyFilterRise(): void { $this->applyBaseFilterColorFilter(255, 170, 0, 0.1); $this->applyBaseFilterBrightness(0.09); $this->applyBaseFilterSaturation(0.1); } - protected function applyFilterHudson() + protected function applyFilterHudson(): void { $this->applyBaseFilterAdjustRGB(1, 1, 1.25); $this->applyBaseFilterContrast(0.1); $this->applyBaseFilterBrightness(0.15); } - protected function applyFilterValencia() + protected function applyFilterValencia(): void { $this->applyBaseFilterColorFilter(255, 225, 80, 0.08); $this->applyBaseFilterSaturation(0.1); $this->applyBaseFilterContrast(0.05); } - protected function applyFilterXpro2() + protected function applyFilterXpro2(): void { $this->applyBaseFilterColorFilter(255, 255, 0, 0.07); $this->applyBaseFilterSaturation(0.2); $this->applyBaseFilterContrast(0.15); } - protected function applyFilterSierra() + protected function applyFilterSierra(): void { $this->applyBaseFilterContrast(-0.15); $this->applyBaseFilterSaturation(0.1); } - protected function applyFilterWillow() + protected function applyFilterWillow(): void { $this->applyBaseFilterGrayscale(); $this->applyBaseFilterColorFilter(100, 28, 210, 0.03); $this->applyBaseFilterBrightness(0.1); } - protected function applyFilterLoFi() + protected function applyFilterLoFi(): void { $this->applyBaseFilterContrast(0.15); $this->applyBaseFilterSaturation(0.2); } - protected function applyFilterInkwell() + protected function applyFilterInkwell(): void { $this->applyBaseFilterGrayscale(); } - protected function applyFilterHefe() + protected function applyFilterHefe(): void { $this->applyBaseFilterContrast(0.1); $this->applyBaseFilterSaturation(0.15); } - protected function applyFilterNashville() + protected function applyFilterNashville(): void { $this->applyBaseFilterColorFilter(220, 115, 188, 0.12); $this->applyBaseFilterContrast(-0.05); } - protected function applyFilterStinson() + protected function applyFilterStinson(): void { $this->applyBaseFilterBrightness(0.1); $this->applyBaseFilterSepia(0.3); } - protected function applyFilterVesper() + protected function applyFilterVesper(): void { $this->applyBaseFilterColorFilter(255, 225, 0, 0.05); $this->applyBaseFilterBrightness(0.06); $this->applyBaseFilterContrast(0.06); } - protected function applyFilterEarlybird() + protected function applyFilterEarlybird(): void { $this->applyBaseFilterColorFilter(255, 165, 40, 0.2); } - protected function applyFilterBrannan() + protected function applyFilterBrannan(): void { $this->applyBaseFilterContrast(0.2); $this->applyBaseFilterColorFilter(140, 10, 185, 0.1); } - protected function applyFilterSutro() + protected function applyFilterSutro(): void { $this->applyBaseFilterBrightness(-0.1); $this->applyBaseFilterContrast(-0.1); } - protected function applyFilterToaster() + protected function applyFilterToaster(): void { $this->applyBaseFilterSepia(0.1); $this->applyBaseFilterColorFilter(255, 145, 0, 0.2); } - protected function applyFilterWalden() + protected function applyFilterWalden(): void { $this->applyBaseFilterBrightness(0.1); $this->applyBaseFilterColorFilter(255, 255, 0, 0.2); } - protected function applyFilterNinteenSeventySeven() + protected function applyFilterNinteenSeventySeven(): void { $this->applyBaseFilterColorFilter(255, 25, 0, 0.15); $this->applyBaseFilterBrightness(0.1); } - protected function applyFilterKelvin() + protected function applyFilterKelvin(): void { $this->applyBaseFilterColorFilter(255, 140, 0, 0.1); $this->applyBaseFilterAdjustRGB(1.15, 1.05, 1); $this->applyBaseFilterSaturation(0.35); } - protected function applyFilterMaven() + protected function applyFilterMaven(): void { $this->applyBaseFilterColorFilter(225, 240, 0, 0.1); $this->applyBaseFilterSaturation(0.25); $this->applyBaseFilterContrast(0.05); } - protected function applyFilterGinza() + protected function applyFilterGinza(): void { $this->applyBaseFilterSepia(0.06); $this->applyBaseFilterBrightness(0.1); } - protected function applyFilterSkyline() + protected function applyFilterSkyline(): void { $this->applyBaseFilterSaturation(0.35); $this->applyBaseFilterBrightness(0.1); } - protected function applyFilterDogpatch() + protected function applyFilterDogpatch(): void { $this->applyBaseFilterContrast(0.15); $this->applyBaseFilterBrightness(0.1); } - protected function applyFilterBrooklyn() + protected function applyFilterBrooklyn(): void { $this->applyBaseFilterColorFilter(25, 240, 252, 0.05); $this->applyBaseFilterSepia(0.3); } - protected function applyFilterHelena() + protected function applyFilterHelena(): void { $this->applyBaseFilterColorFilter(208, 208, 86, 0.2); $this->applyBaseFilterContrast(0.15); } - protected function applyFilterAshby() + protected function applyFilterAshby(): void { $this->applyBaseFilterColorFilter(255, 160, 25, 0.1); $this->applyBaseFilterBrightness(0.1); } - protected function applyFilterCharmes() + protected function applyFilterCharmes(): void { $this->applyBaseFilterColorFilter(255, 50, 80, 0.12); $this->applyBaseFilterContrast(0.05); } - protected function applyBaseFilterBrightness(float $value) + protected function applyBaseFilterBrightness(float $value): void { // https://github.com/scaleflex/filerobot-image-editor/blob/7113bf4968d97f41381f4a2965a59defd44562c8/packages/react-filerobot-image-editor/src/custom/filters/BaseFilters.js#L2 $this->applyBrighten($value); } - protected function applyBaseFilterContrast(float $value) + protected function applyBaseFilterContrast(float $value): void { // https://github.com/scaleflex/filerobot-image-editor/blob/7113bf4968d97f41381f4a2965a59defd44562c8/packages/react-filerobot-image-editor/src/custom/filters/BaseFilters.js#L14 $value *= 255; @@ -606,16 +606,17 @@ class FileRobotMagick $this->image->functionImage(\Imagick::FUNCTION_POLYNOMIAL, [$m, $c], \Imagick::CHANNEL_ALL); } - protected function applyBaseFilterSaturation(float $value) + protected function applyBaseFilterSaturation(float $value): void { // https://github.com/scaleflex/filerobot-image-editor/blob/7113bf4968d97f41381f4a2965a59defd44562c8/packages/react-filerobot-image-editor/src/custom/filters/BaseFilters.js#L24 $this->applyHSV(0, $value, 0); // lazy } - protected function applyBaseFilterGrayscale() + protected function applyBaseFilterGrayscale(): void { // https://github.com/scaleflex/filerobot-image-editor/blob/7113bf4968d97f41381f4a2965a59defd44562c8/packages/react-filerobot-image-editor/src/custom/filters/BaseFilters.js#L38 // y = 0.2126 * r + 0.7152 * g + 0.0722 * b; + /** @psalm-suppress InvalidArgument */ $this->image->colorMatrixImage([ 0.2126, 0.7152, 0.0722, 0, 0, 0.2126, 0.7152, 0.0722, 0, 0, @@ -625,9 +626,10 @@ class FileRobotMagick ]); } - protected function applyBaseFilterSepia(float $value) + protected function applyBaseFilterSepia(float $value): void { // https://github.com/scaleflex/filerobot-image-editor/blob/7113bf4968d97f41381f4a2965a59defd44562c8/packages/react-filerobot-image-editor/src/custom/filters/BaseFilters.js#L46 + /** @psalm-suppress InvalidArgument */ $this->image->colorMatrixImage([ 1 - 0.607 * $value, 0.769 * $value, 0.189 * $value, 0, 0, 0.349 * $value, 1 - 0.314 * $value, 0.168 * $value, 0, 0, @@ -637,9 +639,10 @@ class FileRobotMagick ]); } - protected function applyBaseFilterAdjustRGB(float $r, float $g, float $b) + protected function applyBaseFilterAdjustRGB(float $r, float $g, float $b): void { // https://github.com/scaleflex/filerobot-image-editor/blob/7113bf4968d97f41381f4a2965a59defd44562c8/packages/react-filerobot-image-editor/src/custom/filters/BaseFilters.js#L57 + /** @psalm-suppress InvalidArgument */ $this->image->colorMatrixImage([ $r, 0, 0, 0, 0, 0, $g, 0, 0, 0, @@ -649,7 +652,7 @@ class FileRobotMagick ]); } - protected function applyBaseFilterColorFilter(float $r, float $g, float $b, float $v) + protected function applyBaseFilterColorFilter(float $r, float $g, float $b, float $v): void { // https://github.com/scaleflex/filerobot-image-editor/blob/7113bf4968d97f41381f4a2965a59defd44562c8/packages/react-filerobot-image-editor/src/custom/filters/BaseFilters.js#L63 // y = x - (x - k) * v = (1 - v) * x + k * v diff --git a/lib/Service/Index.php b/lib/Service/Index.php index 9d5123f0..da095fd5 100644 --- a/lib/Service/Index.php +++ b/lib/Service/Index.php @@ -153,7 +153,7 @@ class Index ; // Filter out files that are already indexed - $addFilter = static function (string $table, string $alias) use (&$query) { + $addFilter = static function (string $table, string $alias) use (&$query): void { $query->leftJoin('f', $table, $alias, $query->expr()->andX( $query->expr()->eq('f.fileid', "{$alias}.fileid"), $query->expr()->eq('f.mtime', "{$alias}.mtime"), @@ -224,7 +224,7 @@ class Index /** * Get total number of files that are indexed. */ - public function getIndexedCount() + public function getIndexedCount(): int { $query = $this->db->getQueryBuilder(); $query->select($query->createFunction('COUNT(DISTINCT fileid)')) @@ -282,8 +282,10 @@ class Index return \in_array($file->getMimeType(), Application::VIDEO_MIMES, true); } - /** Log to console if CLI or logger */ - private function error(string $message) + /** + * Log error to console if CLI or logger. + */ + private function error(string $message): void { $this->logger->error($message); @@ -292,8 +294,10 @@ class Index } } - /** Log to console if CLI */ - private function log(string $message, bool $overwrite = false) + /** + * Log to console if CLI. + */ + private function log(string $message, bool $overwrite = false): void { if ($this->output) { if ($overwrite) { diff --git a/lib/Service/Places.php b/lib/Service/Places.php index 4ed5cfdc..41462ffd 100644 --- a/lib/Service/Places.php +++ b/lib/Service/Places.php @@ -34,8 +34,10 @@ class Places /** * Make SQL query to detect GIS type. + * + * @psalm-return 0|1|2 */ - public function detectGisType() + public function detectGisType(): int { // Make sure database prefix is set $prefix = $this->config->getSystemValue('dbtableprefix', '') ?: ''; @@ -83,7 +85,7 @@ class Places public function geomCount(): int { try { - return $this->connection->executeQuery('SELECT COUNT(osm_id) as c FROM memories_planet_geometry')->fetchOne(); + return (int) $this->connection->executeQuery('SELECT COUNT(osm_id) as c FROM memories_planet_geometry')->fetchOne(); } catch (\Exception $e) { return 0; } @@ -244,7 +246,7 @@ class Places $txnCount = 0; // Function to commit the current transaction - $transact = function () use (&$txnCount) { + $transact = function () use (&$txnCount): void { if (++$txnCount >= DB_TRANSACTION_SIZE) { $this->connection->commit(); $this->connection->beginTransaction(); @@ -379,7 +381,7 @@ class Places /** * Recalculate all places for all users. */ - public function recalculateAll() + public function recalculateAll(): void { echo "Recalculating places for all files (do not interrupt this process)...\n"; flush(); diff --git a/lib/Util.php b/lib/Util.php index 62650bb7..a9934d05 100644 --- a/lib/Util.php +++ b/lib/Util.php @@ -12,7 +12,6 @@ use OCP\App\IAppManager; use OCP\Files\Node; use OCP\Files\Search\ISearchBinaryOperator; use OCP\Files\Search\ISearchComparison; -use OCP\IAppConfig; use OCP\IConfig; class Util @@ -23,8 +22,10 @@ class Util /** * Get host CPU architecture (amd64 or aarch64). + * + * @psalm-return 'aarch64'|'amd64'|null */ - public static function getArch() + public static function getArch(): ?string { $uname = php_uname('m'); if (false !== stripos($uname, 'aarch64') || false !== stripos($uname, 'arm64')) { @@ -39,8 +40,10 @@ class Util /** * Get the libc type for host (glibc or musl). + * + * @psalm-return 'glibc'|'musl'|null */ - public static function getLibc() + public static function getLibc(): ?string { if ($ldd = shell_exec('ldd --version 2>&1')) { if (false !== stripos($ldd, 'musl')) { @@ -89,8 +92,8 @@ class Util return false; } - $config = \OC::$server->get(IAppConfig::class); - if ('true' !== $config->getValue('recognize', 'faces.enabled', 'false')) { + $config = \OC::$server->get(IConfig::class); + if ('true' !== $config->getAppValue('recognize', 'faces.enabled', 'false')) { return false; } @@ -192,10 +195,12 @@ class Util * @param mixed $key Key to set * @param mixed $value Value to set */ - public static function forceFileInfo(Node &$node, $key, $value) + public static function forceFileInfo(Node &$node, $key, $value): void { /** @var \OC\Files\Node\Node */ $node = $node; + + /** @psalm-suppress UndefinedInterfaceMethod */ $node->getFileInfo()[$key] = $value; } @@ -205,7 +210,7 @@ class Util * @param mixed $node File to patch * @param mixed $permissions Permissions to set */ - public static function forcePermissions(Node &$node, int $permissions) + public static function forcePermissions(Node &$node, int $permissions): void { self::forceFileInfo($node, 'permissions', $permissions); } @@ -284,7 +289,7 @@ class Util * * @param $folder Folder to search */ - public static function getAnyMedia(\OCP\Files\Folder $folder): Node + public static function getAnyMedia(\OCP\Files\Folder $folder): ?Node { $query = new SearchQuery(new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, [ new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', 'image/%'), @@ -338,11 +343,11 @@ class Util /** * Sanitize a path to keep only ASCII characters and special characters. */ - public static function sanitizePath(string $path): string + public static function sanitizePath(string $path): ?string { $path = str_replace("\0", '', $path); // remove null characters - return mb_ereg_replace('\/\/+', '/', $path); // remove extra slashes + return mb_ereg_replace('\/\/+', '/', $path) ?: null; // remove extra slashes } /** diff --git a/lib/UtilController.php b/lib/UtilController.php index eb1b5b15..da4e2838 100644 --- a/lib/UtilController.php +++ b/lib/UtilController.php @@ -32,6 +32,7 @@ trait UtilController */ public static function guardExDirect(\Closure $closure): Http\Response { + /** @psalm-suppress MissingTemplateParam */ return new class($closure) extends Http\Response implements Http\ICallbackResponse { private \Closure $_closure; diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 00000000..b166e2bf --- /dev/null +++ b/psalm.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file