refactor: PHP 8 syntax migration
Signed-off-by: Varun Patil <radialapps@gmail.com>pull/877/head
parent
73624ce5f2
commit
bd2101e7bb
|
@ -26,7 +26,7 @@ $config
|
||||||
'@PhpCsFixer' => true,
|
'@PhpCsFixer' => true,
|
||||||
'@PhpCsFixer:risky' => true,
|
'@PhpCsFixer:risky' => true,
|
||||||
'general_phpdoc_annotation_remove' => ['annotations' => ['expectedDeprecation']], // one should use PHPUnit built-in method instead
|
'general_phpdoc_annotation_remove' => ['annotations' => ['expectedDeprecation']], // one should use PHPUnit built-in method instead
|
||||||
'phpdoc_to_comment' => ['ignored_tags' => ['psalm-suppress', 'template-implements']],
|
'phpdoc_to_comment' => ['ignored_tags' => ['psalm-suppress', 'template-implements', 'var']],
|
||||||
'trailing_comma_in_multiline' => ['elements' => ['arrays', 'parameters', 'arguments']],
|
'trailing_comma_in_multiline' => ['elements' => ['arrays', 'parameters', 'arguments']],
|
||||||
'modernize_strpos' => true,
|
'modernize_strpos' => true,
|
||||||
'no_alias_functions' => true,
|
'no_alias_functions' => true,
|
||||||
|
|
|
@ -27,6 +27,7 @@ use OCA\Memories\Db\AlbumsQuery;
|
||||||
use OCA\Memories\Db\TimelineQuery;
|
use OCA\Memories\Db\TimelineQuery;
|
||||||
use OCA\Memories\Exceptions;
|
use OCA\Memories\Exceptions;
|
||||||
use OCA\Memories\Util;
|
use OCA\Memories\Util;
|
||||||
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
|
|
||||||
class AlbumsBackend extends Backend
|
class AlbumsBackend extends Backend
|
||||||
|
@ -57,7 +58,7 @@ class AlbumsBackend extends Backend
|
||||||
return explode('/', $name)[1];
|
return explode('/', $name)[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function transformDayQuery(&$query, bool $aggregate): void
|
public function transformDayQuery(IQueryBuilder &$query, bool $aggregate): void
|
||||||
{
|
{
|
||||||
$albumId = (string) $this->request->getParam(self::clusterType());
|
$albumId = (string) $this->request->getParam(self::clusterType());
|
||||||
|
|
||||||
|
@ -102,7 +103,7 @@ class AlbumsBackend extends Backend
|
||||||
|
|
||||||
// Add display names for users
|
// Add display names for users
|
||||||
$userManager = \OC::$server->get(\OCP\IUserManager::class);
|
$userManager = \OC::$server->get(\OCP\IUserManager::class);
|
||||||
array_walk($list, static function (&$item) use ($userManager) {
|
array_walk($list, static function (array &$item) use ($userManager) {
|
||||||
$user = $userManager->get($item['user']);
|
$user = $userManager->get($item['user']);
|
||||||
$item['user_display'] = $user ? $user->getDisplayName() : null;
|
$item['user_display'] = $user ? $user->getDisplayName() : null;
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||||
namespace OCA\Memories\ClustersBackend;
|
namespace OCA\Memories\ClustersBackend;
|
||||||
|
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
use OCP\Files\SimpleFS\ISimpleFile;
|
||||||
|
|
||||||
abstract class Backend
|
abstract class Backend
|
||||||
{
|
{
|
||||||
|
@ -49,7 +50,7 @@ abstract class Backend
|
||||||
* @param IQueryBuilder $query Query builder
|
* @param IQueryBuilder $query Query builder
|
||||||
* @param bool $aggregate Whether this is an aggregate query
|
* @param bool $aggregate Whether this is an aggregate query
|
||||||
*/
|
*/
|
||||||
abstract public function transformDayQuery(&$query, bool $aggregate): void;
|
abstract public function transformDayQuery(IQueryBuilder &$query, bool $aggregate): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply post-query transformations for the given photo object.
|
* Apply post-query transformations for the given photo object.
|
||||||
|
@ -110,12 +111,12 @@ abstract class Backend
|
||||||
/**
|
/**
|
||||||
* Perform any post processing and get the blob from the preview file.
|
* Perform any post processing and get the blob from the preview file.
|
||||||
*
|
*
|
||||||
* @param \OCP\Files\SimpleFS\ISimpleFile $file Preview file
|
* @param ISimpleFile $file Preview file
|
||||||
* @param array $photo Photo object
|
* @param array $photo Photo object
|
||||||
*
|
*
|
||||||
* @return array [Blob, mimetype] of data
|
* @return array [Blob, mimetype] of data
|
||||||
*/
|
*/
|
||||||
public function getPreviewBlob($file, $photo): array
|
public function getPreviewBlob(ISimpleFile $file, array $photo): array
|
||||||
{
|
{
|
||||||
return [$file->getContent(), $file->getMimeType()];
|
return [$file->getContent(), $file->getMimeType()];
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ namespace OCA\Memories\ClustersBackend;
|
||||||
|
|
||||||
use OCA\Memories\Db\TimelineQuery;
|
use OCA\Memories\Db\TimelineQuery;
|
||||||
use OCA\Memories\Util;
|
use OCA\Memories\Util;
|
||||||
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
use OCP\Files\SimpleFS\ISimpleFile;
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
|
|
||||||
|
@ -54,7 +56,7 @@ class FaceRecognitionBackend extends Backend
|
||||||
&& Util::facerecognitionIsEnabled();
|
&& Util::facerecognitionIsEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function transformDayQuery(&$query, bool $aggregate): void
|
public function transformDayQuery(IQueryBuilder &$query, bool $aggregate): void
|
||||||
{
|
{
|
||||||
$personStr = (string) $this->request->getParam('facerecognition');
|
$personStr = (string) $this->request->getParam('facerecognition');
|
||||||
|
|
||||||
|
@ -196,7 +198,7 @@ class FaceRecognitionBackend extends Backend
|
||||||
$this->sortByScores($photos);
|
$this->sortByScores($photos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPreviewBlob($file, $photo): array
|
public function getPreviewBlob(ISimpleFile $file, array $photo): array
|
||||||
{
|
{
|
||||||
return $this->cropFace($file, $photo, 1.8);
|
return $this->cropFace($file, $photo, 1.8);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace OCA\Memories\ClustersBackend;
|
namespace OCA\Memories\ClustersBackend;
|
||||||
|
|
||||||
|
use OCP\Files\SimpleFS\ISimpleFile;
|
||||||
|
|
||||||
trait PeopleBackendUtils
|
trait PeopleBackendUtils
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -83,7 +85,7 @@ trait PeopleBackendUtils
|
||||||
* - width: width of the face in the image (percentage)
|
* - width: width of the face in the image (percentage)
|
||||||
* - height: height of the face in the image (percentage)
|
* - height: height of the face in the image (percentage)
|
||||||
*
|
*
|
||||||
* @param \OCP\Files\SimpleFS\ISimpleFile $file Actual file containing the image
|
* @param ISimpleFile $file Actual file containing the image
|
||||||
* @param array $photo The face object
|
* @param array $photo The face object
|
||||||
* @param float $padding The padding to add around the face
|
* @param float $padding The padding to add around the face
|
||||||
*
|
*
|
||||||
|
@ -93,7 +95,7 @@ trait PeopleBackendUtils
|
||||||
*
|
*
|
||||||
* @psalm-return list{string, string}
|
* @psalm-return list{string, string}
|
||||||
*/
|
*/
|
||||||
private function cropFace($file, array $photo, float $padding): array
|
private function cropFace(ISimpleFile $file, array $photo, float $padding): array
|
||||||
{
|
{
|
||||||
$img = new \OCP\Image();
|
$img = new \OCP\Image();
|
||||||
$img->loadFromData($file->getContent());
|
$img->loadFromData($file->getContent());
|
||||||
|
@ -124,11 +126,8 @@ trait PeopleBackendUtils
|
||||||
$img->scaleDownToFit(512, 512);
|
$img->scaleDownToFit(512, 512);
|
||||||
|
|
||||||
// Get blob and mimetype
|
// Get blob and mimetype
|
||||||
$data = $img->data();
|
$data = $img->data() ?: throw new \Exception('Could not get image data');
|
||||||
$mime = $img->mimeType();
|
$mime = $img->mimeType() ?: throw new \Exception('Could not get image mimetype');
|
||||||
if (null === $data || null === $mime) {
|
|
||||||
throw new \Exception('Could not get image data');
|
|
||||||
}
|
|
||||||
|
|
||||||
return [$data, $mime];
|
return [$data, $mime];
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ namespace OCA\Memories\ClustersBackend;
|
||||||
|
|
||||||
use OCA\Memories\Db\TimelineQuery;
|
use OCA\Memories\Db\TimelineQuery;
|
||||||
use OCA\Memories\Util;
|
use OCA\Memories\Util;
|
||||||
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
|
|
||||||
class PlacesBackend extends Backend
|
class PlacesBackend extends Backend
|
||||||
|
@ -49,7 +50,7 @@ class PlacesBackend extends Backend
|
||||||
return Util::placesGISType() > 0;
|
return Util::placesGISType() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function transformDayQuery(&$query, bool $aggregate): void
|
public function transformDayQuery(IQueryBuilder &$query, bool $aggregate): void
|
||||||
{
|
{
|
||||||
$locationId = (int) $this->request->getParam('places');
|
$locationId = (int) $this->request->getParam('places');
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,8 @@ namespace OCA\Memories\ClustersBackend;
|
||||||
|
|
||||||
use OCA\Memories\Db\TimelineQuery;
|
use OCA\Memories\Db\TimelineQuery;
|
||||||
use OCA\Memories\Util;
|
use OCA\Memories\Util;
|
||||||
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
use OCP\Files\SimpleFS\ISimpleFile;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
|
|
||||||
class RecognizeBackend extends Backend
|
class RecognizeBackend extends Backend
|
||||||
|
@ -51,7 +53,7 @@ class RecognizeBackend extends Backend
|
||||||
return Util::recognizeIsEnabled();
|
return Util::recognizeIsEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function transformDayQuery(&$query, bool $aggregate): void
|
public function transformDayQuery(IQueryBuilder &$query, bool $aggregate): void
|
||||||
{
|
{
|
||||||
// Check if Recognize is enabled
|
// Check if Recognize is enabled
|
||||||
if (!$this->isEnabled()) {
|
if (!$this->isEnabled()) {
|
||||||
|
@ -232,7 +234,7 @@ class RecognizeBackend extends Backend
|
||||||
$this->sortByScores($photos);
|
$this->sortByScores($photos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPreviewBlob($file, $photo): array
|
public function getPreviewBlob(ISimpleFile $file, array $photo): array
|
||||||
{
|
{
|
||||||
return $this->cropFace($file, $photo, 1.5);
|
return $this->cropFace($file, $photo, 1.5);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ class TagsBackend extends Backend
|
||||||
return Util::tagsIsEnabled();
|
return Util::tagsIsEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function transformDayQuery(&$query, bool $aggregate): void
|
public function transformDayQuery(IQueryBuilder &$query, bool $aggregate): void
|
||||||
{
|
{
|
||||||
$tagName = (string) $this->request->getParam('tags');
|
$tagName = (string) $this->request->getParam('tags');
|
||||||
|
|
||||||
|
|
|
@ -57,11 +57,13 @@ class IndexOpts
|
||||||
|
|
||||||
class Index extends Command
|
class Index extends Command
|
||||||
{
|
{
|
||||||
// IO
|
/** @psalm-suppress PropertyNotSetInConstructor */
|
||||||
private InputInterface $input;
|
private InputInterface $input;
|
||||||
|
|
||||||
|
/** @psalm-suppress PropertyNotSetInConstructor */
|
||||||
private OutputInterface $output;
|
private OutputInterface $output;
|
||||||
|
|
||||||
// Command options
|
/** @psalm-suppress PropertyNotSetInConstructor */
|
||||||
private IndexOpts $opts;
|
private IndexOpts $opts;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
@ -184,9 +186,9 @@ class Index extends Command
|
||||||
/**
|
/**
|
||||||
* Run function for all users (or selected user if set).
|
* Run function for all users (or selected user if set).
|
||||||
*
|
*
|
||||||
* @param mixed $closure
|
* @param \Closure(IUser $user): void $closure
|
||||||
*/
|
*/
|
||||||
private function runForUsers($closure): void
|
private function runForUsers(\Closure $closure): void
|
||||||
{
|
{
|
||||||
if ($uid = $this->opts->user) {
|
if ($uid = $this->opts->user) {
|
||||||
if ($user = $this->userManager->get($uid)) {
|
if ($user = $this->userManager->get($uid)) {
|
||||||
|
@ -203,7 +205,7 @@ class Index extends Command
|
||||||
$this->output->writeln("<error>Group {$gid} not found</error>\n");
|
$this->output->writeln("<error>Group {$gid} not found</error>\n");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->userManager->callForSeenUsers(static fn (IUser $user) => $closure($user));
|
$this->userManager->callForSeenUsers($closure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,10 @@ class MigrateGoogleTakeout extends Command
|
||||||
protected const MIGRATOR_VERSION = 1;
|
protected const MIGRATOR_VERSION = 1;
|
||||||
protected const MIGRATED_KEY = 'memoriesMigratorVersion';
|
protected const MIGRATED_KEY = 'memoriesMigratorVersion';
|
||||||
|
|
||||||
|
/** @psalm-suppress PropertyNotSetInConstructor */
|
||||||
protected OutputInterface $output;
|
protected OutputInterface $output;
|
||||||
|
|
||||||
|
/** @psalm-suppress PropertyNotSetInConstructor */
|
||||||
protected InputInterface $input;
|
protected InputInterface $input;
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
|
@ -124,18 +127,22 @@ class MigrateGoogleTakeout extends Command
|
||||||
|
|
||||||
protected function migrateUser(IUser $user): void
|
protected function migrateUser(IUser $user): void
|
||||||
{
|
{
|
||||||
$this->output->writeln("Migrating user {$user->getUID()}");
|
$uid = $user->getUID();
|
||||||
|
$this->output->writeln("Migrating user {$uid}");
|
||||||
|
|
||||||
// Get user's root folder
|
// Get user's root folder
|
||||||
\OC_Util::tearDownFS();
|
\OC_Util::tearDownFS();
|
||||||
\OC_Util::setupFS($user->getUID());
|
\OC_Util::setupFS($uid);
|
||||||
$folder = $this->rootFolder->getUserFolder($user->getUID());
|
$folder = $this->rootFolder->getUserFolder($uid);
|
||||||
|
|
||||||
// Check if we need to migrate a specific folder
|
// Check if we need to migrate a specific folder
|
||||||
if ($path = $this->input->getOption('folder')) {
|
if ($path = $this->input->getOption('folder')) {
|
||||||
try {
|
try {
|
||||||
$folder = $folder->get($path);
|
$folder = $folder->get($path);
|
||||||
} catch (\Exception $e) {
|
if (!$folder instanceof Folder) {
|
||||||
|
throw new \Exception();
|
||||||
|
}
|
||||||
|
} catch (\Exception) {
|
||||||
$this->output->writeln("<error>Folder {$path} does not exist</error>");
|
$this->output->writeln("<error>Folder {$path} does not exist</error>");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -282,15 +289,10 @@ class MigrateGoogleTakeout extends Command
|
||||||
++$this->nProcessed;
|
++$this->nProcessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return (float|mixed|string)[]
|
|
||||||
*
|
|
||||||
* @psalm-return array<string, float|mixed|string>
|
|
||||||
*/
|
|
||||||
protected function takeoutToExiftoolJson(array $json): array
|
protected function takeoutToExiftoolJson(array $json): array
|
||||||
{
|
{
|
||||||
// Helper to get a value from nested JSON
|
// Helper to get a value from nested JSON
|
||||||
$get = static function (string $source) use ($json) {
|
$get = static function (string $source) use ($json): mixed {
|
||||||
$keys = array_reverse(explode('.', $source));
|
$keys = array_reverse(explode('.', $source));
|
||||||
while (\count($keys) > 0) {
|
while (\count($keys) > 0) {
|
||||||
$key = array_pop($keys);
|
$key = array_pop($keys);
|
||||||
|
@ -336,8 +338,6 @@ class MigrateGoogleTakeout extends Command
|
||||||
$txf['GPSAltitude'] = $get('geoData.altitude');
|
$txf['GPSAltitude'] = $get('geoData.altitude');
|
||||||
|
|
||||||
// Remove all null values
|
// Remove all null values
|
||||||
return array_filter($txf, static function ($value) {
|
return array_filter($txf, static fn (mixed $value) => null !== $value);
|
||||||
return null !== $value;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
class PlacesSetup extends Command
|
class PlacesSetup extends Command
|
||||||
{
|
{
|
||||||
|
/** @psalm-suppress PropertyNotSetInConstructor */
|
||||||
protected OutputInterface $output;
|
protected OutputInterface $output;
|
||||||
|
|
||||||
public function __construct(protected Places $places)
|
public function __construct(protected Places $places)
|
||||||
|
|
|
@ -49,10 +49,8 @@ class AdminController extends GenericApiController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @AdminRequired
|
* @AdminRequired
|
||||||
*
|
|
||||||
* @param mixed $value
|
|
||||||
*/
|
*/
|
||||||
public function setSystemConfig(string $key, $value): Http\Response
|
public function setSystemConfig(string $key, mixed $value): Http\Response
|
||||||
{
|
{
|
||||||
return Util::guardEx(function () use ($key, $value) {
|
return Util::guardEx(function () use ($key, $value) {
|
||||||
// Make sure not running in read-only mode
|
// Make sure not running in read-only mode
|
||||||
|
@ -94,7 +92,7 @@ class AdminController extends GenericApiController
|
||||||
$exiftoolNoLocal = Util::getSystemConfig('memories.exiftool_no_local');
|
$exiftoolNoLocal = Util::getSystemConfig('memories.exiftool_no_local');
|
||||||
$status['exiftool'] = $this->getExecutableStatus(
|
$status['exiftool'] = $this->getExecutableStatus(
|
||||||
static fn () => BinExt::getExiftoolPBin(),
|
static fn () => BinExt::getExiftoolPBin(),
|
||||||
static fn ($p) => BinExt::testExiftool(),
|
static fn () => BinExt::testExiftool(),
|
||||||
!$exiftoolNoLocal,
|
!$exiftoolNoLocal,
|
||||||
!$exiftoolNoLocal,
|
!$exiftoolNoLocal,
|
||||||
);
|
);
|
||||||
|
@ -103,7 +101,7 @@ class AdminController extends GenericApiController
|
||||||
/** @psalm-suppress ForbiddenCode */
|
/** @psalm-suppress ForbiddenCode */
|
||||||
$status['perl'] = $this->getExecutableStatus(
|
$status['perl'] = $this->getExecutableStatus(
|
||||||
trim(shell_exec('which perl') ?: '/bin/perl'),
|
trim(shell_exec('which perl') ?: '/bin/perl'),
|
||||||
static fn ($p) => BinExt::testSystemPerl($p),
|
static fn (string $p) => BinExt::testSystemPerl($p),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check number of indexed files
|
// Check number of indexed files
|
||||||
|
@ -220,14 +218,14 @@ class AdminController extends GenericApiController
|
||||||
/**
|
/**
|
||||||
* Get the status of an executable.
|
* Get the status of an executable.
|
||||||
*
|
*
|
||||||
* @param \Closure|string $path Path to the executable
|
* @param (\Closure():string)|string $path Path to the executable
|
||||||
* @param ?\Closure $testFunction Function to test the executable
|
* @param null|(\Closure(string):string) $testFunction Function to test the executable
|
||||||
* @param bool $testIfFile Test if the path is a file
|
* @param bool $testIfFile Test if the path is a file
|
||||||
* @param bool $testIfExecutable Test if the path is executable
|
* @param bool $testIfExecutable Test if the path is executable
|
||||||
*/
|
*/
|
||||||
private function getExecutableStatus(
|
private function getExecutableStatus(
|
||||||
$path,
|
\Closure|string $path,
|
||||||
?\Closure $testFunction = null,
|
null|\Closure $testFunction = null,
|
||||||
bool $testIfFile = true,
|
bool $testIfFile = true,
|
||||||
bool $testIfExecutable = true,
|
bool $testIfExecutable = true,
|
||||||
): string {
|
): string {
|
||||||
|
@ -239,10 +237,6 @@ class AdminController extends GenericApiController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!\is_string($path)) {
|
|
||||||
return 'not_found';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($testIfFile && !is_file($path)) {
|
if ($testIfFile && !is_file($path)) {
|
||||||
return 'not_found';
|
return 'not_found';
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ class ArchiveController extends GenericApiController
|
||||||
*
|
*
|
||||||
* Move one file to the archive folder
|
* Move one file to the archive folder
|
||||||
*
|
*
|
||||||
* @param string fileid
|
* @param string $id File ID to archive / unarchive
|
||||||
*/
|
*/
|
||||||
public function archive(string $id): Http\Response
|
public function archive(string $id): Http\Response
|
||||||
{
|
{
|
||||||
|
@ -73,6 +73,7 @@ class ArchiveController extends GenericApiController
|
||||||
$parent = $file->getParent();
|
$parent = $file->getParent();
|
||||||
$isArchived = false;
|
$isArchived = false;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
/** @psalm-suppress DocblockTypeContradiction */
|
||||||
if (null === $parent) {
|
if (null === $parent) {
|
||||||
throw new \Exception('Cannot get correct parent of file');
|
throw new \Exception('Cannot get correct parent of file');
|
||||||
}
|
}
|
||||||
|
@ -108,6 +109,9 @@ class ArchiveController extends GenericApiController
|
||||||
|
|
||||||
// Get path of current file relative to the parent folder
|
// Get path of current file relative to the parent folder
|
||||||
$relativeFilePath = $parent->getRelativePath($file->getPath());
|
$relativeFilePath = $parent->getRelativePath($file->getPath());
|
||||||
|
if (!$relativeFilePath) {
|
||||||
|
throw new \Exception('Cannot get relative path of file');
|
||||||
|
}
|
||||||
|
|
||||||
// Check if we want to archive or unarchive
|
// Check if we want to archive or unarchive
|
||||||
$body = $this->request->getParams();
|
$body = $this->request->getParams();
|
||||||
|
|
|
@ -32,7 +32,11 @@ use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
|
||||||
class ClustersController extends GenericApiController
|
class ClustersController extends GenericApiController
|
||||||
{
|
{
|
||||||
/** Current backend for this instance */
|
/**
|
||||||
|
* Current backend for this instance.
|
||||||
|
*
|
||||||
|
* @psalm-suppress PropertyNotSetInConstructor
|
||||||
|
*/
|
||||||
protected ClustersBackend\Backend $backend;
|
protected ClustersBackend\Backend $backend;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -42,17 +42,11 @@ class DownloadController extends GenericApiController
|
||||||
*
|
*
|
||||||
* Request to download one or more files
|
* Request to download one or more files
|
||||||
*
|
*
|
||||||
* @param mixed $files
|
* @param int[] $files List of file IDs
|
||||||
*/
|
*/
|
||||||
public function request($files): Http\Response
|
public function request(array $files): Http\Response
|
||||||
{
|
{
|
||||||
return Util::guardEx(static function () use ($files) {
|
return Util::guardEx(static function () use ($files) {
|
||||||
// Get ids from body
|
|
||||||
if (null === $files || !\is_array($files)) {
|
|
||||||
throw Exceptions::MissingParameter('files');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return id
|
|
||||||
$handle = self::createHandle('memories', $files);
|
$handle = self::createHandle('memories', $files);
|
||||||
|
|
||||||
return new JSONResponse(['handle' => $handle]);
|
return new JSONResponse(['handle' => $handle]);
|
||||||
|
@ -65,7 +59,7 @@ class DownloadController extends GenericApiController
|
||||||
* The calling controller must have the UseSession annotation.
|
* The calling controller must have the UseSession annotation.
|
||||||
*
|
*
|
||||||
* @param string $name Name of zip file
|
* @param string $name Name of zip file
|
||||||
* @param int[] $files
|
* @param int[] $files List of file IDs
|
||||||
*/
|
*/
|
||||||
public static function createHandle(string $name, array $files): string
|
public static function createHandle(string $name, array $files): string
|
||||||
{
|
{
|
||||||
|
@ -282,7 +276,7 @@ class DownloadController extends GenericApiController
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var bool|resource */
|
/** @var false|resource */
|
||||||
$handle = false;
|
$handle = false;
|
||||||
|
|
||||||
/** @var ?\OCP\Files\File */
|
/** @var ?\OCP\Files\File */
|
||||||
|
|
|
@ -21,7 +21,7 @@ class FoldersController extends GenericApiController
|
||||||
return Util::guardEx(function () use ($folder) {
|
return Util::guardEx(function () use ($folder) {
|
||||||
try {
|
try {
|
||||||
$node = Util::getUserFolder()->get($folder);
|
$node = Util::getUserFolder()->get($folder);
|
||||||
} catch (\OCP\Files\NotFoundException $e) {
|
} catch (\OCP\Files\NotFoundException) {
|
||||||
throw Exceptions::NotFound('Folder not found');
|
throw Exceptions::NotFound('Folder not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,10 +81,12 @@ class ImageController extends GenericApiController
|
||||||
return Util::guardExDirect(function (Http\IOutput $out) {
|
return Util::guardExDirect(function (Http\IOutput $out) {
|
||||||
// read body to array
|
// read body to array
|
||||||
$body = file_get_contents('php://input');
|
$body = file_get_contents('php://input');
|
||||||
|
|
||||||
|
/** @var array<array> */
|
||||||
$files = json_decode($body, true);
|
$files = json_decode($body, true);
|
||||||
|
|
||||||
// Filter files with valid parameters
|
// Filter files with valid parameters
|
||||||
$files = array_filter($files, static function ($file) {
|
$files = array_filter($files, static function (array $file) {
|
||||||
return isset($file['reqid'], $file['fileid'], $file['x'], $file['y'], $file['a'])
|
return isset($file['reqid'], $file['fileid'], $file['x'], $file['y'], $file['a'])
|
||||||
&& (int) $file['fileid'] > 0
|
&& (int) $file['fileid'] > 0
|
||||||
&& (int) $file['x'] > 0
|
&& (int) $file['x'] > 0
|
||||||
|
@ -92,14 +94,14 @@ class ImageController extends GenericApiController
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sort files by size, ascending
|
// Sort files by size, ascending
|
||||||
usort($files, static function ($a, $b) {
|
usort($files, static function (array $a, array $b) {
|
||||||
$aArea = (int) $a['x'] * (int) $a['y'];
|
$aArea = (int) $a['x'] * (int) $a['y'];
|
||||||
$bArea = (int) $b['x'] * (int) $b['y'];
|
$bArea = (int) $b['x'] * (int) $b['y'];
|
||||||
|
|
||||||
return $aArea <=> $bArea;
|
return $aArea <=> $bArea;
|
||||||
});
|
});
|
||||||
|
|
||||||
/** @var \OCP\IPreview $previewManager */
|
/** @var \OCP\IPreview */
|
||||||
$previewManager = \OC::$server->get(\OCP\IPreview::class);
|
$previewManager = \OC::$server->get(\OCP\IPreview::class);
|
||||||
|
|
||||||
// For checking max previews
|
// For checking max previews
|
||||||
|
@ -202,8 +204,7 @@ class ImageController extends GenericApiController
|
||||||
$info['basename'] = $file->getName();
|
$info['basename'] = $file->getName();
|
||||||
|
|
||||||
// Allow these ony for logged in users
|
// Allow these ony for logged in users
|
||||||
$user = $this->userSession->getUser();
|
if ($user = $this->userSession->getUser()) {
|
||||||
if (null !== $user) {
|
|
||||||
// Get the path of the file relative to current user
|
// Get the path of the file relative to current user
|
||||||
// "/admin/files/Photos/Camera/20230821_135017.jpg" => "/Photos/..."
|
// "/admin/files/Photos/Camera/20230821_135017.jpg" => "/Photos/..."
|
||||||
$parts = explode('/', $file->getPath());
|
$parts = explode('/', $file->getPath());
|
||||||
|
@ -242,9 +243,6 @@ class ImageController extends GenericApiController
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
*
|
*
|
||||||
* Set the exif data for a file.
|
* Set the exif data for a file.
|
||||||
*
|
|
||||||
* @param int fileid
|
|
||||||
* @param array raw exif data
|
|
||||||
*/
|
*/
|
||||||
public function setExif(int $id, array $raw): Http\Response
|
public function setExif(int $id, array $raw): Http\Response
|
||||||
{
|
{
|
||||||
|
@ -398,7 +396,9 @@ class ImageController extends GenericApiController
|
||||||
* @param string $blob Blob of image data in any format
|
* @param string $blob Blob of image data in any format
|
||||||
* @param string $mimetype Mimetype of image data
|
* @param string $mimetype Mimetype of image data
|
||||||
*
|
*
|
||||||
* @return array [blob, mimetype]
|
* @return string[] [blob, mimetype]
|
||||||
|
*
|
||||||
|
* @psalm-return list{string, string}
|
||||||
*/
|
*/
|
||||||
private function getImageJPEG($blob, $mimetype): array
|
private function getImageJPEG($blob, $mimetype): array
|
||||||
{
|
{
|
||||||
|
@ -435,6 +435,8 @@ class ImageController extends GenericApiController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the tags for a file.
|
* Get the tags for a file.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
*/
|
*/
|
||||||
private function getTags(int $fileId): array
|
private function getTags(int $fileId): array
|
||||||
{
|
{
|
||||||
|
@ -447,12 +449,10 @@ class ImageController extends GenericApiController
|
||||||
$objectMapper = \OC::$server->get(\OCP\SystemTag\ISystemTagObjectMapper::class);
|
$objectMapper = \OC::$server->get(\OCP\SystemTag\ISystemTagObjectMapper::class);
|
||||||
$tagIds = $objectMapper->getTagIdsForObjects([$fileId], 'files')[(string) $fileId];
|
$tagIds = $objectMapper->getTagIdsForObjects([$fileId], 'files')[(string) $fileId];
|
||||||
|
|
||||||
// Get the tag names and filter out the ones that are not user visible
|
// Get all matching tag objects
|
||||||
$tagManager = \OC::$server->get(\OCP\SystemTag\ISystemTagManager::class);
|
$tags = \OC::$server->get(\OCP\SystemTag\ISystemTagManager::class)->getTagsByIds($tagIds);
|
||||||
|
|
||||||
/** @var \OCP\SystemTag\ISystemTag[] */
|
|
||||||
$tags = $tagManager->getTagsByIds($tagIds);
|
|
||||||
|
|
||||||
|
// Filter out the tags that are not user visible
|
||||||
$visible = array_filter($tags, static fn ($t) => $t->isUserVisible());
|
$visible = array_filter($tags, static fn ($t) => $t->isUserVisible());
|
||||||
|
|
||||||
// Get the tag names
|
// Get the tag names
|
||||||
|
|
|
@ -40,7 +40,7 @@ class OtherController extends GenericApiController
|
||||||
* @param string key the identifier to change
|
* @param string key the identifier to change
|
||||||
* @param string value the value to set
|
* @param string value the value to set
|
||||||
*
|
*
|
||||||
* @return JSONResponse an empty JSONResponse with respective http status code
|
* @return Http\Response empty JSONResponse with respective http status code
|
||||||
*/
|
*/
|
||||||
public function setUserConfig(string $key, string $value): Http\Response
|
public function setUserConfig(string $key, string $value): Http\Response
|
||||||
{
|
{
|
||||||
|
@ -77,7 +77,7 @@ class OtherController extends GenericApiController
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function to get user config values
|
// helper function to get user config values
|
||||||
$getAppConfig = function ($key, $default) use ($uid) {
|
$getAppConfig = function (string $key, mixed $default) use ($uid): mixed {
|
||||||
return $this->config->getUserValue($uid, Application::APPNAME, $key, $default);
|
return $this->config->getUserValue($uid, Application::APPNAME, $key, $default);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ class OtherController extends GenericApiController
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function describeApi(): JSONResponse
|
public function describeApi(): Http\Response
|
||||||
{
|
{
|
||||||
return Util::guardEx(static function () {
|
return Util::guardEx(static function () {
|
||||||
$appManager = \OC::$server->get(\OCP\App\IAppManager::class);
|
$appManager = \OC::$server->get(\OCP\App\IAppManager::class);
|
||||||
|
@ -138,7 +138,7 @@ class OtherController extends GenericApiController
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$info['uid'] = Util::getUID();
|
$info['uid'] = Util::getUID();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception) {
|
||||||
$info['uid'] = null;
|
$info['uid'] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ use OCA\Memories\Service\BinExt;
|
||||||
use OCA\Memories\Util;
|
use OCA\Memories\Util;
|
||||||
use OCP\AppFramework\Controller;
|
use OCP\AppFramework\Controller;
|
||||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||||
|
use OCP\AppFramework\Http\Response;
|
||||||
use OCP\AppFramework\Http\Template\PublicTemplateResponse;
|
use OCP\AppFramework\Http\Template\PublicTemplateResponse;
|
||||||
use OCP\AppFramework\Http\TemplateResponse;
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
use OCP\EventDispatcher\IEventDispatcher;
|
use OCP\EventDispatcher\IEventDispatcher;
|
||||||
|
@ -29,7 +30,7 @@ class PageController extends Controller
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function main()
|
public function main(): Response
|
||||||
{
|
{
|
||||||
// Check native version if available
|
// Check native version if available
|
||||||
$nativeVer = Util::callerNativeVersion();
|
$nativeVer = Util::callerNativeVersion();
|
||||||
|
@ -59,11 +60,11 @@ class PageController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the common content security policy */
|
/** Get the common content security policy */
|
||||||
public static function getCSP()
|
public static function getCSP(): ContentSecurityPolicy
|
||||||
{
|
{
|
||||||
// Image domains MUST be added to the connect domain list
|
// Image domains MUST be added to the connect domain list
|
||||||
// because of the service worker fetch() call
|
// because of the service worker fetch() call
|
||||||
$addImageDomain = static function ($url) use (&$policy): void {
|
$addImageDomain = static function (string $url) use (&$policy): void {
|
||||||
$policy->addAllowedImageDomain($url);
|
$policy->addAllowedImageDomain($url);
|
||||||
$policy->addAllowedConnectDomain($url);
|
$policy->addAllowedConnectDomain($url);
|
||||||
};
|
};
|
||||||
|
@ -114,7 +115,7 @@ class PageController extends Controller
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function folder()
|
public function folder(): Response
|
||||||
{
|
{
|
||||||
return $this->main();
|
return $this->main();
|
||||||
}
|
}
|
||||||
|
@ -124,7 +125,7 @@ class PageController extends Controller
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function favorites()
|
public function favorites(): Response
|
||||||
{
|
{
|
||||||
return $this->main();
|
return $this->main();
|
||||||
}
|
}
|
||||||
|
@ -134,7 +135,7 @@ class PageController extends Controller
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function albums()
|
public function albums(): Response
|
||||||
{
|
{
|
||||||
return $this->main();
|
return $this->main();
|
||||||
}
|
}
|
||||||
|
@ -144,7 +145,7 @@ class PageController extends Controller
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function videos()
|
public function videos(): Response
|
||||||
{
|
{
|
||||||
return $this->main();
|
return $this->main();
|
||||||
}
|
}
|
||||||
|
@ -154,7 +155,7 @@ class PageController extends Controller
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function archive()
|
public function archive(): Response
|
||||||
{
|
{
|
||||||
return $this->main();
|
return $this->main();
|
||||||
}
|
}
|
||||||
|
@ -164,7 +165,7 @@ class PageController extends Controller
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function thisday()
|
public function thisday(): Response
|
||||||
{
|
{
|
||||||
return $this->main();
|
return $this->main();
|
||||||
}
|
}
|
||||||
|
@ -174,7 +175,7 @@ class PageController extends Controller
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function recognize()
|
public function recognize(): Response
|
||||||
{
|
{
|
||||||
return $this->main();
|
return $this->main();
|
||||||
}
|
}
|
||||||
|
@ -184,7 +185,7 @@ class PageController extends Controller
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function facerecognition()
|
public function facerecognition(): Response
|
||||||
{
|
{
|
||||||
return $this->main();
|
return $this->main();
|
||||||
}
|
}
|
||||||
|
@ -194,7 +195,7 @@ class PageController extends Controller
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function places()
|
public function places(): Response
|
||||||
{
|
{
|
||||||
return $this->main();
|
return $this->main();
|
||||||
}
|
}
|
||||||
|
@ -204,7 +205,7 @@ class PageController extends Controller
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function tags()
|
public function tags(): Response
|
||||||
{
|
{
|
||||||
return $this->main();
|
return $this->main();
|
||||||
}
|
}
|
||||||
|
@ -214,7 +215,7 @@ class PageController extends Controller
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function map()
|
public function map(): Response
|
||||||
{
|
{
|
||||||
return $this->main();
|
return $this->main();
|
||||||
}
|
}
|
||||||
|
@ -224,7 +225,7 @@ class PageController extends Controller
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function explore()
|
public function explore(): Response
|
||||||
{
|
{
|
||||||
return $this->main();
|
return $this->main();
|
||||||
}
|
}
|
||||||
|
@ -234,7 +235,7 @@ class PageController extends Controller
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function nxsetup()
|
public function nxsetup(): Response
|
||||||
{
|
{
|
||||||
return $this->main();
|
return $this->main();
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ class PublicAlbumController extends Controller
|
||||||
*
|
*
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function showShare(string $token)
|
public function showShare(string $token): Response
|
||||||
{
|
{
|
||||||
// Validate token exists
|
// Validate token exists
|
||||||
$album = $this->albumsQuery->getAlbumByLink($token);
|
$album = $this->albumsQuery->getAlbumByLink($token);
|
||||||
|
|
|
@ -25,6 +25,7 @@ use OCP\Share\IShare;
|
||||||
|
|
||||||
class PublicController extends AuthPublicShareController
|
class PublicController extends AuthPublicShareController
|
||||||
{
|
{
|
||||||
|
/** @psalm-suppress PropertyNotSetInConstructor */
|
||||||
protected IShare $share;
|
protected IShare $share;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
@ -65,7 +66,7 @@ class PublicController extends AuthPublicShareController
|
||||||
$this->share = $this->shareManager->getShareByToken($this->getToken());
|
$this->share = $this->shareManager->getShareByToken($this->getToken());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,7 +81,7 @@ class PublicController extends AuthPublicShareController
|
||||||
// Check whether share exists
|
// Check whether share exists
|
||||||
try {
|
try {
|
||||||
$share = $this->shareManager->getShareByToken($this->getToken());
|
$share = $this->shareManager->getShareByToken($this->getToken());
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception) {
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +145,7 @@ class PublicController extends AuthPublicShareController
|
||||||
|
|
||||||
protected function isPasswordProtected(): bool
|
protected function isPasswordProtected(): bool
|
||||||
{
|
{
|
||||||
|
/** @psalm-suppress RedundantConditionGivenDocblockType */
|
||||||
return null !== $this->share->getPassword();
|
return null !== $this->share->getPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,11 +217,7 @@ class PublicController extends AuthPublicShareController
|
||||||
*/
|
*/
|
||||||
private function getSingleItemInitialState(\OCP\Files\File $file): array
|
private function getSingleItemInitialState(\OCP\Files\File $file): array
|
||||||
{
|
{
|
||||||
$data = $this->tq->getSingleItem($file->getId());
|
return $this->tq->getSingleItem($file->getId())
|
||||||
if (null === $data) {
|
?? throw new NotFoundException();
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,8 @@ use OCA\Memories\Exceptions;
|
||||||
use OCA\Memories\Util;
|
use OCA\Memories\Util;
|
||||||
use OCP\AppFramework\Http;
|
use OCP\AppFramework\Http;
|
||||||
use OCP\AppFramework\Http\JSONResponse;
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
use OCP\Share\IManager;
|
||||||
|
use OCP\Share\IShare;
|
||||||
|
|
||||||
class ShareController extends GenericApiController
|
class ShareController extends GenericApiController
|
||||||
{
|
{
|
||||||
|
@ -34,19 +36,16 @@ class ShareController extends GenericApiController
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
*
|
*
|
||||||
* Get the tokens of a node shared using an external link
|
* Get the tokens of a node shared using an external link
|
||||||
*
|
|
||||||
* @param mixed $id
|
|
||||||
* @param mixed $path
|
|
||||||
*/
|
*/
|
||||||
public function links($id, $path): Http\Response
|
public function links(?int $id, ?string $path): Http\Response
|
||||||
{
|
{
|
||||||
return Util::guardEx(function () use ($id, $path) {
|
return Util::guardEx(function () use ($id, $path) {
|
||||||
$file = $this->getNodeByIdOrPath($id, $path);
|
$file = $this->getNodeByIdOrPath($id, $path);
|
||||||
|
|
||||||
/** @var \OCP\Share\IManager $shareManager */
|
$shares = \OC::$server->get(IManager::class)
|
||||||
$shareManager = \OC::$server->get(\OCP\Share\IManager::class);
|
->getSharesBy(Util::getUID(), IShare::TYPE_LINK, $file, true, 50, 0)
|
||||||
|
;
|
||||||
|
|
||||||
$shares = $shareManager->getSharesBy(Util::getUID(), \OCP\Share\IShare::TYPE_LINK, $file, true, 50, 0);
|
|
||||||
if (empty($shares)) {
|
if (empty($shares)) {
|
||||||
throw Exceptions::NotFound('external links');
|
throw Exceptions::NotFound('external links');
|
||||||
}
|
}
|
||||||
|
@ -61,24 +60,21 @@ class ShareController extends GenericApiController
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
*
|
*
|
||||||
* Share a node using an external link
|
* Share a node using an external link
|
||||||
*
|
|
||||||
* @param mixed $id
|
|
||||||
* @param mixed $path
|
|
||||||
*/
|
*/
|
||||||
public function createNode($id, $path): Http\Response
|
public function createNode(?int $id, ?string $path): Http\Response
|
||||||
{
|
{
|
||||||
return Util::guardEx(function () use ($id, $path) {
|
return Util::guardEx(function () use ($id, $path) {
|
||||||
$file = $this->getNodeByIdOrPath($id, $path);
|
$file = $this->getNodeByIdOrPath($id, $path);
|
||||||
|
|
||||||
$shareManager = \OC::$server->get(\OCP\Share\IManager::class);
|
$manager = \OC::$server->get(IManager::class);
|
||||||
|
|
||||||
$share = $shareManager->newShare();
|
$share = $manager->createShare(
|
||||||
$share->setNode($file);
|
$manager->newShare()
|
||||||
$share->setShareType(\OCP\Share\IShare::TYPE_LINK);
|
->setNode($file)
|
||||||
$share->setSharedBy($this->userSession->getUser()->getUID());
|
->setShareType(\OCP\Share\IShare::TYPE_LINK)
|
||||||
$share->setPermissions(\OCP\Constants::PERMISSION_READ);
|
->setSharedBy(Util::getUID())
|
||||||
|
->setPermissions(\OCP\Constants::PERMISSION_READ),
|
||||||
$share = $shareManager->createShare($share);
|
);
|
||||||
|
|
||||||
return new JSONResponse($this->makeShareResponse($share), Http::STATUS_OK);
|
return new JSONResponse($this->makeShareResponse($share), Http::STATUS_OK);
|
||||||
});
|
});
|
||||||
|
@ -94,22 +90,21 @@ class ShareController extends GenericApiController
|
||||||
return Util::guardEx(static function () use ($id) {
|
return Util::guardEx(static function () use ($id) {
|
||||||
$uid = Util::getUID();
|
$uid = Util::getUID();
|
||||||
|
|
||||||
/** @var \OCP\Share\IManager $shareManager */
|
$manager = \OC::$server->get(\OCP\Share\IManager::class);
|
||||||
$shareManager = \OC::$server->get(\OCP\Share\IManager::class);
|
|
||||||
|
|
||||||
$share = $shareManager->getShareById($id);
|
$share = $manager->getShareById($id);
|
||||||
|
|
||||||
if ($share->getSharedBy() !== $uid) {
|
if ($share->getSharedBy() !== $uid) {
|
||||||
throw Exceptions::Forbidden('You are not the owner of this share');
|
throw Exceptions::Forbidden('You are not the owner of this share');
|
||||||
}
|
}
|
||||||
|
|
||||||
$shareManager->deleteShare($share);
|
$manager->deleteShare($share);
|
||||||
|
|
||||||
return new JSONResponse([], Http::STATUS_OK);
|
return new JSONResponse([], Http::STATUS_OK);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getNodeByIdOrPath($id, $path): \OCP\Files\Node
|
private function getNodeByIdOrPath(?int $id, ?string $path): \OCP\Files\Node
|
||||||
{
|
{
|
||||||
$uid = Util::getUID();
|
$uid = Util::getUID();
|
||||||
|
|
||||||
|
@ -120,7 +115,7 @@ class ShareController extends GenericApiController
|
||||||
} elseif ($path) {
|
} elseif ($path) {
|
||||||
$file = Util::getUserFolder($uid)->get($path);
|
$file = Util::getUserFolder($uid)->get($path);
|
||||||
}
|
}
|
||||||
} catch (\OCP\Files\NotFoundException $e) {
|
} catch (\OCP\Files\NotFoundException) {
|
||||||
throw Exceptions::NotFoundFile($path ?? $id);
|
throw Exceptions::NotFoundFile($path ?? $id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,24 +126,26 @@ class ShareController extends GenericApiController
|
||||||
return $file;
|
return $file;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function makeShareResponse(\OCP\Share\IShare $share): array
|
private function makeShareResponse(IShare $share): array
|
||||||
{
|
{
|
||||||
/** @var \OCP\IURLGenerator $urlGenerator */
|
$token = $share->getToken();
|
||||||
$urlGenerator = \OC::$server->get(\OCP\IURLGenerator::class);
|
$url = \OC::$server->get(\OCP\IURLGenerator::class)
|
||||||
|
->linkToRouteAbsolute('memories.Public.showShare', ['token' => $token])
|
||||||
|
;
|
||||||
|
|
||||||
$tok = $share->getToken();
|
/**
|
||||||
$exp = $share->getExpirationDate();
|
* @psalm-suppress RedundantConditionGivenDocblockType
|
||||||
$url = $urlGenerator->linkToRouteAbsolute('memories.Public.showShare', [
|
* @psalm-suppress DocblockTypeContradiction
|
||||||
'token' => $tok,
|
*/
|
||||||
]);
|
$expiration = $share->getExpirationDate()?->getTimestamp();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => $share->getFullId(),
|
'id' => $share->getFullId(),
|
||||||
'label' => $share->getLabel(),
|
'label' => $share->getLabel(),
|
||||||
'token' => $tok,
|
'token' => $token,
|
||||||
'url' => $url,
|
'url' => $url,
|
||||||
'hasPassword' => $share->getPassword() ? true : false,
|
'hasPassword' => $share->getPassword() ? true : false,
|
||||||
'expiration' => $exp ? $exp->getTimestamp() : null,
|
'expiration' => $expiration,
|
||||||
'editable' => $share->getPermissions() & \OCP\Constants::PERMISSION_UPDATE,
|
'editable' => $share->getPermissions() & \OCP\Constants::PERMISSION_UPDATE,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@ class VideoController extends GenericApiController
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request and check data was received
|
// Request and check data was received
|
||||||
return Util::guardExDirect(function ($out) use ($client, $path, $profile) {
|
return Util::guardExDirect(function (Http\IOutput $out) use ($client, $path, $profile) {
|
||||||
try {
|
try {
|
||||||
$status = $this->getUpstream($client, $path, $profile);
|
$status = $this->getUpstream($client, $path, $profile);
|
||||||
if (409 === $status || -1 === $status) {
|
if (409 === $status || -1 === $status) {
|
||||||
|
@ -261,7 +261,7 @@ class VideoController extends GenericApiController
|
||||||
// Make sure query params are repeated
|
// Make sure query params are repeated
|
||||||
// For example, in folder sharing, we need the params on every request
|
// For example, in folder sharing, we need the params on every request
|
||||||
$url = BinExt::getGoVodUrl($client, $path, $profile);
|
$url = BinExt::getGoVodUrl($client, $path, $profile);
|
||||||
if ($params = $_SERVER['QUERY_STRING']) {
|
if (\array_key_exists('QUERY_STRING', $_SERVER) && !empty($params = $_SERVER['QUERY_STRING'])) {
|
||||||
$url .= "?{$params}";
|
$url .= "?{$params}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,7 +275,7 @@ class VideoController extends GenericApiController
|
||||||
|
|
||||||
// Stream the response to the browser without reading it into memory
|
// Stream the response to the browser without reading it into memory
|
||||||
$headersWritten = false;
|
$headersWritten = false;
|
||||||
curl_setopt($ch, CURLOPT_WRITEFUNCTION, static function ($curl, $data) use (&$headersWritten, $profile) {
|
curl_setopt($ch, CURLOPT_WRITEFUNCTION, static function (mixed $curl, mixed $data) use (&$headersWritten, $profile) {
|
||||||
$returnCode = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
$returnCode = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||||
|
|
||||||
if (200 === $returnCode) {
|
if (200 === $returnCode) {
|
||||||
|
@ -318,9 +318,9 @@ class VideoController extends GenericApiController
|
||||||
/**
|
/**
|
||||||
* POST to go-vod to create a temporary file.
|
* POST to go-vod to create a temporary file.
|
||||||
*
|
*
|
||||||
* @param mixed $blob
|
* @return mixed The response from upstream
|
||||||
*/
|
*/
|
||||||
private static function postFile(string $client, $blob)
|
private static function postFile(string $client, mixed $blob): mixed
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return self::postFileInternal($client, $blob);
|
return self::postFileInternal($client, $blob);
|
||||||
|
@ -333,7 +333,7 @@ class VideoController extends GenericApiController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function postFileInternal(string $client, $blob)
|
private static function postFileInternal(string $client, mixed $blob): mixed
|
||||||
{
|
{
|
||||||
$url = BinExt::getGoVodUrl($client, '/create', 'ignore');
|
$url = BinExt::getGoVodUrl($client, '/create', 'ignore');
|
||||||
|
|
||||||
|
@ -352,7 +352,7 @@ class VideoController extends GenericApiController
|
||||||
throw new \Exception("Could not create temporary file ({$returnCode})");
|
throw new \Exception("Could not create temporary file ({$returnCode})");
|
||||||
}
|
}
|
||||||
|
|
||||||
return json_decode($response, true);
|
return json_decode((string) $response, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -366,6 +366,8 @@ class VideoController extends GenericApiController
|
||||||
// Get file paths for all live photos
|
// Get file paths for all live photos
|
||||||
$liveFiles = array_map(fn ($r) => $this->rootFolder->getById((int) $r['fileid']), $liveRecords);
|
$liveFiles = array_map(fn ($r) => $this->rootFolder->getById((int) $r['fileid']), $liveRecords);
|
||||||
$liveFiles = array_filter($liveFiles, static fn ($files) => \count($files) > 0 && $files[0] instanceof File);
|
$liveFiles = array_filter($liveFiles, static fn ($files) => \count($files) > 0 && $files[0] instanceof File);
|
||||||
|
|
||||||
|
/** @var File[] (checked above) */
|
||||||
$liveFiles = array_map(static fn ($files) => $files[0], $liveFiles);
|
$liveFiles = array_map(static fn ($files) => $files[0], $liveFiles);
|
||||||
|
|
||||||
// Should be filtered enough by now
|
// Should be filtered enough by now
|
||||||
|
@ -380,7 +382,7 @@ class VideoController extends GenericApiController
|
||||||
|
|
||||||
// Remove extension so the filename itself counts in the path
|
// Remove extension so the filename itself counts in the path
|
||||||
if (str_contains($filename, '.')) {
|
if (str_contains($filename, '.')) {
|
||||||
$filename = substr($filename, 0, strrpos($filename, '.'));
|
$filename = substr($filename, 0, strrpos($filename, '.') ?: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get components with the filename as lowercase
|
// Get components with the filename as lowercase
|
||||||
|
|
|
@ -91,9 +91,9 @@ class AlbumsQuery
|
||||||
/**
|
/**
|
||||||
* Check if an album has a file.
|
* Check if an album has a file.
|
||||||
*
|
*
|
||||||
* @return ?string owner of file
|
* @return false|string owner of file if found
|
||||||
*/
|
*/
|
||||||
public function hasFile(int $albumId, int $fileId): ?string
|
public function hasFile(int $albumId, int $fileId): false|string
|
||||||
{
|
{
|
||||||
$query = $this->connection->getQueryBuilder();
|
$query = $this->connection->getQueryBuilder();
|
||||||
$query->select('owner')->from('photos_albums_files')->where(
|
$query->select('owner')->from('photos_albums_files')->where(
|
||||||
|
@ -103,15 +103,15 @@ class AlbumsQuery
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return $query->executeQuery()->fetchOne() ?: null;
|
return $query->executeQuery()->fetchOne();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a file belongs to a user through an album.
|
* Check if a file belongs to a user through an album.
|
||||||
*
|
*
|
||||||
* @return bool|string owner of file
|
* @return false|string owner of file if found
|
||||||
*/
|
*/
|
||||||
public function userHasFile(string $uid, int $fileId)
|
public function userHasFile(string $uid, int $fileId): false|string
|
||||||
{
|
{
|
||||||
$query = $this->connection->getQueryBuilder();
|
$query = $this->connection->getQueryBuilder();
|
||||||
$query->select('paf.owner')->from('photos_albums_files', 'paf')->where(
|
$query->select('paf.owner')->from('photos_albums_files', 'paf')->where(
|
||||||
|
@ -173,6 +173,7 @@ class AlbumsQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user is owner
|
// Check if user is owner
|
||||||
|
/** @psalm-suppress PossiblyUndefinedVariable */
|
||||||
if ($albumUid === $uid) {
|
if ($albumUid === $uid) {
|
||||||
return $album;
|
return $album;
|
||||||
}
|
}
|
||||||
|
@ -264,15 +265,21 @@ class AlbumsQuery
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get list of collaborator ids including user id and groups */
|
/**
|
||||||
|
* Get the various collaborator IDs that a user has.
|
||||||
|
* This includes the groups the user is in and the user itself.
|
||||||
|
*
|
||||||
|
* @return string[] List of collaborator IDs
|
||||||
|
*/
|
||||||
private function getSelfCollaborators(string $uid)
|
private function getSelfCollaborators(string $uid)
|
||||||
{
|
{
|
||||||
|
// Get the user in question
|
||||||
|
$user = \OC::$server->get(\OCP\IUserManager::class)->get($uid)
|
||||||
|
?: throw new \Exception('User not found');
|
||||||
// Get groups for the user
|
// Get groups for the user
|
||||||
$groupManager = \OC::$server->get(\OCP\IGroupManager::class);
|
$groups = \OC::$server->get(\OCP\IGroupManager::class)->getUserGroupIds($user);
|
||||||
$user = \OC::$server->get(\OCP\IUserManager::class)->get($uid);
|
|
||||||
$groups = $groupManager->getUserGroupIds($user);
|
|
||||||
|
|
||||||
// And albums shared with user
|
// Add the user itself as a collaborator
|
||||||
$groups[] = $uid;
|
$groups[] = $uid;
|
||||||
|
|
||||||
return $groups;
|
return $groups;
|
||||||
|
@ -284,8 +291,7 @@ class AlbumsQuery
|
||||||
private function collaboratorsTable(): string
|
private function collaboratorsTable(): string
|
||||||
{
|
{
|
||||||
// https://github.com/nextcloud/photos/commit/20e3e61ad577014e5f092a292c90a8476f630355
|
// https://github.com/nextcloud/photos/commit/20e3e61ad577014e5f092a292c90a8476f630355
|
||||||
$appManager = \OC::$server->get(\OCP\App\IAppManager::class);
|
$photosVersion = \OC::$server->get(\OCP\App\IAppManager::class)->getAppVersion('photos');
|
||||||
$photosVersion = $appManager->getAppVersion('photos');
|
|
||||||
if (version_compare($photosVersion, '2.0.1', '>=')) {
|
if (version_compare($photosVersion, '2.0.1', '>=')) {
|
||||||
return 'photos_albums_collabs';
|
return 'photos_albums_collabs';
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,6 +151,8 @@ class FsManager
|
||||||
*
|
*
|
||||||
* @param Folder $root root folder
|
* @param Folder $root root folder
|
||||||
* @param string $key cache key
|
* @param string $key cache key
|
||||||
|
*
|
||||||
|
* @return string[] List of paths
|
||||||
*/
|
*/
|
||||||
public function getNoMediaFolders(Folder $root, string $key): array
|
public function getNoMediaFolders(Folder $root, string $key): array
|
||||||
{
|
{
|
||||||
|
@ -303,7 +305,7 @@ class FsManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if share is password protected
|
// Check if share is password protected
|
||||||
if (($password = $share->getPassword()) !== null) {
|
if (!empty($password = $share->getPassword())) {
|
||||||
$session = \OC::$server->get(\OCP\ISession::class);
|
$session = \OC::$server->get(\OCP\ISession::class);
|
||||||
|
|
||||||
// https://github.com/nextcloud/server/blob/0447b53bda9fe95ea0cbed765aa332584605d652/lib/public/AppFramework/PublicShareController.php#L119
|
// https://github.com/nextcloud/server/blob/0447b53bda9fe95ea0cbed765aa332584605d652/lib/public/AppFramework/PublicShareController.php#L119
|
||||||
|
@ -318,7 +320,7 @@ class FsManager
|
||||||
return $share;
|
return $share;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getShareNode()
|
public function getShareNode(): ?Node
|
||||||
{
|
{
|
||||||
$share = $this->getShareObject();
|
$share = $this->getShareObject();
|
||||||
if (null === $share) {
|
if (null === $share) {
|
||||||
|
@ -400,6 +402,7 @@ class FsManager
|
||||||
Util::forcePermissions($file, $perm);
|
Util::forcePermissions($file, $perm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @var File */
|
||||||
return $file;
|
return $file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,9 @@ trait TimelineQueryCTE
|
||||||
return self::bundleCTEs([self::CTE_FOLDERS_ALL($hidden), $cte]);
|
return self::bundleCTEs([self::CTE_FOLDERS_ALL($hidden), $cte]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** CTE to get all archive folders recursively in the given top folders */
|
/**
|
||||||
|
* CTE to get all archive folders recursively in the given top folders.
|
||||||
|
*/
|
||||||
protected static function CTE_FOLDERS_ARCHIVE(): string
|
protected static function CTE_FOLDERS_ARCHIVE(): string
|
||||||
{
|
{
|
||||||
$cte = "*PREFIX*cte_folders(fileid) AS (
|
$cte = "*PREFIX*cte_folders(fileid) AS (
|
||||||
|
@ -102,6 +104,9 @@ trait TimelineQueryCTE
|
||||||
return self::bundleCTEs([self::CTE_FOLDERS_ALL(true), $cte]);
|
return self::bundleCTEs([self::CTE_FOLDERS_ALL(true), $cte]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $ctes The CTEs to bundle
|
||||||
|
*/
|
||||||
protected static function bundleCTEs(array $ctes): string
|
protected static function bundleCTEs(array $ctes): string
|
||||||
{
|
{
|
||||||
return 'WITH RECURSIVE '.implode(',', $ctes);
|
return 'WITH RECURSIVE '.implode(',', $ctes);
|
||||||
|
|
|
@ -33,10 +33,8 @@ trait TimelineQueryMap
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMapClusters(
|
public function getMapClusters(float $gridLen, string $bounds): array
|
||||||
float $gridLen,
|
{
|
||||||
string $bounds,
|
|
||||||
): array {
|
|
||||||
$query = $this->connection->getQueryBuilder();
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
|
||||||
// Get the average location of each cluster
|
// Get the average location of each cluster
|
||||||
|
@ -84,6 +82,11 @@ trait TimelineQueryMap
|
||||||
return $clusters;
|
return $clusters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets previews for a list of map clusters.
|
||||||
|
*
|
||||||
|
* @param int[] $clusterIds
|
||||||
|
*/
|
||||||
public function getMapClusterPreviews(array $clusterIds): array
|
public function getMapClusterPreviews(array $clusterIds): array
|
||||||
{
|
{
|
||||||
$query = $this->connection->getQueryBuilder();
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
@ -131,6 +134,8 @@ trait TimelineQueryMap
|
||||||
/**
|
/**
|
||||||
* Gets the suggested initial coordinates for the map.
|
* Gets the suggested initial coordinates for the map.
|
||||||
* Uses the coordinates of the newest photo (by date).
|
* Uses the coordinates of the newest photo (by date).
|
||||||
|
*
|
||||||
|
* @psalm-return array{lat: float, lon: float}|null
|
||||||
*/
|
*/
|
||||||
public function getMapInitialPosition(): ?array
|
public function getMapInitialPosition(): ?array
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,7 +8,10 @@ use OCP\Files\FileInfo;
|
||||||
|
|
||||||
class TimelineRoot
|
class TimelineRoot
|
||||||
{
|
{
|
||||||
|
/** @var array<int, \OCP\Files\FileInfo> */
|
||||||
protected array $folders = [];
|
protected array $folders = [];
|
||||||
|
|
||||||
|
/** @var array<int, string> */
|
||||||
protected array $folderPaths = [];
|
protected array $folderPaths = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,7 +32,7 @@ class TimelineRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add top level folder
|
// Add top level folder
|
||||||
$this->setFolder($info->getId(), $info, $path);
|
$this->setFolder($info->getId() ?? 0, $info, $path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,28 +92,18 @@ class TimelineRoot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFolderPath(int $id)
|
/** @return int[] */
|
||||||
{
|
|
||||||
return $this->folderPaths[$id];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return int[]
|
|
||||||
*/
|
|
||||||
public function getIds(): array
|
public function getIds(): array
|
||||||
{
|
{
|
||||||
return array_keys($this->folderPaths);
|
return array_keys($this->folderPaths);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getOneId(): ?int
|
||||||
* @return null|int
|
|
||||||
*/
|
|
||||||
public function getOneId()
|
|
||||||
{
|
{
|
||||||
return array_key_first($this->folders);
|
return array_key_first($this->folders);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFolder(int $id)
|
public function getFolder(int $id): ?FileInfo
|
||||||
{
|
{
|
||||||
return $this->folders[$id];
|
return $this->folders[$id];
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,7 +138,7 @@ class TimelineWrite
|
||||||
$buid = $prevRow ? $prevRow['buid'] : '';
|
$buid = $prevRow ? $prevRow['buid'] : '';
|
||||||
if (empty($buid)) {
|
if (empty($buid)) {
|
||||||
$imageUniqueId = \array_key_exists('ImageUniqueID', $exif) ? $exif['ImageUniqueID'] : null;
|
$imageUniqueId = \array_key_exists('ImageUniqueID', $exif) ? $exif['ImageUniqueID'] : null;
|
||||||
$buid = Exif::getBUID($file->getName(), $imageUniqueId, $file->getSize());
|
$buid = Exif::getBUID($file->getName(), $imageUniqueId, (int) $file->getSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get exif json
|
// Get exif json
|
||||||
|
@ -185,7 +185,7 @@ class TimelineWrite
|
||||||
/**
|
/**
|
||||||
* Remove a file from the exif database.
|
* Remove a file from the exif database.
|
||||||
*/
|
*/
|
||||||
public function deleteFile(File &$file): void
|
public function deleteFile(File $file): void
|
||||||
{
|
{
|
||||||
// Get full record
|
// Get full record
|
||||||
$query = $this->connection->getQueryBuilder();
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
@ -261,7 +261,7 @@ class TimelineWrite
|
||||||
*/
|
*/
|
||||||
private function getCurrentRow(int $fileId): ?array
|
private function getCurrentRow(int $fileId): ?array
|
||||||
{
|
{
|
||||||
$fetch = function (string $table) use ($fileId) {
|
$fetch = function (string $table) use ($fileId): false|null|array {
|
||||||
$query = $this->connection->getQueryBuilder();
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
|
||||||
return $query->select('*')
|
return $query->select('*')
|
||||||
|
@ -277,6 +277,8 @@ class TimelineWrite
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert EXIF data to filtered JSON string.
|
* Convert EXIF data to filtered JSON string.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $exif EXIF data
|
||||||
*/
|
*/
|
||||||
private function getExifJson(array $exif): string
|
private function getExifJson(array $exif): string
|
||||||
{
|
{
|
||||||
|
|
|
@ -49,7 +49,7 @@ trait TimelineWriteOrphans
|
||||||
*
|
*
|
||||||
* @param array $fields list of fields to select
|
* @param array $fields list of fields to select
|
||||||
* @param int $txnSize number of rows to process in a single transaction
|
* @param int $txnSize number of rows to process in a single transaction
|
||||||
* @param \Closure $callback will be passed each row
|
* @param \Closure(array): void $callback will be passed each row
|
||||||
*/
|
*/
|
||||||
public function orphanAndRun(array $fields, int $txnSize, \Closure $callback): void
|
public function orphanAndRun(array $fields, int $txnSize, \Closure $callback): void
|
||||||
{
|
{
|
||||||
|
@ -74,6 +74,9 @@ trait TimelineWriteOrphans
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of orphaned files.
|
* Get a list of orphaned files.
|
||||||
|
*
|
||||||
|
* @param int $count max number of rows to return
|
||||||
|
* @param string[] $fields list of fields to select
|
||||||
*/
|
*/
|
||||||
protected function getSomeOrphans(int $count, array $fields): array
|
protected function getSomeOrphans(int $count, array $fields): array
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,12 +19,12 @@ trait TimelineWritePlaces
|
||||||
* Add places data for a file.
|
* Add places data for a file.
|
||||||
*
|
*
|
||||||
* @param int $fileId The file ID
|
* @param int $fileId The file ID
|
||||||
* @param null|float $lat The latitude of the file
|
* @param ?float $lat The latitude of the file
|
||||||
* @param null|float $lon The longitude of the file
|
* @param ?float $lon The longitude of the file
|
||||||
*
|
*
|
||||||
* @return array The list of osm_id of the places
|
* @return int[] The list of osm_id of the places
|
||||||
*/
|
*/
|
||||||
public function updatePlacesData(int $fileId, $lat, $lon): array
|
public function updatePlacesData(int $fileId, ?float $lat, ?float $lon): array
|
||||||
{
|
{
|
||||||
// Get GIS type
|
// Get GIS type
|
||||||
$gisType = \OCA\Memories\Util::placesGISType();
|
$gisType = \OCA\Memories\Util::placesGISType();
|
||||||
|
@ -74,7 +74,7 @@ trait TimelineWritePlaces
|
||||||
$this->connection->commit();
|
$this->connection->commit();
|
||||||
|
|
||||||
// Return list of osm_id
|
// Return list of osm_id
|
||||||
return array_map(static fn ($row) => $row['osm_id'], $rows);
|
return array_map(static fn ($row) => (int) $row['osm_id'], $rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -155,7 +155,7 @@ trait TimelineWritePlaces
|
||||||
/**
|
/**
|
||||||
* Read coordinates from array and round to 6 decimal places.
|
* Read coordinates from array and round to 6 decimal places.
|
||||||
*
|
*
|
||||||
* Modifies the array to remove invalid coordinates.
|
* Modifies the EXIF array to remove invalid coordinates.
|
||||||
*
|
*
|
||||||
* @return (null|float)[]
|
* @return (null|float)[]
|
||||||
*
|
*
|
||||||
|
|
|
@ -44,7 +44,7 @@ class Exceptions
|
||||||
], Http::STATUS_NOT_FOUND));
|
], Http::STATUS_NOT_FOUND));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function NotFoundFile($identifier): HttpResponseException
|
public static function NotFoundFile(null|int|string $identifier): HttpResponseException
|
||||||
{
|
{
|
||||||
return new HttpResponseException(new DataResponse([
|
return new HttpResponseException(new DataResponse([
|
||||||
'message' => "File not found ({$identifier})",
|
'message' => "File not found ({$identifier})",
|
||||||
|
|
76
lib/Exif.php
76
lib/Exif.php
|
@ -15,21 +15,31 @@ class Exif
|
||||||
private const EXIFTOOL_ARGS = ['-api', 'QuickTimeUTC=1', '-n', '-json'];
|
private const EXIFTOOL_ARGS = ['-api', 'QuickTimeUTC=1', '-n', '-json'];
|
||||||
|
|
||||||
/** Opened instance of exiftool when running in command mode */
|
/** Opened instance of exiftool when running in command mode */
|
||||||
|
/** @var null|resource */
|
||||||
private static $staticProc;
|
private static $staticProc;
|
||||||
|
|
||||||
|
/** @var null|resource[] */
|
||||||
private static $staticPipes;
|
private static $staticPipes;
|
||||||
|
|
||||||
|
/** Disable uisage of static process */
|
||||||
private static bool $noStaticProc = false;
|
private static bool $noStaticProc = false;
|
||||||
|
|
||||||
public static function closeStaticExiftoolProc(): void
|
public static function closeStaticExiftoolProc(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (self::$staticProc) {
|
// Close I/O pipes
|
||||||
|
if (self::$staticPipes) {
|
||||||
fclose(self::$staticPipes[0]);
|
fclose(self::$staticPipes[0]);
|
||||||
fclose(self::$staticPipes[1]);
|
fclose(self::$staticPipes[1]);
|
||||||
fclose(self::$staticPipes[2]);
|
fclose(self::$staticPipes[2]);
|
||||||
|
self::$staticPipes = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close process
|
||||||
|
if (self::$staticProc) {
|
||||||
proc_terminate(self::$staticProc);
|
proc_terminate(self::$staticProc);
|
||||||
proc_close(self::$staticProc);
|
proc_close(self::$staticProc);
|
||||||
self::$staticProc = null;
|
self::$staticProc = null;
|
||||||
self::$staticPipes = null;
|
|
||||||
}
|
}
|
||||||
} catch (\Exception $ex) {
|
} catch (\Exception $ex) {
|
||||||
}
|
}
|
||||||
|
@ -50,10 +60,13 @@ class Exif
|
||||||
if (!self::$staticProc) {
|
if (!self::$staticProc) {
|
||||||
self::initializeStaticExiftoolProc();
|
self::initializeStaticExiftoolProc();
|
||||||
usleep(500000); // wait if error
|
usleep(500000); // wait if error
|
||||||
|
|
||||||
|
/** @psalm-suppress NullArgument */
|
||||||
if (!proc_get_status(self::$staticProc)['running']) {
|
if (!proc_get_status(self::$staticProc)['running']) {
|
||||||
error_log('WARN: Failed to create stay_open exiftool process');
|
error_log('WARN: Failed to create stay_open exiftool process');
|
||||||
self::$noStaticProc = true;
|
self::$noStaticProc = true;
|
||||||
self::$staticProc = null;
|
self::$staticProc = null;
|
||||||
|
self::$staticPipes = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -61,12 +74,15 @@ class Exif
|
||||||
|
|
||||||
if (!proc_get_status(self::$staticProc)['running']) {
|
if (!proc_get_status(self::$staticProc)['running']) {
|
||||||
self::$staticProc = null;
|
self::$staticProc = null;
|
||||||
|
self::$staticPipes = null;
|
||||||
self::ensureStaticExiftoolProc();
|
self::ensureStaticExiftoolProc();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get exif data as a JSON object from a Nextcloud file.
|
* Get exif data as a JSON object from a Nextcloud file.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public static function getExifFromFile(File $file): array
|
public static function getExifFromFile(File $file): array
|
||||||
{
|
{
|
||||||
|
@ -108,6 +124,8 @@ class Exif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get exif data as a JSON object from a local file path.
|
* Get exif data as a JSON object from a local file path.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public static function getExifFromLocalPath(string $path): array
|
public static function getExifFromLocalPath(string $path): array
|
||||||
{
|
{
|
||||||
|
@ -122,6 +140,8 @@ class Exif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse date from exif format and throw error if invalid.
|
* Parse date from exif format and throw error if invalid.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $exif
|
||||||
*/
|
*/
|
||||||
public static function parseExifDate(array $exif): \DateTime
|
public static function parseExifDate(array $exif): \DateTime
|
||||||
{
|
{
|
||||||
|
@ -140,9 +160,12 @@ class Exif
|
||||||
|
|
||||||
// Get timezone from exif
|
// Get timezone from exif
|
||||||
try {
|
try {
|
||||||
$exifTz = $exif['OffsetTimeOriginal'] ?? $exif['OffsetTime'] ?? $exif['LocationTZID'] ?? null;
|
$tzStr = $exif['OffsetTimeOriginal']
|
||||||
$exifTz = new \DateTimeZone($exifTz);
|
?: $exif['OffsetTime']
|
||||||
} catch (\Error $e) {
|
?: $exif['LocationTZID']
|
||||||
|
?: throw new \Exception();
|
||||||
|
$exifTz = new \DateTimeZone((string) $tzStr);
|
||||||
|
} catch (\Exception) {
|
||||||
$exifTz = null;
|
$exifTz = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,13 +223,15 @@ class Exif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the date taken from either the file or exif data if available.
|
* Get the date taken from either the file or exif data if available.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $exif
|
||||||
*/
|
*/
|
||||||
public static function getDateTaken(File $file, array $exif): \DateTime
|
public static function getDateTaken(File $file, array $exif): \DateTime
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return self::parseExifDate($exif);
|
return self::parseExifDate($exif);
|
||||||
} catch (\Exception $ex) {
|
} catch (\Exception) {
|
||||||
} catch (\ValueError $ex) {
|
} catch (\ValueError) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to modification time
|
// Fall back to modification time
|
||||||
|
@ -217,7 +242,7 @@ class Exif
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$dt->setTimezone(new \DateTimeZone($tz));
|
$dt->setTimezone(new \DateTimeZone($tz));
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception) {
|
||||||
throw new \Error("FATAL: system timezone is invalid (TZ): {$tz}");
|
throw new \Error("FATAL: system timezone is invalid (TZ): {$tz}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,9 +260,13 @@ class Exif
|
||||||
/**
|
/**
|
||||||
* Get image dimensions from Exif data.
|
* Get image dimensions from Exif data.
|
||||||
*
|
*
|
||||||
* @return array [width, height]
|
* @param array<string, mixed> $exif
|
||||||
|
*
|
||||||
|
* @return int[]
|
||||||
|
*
|
||||||
|
* @psalm-return list{int, int}
|
||||||
*/
|
*/
|
||||||
public static function getDimensions(array $exif)
|
public static function getDimensions(array $exif): array
|
||||||
{
|
{
|
||||||
$width = $exif['ImageWidth'] ?? 0;
|
$width = $exif['ImageWidth'] ?? 0;
|
||||||
$height = $exif['ImageHeight'] ?? 0;
|
$height = $exif['ImageHeight'] ?? 0;
|
||||||
|
@ -274,7 +303,7 @@ class Exif
|
||||||
* @param mixed $imageUniqueID EXIF field
|
* @param mixed $imageUniqueID EXIF field
|
||||||
* @param int $size the file size in bytes (fallback)
|
* @param int $size the file size in bytes (fallback)
|
||||||
*/
|
*/
|
||||||
public static function getBUID(string $basename, $imageUniqueID, int $size): string
|
public static function getBUID(string $basename, mixed $imageUniqueID, int $size): string
|
||||||
{
|
{
|
||||||
$sfx = "size={$size}";
|
$sfx = "size={$size}";
|
||||||
if (null !== $imageUniqueID && \strlen((string) $imageUniqueID) >= 4) {
|
if (null !== $imageUniqueID && \strlen((string) $imageUniqueID) >= 4) {
|
||||||
|
@ -296,7 +325,7 @@ class Exif
|
||||||
* Set exif data using raw json.
|
* Set exif data using raw json.
|
||||||
*
|
*
|
||||||
* @param string $path to local file
|
* @param string $path to local file
|
||||||
* @param array $data exif data
|
* @param array<string, mixed> $data exif data
|
||||||
*
|
*
|
||||||
* @throws \Exception on failure
|
* @throws \Exception on failure
|
||||||
*/
|
*/
|
||||||
|
@ -330,6 +359,11 @@ class Exif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set exif data using a raw array.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $data exif data
|
||||||
|
*/
|
||||||
public static function setFileExif(File $file, array $data): void
|
public static function setFileExif(File $file, array $data): void
|
||||||
{
|
{
|
||||||
// Get path to local file so we can skip reading
|
// Get path to local file so we can skip reading
|
||||||
|
@ -389,6 +423,7 @@ class Exif
|
||||||
private static function initializeStaticExiftoolProc(): void
|
private static function initializeStaticExiftoolProc(): void
|
||||||
{
|
{
|
||||||
self::closeStaticExiftoolProc();
|
self::closeStaticExiftoolProc();
|
||||||
|
self::$staticPipes = [];
|
||||||
self::$staticProc = proc_open(array_merge(self::getExiftool(), ['-stay_open', 'true', '-@', '-']), [
|
self::$staticProc = proc_open(array_merge(self::getExiftool(), ['-stay_open', 'true', '-@', '-']), [
|
||||||
0 => ['pipe', 'r'],
|
0 => ['pipe', 'r'],
|
||||||
1 => ['pipe', 'w'],
|
1 => ['pipe', 'w'],
|
||||||
|
@ -429,19 +464,34 @@ class Exif
|
||||||
|
|
||||||
private static function getExifFromLocalPathWithStaticProc(string $path): array
|
private static function getExifFromLocalPathWithStaticProc(string $path): array
|
||||||
{
|
{
|
||||||
|
// This function should not be called if there is no static process
|
||||||
|
if (!self::$staticPipes) {
|
||||||
|
throw new \Error('[BUG] No static pipes found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create arguments for exiftool
|
||||||
$args = implode("\n", self::EXIFTOOL_ARGS);
|
$args = implode("\n", self::EXIFTOOL_ARGS);
|
||||||
fwrite(self::$staticPipes[0], "{$path}\n{$args}\n-execute\n");
|
fwrite(self::$staticPipes[0], "{$path}\n{$args}\n-execute\n");
|
||||||
fflush(self::$staticPipes[0]);
|
fflush(self::$staticPipes[0]);
|
||||||
|
|
||||||
|
// The output of exiftool's stay_open process ends with this token
|
||||||
$readyToken = "\n{ready}\n";
|
$readyToken = "\n{ready}\n";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$buf = self::readOrTimeout(self::$staticPipes[1], self::EXIFTOOL_TIMEOUT, $readyToken);
|
$buf = self::readOrTimeout(self::$staticPipes[1], self::EXIFTOOL_TIMEOUT, $readyToken);
|
||||||
|
|
||||||
|
// The output buffer should always contain the ready token
|
||||||
|
// (this is the point of readOrTimeout)
|
||||||
$tokPos = strrpos($buf, $readyToken);
|
$tokPos = strrpos($buf, $readyToken);
|
||||||
|
if (false === $tokPos) {
|
||||||
|
throw new \Error('[BUG] No ready token found in output buffer');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slice everything before the ready token
|
||||||
$buf = substr($buf, 0, $tokPos);
|
$buf = substr($buf, 0, $tokPos);
|
||||||
|
|
||||||
return self::processStdout($buf);
|
return self::processStdout($buf);
|
||||||
} catch (\Exception $ex) {
|
} catch (\Exception) {
|
||||||
error_log("ERROR: Exiftool may have crashed, restarting process [{$path}]");
|
error_log("ERROR: Exiftool may have crashed, restarting process [{$path}]");
|
||||||
self::restartStaticExiftoolProc();
|
self::restartStaticExiftoolProc();
|
||||||
|
|
||||||
|
|
|
@ -126,10 +126,8 @@ class BinExt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect the exiftool binary to use.
|
* Detect the exiftool binary to use.
|
||||||
*
|
|
||||||
* @return false|string
|
|
||||||
*/
|
*/
|
||||||
public static function detectExiftool()
|
public static function detectExiftool(): false|string
|
||||||
{
|
{
|
||||||
if (!empty($path = Util::getSystemConfig('memories.exiftool'))) {
|
if (!empty($path = Util::getSystemConfig('memories.exiftool'))) {
|
||||||
return $path;
|
return $path;
|
||||||
|
@ -371,10 +369,8 @@ class BinExt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect the go-vod binary to use.
|
* Detect the go-vod binary to use.
|
||||||
*
|
|
||||||
* @return false|string
|
|
||||||
*/
|
*/
|
||||||
public static function detectGoVod()
|
public static function detectGoVod(): false|string
|
||||||
{
|
{
|
||||||
$goVodPath = Util::getSystemConfig('memories.vod.path');
|
$goVodPath = Util::getSystemConfig('memories.vod.path');
|
||||||
|
|
||||||
|
@ -445,7 +441,7 @@ class BinExt
|
||||||
return explode(' ', $matches[0])[2];
|
return explode(' ', $matches[0])[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function testSystemPerl(string $path): ?string
|
public static function testSystemPerl(string $path): string
|
||||||
{
|
{
|
||||||
/** @psalm-suppress ForbiddenCode */
|
/** @psalm-suppress ForbiddenCode */
|
||||||
if (($out = shell_exec("{$path} -e 'print \"OK\";'")) !== 'OK') {
|
if (($out = shell_exec("{$path} -e 'print \"OK\";'")) !== 'OK') {
|
||||||
|
@ -453,6 +449,6 @@ class BinExt
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @psalm-suppress ForbiddenCode */
|
/** @psalm-suppress ForbiddenCode */
|
||||||
return shell_exec("{$path} -e 'print $^V;'") ?: null;
|
return shell_exec("{$path} -e 'print $^V;'") ?: 'unknown version';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ class FileRobotImageState
|
||||||
/** 0 to 200 */
|
/** 0 to 200 */
|
||||||
public ?float $warmth = null;
|
public ?float $warmth = null;
|
||||||
|
|
||||||
/** Order of filters */
|
/** @var string[] Order of filters */
|
||||||
public array $finetuneOrder = [];
|
public array $finetuneOrder = [];
|
||||||
|
|
||||||
/** Crop X coordinate */
|
/** Crop X coordinate */
|
||||||
|
|
|
@ -46,9 +46,12 @@ class Index
|
||||||
/**
|
/**
|
||||||
* Callback to check if the process should continue.
|
* Callback to check if the process should continue.
|
||||||
* This is called before every file is indexed.
|
* This is called before every file is indexed.
|
||||||
|
*
|
||||||
|
* @var null|\Closure(): bool
|
||||||
*/
|
*/
|
||||||
public ?\Closure $continueCheck = null;
|
public ?\Closure $continueCheck = null;
|
||||||
|
|
||||||
|
/** @var string[] */
|
||||||
private static ?array $mimeList = null;
|
private static ?array $mimeList = null;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
|
|
@ -23,11 +23,17 @@ class Admin implements ISettings
|
||||||
return new TemplateResponse('memories', 'main', PageController::getMainParams());
|
return new TemplateResponse('memories', 'main', PageController::getMainParams());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function getSection()
|
public function getSection()
|
||||||
{
|
{
|
||||||
return Application::APPNAME;
|
return Application::APPNAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
public function getPriority()
|
public function getPriority()
|
||||||
{
|
{
|
||||||
return 50;
|
return 50;
|
||||||
|
|
|
@ -21,27 +21,36 @@ class AdminSection implements IIconSection
|
||||||
*/
|
*/
|
||||||
public function getForm()
|
public function getForm()
|
||||||
{
|
{
|
||||||
$parameters = [
|
return new TemplateResponse('memories', 'admin', []);
|
||||||
];
|
|
||||||
|
|
||||||
return new TemplateResponse('memories', 'admin', $parameters);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function getID()
|
public function getID()
|
||||||
{
|
{
|
||||||
return 'memories';
|
return 'memories';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function getName()
|
public function getName()
|
||||||
{
|
{
|
||||||
return $this->l->t('Memories');
|
return $this->l->t('Memories');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
public function getPriority()
|
public function getPriority()
|
||||||
{
|
{
|
||||||
return 75;
|
return 75;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function getIcon()
|
public function getIcon()
|
||||||
{
|
{
|
||||||
return $this->urlGenerator->imagePath('memories', 'app-dark.svg');
|
return $this->urlGenerator->imagePath('memories', 'app-dark.svg');
|
||||||
|
|
89
lib/Util.php
89
lib/Util.php
|
@ -27,11 +27,11 @@ class Util
|
||||||
*/
|
*/
|
||||||
public static function getArch(): ?string
|
public static function getArch(): ?string
|
||||||
{
|
{
|
||||||
$uname = php_uname('m');
|
$uname = strtolower(php_uname('m') ?: 'unknown');
|
||||||
if (false !== stripos($uname, 'aarch64') || false !== stripos($uname, 'arm64')) {
|
if (str_contains($uname, 'aarch64') || str_contains($uname, 'arm64')) {
|
||||||
return 'aarch64';
|
return 'aarch64';
|
||||||
}
|
}
|
||||||
if (false !== stripos($uname, 'x86_64') || false !== stripos($uname, 'amd64')) {
|
if (str_contains($uname, 'x86_64') || str_contains($uname, 'amd64')) {
|
||||||
return 'amd64';
|
return 'amd64';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,14 +46,13 @@ class Util
|
||||||
public static function getLibc(): ?string
|
public static function getLibc(): ?string
|
||||||
{
|
{
|
||||||
/** @psalm-suppress ForbiddenCode */
|
/** @psalm-suppress ForbiddenCode */
|
||||||
if ($ldd = shell_exec('ldd --version 2>&1')) {
|
$ldd = strtolower(shell_exec('ldd --version 2>&1') ?: 'unknown');
|
||||||
if (false !== stripos($ldd, 'musl')) {
|
if (str_contains($ldd, 'musl')) {
|
||||||
return 'musl';
|
return 'musl';
|
||||||
}
|
}
|
||||||
if (false !== stripos($ldd, 'glibc')) {
|
if (str_contains($ldd, 'glibc')) {
|
||||||
return 'glibc';
|
return 'glibc';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -79,9 +78,7 @@ class Util
|
||||||
*/
|
*/
|
||||||
public static function tagsIsEnabled(): bool
|
public static function tagsIsEnabled(): bool
|
||||||
{
|
{
|
||||||
$appManager = \OC::$server->get(IAppManager::class);
|
return \OC::$server->get(IAppManager::class)->isEnabledForUser('systemtags');
|
||||||
|
|
||||||
return $appManager->isEnabledForUser('systemtags');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -130,16 +127,14 @@ class Util
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$uid = self::getUID();
|
return 'true' === \OC::$server->get(IConfig::class)
|
||||||
} catch (\Exception $e) {
|
->getUserValue(self::getUID(), 'facerecognition', 'enabled', 'false')
|
||||||
return false;
|
;
|
||||||
|
} catch (\Exception) {
|
||||||
|
// not logged in
|
||||||
}
|
}
|
||||||
|
|
||||||
$enabled = \OC::$server->get(IConfig::class)
|
return false;
|
||||||
->getUserValue($uid, 'facerecognition', 'enabled', 'false')
|
|
||||||
;
|
|
||||||
|
|
||||||
return 'true' === $enabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -163,9 +158,7 @@ class Util
|
||||||
*/
|
*/
|
||||||
public static function previewGeneratorIsEnabled(): bool
|
public static function previewGeneratorIsEnabled(): bool
|
||||||
{
|
{
|
||||||
$appManager = \OC::$server->get(IAppManager::class);
|
return \OC::$server->get(IAppManager::class)->isEnabledForUser('previewgenerator');
|
||||||
|
|
||||||
return $appManager->isEnabledForUser('previewgenerator');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -196,11 +189,11 @@ class Util
|
||||||
* Force a fileinfo value on a node.
|
* Force a fileinfo value on a node.
|
||||||
* This is a hack to avoid subclassing everything.
|
* This is a hack to avoid subclassing everything.
|
||||||
*
|
*
|
||||||
* @param mixed $node File to patch
|
* @param Node $node File to patch
|
||||||
* @param mixed $key Key to set
|
* @param string $key Key to set
|
||||||
* @param mixed $value Value to set
|
* @param mixed $value Value to set
|
||||||
*/
|
*/
|
||||||
public static function forceFileInfo(Node &$node, $key, $value): void
|
public static function forceFileInfo(Node &$node, string $key, mixed $value): void
|
||||||
{
|
{
|
||||||
/** @var \OC\Files\Node\Node */
|
/** @var \OC\Files\Node\Node */
|
||||||
$node = $node;
|
$node = $node;
|
||||||
|
@ -212,8 +205,8 @@ class Util
|
||||||
/**
|
/**
|
||||||
* Force permissions on a node.
|
* Force permissions on a node.
|
||||||
*
|
*
|
||||||
* @param mixed $node File to patch
|
* @param Node $node File to patch
|
||||||
* @param mixed $permissions Permissions to set
|
* @param int $permissions Permissions to set
|
||||||
*/
|
*/
|
||||||
public static function forcePermissions(Node &$node, int $permissions): void
|
public static function forcePermissions(Node &$node, int $permissions): void
|
||||||
{
|
{
|
||||||
|
@ -248,10 +241,10 @@ class Util
|
||||||
/**
|
/**
|
||||||
* Add OG metadata to a page for a node.
|
* Add OG metadata to a page for a node.
|
||||||
*
|
*
|
||||||
* @param $node Node to get metadata from
|
* @param Node $node Node to get metadata from
|
||||||
* @param $title Title of the page
|
* @param string $title Title of the page
|
||||||
* @param $url URL of the page
|
* @param string $url URL of the page
|
||||||
* @param $previewArgs Preview arguments (e.g. token)
|
* @param array $previewArgs Preview arguments (e.g. token)
|
||||||
*/
|
*/
|
||||||
public static function addOgMetadata(Node $node, string $title, string $url, array $previewArgs): void
|
public static function addOgMetadata(Node $node, string $title, string $url, array $previewArgs): void
|
||||||
{
|
{
|
||||||
|
@ -291,8 +284,6 @@ class Util
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a random image or video from a given folder.
|
* Get a random image or video from a given folder.
|
||||||
*
|
|
||||||
* @param $folder Folder to search
|
|
||||||
*/
|
*/
|
||||||
public static function getAnyMedia(\OCP\Files\Folder $folder): ?Node
|
public static function getAnyMedia(\OCP\Files\Folder $folder): ?Node
|
||||||
{
|
{
|
||||||
|
@ -335,36 +326,40 @@ class Util
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get list of timeline paths as array.
|
* Get list of timeline paths as array.
|
||||||
|
*
|
||||||
|
* @return string[] List of paths
|
||||||
*/
|
*/
|
||||||
public static function getTimelinePaths(string $uid): array
|
public static function getTimelinePaths(string $uid): array
|
||||||
{
|
{
|
||||||
$config = \OC::$server->get(IConfig::class);
|
$paths = \OC::$server->get(IConfig::class)
|
||||||
$paths = $config->getUserValue($uid, Application::APPNAME, 'timelinePath', null)
|
->getUserValue($uid, Application::APPNAME, 'timelinePath', null)
|
||||||
?? self::getSystemConfig('memories.timeline.default_path');
|
?: self::getSystemConfig('memories.timeline.default_path');
|
||||||
|
|
||||||
return array_map(static fn ($p) => self::sanitizePath(trim($p)), explode(';', $paths));
|
return array_map(
|
||||||
|
static fn ($p) => self::sanitizePath(trim($p)),
|
||||||
|
explode(';', $paths),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitize a path to keep only ASCII characters and special characters.
|
* Sanitize a path to keep only ASCII characters and special characters.
|
||||||
|
* Blank will be returned on error.
|
||||||
*/
|
*/
|
||||||
public static function sanitizePath(string $path): ?string
|
public static function sanitizePath(string $path): string
|
||||||
{
|
{
|
||||||
$path = str_replace("\0", '', $path); // remove null characters
|
$path = str_replace("\0", '', $path); // remove null characters
|
||||||
|
|
||||||
return mb_ereg_replace('\/\/+', '/', $path) ?: null; // remove extra slashes
|
return mb_ereg_replace('\/\/+', '/', $path) ?: ''; // remove extra slashes
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert SQL UTC date to timestamp.
|
* Convert SQL UTC date to timestamp.
|
||||||
*
|
|
||||||
* @param mixed $sqlDate
|
|
||||||
*/
|
*/
|
||||||
public static function sqlUtcToTimestamp($sqlDate): int
|
public static function sqlUtcToTimestamp(string $sqlDate): int
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return (new \DateTime($sqlDate, new \DateTimeZone('UTC')))->getTimestamp();
|
return (new \DateTime($sqlDate, new \DateTimeZone('UTC')))->getTimestamp();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -372,7 +367,7 @@ class Util
|
||||||
/**
|
/**
|
||||||
* Explode a string into fixed number of components.
|
* Explode a string into fixed number of components.
|
||||||
*
|
*
|
||||||
* @param string $delimiter Delimiter
|
* @param non-empty-string $delimiter Delimiter
|
||||||
* @param string $string String to explode
|
* @param string $string String to explode
|
||||||
* @param int $count Number of components
|
* @param int $count Number of components
|
||||||
*
|
*
|
||||||
|
@ -387,9 +382,9 @@ class Util
|
||||||
* Get a system config key with the correct default.
|
* Get a system config key with the correct default.
|
||||||
*
|
*
|
||||||
* @param string $key System config key
|
* @param string $key System config key
|
||||||
* @param null|mixed $default Default value
|
* @param mixed $default Default value
|
||||||
*/
|
*/
|
||||||
public static function getSystemConfig(string $key, $default = null)
|
public static function getSystemConfig(string $key, mixed $default = null): mixed
|
||||||
{
|
{
|
||||||
$config = \OC::$server->get(\OCP\IConfig::class);
|
$config = \OC::$server->get(\OCP\IConfig::class);
|
||||||
|
|
||||||
|
@ -404,11 +399,9 @@ class Util
|
||||||
/**
|
/**
|
||||||
* Set a system config key.
|
* Set a system config key.
|
||||||
*
|
*
|
||||||
* @param mixed $value
|
|
||||||
*
|
|
||||||
* @throws \InvalidArgumentException
|
* @throws \InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public static function setSystemConfig(string $key, $value): void
|
public static function setSystemConfig(string $key, mixed $value): void
|
||||||
{
|
{
|
||||||
$config = \OC::$server->get(\OCP\IConfig::class);
|
$config = \OC::$server->get(\OCP\IConfig::class);
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,12 @@ trait UtilController
|
||||||
/**
|
/**
|
||||||
* Run a function and catch exceptions to return HTTP response.
|
* Run a function and catch exceptions to return HTTP response.
|
||||||
*
|
*
|
||||||
* @param mixed $function
|
* @param \Closure(): Http\Response $closure
|
||||||
*/
|
*/
|
||||||
public static function guardEx($function): Http\Response
|
public static function guardEx(\Closure $closure): Http\Response
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return $function();
|
return $closure();
|
||||||
} catch (\OCA\Memories\HttpResponseException $e) {
|
} catch (\OCA\Memories\HttpResponseException $e) {
|
||||||
return $e->response;
|
return $e->response;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
@ -29,23 +29,25 @@ trait UtilController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a callback response with guarded exceptions.
|
* Return a callback response with guarded exceptions.
|
||||||
|
*
|
||||||
|
* @param \Closure(Http\IOutput): void $closure
|
||||||
*/
|
*/
|
||||||
public static function guardExDirect(\Closure $closure): Http\Response
|
public static function guardExDirect(\Closure $closure): Http\Response
|
||||||
{
|
{
|
||||||
/** @psalm-suppress MissingTemplateParam */
|
/** @psalm-suppress MissingTemplateParam */
|
||||||
return new class($closure) extends Http\Response implements Http\ICallbackResponse {
|
return new class($closure) extends Http\Response implements Http\ICallbackResponse {
|
||||||
private \Closure $_closure;
|
/**
|
||||||
|
* @param \Closure(Http\IOutput): void $closure
|
||||||
public function __construct(\Closure $closure)
|
*/
|
||||||
|
public function __construct(private \Closure $closure)
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
$this->_closure = $closure;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function callback(Http\IOutput $output)
|
public function callback(Http\IOutput $output)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
($this->_closure)($output);
|
($this->closure)($output);
|
||||||
} catch (\OCA\Memories\HttpResponseException $e) {
|
} catch (\OCA\Memories\HttpResponseException $e) {
|
||||||
$res = $e->response;
|
$res = $e->response;
|
||||||
$output->setHttpResponseCode($res->getStatus());
|
$output->setHttpResponseCode($res->getStatus());
|
||||||
|
@ -73,12 +75,8 @@ trait UtilController
|
||||||
*/
|
*/
|
||||||
public static function getUser(): \OCP\IUser
|
public static function getUser(): \OCP\IUser
|
||||||
{
|
{
|
||||||
$user = \OC::$server->get(\OCP\IUserSession::class)->getUser();
|
return \OC::$server->get(\OCP\IUserSession::class)->getUser()
|
||||||
if (null === $user) {
|
?? throw Exceptions::NotLoggedIn();
|
||||||
throw Exceptions::NotLoggedIn();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $user;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -102,17 +100,15 @@ trait UtilController
|
||||||
/**
|
/**
|
||||||
* Get a user's home folder.
|
* Get a user's home folder.
|
||||||
*
|
*
|
||||||
* @param null|string $uid User ID, or null for current user
|
* @param null|string $uid User ID, or null for the user
|
||||||
*
|
*
|
||||||
* @throws \OCA\Memories\HttpResponseException if the user is not logged in
|
* @throws \OCA\Memories\HttpResponseException if the user is not logged in
|
||||||
*/
|
*/
|
||||||
public static function getUserFolder(?string $uid = null): \OCP\Files\Folder
|
public static function getUserFolder(?string $uid = null): \OCP\Files\Folder
|
||||||
{
|
{
|
||||||
if (null === $uid) {
|
return \OC::$server->get(\OCP\Files\IRootFolder::class)
|
||||||
$uid = self::getUID();
|
->getUserFolder($uid ?? self::getUID())
|
||||||
}
|
;
|
||||||
|
|
||||||
return \OC::$server->get(\OCP\Files\IRootFolder::class)->getUserFolder($uid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -124,14 +120,12 @@ trait UtilController
|
||||||
$config = \OC::$server->get(\OCP\IConfig::class);
|
$config = \OC::$server->get(\OCP\IConfig::class);
|
||||||
$default = $config->getSystemValue('default_language', 'en');
|
$default = $config->getSystemValue('default_language', 'en');
|
||||||
|
|
||||||
// Get UID of the user
|
|
||||||
try {
|
try {
|
||||||
$uid = self::getUID();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return 'en';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get language of the user
|
// Get language of the user
|
||||||
return $config->getUserValue($uid, 'core', 'lang', $default);
|
return $config->getUserValue(self::getUID(), 'core', 'lang', $default);
|
||||||
|
} catch (\Exception) {
|
||||||
|
// Fallback to server language
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue