http://www.mediawiki.org/wiki/Special:Code/MediaWiki/56201
Revision: 56201
Author: btongminh
Date: 2009-09-11 18:28:25 +0000 (Fri, 11 Sep 2009)
Log Message:
-----------
Rewrote this globalusage extension. Still needs a population script and a lot
of work on the UI.
Modified Paths:
--------------
trunk/extensions/GlobalUsage/GlobalUsage.i18n.php
trunk/extensions/GlobalUsage/GlobalUsage.pg.sql
trunk/extensions/GlobalUsage/GlobalUsage.php
trunk/extensions/GlobalUsage/GlobalUsage.sql
trunk/extensions/GlobalUsage/GlobalUsage_body.php
trunk/extensions/GlobalUsage/readme.txt
Added Paths:
-----------
trunk/extensions/GlobalUsage/GlobalUsageHooks.php
trunk/extensions/GlobalUsage/SpecialGlobalUsage.php
Removed Paths:
-------------
trunk/extensions/GlobalUsage/GlobalUsageDaemon.php
trunk/extensions/GlobalUsage/populateGlobalUsage.php
Modified: trunk/extensions/GlobalUsage/GlobalUsage.i18n.php
===================================================================
--- trunk/extensions/GlobalUsage/GlobalUsage.i18n.php 2009-09-11 18:22:33 UTC
(rev 56200)
+++ trunk/extensions/GlobalUsage/GlobalUsage.i18n.php 2009-09-11 18:28:25 UTC
(rev 56201)
@@ -12,6 +12,7 @@
*/
$messages['en'] = array(
'globalusage' => 'Global file usage',
+ 'globalusage-for' => 'Global usage for "$1"',
'globalusage-desc' => '[[Special:GlobalUsage|Special page]] to view
global file usage',
'globalusage-ok' => 'Search',
'globalusage-text' => 'Search global file usage.'
Modified: trunk/extensions/GlobalUsage/GlobalUsage.pg.sql
===================================================================
--- trunk/extensions/GlobalUsage/GlobalUsage.pg.sql 2009-09-11 18:22:33 UTC
(rev 56200)
+++ trunk/extensions/GlobalUsage/GlobalUsage.pg.sql 2009-09-11 18:28:25 UTC
(rev 56201)
@@ -4,8 +4,7 @@
gil_page_namespace TEXT NOT NULL,
gil_page_title TEXT NOT NULL,
gil_to TEXT NOT NULL,
- gil_is_local SMALLINT NOT NULL,
PRIMARY KEY (gil_wiki, gil_page)
);
-CREATE INDEX globalimagelinks_wiki ON globalimagelinks(gil_wiki, gil_to);
-CREATE INDEX globalimagelinks_to ON globalimagelinks(gil_to, gil_is_local);
+CREATE INDEX globalimagelinks_wiki ON globalimagelinks(gil_to, gil_wiki);
+
Modified: trunk/extensions/GlobalUsage/GlobalUsage.php
===================================================================
--- trunk/extensions/GlobalUsage/GlobalUsage.php 2009-09-11 18:22:33 UTC
(rev 56200)
+++ trunk/extensions/GlobalUsage/GlobalUsage.php 2009-09-11 18:28:25 UTC
(rev 56201)
@@ -1,6 +1,6 @@
<?php
/*
- Copyright (c) 2008 Bryan Tong Minh
+ Copyright (c) 2008 - 2009 Bryan Tong Minh
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
@@ -33,37 +33,40 @@
exit( 1 );
}
-// Defines
-define('GUIW_LOCAL', 0);
-define('GUIW_SERVER', 1);
-if (isset($_SERVER) && array_key_exists( 'REQUEST_METHOD', $_SERVER ) ) {
- $dir = dirname(__FILE__) . '/';
+$dir = dirname(__FILE__) . '/';
- $wgExtensionCredits['specialpage'][] = array(
- 'path' => __FILE__,
- 'name' => 'Global Usage',
- 'author' => 'Bryan Tong Minh',
- 'description' => 'Special page to view global file usage',
- 'descriptionmsg' => 'globalusage-desc',
- 'url' => 'http://www.mediawiki.org/wiki/Extension:GlobalUsage',
- 'version' => '1.1',
- );
+$wgExtensionCredits['specialpage'][] = array(
+ 'path' => __FILE__,
+ 'name' => 'Global Usage',
+ 'author' => 'Bryan Tong Minh',
+ 'description' => 'Special page to view global file usage',
+ 'descriptionmsg' => 'globalusage-desc',
+ 'url' => 'http://www.mediawiki.org/wiki/Extension:GlobalUsage',
+ 'version' => '2.0',
+);
- $wgExtensionMessagesFiles['GlobalUsage'] = $dir .
'GlobalUsage.i18n.php';
- $wgAutoloadClasses['GlobalUsage'] = $dir . 'GlobalUsage_body.php';
- $wgExtensionMessageFiles['GlobalUsage'] = $dir . 'GlobalUsage.i18n.php';
- $wgExtensionAliasesFiles['GlobalUsage'] = $dir .
'GlobalUsage.alias.php';
- $wgSpecialPages['GlobalUsage'] = 'GlobalUsage';
+$wgExtensionMessagesFiles['GlobalUsage'] = $dir . 'GlobalUsage.i18n.php';
+$wgAutoloadClasses['GlobalUsage'] = $dir . 'GlobalUsage_body.php';
+$wgAutoloadClasses['GlobalUsageHooks'] = $dir . 'GlobalUsageHooks.php';
+$wgAutoloadClasses['SpecialGlobalUsage'] = $dir . 'SpecialGlobalUsage.php';
+$wgExtensionMessageFiles['GlobalUsage'] = $dir . 'GlobalUsage.i18n.php';
+$wgExtensionAliasesFiles['GlobalUsage'] = $dir . 'GlobalUsage.alias.php';
+$wgSpecialPages['GlobalUsage'] = 'SpecialGlobalUsage';
- $wgHooks['LinksUpdate'][] = array( 'GlobalUsage', 'updateLinks' );
- $wgHooks['ArticleDeleteComplete'][] = array( 'GlobalUsage',
'articleDeleted' );
- $wgHooks['FileDeleteComplete'][] = array( 'GlobalUsage', 'fileDeleted'
);
- $wgHooks['FileUndeleteComplete'][] = array( 'GlobalUsage',
'fileUndeleted' );
- $wgHooks['UploadComplete'][] = array( 'GlobalUsage', 'imageUploaded' );
- $wgHooks['SpecialMovepageAfterMove'][] = array( 'GlobalUsage',
'articleMoved' );
-}
+/* Things that can cause link updates:
+ * - Local LinksUpdate
+ * - Local article deletion (remove from table)
+ * - Local article move (update page title)
+ * - Local file upload/deletion/move (toggle is_local flag)
+ */
+$wgHooks['LinksUpdateComplete'][] = 'GlobalUsageHooks::onLinksUpdate';
+$wgHooks['ArticleDelete'][] = 'GlobalUsageHooks::onArticleDelete';
+$wgHooks['FileUndeleteComplete'][] = 'GlobalUsageHooks::onFileUndelete';
+$wgHooks['UploadComplete'][] = 'GlobalUsageHooks::onUpload';
+$wgHooks['TitleMoveComplete'][] = 'GlobalUsageHooks::onTitleMove';
+
// If set to false, the local database contains the globalimagelinks table
// Else set to something understandable to LBFactory
-$wgguMasterDatabase = false;
+$wgGlobalUsageDatabase = false;
Modified: trunk/extensions/GlobalUsage/GlobalUsage.sql
===================================================================
--- trunk/extensions/GlobalUsage/GlobalUsage.sql 2009-09-11 18:22:33 UTC
(rev 56200)
+++ trunk/extensions/GlobalUsage/GlobalUsage.sql 2009-09-11 18:28:25 UTC
(rev 56201)
@@ -9,16 +9,12 @@
gil_page_title varchar(255) not null,
-- Image name
gil_to varchar(255) not null,
- -- Exists locally
- gil_is_local tinyint(1) not null,
+
-- Note: You might want to shorten the gil_wiki part of the indices.
-- If the domain format is used, only the "en.wikip" part is needed for
an
-- unique lookup
PRIMARY KEY (gil_wiki, gil_page, gil_to),
- -- On gil_is_local change
- INDEX (gil_wiki, gil_to),
- -- On the special page itself
- INDEX (gil_to, gil_is_local)
-) /*$wgDBTableOptions*/;
\ No newline at end of file
+ INDEX (gil_to, gil_wiki)
+) /*$wgDBTableOptions*/;
Deleted: trunk/extensions/GlobalUsage/GlobalUsageDaemon.php
===================================================================
--- trunk/extensions/GlobalUsage/GlobalUsageDaemon.php 2009-09-11 18:22:33 UTC
(rev 56200)
+++ trunk/extensions/GlobalUsage/GlobalUsageDaemon.php 2009-09-11 18:28:25 UTC
(rev 56201)
@@ -1,434 +0,0 @@
-<?php
-
-class GlobalUsageDaemon {
- // This daemon supports updating from multiple wikis
-
- // Array of wiki => timestamp pairs
- public $timestamps;
- // Array of database key => database pairs
- private $databases;
- // Array of localized namespaces
- private $namespaces;
- // Location of the log file
- private $log;
- // Stderr pointer
- private $stderr;
- // Array of wikis containing config settings
- private $wikiList;
-
- public function __construct($log, $wikiList, $silent = false) {
- $this->databases = array();
- $this->timestamps = array();
- $this->namespaces = array();
- $this->log = $log;
- $this->wikiList = $wikiList;
-
- if (!$silent)
- $this->stderr = fopen('php://stderr', 'w');
- else
- $this->stderr = null;
-
- if (($fp = fopen($this->log, 'r')) !== false) {
- flock($fp, LOCK_EX);
- while (!feof($fp)) {
- $line = fgets($fp);
- if (strpos($line, "\t") !== false) {
- list($lw, $lt) = explode("\t", $line,
2);
- $this->timestamps[$lw] = trim($lt);
- }
- }
- fclose($fp);
- }
- $this->debug("Read previous data from {$log}");
- $this->debug('Position is defined for the following wikis: '.
- implode(', ', array_keys($this->timestamps)));
- }
-
- public function debug($string) {
- if ($this->stderr) fwrite($this->stderr, "{$string}\n");
- }
-
- /*
- * Populate the globalimagelinks table from the local imagelinks
- */
- public function populateGlobalUsage($wiki, $interval, $throttle =
1000000, $maxLag) {
- $this->debug("Populating globalimagelinks on {$wiki}");
-
- if (!isset($this->namespaces[$wiki]))
- $this->fetchNamespaces($wiki);
- $namespaces = $this->namespaces[$wiki];
-
- $dbw = GlobalUsage::getDatabase(DB_MASTER);
- $dbw->immediateBegin();
- $dbw->delete('globalimagelinks', array('gil_wiki' => $wiki),
__METHOD__);
-
- $dbr = $this->getDatabase($wiki);
-
- // Account for slave lag
- $res = $dbr->select('recentchanges', 'MAX(rc_timestamp) AS
timestamp');
- $row = $res->fetchRow();
- $timestamp = substr(substr($row['timestamp'], 0, 14 -
$interval).
- '00000000000000', 0, 14);
- $res->free();
-
- $prevPage = 0;
- $prevImage = '';
- $limit = 2000;
- do {
- $loopStart = microtime(true);
-
- $sql =
- // Join order is important for sorting
- 'SELECT STRAIGHT_JOIN '.
- 'page_id, page_namespace, page_title, il_to,
img_name IS NOT NULL AS is_local '.
- 'FROM '.$dbr->tableName('imagelinks').' '.
- // MySQL will choose the il_to index from il_to
> 'O'
- // TODO: Doesn't work on the Toolserver
- 'FORCE INDEX(il_from) '.
- 'LEFT JOIN '.$dbr->tableName('image').' ON
il_to = img_name '.
- 'JOIN '.$dbr->tableName('page').' ON page_id =
il_from '.
- 'WHERE il_from >= '.$prevPage.' AND il_to >
'.$dbr->addQuotes($prevImage).
- 'ORDER BY il_from, il_to ';
- $query = $dbr->limitResult($sql, $limit, 0);
- $res = $dbr->query($query, __METHOD__);
-
- $count = 0;
- $rows = array();
- while ($row = $res->fetchRow()) {
- $count++;
- $rows[] = array(
- 'gil_wiki' => $wiki,
- 'gil_page' => $row['page_id'],
- 'gil_page_namespace' =>
$namespaces[$row['page_namespace']],
- 'gil_page_title' => $row['page_title'],
- 'gil_to' => $row['il_to'],
- 'gil_is_local' => $row['is_local']
- );
- $prevPage = $row['page_id'];
- $prevImage = $row['il_to'];
- }
- $res->free();
-
- $dbw->insert( 'globalimagelinks', $rows, __METHOD__,
'IGNORE' );
-
- $timeTaken = microtime(true) - $loopStart;
- $rps = $count / $timeTaken;
- $this->debug("Inserted {$count} rows in {$timeTaken}
seconds; {$rps} rows per second");
- if ($rps > $throttle) {
- $sleepTime = ($rps / $throttle - 1) *
$timeTaken;
- $this->debug("Throttled {$sleepTime} seconds");
- sleep($sleepTime);
- }
- if ($maxLag) {
- $lb = wfGetLB($wiki);
- do {
- list($host, $lag) = $lb->getMaxLag();
- if ($lag > $maxLag) {
- $this->debug("Waiting for
{$host}; lagged {$lag} seconds");
- sleep($lag - $maxLag);
- }
- } while ($lag > $maxLag);
- }
- } while ($count == $limit);
- $dbw->immediateCommit();
-
- $this->setTimestamp($wiki, $timestamp);
- }
-
- /*
- * Populate the globalimagelinks table from the recentchanges
- */
- public function processRecentChanges($wiki, $interval = 2) {
- global $wgDBtype;
- $dbr = $this->getDatabase($wiki);
- $dbw = GlobalUsage::getDatabase(DB_MASTER);
-
- $tables = array(
- 'img' => $dbr->tableName('image'),
- 'il' => $dbr->tableName('imagelinks'),
- 'log' => $dbr->tableName('logging'),
- 'page' => $dbr->tableName('page'),
- 'rc' => $dbr->tableName('recentchanges'),
- );
-
- // Get timestamp
- $timestamp = substr($this->timestamps[$wiki], 0, 14 -
$interval);
-
- $dbw->immediateBegin();
-
- $timestamp_like_rc = $wgDBtype === 'postgres'
- ? "TO_CHAR(rc_timestamp, 'YYYYMMDDHH24MISS') =
'$timestamp'"
- : "rc_timestamp LIKE '{$timestamp}%'";
-
- // Update links on all recentchanges
- $query = 'SELECT DISTINCT '.
- 'page_id AS id, rc_namespace AS ns, rc_title AS title '.
- "FROM {$tables['rc']}, {$tables['page']} ".
- 'WHERE page_namespace = rc_namespace AND page_title =
rc_title '.
- 'AND rc_namespace <> -1 '.
- "AND $timestamp_like_rc";
-
- $res = $dbr->query($query, __METHOD__);
-
- $rows = array();
- while($row = $res->fetchRow())
- $rows[] = $row;
- $res->free();
- $this->processRows($rows, $wiki, $dbr, $dbw);
-
- $timestamp_like_log = $wgDBtype === 'postgres'
- ? "TO_CHAR(log_timestamp, 'YYYYMMDDHH24MISS') =
'$timestamp'"
- : "log_timestamp LIKE '{$timestamp}%'";
-
- // Update links on deletion or undeletion of an article
- $query = 'SELECT '.
- 'page_id AS id, log_namespace AS ns, log_title AS
title, '.
- 'page_id IS NOT NULL AS is_local '.
- "FROM {$tables['log']} ".
- "LEFT JOIN {$tables['page']} ON ".
- 'page_namespace = log_namespace AND '.
- 'page_title = log_title '.
- "WHERE log_action = 'delete' ".
- "AND $timestamp_like_log";
-
- $res = $dbr->query($query, __METHOD__);
-
- $rows = array();
- while($row = $res->fetchRow()) {
- if ($row['is_local']) {
- $rows[] = $row;
- } else {
- $dbw->delete( 'globalimagelinks', array(
- 'gil_wiki' => $wiki,
- 'gil_page' => $row['id'],
- ), __METHOD__);
- }
- }
- $res->free();
- $this->processRows($rows, $wiki, $dbr, $dbw);
-
- // Set the is_local flag on images
- $query = 'SELECT DISTINCT '.
- 'log_title, img_name IS NOT NULL AS is_local '.
- "FROM {$tables['log']}, {$tables['il']} ".
- "LEFT JOIN {$tables['img']} ON il_to = img_name ".
- "WHERE log_namespace = 6 AND log_type IN ('upload',
'delete') ".
- "AND $timestamp_like_log";
-
- $res = $dbr->query($query, __METHOD__);
-
- while($row = $res->fetchRow()) {
- $dbw->update( 'globalimagelinks',
- array( 'gil_is_local' => $row['is_local'] ),
- array(
- 'gil_wiki' => $wiki,
- 'gil_to' => $row['log_title']
- ),
- __METHOD__ );
- }
- $res->free();
-
- // Update titles on page move
- $res = $dbr->select('logging',
- array('log_namespace', 'log_title', 'log_params'),
- "log_type = 'move' AND $timestamp_like_log",
- __METHOD__);
-
- while($row = $res->fetchRow()) {
- $namespace = '';
- $title = $row['log_params'];
- if (strpos($row['log_params'], ':') !== false) {
- $new_title = explode(':', $row['log_params'],
2);
- if (in_array($new_title[0],
$this->namespaces[$wiki])) {
- list($namespace, $title) = $new_title;
- }
- }
- // FIXME: Unindexed update!
- $dbw->update( 'globalimagelinks', array(
- 'gil_page_namespace' => $namespace,
- 'gil_page_title' => $title
- ), array(
- 'gil_wiki' => $wiki,
- 'gil_page_namespace' => $row['log_namespace'],
- 'gil_page_title' => $row['log_title']
- ), __METHOD__ );
- }
- $res->free();
-
- $dbw->immediateCommit();
-
- // Set new timestamp
- $newTs = wfTimestamp(TS_MW, $this->incrementTimestamp(
- $this->timestamps[$wiki], $interval));
- $this->setTimestamp($wiki, $newTs);
-
- // Return when this function should be called again
- $waitUntil = wfTimestamp(TS_MW,
$this->incrementTimestamp($newTs, $interval));
-
- $res = $dbr->select('recentchanges', 'MAX(rc_timestamp) AS r',
'', __METHOD__);
- $row = $res->fetchRow();
- $res->free();
- return array($waitUntil, $row['r'] > $waitUntil);
-
- }
-
- /*
- * Call doUpdate
- */
- private function processRows($rows, $wiki, $dbr, $dbw) {
- if (count($rows)) {
- foreach ($rows as $row)
- GlobalUsage::doUpdate($row['id'], $wiki,
- $row['ns'], $row['title'], $dbr, $dbw);
- }
- }
-
- /*
- * Get namespace names
- */
- private function fetchNamespaces($wiki) {
- global $wgContLang;
- if ($wiki == GlobalUsage::getLocalInterwiki()) {
- $this->namespaces[$wiki] =
$wgContLang->getFormattedNamespaces();
- } else {
- // Not the current wiki, need to fetch using the API
- $this->debug("Fetching namespaces from external wiki
{$wiki}");
-
- if (!isset($this->wikiList[$wiki])) {
- // Raise error
- }
- $address = $this->wikiList[$wiki];
- $address .=
'?action=query&meta=siteinfo&siprop=namespaces&format=php';
-
- $curl = curl_init($address);
- curl_setopt($curl, CURLOPT_USERAGENT,
'GlobalUsage/1.0');
- curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
- $data = unserialize(curl_exec($curl));
- curl_close($curl);
- if (!$data) return false;
-
- $this->namespaces[$wiki] = array();
- foreach ($data['query']['namespaces'] as $id => $value)
- $this->namespaces[$wiki][$id] = $value['*'];
- return true;
- }
- }
-
- /*
- * Get database object for reading
- */
- private function getDatabase($wiki) {
- if ($wiki == GlobalUsage::getLocalInterwiki())
- return wfGetDB(DB_SLAVE);
- else
- return wfGetDB(DB_SLAVE, array(), $wiki);
- }
-
- /*
- * Save timestamp to a logfile to continue after
- */
- private function setTimestamp($wiki, $timestamp) {
- $written = false;
- if (($fp = fopen($this->log, 'r+')) === false) {
- // Raise an error
- }
-
- flock($fp, LOCK_EX);
- fseek($fp, 0, SEEK_SET);
- while (!feof($fp)) {
- $line = fgets($fp);
- if (strpos($line, "\t") !== false) {
- list($lw, $lt) = explode("\t", $line, 2);
- if ($lw == $wiki) {
- fseek($fp, ftell($fp) - 15, SEEK_SET);
- fwrite($fp, $timestamp);
- $written = true;
- }
- }
- }
- if (!$written) fwrite($fp, "{$wiki}\t{$timestamp}\n");
- fclose($fp);
-
- $this->timestamps[$wiki] = $timestamp;
- }
- private function incrementTimestamp($timestamp, $interval) {
- $timestamp = (string)((int)$timestamp + pow(10, $interval));
- return gmmktime(
- substr($timestamp, 8, 2),
- substr($timestamp, 10, 2),
- substr($timestamp, 12, 2),
- substr($timestamp, 4, 2),
- substr($timestamp, 6, 2),
- substr($timestamp, 0, 4)
- );
- }
-
- public function runLocalDaemon($wiki, $interval) {
- $this->debug("Running local daemon on {$wiki}");
-
- // Fetch namespaces
- if (!isset($this->namespaces[$wiki]))
- if (!$this->fetchNamespaces($wiki))
- die("Could not fetch namespaces for {$wiki}\n");
- $dbr = $this->getDatabase($wiki);
-
- while (true) {
- list($waitUntil, $hasMore) =
$this->processRecentChanges($wiki, $interval);
- while (wfTimestamp(TS_UNIX, $waitUntil) > time() -
$dbr->getLag()) {
- $sleepTime = max(wfTimestamp(TS_UNIX,
$waitUntil) + $dbr->getLag() - time(), 0);
- $this->debug("Sleeping {$sleepTime} seconds: ".
- 'need to wait until '.$waitUntil.
- '; now is '.wfTimestamp(TS_MW));
- sleep($sleepTime);
- }
- }
- }
- public function runDaemon($interval) {
- $waitUntil = array();
-
- foreach ($this->wikiList as $wiki => $info)
- $waitUntil[$wiki] = 0;
-
- $this->debug("Running GlobalUsage daemon on the following
wikis: ".
- implode(', ', array_keys($waitUntil)));
- while (true) {
- // Sort by time
- asort($waitUntil);
- reset($waitUntil);
-
- $dbr = $this->getDatabase(key($waitUntil));
- if (current($waitUntil) != 0) {
- $waitUntilTime = wfTimestamp(TS_UNIX,
current($waitUntil));
- $lag = $dbr->getLag();
- while ($waitUntilTime > time() - $lag) {
- $sleepTime = max($waitUntilTime -
time() + $lag, 0);
- $this->debug("Sleeping {$sleepTime}
seconds: ".
- 'need to wait until
'.current($waitUntil).
- '; now is '.wfTimestamp(TS_MW,
time() - $lag));
- sleep($sleepTime);
- }
- }
-
- $wiki = key($waitUntil);
-
- // Fetch namespaces
- if (!isset($this->namespaces[$wiki]))
- if (!$this->fetchNamespaces($wiki)) {
- $this->debug("Could not fetch
namespaces for {$wiki}");
- unset($waitUntil[$wiki]);
- continue;
- }
-
- $this->debug("Processing recentchanges for {$wiki}");
- $now = time();
- list($waitUntil[$wiki], $hasMore) =
$this->processRecentChanges($wiki, $interval);
- if (!$hasMore) {
- // There are no more entries. Set the timestamp
to *now* to avoid locking
- // of other wikis by rarely updated wikis
- $next = substr(substr(wfTimestamp(TS_MW, $now -
$dbr->getLag()),
- 0, 14 - $interval).'00000000000000', 0,
14);
- $waitUntil[$wiki] = wfTimestamp(TS_MW,
$this->incrementTimestamp($next, $interval));
- }
- }
- }
-}
Added: trunk/extensions/GlobalUsage/GlobalUsageHooks.php
===================================================================
--- trunk/extensions/GlobalUsage/GlobalUsageHooks.php
(rev 0)
+++ trunk/extensions/GlobalUsage/GlobalUsageHooks.php 2009-09-11 18:28:25 UTC
(rev 56201)
@@ -0,0 +1,78 @@
+<?php
+class GlobalUsageHooks {
+ private static $gu = null;
+
+ /**
+ * Hook to LinksUpdateComplete
+ * Deletes old links from usage table and insert new ones.
+ */
+ public static function onLinksUpdate( $linksUpdater ) {
+ $title = $linksUpdater->getTitle();
+
+ // Create a list of locally existing images
+ $images = array_keys( $linksUpdater->getExistingImages() );
+ $localFiles = array_keys(
RepoGroup::singleton()->getLocalRepo()->findFiles( $images ) );
+
+ $gu = self::getGlobalUsage();
+ $gu->deleteFrom( $title->getArticleId( GAID_FOR_UPDATE ) );
+ $gu->setUsage( $title, array_diff( $images, $localFiles ) );
+
+ return true;
+ }
+ /**
+ * Hook to TitleMoveComplete
+ * Sets the page title in usage table to the new name.
+ */
+ public static function onTitleMove( $ot, $nt, $user, $pageid, $redirid
) {
+ $gu = self::getGlobalUsage();
+ $gu->moveTo( $pageid, $nt );
+ return true;
+ }
+ /**
+ * Hook to ArticleDeleteComplete
+ * Deletes entries from usage table.
+ * In case of an image, copies the local link table to the global.
+ */
+ public static function onArticleDelete( $article, $user, $reason ) {
+ $title = $article->getTitle();
+ $gu = self::getGlobalUsage();
+ $gu->deleteFrom( $title->getArticleId( GAID_FOR_UPDATE ) );
+ if ( $title->getNamespace() == NS_FILE ) {
+ $gu->copyFromLocal( $title );
+ }
+ return true;
+ }
+
+ /**
+ * Hook to FileUndeleteComplete
+ * Deletes the file from the global link table.
+ */
+ public static function onFileUndelete( $title, $versions, $user,
$reason ) {
+ $gu = self::getGlobalUsage();
+ $gu->deleteTo( $title );
+ return true;
+ }
+ /**
+ * Hook to UploadComplete
+ * Deletes the file from the global link table.
+ */
+ public static function onUpload( $upload ) {
+ $gu = self::getGlobalUsage();
+ $gu->deleteTo( $upload->getTitle() );
+ return true;
+ }
+
+ /**
+ * Initializes a GlobalUsage object for the current wiki.
+ */
+ private static function getGlobalUsage() {
+ global $wgLocalInterwiki, $wgGlobalUsageDatabase;
+ if ( is_null( self::$gu ) ) {
+ self::$gu = new GlobalUsage( $wgLocalInterwiki,
+ wfGetDB( DB_MASTER, array(),
$wgGlobalUsageDatabase )
+ );
+ }
+
+ return self::$gu;
+ }
+}
\ No newline at end of file
Property changes on: trunk/extensions/GlobalUsage/GlobalUsageHooks.php
___________________________________________________________________
Added: svn:eol-style
+ native
Modified: trunk/extensions/GlobalUsage/GlobalUsage_body.php
===================================================================
--- trunk/extensions/GlobalUsage/GlobalUsage_body.php 2009-09-11 18:22:33 UTC
(rev 56200)
+++ trunk/extensions/GlobalUsage/GlobalUsage_body.php 2009-09-11 18:28:25 UTC
(rev 56201)
@@ -1,185 +1,121 @@
<?php
-class GlobalUsage extends SpecialPage {
- private static $database = array();
- private static $interwiki = null;
+class GlobalUsage {
+ private $interwiki;
+ private $db;
- function __construct() {
- parent::__construct('GlobalUsage');
- wfLoadExtensionMessages( 'GlobalUsage' );
+ /**
+ * Construct a GlobalUsage instance for a certain wiki.
+ *
+ * @param $interwiki string Interwiki prefix of the wiki
+ * @param $db mixed Database object
+ */
+ public function __construct( $interwiki, $db ) {
+ $this->interwiki = $interwiki;
+ $this->db = $db;
}
- static function getDatabase( $dbFlags = DB_MASTER ) {
- global $wgguIsMaster, $wgguMasterDatabase;
- if ( !isset( self::$database[$dbFlags] ) )
- self::$database[$dbFlags] = wfGetDB( $dbFlags, array(),
$wgguMasterDatabase );
- return self::$database[$dbFlags];
- }
- static function getLocalInterwiki() {
- global $wgguInterwikiStyle, $wgLocalInterwiki, $wgServerName;
- if (!self::$interwiki) {
- switch ($wgguInterwikiStyle) {
- case GUIW_LOCAL:
- self::$interwiki = $wgLocalInterwiki;
- break;
- case GUIW_SERVER_NAME:
- self::$interwiki = $wgServerName;
- break;
- default:
- self::$interwiki = $wgLocalInterwiki;
- }
+ /**
+ * Sets the images used by a certain page
+ *
+ * @param $title Title Title of the page
+ * @param $images array Array of db keys of images used
+ */
+ public function setUsage( $title, $images ) {
+ $insert = array();
+ foreach ( $images as $name ) {
+ $insert[] = array(
+ 'gil_wiki' => $this->interwiki,
+ 'gil_page' => $title->getArticleID(
GAID_FOR_UPDATE ),
+ 'gil_page_namespace' => $title->getNsText(),
+ 'gil_page_title' => $title->getText(),
+ 'gil_to' => $name
+ );
}
- return self::$interwiki;
+ $this->db->insert( 'globalimagelinks', $insert, __METHOD__ );
}
-
- static function updateLinks( $linksUpdater ) {
- $title = $linksUpdater->getTitle();
- $dbr = wfGetDB(DB_SLAVE);
- $dbw = self::getDatabase();
- $dbw->immediateBegin();
- self::doUpdate($title->getArticleID(),
self::getLocalInterwiki(),
- $title->getNsText(), $title->getDBkey(), $dbr, $dbw );
- $dbw->immediateCommit();
-
- return true;
+ /**
+ * Deletes all entries from a certain page
+ *
+ * @param $id int Page id of the page
+ */
+ public function deleteFrom( $id ) {
+ $this->db->delete(
+ 'globalimagelinks',
+ array(
+ 'gil_wiki' => $this->interwiki,
+ 'gil_page' => $id
+ ),
+ __METHOD__
+ );
}
+ /**
+ * Deletes all entries to a certain image
+ *
+ * @param $title Title Title of the file
+ */
+ public function deleteTo( $title ) {
+ $this->db->delete(
+ 'globalimagelinks',
+ array(
+ 'gil_wiki' => $this->interwiki,
+ 'gil_to' => $title->getDBkey()
+ ),
+ __METHOD__
+ );
+ }
- /*
- * Perform the update for a certain page on a certain database. The
caller is
- * responsible for creating a master datbase object and performing
- * immediateBegin() and immediateCommit().
+ /**
+ * Copy local links to global table
+ *
+ * @param $title Title Title of the file to copy entries from.
*/
- static function doUpdate( $pageId, $wiki, $pageNamespace, $pageTitle,
- &$dbr, &$dbw ) {
- $query = 'SELECT il_to, img_name IS NOT NULL AS is_local '.
- 'FROM '.$dbr->tableName('imagelinks').' '.
- 'LEFT JOIN '.$dbr->tableName('image').' ON '.
- 'il_to = img_name WHERE il_from = '.$pageId;
- $res = $dbr->query($query, __METHOD__);
+ public function copyFromLocal( $title ) {
+ global $wgContLang;
- $rows = array();
- while ($row = $res->fetchRow()) {
- $rows[] = array(
- "gil_wiki" => $wiki,
- "gil_page" => $pageId,
- "gil_page_namespace" => $pageNamespace,
- "gil_page_title" => $pageTitle,
- "gil_to" => $row['il_to'],
- "gil_is_local" => $row['is_local']);
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select(
+ array( 'imagelinks', 'page' ),
+ array( 'il_to', 'page_id', 'page_namespace',
'page_title' ),
+ array( 'il_from = page_id', 'il_to' =>
$title->getDBkey() ),
+ __METHOD__
+ );
+ $insert = array();
+ foreach ( $res as $row ) {
+ $insert[] = array(
+ 'gil_wiki' => $this->interwiki,
+ 'gil_page' => $row->page_id,
+ 'gil_page_namespace' => $wgContLang->getNsText(
$row->page_namespace ),
+ 'gil_page_title' => $row->page_title,
+ 'gil_to' => $row->il_to,
+ );
}
- $res->free();
-
- $dbw->delete('globalimagelinks', array(
- 'gil_wiki' => $wiki, 'gil_page' => $pageId),
- __METHOD__);
- $dbw->insert( 'globalimagelinks', $rows, __METHOD__, 'IGNORE' );
+ $this->db->insert( 'globalimagelinks', $insert, __METHOD__ );
}
-
- // Set gil_is_local for an image
- static function setLocalFlag( $imageName, $isLocal ) {
- $dbw = self::getDatabase();
- $dbw->immediateBegin();
- $dbw->update( 'globalimagelinks', array( 'gil_is_local' =>
$isLocal ), array(
- 'gil_wiki' => self::getLocalInterwiki(),
- 'gil_to' => $imageName),
- __METHOD__ );
- $dbw->immediateCommit();
+ /**
+ * Changes the page title
+ *
+ * @param $id int Page id of the page
+ * @param $title Title New title of the page
+ */
+ public function moveTo( $id, $title ) {
+ $this->db->update(
+ 'globalimagelinks',
+ array(
+ 'gil_page_namespace' =>
$title->getNsText(),
+ 'gil_page_title' => $title->getText()
+ ),
+ array(
+ 'gil_wiki' => $this->interwiki,
+ 'gil_page' => $id
+ ),
+ __METHOD__
+ );
}
- // Set gil_is_local to false
- static function fileDeleted( &$file, &$oldimage, &$article, &$user,
$reason ) {
- if ( !$oldimage )
- self::setLocalFlag( $article->getTitle()->getDBkey(), 0
);
- return true;
- }
- // Set gil_is_local to true
- static function fileUndeleted( &$title, $versions, &$user, $reason ) {
- self::setLocalFlag( $title->getDBkey(), 1 );
- return true;
- }
- static function imageUploaded( $uploadForm ) {
- $imageName = $uploadForm->mLocalFile->getTitle()->getDBkey();
- self::setLocalFlag( $imageName, 1 );
- return true;
- }
-
- static function articleDeleted( &$article, &$user, $reason ) {
- $dbw = self::getDatabase();
- $dbw->immediateBegin();
- $dbw->delete( 'globalimagelinks', array(
- 'gil_wiki' => self::getLocalInterwiki(),
- // Use GAID_FOR_UPDATE to make sure the old id
is fetched from
- // the link cache
- 'gil_page' =>
$article->getTitle()->getArticleId(GAID_FOR_UPDATE)),
- __METHOD__ );
- $dbw->immediateCommit();
-
- return true;
- }
- static function articleMoved( &$movePageForm, &$from, &$to ) {
- $dbw = self::getDatabase();
- $dbw->immediateBegin();
- $dbw->update( 'globalimagelinks', array(
- 'gil_page_namespace' => $to->getNsText(),
- 'gil_page_title' => $to->getDBkey()
- ), array(
- 'gil_wiki' => self::getLocalInterwiki(),
- 'gil_page' => $to->getArticleId()
- ), __METHOD__ );
- $dbw->immediateCommit();
-
- return true;
- }
- public function execute( $par ) {
- global $wgOut, $wgScript, $wgRequest;
-
- $this->setHeaders();
-
- $self = Title::makeTitle( NS_SPECIAL, 'GlobalUsage' );
- $target= Title::makeTitleSafe( NS_IMAGE, $wgRequest->getText(
'target', $par ) );
-
- $wgOut->addWikiText( wfMsg( 'globalusage-text' ) );
-
- $form = Xml::openElement( 'form', array(
- 'id' => 'mw-globalusage-form',
- 'method' => 'get',
- 'action' => $wgScript ));
- $form .= Xml::hidden( 'title', $self->getPrefixedDbKey() );
- $form .= Xml::openElement( 'fieldset' );
- $form .= Xml::element( 'legend', array(), wfMsg( 'globalusage'
));
- $form .= Xml::inputLabel( wfMsg( 'filename' ), 'target',
- 'target', 50, $target->getDBkey() );
- $form .= Xml::submitButton( wfMsg( 'globalusage-ok' ) );
- $form .= Xml::closeElement( 'fieldset' );
- $form .= Xml::closeElement( 'form' );
-
- $wgOut->addHTML( $form );
-
- if ( !$target->getDBkey() ) return;
-
- $dbr = self::getDatabase( DB_SLAVE );
- $res = $dbr->select( 'globalimagelinks',
- array( 'gil_wiki', 'gil_page_namespace',
'gil_page_title' ),
- array( 'gil_to' => $target->getDBkey(), 'gil_is_local'
=> 0 ),
- __METHOD__ );
-
- // Quick dirty list output
- while ( $row = $dbr->fetchObject($res) )
- $wgOut->addWikiText(self::formatItem( $row ) );
- $dbr->freeResult($res);
- }
-
- public static function formatItem( $row ) {
- $out = '* [[';
- if ( self::getLocalInterwiki() != $row->gil_wiki )
- $out .= ':'.$row->gil_wiki;
- if ( $row->gil_page_namespace )
- $out .= ':'.str_replace('_', ' ',
$row->gil_page_namespace);
- $out .= ':'.str_replace('_', ' ', $row->gil_page_title)."]]\n";
- return $out;
- }
+
}
Added: trunk/extensions/GlobalUsage/SpecialGlobalUsage.php
===================================================================
--- trunk/extensions/GlobalUsage/SpecialGlobalUsage.php
(rev 0)
+++ trunk/extensions/GlobalUsage/SpecialGlobalUsage.php 2009-09-11 18:28:25 UTC
(rev 56201)
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Crappy ui towards globalimagelinks
+ */
+
+class SpecialGlobalUsage extends SpecialPage {
+ public function __construct() {
+ parent::__construct( 'GlobalUsage', 'globalusage' );
+
+ wfLoadExtensionMessages( 'globalusage' );
+ }
+
+ public function execute( $par ) {
+ global $wgOut, $wgRequest;
+
+ $target = $par ? $par : $wgRequest->getVal( 'target' );
+ $title = Title::newFromText( $target, NS_FILE );
+
+ $this->setHeaders();
+
+ if ( is_null( $title ) )
+ {
+ $wgOut->setPageTitle( wfMsg( 'globalusage' ) );
+ return;
+ }
+
+ $wgOut->setPageTitle( wfMsg( 'globalusage-for',
$title->getPrefixedText() ) );
+
+ $pager = new GlobalUsagePager( $title );
+
+ $wgOut->addHTML(
+ '<p>' . $pager->getNavigationBar() . '</p>' .
+ '<ul>' . $pager->getBody() . '</ul>' .
+ '<p>' . $pager->getNavigationBar() . '</p>' );
+ }
+}
+
+/**
+ * Pager for globalimagelinks.
+ */
+class GlobalUsagePager extends IndexPager {
+ public function __construct( $title = null ) {
+ // Initialize parent first
+ parent::__construct();
+
+ $this->title = $title;
+
+ // Override the DB
+ global $wgGlobalUsageDatabase;
+ $this->mDb = wfGetDB( DB_SLAVE, array(), $wgGlobalUsageDatabase
);
+ }
+ public function formatRow( $row ) {
+ return '<li>'
+ . htmlspecialchars( $row->gil_wiki ) . ':'
+ . htmlspecialchars( $row->gil_page_namespace )
. ':'
+ . htmlspecialchars( $row->gil_page_title )
+ . "</li>\n";
+ }
+ public function getQueryInfo() {
+ $info = array(
+ 'tables' => array( 'globalimagelinks' ),
+ 'fields' => array(
+ 'gil_wiki',
+ 'gil_page_namespace',
+ 'gil_page_title',
+ ),
+ );
+ if ( !is_null( $this->title ) && $this->title->getNamespace()
== NS_FILE ) {
+ $info['conds'] = array( 'gil_to' =>
$this->title->getDBkey() );
+ }
+ return $info;
+ }
+ public function getIndexField() {
+ // FIXME: This is non-unique! Needs a hack in IndexPager to
sort on (wiki, page)
+ return 'gil_wiki';
+ }
+
+
+ public function getNavigationBar() {
+ global $wgLang;
+
+ if ( isset( $this->mNavigationBar ) ) {
+ return $this->mNavigationBar;
+ }
+ $fmtLimit = $wgLang->formatNum( $this->mLimit );
+ $linkTexts = array(
+ 'prev' => wfMsgExt( 'whatlinkshere-prev', array(
'escape', 'parsemag' ), $fmtLimit ),
+ 'next' => wfMsgExt( 'whatlinkshere-next', array(
'escape', 'parsemag' ), $fmtLimit ),
+ 'first' => wfMsgHtml( 'page_first' ),
+ 'last' => wfMsgHtml( 'page_last' )
+ );
+
+ $pagingLinks = $this->getPagingLinks( $linkTexts );
+ $limitLinks = $this->getLimitLinks();
+ $limits = $wgLang->pipeList( $limitLinks );
+
+ $this->mNavigationBar = "(" . $wgLang->pipeList( array(
$pagingLinks['first'], $pagingLinks['last'] ) ) . ") " .
+ wfMsgExt( 'viewprevnext', array( 'parsemag', 'escape',
'replaceafter' ), $pagingLinks['prev'], $pagingLinks['next'], $limits );
+ return $this->mNavigationBar;
+ }
+}
\ No newline at end of file
Property changes on: trunk/extensions/GlobalUsage/SpecialGlobalUsage.php
___________________________________________________________________
Added: svn:eol-style
+ native
Deleted: trunk/extensions/GlobalUsage/populateGlobalUsage.php
===================================================================
--- trunk/extensions/GlobalUsage/populateGlobalUsage.php 2009-09-11
18:22:33 UTC (rev 56200)
+++ trunk/extensions/GlobalUsage/populateGlobalUsage.php 2009-09-11
18:28:25 UTC (rev 56201)
@@ -1,116 +0,0 @@
-<?php
-
-$wgguWikiList = array();
-
-$optionsWithArgs = array( 'wiki', 'interval', 'log', 'throttle' );
-
-require_once 'maintenance/commandLine.inc';
-require_once 'extensions/GlobalUsage/GlobalUsage.php';
-require_once 'extensions/GlobalUsage/GlobalUsage_body.php';
-require_once 'extensions/GlobalUsage/GlobalUsageDaemon.php';
-
-/*
- * We might want to use stdout later to to output useful data, so output error
- * messages to stderr where they belong.
- */
-function dieInit($msg, $exitCode = 1) {
- $fp = fopen('php://stderr', 'w');
- fwrite($fp, $msg);
- fwrite($fp, "\n");
- fclose($fp);
- exit($exitCode);
-}
-
-if(isset($options['help']) || !isset($options['log']))
- dieInit(
-"This script will populate the GlobalUsage table from the local imagelinks
-table. It will then continue to keep the table up to data using the logging
-and recentchanges tables.
-
-Usage:
- php extensions/GlobalUsage/populateGlobalUsage.inc --log <file>
- [--wiki <wiki>] [--interval <interval>] [--daemon]
- [--verbose] [--help]
-
- --log File to log current timestamp to
-
- --wiki The wiki to populate from. If this is equal to
- \$wgLocalInterwiki the database settings will be read
- from LocalSettings.php. If this is not equal, a config
- variable \$wgguWikiList[\$wiki] is expected with the
- url of the API entry point.
- --interval Pull interval in powers of 10.
- --throttle Maximum number of rows per second the populator is
- allowed to insert.
- --wait-for-slaves
- Seconds to wait for slaves. Default 0, disabled.
-
- --daemon Run as daemon processing all wikis in \$wgguWikiList.
- Useful when the extension can't be installed.
- --no-daemon Does not run a daemon after population
-
- --silent Don't print information to stderr
- --help Show this help
-",
- // Only exit with code 0 when no actual error occured
- intval(!isset($options['help'])));
-
-$defaults = array(
- 'wiki' => GlobalUsage::getLocalInterwiki(),
- 'interval' => 2,
- 'daemon' => false,
- 'no-daemon' => false,
- 'silent' => false,
- 'throttle' => 1000000,
- 'wait-for-slaves' => 0,
-);
-
-$options = array_merge( $defaults, $options );
-
-/*
- * Check whether the passed parameters are sane
- */
-
-// Check whether the log file is writable
-if (!touch($options['log']))
- dieInit("Unable to modify {$options['log']}");
-
-// Check whether the specified wiki is known
-if ($options['wiki'] != GlobalUsage::getLocalInterwiki()
- && !isset($wgguWikiList[$options['wiki']])
- && !$options['daemon'])
- dieInit("Unknown wiki '{$options['wiki']}' in \$wgguWikiList");
-
-// Check whether interval is within bounds
-$options['interval'] = intval($options['interval']);
-if ($options['interval'] < 1 || $options['interval'] > 13)
- dieInit("Interval must be at > 0 and < 14");
-
-// Check the throttle
-$options['throttle'] = intval($options['throttle']);
-if ($options['throttle'] < 1)
- dieInit("Throttle must be >= 1");
-
-if (!$options['daemon']) {
- // Remove all but the specified wiki
- $wgguWikiList = array($options['wiki'] =>
$wgguWikiList[$options['wiki']]);
-}
-
-// Create the daemon object
-$daemon = new GlobalUsageDaemon($options['log'], $wgguWikiList,
$options['silent']);
-
-// Populate all unpopulated wikis
-foreach($wgguWikiList as $wiki => $info) {
- if (!isset($daemon->timestamps[$wiki]))
- $daemon->populateGlobalUsage($wiki, $options['interval'],
- $options['throttle'],
intval($options['wait-for-slaves']));
-}
-
-// Run the daemon
-if ($options['no-daemon']) exit(0);
-if ($options['daemon'])
- $daemon->runDaemon($options['interval']);
-else
- $daemon->runLocalDaemon($options['wiki'], $options['interval']);
-
-// This point is never reached
\ No newline at end of file
Modified: trunk/extensions/GlobalUsage/readme.txt
===================================================================
--- trunk/extensions/GlobalUsage/readme.txt 2009-09-11 18:22:33 UTC (rev
56200)
+++ trunk/extensions/GlobalUsage/readme.txt 2009-09-11 18:28:25 UTC (rev
56201)
@@ -11,21 +11,4 @@
may use different namespaces, the namespace name needs to be included in the
link as well.
-There should be one globalimagelinks table per farm, even if multiple shared
-image repositories are used. The field gil_is_local indicates whether the file
-exists locally.
-== GlobalUsageDaemon and populating the table ==
-This extension provides a daemon which can be used when for some reason hooks
-can not be used, like on the Toolserver. This daemon will readout recentchanges
-to fill the globalimagelinks table.
-
-This daemon is also useful for populating the globalimagelinks table. Using
-this method it is possible to get an as consistent as posible database. To do
-so first create the table on the master wiki. Then, on each project separately:
-* Run extensions/GlobalUsage/populateGlobalUsage.php on that wiki. See
- php extensions/GlobalUsage/populateGlobalUsage.php --help for information.
-* When the daemon has finished populating the table from the local imagelinks
- and started to follow recentchanges, enable the extension.
-* When the daemon has reached the time at which the extension was enabled,
- the daemon can be stopped.
\ No newline at end of file
_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs