Addshore has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/277350

Change subject: Introduce ClearUserWatchlistJob
......................................................................

Introduce ClearUserWatchlistJob

Change-Id: Icea573a10078ea3f09dc2e4e9fdc737bf639935d
---
M autoload.php
M includes/DefaultSettings.php
M includes/WatchedItemStore.php
A includes/jobqueue/jobs/ClearUserWatchlistJob.php
A tests/phpunit/includes/jobqueue/jobs/ClearUserWatchlistJobTest.php
5 files changed, 189 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/50/277350/1

diff --git a/autoload.php b/autoload.php
index e74df0a..183ed08 100644
--- a/autoload.php
+++ b/autoload.php
@@ -236,6 +236,7 @@
        'CleanupPreferences' => __DIR__ . '/maintenance/cleanupPreferences.php',
        'CleanupRemovedModules' => __DIR__ . 
'/maintenance/cleanupRemovedModules.php',
        'CleanupSpam' => __DIR__ . '/maintenance/cleanupSpam.php',
+       'ClearUserWatchlistJob' => __DIR__ . 
'/includes/jobqueue/jobs/ClearUserWatchlistJob.php',
        'ClearInterwikiCache' => __DIR__ . 
'/maintenance/clearInterwikiCache.php',
        'CliInstaller' => __DIR__ . '/includes/installer/CliInstaller.php',
        'CloneDatabase' => __DIR__ . '/includes/db/CloneDatabase.php',
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index c04602c..1071fbc 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -6930,6 +6930,7 @@
        'refreshLinksDynamic' => 'RefreshLinksJob',
        'activityUpdateJob' => 'ActivityUpdateJob',
        'categoryMembershipChange' => 'CategoryMembershipChangeJob',
+       'clearUserWatchlist' => 'ClearUserWatchlistJob',
        'cdnPurge' => 'CdnPurgeJob',
        'enqueue' => 'EnqueueJob', // local queue for multi-DC setups
        'null' => 'NullJob'
