Skip to content

Commit

Permalink
feat: Representation of changed resources #1
Browse files Browse the repository at this point in the history
  • Loading branch information
it-spiderman committed Dec 7, 2021
1 parent 000ed2c commit 0ea2c0d
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 53 deletions.
2 changes: 1 addition & 1 deletion extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
},
"controlTranscludedContent": {
"class": "DataAccounting\\Hook\\ControlTranscludedContent",
"services": [ "DataAccountingHashLookup", "RevisionStore", "RepoGroup" ]
"services": [ "DataAccountingTransclusionManager", "RevisionStore", "RepoGroup" ]
}
},
"RestRoutes": [
Expand Down
28 changes: 28 additions & 0 deletions includes/Content/TransclusionHashes.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@

namespace DataAccounting\Content;

use DataAccounting\TransclusionManager;
use FormatJson;
use JsonContent;
use MediaWiki\MediaWikiServices;
use ParserOptions;
use ParserOutput;
use Title;

class TransclusionHashes extends JsonContent {
public const CONTENT_MODEL_TRANSCLUSION_HASHES = 'transclusion-hashes';
Expand All @@ -24,6 +29,29 @@ public function setHashmap( array $hashmap ) {
$this->mText = json_encode( $hashmap );
}

protected function fillParserOutput(
Title $title, $revId, ParserOptions $options, $generateHtml, ParserOutput &$output
) {
/** @var TransclusionManager $transclusionManager */
$transclusionManager = MediaWikiServices::getInstance()->getService(
'DataAccountingTransclusionManager'
);
$revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
$revision = $revisionStore->getRevisionById( $revId );
if ( $revision === null ){
return;
}
$states = $transclusionManager->getTransclusionState( $revision );

$text = '';
foreach ( $states as $page => $state ) {
$text .= "* {$page} => {$state['state']}\n\n";
$text .= " {$state['hash']} \n";
}

$output->setText( ( new \RawMessage( $text ) )->parseAsBlock() );
}

/**
* @return array
*/
Expand Down
62 changes: 46 additions & 16 deletions includes/HashLookup.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@
use File;
use MediaWiki\Storage\RevisionRecord;
use MediaWiki\Storage\RevisionStore;
use Title;
use Wikimedia\Rdbms\ILoadBalancer;

class HashLookup {
public const HASH_TYPE_VERIFICATION = 'verification_hash';
public const HASH_TYPE_CONTENT = 'hash_content';
public const HASH_TYPE_METADATA = 'hash_metadata';
public const HASH_TYPE_SIGNATURE = 'signature_hash';

/** @var ILoadBalancer */
private $lb;
/** @var RevisionStore */
Expand All @@ -18,6 +24,33 @@ public function __construct( ILoadBalancer $loadBalancer, RevisionStore $revisio
$this->revisionStore = $revisionStore;
}

/**
* @param Title $title
* @param string $type
* @return string|null
*/
public function getLatestHashForTitle( Title $title, $type = self::HASH_TYPE_VERIFICATION ): ?string {
$res = $this->lb->getConnection( DB_REPLICA )->selectRow(
'revision_verification',
[ $type ],
[ 'page_title' => $title->getPrefixedDBkey() ],
__METHOD__,
[
'ORDER BY' => [ 'rev_id DESC' ],
]
);

if ( !$res ) {
return null;
}

return $res->$type ?? null;
}

/**
* @param string $hash
* @return RevisionRecord|null
*/
public function getRevisionForHash( string $hash ): ?RevisionRecord {
$res = $this->lb->getConnection( DB_REPLICA )->selectRow(
'revision_verification',
Expand All @@ -34,25 +67,22 @@ public function getRevisionForHash( string $hash ): ?RevisionRecord {
}

/**
* @param string $hash
* @param File $file
* @return File|null
* @param RevisionRecord $revision
* @param string $type
* @return string|null
*/
public function getFileForHash( string $hash, File $file ): ?File {
$revision = $this->getRevisionForHash( $hash );
if ( !$revision ) {
public function getHashForRevision( RevisionRecord $revision, $type = self::HASH_TYPE_VERIFICATION ): ?string {
$res = $this->lb->getConnection( DB_REPLICA )->selectRow(
'revision_verification',
[ $type ],
[ 'rev_id' => $revision->getId() ],
__METHOD__
);

if ( !$res ) {
return null;
}
if ( $revision->isCurrent() ) {
return $file;
}
$oldFiles = $file->getHistory();
foreach( $oldFiles as $oldFile ) {
if ( $oldFile->getTimestamp() === $revision->getTimestamp() ) {
return $oldFile;
}
}

return null;
return $res->$type ?? null;
}
}
50 changes: 14 additions & 36 deletions includes/Hook/ControlTranscludedContent.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

namespace DataAccounting\Hook;

use DataAccounting\Content\TransclusionHashes;
use DataAccounting\HashLookup;
use DataAccounting\TransclusionManager;
use MediaWiki\Hook\BeforeParserFetchFileAndTitleHook;
use MediaWiki\Hook\BeforeParserFetchTemplateRevisionRecordHook;
use MediaWiki\Linker\LinkTarget;
Expand All @@ -15,20 +14,22 @@
use Title;

class ControlTranscludedContent implements BeforeParserFetchTemplateRevisionRecordHook, BeforeParserFetchFileAndTitleHook {
/** @var HashLookup */
private HashLookup $hashLookup;
/** @var TransclusionManager */
private TransclusionManager $transclusionManager;
/** @var RevisionStore */
private RevisionStore $revisionStore;
/** @var RepoGroup */
private RepoGroup $repoGroup;

/**
* @param HashLookup $hashLookup
* @param TransclusionManager $transclusionManager
* @param RevisionStore $revisionStore
* @param RepoGroup $repoGroup
*/
public function __construct( HashLookup $hashLookup, RevisionStore $revisionStore, RepoGroup $repoGroup ) {
$this->hashLookup = $hashLookup;
public function __construct(
TransclusionManager $transclusionManager, RevisionStore $revisionStore, RepoGroup $repoGroup
) {
$this->transclusionManager = $transclusionManager;
$this->revisionStore = $revisionStore;
$this->repoGroup = $repoGroup;
}
Expand Down Expand Up @@ -63,15 +64,15 @@ public function onBeforeParserFetchFileAndTitle( $parser, $nt, &$options, &$desc
return true;
}

$hashes = $this->getHashesForRevision( $revision );
$hash = $this->getHashForTitle( $hashes, $nt );
$hashes = $this->transclusionManager->getTransclusionHashes( $revision );
$hash = $this->transclusionManager->getHashForTitle( $hashes, $nt );
if ( $hash === null ) {
// Image did not exist at the time of hashing, show broken link
$options['broken'] = true;
return true;
}

$oldFile = $this->hashLookup->getFileForHash( $hash, $file );
$oldFile = $this->transclusionManager->getFileForHash( $hash, $file );
if ( $oldFile->getSha1() === $file->getSha1() && $oldFile->getTimestamp() === $file->getTimestamp() ) {
// Showing latest
return true;
Expand All @@ -97,16 +98,16 @@ public function onBeforeParserFetchTemplateRevisionRecord(
if ( !$revision ) {
return;
}
$hashes = $this->getHashesForRevision( $revision );
$hashes = $this->transclusionManager->getTransclusionHashes( $revision );
if ( empty( $hashes ) ) {
return;
}
$hash = $this->getHashForTitle( $hashes, $title );
$hash = $this->transclusionManager->getHashForTitle( $hashes, $title );
if ( $hash === null ) {
$skip = true;
return;
}
$revRecord = $this->hashLookup->getRevisionForHash( $hash );
$revRecord = $this->transclusionManager->getRevisionForHash( $hash );
}

/**
Expand All @@ -120,27 +121,4 @@ private function getRevision( $title ): ?RevisionRecord {
}
return $this->revisionStore->getRevisionById( $oldid );
}

private function getHashesForRevision( RevisionRecord $revision ) {
$content = $revision->getContent( TransclusionHashes::SLOT_ROLE_TRANSCLUSION_HASHES );
if ( !$content instanceof TransclusionHashes ) {
return [];
}
return $content->getResourceHashes();
}

/**
* @param array $hashes
* @param LinkTarget|PageReference $title
* @return string|null
*/
private function getHashForTitle( array $hashes, $title ): ?string {
foreach ( $hashes as $hashEntity ) {
if ( $title->getNamespace() === $hashEntity->ns && $title->getDBkey() === $hashEntity->dbkey ) {
return $hashEntity->hash;
}
}

return null;
}
}
7 changes: 7 additions & 0 deletions includes/ServiceWiring.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use DataAccounting\Config\Handler;
use DataAccounting\HashLookup;
use DataAccounting\TransclusionManager;
use MediaWiki\MediaWikiServices;

return [
Expand All @@ -16,4 +17,10 @@
$services->getRevisionStore()
);
},
'DataAccountingTransclusionManager' => static function( MediaWikiServices $services ): TransclusionManager {
return new TransclusionManager(
$services->getTitleFactory(),
$services->get( 'DataAccountingHashLookup' )
);
}
];
117 changes: 117 additions & 0 deletions includes/TransclusionManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

namespace DataAccounting;

use DataAccounting\Content\TransclusionHashes;
use File;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\Page\PageReference;
use MediaWiki\Revision\RevisionRecord;
use TitleFactory;

class TransclusionManager {
public const STATE_NEW_VERSION = 'new-version';
public const STATE_HASH_CHANGED = 'hash-changed';
public const STATE_UNCHANGED = 'unchanged';

/** @var TitleFactory */
private $titleFactory;
/** @var HashLookup */
private $hashLookup;

public function __construct( TitleFactory $titleFactory, HashLookup $hashLookup ) {
$this->titleFactory = $titleFactory;
$this->hashLookup = $hashLookup;
}

public function getTransclusionState( RevisionRecord $revision ) {
$states = [];
$transclusions = $this->getTransclusionHashes( $revision );
foreach ( $transclusions as $transclusion ) {
$title = $this->titleFactory->makeTitle( $transclusion->ns, $transclusion->dbkey );
$latestHash = $this->hashLookup->getLatestHashForTitle( $title );
if ( $transclusion->hash !== null ) {
$hashExists = $this->hashLookup->getRevisionForHash( $transclusion->hash ) !== null;
if ( !$hashExists ) {
$states[$title->getPrefixedDBkey()] = [
'state' => static::STATE_HASH_CHANGED,
'hash' => $transclusion->hash,
'new_hash' => $this->hashLookup->getHashForRevision( $revision ),
];
continue;
}
}

if ( $latestHash !== $transclusion->hash ) {
$states[$title->getPrefixedDBkey()] = [
'state' => static::STATE_NEW_VERSION,
'hash' => $transclusion->hash,
'new_hash' => $latestHash
];
continue;
}
$states[$title->getPrefixedDBkey()] = [
'state' => static::STATE_UNCHANGED,
'hash' => $transclusion->hash
];
}

return $states;
}

/**
* @param RevisionRecord $revision
* @return array
*/
public function getTransclusionHashes( RevisionRecord $revision ): array {
$content = $revision->getContent( TransclusionHashes::SLOT_ROLE_TRANSCLUSION_HASHES );
if ( !$content instanceof TransclusionHashes ) {
return [];
}
return $content->getResourceHashes();
}

/**
* @param array $hashes
* @param LinkTarget|PageReference $title
* @return string|null
*/
public function getHashForTitle( array $hashes, $title ): ?string {
foreach ( $hashes as $hashEntity ) {
if ( $title->getNamespace() === $hashEntity->ns && $title->getDBkey() === $hashEntity->dbkey ) {
return $hashEntity->hash;
}
}

return null;
}

/**
* @param string $hash
* @param File $file
* @return File|null
*/
public function getFileForHash( string $hash, File $file ): ?File {
$revision = $this->hashLookup->getRevisionForHash( $hash );
if ( !$revision ) {
return null;
}
if ( $revision->isCurrent() ) {
return $file;
}
$oldFiles = $file->getHistory();
foreach( $oldFiles as $oldFile ) {
if ( $oldFile->getTimestamp() === $revision->getTimestamp() ) {
return $oldFile;
}
}
}

/**
* @param string $hash
* @return RevisionRecord|null
*/
public function getRevisionForHash( string $hash ): ?RevisionRecord {
return $this->hashLookup->getRevisionForHash( $hash );
}
}

0 comments on commit 0ea2c0d

Please sign in to comment.