Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
TTMServerMessageUpdateJob.php
Go to the documentation of this file.
1<?php
14use MediaWiki\Logger\LoggerFactory;
15use MediaWiki\MediaWikiServices;
16use MediaWiki\Title\Title;
17use Psr\Log\LoggerInterface;
18
34class TTMServerMessageUpdateJob extends Job {
39 protected const MAX_ERROR_RETRY = 4;
40
46 protected const WRITE_BACKOFF_EXPONENT = 7;
48 private $jobQueueGroup;
49 private const CHANNEL_NAME = 'Translate.TtmServerUpdates';
50 private LoggerInterface $logger;
51
57 public static function newJob( MessageHandle $handle, $command ) {
58 $job = new self( $handle->getTitle(), [ 'command' => $command ] );
59
60 return $job;
61 }
62
67 public function __construct( $title, $params = [] ) {
68 parent::__construct(
69 'TTMServerMessageUpdateJob',
70 $title,
71 $params + [
72 'command' => 'rebuild',
73 'service' => null,
74 'errorCount' => 0,
75 ]
76 );
77
78 $this->jobQueueGroup = MediaWikiServices::getInstance()->getJobQueueGroup();
79 $this->logger = LoggerFactory::getInstance( self::CHANNEL_NAME );
80 }
81
86 public function run() {
87 $services = $this->getServersToUpdate( $this->params['service'] );
88 foreach ( $services as $serviceId => $service ) {
89 $this->runCommandWithRetry( $service, $serviceId );
90 }
91 return true;
92 }
93
95 public function allowRetries() {
96 return false;
97 }
98
104 private function runCommandWithRetry( WritableTtmServer $ttmServer, string $serviceName ): void {
105 try {
106 $this->runCommand( $ttmServer, $serviceName );
107 } catch ( Exception $e ) {
108 $this->requeueError( $serviceName, $e );
109 }
110 }
111
116 private function requeueError( $serviceName, $e ) {
117 $this->logger->warning(
118 'Exception thrown while running {command} on ' .
119 'service {service}: {errorMessage}',
120 [
121 'command' => $this->params['command'],
122 'service' => $serviceName,
123 'errorMessage' => $e->getMessage(),
124 'exception' => $e,
125 ]
126 );
127 if ( $this->params['errorCount'] >= self::MAX_ERROR_RETRY ) {
128 $this->logger->warning(
129 'Dropping failing job {command} for service {service} ' .
130 'after repeated failure',
131 [
132 'command' => $this->params['command'],
133 'service' => $serviceName,
134 ]
135 );
136 return;
137 }
138
139 $delay = self::backoffDelay( $this->params['errorCount'] );
140 $job = clone $this;
141 $job->params['errorCount']++;
142 $job->params['service'] = $serviceName;
143 $job->setDelay( $delay );
144 $this->logger->info(
145 'Update job reported failure on service {service}. ' .
146 'Requeueing job with delay of {delay}.',
147 [
148 'service' => $serviceName,
149 'delay' => $delay
150 ]
151 );
152 $this->resend( $job );
153 }
154
159 protected function resend( self $job ) {
160 $this->jobQueueGroup->push( $job );
161 }
162
163 private function runCommand( WritableTtmServer $ttmServer, string $serverName ) {
164 $handle = $this->getHandle();
165 $command = $this->params['command'];
166
167 if ( $command === 'delete' ) {
168 $this->updateItem( $ttmServer, $handle, null, false );
169 } elseif ( $command === 'rebuild' ) {
170 $this->updateMessage( $ttmServer, $handle );
171 } elseif ( $command === 'refresh' ) {
172 $this->updateTranslation( $ttmServer, $handle );
173 }
174
175 $this->logger->info(
176 "{command} command completed on {server} for {handle}",
177 [
178 'command' => $command,
179 'server' => $serverName,
180 'handle' => $handle->getTitle()->getPrefixedText()
181 ]
182 );
183 }
184
190 protected function getHandle() {
191 return new MessageHandle( $this->title );
192 }
193
200 protected function getTranslation( MessageHandle $handle ) {
201 return Utilities::getMessageContent(
202 $handle->getKey(),
203 $handle->getCode(),
204 $handle->getTitle()->getNamespace()
205 );
206 }
207
208 private function updateMessage( WritableTtmServer $ttmserver, MessageHandle $handle ) {
209 // Base page update, e.g. group change. Update everything.
210 $translations = Utilities::getTranslations( $handle );
211 foreach ( $translations as $page => $data ) {
212 $tTitle = Title::makeTitle( $this->title->getNamespace(), $page );
213 $tHandle = new MessageHandle( $tTitle );
214 $this->updateItem( $ttmserver, $tHandle, $data[0], $tHandle->isFuzzy() );
215 }
216 }
217
218 private function updateTranslation( WritableTtmServer $ttmserver, MessageHandle $handle ) {
219 // Update only this translation
220 $translation = $this->getTranslation( $handle );
221 $this->updateItem( $ttmserver, $handle, $translation, $handle->isFuzzy() );
222 }
223
224 private function updateItem( WritableTtmServer $ttmserver, MessageHandle $handle, $text, $fuzzy ) {
225 if ( $fuzzy ) {
226 $text = null;
227 }
228 $ttmserver->update( $handle, $text );
229 }
230
231 private function getServersToUpdate( ?string $requestedServiceId ): array {
232 $ttmServerFactory = Services::getInstance()->getTtmServerFactory();
233 if ( $requestedServiceId ) {
234 if ( !$ttmServerFactory->has( $requestedServiceId ) ) {
235 $this->logger->warning(
236 'Received update job for a an unknown service {service}.',
237 [ 'service' => $requestedServiceId ]
238 );
239 return [];
240 }
241
242 return [ $requestedServiceId => $ttmServerFactory->create( $requestedServiceId ) ];
243 }
244
245 try {
246 return $ttmServerFactory->getWritable();
247 } catch ( Exception $e ) {
248 $this->logger->error(
249 'There was an error while fetching writable TTM services. Error: {error}',
250 [ 'error' => $e->getMessage() ]
251 );
252 }
253
254 return [];
255 }
256
268 public function setDelay( $delay ) {
269 $jobQueue = $this->jobQueueGroup->get( $this->getType() );
270 if ( !$delay || !$jobQueue->delayedJobsEnabled() ) {
271 return;
272 }
273 $oldTime = $this->getReleaseTimestamp();
274 $newTime = time() + $delay;
275 if ( $oldTime !== null && $oldTime >= $newTime ) {
276 return;
277 }
278 $this->params[ 'jobReleaseTimestamp' ] = $newTime;
279 }
280
287 public static function backoffDelay( $errorCount ) {
288 return ceil( pow(
289 2,
290 static::WRITE_BACKOFF_EXPONENT + rand( 0, min( $errorCount, 4 ) )
291 ) );
292 }
293}
Class for pointing to messages, like Title class is for titles.
getKey()
Returns the identified or guessed message key.
Minimal service container.
Definition Services.php:58
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:31
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.