diff --git a/includes/WatchedItemStore.php b/includes/WatchedItemStore.php
index 806db5e..0bbc8e1 100644
--- a/includes/WatchedItemStore.php
+++ b/includes/WatchedItemStore.php
@@ -185,6 +185,15 @@
        }
 
        /**
+        * Queues a job that will clear the users watchlist using the Job Queue
+        *
+        * @param User $user
+        */
+       public function clearUserWatchedItemsUsingJobQueue( User $user ) {
+               JobQueueGroup::singleton()->push( 
ClearUserWatchlistJob::newForUser( $user ) );
+       }
+
+       /**
         * Count the number of individual items that are watched by the user.
         * If a subject and corresponding talk page are watched this will 
return 2.
         *
diff --git a/includes/jobqueue/jobs/ClearUserWatchlistJob.php 
b/includes/jobqueue/jobs/ClearUserWatchlistJob.php
new file mode 100644
index 0000000..39810a5
--- /dev/null
+++ b/includes/jobqueue/jobs/ClearUserWatchlistJob.php
@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * Job to clear a users watchlist in batches.
+ *
+ * @author Addshore
+ *
+ * @ingroup JobQueue
+ * @since 1.27
+ */
+class ClearUserWatchlistJob extends Job {
+
+       /**
+        * @var LoadBalancer
+        */
+       private $loadBalancer;
+
+       public static function newForUser( User $user ) {
+               return new self( null, [ 'userId' => $user->getId() ] );
+       }
+
+       public function __construct( Title $title = null, array $params ) {
+               if ( !array_key_exists( 'batchSize', $params ) ) {
+                       $params['batchSize'] = 1000;
+               }
+
+               parent::__construct(
+                       'clearUserWatchlist',
+                       SpecialPage::getTitleFor( 'EditWatchlist', 'clear' ),
+                       $params
+               );
+
+               $this->removeDuplicates = true;
+               $this->loadBalancer = wfGetLB();
+       }
+
+       public function run() {
+               $userId = $this->params['userId'];
+
+               $dbw = $this->loadBalancer->getConnection( DB_WRITE, [ 
'watchlist' ] );
+
+               // Use a named lock so that jobs for this page see each others' 
changes
+               $lockKey = "ClearUserWatchlistJob:$userId";
+               $scopedLock = $dbw->getScopedLockAndFlush( $lockKey, 
__METHOD__, 10 );
+               if ( !$scopedLock ) {
+                       $this->setLastError( "Could not acquire lock 
'$lockKey'" );
+                       return false;
+               }
+
+               $dbr = $this->loadBalancer->getConnection( DB_SLAVE, [ 
'watchlist' ] );
+
+               if ( !$this->loadBalancer->safeWaitForMasterPos( $dbr ) ) {
+                       $this->setLastError( "Timed out while waiting for slave 
to catch up" );
+                       return false;
+               }
+
+               $result = $dbr->select(
+                       'watchlist',
+                       [ 'wl_namespace', 'wl_title' ],
+                       [ 'wl_user' => $userId, ],
+                       __METHOD__,
+                       [
+                               'ORDER BY' => 'wl_namespace, wl_title ASC',
+                               'LIMIT' => $this->params['batchSize'],
+                       ]
+               );
+
+               $this->loadBalancer->reuseConnection( $dbr );
+
+               if ( $result === false ) {
+                       $this->setLastError( "Failed to select watchlist 
entries" );
+                       return false;
+               }
+
+               if ( $result->numRows() == 0 ) {
+                       return true;
+               }
+
+               $dbw = $this->loadBalancer->getConnection( DB_MASTER, [ 
'watchlist' ] );
+
+               $deleteConds = [];
+               foreach ( $result as $row ) {
+                       $deleteConds[] = $dbw->makeList(
+                               [
+                                       'wl_namespace' => $row->wl_namespace,
+                                       'wl_title' => $row->wl_title,
+                                       'wl_user' => $userId,
+                               ],
+                               LIST_AND
+                       );
+               }
+
+               $result = $dbw->delete(
+                       'watchlist',
+                       $dbw->makeList( $deleteConds, LIST_OR ),
+                       __METHOD__
+               );
+
+               $this->loadBalancer->reuseConnection( $dbw );
+
+               if ( $result === false ) {
+                       $this->setLastError( "Failed to delete watchlist 
entries" );
+                       return false;
+               }
+
+               JobQueueGroup::singleton()->push( new self( $this->getTitle(), 
$this->getParams() ) );
+
+               return true;
+       }
+
+       public function getDeduplicationInfo() {
+               return [
+                       'type' => $this->getType(),
+                       'userId' => $this->params['userId'],
+               ];
+       }
+}
diff --git a/tests/phpunit/includes/jobqueue/jobs/ClearUserWatchlistJobTest.php 
b/tests/phpunit/includes/jobqueue/jobs/ClearUserWatchlistJobTest.php
new file mode 100644
index 0000000..d332a23
--- /dev/null
+++ b/tests/phpunit/includes/jobqueue/jobs/ClearUserWatchlistJobTest.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @covers ClearUserWatchlistJob
+ *
+ * @group JobQueue
+ * @group Database
+ *
+ * @licence GNU GPL v2+
+ * @author Addshore
+ */
+class ClearUserWatchlistJobTest extends MediaWikiTestCase {
+
+       public function setUp() {
+               parent::setUp();
+               self::$users['ClearUserWatchlistJobTestUser']
+                       = new TestUser( 'ClearUserWatchlistJobTestUser' );
+               JobQueueGroup::destroySingletons();
+       }
+
+       private function getUser() {
+               return self::$users['ClearUserWatchlistJobTestUser']->getUser();
+       }
+
+       private function runJobs( $jobLimit = 9999 ) {
+               $runJobs = new RunJobs;
+               $runJobs->loadParamsAndArgs( null, [ 'quiet' => true, 'maxjobs' 
=> $jobLimit ] );
+               $runJobs->execute();
+       }
+
+       public function testRun() {
+               $user = $this->getUser();
+               $watchedItemStore = WatchedItemStore::getDefaultInstance();
+               $this->runJobs(); // Ensure the queue is empty
+
+               $watchedItemStore->addWatch( $user, new TitleValue( 0, 'A' ) );
+               $watchedItemStore->addWatch( $user, new TitleValue( 1, 'A' ) );
+               $watchedItemStore->addWatch( $user, new TitleValue( 0, 'B' ) );
+               $watchedItemStore->addWatch( $user, new TitleValue( 1, 'B' ) );
+
+               JobQueueGroup::singleton()->push(
+                       new ClearUserWatchlistJob(
+                               null,
+                               [ 'userId' => $user->getId(), 'batchSize' => 2 ]
+                       )
+               );
+
+               $this->assertEquals( 1, 
JobQueueGroup::singleton()->getQueueSizes()['clearUserWatchlist'] );
+               $this->assertEquals( 4, $watchedItemStore->countWatchedItems( 
$user ) );
+               $this->runJobs( 1 );
+               $this->assertEquals( 1, 
JobQueueGroup::singleton()->getQueueSizes()['clearUserWatchlist'] );
+               $this->assertEquals( 2, $watchedItemStore->countWatchedItems( 
$user ) );
+               $this->runJobs( 1 );
+               $this->assertEquals( 1, 
JobQueueGroup::singleton()->getQueueSizes()['clearUserWatchlist'] );
+               $this->assertEquals( 0, $watchedItemStore->countWatchedItems( 
$user ) );
+               $this->runJobs( 1 );
+               $this->assertEquals( 0, 
JobQueueGroup::singleton()->getQueueSizes()['clearUserWatchlist'] );
+
+       }
+
+}
\ No newline at end of file

-- 
To view, visit https://gerrit.wikimedia.org/r/277350
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Icea573a10078ea3f09dc2e4e9fdc737bf639935d
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Addshore <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to