return false;
}
+ $empty = true;
+ $failed = 0;
foreach ( $this->partitionQueues as $queue ) {
try {
- if ( !$queue->doIsEmpty() ) {
- $this->cache->add( $key, 'false', self::CACHE_TTL_LONG );
-
- return false;
- }
+ $empty = $empty && $queue->doIsEmpty();
} catch ( JobQueueError $e ) {
+ ++$failed;
MWExceptionHandler::logException( $e );
}
}
+ $this->throwErrorIfAllPartitionsDown( $failed );
- $this->cache->add( $key, 'true', self::CACHE_TTL_LONG );
-
- return true;
+ $this->cache->add( $key, $empty ? 'true' : 'false', self::CACHE_TTL_LONG );
+ return !$empty;
}
protected function doGetSize() {
return $count;
}
- $count = 0;
+ $failed = 0;
foreach ( $this->partitionQueues as $queue ) {
try {
$count += $queue->$method();
} catch ( JobQueueError $e ) {
+ ++$failed;
MWExceptionHandler::logException( $e );
}
}
+ $this->throwErrorIfAllPartitionsDown( $failed );
$this->cache->set( $key, $count, self::CACHE_TTL_SHORT );
} else {
$partitionRing = $partitionRing->newWithoutLocation( $partition ); // blacklist
if ( !$partitionRing ) {
- throw new JobQueueError( "Could not insert job(s), all partitions are down." );
+ throw new JobQueueError( "Could not insert job(s), no partitions available." );
}
$jobsLeft = array_merge( $jobsLeft, $jobBatch ); // not inserted
}
} else {
$partitionRing = $partitionRing->newWithoutLocation( $partition ); // blacklist
if ( !$partitionRing ) {
- throw new JobQueueError( "Could not insert job(s), all partitions are down." );
+ throw new JobQueueError( "Could not insert job(s), no partitions available." );
}
$jobsLeft = array_merge( $jobsLeft, $jobBatch ); // not inserted
}
$partitionsTry = $this->partitionMap; // (partition => weight)
+ $failed = 0;
while ( count( $partitionsTry ) ) {
$partition = ArrayUtils::pickRandom( $partitionsTry );
if ( $partition === false ) {
try {
$job = $queue->pop();
} catch ( JobQueueError $e ) {
- $job = false;
+ ++$failed;
MWExceptionHandler::logException( $e );
+ $job = false;
}
if ( $job ) {
$job->metadata['QueuePartition'] = $partition;
unset( $partitionsTry[$partition] ); // blacklist partition
}
}
+ $this->throwErrorIfAllPartitionsDown( $failed );
$this->cache->set( $key, 'true', JobQueueDB::CACHE_TTL_LONG );
}
protected function doDelete() {
+ $failed = 0;
/** @var JobQueue $queue */
foreach ( $this->partitionQueues as $queue ) {
try {
$queue->doDelete();
} catch ( JobQueueError $e ) {
+ ++$failed;
MWExceptionHandler::logException( $e );
}
}
+ $this->throwErrorIfAllPartitionsDown( $failed );
+ return true;
}
protected function doWaitForBackups() {
+ $failed = 0;
/** @var JobQueue $queue */
foreach ( $this->partitionQueues as $queue ) {
try {
$queue->waitForBackups();
} catch ( JobQueueError $e ) {
+ ++$failed;
MWExceptionHandler::logException( $e );
}
}
+ $this->throwErrorIfAllPartitionsDown( $failed );
}
protected function doGetPeriodicTasks() {
protected function doGetSiblingQueuesWithJobs( array $types ) {
$result = array();
+ $failed = 0;
/** @var JobQueue $queue */
foreach ( $this->partitionQueues as $queue ) {
try {
break; // short-circuit
}
} catch ( JobQueueError $e ) {
+ ++$failed;
MWExceptionHandler::logException( $e );
}
}
+ $this->throwErrorIfAllPartitionsDown( $failed );
return array_values( $result );
}
protected function doGetSiblingQueueSizes( array $types ) {
$result = array();
-
+ $failed = 0;
/** @var JobQueue $queue */
foreach ( $this->partitionQueues as $queue ) {
try {
return null; // not supported on all partitions; bail
}
} catch ( JobQueueError $e ) {
+ ++$failed;
MWExceptionHandler::logException( $e );
}
}
+ $this->throwErrorIfAllPartitionsDown( $failed );
return $result;
}
+ /**
+ * Throw an error if no partitions available
+ *
+ * @param int $down The number of up partitions down
+ * @return void
+ * @throws JobQueueError
+ */
+ protected function throwErrorIfAllPartitionsDown( $down ) {
+ if ( $down >= count( $this->partitionQueues ) ) {
+ throw new JobQueueError( 'No queue partitions available.' );
+ }
+ }
+
public function setTestingPrefix( $key ) {
/** @var JobQueue $queue */
foreach ( $this->partitionQueues as $queue ) {
$u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
$u->saveSettings();
- # Update user count
+ // Update user count
DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
+ // Watch user's userpage and talk page
+ $u->addWatch( $u->getUserPage(), WatchedItem::IGNORE_USER_RIGHTS );
+
return Status::newGood( $u );
}
}
},
+ /**
+ * Hide the element with suggestions and clean up some state.
+ */
+ hide: function ( context ) {
+ // Remove any highlights, including on "special" items
+ context.data.$container.find( '.suggestions-result-current' ).removeClass( 'suggestions-result-current' );
+ // Hide the container
+ context.data.$container.hide();
+ },
+
/**
* Restore the text the user originally typed in the textbox, before it
* was overwritten by highlight(). This restores the value the currently
// if the textbox is empty then clear the result div, but leave other settings intouched
function maybeFetch() {
if ( context.data.$textbox.val().length === 0 ) {
- context.data.$container.hide();
+ $.suggestions.hide( context );
context.data.prevText = '';
} else if (
context.data.$textbox.val() !== context.data.prevText ||
if ( context.data !== undefined ) {
if ( context.data.$textbox.val().length === 0 ) {
// Hide the div when no suggestion exist
- context.data.$container.hide();
+ $.suggestions.hide( context );
} else {
// Rebuild the suggestions list
context.data.$container.show();
break;
// Escape
case 27:
- context.data.$container.hide();
+ $.suggestions.hide( context );
$.suggestions.restore( context );
$.suggestions.cancel( context );
context.data.$textbox.trigger( 'change' );
break;
// Enter
case 13:
- context.data.$container.hide();
preventDefault = wasVisible;
selected = context.data.$container.find( '.suggestions-result-current' );
+ $.suggestions.hide( context );
if ( selected.length === 0 || context.data.selectedWithMouse ) {
// if nothing is selected OR if something was selected with the mouse,
// cancel any current requests and submit the form
// do not interfere with non-left clicks or if modifier keys are pressed (e.g. ctrl-click)
if ( !( e.which !== 1 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey ) ) {
$.suggestions.highlight( context, $result, true );
- context.data.$container.hide();
+ $.suggestions.hide( context );
if ( typeof context.config.result.select === 'function' ) {
context.config.result.select.call( $result, context.data.$textbox );
}
}
// do not interfere with non-left clicks or if modifier keys are pressed (e.g. ctrl-click)
if ( !( e.which !== 1 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey ) ) {
- context.data.$container.hide();
+ $.suggestions.hide( context );
if ( typeof context.config.special.select === 'function' ) {
context.config.special.select.call( $special, context.data.$textbox );
}
if ( context.data.mouseDownOn.length > 0 ) {
return;
}
- context.data.$container.hide();
+ $.suggestions.hide( context );
$.suggestions.cancel( context );
} );
}
// Compatibility map
map = {
- browsers: {
- // Left-to-right languages
- ltr: {
- // SimpleSearch is broken in Opera < 9.6
- opera: [['>=', 9.6]],
- docomo: false,
- blackberry: false,
- ipod: false,
- iphone: false
- },
- // Right-to-left languages
- rtl: {
- opera: [['>=', 9.6]],
- docomo: false,
- blackberry: false,
- ipod: false,
- iphone: false
- }
- }
+ // SimpleSearch is broken in Opera < 9.6
+ opera: [['>=', 9.6]],
+ docomo: false,
+ blackberry: false,
+ ipod: false,
+ iphone: false
};
if ( !$.client.test( map ) ) {
font-size: 84%;
line-height: 1.2em;
margin: 0 0 1.4em 1em;
- color: #7d7d7d;
+ color: #545454;
width: auto;
}
span.subpages {
}
#contentSub {
- color: #888;
+ color: #545454;
font-size: small;
padding-left: 2em;
}
return $cases;
}
+ /**
+ * @dataProvider provider_testGetFileStat
+ * @covers FileBackend::streamFile
+ */
+ public function testStreamFile( $path, $content, $alreadyExists ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestStreamFile( $path, $content, $alreadyExists );
+ $this->tearDownFiles();
+ }
+
+ private function doTestStreamFile( $path, $content ) {
+ $backendName = $this->backendClass();
+
+ // Test doStreamFile() directly to avoid header madness
+ $class = new ReflectionClass( $this->backend );
+ $method = $class->getMethod( 'doStreamFile' );
+ $method->setAccessible( true );
+
+ if ( $content !== null ) {
+ $this->prepare( array( 'dir' => dirname( $path ) ) );
+ $status = $this->create( array( 'dst' => $path, 'content' => $content ) );
+ $this->assertGoodStatus( $status,
+ "Creation of file at $path succeeded ($backendName)." );
+
+ ob_start();
+ $method->invokeArgs( $this->backend, array( array( 'src' => $path ) ) );
+ $data = ob_get_contents();
+ ob_end_clean();
+
+ $this->assertEquals( $content, $data, "Correct content streamed from '$path'" );
+ } else { // 404 case
+ ob_start();
+ $method->invokeArgs( $this->backend, array( array( 'src' => $path ) ) );
+ $data = ob_get_contents();
+ ob_end_clean();
+
+ $this->assertEquals( '', $data, "Correct content streamed from '$path' ($backendName)" );
+ }
+ }
+
+ public static function provider_testStreamFile() {
+ $cases = array();
+
+ $base = self::baseStorePath();
+ $cases[] = array( "$base/unittest-cont1/e/b/z/some_file.txt", "some file contents" );
+ $cases[] = array( "$base/unittest-cont1/e/b/some-other_file.txt", null );
+
+ return $cases;
+ }
+
/**
* @dataProvider provider_testGetFileContents
* @covers FileBackend::getFileContents