diff --git a/lib/BackgroundJob/AddPhotoJob.php b/lib/BackgroundJob/AddPhotoJob.php index 53d42cdeb..d5fa308a2 100644 --- a/lib/BackgroundJob/AddPhotoJob.php +++ b/lib/BackgroundJob/AddPhotoJob.php @@ -15,13 +15,12 @@ use OCA\Maps\Service\PhotofilesService; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\QueuedJob; +use OCP\Files\File; use OCP\Files\IRootFolder; use OCP\ICache; use OCP\ICacheFactory; class AddPhotoJob extends QueuedJob { - private readonly IRootFolder $root; - private readonly ICacheFactory $cacheFactory; private readonly ICache $backgroundJobCache; /** @@ -31,23 +30,20 @@ class AddPhotoJob extends QueuedJob { */ public function __construct( ITimeFactory $timeFactory, - IRootFolder $root, + private readonly IRootFolder $root, private readonly PhotofilesService $photofilesService, - ICacheFactory $cacheFactory, + private readonly ICacheFactory $cacheFactory, ) { parent::__construct($timeFactory); - $this->root = $root; - $this->cacheFactory = $cacheFactory; $this->backgroundJobCache = $this->cacheFactory->createDistributed('maps:background-jobs'); } public function run($argument): void { $userFolder = $this->root->getUserFolder($argument['userId']); - $files = $userFolder->getById($argument['photoId']); - if (empty($files)) { + $file = $userFolder->getFirstNodeById($argument['photoId']); + if (!$file instanceof File) { return; } - $file = array_shift($files); $this->photofilesService->addPhotoNow($file, $argument['userId']); $counter = $this->backgroundJobCache->get('recentlyAdded:' . $argument['userId']) ?? 0; diff --git a/lib/BackgroundJob/LaunchUsersInstallScanJob.php b/lib/BackgroundJob/LaunchUsersInstallScanJob.php index 71f5de070..93d422b4c 100644 --- a/lib/BackgroundJob/LaunchUsersInstallScanJob.php +++ b/lib/BackgroundJob/LaunchUsersInstallScanJob.php @@ -30,12 +30,13 @@ public function __construct( ITimeFactory $timeFactory, private readonly IJobList $jobList, private readonly IUserManager $userManager, + private readonly LoggerInterface $logger, ) { parent::__construct($timeFactory); } public function run($argument): void { - \OCP\Server::get(LoggerInterface::class)->debug('Launch users install scan jobs cronjob executed'); + $this->logger->debug('Launch users install scan jobs cronjob executed'); $this->userManager->callForSeenUsers(function (IUser $user): void { $this->jobList->add(UserInstallScanJob::class, ['userId' => $user->getUID()]); }); diff --git a/lib/BackgroundJob/LookupMissingGeoJob.php b/lib/BackgroundJob/LookupMissingGeoJob.php index dfdcbf55f..fe81daa20 100644 --- a/lib/BackgroundJob/LookupMissingGeoJob.php +++ b/lib/BackgroundJob/LookupMissingGeoJob.php @@ -29,12 +29,13 @@ public function __construct( ITimeFactory $timeFactory, private readonly AddressService $addressService, private readonly IJobList $jobList, + private readonly LoggerInterface $logger, ) { parent::__construct($timeFactory); } public function run($argument): void { - \OCP\Server::get(LoggerInterface::class)->debug('Maps address lookup cronjob executed'); + $this->logger->debug('Maps address lookup cronjob executed'); // lookup at most 200 addresses if (!$this->addressService->lookupMissingGeo(200)) { // if not all addresses where looked up successfully add a new job for next time diff --git a/lib/BackgroundJob/UpdatePhotoByFileJob.php b/lib/BackgroundJob/UpdatePhotoByFileJob.php index 8edf71fb6..5a423bc2c 100644 --- a/lib/BackgroundJob/UpdatePhotoByFileJob.php +++ b/lib/BackgroundJob/UpdatePhotoByFileJob.php @@ -15,43 +15,36 @@ use OCA\Maps\Service\PhotofilesService; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\QueuedJob; +use OCP\Files\File; use OCP\Files\IRootFolder; use OCP\ICache; use OCP\ICacheFactory; class UpdatePhotoByFileJob extends QueuedJob { - private readonly IRootFolder $root; - - private readonly ICacheFactory $cacheFactory; - private readonly ICache $backgroundJobCache; /** * UserInstallScanJob constructor. * * A QueuedJob to scan user storage for photos and tracks - * */ public function __construct( ITimeFactory $timeFactory, - IRootFolder $root, + private readonly IRootFolder $root, private readonly PhotofilesService $photofilesService, ICacheFactory $cacheFactory, ) { parent::__construct($timeFactory); - $this->root = $root; - $this->cacheFactory = $cacheFactory; - $this->backgroundJobCache = $this->cacheFactory->createDistributed('maps:background-jobs'); + $this->backgroundJobCache = $cacheFactory->createDistributed('maps:background-jobs'); } public function run($argument): void { $userFolder = $this->root->getUserFolder($argument['userId']); - $files = $userFolder->getById($argument['fileId']); - if (empty($files)) { + $file = $userFolder->getFirstNodeById($argument['fileId']); + if (!$file instanceof File) { return; } - $file = array_shift($files); $this->photofilesService->updateByFileNow($file); $counter = $this->backgroundJobCache->get('recentlyUpdated:' . $argument['userId']) ?? 0; diff --git a/lib/BackgroundJob/UserInstallScanJob.php b/lib/BackgroundJob/UserInstallScanJob.php index 474a8c5b4..1cda26150 100644 --- a/lib/BackgroundJob/UserInstallScanJob.php +++ b/lib/BackgroundJob/UserInstallScanJob.php @@ -15,16 +15,11 @@ use OCA\Maps\Service\PhotofilesService; use OCA\Maps\Service\TracksService; use OCP\AppFramework\Utility\ITimeFactory; -use OCP\BackgroundJob\IJobList; use OCP\BackgroundJob\QueuedJob; use OCP\IConfig; -use OCP\IUserManager; use Psr\Log\LoggerInterface; class UserInstallScanJob extends QueuedJob { - - private readonly IConfig $config; - /** * UserInstallScanJob constructor. * @@ -32,26 +27,24 @@ class UserInstallScanJob extends QueuedJob { */ public function __construct( ITimeFactory $timeFactory, - IJobList $jobList, - IUserManager $userManager, - IConfig $config, + private readonly IConfig $config, private readonly PhotofilesService $photofilesService, private readonly TracksService $tracksService, + private readonly LoggerInterface $logger, ) { parent::__construct($timeFactory); - $this->config = $config; } public function run($argument): void { $userId = $argument['userId']; - \OCP\Server::get(LoggerInterface::class)->debug('Launch user install scan job for ' . $userId . ' cronjob executed'); + $this->logger->debug('Launch user install scan job for ' . $userId . ' cronjob executed'); // scan photos and tracks for given user $this->rescanUserPhotos($userId); $this->rescanUserTracks($userId); $this->config->setUserValue($userId, 'maps', 'installScanDone', 'yes'); } - private function rescanUserPhotos($userId): void { + private function rescanUserPhotos(string $userId): void { //$this->output->info('======== User '.$userId.' ========'."\n"); $c = 1; foreach ($this->photofilesService->rescan($userId) as $path) { diff --git a/lib/Command/RegisterMimetypes.php b/lib/Command/RegisterMimetypes.php index 28a9889fd..f4dd940f5 100644 --- a/lib/Command/RegisterMimetypes.php +++ b/lib/Command/RegisterMimetypes.php @@ -25,10 +25,7 @@ public function __construct( parent::__construct(); } - /** - * @return void - */ - protected function configure() { + protected function configure(): void { $this->setName('maps:register-mimetypes') ->setDescription('Registers the maps mimetypes for existing and new files.'); } diff --git a/lib/Command/RescanPhotos.php b/lib/Command/RescanPhotos.php index 7849b2dd2..219a2575c 100644 --- a/lib/Command/RescanPhotos.php +++ b/lib/Command/RescanPhotos.php @@ -24,28 +24,16 @@ use Symfony\Component\Console\Output\OutputInterface; class RescanPhotos extends Command { - - protected IUserManager $userManager; - protected OutputInterface $output; - protected IManager $encryptionManager; - protected IConfig $config; - public function __construct( - IUserManager $userManager, - IManager $encryptionManager, - protected PhotofilesService $photofilesService, - IConfig $config, + private readonly IUserManager $userManager, + private readonly IManager $encryptionManager, + private readonly PhotofilesService $photofilesService, + private readonly IConfig $config, ) { parent::__construct(); - $this->userManager = $userManager; - $this->encryptionManager = $encryptionManager; - $this->config = $config; } - /** - * @return void - */ - protected function configure() { + protected function configure(): void { $this->setName('maps:scan-photos') ->setDescription('Rescan photos GPS exif data') ->addArgument( @@ -71,7 +59,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('Encryption is enabled. Aborted.'); return 1; } - $this->output = $output; $userId = $input->getArgument('user_id'); $pathToScan = $input->getArgument('path'); $inBackground = !($input->getOption('now') ?? true); diff --git a/lib/Command/RescanTracks.php b/lib/Command/RescanTracks.php index 1826f9e71..fe846ffd9 100644 --- a/lib/Command/RescanTracks.php +++ b/lib/Command/RescanTracks.php @@ -24,23 +24,18 @@ class RescanTracks extends Command { - protected IUserManager $userManager; protected OutputInterface $output; - protected IManager $encryptionManager; - protected IConfig $config; public function __construct( - IUserManager $userManager, - IManager $encryptionManager, + private readonly IUserManager $userManager, + private readonly IManager $encryptionManager, protected TracksService $tracksService, - IConfig $config, + private readonly IConfig $config, ) { parent::__construct(); - $this->userManager = $userManager; - $this->encryptionManager = $encryptionManager; - $this->config = $config; } - protected function configure() { + + protected function configure(): void { $this->setName('maps:scan-tracks') ->setDescription('Rescan track files') ->addArgument( diff --git a/lib/Controller/FavoritesController.php b/lib/Controller/FavoritesController.php index 04e988bee..90905bfbf 100644 --- a/lib/Controller/FavoritesController.php +++ b/lib/Controller/FavoritesController.php @@ -23,6 +23,7 @@ use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Services\IAppConfig; +use OCP\Files\File; use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; @@ -415,27 +416,24 @@ public function importFavorites(string $path): DataResponse { $userFolder = $this->userFolder; $cleanpath = str_replace(['../', '..\\'], '', $path); - if ($userFolder->nodeExists($cleanpath)) { - $file = $userFolder->get($cleanpath); - if ($file->getType() === \OCP\Files\FileInfo::TYPE_FILE - and $file->isReadable()) { - $lowerFileName = strtolower((string)$file->getName()); - if (str_ends_with($lowerFileName, '.gpx') or str_ends_with($lowerFileName, '.kml') - || str_ends_with($lowerFileName, '.kmz') or str_ends_with($lowerFileName, '.json') - || str_ends_with($lowerFileName, '.geojson')) { - $result = $this->favoritesService->importFavorites($this->userId, $file); - return new DataResponse($result); - } else { - // invalid extension - return new DataResponse($this->l->t('Invalid file extension'), 400); - } - } else { - // directory or not readable - return new DataResponse($this->l->t('Impossible to read the file'), 400); - } - } else { - // does not exist + if (!$userFolder->nodeExists($cleanpath)) { return new DataResponse($this->l->t('File does not exist'), 400); } + + $file = $userFolder->get($cleanpath); + if (!$file instanceof File || !$file->isReadable()) { + return new DataResponse($this->l->t('Impossible to read the file'), 400); + } + + $lowerFileName = strtolower((string)$file->getName()); + if (str_ends_with($lowerFileName, '.gpx') or str_ends_with($lowerFileName, '.kml') + || str_ends_with($lowerFileName, '.kmz') or str_ends_with($lowerFileName, '.json') + || str_ends_with($lowerFileName, '.geojson')) { + $result = $this->favoritesService->importFavorites($this->userId, $file); + return new DataResponse($result); + } + + // invalid extension + return new DataResponse($this->l->t('Invalid file extension'), 400); } } diff --git a/lib/Controller/PublicTracksController.php b/lib/Controller/PublicTracksController.php index 9b945b1c1..98a5c032c 100644 --- a/lib/Controller/PublicTracksController.php +++ b/lib/Controller/PublicTracksController.php @@ -13,33 +13,30 @@ namespace OCA\Maps\Controller; use OCA\Maps\Service\TracksService; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Services\IInitialState; use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\File; +use OCP\Files\Folder; use OCP\Files\IRootFolder; +use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; use OCP\IAppConfig; use OCP\IGroupManager; use OCP\IL10N; use OCP\IRequest; -use OCP\IServerContainer; use OCP\ISession; use OCP\IURLGenerator; use OCP\IUserManager; -use OCP\Share; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager as ShareManager; +use OCP\Share\IShare; use function OCA\Maps\Helper\remove_utf8_bom; class PublicTracksController extends PublicPageController { - - protected ShareManager $shareManager; - protected IUserManager $userManager; - protected IL10N $l; - protected $appName; - protected IRootFolder $root; - public function __construct( string $appName, IRequest $request, @@ -50,23 +47,18 @@ public function __construct( ShareManager $shareManager, IUserManager $userManager, ISession $session, - IServerContainer $serverContainer, protected IGroupManager $groupManager, - IL10N $l, + protected readonly IL10N $l, protected TracksService $tracksService, - IRootFolder $root, + protected readonly IRootFolder $root, ) { parent::__construct($appName, $request, $session, $urlGenerator, $eventDispatcher, $appConfig, $initialState, $shareManager, $userManager); - $this->l = $l; - $this->root = $root; } /** * Validate the permissions of the share - * - * @return bool */ - private function validateShare(\OCP\Share\IShare $share) { + private function validateShare(IShare $share): bool { // If the owner is disabled no access to the link is granted $owner = $this->userManager->get($share->getShareOwner()); if ($owner === null || !$owner->isEnabled()) { @@ -83,15 +75,14 @@ private function validateShare(\OCP\Share\IShare $share) { } /** - * @return \OCP\Share\IShare * @throws NotFoundException */ - private function getShare() { + private function getShare(): IShare { // Check whether share exists try { $share = $this->shareManager->getShareByToken($this->getToken()); } catch (ShareNotFound) { - // The share does not exists, we do not emit an ShareLinkAccessedEvent + // The share does not exist, we do not emit an ShareLinkAccessedEvent throw new NotFoundException(); } @@ -102,10 +93,9 @@ private function getShare() { } /** - * @return \OCP\Files\File|\OCP\Files\Folder * @throws NotFoundException */ - private function getShareNode() { + private function getShareNode(): Node { \OC_User::setIncognitoMode(true); $share = $this->getShare(); @@ -114,18 +104,18 @@ private function getShareNode() { } /** - * @PublicPage * @throws NotFoundException * @throws NotPermittedException * @throws \OC\User\NoUserException */ + #[PublicPage] public function getTracks(): DataResponse { $share = $this->getShare(); - $hideDownload = (bool)$share->getHideDownload(); + $hideDownload = $share->getHideDownload(); $permissions = $share->getPermissions(); $folder = $this->getShareNode(); $isReadable = (bool)($permissions & (1 << 0)); - if ($isReadable) { + if ($isReadable && $folder instanceof Folder) { $owner = $share->getShareOwner(); $pre_path = $this->root->getUserFolder($owner)->getPath(); $tracks = $this->tracksService->getTracksFromDB($owner, $folder, true, false, false); @@ -145,131 +135,118 @@ public function getTracks(): DataResponse { } /** - * @PublicPage - * @param $id - * @return DataResponse * @throws NotFoundException * @throws NotPermittedException * @throws \OCP\Files\InvalidPathException */ - public function getTrackContentByFileId($id) { + #[PublicPage] + public function getTrackContentByFileId(int $id): DataResponse { $share = $this->getShare(); $permissions = $share->getPermissions(); $folder = $this->getShareNode(); + if (!$folder instanceof Folder) { + return new DataResponse($this->l->t('Share folder not found'), 400); + } + $isReadable = (bool)($permissions & (1 << 0)); if (!$isReadable) { throw new NotPermittedException(); } $owner = $share->getShareOwner(); $track = $this->tracksService->getTrackByFileIDFromDB($id, $owner); - $res = is_null($track) ? null : $folder->getById($track['file_id']); - if (is_array($res) and count($res) > 0) { - $trackFile = array_shift($res); - if ($trackFile->getType() === \OCP\Files\FileInfo::TYPE_FILE) { - $trackContent = remove_utf8_bom($trackFile->getContent()); - // compute metadata if necessary - // first time we get it OR the file changed - if (!$track['metadata'] || $track['etag'] !== $trackFile->getEtag()) { - $metadata = $this->tracksService->generateTrackMetadata($trackFile); - $this->tracksService->editTrackInDB($track['id'], null, $metadata, $trackFile->getEtag()); - } else { - $metadata = $track['metadata']; - } - return new DataResponse([ - 'metadata' => $metadata, - 'content' => $trackContent - ]); - } else { - return new DataResponse($this->l->t('Bad file type'), 400); - } - } else { + $trackFile = $folder->getFirstNodeById($track['file_id']); + if (!$trackFile instanceof File) { return new DataResponse($this->l->t('File not found'), 400); } + $trackContent = remove_utf8_bom($trackFile->getContent()); + // compute metadata if necessary + // first time we get it OR the file changed + if (!$track['metadata'] || $track['etag'] !== $trackFile->getEtag()) { + $metadata = $this->tracksService->generateTrackMetadata($trackFile); + $this->tracksService->editTrackInDB($track['id'], null, $metadata, $trackFile->getEtag()); + } else { + $metadata = $track['metadata']; + } + return new DataResponse([ + 'metadata' => $metadata, + 'content' => $trackContent + ]); } /** - * @PublicPage - * @param $id * @throws NotFoundException * @throws \OCP\Files\InvalidPathException */ - public function getTrackFileContent($id): DataResponse { + #[PublicPage] + public function getTrackFileContent(int $id): DataResponse { $track = $this->tracksService->getTrackFromDB($id); - $res = is_null($track) ? null : $this->getShareNode()->getById($track['file_id']); - if (is_array($res) and count($res) > 0) { - $trackFile = array_shift($res); - if ($trackFile->getType() === \OCP\Files\FileInfo::TYPE_FILE) { - $trackContent = remove_utf8_bom($trackFile->getContent()); - // compute metadata if necessary - // first time we get it OR the file changed - if (!$track['metadata'] || $track['etag'] !== $trackFile->getEtag()) { - $metadata = $this->tracksService->generateTrackMetadata($trackFile); - $this->tracksService->editTrackInDB($track['id'], null, $metadata, $trackFile->getEtag()); - } else { - $metadata = $track['metadata']; - } - return new DataResponse([ - 'metadata' => $metadata, - 'content' => $trackContent - ]); - } else { - return new DataResponse($this->l->t('Bad file type'), 400); - } - } else { + if ($track === null) { return new DataResponse($this->l->t('File not found'), 400); } + + $folder = $this->getShareNode(); + if (!$folder instanceof Folder) { + return new DataResponse($this->l->t('File not found'), 400); + } + + $trackFile = $folder->getFirstNodeById($track['file_id']); + if (!$trackFile instanceof File) { + return new DataResponse($this->l->t('Bad file type'), 400); + } + + $trackContent = remove_utf8_bom($trackFile->getContent()); + // compute metadata if necessary + // first time we get it OR the file changed + if (!$track['metadata'] || $track['etag'] !== $trackFile->getEtag()) { + $metadata = $this->tracksService->generateTrackMetadata($trackFile); + $this->tracksService->editTrackInDB($track['id'], null, $metadata, $trackFile->getEtag()); + } else { + $metadata = $track['metadata']; + } + return new DataResponse([ + 'metadata' => $metadata, + 'content' => $trackContent + ]); } /** - * @PublicPage - * @param $id - * @param $color - * @param $metadata - * @param $etag * @throws NotFoundException * @throws NotPermittedException */ - public function editTrack($id, $color, $metadata, $etag): DataResponse { + #[PublicPage] + public function editTrack(int $id, ?string $color, ?string $metadata, ?string $etag): DataResponse { $share = $this->getShare(); $permissions = $share->getPermissions(); $this->getShareNode(); $isUpdateable = (bool)($permissions & (1 << 1)); - if ($isUpdateable) { - $owner = $share->getShareOwner(); - $track = $this->tracksService->getTrackFromDB($id, $owner); - if ($track !== null) { - $this->tracksService->editTrackInDB($id, $color, $metadata, $etag); - return new DataResponse('EDITED'); - } else { - return new DataResponse($this->l->t('No such track'), 400); - } - } else { + if (!$isUpdateable) { throw new NotPermittedException(); } + $owner = $share->getShareOwner(); + $track = $this->tracksService->getTrackFromDB($id, $owner); + if ($track !== null) { + $this->tracksService->editTrackInDB($id, $color, $metadata, $etag); + return new DataResponse('EDITED'); + } + return new DataResponse($this->l->t('No such track'), 400); } - /** - * @NoAdminRequired - * @param $id - */ - public function deleteTrack($id): DataResponse { + #[NoAdminRequired] + public function deleteTrack(int $id): DataResponse { $share = $this->getShare(); $permissions = $share->getPermissions(); $this->getShareNode(); $isUpdateable = (bool)($permissions & (1 << 1)); - //It's allowed to delete a track from the share, if the share is updateable - if ($isUpdateable) { - $owner = $share->getShareOwner(); - $track = $this->tracksService->getTrackFromDB($id, $owner); - if ($track !== null) { - $this->tracksService->deleteTrackFromDB($id); - return new DataResponse('DELETED'); - } else { - return new DataResponse($this->l->t('No such track'), 400); - } - } else { + //It's allowed to delete a track from the share, if the share is updatable + if (!$isUpdateable) { throw new NotPermittedException(); } + $owner = $share->getShareOwner(); + $track = $this->tracksService->getTrackFromDB($id, $owner); + if ($track !== null) { + $this->tracksService->deleteTrackFromDB($id); + return new DataResponse('DELETED'); + } + return new DataResponse($this->l->t('No such track'), 400); } - } diff --git a/lib/Controller/RoutingController.php b/lib/Controller/RoutingController.php index 041ad83f5..e460d207c 100644 --- a/lib/Controller/RoutingController.php +++ b/lib/Controller/RoutingController.php @@ -12,83 +12,65 @@ namespace OCA\Maps\Controller; -use OCP\App\IAppManager; +use OCA\Maps\Service\TracksService; use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; -use OCP\IConfig; -use OCP\IDateTimeZone; -use OCP\IGroupManager; +use OCP\AppFramework\Services\IAppConfig; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; use OCP\IL10N; use OCP\IRequest; -use OCP\IServerContainer; -use OCP\IUserManager; -use OCP\Share\IManager; class RoutingController extends Controller { - - private $userfolder; - private $appVersion; - private $l; - protected $appName; + private Folder $userfolder; + private readonly string $appVersion; public function __construct( - $AppName, + string $appName, IRequest $request, - IServerContainer $serverContainer, - IConfig $config, - IManager $shareManager, - IAppManager $appManager, - IUserManager $userManager, - IGroupManager $groupManager, - IL10N $l, - IDateTimeZone $dateTimeZone, - private readonly \OCA\Maps\Service\TracksService $tracksService, - private $userId, + IRootFolder $rootFolder, + IAppConfig $config, + private readonly IL10N $l, + private readonly TracksService $tracksService, + private readonly ?string $userId, ) { - parent::__construct($AppName, $request); - $this->appName = $AppName; - $this->appVersion = $config->getAppValue('maps', 'installed_version'); - $this->l = $l; - if ($this->userId !== '' and $this->userId !== null and $serverContainer !== null) { + parent::__construct($appName, $request); + $this->appVersion = $config->getAppValueString('maps', 'installed_version'); + if ($this->userId !== '' && $this->userId !== null) { // path of user files folder relative to DATA folder - $this->userfolder = $serverContainer->getUserFolder($this->userId); + $this->userfolder = $rootFolder->getUserFolder($this->userId); } } /** - * @NoAdminRequired - * @param $type + * @param 'route'|'track' $type * @param $coords - * @param $name * @param $totDist * @param $totTime * @throws \OCP\Files\NotFoundException * @throws \OCP\Files\NotPermittedException */ - public function exportRoute($type, $coords, $name, $totDist, $totTime, $myMapId = null): DataResponse { + #[NoAdminRequired] + public function exportRoute(string $type, $coords, string $name, $totDist, $totTime, ?int $myMapId = null): DataResponse { // create /Maps directory if necessary $userFolder = $this->userfolder; - if (is_null($myMapId) || $myMapId === '') { - if (!$userFolder->nodeExists('/Maps')) { - $userFolder->newFolder('Maps'); - } - if ($userFolder->nodeExists('/Maps')) { - $mapsFolder = $userFolder->get('/Maps'); - if ($mapsFolder->getType() !== \OCP\Files\FileInfo::TYPE_FOLDER) { - return new DataResponse($this->l->t('/Maps is not a directory'), 400); - } elseif (!$mapsFolder->isCreatable()) { - return new DataResponse($this->l->t('/Maps directory is not writeable'), 400); + if (is_null($myMapId) || $myMapId === 0) { + try { + /** @var Folder $mapsFolder */ + $mapsFolder = $userFolder->get('Maps'); + } catch (NotFoundException) { + try { + $mapsFolder = $userFolder->newFolder('Maps'); + } catch (NotPermittedException) { + return new DataResponse($this->l->t('Impossible to create /Maps directory'), 400); } - } else { - return new DataResponse($this->l->t('Impossible to create /Maps directory'), 400); } } else { - $folders = $userFolder->getById($myMapId); - if (!is_array($folders) or count($folders) === 0) { - return new DataResponse('myMaps Folder not found', 404); - } - $mapsFolder = array_shift($folders); - if (is_null($mapsFolder)) { + $mapsFolder = $userFolder->getFirstNodeById($myMapId); + if (!$mapsFolder instanceof Folder) { return new DataResponse('myMaps Folder not found', 404); } } diff --git a/lib/Controller/TracksController.php b/lib/Controller/TracksController.php index a2de6886a..db7a68e5c 100644 --- a/lib/Controller/TracksController.php +++ b/lib/Controller/TracksController.php @@ -16,6 +16,7 @@ use OCP\AppFramework\Controller; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; +use OCP\Files\File; use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\IL10N; @@ -65,23 +66,22 @@ public function getTrackContentByFileId(int $id): DataResponse { $res = is_null($track) ? null : $this->userfolder->getById($track['file_id']); if (is_array($res) and count($res) > 0) { $trackFile = $res[0]; - if ($trackFile->getType() === \OCP\Files\FileInfo::TYPE_FILE) { - $trackContent = remove_utf8_bom($trackFile->getContent()); - // compute metadata if necessary - // first time we get it OR the file changed - if (!$track['metadata'] || $track['etag'] !== $trackFile->getEtag()) { - $metadata = $this->tracksService->generateTrackMetadata($trackFile); - $this->tracksService->editTrackInDB($track['id'], null, $metadata, $trackFile->getEtag()); - } else { - $metadata = $track['metadata']; - } - return new DataResponse([ - 'metadata' => $metadata, - 'content' => $trackContent - ]); - } else { + if (!$trackFile instanceof File) { return new DataResponse($this->l->t('Bad file type'), 400); } + $trackContent = remove_utf8_bom($trackFile->getContent()); + // compute metadata if necessary + // first time we get it OR the file changed + if (!$track['metadata'] || $track['etag'] !== $trackFile->getEtag()) { + $metadata = $this->tracksService->generateTrackMetadata($trackFile); + $this->tracksService->editTrackInDB($track['id'], null, $metadata, $trackFile->getEtag()); + } else { + $metadata = $track['metadata']; + } + return new DataResponse([ + 'metadata' => $metadata, + 'content' => $trackContent + ]); } else { return new DataResponse($this->l->t('File not found'), 400); } @@ -97,23 +97,23 @@ public function getTrackFileContent(int $id): DataResponse { $res = is_null($track) ? null : $this->userfolder->getById($track['file_id']); if (is_array($res) and count($res) > 0) { $trackFile = $res[0]; - if ($trackFile->getType() === \OCP\Files\FileInfo::TYPE_FILE) { - $trackContent = remove_utf8_bom($trackFile->getContent()); - // compute metadata if necessary - // first time we get it OR the file changed - if (!$track['metadata'] || $track['etag'] !== $trackFile->getEtag()) { - $metadata = $this->tracksService->generateTrackMetadata($trackFile); - $this->tracksService->editTrackInDB($track['id'], null, $metadata, $trackFile->getEtag()); - } else { - $metadata = $track['metadata']; - } - return new DataResponse([ - 'metadata' => $metadata, - 'content' => $trackContent - ]); - } else { + if (!$trackFile instanceof File) { return new DataResponse($this->l->t('Bad file type'), 400); } + + $trackContent = remove_utf8_bom($trackFile->getContent()); + // compute metadata if necessary + // first time we get it OR the file changed + if (!$track['metadata'] || $track['etag'] !== $trackFile->getEtag()) { + $metadata = $this->tracksService->generateTrackMetadata($trackFile); + $this->tracksService->editTrackInDB($track['id'], null, $metadata, $trackFile->getEtag()); + } else { + $metadata = $track['metadata']; + } + return new DataResponse([ + 'metadata' => $metadata, + 'content' => $trackContent + ]); } else { return new DataResponse($this->l->t('File not found'), 400); } @@ -140,5 +140,4 @@ public function deleteTrack(int $id): DataResponse { return new DataResponse($this->l->t('No such track'), 400); } } - } diff --git a/lib/Service/PhotofilesService.php b/lib/Service/PhotofilesService.php index 40a90337a..807ecf6b3 100644 --- a/lib/Service/PhotofilesService.php +++ b/lib/Service/PhotofilesService.php @@ -15,18 +15,17 @@ use OCA\Maps\BackgroundJob\AddPhotoJob; use OCA\Maps\BackgroundJob\UpdatePhotoByFileJob; use OCA\Maps\DB\Geophoto; +use OCA\Maps\DB\GeophotoMapper; use OCA\Maps\Helper\ExifDataInvalidException; use OCA\Maps\Helper\ExifDataNoLocationException; use OCA\Maps\Helper\ExifGeoData; use OCP\AppFramework\Db\DoesNotExistException; use OCP\BackgroundJob\IJobList; use OCP\Files\File; -use OCP\Files\FileInfo; use OCP\Files\Folder; use OCP\Files\IRootFolder; -use OCP\Files\Node; +use OCP\ICache; use OCP\ICacheFactory; -use OCP\IL10N; use OCP\Share\IManager; use Psr\Log\LoggerInterface; @@ -42,8 +41,8 @@ use lsolesen\pel\PelTiff; class PhotofilesService { - private readonly \OCP\ICache $photosCache; - private readonly \OCP\ICache $backgroundJobCache; + private readonly ICache $photosCache; + private readonly ICache $backgroundJobCache; public const PHOTO_MIME_TYPES = ['image/jpeg', 'image/tiff', 'image/heic']; @@ -51,8 +50,7 @@ public function __construct( private readonly LoggerInterface $logger, private readonly ICacheFactory $cacheFactory, private readonly IRootFolder $root, - private readonly IL10N $l10n, - private readonly \OCA\Maps\DB\GeophotoMapper $photoMapper, + private readonly GeophotoMapper $photoMapper, private readonly IManager $shareManager, private readonly IJobList $jobList, ) { @@ -60,7 +58,7 @@ public function __construct( $this->backgroundJobCache = $this->cacheFactory->createDistributed('maps:background-jobs'); } - public function rescan($userId, $inBackground = true, $pathToScan = null) { + public function rescan(string $userId, bool $inBackground = true, $pathToScan = null) { $this->photosCache->clear($userId); $userFolder = $this->root->getUserFolder($userId); if ($pathToScan === null) { @@ -82,7 +80,7 @@ public function rescan($userId, $inBackground = true, $pathToScan = null) { // add the file for its owner and users that have access // check if it's already in DB before adding - public function addByFile(Node $file): bool { + public function addByFile(File $file): bool { if ($this->isPhoto($file)) { $ownerId = $file->getOwner()->getUID(); $this->addPhoto($file, $ownerId); @@ -101,34 +99,8 @@ public function addByFile(Node $file): bool { } } - public function addByFileIdUserId($fileId, $userId): void { - $userFolder = $this->root->getUserFolder($userId); - $files = $userFolder->getById($fileId); - if (empty($files)) { - return; - } - $file = array_shift($files); - if ($file !== null and $this->isPhoto($file)) { - $this->addPhoto($file, $userId); - } - } - - public function addByFolderIdUserId($folderId, $userId): void { - $folders = $this->root->getById($folderId); - if (empty($folders)) { - return; - } - $folder = array_shift($folders); - if ($folder !== null) { - $photos = $this->gatherPhotoFiles($folder, true); - foreach ($photos as $photo) { - $this->addPhoto($photo, $userId); - } - } - } - // add all photos of a folder taking care of shared accesses - public function addByFolder($folder): void { + public function addByFolder(Folder $folder): void { $photos = $this->gatherPhotoFiles($folder, true); foreach ($photos as $photo) { $this->addByFile($photo); @@ -139,7 +111,7 @@ public function updateByFile(File $file): void { $this->jobList->add(UpdatePhotoByFileJob::class, ['fileId' => $file->getId(), 'userId' => $file->getOwner()->getUID()]); } - public function updateByFileNow(Node $file): void { + public function updateByFileNow(File $file): void { if ($this->isPhoto($file)) { $exif = $this->getExif($file); if (!is_null($exif)) { @@ -156,7 +128,7 @@ public function updateByFileNow(Node $file): void { } } - public function deleteByFile(Node $file): void { + public function deleteByFile(File $file): void { $this->photoMapper->deleteByFileId($file->getId()); } @@ -171,7 +143,7 @@ public function deleteByFileIdUserId($fileId, $userId): void { } } - public function deleteByFolder(Node $folder): void { + public function deleteByFolder(Folder $folder): void { $photos = $this->gatherPhotoFiles($folder, true); foreach ($photos as $photo) { $this->photoMapper->deleteByFileId($photo->getId()); @@ -179,11 +151,10 @@ public function deleteByFolder(Node $folder): void { } // delete folder photos only if it's not accessible to user anymore - public function deleteByFolderIdUserId($folderId, $userId): void { + public function deleteByFolderIdUserId(int $folderId, string $userId): void { $userFolder = $this->root->getUserFolder($userId); - $folders = $userFolder->getById($folderId); - if (is_array($folders) and count($folders) === 1) { - $folder = array_shift($folders); + $folder = $userFolder->getFirstNodeById($folderId); + if ($folder instanceof Folder) { $photos = $this->gatherPhotoFiles($folder, true); foreach ($photos as $photo) { $this->photoMapper->deleteByFileIdUserId($photo->getId(), $userId); @@ -192,9 +163,6 @@ public function deleteByFolderIdUserId($folderId, $userId): void { } } - /** - * @param $userId - */ public function getBackgroundJobStatus(string $userId): array { $add_counter = 0; $addJobsRunning = false; @@ -226,7 +194,7 @@ public function getBackgroundJobStatus(string $userId): array { ]; } - public function setPhotosFilesCoords($userId, $paths, $lats, $lngs, $directory): array { + public function setPhotosFilesCoords(string $userId, $paths, $lats, $lngs, $directory): array { if ($directory) { return $this->setDirectoriesCoords($userId, $paths, $lats, $lngs); } else { @@ -237,7 +205,7 @@ public function setPhotosFilesCoords($userId, $paths, $lats, $lngs, $directory): /** * @return array{path: (array | string | null), lat: mixed, lng: mixed, oldLat: mixed, oldLng: mixed}[] */ - private function setDirectoriesCoords($userId, $paths, $lats, $lngs): array { + private function setDirectoriesCoords(string $userId, $paths, $lats, $lngs): array { $lat = $lats[0] ?? 0; $lng = $lngs[0] ?? 0; $userFolder = $this->root->getUserFolder($userId); @@ -246,7 +214,7 @@ private function setDirectoriesCoords($userId, $paths, $lats, $lngs): array { $cleanDirPath = str_replace(['../', '..\\'], '', $dirPath); if ($userFolder->nodeExists($cleanDirPath)) { $dir = $userFolder->get($cleanDirPath); - if ($dir->getType() === FileInfo::TYPE_FOLDER) { + if ($dir instanceof Folder) { $nodes = $dir->getDirectoryListing(); foreach ($nodes as $node) { if ($this->isPhoto($node) && $node->isUpdateable()) { @@ -271,7 +239,7 @@ private function setDirectoriesCoords($userId, $paths, $lats, $lngs): array { /** * @return array{path: (array | string | null), lat: mixed, lng: mixed, oldLat: mixed, oldLng: mixed}[] */ - private function setFilesCoords($userId, $paths, array $lats, array $lngs): array { + private function setFilesCoords(string $userId, $paths, array $lats, array $lngs): array { $userFolder = $this->root->getUserFolder($userId); $done = []; @@ -279,7 +247,7 @@ private function setFilesCoords($userId, $paths, array $lats, array $lngs): arra $cleanpath = str_replace(['../', '..\\'], '', $path); if ($userFolder->nodeExists($cleanpath)) { $file = $userFolder->get($cleanpath); - if ($this->isPhoto($file) && $file->isUpdateable()) { + if ($file instanceof File && $this->isPhoto($file) && $file->isUpdateable()) { $lat = (count($lats) > $i) ? $lats[$i] : $lats[0]; $lng = (count($lngs) > $i) ? $lngs[$i] : $lngs[0]; try { @@ -305,7 +273,7 @@ private function setFilesCoords($userId, $paths, array $lats, array $lngs): arra /** * @return array{path: (array | string | null), lat: null, lng: null, oldLat: mixed, oldLng: mixed}[] */ - public function resetPhotosFilesCoords($userId, $paths): array { + public function resetPhotosFilesCoords(string $userId, array $paths): array { $userFolder = $this->root->getUserFolder($userId); $done = []; @@ -313,7 +281,7 @@ public function resetPhotosFilesCoords($userId, $paths): array { $cleanpath = str_replace(['../', '..\\'], '', $path); if ($userFolder->nodeExists($cleanpath)) { $file = $userFolder->get($cleanpath); - if ($this->isPhoto($file) && $file->isUpdateable()) { + if ($file instanceof File && $this->isPhoto($file) && $file->isUpdateable()) { $photo = $this->photoMapper->findByFileIdUserId($file->getId(), $userId); $done[] = [ 'path' => preg_replace('/^files/', '', (string)$file->getInternalPath()), @@ -331,27 +299,29 @@ public function resetPhotosFilesCoords($userId, $paths): array { } // avoid adding photo if it already exists in the DB - private function addPhoto($photo, $userId): void { + private function addPhoto(File $photo, string $userId): void { $this->jobList->add(AddPhotoJob::class, ['photoId' => $photo->getId(), 'userId' => $userId]); } - public function addPhotoNow($photo, $userId): void { + public function addPhotoNow(File $photo, string $userId): void { $exif = $this->getExif($photo); - if (!is_null($exif)) { - // filehooks are triggered several times (2 times for file creation) - // so we need to be sure it's not inserted several times - // by checking if it already exists in DB - // OR by using file_id in primary key - try { - $this->photoMapper->findByFileIdUserId($photo->getId(), $userId); - } catch (DoesNotExistException) { - $this->insertPhoto($photo, $userId, $exif); - } - $this->photosCache->clear($userId); + if ($exif === null) { + return; } + + // filehooks are triggered several times (2 times for file creation) + // so we need to be sure it's not inserted several times + // by checking if it already exists in DB + // OR by using file_id in primary key + try { + $this->photoMapper->findByFileIdUserId($photo->getId(), $userId); + } catch (DoesNotExistException) { + $this->insertPhoto($photo, $userId, $exif); + } + $this->photosCache->clear($userId); } - private function insertPhoto($photo, $userId, \OCA\Maps\Helper\ExifGeoData $exif): void { + private function insertPhoto(File $photo, string $userId, ExifGeoData $exif): void { $photoEntity = new Geophoto(); $photoEntity->setFileId($photo->getId()); $photoEntity->setLat( @@ -368,45 +338,20 @@ private function insertPhoto($photo, $userId, \OCA\Maps\Helper\ExifGeoData $exif $this->photosCache->clear($userId); } - private function updatePhoto($file, \OCA\Maps\Helper\ExifGeoData $exif): void { + private function updatePhoto($file, ExifGeoData $exif): void { $lat = is_numeric($exif->lat) && !is_nan($exif->lat) ? $exif->lat : null; $lng = is_numeric($exif->lng) && !is_nan($exif->lng) ? $exif->lng : null; $this->photoMapper->updateByFileId($file->getId(), $lat, $lng); } - private function normalizePath($node): string|array { - return str_replace('files', '', $node->getInternalPath()); - } - - public function getPhotosByFolder($userId, $path): array { - $userFolder = $this->root->getUserFolder($userId); - $folder = $userFolder->get($path); - return $this->getPhotosListForFolder($folder); - } - /** - * @return \stdClass[] + * @return list */ - private function getPhotosListForFolder($folder): array { - $FilesList = $this->gatherPhotoFiles($folder, false); - $notes = []; - foreach ($FilesList as $File) { - $file_object = new \stdClass(); - $file_object->fileId = $File->getId(); - $file_object->path = $this->normalizePath($File); - $notes[] = $file_object; - } - return $notes; - } - - /** - * @return mixed[] - */ - private function gatherPhotoFiles($folder, $recursive): array { + private function gatherPhotoFiles(Folder $folder, bool $recursive): array { $notes = []; $nodes = $folder->getDirectoryListing(); foreach ($nodes as $node) { - if ($node->getType() === FileInfo::TYPE_FOLDER and $recursive) { + if ($node instanceof Folder and $recursive) { // we don't explore external storages for which previews are disabled if ($node->isMounted()) { $options = $node->getMountPoint()->getOptions(); @@ -423,17 +368,14 @@ private function gatherPhotoFiles($folder, $recursive): array { } continue; } - if ($this->isPhoto($node)) { + if ($node instanceof File && $this->isPhoto($node)) { $notes[] = $node; } } return $notes; } - private function isPhoto($file): bool { - if ($file->getType() !== \OCP\Files\FileInfo::TYPE_FILE) { - return false; - } + private function isPhoto(File $file): bool { if (!in_array($file->getMimetype(), self::PHOTO_MIME_TYPES)) { return false; } @@ -443,10 +385,8 @@ private function isPhoto($file): bool { /** * Get exif geo Data object * returns with null in any validation or Critical errors - * - * @param $file */ - private function getExif($file) : ?ExifGeoData { + private function getExif(File $file) : ?ExifGeoData { $path = $file->getStorage()->getLocalFile($file->getInternalPath()); try { $exif_geo_data = ExifGeoData::get($path); @@ -463,7 +403,7 @@ private function getExif($file) : ?ExifGeoData { return $exif_geo_data; } - private function resetExifCoords($file): void { + private function resetExifCoords(File $file): void { $data = new PelDataWindow($file->getContent()); $pelJpeg = new PelJpeg($data); @@ -491,7 +431,7 @@ private function resetExifCoords($file): void { $file->putContent($pelJpeg->getBytes()); } - private function setExifCoords($file, $lat, $lng): void { + private function setExifCoords(File $file, $lat, $lng): void { $data = new PelDataWindow($file->getContent()); $pelJpeg = new PelJpeg($data); diff --git a/lib/Service/TracksService.php b/lib/Service/TracksService.php index d02bef19c..84597f094 100644 --- a/lib/Service/TracksService.php +++ b/lib/Service/TracksService.php @@ -201,7 +201,7 @@ private function dbRowToTrack(array $row, $folder, $userFolder, bool $defaultMap return null; } $file = array_shift($files); - if ($file === null || $file->getType() !== \OCP\Files\FileInfo::TYPE_FILE) { + if (!$file instanceof File) { if ($defaultMap) { $this->deleteTrackFromDB($row['id']); } diff --git a/package-lock.json b/package-lock.json index 235f929b3..63c0e71de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@nextcloud/paths": "^2.1.0", "@nextcloud/router": "^3.0.1", "@nextcloud/sharing": "^0.2.5", - "@nextcloud/vue": "^9.5.0", + "@nextcloud/vue": "^9.8.2", "@raruto/leaflet-elevation": "^2.5.2", "axios": "^1.17.0", "geojson": "^0.5.0", diff --git a/package.json b/package.json index 12405fad2..17899ac1e 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@nextcloud/paths": "^2.1.0", "@nextcloud/router": "^3.0.1", "@nextcloud/sharing": "^0.2.5", - "@nextcloud/vue": "^9.5.0", + "@nextcloud/vue": "^9.8.2", "@raruto/leaflet-elevation": "^2.5.2", "axios": "^1.17.0", "geojson": "^0.5.0", diff --git a/tests/Unit/Controller/PhotosControllerTest.php b/tests/Unit/Controller/PhotosControllerTest.php index b6bfdc921..4ac002cf0 100644 --- a/tests/Unit/Controller/PhotosControllerTest.php +++ b/tests/Unit/Controller/PhotosControllerTest.php @@ -22,10 +22,11 @@ use OCP\IGroupManager; use OCP\IRequest; use OCP\IUserManager; -use OCP\L10N\IFactory; use OCP\Server; +use OCP\Share\IManager; use PHPUnit\Framework\MockObject\MockObject; use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; class PhotosControllerTest extends \PHPUnit\Framework\TestCase { private string $appName; @@ -80,12 +81,11 @@ protected function setUp(): void { $this->GeoPhotosService = $c->get(GeoPhotoService::class); $this->photoFileService = new PhotoFilesService( - $c->get(\Psr\Log\LoggerInterface::class), + $c->get(LoggerInterface::class), $c->get(ICacheFactory::class), $this->rootFolder, - $c->get(IFactory::class)->get('maps'), $c->get(GeophotoMapper::class), - $c->get(\OCP\Share\IManager::class), + $c->get(IManager::class), $c->get(\OCP\BackgroundJob\IJobList::class) ); @@ -135,8 +135,6 @@ protected function tearDown(): void { } public function testAddGetPhotos(): void { - $this->app->getContainer(); - $userfolder = $this->container->get(IRootFolder::class)->getUserFolder('test'); $filename = 'tests/test_files/nc.jpg';