Track what wikis a user has unread notifications on

This implements a backend layer and database storage for tracking what
wikis a user has unread notifications on. It is not yet exposed via any
API.

Whenever the notification counts on the local wiki are reset, a deferred
update is queued to also update the central database table.

Change-Id: Id1498bdeb5811d6848dc66781ffca03e726eab90
This commit is contained in:
Kunal Mehta 2015-10-26 08:27:31 -07:00
parent c3787f4c51
commit b85f978ddd
6 changed files with 228 additions and 21 deletions

View file

@ -135,6 +135,14 @@ $wgNotificationReplyName = 'No Reply';
// use any key defined in $wgExternalServers
$wgEchoCluster = false;
// Shared database to use for keeping track of cross-wiki unread notifications
// false to not keep track of it at all
$wgEchoSharedTrackingDB = false;
// Cluster the shared tracking database is located on, false if it is on the
// main one. Must be a key defined in $wgExternalServers
$wgEchoSharedTrackingCluster = false;
// The max notification count showed in badge
// The max number showed in bundled message, eg, <user> and 99+ others <action>
$wgEchoMaxNotificationCount = 99;

View file

@ -86,6 +86,7 @@ $wgAutoloadClasses += array(
'EchoTextEmailFormatter' => __DIR__ . '/includes/EmailFormatter.php',
'EchoTitleLocalCache' => __DIR__ . '/includes/cache/TitleLocalCache.php',
'EchoTitleLocalCacheTest' => __DIR__ . '/tests/phpunit/cache/TitleLocalCacheTest.php',
'EchoUnreadWikis' => __DIR__ . '/includes/UnreadWikis.php',
'EchoUserLocator' => __DIR__ . '/includes/UserLocator.php',
'EchoUserLocatorTest' => __DIR__ . '/tests/phpunit/UserLocatorTest.php',
'EchoUserNotificationGateway' => __DIR__ . '/includes/gateway/UserNotificationGateway.php',

View file

@ -0,0 +1,18 @@
CREATE TABLE /*_*/echo_unread_wikis (
# Primary key
euw_id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
# Global user id
euw_user INT UNSIGNED NOT NULL,
# Name of wiki
euw_wiki VARCHAR(30) NOT NULL,
# unread alerts count on that wiki
euw_alerts INT UNSIGNED NOT NULL,
# Timestamp of the most recent unread alert
euw_alerts_ts BINARY(14) NOT NULL,
# unread messages count on that wiki
euw_messages INT UNSIGNED NOT NULL,
# Timestamp of the most recent unread message
euw_messages_ts BINARY(14) NOT NULL
) /*$wgDBTableOptions*/;
CREATE UNIQUE INDEX /*i*/echo_unread_wikis_user_wiki ON /*_*/echo_unread_wikis (euw_user,euw_wiki);

View file

@ -6,25 +6,25 @@
*/
class MWEchoDbFactory {
/**
* The wiki to access the database for
* @var string|bool
*/
protected $wiki;
/**
* The cluster for the database
* @var string|bool
*/
protected $cluster;
private $cluster;
private $shared;
private $sharedCluster;
/**
* @param string|bool
* @param string|bool
* @param string|bool $cluster
* @param string|bool $shared
* @param string|bool $sharedCluster
*/
public function __construct( $cluster = false, $wiki = false ) {
public function __construct( $cluster = false, $shared = false, $sharedCluster = false ) {
$this->cluster = $cluster;
$this->wiki = $wiki;
$this->shared = $shared;
$this->sharedCluster = $sharedCluster;
}
/**
@ -35,22 +35,34 @@ class MWEchoDbFactory {
* @return MWEchoDbFactory
*/
public static function newFromDefault() {
global $wgEchoCluster;
global $wgEchoCluster, $wgEchoSharedTrackingDB, $wgEchoSharedTrackingCluster;
return new self( $wgEchoCluster );
return new self( $wgEchoCluster, $wgEchoSharedTrackingDB, $wgEchoSharedTrackingCluster );
}
/**
* Get the database load balancer
* @param $wiki string|bool The wiki ID, or false for the current wiki
* @return LoadBalancer
*/
protected function getLB() {
// Use the external db defined for Echo
if ( $this->cluster ) {
$lb = wfGetLBFactory()->getExternalLB( $this->cluster, $this->wiki );
$lb = wfGetLBFactory()->getExternalLB( $this->cluster );
} else {
$lb = wfGetLB( $this->wiki );
$lb = wfGetLB();
}
return $lb;
}
/**
* @return LoadBalancer
*/
protected function getSharedLB() {
if ( $this->sharedCluster ) {
$lb = wfGetLBFactory()->getExternalLB( $this->sharedCluster );
} else {
$lb = wfGetLB();
}
return $lb;
@ -63,9 +75,24 @@ class MWEchoDbFactory {
* @return DatabaseBase
*/
public function getEchoDb( $db, $groups = array() ) {
return $this->getLB()->getConnection( $db, $groups, $this->wiki );
return $this->getLB()->getConnection( $db, $groups );
}
/**
* @param $db int Index of the connection to get
* @param array $groups Query groups
* @return bool|DatabaseBase false if no shared db is configured
*/
public function getSharedDb( $db, $groups = array() ) {
if ( !$this->shared ) {
return false;
}
return $this->getSharedLB()->getConnection( $db, $groups, $this->shared );
}
/**
* Wrapper function for wfGetDB, some extensions like MobileFrontend is
* using this to issue sql queries against Echo database directly. This

View file

@ -412,14 +412,22 @@ class MWEchoNotifUser {
public function resetNotificationCount( $dbSource = DB_SLAVE ) {
// Reset notification count for all sections as well
$this->getNotificationCount( false, $dbSource, EchoAttributeManager::ALL );
$this->getNotificationCount( false, $dbSource, EchoAttributeManager::ALERT );
$this->getNotificationCount( false, $dbSource, EchoAttributeManager::MESSAGE );
$alertCount = $this->getNotificationCount( false, $dbSource, EchoAttributeManager::ALERT );
$msgCount = $this->getNotificationCount( false, $dbSource, EchoAttributeManager::MESSAGE );
$user = $this->mUser;
// when notification count needs to be updated, last notification may have
// changed too, so we need to invalidate that cache too
$this->getLastUnreadNotificationTime( false, $dbSource, EchoAttributeManager::ALL );
$this->getLastUnreadNotificationTime( false, $dbSource, EchoAttributeManager::ALERT );
$this->getLastUnreadNotificationTime( false, $dbSource, EchoAttributeManager::MESSAGE );
$alertUnread = $this->getLastUnreadNotificationTime( false, $dbSource, EchoAttributeManager::ALERT );
$msgUnread = $this->getLastUnreadNotificationTime( false, $dbSource, EchoAttributeManager::MESSAGE );
$this->mUser->invalidateCache();
DeferredUpdates::addCallableUpdate( function () use ( $user, $alertCount, $alertUnread, $msgCount, $msgUnread ) {
$uw = EchoUnreadWikis::newFromUser( $user );
if ( $uw ) {
$uw->updateCount( wfWikiID(), $alertCount, $alertUnread, $msgCount, $msgUnread );
}
} );
}
/**

145
includes/UnreadWikis.php Normal file
View file

@ -0,0 +1,145 @@
<?php
/**
* Manages what wikis a user has unread notifications on
*
*/
class EchoUnreadWikis {
const ALERT = 'alert';
const MESSAGE = 'message';
/**
* @var int
*/
private $id;
/**
* @var MWEchoDbFactory
*/
private $dbFactory;
/**
* @param int $id Central user id
*/
public function __construct( $id ) {
$this->id = $id;
$this->dbFactory = MWEchoDbFactory::newFromDefault();
}
/**
* If CentralAuth is installed, use that. Otherwise
* assume they're using shared user tables.
*
* @param User $user
* @return EchoUnreadWikis|bool
*/
public static function newFromUser( User $user ) {
if ( class_exists( 'CentralAuth' ) ) {
// @todo don't be CA specific (see T111302/CentralIdLookup)
$caUser = CentralAuthUser::getInstance( $user );
if ( $caUser->isAttached() ) {
return new self( $caUser->getId() );
} else {
return false;
}
}
return new self( $user->getId() );
}
/**
* @param int $index DB_* constant
* @return bool|DatabaseBase
*/
private function getDB( $index ) {
return $this->dbFactory->getSharedDb( $index );
}
/**
* @return array
*/
public function getUnreadCounts() {
$dbr = $this->getDB( DB_SLAVE );
if ( $dbr === false ) {
return array();
}
$rows = $dbr->select(
'echo_unread_wikis',
array(
'euw_wiki',
'euw_alerts', 'euw_alerts_ts',
'euw_messages', 'euw_messages_ts',
),
array( 'euw_user' => $this->id ),
__METHOD__
);
$wikis = array();
foreach ( $rows as $row ) {
if ( !$row->euw_alerts && !$row->euw_messages ) {
// This shouldn't happen, but lets be safe...
continue;
}
$wikis[$row->euw_wiki] = array(
self::ALERT => array(
'count' => $row->euw_alerts,
'ts' => $row->euw_alerts_ts,
),
self::MESSAGE => array(
'count' => $row->euw_messages,
'ts' => $row->euw_messages_ts,
),
);
}
return $wikis;
}
/**
* @param string $wiki
* @param int $alertCount
* @param MWTimestamp|bool $alertTime
* @param int $msgCount
* @param MWTimestamp|bool $msgTime
*/
public function updateCount( $wiki, $alertCount, $alertTime, $msgCount, $msgTime ) {
$dbw = $this->getDB( DB_MASTER );
if ( $dbw === false ) {
return;
}
$defaultTS = '00000000000000';
if ( $alertCount || $msgCount ) {
$values = array(
'euw_alerts' => $alertCount,
'euw_alerts_ts' => $alertCount
? $alertTime->getTimestamp( TS_MW )
: $defaultTS,
'euw_messages' => $msgCount,
'euw_messages_ts' => $msgCount
? $msgTime->getTimestamp( TS_MW )
: $defaultTS,
);
$dbw->upsert(
'echo_unread_wikis',
array(
'euw_user' => $this->id,
'euw_wiki' => $wiki,
) + $values,
array( 'euw_user', 'euw_wiki' ),
$values,
__METHOD__
);
} else {
// No unread notifications, delete the row
$dbw->delete(
'echo_unread_wikis',
array( 'euw_user' => $this->id, 'euw_wiki' => $wiki ),
__METHOD__
);
}
}
}