Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
67.27% |
37 / 55 |
|
63.64% |
7 / 11 |
CRAP | |
0.00% |
0 / 1 |
TranslatorActivity | |
67.27% |
37 / 55 |
|
63.64% |
7 / 11 |
36.46 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
inLanguage | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
5.02 | |||
getFromCache | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getCacheKey | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isStale | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
queueCacheRefresh | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
doQueryAndCache | |
87.50% |
14 / 16 |
|
0.00% |
0 / 1 |
2.01 | |||
addToCache | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
updateAllLanguages | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
updateLanguage | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
isValidLanguage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\Statistics; |
5 | |
6 | use InvalidArgumentException; |
7 | use JobQueueGroup; |
8 | use MediaWiki\Extension\Translate\Utilities\Utilities; |
9 | use MediaWiki\Languages\LanguageNameUtils; |
10 | use MediaWiki\PoolCounter\PoolCounterWorkViaCallback; |
11 | use Wikimedia\ObjectCache\BagOStuff; |
12 | use Wikimedia\Timestamp\ConvertibleTimestamp; |
13 | |
14 | /** |
15 | * Handles caching of translator activity. |
16 | * |
17 | * @author Niklas Laxström |
18 | * @license GPL-2.0-or-later |
19 | * @since 2020.04 |
20 | */ |
21 | class TranslatorActivity { |
22 | public const CACHE_TIME = 3 * 24 * 3600; |
23 | // 25 hours so that it's easy to configure the maintenance script run daily |
24 | public const CACHE_STALE = 25 * 3600; |
25 | private BagOStuff $cache; |
26 | private TranslatorActivityQuery $query; |
27 | private JobQueueGroup $jobQueue; |
28 | |
29 | public function __construct( |
30 | BagOStuff $cache, |
31 | TranslatorActivityQuery $query, |
32 | JobQueueGroup $jobQueue |
33 | ) { |
34 | $this->cache = $cache; |
35 | $this->query = $query; |
36 | $this->jobQueue = $jobQueue; |
37 | } |
38 | |
39 | /** |
40 | * Get translations activity for a given language. |
41 | * @throws StatisticsUnavailable If loading statistics is temporarily not possible. |
42 | */ |
43 | public function inLanguage( string $language ): array { |
44 | if ( !$this->isValidLanguage( $language ) ) { |
45 | throw new InvalidArgumentException( "Invalid language tag '$language'" ); |
46 | } |
47 | |
48 | $cachedValue = $this->getFromCache( $language ); |
49 | |
50 | if ( is_array( $cachedValue ) ) { |
51 | if ( $this->isStale( $cachedValue ) ) { |
52 | $this->queueCacheRefresh( $language ); |
53 | } |
54 | |
55 | return $cachedValue; |
56 | } |
57 | |
58 | $queriedValue = $this->doQueryAndCache( $language ); |
59 | if ( !$queriedValue ) { |
60 | throw new StatisticsUnavailable( "Unable to load stats" ); |
61 | } |
62 | |
63 | return $queriedValue; |
64 | } |
65 | |
66 | private function getFromCache( string $language ) { |
67 | $cacheKey = $this->getCacheKey( $language ); |
68 | return $this->cache->get( $cacheKey ); |
69 | } |
70 | |
71 | private function getCacheKey( string $language ): string { |
72 | return $this->cache->makeKey( 'translate-translator-activity-v4', $language ); |
73 | } |
74 | |
75 | private function isStale( array $value ): bool { |
76 | $age = intval( ConvertibleTimestamp::now( TS_UNIX ) ) - $value['asOfTime']; |
77 | return $age >= self::CACHE_STALE; |
78 | } |
79 | |
80 | private function queueCacheRefresh( string $language ): void { |
81 | $job = UpdateTranslatorActivityJob::newJobForLanguage( $language ); |
82 | $this->jobQueue->push( $job ); |
83 | } |
84 | |
85 | private function doQueryAndCache( string $language ) { |
86 | $now = (int)ConvertibleTimestamp::now( TS_UNIX ); |
87 | |
88 | $work = new PoolCounterWorkViaCallback( |
89 | 'TranslateFetchTranslators', "TranslateFetchTranslators-$language", [ |
90 | 'doWork' => function () use ( $language, $now ) { |
91 | $users = $this->query->inLanguage( $language ); |
92 | $data = [ 'users' => $users, 'asOfTime' => $now ]; |
93 | $this->addToCache( $data, $language ); |
94 | return $data; |
95 | }, |
96 | 'doCachedWork' => function () use ( $language ) { |
97 | $data = $this->getFromCache( $language ); |
98 | // Use new cache value from other thread |
99 | return is_array( $data ) ? $data : false; |
100 | }, |
101 | ] |
102 | ); |
103 | |
104 | return $work->execute(); |
105 | } |
106 | |
107 | private function addToCache( array $value, string $language ): void { |
108 | $cacheKey = $this->getCacheKey( $language ); |
109 | $this->cache->set( $cacheKey, $value, self::CACHE_TIME ); |
110 | } |
111 | |
112 | /** Update cache for all languages, even if not stale. */ |
113 | public function updateAllLanguages(): void { |
114 | $now = (int)ConvertibleTimestamp::now( TS_UNIX ); |
115 | |
116 | $data = $this->query->inAllLanguages(); |
117 | // In case there is no activity for a supported languages, cache empty results |
118 | $validLanguages = Utilities::getLanguageNames( LanguageNameUtils::AUTONYMS ); |
119 | foreach ( $validLanguages as $language ) { |
120 | $data[$language] ??= []; |
121 | } |
122 | |
123 | foreach ( $data as $language => $users ) { |
124 | if ( !$this->isValidLanguage( $language ) ) { |
125 | continue; |
126 | } |
127 | |
128 | $data = [ 'users' => $users, 'asOfTime' => $now ]; |
129 | $this->addToCache( $data, $language ); |
130 | } |
131 | } |
132 | |
133 | /** |
134 | * Update cache for one language, even if not stale. |
135 | * @throws StatisticsUnavailable If loading statistics is temporarily not possible. |
136 | */ |
137 | public function updateLanguage( string $language ): void { |
138 | if ( !$this->isValidLanguage( $language ) ) { |
139 | throw new InvalidArgumentException( "Invalid language tag '$language'" ); |
140 | } |
141 | |
142 | $queriedValue = $this->doQueryAndCache( $language ); |
143 | if ( !$queriedValue ) { |
144 | throw new StatisticsUnavailable( 'Unable to load stats' ); |
145 | } |
146 | } |
147 | |
148 | private function isValidLanguage( string $language ): bool { |
149 | return Utilities::isSupportedLanguageCode( $language ); |
150 | } |
151 | } |