Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
86.27% |
44 / 51 |
|
50.00% |
2 / 4 |
CRAP | |
0.00% |
0 / 1 |
ClearUserWatchlistJob | |
86.27% |
44 / 51 |
|
50.00% |
2 / 4 |
10.26 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
newForUser | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
run | |
86.36% |
38 / 44 |
|
0.00% |
0 / 1 |
7.12 | |||
getDeduplicationInfo | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | use MediaWiki\MainConfigNames; |
4 | use MediaWiki\MediaWikiServices; |
5 | use MediaWiki\User\UserIdentity; |
6 | |
7 | /** |
8 | * Job to clear a users watchlist in batches. |
9 | * |
10 | * @since 1.31 |
11 | * @ingroup JobQueue |
12 | * @author Addshore |
13 | */ |
14 | class ClearUserWatchlistJob extends Job implements GenericParameterJob { |
15 | /** |
16 | * @param array $params |
17 | * - userId, The ID for the user whose watchlist is being cleared. |
18 | * - maxWatchlistId, The maximum wl_id at the time the job was first created, |
19 | */ |
20 | public function __construct( array $params ) { |
21 | parent::__construct( 'clearUserWatchlist', $params ); |
22 | |
23 | $this->removeDuplicates = true; |
24 | } |
25 | |
26 | /** |
27 | * @param UserIdentity $user User to clear the watchlist for. |
28 | * @param int $maxWatchlistId The maximum wl_id at the time the job was first created. |
29 | * |
30 | * @return ClearUserWatchlistJob |
31 | */ |
32 | public static function newForUser( UserIdentity $user, $maxWatchlistId ) { |
33 | return new self( [ 'userId' => $user->getId(), 'maxWatchlistId' => $maxWatchlistId ] ); |
34 | } |
35 | |
36 | public function run() { |
37 | $updateRowsPerQuery = MediaWikiServices::getInstance()->getMainConfig()->get( |
38 | MainConfigNames::UpdateRowsPerQuery ); |
39 | $userId = $this->params['userId']; |
40 | $maxWatchlistId = $this->params['maxWatchlistId']; |
41 | $batchSize = $updateRowsPerQuery; |
42 | |
43 | $loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer(); |
44 | $dbw = $loadBalancer->getConnectionRef( DB_PRIMARY ); |
45 | $dbr = $loadBalancer->getConnectionRef( DB_REPLICA ); |
46 | |
47 | // Wait before lock to try to reduce time waiting in the lock. |
48 | if ( !$loadBalancer->waitForPrimaryPos( $dbr ) ) { |
49 | $this->setLastError( 'Timed out waiting for replica to catch up before lock' ); |
50 | return false; |
51 | } |
52 | |
53 | // Use a named lock so that jobs for this user see each others' changes |
54 | $lockKey = "{{$dbw->getDomainID()}}:ClearUserWatchlist:$userId"; // per-wiki |
55 | $scopedLock = $dbw->getScopedLockAndFlush( $lockKey, __METHOD__, 10 ); |
56 | if ( !$scopedLock ) { |
57 | $this->setLastError( "Could not acquire lock '$lockKey'" ); |
58 | return false; |
59 | } |
60 | |
61 | if ( !$loadBalancer->waitForPrimaryPos( $dbr ) ) { |
62 | $this->setLastError( 'Timed out waiting for replica to catch up within lock' ); |
63 | return false; |
64 | } |
65 | |
66 | // Clear any stale REPEATABLE-READ snapshot |
67 | $dbr->flushSnapshot( __METHOD__ ); |
68 | |
69 | $watchlistIds = $dbr->newSelectQueryBuilder() |
70 | ->select( 'wl_id' ) |
71 | ->from( 'watchlist' ) |
72 | ->where( [ 'wl_user' => $userId ] ) |
73 | ->andWhere( $dbr->expr( 'wl_id', '<=', $maxWatchlistId ) ) |
74 | ->limit( $batchSize ) |
75 | ->caller( __METHOD__ )->fetchFieldValues(); |
76 | if ( count( $watchlistIds ) == 0 ) { |
77 | return true; |
78 | } |
79 | |
80 | $dbw->newDeleteQueryBuilder() |
81 | ->deleteFrom( 'watchlist' ) |
82 | ->where( [ 'wl_id' => $watchlistIds ] ) |
83 | ->caller( __METHOD__ )->execute(); |
84 | if ( MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::WatchlistExpiry ) ) { |
85 | $dbw->newDeleteQueryBuilder() |
86 | ->deleteFrom( 'watchlist_expiry' ) |
87 | ->where( [ 'we_item' => $watchlistIds ] ) |
88 | ->caller( __METHOD__ )->execute(); |
89 | } |
90 | |
91 | // Commit changes and remove lock before inserting next job. |
92 | $lbf = MediaWikiServices::getInstance()->getDBLoadBalancerFactory(); |
93 | $lbf->commitPrimaryChanges( __METHOD__ ); |
94 | unset( $scopedLock ); |
95 | |
96 | if ( count( $watchlistIds ) === (int)$batchSize ) { |
97 | // Until we get less results than the limit, recursively push |
98 | // the same job again. |
99 | MediaWikiServices::getInstance()->getJobQueueGroup()->push( new self( $this->getParams() ) ); |
100 | } |
101 | |
102 | return true; |
103 | } |
104 | |
105 | public function getDeduplicationInfo() { |
106 | $info = parent::getDeduplicationInfo(); |
107 | // This job never has a namespace or title so we can't use it for deduplication |
108 | unset( $info['namespace'] ); |
109 | unset( $info['title'] ); |
110 | return $info; |
111 | } |
112 | |
113 | } |