diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php index a6c9b8b4ebe4e..6b6f622a5a755 100644 --- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php @@ -436,7 +436,7 @@ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) \OC::$server->get(LoggerInterface::class)->debug('Inefficient fetching of metadata'); } - return json_encode((object)$sizeMetadata->getMetadata(), JSON_THROW_ON_ERROR); + return $sizeMetadata->getValue(); }); } } diff --git a/core/Migrations/Version24000Date20220404230027.php b/core/Migrations/Version24000Date20220404230027.php index d53a43af9592a..bfb3cc5c00be2 100644 --- a/core/Migrations/Version24000Date20220404230027.php +++ b/core/Migrations/Version24000Date20220404230027.php @@ -52,11 +52,14 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt 'notnull' => true, 'length' => 50, ]); - $table->addColumn('metadata', Types::JSON, [ + $table->addColumn('value', Types::STRING, [ 'notnull' => true, ]); $table->setPrimaryKey(['id', 'group_name'], 'file_metadata_idx'); + + return $schema; } - return $schema; + + return null; } } diff --git a/core/Migrations/Version27000Date20230309104325.php b/core/Migrations/Version27000Date20230309104325.php new file mode 100644 index 0000000000000..08d9d352d8007 --- /dev/null +++ b/core/Migrations/Version27000Date20230309104325.php @@ -0,0 +1,122 @@ + + * + * @author Louis Chmn + * + * @license GNU AGPL version 3 or any later version + * + * 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 OC\Core\Migrations; + +use Closure; +use OCP\DB\ISchemaWrapper; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\DB\Types; +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +/** + * Migrate oc_file_metadata.metadata as JSON type to oc_file_metadata.value a STRING type + * @see \OC\Metadata\FileMetadata + */ +class Version27000Date20230309104325 extends SimpleMigrationStep { + public function __construct( + private IDBConnection $connection + ) { + } + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + $metadataTable = $schema->getTable('file_metadata'); + + if ($metadataTable->hasColumn('value')) { + return null; + } + + $metadataTable->addColumn('value', Types::STRING, [ + 'notnull' => true, + ]); + return $schema; + } + + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return void + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + $metadataTable = $schema->getTable('file_metadata'); + + if (!$metadataTable->hasColumn('metadata')) { + return; + } + + $insertQuery = $this->connection->getQueryBuilder(); + $insertQuery->insert('file_metadata') + ->where($insertQuery->expr()->eq('id', $insertQuery->createParameter('id'))) + ->values(['value' => $insertQuery->createParameter('value')]); + + $selectQuery = $this->connection->getQueryBuilder(); + $selectQuery->select('id, metadata') + ->from('file_metadata') + ->orderBy('id', 'ASC') + ->setMaxResults(1000); + + $offset = 0; + $movedRows = 0; + do { + $movedRows = $this->chunkedCopying($insertQuery, $selectQuery, $offset); + $offset += $movedRows; + } while ($movedRows !== 0); + } + + protected function chunkedCopying(IQueryBuilder $insertQuery, IQueryBuilder $selectQuery, int $offset): int { + $this->connection->beginTransaction(); + + $results = $selectQuery + ->setFirstResult($offset) + ->executeQuery(); + + while ($row = $results->fetch()) { + $insertQuery + ->setParameter('id', (int)$row['id']) + ->setParameter('value', (int)$row['metadata']) + ->executeStatement(); + } + + $results->closeCursor(); + $this->connection->commit(); + + return $results->rowCount(); + } +} diff --git a/core/Migrations/Version27000Date20230309104802.php b/core/Migrations/Version27000Date20230309104802.php new file mode 100644 index 0000000000000..4bd50fe03962c --- /dev/null +++ b/core/Migrations/Version27000Date20230309104802.php @@ -0,0 +1,57 @@ + + * + * @author Louis Chmn + * + * @license GNU AGPL version 3 or any later version + * + * 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 OC\Core\Migrations; + +use Closure; +use OCP\DB\ISchemaWrapper; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +/** + * Migrate oc_file_metadata.metadata as JSON type to oc_file_metadata.value a STRING type + * @see \OC\Metadata\FileMetadata + */ +class Version27000Date20230309104802 extends SimpleMigrationStep { + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + $metadataTable = $schema->getTable('file_metadata'); + + if ($metadataTable->hasColumn('metadata')) { + $metadataTable->dropColumn('metadata'); + return $schema; + } + + return null; + } +} diff --git a/lib/private/Metadata/FileMetadata.php b/lib/private/Metadata/FileMetadata.php index 9ad0f9d35c66a..d4532e25b683b 100644 --- a/lib/private/Metadata/FileMetadata.php +++ b/lib/private/Metadata/FileMetadata.php @@ -28,16 +28,24 @@ /** * @method string getGroupName() * @method void setGroupName(string $groupName) - * @method array getMetadata() - * @method void setMetadata(array $metadata) + * @method string getValue() + * @method void setValue(string $value) * @see \OC\Core\Migrations\Version240000Date20220404230027 */ class FileMetadata extends Entity { protected ?string $groupName = null; - protected ?array $metadata = null; + protected ?string $value = null; public function __construct() { $this->addType('groupName', 'string'); - $this->addType('metadata', Types::JSON); + $this->addType('value', Types::STRING); + } + + public function getDecodedValue(): object { + return json_decode($this->getValue()); + } + + public function setObjectAsValue(array $value): void { + $this->setValue(json_encode($value, JSON_THROW_ON_ERROR)); } } diff --git a/lib/private/Metadata/FileMetadataMapper.php b/lib/private/Metadata/FileMetadataMapper.php index f8f8df4bf3bea..f3120e5e5153e 100644 --- a/lib/private/Metadata/FileMetadataMapper.php +++ b/lib/private/Metadata/FileMetadataMapper.php @@ -89,7 +89,7 @@ public function findForGroupForFiles(array $fileIds, string $groupName): array { continue; } $empty = new FileMetadata(); - $empty->setMetadata([]); + $empty->setValue(''); $empty->setGroupName($groupName); $empty->setId($id); $metadata[$id] = $empty; @@ -132,13 +132,13 @@ public function update(Entity $entity): Entity { $idType = $this->getParameterTypeForProperty($entity, 'id'); $groupNameType = $this->getParameterTypeForProperty($entity, 'groupName'); - $metadataValue = $entity->getMetadata(); - $metadataType = $this->getParameterTypeForProperty($entity, 'metadata'); + $value = $entity->getValue(); + $valueType = $this->getParameterTypeForProperty($entity, 'value'); $qb = $this->db->getQueryBuilder(); $qb->update($this->tableName) - ->set('metadata', $qb->createNamedParameter($metadataValue, $metadataType)) + ->set('value', $qb->createNamedParameter($value, $valueType)) ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, $idType))) ->andWhere($qb->expr()->eq('group_name', $qb->createNamedParameter($groupName, $groupNameType))) ->executeStatement(); diff --git a/lib/private/Metadata/MetadataManager.php b/lib/private/Metadata/MetadataManager.php index 77407a2f5295b..6d96ff1ab6806 100644 --- a/lib/private/Metadata/MetadataManager.php +++ b/lib/private/Metadata/MetadataManager.php @@ -78,6 +78,9 @@ public function clearMetadata(int $fileId): void { $this->fileMetadataMapper->clear($fileId); } + /** + * @return array + */ public function fetchMetadataFor(string $group, array $fileIds): array { return $this->fileMetadataMapper->findForGroupForFiles($fileIds, $group); } diff --git a/lib/private/Metadata/Provider/ExifProvider.php b/lib/private/Metadata/Provider/ExifProvider.php index ae2c57ba7e5fd..aee1e8f849669 100644 --- a/lib/private/Metadata/Provider/ExifProvider.php +++ b/lib/private/Metadata/Provider/ExifProvider.php @@ -65,12 +65,12 @@ public function execute(File $file): array { $size = new FileMetadata(); $size->setGroupName('size'); $size->setId($file->getId()); - $size->setMetadata([]); + $size->setObjectAsValue([]); if (!$data) { $sizeResult = getimagesizefromstring($file->getContent()); if ($sizeResult !== false) { - $size->setMetadata([ + $size->setObjectAsValue([ 'width' => $sizeResult[0], 'height' => $sizeResult[1], ]); @@ -79,7 +79,7 @@ public function execute(File $file): array { } } elseif (array_key_exists('COMPUTED', $data)) { if (array_key_exists('Width', $data['COMPUTED']) && array_key_exists('Height', $data['COMPUTED'])) { - $size->setMetadata([ + $size->setObjectAsValue([ 'width' => $data['COMPUTED']['Width'], 'height' => $data['COMPUTED']['Height'], ]); @@ -95,7 +95,7 @@ public function execute(File $file): array { $gps = new FileMetadata(); $gps->setGroupName('gps'); $gps->setId($file->getId()); - $gps->setMetadata([ + $gps->setObjectAsValue([ 'latitude' => $this->gpsDegreesToDecimal($data['GPS']['GPSLatitude'], $data['GPS']['GPSLatitudeRef']), 'longitude' => $this->gpsDegreesToDecimal($data['GPS']['GPSLongitude'], $data['GPS']['GPSLongitudeRef']), ]);