Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
TTMServerMessageUpdateJob.php
Go to the documentation of this file.
1<?php
13use MediaWiki\Logger\LoggerFactory;
14use MediaWiki\MediaWikiServices;
15use Psr\Log\LoggerInterface;
16
37class TTMServerMessageUpdateJob extends Job {
42 protected const MAX_ERROR_RETRY = 4;
43
49 protected const WRITE_BACKOFF_EXPONENT = 7;
51 private $jobQueueGroup;
52 private const CHANNEL_NAME = 'Translate.TtmServerUpdates';
53 private LoggerInterface $logger;
54
60 public static function newJob( MessageHandle $handle, $command ) {
61 $job = new self( $handle->getTitle(), [ 'command' => $command ] );
62
63 return $job;
64 }
65
70 public function __construct( $title, $params = [] ) {
71 parent::__construct(
72 __CLASS__,
73 $title,
74 $params + [
75 'command' => 'rebuild',
76 'service' => null,
77 'errorCount' => 0,
78 ]
79 );
80
81 $this->jobQueueGroup = MediaWikiServices::getInstance()->getJobQueueGroup();
82 $this->logger = LoggerFactory::getInstance( self::CHANNEL_NAME );
83 }
84
89 public function run() {
90 $services = $this->getServersToUpdate( $this->params['service'] );
91 foreach ( $services as $serviceId => $service ) {
92 $this->runCommandWithRetry( $service, $serviceId );
93 }
94 return true;
95 }
96
98 public function allowRetries() {
99 return false;
100 }
101
107 private function runCommandWithRetry( WritableTtmServer $ttmServer, string $serviceName ): void {
108 try {
109 $this->runCommand( $ttmServer, $serviceName );
110 } catch ( Exception $e ) {
111 $this->requeueError( $serviceName, $e );
112 }
113 }
114
119 private function requeueError( $serviceName, $e ) {
120 $this->logger->warning(
121 'Exception thrown while running {command} on ' .
122 'service {service}: {errorMessage}',
123 [
124 'command' => $this->params['command'],
125 'service' => $serviceName,
126 'errorMessage' => $e->getMessage(),
127 'exception' => $e,
128 ]
129 );
130 if ( $this->params['errorCount'] >= self::MAX_ERROR_RETRY ) {
131 $this->logger->warning(
132 'Dropping failing job {command} for service {service} ' .
133 'after repeated failure',
134 [
135 'command' => $this->params['command'],
136 'service' => $serviceName,
137 ]
138 );
139 return;
140 }
141
142 $delay = self::backoffDelay( $this->params['errorCount'] );
143 $job = clone $this;
144 $job->params['errorCount']++;
145 $job->params['service'] = $serviceName;
146 $job->setDelay( $delay );
147 $this->logger->info(
148 'Update job reported failure on service {service}. ' .
149 'Requeueing job with delay of {delay}.',
150 [
151 'service' => $serviceName,
152 'delay' => $delay
153 ]
154 );
155 $this->resend( $job );
156 }
157
162 protected function resend( self $job ) {
163 $this->jobQueueGroup->push( $job );
164 }
165
166 private function runCommand( WritableTtmServer $ttmServer, string $serverName ) {
167 $handle = $this->getHandle();
168 $command = $this->params['command'];
169
170 if ( $command === 'delete' ) {
171 $this->updateItem( $ttmServer, $handle, null, false );
172 } elseif ( $command === 'rebuild' ) {
173 $this->updateMessage( $ttmServer, $handle );
174 } elseif ( $command === 'refresh' ) {
175 $this->updateTranslation( $ttmServer, $handle );
176 }
177
178 $this->logger->info(
179 "{command} command completed on {server} for {handle}",
180 [
181 'command' => $command,
182 'server' => $serverName,
183 'handle' => $handle->getTitle()->getPrefixedText()
184 ]
185 );
186 }
187
193 protected function getHandle() {
194 return new MessageHandle( $this->title );
195 }
196
203 protected function getTranslation( MessageHandle $handle ) {
204 return Utilities::getMessageContent(
205 $handle->getKey(),
206 $handle->getCode(),
207 $handle->getTitle()->getNamespace()
208 );
209 }
210
211 private function updateMessage( WritableTtmServer $ttmserver, MessageHandle $handle ) {
212 // Base page update, e.g. group change. Update everything.
213 $translations = Utilities::getTranslations( $handle );
214 foreach ( $translations as $page => $data ) {
215 $tTitle = Title::makeTitle( $this->title->getNamespace(), $page );
216 $tHandle = new MessageHandle( $tTitle );
217 $this->updateItem( $ttmserver, $tHandle, $data[0], $tHandle->isFuzzy() );
218 }
219 }
220
221 private function updateTranslation( WritableTtmServer $ttmserver, MessageHandle $handle ) {
222 // Update only this translation
223 $translation = $this->getTranslation( $handle );
224 $this->updateItem( $ttmserver, $handle, $translation, $handle->isFuzzy() );
225 }
226
227 private function updateItem( WritableTtmServer $ttmserver, MessageHandle $handle, $text, $fuzzy ) {
228 if ( $fuzzy ) {
229 $text = null;
230 }
231 $ttmserver->update( $handle, $text );
232 }
233
234 private function getServersToUpdate( ?string $requestedServiceId ): array {
235 $ttmServerFactory = Services::getInstance()->getTtmServerFactory();
236 if ( $requestedServiceId ) {
237 if ( !$ttmServerFactory->has( $requestedServiceId ) ) {
238 $this->logger->warning(
239 'Received update job for a an unknown service {service}.',
240 [ 'service' => $requestedServiceId ]
241 );
242 return [];
243 }
244
245 return [ $requestedServiceId => $ttmServerFactory->create( $requestedServiceId ) ];
246 }
247
248 try {
249 return $ttmServerFactory->getWritable();
250 } catch ( Exception $e ) {
251 $this->logger->error(
252 'There was an error while fetching writable TTM services. Error: {error}',
253 [ 'error' => $e->getMessage() ]
254 );
255 }
256
257 return [];
258 }
259
271 public function setDelay( $delay ) {
272 $jobQueue = $this->jobQueueGroup->get( $this->getType() );
273 if ( !$delay || !$jobQueue->delayedJobsEnabled() ) {
274 return;
275 }
276 $oldTime = $this->getReleaseTimestamp();
277 $newTime = time() + $delay;
278 if ( $oldTime !== null && $oldTime >= $newTime ) {
279 return;
280 }
281 $this->params[ 'jobReleaseTimestamp' ] = $newTime;
282 }
283
290 public static function backoffDelay( $errorCount ) {
291 return ceil( pow(
292 2,
293 static::WRITE_BACKOFF_EXPONENT + rand( 0, min( $errorCount, 4 ) )
294 ) );
295 }
296}
Minimal service container.
Definition Services.php:44
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:31
Class for pointing to messages, like Title class is for titles.
isFuzzy()
Check if a title is marked as fuzzy.
getTitle()
Get the original title.
getCode()
Returns the language code.
getKey()
Returns the identified or guessed message key.
Job for updating translation memory.
const MAX_ERROR_RETRY
Number of retries allowed, 4 means we attempt to run the job 5 times (1 initial attempt + 4 retries).
static newJob(MessageHandle $handle, $command)
getTranslation(MessageHandle $handle)
Extracted for testing purpose.
run()
Fetch all the translations and update them.
resend(self $job)
Extracted for testing purpose.
getHandle()
Extracted for testing purpose.
setDelay( $delay)
Set a delay for this job.
const WRITE_BACKOFF_EXPONENT
Constant used by backoffDelay().
Interface for TTMServer that can be updated.
update(MessageHandle $handle, ?string $targetText)
Shovels the new translation into translation memory.