diff --git a/appinfo/info.xml b/appinfo/info.xml
index cbcefa5bc..677c63729 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -32,7 +32,6 @@
OCA\Photos\Command\UpdateReverseGeocodingFilesCommand
- OCA\Photos\Command\MapMediaToPlaceCommand
@@ -48,4 +47,4 @@
OCA\Photos\Jobs\AutomaticPlaceMapperJob
-
+
\ No newline at end of file
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index a0f50f199..5c4523b72 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -28,15 +28,19 @@
use OCA\DAV\Connector\Sabre\Principal;
use OCA\DAV\Events\SabrePluginAuthInitEvent;
use OCA\Photos\Listener\AlbumsManagementEventListener;
-use OCA\Photos\Listener\PlaceManagerEventListener;
use OCA\Photos\Listener\SabrePluginAuthInitListener;
use OCA\Photos\Listener\TagListener;
+use OCA\Photos\MetadataProvider\ExifMetadataProvider;
+use OCA\Photos\MetadataProvider\OriginalDateTimeMetadataProvider;
+use OCA\Photos\MetadataProvider\PlaceMetadataProvider;
+use OCA\Photos\MetadataProvider\SizeMetadataProvider;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\Files\Events\Node\NodeDeletedEvent;
-use OCP\Files\Events\Node\NodeWrittenEvent;
+use OCP\FilesMetadata\Event\MetadataBackgroundEvent;
+use OCP\FilesMetadata\Event\MetadataLiveEvent;
use OCP\Group\Events\GroupDeletedEvent;
use OCP\Group\Events\UserRemovedEvent;
use OCP\Share\Events\ShareDeletedEvent;
@@ -75,8 +79,12 @@ public function register(IRegistrationContext $context): void {
/** Register $principalBackend for the DAV collection */
$context->registerServiceAlias('principalBackend', Principal::class);
- // Priority of -1 to be triggered after event listeners populating metadata.
- $context->registerEventListener(NodeWrittenEvent::class, PlaceManagerEventListener::class, -1);
+ // Metadata
+ $context->registerEventListener(MetadataLiveEvent::class, ExifMetadataProvider::class);
+ $context->registerEventListener(MetadataLiveEvent::class, SizeMetadataProvider::class);
+ $context->registerEventListener(MetadataLiveEvent::class, OriginalDateTimeMetadataProvider::class);
+ $context->registerEventListener(MetadataLiveEvent::class, PlaceMetadataProvider::class);
+ $context->registerEventListener(MetadataBackgroundEvent::class, PlaceMetadataProvider::class);
$context->registerEventListener(NodeDeletedEvent::class, AlbumsManagementEventListener::class);
$context->registerEventListener(UserRemovedEvent::class, AlbumsManagementEventListener::class);
diff --git a/lib/Command/MapMediaToPlaceCommand.php b/lib/Command/MapMediaToPlaceCommand.php
deleted file mode 100644
index 2c95d8bc8..000000000
--- a/lib/Command/MapMediaToPlaceCommand.php
+++ /dev/null
@@ -1,116 +0,0 @@
-
- *
- * @author Louis Chemineau
- *
- * @license AGPL-3.0-or-later
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- *
- */
-namespace OCA\Photos\Command;
-
-use OCA\Photos\Service\MediaPlaceManager;
-use OCP\Files\Folder;
-use OCP\Files\IRootFolder;
-use OCP\IConfig;
-use OCP\IUserManager;
-use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Input\InputOption;
-use Symfony\Component\Console\Output\OutputInterface;
-
-class MapMediaToPlaceCommand extends Command {
- public function __construct(
- private IRootFolder $rootFolder,
- private MediaPlaceManager $mediaPlaceManager,
- private IConfig $config,
- private IUserManager $userManager,
- ) {
- parent::__construct();
- }
-
- /**
- * Configure the command
- */
- protected function configure(): void {
- $this->setName('photos:map-media-to-place')
- ->setDescription('Reverse geocode media coordinates.')
- ->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'Limit the mapping to a user.', null);
- }
-
- /**
- * Execute the command
- */
- protected function execute(InputInterface $input, OutputInterface $output): int {
- if (!$this->config->getSystemValueBool('enable_file_metadata', true)) {
- throw new \Exception('File metadata is not enabled.');
- }
-
- $userId = $input->getOption('user');
- if ($userId === null) {
- $this->scanForAllUsers($output);
- } else {
- $this->scanFilesForUser($userId, $output);
- }
-
- return 0;
- }
-
- private function scanForAllUsers(OutputInterface $output): void {
- $users = $this->userManager->search('');
-
- $output->writeln("Scanning all users:");
- foreach ($users as $user) {
- $this->scanFilesForUser($user->getUID(), $output);
- }
- }
-
- private function scanFilesForUser(string $userId, OutputInterface $output): void {
- $userFolder = $this->rootFolder->getUserFolder($userId);
- $output->write(" - Scanning files for $userId");
- $startTime = time();
- $count = $this->scanFolder($userFolder);
- $timeElapse = time() - $startTime;
- $output->writeln(" - $count files, $timeElapse sec");
- }
-
- private function scanFolder(Folder $folder): int {
- $count = 0;
-
- // Do not scan share and other moveable mounts.
- if ($folder->getMountPoint() instanceof \OC\Files\Mount\MoveableMount) {
- return $count;
- }
-
- foreach ($folder->getDirectoryListing() as $node) {
- if ($node instanceof Folder) {
- $count += $this->scanFolder($node);
- continue;
- }
-
- if (!str_starts_with($node->getMimeType(), 'image')) {
- continue;
- }
-
- $this->mediaPlaceManager->setPlaceForFile($node->getId());
- $count++;
- }
-
- return $count;
- }
-}
diff --git a/lib/DB/PhotosFile.php b/lib/DB/PhotosFile.php
index 7a3ab90a9..982237d1f 100644
--- a/lib/DB/PhotosFile.php
+++ b/lib/DB/PhotosFile.php
@@ -25,12 +25,7 @@
namespace OCA\Photos\DB;
-use OC\Metadata\FileMetadata;
-
class PhotosFile {
- /** @var array */
- private array $metaData = [];
-
public function __construct(
private int $fileId,
private string $name,
@@ -64,16 +59,4 @@ public function getMTime(): int {
public function getEtag(): string {
return $this->etag;
}
-
- public function setMetadata(string $key, FileMetadata $value): void {
- $this->metaData[$key] = $value;
- }
-
- public function hasMetadata(string $key): bool {
- return isset($this->metaData[$key]);
- }
-
- public function getMetadata(string $key): FileMetadata {
- return $this->metaData[$key];
- }
}
diff --git a/lib/DB/Place/PlaceMapper.php b/lib/DB/Place/PlaceMapper.php
index 7147dc8d7..eef5fe322 100644
--- a/lib/DB/Place/PlaceMapper.php
+++ b/lib/DB/Place/PlaceMapper.php
@@ -25,20 +25,22 @@
namespace OCA\Photos\DB\Place;
-use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
+use OCA\Photos\AppInfo\Application;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\IMimeTypeLoader;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
+use OCP\FilesMetadata\IFilesMetadataManager;
use OCP\IDBConnection;
class PlaceMapper {
- public const METADATA_GROUP = 'photos_place';
+ public const METADATA_KEY = 'photos-place';
public function __construct(
private IDBConnection $connection,
private IMimeTypeLoader $mimeTypeLoader,
private IRootFolder $rootFolder,
+ private IFilesMetadataManager $filesMetadataManager,
) {
}
@@ -49,20 +51,21 @@ public function findPlacesForUser(string $userId): array {
->getMountPoint()
->getNumericStorageId();
- $mimepart = $this->mimeTypeLoader->getId('image');
+
+ $mimetypes = array_map(fn ($mimetype) => $this->mimeTypeLoader->getId($mimetype), Application::IMAGE_MIMES);
$qb = $this->connection->getQueryBuilder();
- $rows = $qb->selectDistinct('meta.value')
- ->from('file_metadata', 'meta')
- ->join('meta', 'filecache', 'file', $qb->expr()->eq('file.fileid', 'meta.id', IQueryBuilder::PARAM_INT))
- ->where($qb->expr()->eq('file.storage', $qb->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
- ->andWhere($qb->expr()->eq('file.mimepart', $qb->createNamedParameter($mimepart, IQueryBuilder::PARAM_INT)))
- ->andWhere($qb->expr()->eq('meta.group_name', $qb->createNamedParameter(self::METADATA_GROUP)))
+ $qb->selectDistinct('meta_value')
+ ->from('filecache', 'file');
+ $metadataQuery = $this->filesMetadataManager->getMetadataQuery($qb, 'file', 'fileid');
+ $metadataQuery->joinIndex(self::METADATA_KEY, true);
+ $rows = $qb->where($qb->expr()->eq('file.storage', $qb->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
+ ->andWhere($qb->expr()->in('file.mimetype', $qb->createNamedParameter($mimetypes, IQueryBuilder::PARAM_INT_ARRAY)))
->executeQuery()
->fetchAll();
- return array_map(fn ($row) => new PlaceInfo($userId, $row['value']), $rows);
+ return array_map(fn ($row) => new PlaceInfo($userId, $row['meta_value']), $rows);
}
/** @return PlaceInfo */
@@ -72,17 +75,17 @@ public function findPlaceForUser(string $userId, string $place): PlaceInfo {
->getMountPoint()
->getNumericStorageId();
- $mimepart = $this->mimeTypeLoader->getId('image');
+ $mimetypes = array_map(fn ($mimetype) => $this->mimeTypeLoader->getId($mimetype), Application::IMAGE_MIMES);
$qb = $this->connection->getQueryBuilder();
- $rows = $qb->selectDistinct('meta.value')
- ->from('file_metadata', 'meta')
- ->join('meta', 'filecache', 'file', $qb->expr()->eq('file.fileid', 'meta.id', IQueryBuilder::PARAM_INT))
- ->where($qb->expr()->eq('file.storage', $qb->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
- ->andWhere($qb->expr()->eq('file.mimepart', $qb->createNamedParameter($mimepart, IQueryBuilder::PARAM_INT)))
- ->andWhere($qb->expr()->eq('meta.group_name', $qb->createNamedParameter(self::METADATA_GROUP)))
- ->andWhere($qb->expr()->eq('meta.value', $qb->createNamedParameter($place)))
+ $qb->selectDistinct('meta_value')
+ ->from('filecache', 'file');
+ $metadataQuery = $this->filesMetadataManager->getMetadataQuery($qb, 'file', 'fileid');
+ $metadataQuery->joinIndex(self::METADATA_KEY, true);
+ $rows = $qb->where($qb->expr()->eq('file.storage', $qb->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
+ ->andWhere($qb->expr()->in('file.mimetype', $qb->createNamedParameter($mimetypes, IQueryBuilder::PARAM_INT_ARRAY)))
+ ->andWhere($qb->expr()->eq('meta_value', $qb->createNamedParameter($place)))
->executeQuery()
->fetchAll();
@@ -90,7 +93,7 @@ public function findPlaceForUser(string $userId, string $place): PlaceInfo {
throw new NotFoundException();
}
- return new PlaceInfo($userId, $rows[0]['value']);
+ return new PlaceInfo($userId, $rows[0]['meta_value']);
}
/** @return PlaceFile[] */
@@ -100,17 +103,17 @@ public function findFilesForUserAndPlace(string $userId, string $place) {
->getMountPoint()
->getNumericStorageId();
- $mimepart = $this->mimeTypeLoader->getId('image');
+ $mimetypes = array_map(fn ($mimetype) => $this->mimeTypeLoader->getId($mimetype), Application::IMAGE_MIMES);
$qb = $this->connection->getQueryBuilder();
- $rows = $qb->select('file.fileid', 'file.name', 'file.mimetype', 'file.size', 'file.mtime', 'file.etag', 'meta.value')
- ->from('file_metadata', 'meta')
- ->join('meta', 'filecache', 'file', $qb->expr()->eq('file.fileid', 'meta.id', IQueryBuilder::PARAM_INT))
- ->where($qb->expr()->eq('file.storage', $qb->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
- ->andWhere($qb->expr()->eq('file.mimepart', $qb->createNamedParameter($mimepart, IQueryBuilder::PARAM_INT)))
- ->andWhere($qb->expr()->eq('meta.group_name', $qb->createNamedParameter(self::METADATA_GROUP)))
- ->andWhere($qb->expr()->eq('meta.value', $qb->createNamedParameter($place)))
+ $rows = $qb->select('file.fileid', 'file.name', 'file.mimetype', 'file.size', 'file.mtime', 'file.etag', 'meta_value')
+ ->from('filecache', 'file');
+ $metadataQuery = $this->filesMetadataManager->getMetadataQuery($qb, 'file', 'fileid');
+ $metadataQuery->joinIndex(self::METADATA_KEY, true);
+ $rows = $qb->where($qb->expr()->eq('file.storage', $qb->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
+ ->andWhere($qb->expr()->in('file.mimetype', $qb->createNamedParameter($mimetypes, IQueryBuilder::PARAM_INT_ARRAY)))
+ ->andWhere($qb->expr()->eq('meta_value', $qb->createNamedParameter($place)))
->executeQuery()
->fetchAll();
@@ -122,7 +125,7 @@ public function findFilesForUserAndPlace(string $userId, string $place) {
(int)$row['size'],
(int)$row['mtime'],
$row['etag'],
- $row['value']
+ $row['meta_value']
),
$rows,
);
@@ -134,19 +137,19 @@ public function findFileForUserAndPlace(string $userId, string $place, string $f
->getMountPoint()
->getNumericStorageId();
- $mimepart = $this->mimeTypeLoader->getId('image');
+ $mimetypes = array_map(fn ($mimetype) => $this->mimeTypeLoader->getId($mimetype), Application::IMAGE_MIMES);
$qb = $this->connection->getQueryBuilder();
- $rows = $qb->select('file.fileid', 'file.name', 'file.mimetype', 'file.size', 'file.mtime', 'file.etag', 'meta.value')
- ->from('file_metadata', 'meta')
- ->join('meta', 'filecache', 'file', $qb->expr()->eq('file.fileid', 'meta.id', IQueryBuilder::PARAM_INT))
- ->where($qb->expr()->eq('file.storage', $qb->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
- ->andWhere($qb->expr()->eq('file.mimepart', $qb->createNamedParameter($mimepart, IQueryBuilder::PARAM_INT)))
+ $rows = $qb->select('file.fileid', 'file.name', 'file.mimetype', 'file.size', 'file.mtime', 'file.etag', 'meta_value')
+ ->from('filecache', 'file');
+ $metadataQuery = $this->filesMetadataManager->getMetadataQuery($qb, 'file', 'fileid');
+ $metadataQuery->joinIndex(self::METADATA_KEY, true);
+ $rows = $qb->where($qb->expr()->eq('file.storage', $qb->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('file.fileid', $qb->createNamedParameter($fileId)))
->andWhere($qb->expr()->eq('file.name', $qb->createNamedParameter($fileName)))
- ->andWhere($qb->expr()->eq('meta.group_name', $qb->createNamedParameter(self::METADATA_GROUP)))
- ->andWhere($qb->expr()->eq('meta.value', $qb->createNamedParameter($place)))
+ ->andWhere($qb->expr()->in('file.mimetype', $qb->createNamedParameter($mimetypes, IQueryBuilder::PARAM_INT_ARRAY)))
+ ->andWhere($qb->expr()->eq('meta_value', $qb->createNamedParameter($place)))
->executeQuery()
->fetchAll();
@@ -161,35 +164,12 @@ public function findFileForUserAndPlace(string $userId, string $place, string $f
(int)$rows[0]['size'],
(int)$rows[0]['mtime'],
$rows[0]['etag'],
- $rows[0]['value']
+ $rows[0]['meta_value']
);
}
public function setPlaceForFile(string $place, int $fileId): void {
- try {
- $query = $this->connection->getQueryBuilder();
- $query->insert('file_metadata')
- ->values([
- "id" => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
- "group_name" => $query->createNamedParameter(self::METADATA_GROUP),
- "value" => $query->createNamedParameter($place),
- ])
- ->executeStatement();
- } catch (\Exception $ex) {
- if ($ex->getPrevious() instanceof UniqueConstraintViolationException) {
- $this->updatePlaceForFile($place, $fileId);
- } else {
- throw $ex;
- }
- }
- }
-
- public function updatePlaceForFile(string $place, int $fileId): void {
- $query = $this->connection->getQueryBuilder();
- $query->update('file_metadata')
- ->set("value", $query->createNamedParameter($place))
- ->where($query->expr()->eq('id', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
- ->andWhere($query->expr()->eq('group_name', $query->createNamedParameter(self::METADATA_GROUP)))
- ->executeStatement();
+ $metadata = $this->filesMetadataManager->getMetadata($fileId, true);
+ $metadata->set('gps', $place, true);
}
}
diff --git a/lib/Jobs/MapMediaToPlaceJob.php b/lib/Jobs/MapMediaToPlaceJob.php
deleted file mode 100644
index 2facda95a..000000000
--- a/lib/Jobs/MapMediaToPlaceJob.php
+++ /dev/null
@@ -1,48 +0,0 @@
-
- *
- * @author Louis Chemineau
- *
- * @license AGPL-3.0-or-later
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- *
- */
-
-namespace OCA\Photos\Jobs;
-
-use OCA\Photos\Service\MediaPlaceManager;
-use OCP\AppFramework\Utility\ITimeFactory;
-use OCP\BackgroundJob\QueuedJob;
-
-class MapMediaToPlaceJob extends QueuedJob {
- private MediaPlaceManager $mediaPlaceManager;
-
- public function __construct(
- ITimeFactory $time,
- MediaPlaceManager $mediaPlaceManager
- ) {
- parent::__construct($time);
- $this->mediaPlaceManager = $mediaPlaceManager;
- }
-
- protected function run($argument) {
- [$fileId] = $argument;
-
- $this->mediaPlaceManager->setPlaceForFile($fileId);
- }
-}
diff --git a/lib/Listener/PlaceManagerEventListener.php b/lib/Listener/PlaceManagerEventListener.php
deleted file mode 100644
index c9954f853..000000000
--- a/lib/Listener/PlaceManagerEventListener.php
+++ /dev/null
@@ -1,71 +0,0 @@
-
- *
- * @author Louis Chemineau
- *
- * @license AGPL-3.0-or-later
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- *
- */
-
-namespace OCA\Photos\Listener;
-
-use OCA\Photos\Jobs\MapMediaToPlaceJob;
-use OCA\Photos\Service\MediaPlaceManager;
-use OCP\BackgroundJob\IJobList;
-use OCP\EventDispatcher\Event;
-use OCP\EventDispatcher\IEventListener;
-use OCP\Files\Events\Node\NodeWrittenEvent;
-use OCP\IConfig;
-
-/**
- * Listener to add place info from the database.
- */
-class PlaceManagerEventListener implements IEventListener {
- public function __construct(
- private MediaPlaceManager $mediaPlaceManager,
- private IConfig $config,
- private IJobList $jobList,
- ) {
- }
-
- public function handle(Event $event): void {
- if (!$this->config->getSystemValueBool('enable_file_metadata', true)) {
- return;
- }
-
- if ($event instanceof NodeWrittenEvent) {
- if (!$this->isCorrectPath($event->getNode()->getPath())) {
- return;
- }
-
- if (!str_starts_with($event->getNode()->getMimeType(), 'image')) {
- return;
- }
-
- $fileId = $event->getNode()->getId();
-
- $this->jobList->add(MapMediaToPlaceJob::class, [$fileId]);
- }
- }
-
- private function isCorrectPath(string $path): bool {
- // TODO make this more dynamic, we have the same issue in other places
- return !str_starts_with($path, 'appdata_') && !str_starts_with($path, 'files_versions/');
- }
-}
diff --git a/lib/MetadataProvider/ExifMetadataProvider.php b/lib/MetadataProvider/ExifMetadataProvider.php
new file mode 100644
index 000000000..3d6f4b652
--- /dev/null
+++ b/lib/MetadataProvider/ExifMetadataProvider.php
@@ -0,0 +1,146 @@
+
+ * @copyright Copyright 2022 Louis Chmn
+ * @license AGPL-3.0-or-later
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see
+ *
+ */
+
+namespace OCA\Photos\MetadataProvider;
+
+use OCA\Photos\AppInfo\Application;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Files\File;
+use OCP\FilesMetadata\Event\MetadataLiveEvent;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Extract EXIF, IFD0, and GPS data from a picture file.
+ * EXIF data reference: https://web.archive.org/web/20220428165430/exif.org/Exif2-2.PDF
+ *
+ * @template-implements IEventListener
+ */
+class ExifMetadataProvider implements IEventListener {
+ public function __construct(
+ private LoggerInterface $logger
+ ) {
+ }
+
+ public function handle(Event $event): void {
+ if (!($event instanceof MetadataLiveEvent)) {
+ return;
+ }
+
+ $node = $event->getNode();
+
+ if (!$node instanceof File) {
+ return;
+ }
+
+ $path = $node->getPath();
+ if (str_starts_with($path, 'appdata_') || str_starts_with($path, 'files_versions/') || str_starts_with($path, 'files_trashbin/')) {
+ return;
+ }
+
+ if (!in_array($node->getMimeType(), Application::IMAGE_MIMES)) {
+ return;
+ }
+
+ if (!extension_loaded('exif')) {
+ return;
+ }
+
+ $fileDescriptor = $node->fopen('rb');
+ if ($fileDescriptor === false) {
+ return;
+ }
+
+ $rawExifData = null;
+
+ try {
+ // HACK: The stream_set_chunk_size call is needed to make reading exif data reliable.
+ // This is to trigger this condition: https://github.com/php/php-src/blob/d64aa6f646a7b5e58359dc79479860164239580a/main/streams/streams.c#L710
+ // But I don't understand yet why 1 as a special meaning.
+ $oldBufferSize = stream_set_chunk_size($fileDescriptor, 1);
+ $rawExifData = @exif_read_data($fileDescriptor, 'EXIF, GPS', true);
+ // We then revert the change after having read the exif data.
+ stream_set_chunk_size($fileDescriptor, $oldBufferSize);
+ } catch (\Exception $ex) {
+ $this->logger->info("Failed to extract metadata for " . $node->getId(), ['exception' => $ex]);
+ }
+
+ if ($rawExifData && array_key_exists('EXIF', $rawExifData)) {
+ $event->getMetadata()->setArray('photos-exif', $rawExifData['EXIF']);
+ }
+
+ if ($rawExifData && array_key_exists('IFD0', $rawExifData)) {
+ $event->getMetadata()->setArray('photos-ifd0', $rawExifData['IFD0']);
+ }
+
+ if (
+ $rawExifData &&
+ array_key_exists('GPS', $rawExifData)
+ ) {
+ $gps = [];
+
+ if (
+ array_key_exists('GPSLatitude', $rawExifData['GPS']) && array_key_exists('GPSLatitudeRef', $rawExifData['GPS']) &&
+ array_key_exists('GPSLongitude', $rawExifData['GPS']) && array_key_exists('GPSLongitudeRef', $rawExifData['GPS'])
+ ) {
+ $gps['latitude'] = $this->gpsDegreesToDecimal($rawExifData['GPS']['GPSLatitude'], $rawExifData['GPS']['GPSLatitudeRef']);
+ $gps['longitude'] = $this->gpsDegreesToDecimal($rawExifData['GPS']['GPSLongitude'], $rawExifData['GPS']['GPSLongitudeRef']);
+ }
+
+ if (array_key_exists('GPSAltitude', $rawExifData['GPS']) && array_key_exists('GPSAltitudeRef', $rawExifData['GPS'])) {
+ $gps['altitude'] = ($rawExifData['GPS']['GPSAltitudeRef'] === "\u{0000}" ? 1 : -1) * $this->parseGPSData($rawExifData['GPS']['GPSAltitude']);
+ }
+
+ if (!empty($gps)) {
+ $event->getMetadata()->setArray('photos-gps', $gps);
+ }
+ }
+ }
+
+ /**
+ * @param array|string $coordinates
+ */
+ private function gpsDegreesToDecimal($coordinates, ?string $hemisphere): float {
+ if (is_string($coordinates)) {
+ $coordinates = array_map("trim", explode(",", $coordinates));
+ }
+
+ if (count($coordinates) !== 3) {
+ throw new \Exception('Invalid coordinate format: ' . json_encode($coordinates));
+ }
+
+ [$degrees, $minutes, $seconds] = array_map(fn ($rawDegree) => $this->parseGPSData($rawDegree), $coordinates);
+
+ $sign = ($hemisphere === 'W' || $hemisphere === 'S') ? -1 : 1;
+ return $sign * ($degrees + $minutes / 60 + $seconds / 3600);
+ }
+
+ private function parseGPSData(string $rawData): float {
+ $parts = explode('/', $rawData);
+
+ if ($parts[1] === '0') {
+ return 0;
+ }
+
+ return floatval($parts[0]) / floatval($parts[1] ?? 1);
+ }
+}
diff --git a/lib/MetadataProvider/OriginalDateTimeMetadataProvider.php b/lib/MetadataProvider/OriginalDateTimeMetadataProvider.php
new file mode 100644
index 000000000..6db62a8a1
--- /dev/null
+++ b/lib/MetadataProvider/OriginalDateTimeMetadataProvider.php
@@ -0,0 +1,94 @@
+
+ * @copyright Copyright 2022 Louis Chmn
+ * @license AGPL-3.0-or-later
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see
+ *
+ */
+
+namespace OCA\Photos\MetadataProvider;
+
+use DateTime;
+use OCA\Photos\AppInfo\Application;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Files\File;
+use OCP\FilesMetadata\Event\MetadataLiveEvent;
+
+/**
+ * @template-implements IEventListener
+ */
+class OriginalDateTimeMetadataProvider implements IEventListener {
+ public function __construct() {
+ }
+
+ public array $regexpToDateFormatMap = [
+ "/^IMG_([0-9]{8}_[0-9]{6})/" => "Ymd_Gis",
+ "/^PANO_([0-9]{8}_[0-9]{6})/" => "Ymd_Gis",
+ "/^PXL_([0-9]{8}_[0-9]{6})/" => "Ymd_Gis",
+ "/^([0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{4})/" => "Y-m-d-G-i-s",
+ ];
+
+ public function handle(Event $event): void {
+ if (!($event instanceof MetadataLiveEvent)) {
+ return;
+ }
+
+ $node = $event->getNode();
+
+ if (!$node instanceof File) {
+ return;
+ }
+
+ $path = $node->getPath();
+ if (str_starts_with($path, 'appdata_') || str_starts_with($path, 'files_versions/') || str_starts_with($path, 'files_trashbin/')) {
+ return;
+ }
+
+ if (!in_array($node->getMimeType(), Application::IMAGE_MIMES)) {
+ return;
+ }
+
+ $metadata = $event->getMetadata();
+
+ // Try to use EXIF data.
+ if ($metadata->hasKey('photos-exif') && array_key_exists('DateTimeOriginal', $metadata->getArray('photos-exif'))) {
+ $rawDateTimeOriginal = $metadata->getArray('photos-exif')['DateTimeOriginal'];
+ $dateTimeOriginal = DateTime::createFromFormat("Y:m:d G:i:s", $rawDateTimeOriginal);
+ $metadata->setInt('photos-original_date_time', $dateTimeOriginal->getTimestamp(), true);
+ return;
+ }
+
+ // Try to parse the date out of the name.
+ $name = $node->getName();
+ $matches = [];
+
+ foreach ($this->regexpToDateFormatMap as $regexp => $format) {
+ $matchesCount = preg_match($regexp, $name, $matches);
+ if ($matchesCount === 0) {
+ continue;
+ }
+
+ $dateTimeOriginal = DateTime::createFromFormat($format, $matches[1]);
+ $metadata->setInt('photos-original_date_time', $dateTimeOriginal->getTimestamp(), true);
+ return;
+ }
+
+ // Fallback to the mtime.
+ $metadata->setInt('photos-original_date_time', $node->getMTime(), true);
+ }
+}
diff --git a/lib/MetadataProvider/PlaceMetadataProvider.php b/lib/MetadataProvider/PlaceMetadataProvider.php
new file mode 100644
index 000000000..384074984
--- /dev/null
+++ b/lib/MetadataProvider/PlaceMetadataProvider.php
@@ -0,0 +1,67 @@
+
+ * @license AGPL-3.0-or-later
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see
+ *
+ */
+
+
+namespace OCA\Photos\MetadataProvider;
+
+use OCA\Photos\AppInfo\Application;
+use OCA\Photos\Service\MediaPlaceManager;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Files\File;
+use OCP\FilesMetadata\Event\MetadataBackgroundEvent;
+use OCP\FilesMetadata\Event\MetadataLiveEvent;
+
+class PlaceMetadataProvider implements IEventListener {
+ public function __construct(
+ private MediaPlaceManager $mediaPlaceManager
+ ) {
+ }
+
+ public function handle(Event $event): void {
+ if ($event instanceof MetadataLiveEvent) {
+ $node = $event->getNode();
+
+ if (!$node instanceof File) {
+ return;
+ }
+
+ $path = $node->getPath();
+ if (str_starts_with($path, 'appdata_') || str_starts_with($path, 'files_versions/') || str_starts_with($path, 'files_trashbin/')) {
+ return;
+ }
+
+ if (!in_array($node->getMimeType(), Application::IMAGE_MIMES)) {
+ return;
+ }
+
+ $event->requestBackgroundJob();
+ }
+
+ if ($event instanceof MetadataBackgroundEvent) {
+ $metadata = $event->getMetadata();
+ $place = $this->mediaPlaceManager->getPlaceForFile($event->getNode()->getId());
+ if ($place !== null) {
+ $metadata->set('photos-place', $place, true);
+ }
+ }
+ }
+}
diff --git a/lib/MetadataProvider/SizeMetadataProvider.php b/lib/MetadataProvider/SizeMetadataProvider.php
new file mode 100644
index 000000000..e262a4436
--- /dev/null
+++ b/lib/MetadataProvider/SizeMetadataProvider.php
@@ -0,0 +1,72 @@
+
+ * @copyright Copyright 2022 Louis Chmn
+ * @license AGPL-3.0-or-later
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see
+ *
+ */
+
+namespace OCA\Photos\MetadataProvider;
+
+use OCA\Photos\AppInfo\Application;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Files\File;
+use OCP\FilesMetadata\Event\MetadataLiveEvent;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @template-implements IEventListener
+ */
+class SizeMetadataProvider implements IEventListener {
+ public function __construct(
+ private LoggerInterface $logger
+ ) {
+ }
+
+ public function handle(Event $event): void {
+ if (!($event instanceof MetadataLiveEvent)) {
+ return;
+ }
+
+ $node = $event->getNode();
+
+ if (!$node instanceof File) {
+ return;
+ }
+
+ $path = $node->getPath();
+ if (str_starts_with($path, 'appdata_') || str_starts_with($path, 'files_versions/') || str_starts_with($path, 'files_trashbin/')) {
+ return;
+ }
+
+ if (!in_array($node->getMimeType(), Application::IMAGE_MIMES)) {
+ return;
+ }
+
+ $size = getimagesizefromstring($node->getContent());
+
+ if ($size === false) {
+ return;
+ }
+
+ $event->getMetadata()->setArray('photos-size', [
+ 'width' => $size[0],
+ 'height' => $size[1],
+ ]);
+ }
+}
diff --git a/lib/Sabre/PropFindPlugin.php b/lib/Sabre/PropFindPlugin.php
index b94a3a07e..a37d006f9 100644
--- a/lib/Sabre/PropFindPlugin.php
+++ b/lib/Sabre/PropFindPlugin.php
@@ -23,7 +23,6 @@
namespace OCA\Photos\Sabre;
-use OC\Metadata\IMetadataManager;
use OCA\DAV\Connector\Sabre\FilesPlugin;
use OCA\Photos\Album\AlbumMapper;
use OCA\Photos\Sabre\Album\AlbumPhoto;
@@ -53,23 +52,18 @@ class PropFindPlugin extends ServerPlugin {
public const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions';
private IConfig $config;
- private IMetadataManager $metadataManager;
private IPreview $previewManager;
- private bool $metadataEnabled;
private ?Tree $tree;
private AlbumMapper $albumMapper;
public function __construct(
IConfig $config,
- IMetadataManager $metadataManager,
IPreview $previewManager,
AlbumMapper $albumMapper
) {
$this->config = $config;
- $this->metadataManager = $metadataManager;
$this->previewManager = $previewManager;
$this->albumMapper = $albumMapper;
- $this->metadataEnabled = $this->config->getSystemValueBool('enable_file_metadata', true);
}
/**
@@ -111,21 +105,8 @@ public function propFind(PropFind $propFind, INode $node): void {
$propFind->handle(FilesPlugin::HAS_PREVIEW_PROPERTYNAME, fn () => json_encode($this->previewManager->isAvailable($fileInfo)));
// Remove G permission as it does not make sense in the context of photos.
$propFind->handle(FilesPlugin::PERMISSIONS_PROPERTYNAME, fn () => str_replace('G', '', DavUtil::getDavPermissions($node->getFileInfo())));
-
- if ($this->metadataEnabled) {
- $propFind->handle(FilesPlugin::FILE_METADATA_SIZE, function () use ($node) {
- if (!str_starts_with($node->getFile()->getMimetype(), 'image')) {
- return json_encode((object)[]);
- }
-
- if ($node->getFile()->hasMetadata('size')) {
- $sizeMetadata = $node->getFile()->getMetadata('size');
- } else {
- $sizeMetadata = $this->metadataManager->fetchMetadataFor('size', [$node->getFile()->getFileId()])[$node->getFile()->getFileId()];
- }
-
- return $sizeMetadata->getValue();
- });
+ foreach ($node->getFileInfo()->getMetadata() as $metadataKey => $metadataValue) {
+ $propFind->handle(FilesPlugin::FILE_METADATA_PREFIX.$metadataKey, $metadataValue['value']);
}
}
@@ -136,35 +117,11 @@ public function propFind(PropFind $propFind, INode $node): void {
$propFind->handle(self::LOCATION_PROPERTYNAME, fn () => $node->getAlbum()->getAlbum()->getLocation());
$propFind->handle(self::DATE_RANGE_PROPERTYNAME, fn () => json_encode($node->getDateRange()));
$propFind->handle(self::COLLABORATORS_PROPERTYNAME, fn () => $node->getCollaborators());
-
- // TODO detect dynamically which metadata groups are requested and
- // preload all of them and not just size
- if ($this->metadataEnabled && in_array(FilesPlugin::FILE_METADATA_SIZE, $propFind->getRequestedProperties(), true)) {
- $fileIds = $node->getAlbum()->getFileIds();
-
- $preloadedMetadata = $this->metadataManager->fetchMetadataFor('size', $fileIds);
- foreach ($node->getAlbum()->getFiles() as $file) {
- if (str_starts_with($file->getMimeType(), 'image')) {
- $file->setMetadata('size', $preloadedMetadata[$file->getFileId()]);
- }
- }
- }
}
if ($node instanceof PlaceRoot) {
$propFind->handle(self::LAST_PHOTO_PROPERTYNAME, fn () => $node->getFirstPhoto());
$propFind->handle(self::NBITEMS_PROPERTYNAME, fn () => count($node->getChildren()));
-
- // TODO detect dynamically which metadata groups are requested and
- // preload all of them and not just size
- if ($this->metadataEnabled && in_array(FilesPlugin::FILE_METADATA_SIZE, $propFind->getRequestedProperties(), true)) {
- $fileIds = $node->getFileIds();
- $preloadedMetadata = $this->metadataManager->fetchMetadataFor('size', $fileIds);
-
- foreach ($node->getChildren() as $file) {
- $file->getFile()->setMetadata('size', $preloadedMetadata[$file->getFileId()]);
- }
- }
}
}
diff --git a/lib/Service/MediaPlaceManager.php b/lib/Service/MediaPlaceManager.php
index c1e11f7b6..24943d183 100644
--- a/lib/Service/MediaPlaceManager.php
+++ b/lib/Service/MediaPlaceManager.php
@@ -25,12 +25,12 @@
namespace OCA\Photos\Service;
-use OC\Metadata\IMetadataManager;
use OCA\Photos\DB\Place\PlaceMapper;
+use OCP\FilesMetadata\IFilesMetadataManager;
class MediaPlaceManager {
public function __construct(
- private IMetadataManager $metadataManager,
+ private IFilesMetadataManager $filesMetadataManager,
private ReverseGeoCoderService $rgcService,
private PlaceMapper $placeMapper,
) {
@@ -46,30 +46,27 @@ public function setPlaceForFile(int $fileId): void {
$this->placeMapper->setPlaceForFile($place, $fileId);
}
- public function updatePlaceForFile(int $fileId): void {
- $place = $this->getPlaceForFile($fileId);
+ // public function updatePlaceForFile(int $fileId): void {
+ // $place = $this->getPlaceForFile($fileId);
- if ($place === null) {
- return;
- }
+ // if ($place === null) {
+ // return;
+ // }
- $this->placeMapper->updatePlaceForFile($place, $fileId);
- }
+ // $this->placeMapper->setPlaceForFile($place, $fileId);
+ // }
- private function getPlaceForFile(int $fileId): ?string {
- $gpsMetadata = $this->metadataManager->fetchMetadataFor('gps', [$fileId])[$fileId];
- $metadata = $gpsMetadata->getDecodedValue();
+ public function getPlaceForFile(int $fileId): ?string {
+ $metadata = $this->filesMetadataManager->getMetadata($fileId, true);
- if (count($metadata) === 0) {
+ if (!$metadata->hasKey('photos-gps')) {
return null;
}
- $latitude = $metadata['latitude'];
- $longitude = $metadata['longitude'];
+ $coordinate = $metadata->getArray('photos-gps');
- if ($latitude === null || $longitude === null) {
- return null;
- }
+ $latitude = $coordinate['latitude'];
+ $longitude = $coordinate['longitude'];
return $this->rgcService->getPlaceForCoordinates($latitude, $longitude);
}
diff --git a/src/components/FilesListViewer.vue b/src/components/FilesListViewer.vue
index c9685d7d3..d4e803850 100644
--- a/src/components/FilesListViewer.vue
+++ b/src/components/FilesListViewer.vue
@@ -250,9 +250,9 @@ export default {
const file = this.files[fileId]
return {
id: file.fileid,
- width: file.fileMetadataSizeParsed.width,
- height: file.fileMetadataSizeParsed.height,
- ratio: this.croppedLayout ? 1 : file.fileMetadataSizeParsed.width / file.fileMetadataSizeParsed.height,
+ width: file.metadataPhotosSize.width,
+ height: file.metadataPhotosSize.height,
+ ratio: this.croppedLayout ? 1 : file.metadataPhotosSize.width / file.metadataPhotosSize.height,
}
},
diff --git a/src/services/DavRequest.js b/src/services/DavRequest.js
index a218b45ef..97e2933ef 100644
--- a/src/services/DavRequest.js
+++ b/src/services/DavRequest.js
@@ -27,7 +27,8 @@ const props = `
-
+
+
diff --git a/src/services/collectionFetcher.js b/src/services/collectionFetcher.js
index 8f22884f4..19d09e48a 100644
--- a/src/services/collectionFetcher.js
+++ b/src/services/collectionFetcher.js
@@ -42,9 +42,9 @@ import { genFileInfo } from '../utils/fileUtils.js'
* @property {string} basename - The name of the file (ex: "790-IMG_20180906_085724.jpg").
* @property {string} filename - The file name of the file (ex: "/photos/admin/places/Athens/790-IMG_20180906_085724.jpg").
* @property {string} source - The full source of the collection (ex: "https://nextcloud_server1.test/remote.php/dav//photos/admin/places/Athens/790-IMG_20180906_085724.jpg").
- * @property {object} fileMetadataSizeParsed - The metadata of the file.
- * @property {number} fileMetadataSizeParsed.width - The width of the file.
- * @property {number} fileMetadataSizeParsed.height - The height of the file.
+ * @property {object} metadataPhotosSize - The metadata of the file.
+ * @property {number} metadataPhotosSize.width - The width of the file.
+ * @property {number} metadataPhotosSize.height - The height of the file.
*/
/** @typedef {Object} IndexedCollections */
@@ -84,7 +84,8 @@ function getCollectionFilesDavRequest(extraProps = []) {
-
+
+
diff --git a/src/services/fileFetcher.js b/src/services/fileFetcher.js
index a5fcca8b7..0d537fadc 100644
--- a/src/services/fileFetcher.js
+++ b/src/services/fileFetcher.js
@@ -39,7 +39,8 @@ function getCollectionFilesDavRequest(extraProps = []) {
-
+
+
diff --git a/src/store/files.js b/src/store/files.js
index 8b55c3b03..e9ac283f9 100644
--- a/src/store/files.js
+++ b/src/store/files.js
@@ -50,12 +50,12 @@ const mutations = {
}
if (file.fileid >= 0) {
- if (file.fileMetadataSize?.length > 1) {
- file.fileMetadataSizeParsed = JSON.parse(file.fileMetadataSize?.replace(/"/g, '"') ?? '{}')
- file.fileMetadataSizeParsed.width = file.fileMetadataSizeParsed?.width ?? 256
- file.fileMetadataSizeParsed.height = file.fileMetadataSizeParsed?.height ?? 256
+ file.metadataPhotosSize = {}
+ if (file.width && file.height) {
+ file.metadataPhotosSize.width = file.width
+ file.metadataPhotosSize.height = file.height
} else {
- file.fileMetadataSizeParsed = { width: 256, height: 256 }
+ file.metadataPhotosSize = { width: 256, height: 256 }
}
}
@@ -63,9 +63,10 @@ const mutations = {
file.fileid = file.fileid.toString()
// Precalculate dates as it is expensive.
- file.timestamp = moment(file.lastmod).unix() // For sorting
- file.month = moment(file.lastmod).format('YYYYMM') // For grouping by month
- file.day = moment(file.lastmod).format('MMDD') // For On this day
+ const date = moment(file.lastmod)
+ file.timestamp = date.unix() // For sorting
+ file.month = date.format('YYYYMM') // For grouping by month
+ file.day = date.format('MMDD') // For On this day
// Schedule the file to add
files[file.fileid] = file
diff --git a/tests/stub.phpstub b/tests/stub.phpstub
index edb8340e5..dcd5fc438 100644
--- a/tests/stub.phpstub
+++ b/tests/stub.phpstub
@@ -583,74 +583,6 @@ namespace OC\Files\Storage\Wrapper{
}
}
-namespace OC\Metadata {
-
-use OCP\Files\File;
-
-/**
- * Interface to manage additional metadata for files
- */
-interface IMetadataManager {
- /**
- * @param class-string $className
- */
- public function registerProvider(string $className): void;
-
- /**
- * Generate the metadata for one file
- */
- public function generateMetadata(File $node, bool $checkExisting = false): void;
-
- /**
- * Clear the metadata for one file
- */
- public function clearMetadata(int $fileId): void;
-
- /** @return array */
- public function fetchMetadataFor(string $group, array $fileIds): array;
-
- /**
- * Get the capabilites as an array of mimetype regex to the type provided
- */
- public function getCapabilities(): array;
-}
-
-/**
- * Interface for the metadata providers. If you want an application to provide
- * some metadata, you can use this to store them.
- */
-interface IMetadataProvider {
- /**
- * The list of groups that this metadata provider is able to provide.
- *
- * @return string[]
- */
- public static function groupsProvided(): array;
-
- /**
- * Check if the metadata provider is available. A metadata provider might be
- * unavailable due to a php extension not being installed.
- */
- public static function isAvailable(): bool;
-
- /**
- * Get the mimetypes supported as a regex.
- */
- public static function getMimetypesSupported(): string;
-
- /**
- * Execute the extraction on the specified file. The metadata should be
- * grouped by metadata
- *
- * Each group should be json serializable and the string representation
- * shouldn't be longer than 4000 characters.
- *
- * @param File $file The file to extract the metadata from
- * @param array An array containing all the metadata fetched.
- */
- public function execute(File $file): array;
-}
-
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;