Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
65.22% covered (warning)
65.22%
30 / 46
25.00% covered (danger)
25.00%
1 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
FlushUtterancesFromStoreByExpirationJobQueue
65.22% covered (warning)
65.22%
30 / 46
25.00% covered (danger)
25.00%
1 / 4
10.69
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 maybeQueueJob
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 queueJob
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 isTimeToQueueJob
81.82% covered (warning)
81.82%
18 / 22
0.00% covered (danger)
0.00%
0 / 1
4.10
1<?php
2
3namespace MediaWiki\Wikispeech\Utterance;
4
5/**
6 * @file
7 * @ingroup Extensions
8 * @license GPL-2.0-or-later
9 */
10
11use DateTime;
12use MediaWiki\Logger\LoggerFactory;
13use MediaWiki\MediaWikiServices;
14use Psr\Log\LoggerInterface;
15use Title;
16use WANObjectCache;
17
18/**
19 * Periodically flushes out old utterances from the utterance store.
20 *
21 * This is an important part of the extension architecture, making sure
22 * that utterance audio files and synthesis metadata files are updated
23 * when underlying voice synthesis is improved.
24 *
25 * The job is currently queued at the same time as utterances are created,
26 * with a configurable minimum amount of minutes delay between queues by
27 * executing {@link FlushUtterancesFromStoreByExpirationJob::maybeQueueJob()}.
28 *
29 * This is more or less equivalent to a semi dysfunctional cron tab. It might
30 * make sense to consider implement queuing the job periodically using a real
31 * cron tab, but that would also require more manual non standard work when
32 * setting up Wikispeech. It might also cause problems with future upgrades.
33 *
34 * One downside to the current approach is, that in a highly distributed
35 * environment with many active users there might be multiple jobs queued
36 * before the WAN cache is synchronized. Executing this job multiple times
37 * is however not a problem in it self more than being a bit of waste of
38 * resources. In the case where Wikispeech is configured to only flush a
39 * limited batch of utterances in each job, it might actually be a good
40 * thing that multiple jobs are queued.
41 *
42 * @see UtteranceStore::flushUtterancesByExpirationDate()
43 * @see FlushUtterancesFromStoreByExpirationJob
44 *
45 * @since 0.1.7
46 */
47class FlushUtterancesFromStoreByExpirationJobQueue {
48
49    /** @var LoggerInterface */
50    private $logger;
51
52    /** @var WANObjectCache */
53    private $cache;
54
55    /** @var string */
56    private $cacheKey;
57
58    /** @var string WAN cache key class namespace */
59    public static $cacheKeyClass = 'Wikispeech.flushUtterancesFromStoreByExpirationJob';
60
61    /** @var string WAN cache key component used by pseudo-crontab */
62    public static $cacheKeyComponentPreviousDateTimeQueued = 'previousDateTimeQueued';
63
64    /** @var int */
65    private $minimumMinutesBetweenFlushExpiredUtterancesJobs;
66
67    /**
68     * @since 0.1.7
69     */
70    public function __construct() {
71        $this->logger = LoggerFactory::getInstance( 'Wikispeech' );
72        $config = MediaWikiServices::getInstance()
73            ->getConfigFactory()
74            ->makeConfig( 'wikispeech' );
75        $this->minimumMinutesBetweenFlushExpiredUtterancesJobs = intval(
76            $config->get( 'WikispeechMinimumMinutesBetweenFlushExpiredUtterancesJobs' )
77        );
78        $this->cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
79        $this->cacheKey = $this->cache->makeKey(
80            self::$cacheKeyClass,
81            self::$cacheKeyComponentPreviousDateTimeQueued
82        );
83    }
84
85    /**
86     * Queues a job if criteria to do so has been met.
87     *
88     * Convenience method that calls {@link queueJob()}
89     * if {@link isTimeToQueueJob()} returns true.
90     *
91     * @since 0.1.7
92     * @return bool Whether or not job was queued.
93     */
94    public function maybeQueueJob() {
95        if ( $this->isTimeToQueueJob() ) {
96            $this->queueJob();
97            return true;
98        }
99        return false;
100    }
101
102    /**
103     * Queues a job.
104     *
105     * @since 0.1.7
106     */
107    public function queueJob() {
108        $this->cache->set( $this->cacheKey, new DateTime() );
109        $jobQueueGroup = MediaWikiServices::getInstance()->getJobQueueGroup();
110        $jobQueueGroup->push(
111            new FlushUtterancesFromStoreByExpirationJob(
112                Title::newMainPage(),
113                []
114            )
115        );
116    }
117
118    /**
119     * Evaluates if the criteria has been met to queue a job.
120     * Currently that means that at least as many minutes as defined in config
121     * has past since previous queueing of the job.
122     *
123     * @since 0.1.7
124     * @return bool Whether or not it's time to queue the flush job
125     */
126    public function isTimeToQueueJob() {
127        if ( !$this->minimumMinutesBetweenFlushExpiredUtterancesJobs ) {
128            return false;
129        }
130        $previousDateTimeQueued = $this->cache->get( $this->cacheKey );
131        $this->logger->debug( __METHOD__ . ': ' .
132            'previousDateTimeQueued {previousDateTimeQueued}',
133            [ 'previousDateTimeQueued' => $previousDateTimeQueued ]
134        );
135        if ( !$previousDateTimeQueued ) {
136            // Either the cached value was never set or the cache is offline.
137            // In case of the latter, avoid queuing jobs on each request
138            // by requiring a cached value. This will set the initial cached value.
139            $this->cache->set( $this->cacheKey, new DateTime() );
140            return false;
141        }
142        $nextPossibleJobQueueTimestamp = date_add(
143            $previousDateTimeQueued,
144            date_interval_create_from_date_string(
145                $this->minimumMinutesBetweenFlushExpiredUtterancesJobs . ' minutes'
146            )
147        );
148        if ( !$nextPossibleJobQueueTimestamp ) {
149            $this->logger->error( __METHOD__ . ': ' .
150                'Unable to calculate next possible job queue DateTime.'
151            );
152            return false;
153        }
154        return new DateTime() >= $nextPossibleJobQueueTimestamp;
155    }
156}