refactor: add psalm

Signed-off-by: Varun Patil <radialapps@gmail.com>
pull/877/head
Varun Patil 2023-10-14 01:25:50 -07:00
parent ba959d2c43
commit 71ef41f763
47 changed files with 315 additions and 207 deletions

View File

@ -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)
;

View File

@ -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:

View File

@ -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": {}
}

View File

@ -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
}

View File

@ -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);
}

View File

@ -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();

View File

@ -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];
}
}

View File

@ -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);
}

View File

@ -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)) {

View File

@ -296,7 +296,12 @@ class MigrateGoogleTakeout extends Command
++$this->nProcessed;
}
protected function takeoutToExiftoolJson(array $json)
/**
* @return (float|mixed|string)[]
*
* @psalm-return array<string, float|mixed|string>
*/
protected function takeoutToExiftoolJson(array $json): array
{
// Helper to get a value from nested JSON
$get = static function (string $source) use ($json) {

View File

@ -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');

View File

@ -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');
}

View File

@ -282,7 +282,7 @@ class DownloadController extends GenericApiController
/** @var bool|resource */
$handle = false;
/** @var ?File */
/** @var ?\OCP\Files\File */
$file = null;
/** @var ?string */

View File

@ -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');

View File

@ -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);
};

View File

@ -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(),

View File

@ -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'];

View File

@ -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);

View File

@ -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

View File

@ -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);
}
/**

View File

@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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']

View File

@ -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();

View File

@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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')

View File

@ -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();

View File

@ -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');

View File

@ -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;

View File

@ -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) {

View File

@ -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!

View File

@ -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;

View File

@ -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) {

View File

@ -26,6 +26,9 @@ use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\ISession;
/**
* @template-implements IEventListener<Event>
*/
class BeforeTemplateListener implements IEventListener
{
private ISession $session;

View File

@ -27,6 +27,9 @@ use OCP\EventDispatcher\IEventListener;
use OCP\Files\Events\Node\NodeDeletedEvent;
use OCP\Files\Folder;
/**
* @template-implements IEventListener<Event>
*/
class PostDeleteListener implements IEventListener
{
private TimelineWrite $util;

View File

@ -25,6 +25,9 @@ use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\ISession;
/**
* @template-implements IEventListener<Event>
*/
class PostLogoutListener implements IEventListener
{
public const CLEAR_CACHE_KEY = 'memories_clear_cache';

View File

@ -29,6 +29,9 @@ use OCP\Files\Events\Node\NodeTouchedEvent;
use OCP\Files\Events\Node\NodeWrittenEvent;
use Psr\Log\LoggerInterface;
/**
* @template-implements IEventListener<Event>
*/
class PostWriteListener implements IEventListener
{
private TimelineWrite $timelineWrite;

View File

@ -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;
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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();

View File

@ -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
}
/**

View File

@ -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;

38
psalm.xml 100644
View File

@ -0,0 +1,38 @@
<?xml version="1.0"?>
<psalm
totallyTyped="true"
errorLevel="5"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="lib" />
<ignoreFiles>
<directory name="tools" />
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<extraFiles>
<directory name="../../lib" />
<directory name="../../3rdparty/doctrine" />
<directory name="../../3rdparty/psr" />
<directory name="../../3rdparty/guzzlehttp" />
<directory name="../../3rdparty/doctrine" />
<directory name="../../apps/files/lib/Event" />
</extraFiles>
<issueHandlers>
<UndefinedDocblockClass>
<errorLevel type="suppress">
<referencedClass name="Doctrine\DBAL\Schema\Schema" />
<referencedClass name="Doctrine\DBAL\Schema\SchemaException" />
<referencedClass name="Doctrine\DBAL\Driver\Statement" />
<referencedClass name="Doctrine\DBAL\Schema\Table" />
<referencedClass name="Doctrine\DBAL\Platforms\AbstractPlatform" />
</errorLevel>
</UndefinedDocblockClass>
</issueHandlers>
</psalm>