diff options
author | Amir Sarabadani <ladsgroup@gmail.com> | 2022-04-01 17:16:26 +0200 |
---|---|---|
committer | Krinkle <krinkle@fastmail.com> | 2022-04-07 01:11:46 +0000 |
commit | 5da58d60289089651240a16baf90cde2235579e3 (patch) | |
tree | ae6108ccd3049ac0b177384a61f108791e8934fa | |
parent | e4bbad4ffeff8e39c45cd13d835621670d3d339d (diff) | |
download | mediawikicore-5da58d60289089651240a16baf90cde2235579e3.tar.gz mediawikicore-5da58d60289089651240a16baf90cde2235579e3.zip |
file: Move json metadata storage out of LocalFile
So ArchivedFile could use it as well
Bug: T298417
Bug: T298398
Change-Id: Idd258437d9f91824195ed0c12b5694841cb365b0
-rw-r--r-- | autoload.php | 1 | ||||
-rw-r--r-- | includes/filerepo/file/LocalFile.php | 108 | ||||
-rw-r--r-- | includes/filerepo/file/MetadataStorageHelper.php | 154 | ||||
-rw-r--r-- | includes/filerepo/file/OldLocalFile.php | 8 |
4 files changed, 173 insertions, 98 deletions
diff --git a/autoload.php b/autoload.php index 43549820c972..f6566a043e23 100644 --- a/autoload.php +++ b/autoload.php @@ -1131,6 +1131,7 @@ $wgAutoloadLocalClasses = [ 'MessageContent' => __DIR__ . '/includes/content/MessageContent.php', 'MessageLocalizer' => __DIR__ . '/includes/language/MessageLocalizer.php', 'MessageSpecifier' => __DIR__ . '/includes/libs/MessageSpecifier.php', + 'MetadataStorageHelper' => __DIR__ . '/includes/filerepo/file/MetadataStorageHelper.php', 'MigrateActors' => __DIR__ . '/maintenance/includes/MigrateActors.php', 'MigrateArchiveText' => __DIR__ . '/maintenance/migrateArchiveText.php', 'MigrateComments' => __DIR__ . '/maintenance/migrateComments.php', diff --git a/includes/filerepo/file/LocalFile.php b/includes/filerepo/file/LocalFile.php index 6ba0ea93e7ff..9ca0a6a5b95e 100644 --- a/includes/filerepo/file/LocalFile.php +++ b/includes/filerepo/file/LocalFile.php @@ -26,7 +26,6 @@ use MediaWiki\Logger\LoggerFactory; use MediaWiki\MediaWikiServices; use MediaWiki\Permissions\Authority; use MediaWiki\Revision\RevisionRecord; -use MediaWiki\Storage\BlobStore; use MediaWiki\User\UserIdentity; use MediaWiki\User\UserIdentityValue; use Wikimedia\Rdbms\Blob; @@ -174,6 +173,9 @@ class LocalFile extends File { /** @var bool True if file is not present in file system. Not to be cached in memcached */ private $missing; + /** @var MetadataStorageHelper */ + private $metadataStorageHelper; + // @note: higher than IDBAccessObject constants private const LOAD_ALL = 16; // integer; load all the lazy fields too (like metadata) @@ -188,7 +190,7 @@ class LocalFile extends File { * @stable to override * * @param Title $title - * @param FileRepo $repo + * @param LocalRepo $repo * @param null $unused * * @return static @@ -204,7 +206,7 @@ class LocalFile extends File { * @stable to override * * @param stdClass $row - * @param FileRepo $repo + * @param LocalRepo $repo * * @return static */ @@ -310,10 +312,11 @@ class LocalFile extends File { * @stable to call * * @param Title $title - * @param FileRepo $repo + * @param LocalRepo $repo */ public function __construct( $title, $repo ) { parent::__construct( $title, $repo ); + $this->metadataStorageHelper = new MetadataStorageHelper( $repo ); $this->assertRepoDefined(); $this->assertTitleDefined(); @@ -1090,25 +1093,13 @@ class LocalFile extends File { $addresses[$itemName] = $this->unloadedMetadataBlobs[$itemName]; } } + if ( $addresses ) { - $blobStore = $this->repo->getBlobStore(); - if ( !$blobStore ) { - LoggerFactory::getInstance( 'LocalFile' )->warning( - "Unable to load metadata: repo has no blob store" ); - return $result; - } - $status = $blobStore->getBlobBatch( $addresses ); - if ( !$status->isGood() ) { - $msg = Status::wrap( $status )->getWikiText( - false, false, 'en' ); - LoggerFactory::getInstance( 'LocalFile' )->warning( - "Error loading metadata from BlobStore: $msg" ); - } + $resultFromBlob = $this->metadataStorageHelper->getMetadataFromBlobStore( $addresses ); foreach ( $addresses as $itemName => $address ) { unset( $this->unloadedMetadataBlobs[$itemName] ); - $json = $status->getValue()[$address] ?? null; - if ( $json !== null ) { - $value = $this->jsonDecode( $json ); + $value = $resultFromBlob[$itemName] ?? null; + if ( $value !== null ) { $result[$itemName] = $value; $this->metadataArray[$itemName] = $value; } @@ -1118,42 +1109,6 @@ class LocalFile extends File { } /** - * Do JSON encoding with local flags. Throw an exception if the data cannot be - * serialized. - * - * @throws MWException - * @param mixed $data - * @return string - */ - private function jsonEncode( $data ): string { - $s = json_encode( $data, - JSON_INVALID_UTF8_IGNORE | - JSON_UNESCAPED_SLASHES | - JSON_UNESCAPED_UNICODE ); - if ( $s === false ) { - throw new MWException( __METHOD__ . ': metadata is not JSON-serializable ' . - '(type = ' . $this->getMimeType() . ')' ); - } - return $s; - } - - /** - * Do JSON decoding with local flags. - * - * This doesn't use JsonCodec because JsonCodec can construct objects, - * which we don't want. - * - * Does not throw. Returns false on failure. - * - * @param string $s - * @return mixed The decoded value, or false on failure - */ - private function jsonDecode( string $s ) { - // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - return @json_decode( $s, true, 512, JSON_INVALID_UTF8_IGNORE ); - } - - /** * Serialize the metadata array for insertion into img_metadata, oi_metadata * or fa_metadata. * @@ -1196,42 +1151,7 @@ class LocalFile extends File { $envelope['blobs'] = $this->metadataBlobs; } - // Try encoding - $s = $this->jsonEncode( $envelope ); - - // Decide whether to try splitting the metadata. - // Return early if it's not going to happen. - if ( !$this->repo->isSplitMetadataEnabled() - || !$this->getHandler() - || !$this->getHandler()->useSplitMetadata() - ) { - return $s; - } - $threshold = $this->repo->getSplitMetadataThreshold(); - if ( !$threshold || strlen( $s ) <= $threshold ) { - return $s; - } - $blobStore = $this->repo->getBlobStore(); - if ( !$blobStore ) { - return $s; - } - - // The data as a whole is above the item threshold. Look for - // large items that can be split out. - $blobAddresses = []; - foreach ( $envelope['data'] as $name => $value ) { - $encoded = $this->jsonEncode( $value ); - if ( strlen( $encoded ) > $threshold ) { - $blobAddresses[$name] = $blobStore->storeBlob( - $encoded, - [ BlobStore::IMAGE_HINT => $this->getName() ] - ); - } - } - // Remove any items that were split out - $envelope['data'] = array_diff_key( $envelope['data'], $blobAddresses ); - $envelope['blobs'] = $blobAddresses; - $s = $this->jsonEncode( $envelope ); + list( $s, $blobAddresses ) = $this->metadataStorageHelper->getJsonMetadata( $this, $envelope ); // Repeated calls to this function should not keep inserting more blobs $this->metadataBlobs += $blobAddresses; @@ -1252,7 +1172,7 @@ class LocalFile extends File { $threshold = $this->repo->getSplitMetadataThreshold(); $directItems = array_diff_key( $this->metadataArray, $this->metadataBlobs ); foreach ( $directItems as $value ) { - if ( strlen( $this->jsonEncode( $value ) ) > $threshold ) { + if ( strlen( $this->metadataStorageHelper->jsonEncode( $value ) ) > $threshold ) { return true; } } @@ -1289,7 +1209,7 @@ class LocalFile extends File { return; } if ( $metadataString[0] === '{' ) { - $envelope = $this->jsonDecode( $metadataString ); + $envelope = $this->metadataStorageHelper->jsonDecode( $metadataString ); if ( !$envelope ) { // Legacy error encoding $this->metadataArray = [ '_error' => $metadataString ]; diff --git a/includes/filerepo/file/MetadataStorageHelper.php b/includes/filerepo/file/MetadataStorageHelper.php new file mode 100644 index 000000000000..1c3282ae6bca --- /dev/null +++ b/includes/filerepo/file/MetadataStorageHelper.php @@ -0,0 +1,154 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup FileAbstraction + */ + +use MediaWiki\Logger\LoggerFactory; +use MediaWiki\Storage\BlobStore; + +/** + * Helper for storage of metadata. Sharing the code between LocalFile and ArchivedFile + * + * @internal + * @ingroup FileAbstraction + */ +class MetadataStorageHelper { + /** @var LocalRepo */ + private $repo; + + public function __construct( LocalRepo $repo ) { + $this->repo = $repo; + } + + /** + * Get metadata in JSON format ready for DB insertion, optionally splitting + * items out to BlobStore. + * + * @param LocalFile|ArchivedFile $file + * @param array $envelope + * @return array + */ + public function getJsonMetadata( $file, $envelope ) { + // Try encoding + $s = $this->jsonEncode( $envelope ); + + // Decide whether to try splitting the metadata. + // Return early if it's not going to happen. + if ( !$this->repo->isSplitMetadataEnabled() + || !$file->getHandler() + || !$file->getHandler()->useSplitMetadata() + ) { + return [ $s, [] ]; + } + $threshold = $this->repo->getSplitMetadataThreshold(); + if ( !$threshold || strlen( $s ) <= $threshold ) { + return [ $s, [] ]; + } + $blobStore = $this->repo->getBlobStore(); + if ( !$blobStore ) { + return [ $s, [] ]; + } + + // The data as a whole is above the item threshold. Look for + // large items that can be split out. + $blobAddresses = []; + foreach ( $envelope['data'] as $name => $value ) { + $encoded = $this->jsonEncode( $value ); + if ( strlen( $encoded ) > $threshold ) { + $blobAddresses[$name] = $blobStore->storeBlob( + $encoded, + [ BlobStore::IMAGE_HINT => $file->getName() ] + ); + } + } + // Remove any items that were split out + $envelope['data'] = array_diff_key( $envelope['data'], $blobAddresses ); + $envelope['blobs'] = $blobAddresses; + $s = $this->jsonEncode( $envelope ); + + return [ $s, $blobAddresses ]; + } + + /** + * Do JSON encoding with local flags. Throw an exception if the data cannot be + * serialized. + * + * @throws MWException + * @param mixed $data + * @return string + */ + public function jsonEncode( $data ): string { + $s = json_encode( $data, + JSON_INVALID_UTF8_IGNORE | + JSON_UNESCAPED_SLASHES | + JSON_UNESCAPED_UNICODE ); + if ( $s === false ) { + throw new MWException( __METHOD__ . ': metadata is not JSON-serializable ' ); + } + return $s; + } + + /** + * @param array $addresses + * @return array + */ + public function getMetadataFromBlobStore( array $addresses ): array { + $result = []; + if ( $addresses ) { + $blobStore = $this->repo->getBlobStore(); + if ( !$blobStore ) { + LoggerFactory::getInstance( 'LocalFile' )->warning( + "Unable to load metadata: repo has no blob store" ); + return $result; + } + $status = $blobStore->getBlobBatch( $addresses ); + if ( !$status->isGood() ) { + $msg = Status::wrap( $status )->getWikiText( + false, false, 'en' ); + LoggerFactory::getInstance( 'LocalFile' )->warning( + "Error loading metadata from BlobStore: $msg" ); + } + foreach ( $addresses as $itemName => $address ) { + $json = $status->getValue()[$address] ?? null; + if ( $json !== null ) { + $value = $this->jsonDecode( $json ); + $result[$itemName] = $value; + } + } + } + return $result; + } + + /** + * Do JSON decoding with local flags. + * + * This doesn't use JsonCodec because JsonCodec can construct objects, + * which we don't want. + * + * Does not throw. Returns false on failure. + * + * @param string $s + * @return mixed The decoded value, or false on failure + */ + public function jsonDecode( string $s ) { + // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged + return @json_decode( $s, true, 512, JSON_INVALID_UTF8_IGNORE ); + } + +} diff --git a/includes/filerepo/file/OldLocalFile.php b/includes/filerepo/file/OldLocalFile.php index 7e6123faa46b..031fe4bd9a2c 100644 --- a/includes/filerepo/file/OldLocalFile.php +++ b/includes/filerepo/file/OldLocalFile.php @@ -44,7 +44,7 @@ class OldLocalFile extends LocalFile { /** * @stable to override * @param Title $title - * @param FileRepo $repo + * @param LocalRepo $repo * @param string|int|null $time * @return static * @throws MWException @@ -62,7 +62,7 @@ class OldLocalFile extends LocalFile { * @stable to override * * @param Title $title - * @param FileRepo $repo + * @param LocalRepo $repo * @param string $archiveName * @return static */ @@ -74,7 +74,7 @@ class OldLocalFile extends LocalFile { * @stable to override * * @param stdClass $row - * @param FileRepo $repo + * @param LocalRepo $repo * @return static */ public static function newFromRow( $row, $repo ) { @@ -179,7 +179,7 @@ class OldLocalFile extends LocalFile { * @stable to call * * @param Title $title - * @param FileRepo $repo + * @param LocalRepo $repo * @param string|int|null $time Timestamp or null to load by archive name * @param string|null $archiveName Archive name or null to load by timestamp * @throws MWException |