MediaWiki
REL1_20
|
00001 <?php 00059 abstract class FileBackend { 00060 protected $name; // string; unique backend name 00061 protected $wikiId; // string; unique wiki name 00062 protected $readOnly; // string; read-only explanation message 00063 protected $parallelize; // string; when to do operations in parallel 00064 protected $concurrency; // integer; how many operations can be done in parallel 00065 00067 protected $lockManager; 00069 protected $fileJournal; 00070 00093 public function __construct( array $config ) { 00094 $this->name = $config['name']; 00095 if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) { 00096 throw new MWException( "Backend name `{$this->name}` is invalid." ); 00097 } 00098 $this->wikiId = isset( $config['wikiId'] ) 00099 ? $config['wikiId'] 00100 : wfWikiID(); // e.g. "my_wiki-en_" 00101 $this->lockManager = ( $config['lockManager'] instanceof LockManager ) 00102 ? $config['lockManager'] 00103 : LockManagerGroup::singleton()->get( $config['lockManager'] ); 00104 $this->fileJournal = isset( $config['fileJournal'] ) 00105 ? ( ( $config['fileJournal'] instanceof FileJournal ) 00106 ? $config['fileJournal'] 00107 : FileJournal::factory( $config['fileJournal'], $this->name ) ) 00108 : FileJournal::factory( array( 'class' => 'NullFileJournal' ), $this->name ); 00109 $this->readOnly = isset( $config['readOnly'] ) 00110 ? (string)$config['readOnly'] 00111 : ''; 00112 $this->parallelize = isset( $config['parallelize'] ) 00113 ? (string)$config['parallelize'] 00114 : 'off'; 00115 $this->concurrency = isset( $config['concurrency'] ) 00116 ? (int)$config['concurrency'] 00117 : 50; 00118 } 00119 00127 final public function getName() { 00128 return $this->name; 00129 } 00130 00137 final public function getWikiId() { 00138 return $this->wikiId; 00139 } 00140 00146 final public function isReadOnly() { 00147 return ( $this->readOnly != '' ); 00148 } 00149 00155 final public function getReadOnlyReason() { 00156 return ( $this->readOnly != '' ) ? $this->readOnly : false; 00157 } 00158 00289 final public function doOperations( array $ops, array $opts = array() ) { 00290 if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) { 00291 return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly ); 00292 } 00293 if ( empty( $opts['force'] ) ) { // sanity 00294 unset( $opts['nonLocking'] ); 00295 unset( $opts['allowStale'] ); 00296 } 00297 $opts['concurrency'] = 1; // off 00298 if ( $this->parallelize === 'implicit' ) { 00299 if ( !isset( $opts['parallelize'] ) || $opts['parallelize'] ) { 00300 $opts['concurrency'] = $this->concurrency; 00301 } 00302 } elseif ( $this->parallelize === 'explicit' ) { 00303 if ( !empty( $opts['parallelize'] ) ) { 00304 $opts['concurrency'] = $this->concurrency; 00305 } 00306 } 00307 return $this->doOperationsInternal( $ops, $opts ); 00308 } 00309 00313 abstract protected function doOperationsInternal( array $ops, array $opts ); 00314 00326 final public function doOperation( array $op, array $opts = array() ) { 00327 return $this->doOperations( array( $op ), $opts ); 00328 } 00329 00340 final public function create( array $params, array $opts = array() ) { 00341 return $this->doOperation( array( 'op' => 'create' ) + $params, $opts ); 00342 } 00343 00354 final public function store( array $params, array $opts = array() ) { 00355 return $this->doOperation( array( 'op' => 'store' ) + $params, $opts ); 00356 } 00357 00368 final public function copy( array $params, array $opts = array() ) { 00369 return $this->doOperation( array( 'op' => 'copy' ) + $params, $opts ); 00370 } 00371 00382 final public function move( array $params, array $opts = array() ) { 00383 return $this->doOperation( array( 'op' => 'move' ) + $params, $opts ); 00384 } 00385 00396 final public function delete( array $params, array $opts = array() ) { 00397 return $this->doOperation( array( 'op' => 'delete' ) + $params, $opts ); 00398 } 00399 00488 final public function doQuickOperations( array $ops, array $opts = array() ) { 00489 if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) { 00490 return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly ); 00491 } 00492 foreach ( $ops as &$op ) { 00493 $op['overwrite'] = true; // avoids RTTs in key/value stores 00494 } 00495 return $this->doQuickOperationsInternal( $ops ); 00496 } 00497 00502 abstract protected function doQuickOperationsInternal( array $ops ); 00503 00514 final public function doQuickOperation( array $op ) { 00515 return $this->doQuickOperations( array( $op ) ); 00516 } 00517 00528 final public function quickCreate( array $params ) { 00529 return $this->doQuickOperation( array( 'op' => 'create' ) + $params ); 00530 } 00531 00542 final public function quickStore( array $params ) { 00543 return $this->doQuickOperation( array( 'op' => 'store' ) + $params ); 00544 } 00545 00556 final public function quickCopy( array $params ) { 00557 return $this->doQuickOperation( array( 'op' => 'copy' ) + $params ); 00558 } 00559 00570 final public function quickMove( array $params ) { 00571 return $this->doQuickOperation( array( 'op' => 'move' ) + $params ); 00572 } 00573 00584 final public function quickDelete( array $params ) { 00585 return $this->doQuickOperation( array( 'op' => 'delete' ) + $params ); 00586 } 00587 00600 abstract public function concatenate( array $params ); 00601 00619 final public function prepare( array $params ) { 00620 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) { 00621 return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly ); 00622 } 00623 return $this->doPrepare( $params ); 00624 } 00625 00629 abstract protected function doPrepare( array $params ); 00630 00646 final public function secure( array $params ) { 00647 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) { 00648 return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly ); 00649 } 00650 return $this->doSecure( $params ); 00651 } 00652 00656 abstract protected function doSecure( array $params ); 00657 00674 final public function publish( array $params ) { 00675 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) { 00676 return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly ); 00677 } 00678 return $this->doPublish( $params ); 00679 } 00680 00684 abstract protected function doPublish( array $params ); 00685 00698 final public function clean( array $params ) { 00699 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) { 00700 return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly ); 00701 } 00702 return $this->doClean( $params ); 00703 } 00704 00708 abstract protected function doClean( array $params ); 00709 00720 abstract public function fileExists( array $params ); 00721 00731 abstract public function getFileTimestamp( array $params ); 00732 00743 abstract public function getFileContents( array $params ); 00744 00754 abstract public function getFileSize( array $params ); 00755 00770 abstract public function getFileStat( array $params ); 00771 00781 abstract public function getFileSha1Base36( array $params ); 00782 00793 abstract public function getFileProps( array $params ); 00794 00809 abstract public function streamFile( array $params ); 00810 00830 abstract public function getLocalReference( array $params ); 00831 00843 abstract public function getLocalCopy( array $params ); 00844 00858 abstract public function directoryExists( array $params ); 00859 00877 abstract public function getDirectoryList( array $params ); 00878 00891 final public function getTopDirectoryList( array $params ) { 00892 return $this->getDirectoryList( array( 'topOnly' => true ) + $params ); 00893 } 00894 00911 abstract public function getFileList( array $params ); 00912 00925 final public function getTopFileList( array $params ) { 00926 return $this->getFileList( array( 'topOnly' => true ) + $params ); 00927 } 00928 00936 public function preloadCache( array $paths ) {} 00937 00945 public function clearCache( array $paths = null ) {} 00946 00957 final public function lockFiles( array $paths, $type ) { 00958 return $this->lockManager->lock( $paths, $type ); 00959 } 00960 00968 final public function unlockFiles( array $paths, $type ) { 00969 return $this->lockManager->unlock( $paths, $type ); 00970 } 00971 00985 final public function getScopedFileLocks( array $paths, $type, Status $status ) { 00986 return ScopedLock::factory( $this->lockManager, $paths, $type, $status ); 00987 } 00988 01005 abstract public function getScopedLocksForOps( array $ops, Status $status ); 01006 01014 final public function getRootStoragePath() { 01015 return "mwstore://{$this->name}"; 01016 } 01017 01023 final public function getJournal() { 01024 return $this->fileJournal; 01025 } 01026 01034 final public static function isStoragePath( $path ) { 01035 return ( strpos( $path, 'mwstore://' ) === 0 ); 01036 } 01037 01046 final public static function splitStoragePath( $storagePath ) { 01047 if ( self::isStoragePath( $storagePath ) ) { 01048 // Remove the "mwstore://" prefix and split the path 01049 $parts = explode( '/', substr( $storagePath, 10 ), 3 ); 01050 if ( count( $parts ) >= 2 && $parts[0] != '' && $parts[1] != '' ) { 01051 if ( count( $parts ) == 3 ) { 01052 return $parts; // e.g. "backend/container/path" 01053 } else { 01054 return array( $parts[0], $parts[1], '' ); // e.g. "backend/container" 01055 } 01056 } 01057 } 01058 return array( null, null, null ); 01059 } 01060 01068 final public static function normalizeStoragePath( $storagePath ) { 01069 list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath ); 01070 if ( $relPath !== null ) { // must be for this backend 01071 $relPath = self::normalizeContainerPath( $relPath ); 01072 if ( $relPath !== null ) { 01073 return ( $relPath != '' ) 01074 ? "mwstore://{$backend}/{$container}/{$relPath}" 01075 : "mwstore://{$backend}/{$container}"; 01076 } 01077 } 01078 return null; 01079 } 01080 01089 final public static function parentStoragePath( $storagePath ) { 01090 $storagePath = dirname( $storagePath ); 01091 list( $b, $cont, $rel ) = self::splitStoragePath( $storagePath ); 01092 return ( $rel === null ) ? null : $storagePath; 01093 } 01094 01101 final public static function extensionFromPath( $path ) { 01102 $i = strrpos( $path, '.' ); 01103 return strtolower( $i ? substr( $path, $i + 1 ) : '' ); 01104 } 01105 01113 final public static function isPathTraversalFree( $path ) { 01114 return ( self::normalizeContainerPath( $path ) !== null ); 01115 } 01116 01125 final public static function makeContentDisposition( $type, $filename = '' ) { 01126 $parts = array(); 01127 01128 $type = strtolower( $type ); 01129 if ( !in_array( $type, array( 'inline', 'attachment' ) ) ) { 01130 throw new MWException( "Invalid Content-Disposition type '$type'." ); 01131 } 01132 $parts[] = $type; 01133 01134 if ( strlen( $filename ) ) { 01135 $parts[] = "filename*=UTF-8''" . rawurlencode( basename( $filename ) ); 01136 } 01137 01138 return implode( ';', $parts ); 01139 } 01140 01151 final protected static function normalizeContainerPath( $path ) { 01152 // Normalize directory separators 01153 $path = strtr( $path, '\\', '/' ); 01154 // Collapse any consecutive directory separators 01155 $path = preg_replace( '![/]{2,}!', '/', $path ); 01156 // Remove any leading directory separator 01157 $path = ltrim( $path, '/' ); 01158 // Use the same traversal protection as Title::secureAndSplit() 01159 if ( strpos( $path, '.' ) !== false ) { 01160 if ( 01161 $path === '.' || 01162 $path === '..' || 01163 strpos( $path, './' ) === 0 || 01164 strpos( $path, '../' ) === 0 || 01165 strpos( $path, '/./' ) !== false || 01166 strpos( $path, '/../' ) !== false 01167 ) { 01168 return null; 01169 } 01170 } 01171 return $path; 01172 } 01173 }