Merge "Update parts order in message files"
authorIAlex <ialex.wiki@gmail.com>
Thu, 1 Nov 2012 05:46:31 +0000 (05:46 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 1 Nov 2012 05:46:31 +0000 (05:46 +0000)
includes/Revision.php
includes/api/ApiEditPage.php
includes/job/JobQueue.php
includes/job/JobQueueDB.php
maintenance/nextJobDB.php
skins/CologneBlue.php
skins/cologneblue/screen.css

index afba498..431be69 100644 (file)
@@ -607,7 +607,7 @@ class Revision implements IDBAccessObject {
                                        $this->mPage = $this->mTitle->getArticleID();
                                } elseif ( $this->mTitle->getArticleID() !== $this->mPage ) {
                                        // got different page IDs, something is wrong.
-                                       wfWarn( "Page ID " . $this->mPage . " mismatches the ID "
+                                       throw new MWException( "Page ID " . $this->mPage . " mismatches the ID "
                                                        . $this->mTitle->getArticleID() . " provided by the Title object." );
                                }
                        }
index ec1b06e..4cb91bc 100644 (file)
@@ -246,6 +246,11 @@ class ApiEditPage extends ApiBase {
                        $requestArray['wpSectionTitle'] = $params['sectiontitle'];
                }
 
+               // TODO: Pass along information from 'undoafter' as well
+               if ( $params['undo'] > 0 ) {
+                       $requestArray['wpUndidRevision'] = $params['undo'];
+               }
+
                // Watch out for basetimestamp == ''
                // wfTimestamp() treats it as NOW, almost certainly causing an edit conflict
                if ( !is_null( $params['basetimestamp'] ) && $params['basetimestamp'] != '' ) {
index 4637bd2..2778829 100644 (file)
@@ -47,7 +47,7 @@ abstract class JobQueue {
        /**
         * Get a job queue object of the specified type.
         * $params includes:
-        *     class : what job class to use (determines job type)
+        *     class : What job class to use (determines job type)
         *     wiki  : wiki ID of the wiki the jobs are for (defaults to current wiki)
         *     type  : The name of the job types this queue handles
         *     order : Order that pop() selects jobs, either "timestamp" or "random".
index d9465fb..e9af92a 100644 (file)
@@ -126,30 +126,25 @@ class JobQueueDB extends JobQueue {
                        do { // retry when our row is invalid or deleted as a duplicate
                                // Try to reserve a row in the DB...
                                if ( $this->order === 'timestamp' ) { // oldest first
-                                       $found = $this->claim( $uuid, 0, true );
+                                       $row = $this->claimOldest( $uuid );
                                } else { // random first
-                                       $rand  = mt_rand( 0, self::MAX_JOB_RANDOM ); // encourage concurrent UPDATEs
-                                       $gte   = (bool)mt_rand( 0, 1 ); // find rows with rand before/after $rand
-                                       $found = $this->claim( $uuid, $rand, $gte )
-                                               || $this->claim( $uuid, $rand, !$gte ); // try both directions
+                                       $rand = mt_rand( 0, self::MAX_JOB_RANDOM ); // encourage concurrent UPDATEs
+                                       $gte  = (bool)mt_rand( 0, 1 ); // find rows with rand before/after $rand
+                                       $row  = $this->claimRandom( $uuid, $rand, $gte );
+                                       if ( !$row ) { // need to try the other direction
+                                               $row = $this->claimRandom( $uuid, $rand, !$gte );
+                                       }
                                }
                                // Check if we found a row to reserve...
-                               if ( !$found ) {
+                               if ( !$row ) {
                                        $wgMemc->set( $this->getEmptinessCacheKey(), 'true', self::CACHE_TTL );
                                        break; // nothing to do
                                }
-                               // Fetch any row that we just reserved...
-                               $row = $dbw->selectRow( 'job', '*',
-                                       array( 'job_cmd' => $this->type, 'job_token' => $uuid ), __METHOD__ );
-                               // Check if another process deleted it as a duplicate
-                               if ( !$row ) {
-                                       wfDebugLog( 'JobQueueDB', "Row deleted as duplicate by another process." );
-                                       continue; // try again
-                               }
                                // Get the job object from the row...
                                $title = Title::makeTitleSafe( $row->job_namespace, $row->job_title );
                                if ( !$title ) {
                                        $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
+                                       wfIncrStats( 'job-pop' );
                                        wfDebugLog( 'JobQueueDB', "Row has invalid title '{$row->job_title}'." );
                                        continue; // try again
                                }
@@ -162,6 +157,7 @@ class JobQueueDB extends JobQueue {
                                                        "job_id != {$dbw->addQuotes( $row->job_id )}" ),
                                                __METHOD__
                                        );
+                                       wfIncrStats( 'job-pop', $dbw->affectedRows() );
                                }
                                break; // done
                        } while( true );
@@ -176,51 +172,105 @@ class JobQueueDB extends JobQueue {
 
        /**
         * Reserve a row with a single UPDATE without holding row locks over RTTs...
+        *
         * @param $uuid string 32 char hex string
         * @param $rand integer Random unsigned integer (31 bits)
         * @param $gte bool Search for job_random >= $random (otherwise job_random <= $random)
-        * @return integer Number of affected rows
+        * @return Row|false
         */
-       protected function claim( $uuid, $rand, $gte ) {
+       protected function claimRandom( $uuid, $rand, $gte ) {
                $dbw  = $this->getMasterDB();
                $dir  = $gte ? 'ASC' : 'DESC';
                $ineq = $gte ? '>=' : '<=';
-               if ( $dbw->getType() === 'mysql' ) {
-                       // Per http://bugs.mysql.com/bug.php?id=6980, we can't use subqueries on the
-                       // same table being changed in an UPDATE query in MySQL (gives Error: 1093).
-                       // Oracle and Postgre have no such limitation. However, MySQL offers an
-                       // alternative here by supporting ORDER BY + LIMIT for UPDATE queries.
-                       // The DB wrapper functions do not support this, so it's done manually.
-                       $dbw->query( "UPDATE {$dbw->tableName( 'job' )}
-                               SET
-                                       job_token = {$dbw->addQuotes( $uuid ) },
-                                       job_token_timestamp = {$dbw->addQuotes( $dbw->timestamp() )}
-                               WHERE (
-                                       job_cmd = {$dbw->addQuotes( $this->type )}
-                                       AND job_token = {$dbw->addQuotes( '' )}
-                                       AND job_random {$ineq} {$dbw->addQuotes( $rand )}
-                               ) ORDER BY job_random {$dir} LIMIT 1",
-                               __METHOD__
-                       );
-               } else {
-                       // Use a subquery to find the job, within an UPDATE to claim it.
-                       // This uses as much of the DB wrapper functions as possible.
-                       $dbw->update( 'job',
-                               array( 'job_token' => $uuid, 'job_token_timestamp' => $dbw->timestamp() ),
-                               array( 'job_id = (' .
-                                       $dbw->selectSQLText( 'job', 'job_id',
-                                               array(
-                                                       'job_cmd'   => $this->type,
-                                                       'job_token' => '',
-                                                       "job_random {$ineq} {$dbw->addQuotes( $rand )}" ),
-                                               __METHOD__,
-                                               array( 'ORDER BY' => "job_random {$dir}", 'LIMIT' => 1 ) ) .
-                                       ')'
-                               ),
-                               __METHOD__
+
+               $row = false; // the row acquired
+               // This uses a replication safe method for acquiring jobs. One could use UPDATE+LIMIT
+               // instead, but that either uses ORDER BY (in which case it deadlocks in MySQL) or is
+               // not replication safe. Due to http://bugs.mysql.com/bug.php?id=6980, subqueries cannot
+               // be used here with MySQL.
+               do {
+                       $row = $dbw->selectRow( 'job', '*', // find a random job
+                               array(
+                                       'job_cmd'   => $this->type,
+                                       'job_token' => '',
+                                       "job_random {$ineq} {$dbw->addQuotes( $rand )}" ),
+                               __METHOD__,
+                               array( 'ORDER BY' => "job_random {$dir}" )
                        );
-               }
-               return $dbw->affectedRows();
+                       if ( $row ) { // claim the job
+                               $dbw->update( 'job', // update by PK
+                                       array( 'job_token' => $uuid, 'job_token_timestamp' => $dbw->timestamp() ),
+                                       array( 'job_cmd' => $this->type, 'job_id' => $row->job_id, 'job_token' => '' ),
+                                       __METHOD__
+                               );
+                               // This might get raced out by another runner when claiming the previously
+                               // selected row. The use of job_random should minimize this problem, however.
+                               if ( !$dbw->affectedRows() ) {
+                                       $row = false; // raced out
+                               }
+                       } else {
+                               break; // nothing to do
+                       }
+               } while ( !$row );
+
+               return $row;
+       }
+
+       /**
+        * Reserve a row with a single UPDATE without holding row locks over RTTs...
+        *
+        * @param $uuid string 32 char hex string
+        * @return Row|false
+        */
+       protected function claimOldest( $uuid ) {
+               $dbw  = $this->getMasterDB();
+
+               $row = false; // the row acquired
+               do {
+                       if ( $dbw->getType() === 'mysql' ) {
+                               // Per http://bugs.mysql.com/bug.php?id=6980, we can't use subqueries on the
+                               // same table being changed in an UPDATE query in MySQL (gives Error: 1093).
+                               // Oracle and Postgre have no such limitation. However, MySQL offers an
+                               // alternative here by supporting ORDER BY + LIMIT for UPDATE queries.
+                               $dbw->query( "UPDATE {$dbw->tableName( 'job' )}
+                                       SET
+                                               job_token = {$dbw->addQuotes( $uuid ) },
+                                               job_token_timestamp = {$dbw->addQuotes( $dbw->timestamp() )}
+                                       WHERE (
+                                               job_cmd = {$dbw->addQuotes( $this->type )}
+                                               AND job_token = {$dbw->addQuotes( '' )}
+                                       ) ORDER BY job_random ASC LIMIT 1",
+                                       __METHOD__
+                               );
+                       } else {
+                               // Use a subquery to find the job, within an UPDATE to claim it.
+                               // This uses as much of the DB wrapper functions as possible.
+                               $dbw->update( 'job',
+                                       array( 'job_token' => $uuid, 'job_token_timestamp' => $dbw->timestamp() ),
+                                       array( 'job_id = (' .
+                                               $dbw->selectSQLText( 'job', 'job_id',
+                                                       array( 'job_cmd' => $this->type, 'job_token' => '' ),
+                                                       __METHOD__,
+                                                       array( 'ORDER BY' => 'job_random ASC', 'LIMIT' => 1 ) ) .
+                                               ')'
+                                       ),
+                                       __METHOD__
+                               );
+                       }
+                       // Fetch any row that we just reserved...
+                       if ( $dbw->affectedRows() ) {
+                               $row = $dbw->selectRow( 'job', '*',
+                                       array( 'job_cmd' => $this->type, 'job_token' => $uuid ), __METHOD__
+                               );
+                               if ( !$row ) { // raced out by duplicate job removal
+                                       wfDebugLog( 'JobQueueDB', "Row deleted as duplicate by another process." );
+                               }
+                       } else {
+                               break; // nothing to do
+                       }
+               } while ( !$row );
+
+               return $row;
        }
 
        /**
@@ -229,10 +279,7 @@ class JobQueueDB extends JobQueue {
         */
        protected function doAck( Job $job ) {
                $dbw = $this->getMasterDB();
-               if ( $dbw->trxLevel() ) {
-                       wfWarn( "Attempted to ack a job in a transaction; committing first." );
-                       $dbw->commit(); // push existing transaction
-               }
+               $dbw->commit( __METHOD__, 'flush' ); // flush existing transaction
 
                $autoTrx = $dbw->getFlag( DBO_TRX ); // automatic begin() enabled?
                $dbw->clearFlag( DBO_TRX ); // make each query its own transaction
index e66e981..75018de 100644 (file)
@@ -97,17 +97,23 @@ class nextJobDB extends Maintenance {
         * @return bool
         */
        function checkJob( $type, $dbName ) {
-               $lb = wfGetLB( $dbName );
-               $db = $lb->getConnection( DB_MASTER, array(), $dbName );
+               global $wgJobTypesExcludedFromDefaultQueue;
+
                if ( $type === false ) {
-                       $conds = Job::defaultQueueConditions( );
+                       $lb = wfGetLB( $dbName );
+                       $db = $lb->getConnection( DB_MASTER, array(), $dbName );
+                       $conds = array();
+                       if ( count( $wgJobTypesExcludedFromDefaultQueue ) > 0 ) {
+                               foreach ( $wgJobTypesExcludedFromDefaultQueue as $cmdType ) {
+                                       $conds[] = "job_cmd != " . $db->addQuotes( $cmdType );
+                               }
+                       }
+                       $exists = (bool)$db->selectField( 'job', '1', $conds, __METHOD__ );
+                       $lb->reuseConnection( $db );
                } else {
-                       $conds = array( 'job_cmd' => $type );
+                       $exists = !JobQueueGroup::singleton( $dbName )->get( $type )->isEmpty();
                }
 
-
-               $exists = (bool) $db->selectField( 'job', '1', $conds, __METHOD__ );
-               $lb->reuseConnection( $db );
                return $exists;
        }
 
index 4a03aa0..8c97a3c 100644 (file)
@@ -540,13 +540,48 @@ class CologneBlueTemplate extends BaseTemplate {
        }
 
        /**
-        * @param $heading string
-        * @return string
+        * Adds CologneBlue-specific items to the sidebar: qbedit, qbpageoptions and qbmyoptions menus.
+        *
+        * @param $bar sidebar data
+        * @return array modified sidebar data
         *
         * @fixed
         */
-       function menuHead( $heading ) {
-               return "\n<h6>" . htmlspecialchars( $heading ) . "</h6>";
+       function sidebarAdditions( $bar ) {
+               // "This page" and "Edit" menus
+               // We need to do some massaging here... we reuse all of the items, except for $...['views']['view'],
+               // as $...['namespaces']['main'] and $...['namespaces']['talk'] together serve the same purpose.
+               // We also don't use $...['variants'], these are displayed in the top menu.
+               $content_navigation = $this->data['content_navigation'];
+               $qbpageoptions = array_merge(
+                       $content_navigation['namespaces'],
+                       array(
+                               'history' => $content_navigation['views']['history'],
+                               'watch' => $content_navigation['actions']['watch'],
+                               'unwatch' => $content_navigation['actions']['unwatch'],
+                       )
+               );
+               $content_navigation['actions']['watch'] = null;
+               $content_navigation['actions']['unwatch'] = null;
+               $qbedit = array_merge(
+                       array(
+                               'edit' => $content_navigation['views']['edit'],
+                               'addsection' => $content_navigation['views']['addsection'],
+                       ),
+                       $content_navigation['actions']
+               );
+
+               // Personal tools ("My pages")
+               $qbmyoptions = $this->getPersonalTools();
+               foreach ( array ( 'logout', 'createaccount', 'login', 'anonlogin' ) as $key ) {
+                       $qbmyoptions[$key] = null;
+               }
+
+               $bar['qbedit'] = $qbedit;
+               $bar['qbpageoptions'] = $qbpageoptions;
+               $bar['qbmyoptions'] = $qbmyoptions;
+
+               return $bar;
        }
 
        /**
@@ -557,117 +592,83 @@ class CologneBlueTemplate extends BaseTemplate {
         *
         * @fixed
         */
-       function quickBar(){
-               $s = "\n<div id='quickbar'>";
-
-               $sep = "<br />\n";
-
-               $plain_bar = $this->data['sidebar'];
+       function quickBar() {
+               // Massage the sidebar. We want to:
+               // * place SEARCH at the beginning
+               // * add new portlets before TOOLBOX (or at the end, if it's missing)
+               // * remove LANGUAGES (langlinks are displayed elsewhere)
+               $orig_bar = $this->data['sidebar'];
                $bar = array();
-
-               // Massage the sidebar
-               // We want to place SEARCH at the beginning and a lot of stuff before TOOLBOX (or at the end, if it's missing)
-               $additions_done = false;
-               while ( !$additions_done ) {
-                       $bar = array(); // Empty it out
-
-                       // Always display search on top
-                       $bar['SEARCH'] = true;
-
-                       foreach ( $plain_bar as $heading => $links ) {
-                               if ( $heading == 'TOOLBOX' ) {
-                                       if( $links !== NULL ) {
-                                               // If this is not a toolbox prosthetic we inserted outselves, fill it out
-                                               $plain_bar['TOOLBOX'] = $this->getToolbox();
-                                       }
-
-                                       // And insert the stuff
-
-                                       // "This page" and "Edit" menus
-                                       // We need to do some massaging here... we reuse all of the items, except for $...['views']['view'],
-                                       // as $...['namespaces']['main'] and $...['namespaces']['talk'] together serve the same purpose.
-                                       // We also don't use $...['variants'], these are displayed in the top menu.
-                                       $content_navigation = $this->data['content_navigation'];
-                                       $qbpageoptions = array_merge(
-                                               $content_navigation['namespaces'],
-                                               array(
-                                                       'history' => $content_navigation['views']['history'],
-                                                       'watch' => $content_navigation['actions']['watch'],
-                                                       'unwatch' => $content_navigation['actions']['unwatch'],
-                                               )
-                                       );
-                                       $content_navigation['actions']['watch'] = null;
-                                       $content_navigation['actions']['unwatch'] = null;
-                                       $qbedit = array_merge(
-                                               array(
-                                                       'edit' => $content_navigation['views']['edit'],
-                                                       'addsection' => $content_navigation['views']['addsection'],
-                                               ),
-                                               $content_navigation['actions']
-                                       );
-                                       $bar['qbedit'] = $qbedit;
-                                       $bar['qbpageoptions'] = $qbpageoptions;
-
-                                       // Personal tools ("My pages")
-                                       $bar['qbmyoptions'] = $this->getPersonalTools();
-                                       foreach ( array ( 'logout', 'createaccount', 'login', 'anonlogin' ) as $key ) {
-                                               $bar['qbmyoptions'][$key] = null;
-                                       }
-
-                                       $additions_done = true;
-                               }
-
-                               // Re-insert current heading, unless it's SEARCH
-                               if ( $heading != 'SEARCH' ) {
-                                       $bar[$heading] = $plain_bar[$heading];
-                               }
+               $hasToolbox = false;
+
+               // Always display search first
+               $bar['SEARCH'] = true;
+               // Copy everything except for langlinks, inserting new items before toolbox
+               foreach ( $orig_bar as $heading => $data ) {
+                       if ( $heading == 'TOOLBOX' ) {
+                               // Insert the stuff
+                               $bar = $this->sidebarAdditions( $bar );
+                               $hasToolbox = true;
                        }
 
-                       // If TOOLBOX is missing, $additions_done is still false
-                       if ( !$additions_done ) {
-                               $plain_bar['TOOLBOX'] = false;
+                       if ( $heading != 'LANGUAGES' ) {
+                               $bar[$heading] = $data;
                        }
                }
+               // If toolbox is missing, add our items at the end
+               if ( !$hasToolbox ) {
+                       $bar = $this->sidebarAdditions( $bar );
+               }
 
-               foreach ( $bar as $heading => $links ) {
+
+               // Fill out special sidebar items with content
+               $orig_bar = $bar;
+               $bar = array();
+               foreach ( $orig_bar as $heading => $data ) {
                        if ( $heading == 'SEARCH' ) {
-                               $s .= $this->menuHead( wfMessage( 'qbfind' )->text() );
-                               $s .= $this->searchForm( 'sidebar' );
-                       } elseif ( $heading == 'LANGUAGES' ) {
-                               // discard these; we display languages below page content
-                       } elseif ( $links ) {
-                               if ( is_array( $links ) ) {
-                                       // Use the navigation heading from standard sidebar as the "browse" section
-                                       if ( $heading == 'navigation' ) {
-                                               $heading = 'qbbrowse';
-                                       }
-                                       if ( $heading == 'TOOLBOX' ) {
-                                               $heading = 'toolbox';
-                                       }
+                               $bar['qbfind'] = $this->searchForm( 'sidebar' );
+                       } elseif ( $heading == 'TOOLBOX' ) {
+                               $bar['toolbox'] = $this->getToolbox();
+                       } elseif ( $heading == 'navigation' ) {
+                               // Use the navigation heading from standard sidebar as the "browse" section
+                               $bar['qbbrowse'] = $data;
+                       } else {
+                               $bar[$heading] = $data;
+                       }
+               }
 
-                                       $headingMsg = wfMessage( $heading );
-                                       $any_link = false;
-                                       $t = $this->menuHead( $headingMsg->exists() ? $headingMsg->escaped() : htmlspecialchars( $heading ) );
 
-                                       foreach ( $links as $key => $link ) {
-                                               // Can be empty due to rampant sidebar massaging we're doing above
-                                               if ( $link ) {
-                                                       $any_link = true;
-                                                       $t .= $this->makeListItem( $key, $link, array( 'tag' => 'span' ) ) . $sep;
-                                               }
-                                       }
+               // Output the sidebar
+               $s = "<div id='quickbar'>\n";
+
+               foreach ( $bar as $heading => $data ) {
+                       $headingMsg = wfMessage( $heading );
+                       $headingHTML = "<h6>" . ( $headingMsg->exists() ? $headingMsg->escaped() : htmlspecialchars( $heading ) ) . "</h6>";
+                       $portletId = Sanitizer::escapeId( "p-$heading" );
+                       $listHTML = "";
 
-                                       if ( $any_link ) {
-                                               $s .= $t;
+                       if ( is_array( $data ) ) {
+                               // $data is an array of links
+                               foreach ( $data as $key => $link ) {
+                                       // Can be empty due to how the sidebar additions are done
+                                       if ( $link ) {
+                                               $listHTML .= $this->makeListItem( $key, $link );
                                        }
-                               } else {
-                                       // $links can be a HTML string
-                                       $s .= $links;
                                }
+                               if ( $listHTML ) {
+                                       $listHTML = "<ul>$listHTML</ul>";
+                               }
+                       } else {
+                               // $data is a HTML <ul>-list string
+                               $listHTML = $data;
+                       }
+
+                       if ( $listHTML ) {
+                               $s .= "<div class=\"portlet\" id=\"$portletId\">\n$headingHTML\n$listHTML\n</div>\n";
                        }
                }
 
-               $s .= $sep . "\n</div>\n";
+               $s .= "</div>\n";
                return $s;
        }
 
index aac786d..51186d8 100644 (file)
@@ -110,6 +110,14 @@ textarea {
        margin-top: 0;
 }
 
+#quickbar .portlet ul,
+#quickbar .portlet li {
+       list-style-type: none;
+       margin: 0;
+       padding: 0;
+       line-height: inherit;
+}
+
 h1 {
        color: #666666;
        font-family: Verdana, Arial, sans-serif;