Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 73 |
|
0.00% |
0 / 4 |
CRAP | |
0.00% |
0 / 1 |
UpdateMessageBundleJob | |
0.00% |
0 / 73 |
|
0.00% |
0 / 4 |
132 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
newJob | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
run | |
0.00% |
0 / 57 |
|
0.00% |
0 / 1 |
30 | |||
getMessageSubscriptionState | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\MessageBundleTranslation; |
5 | |
6 | use Job; |
7 | use MediaWiki\Extension\Translate\LogNames; |
8 | use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroups; |
9 | use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroupSubscription; |
10 | use MediaWiki\Extension\Translate\MessageLoading\RebuildMessageIndexJob; |
11 | use MediaWiki\Extension\Translate\Services; |
12 | use MediaWiki\Extension\Translate\Statistics\MessageGroupStats; |
13 | use MediaWiki\Extension\Translate\Synchronization\UpdateMessageJob; |
14 | use MediaWiki\Logger\LoggerFactory; |
15 | use MediaWiki\MediaWikiServices; |
16 | use MediaWiki\Title\Title; |
17 | |
18 | /** |
19 | * @author Niklas Laxström |
20 | * @license GPL-2.0-or-later |
21 | * @since 2021.12 |
22 | */ |
23 | class UpdateMessageBundleJob extends Job { |
24 | /** @inheritDoc */ |
25 | public function __construct( Title $title, $params = [] ) { |
26 | parent::__construct( 'UpdateMessageBundle', $title, $params ); |
27 | } |
28 | |
29 | public static function newJob( Title $bundlePageTitle, int $revisionId, ?int $previousRevisionId ): self { |
30 | return new self( |
31 | $bundlePageTitle, |
32 | [ |
33 | 'revisionId' => $revisionId, |
34 | 'previousRevisionId' => $previousRevisionId, |
35 | ] |
36 | ); |
37 | } |
38 | |
39 | /** @inheritDoc */ |
40 | public function run(): bool { |
41 | $mwInstance = MediaWikiServices::getInstance(); |
42 | $lb = $mwInstance->getDBLoadBalancerFactory(); |
43 | $jobQueue = $mwInstance->getJobQueueGroup(); |
44 | $logger = LoggerFactory::getInstance( LogNames::MESSAGE_BUNDLE ); |
45 | $messageIndex = Services::getInstance()->getMessageIndex(); |
46 | $messageGroupSubscription = Services::getInstance()->getMessageGroupSubscription(); |
47 | |
48 | $logger->info( 'UpdateMessageBundleJob: Starting job for: ' . $this->getTitle()->getPrefixedText() ); |
49 | |
50 | // Not sure if this is necessary, but it should ensure that this job, which was created |
51 | // when a revision was saved, can read that revision from the replica. In addition, this |
52 | // may potentially do a bunch of more writes that could cause more replication lag. |
53 | if ( !$lb->waitForReplication() ) { |
54 | $logger->warning( 'UpdateMessageBundleJob: Continuing despite replication lag' ); |
55 | } |
56 | |
57 | // Setup |
58 | $bundlePageTitle = $this->getTitle(); |
59 | $name = $bundlePageTitle->getPrefixedText(); |
60 | $pageId = $bundlePageTitle->getId(); |
61 | $groupId = MessageBundleMessageGroup::getGroupId( $name ); |
62 | $params = $this->getParams(); |
63 | // We don't care about the group description or label, so no need to pass it through |
64 | $group = new MessageBundleMessageGroup( |
65 | $groupId, $name, $pageId, $params['revisionId'], null, null |
66 | ); |
67 | $messages = $group->getDefinitions(); |
68 | $previousMessages = []; |
69 | if ( $params['previousRevisionId'] ) { |
70 | $groupPreviousVersion = new MessageBundleMessageGroup( |
71 | $groupId, $name, $pageId, $params['previousRevisionId'], null, null |
72 | ); |
73 | $previousMessages = $groupPreviousVersion->getDefinitions(); |
74 | } |
75 | |
76 | // Fill in the front-cache. Ideally this should be done right away, but hopefully |
77 | // this is okay since we only trigger message group cache rebuild later in this job. |
78 | // It's possible that some other change triggers it earlier and makes the new group |
79 | // available before this step is complete. |
80 | $newKeys = array_diff( array_keys( $messages ), array_keys( $previousMessages ) ); |
81 | $messageIndex->storeInterim( $group, $newKeys ); |
82 | |
83 | // Create jobs that will update the '/' source language pages. These pages should |
84 | // exist so that the editor can show differences for changed messages. Also compare |
85 | // against previous version (if any) to determine whether to mark translations as |
86 | // outdated. There is no support for renames. |
87 | $jobs = []; |
88 | $namespace = $group->getNamespace(); |
89 | $code = $group->getSourceLanguage(); |
90 | foreach ( $messages as $key => $value ) { |
91 | $title = Title::makeTitle( $namespace, "$key/$code" ); |
92 | $subscriptionState = $this->getMessageSubscriptionState( $previousMessages, $newKeys, $key, $value ); |
93 | $fuzzy = $subscriptionState === null; |
94 | $jobs[] = UpdateMessageJob::newJob( $title, $value, $fuzzy ); |
95 | |
96 | if ( $subscriptionState ) { |
97 | $messageGroupSubscription->queueMessage( $title, $subscriptionState, $groupId ); |
98 | } |
99 | } |
100 | $jobQueue->push( $jobs ); |
101 | $logger->info( |
102 | 'UpdateMessageBundleJob: Added {number} UpdateMessageJobs to the job queue for: {title}', |
103 | [ |
104 | 'number' => count( $jobs ), |
105 | 'title' => $name |
106 | ] |
107 | ); |
108 | |
109 | // TODO: Ideally we would only invalidate message bundle message group cache |
110 | MessageGroups::singleton()->recache(); |
111 | |
112 | // Schedule message index update. Thanks to front caching, it is okay if this takes |
113 | // a while (and on large wikis it does take a while!). Running it as a separate job |
114 | // also allows de-duplication. |
115 | $job = RebuildMessageIndexJob::newJob(); |
116 | $jobQueue->push( $job ); |
117 | $logger->info( |
118 | 'UpdateMessageBundleJob: {title}: Queue RebuildMessageIndexJob', |
119 | [ 'title' => $name ] |
120 | ); |
121 | |
122 | // Refresh or fill translations statistics. If this a new group, this prevents |
123 | // calculating the stats on the fly during read requests. If an existing group, this |
124 | // makes sure that the statistics are up-to-date. |
125 | MessageGroupStats::forGroup( |
126 | $groupId, |
127 | MessageGroupStats::FLAG_NO_CACHE | MessageGroupStats::FLAG_IMMEDIATE_WRITES |
128 | ); |
129 | |
130 | $messageGroupSubscription->queueNotificationJob(); |
131 | |
132 | return true; |
133 | } |
134 | |
135 | /** |
136 | * Return a message subscription state based on whether a message is new, updated |
137 | * or null if it hasn't been changed at all. |
138 | */ |
139 | private function getMessageSubscriptionState( |
140 | ?array $previousMessages, |
141 | array $newKeys, |
142 | string $key, |
143 | string $value |
144 | ): ?string { |
145 | if ( in_array( $key, $newKeys ) ) { |
146 | return MessageGroupSubscription::STATE_ADDED; |
147 | } |
148 | |
149 | $previousValue = $previousMessages[$key] ?? null; |
150 | $isFuzzy = $previousMessages !== null && $previousValue !== $value; |
151 | if ( $isFuzzy ) { |
152 | return MessageGroupSubscription::STATE_UPDATED; |
153 | } |
154 | |
155 | return null; |
156 | } |
157 | } |