https://www.mediawiki.org/wiki/Special:Code/MediaWiki/103530
Revision: 103530 Author: aaron Date: 2011-11-18 00:09:30 +0000 (Fri, 18 Nov 2011) Log Message: ----------- Committed all working changes so far: * Moved FileBackend classes to backend/ * Renamed the backend classes and cleans up the hierarchy * Added FileOp class hierarchy instead of just procedural doOps() code * Added unfinished FileLockManager hierarchy * Dug into FileBackendMultiWrite code * Started FS backend code, lots of rough stuff :) Added Paths: ----------- branches/FileBackend/phase3/includes/filerepo/backend/ branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php branches/FileBackend/phase3/includes/filerepo/backend/FileBackendMultiWrite.php branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php Removed Paths: ------------- branches/FileBackend/phase3/includes/filerepo/AbstractFileStore.php branches/FileBackend/phase3/includes/filerepo/FileBackend.php Deleted: branches/FileBackend/phase3/includes/filerepo/AbstractFileStore.php =================================================================== --- branches/FileBackend/phase3/includes/filerepo/AbstractFileStore.php 2011-11-18 00:01:43 UTC (rev 103529) +++ branches/FileBackend/phase3/includes/filerepo/AbstractFileStore.php 2011-11-18 00:09:30 UTC (rev 103530) @@ -1,131 +0,0 @@ -<?php - -/** - * This class defines the methods as abstract that should be implemented in - * child classes if the target store supports the operation. - */ -abstract class AbstractFileStore { - - /** - * Array of operations that are supported by this data store - * - * This could potentially be populated via reflection at some point - * - * <code> - * $supportedOperations = array('store', 'copy', 'delete') - * </code> - * - * @var Array - */ - protected $supportedOperations; - - /** - * Setup the FileStore. The operations in this constructor are required for - * the object to operate properly. If any subclasses use a constructor they - * must call this parent constructor as well - */ - public function __construct() { - // Make sure the getFileProps is in the supportedOperations array - // The support for this only exists in this parent class - $this->supportedOperations[] = 'getFileProps'; - } - - /** - * Returns a list of supported operations - * - * @return Array - */ - public function getSupportedOperations() { - return $this->supportedOperations; - } - - /** - * Use this function in subclasses to define the specific FileStore - * store functionality - * - * @param Array $params - */ - abstract function store( $params ) { - return -1; - } - - /** - * Use this function in subclasses to define the specific FileStore - * relocate functionality - * - * @param Array $params - */ - abstract function relocate( $params ) { - return -1; - } - - /** - * Use this function in subclasses to define the specific FileStore - * copy functionality - * - * @param Array $params - */ - abstract function copy( $params ) { - return -1; - } - - /** - * Use this function in subclasses to define the specific FileStore - * delete functionality - * - * @param Array $params - */ - abstract function delete( $params ) { - return -1; - } - - /** - * Use this function in subclasses to define the specific FileStore - * move functionality - * - * @param Array $params - */ - abstract function move( $params ) { - return -1; - } - - /** - * Use this function in subclasses to define the specific FileStore - * concatinate functionality - * - * @param Array $params - */ - abstract function concatinate( $params ) { - return -1; - } - - /** - * Use this function in subclasses to define the specific FileStore - * file exists functionality - * - * @param Array $params - */ - abstract function fileExists( $params ) { - return -1; - } - - /** - * Use this function in subclasses to define the specific FileStore - * retreive functionality - * - * @param Array $params - */ - abstract function getLocalCopy( $params ) { - return -1; - } - - /** - * Get the properties of a file - * - * @param Array $params - * @return Array - */ - public function getFileProps( $params ) { - throw new MWException(__METHOD__ . " has not yet been implemented in AbstractFileStore"); - } -} // end class \ No newline at end of file Deleted: branches/FileBackend/phase3/includes/filerepo/FileBackend.php =================================================================== --- branches/FileBackend/phase3/includes/filerepo/FileBackend.php 2011-11-18 00:01:43 UTC (rev 103529) +++ branches/FileBackend/phase3/includes/filerepo/FileBackend.php 2011-11-18 00:09:30 UTC (rev 103530) @@ -1,158 +0,0 @@ -<?php - -/** - * Handles all operations on real files. The FileBackend should be the single - * point of interface to the filesystem in all core and extension code. To manipulate - * files contained within the current MediaWiki installation the dev/extension - * writer need only to interact with the doOps() method in this class - * - * PLEASE PLEASE leave feedback on improving this file abstraction layer - * - * @todo Implement streamFile - * @todo Implement enumFiles - * @todo Design and implement smart file chunking uploads - */ -abstract class FileBackend { - - /** - * This should be a subclass of the AbstractFileStore and specific to a - * single data store - * @var AbstractFileStore - */ - private $registeredDataStore = array(); - - /** - * Constructor - * Loads the required data store object for interacting with any data store. - * The store is loaded via a config object that is loosely structured as - * - * <code> - * $conf = array( 'store' => 'LocalFileSystemDataStore' ) - * </code> - * - * The only required array item is 'store' which specifies which data store - * object to load. Additional options may be in that array that are needed - * for various configuration details of the specific store - * - * @param Array $conf - */ - public function __construct($conf) { - if( !is_array( $conf ) ) { - // Ensure there is configuration data present - throw new MWException( __METHOD__ . ': no data store configuration available' ); - } elseif( !isset( $conf['store'] ) ) { - // If a store is not specified use the LocalFileSystemDataStore - $conf['store'] = 'LocalFileSystemDataStore'; - } - if( !file_exists( __DIR__ . "/dataStores/{$conf['store']}.php" ) ) { - // Ensure store object exists - throw new MWException( __METHOD__ . ": {$conf['store']} data store object does not exist" ); - } - - // Load the dataStore object - require_once "dataStore/{$conf['store']}.php"; - $this->registerDataStore( new $conf['store']($conf) ); - } - - - - /** - * This is the main entry point into the file system back end. Callers will - * supply a list of operations to be performed (almost like a script) as an - * array. This class will then handle handing the operations off to the - * correct file store module - * - * For more information about a specific operation see the abstract method - * definitions in AbstractFileStore or the extended class - * - * Using $ops - * $ops is an array of arrays. The first array holds a list of operations. - * The inner array contains the parameters, E.G: - * <code> - * $ops = array( - * array('operation' => 'store', - * 'src' => '/tmp/uploads/picture.png', - * 'dest' => 'zone/uploadedFilename.png' - * ) - * ); - * </code> - * @param Array $ops - Array of arrays containing N operations to execute IN ORDER - * @return Status - */ - public function doOps( $ops, $reversed = 0 ) { - if(!is_array($ops)) { - throw new MWException(__METHOD__ . " not provided with an operations array"); - } - - $st = ''; // Status message string - $statusObject = ''; // some sort of status object that can hold other status objects - - foreach( $ops AS $i => $op ) { - if( in_array( $op['operation'], $this->registeredDataStore->getSupportedOperations() ) ) { - // Ensure that the operation is supported before attempting to do it - $st = $this->registeredDataStore->$op['operation'](); - } else { - $st = "{$op['operation']} is not supported by the current data store"; - } - - - $statusObject->append( $st ); - if ( $st && $reversed) { - // oh noes! Something went wrong AGAIN. - // pray to gods. - return 'STATUS OBJECT'; - } elseif ( $st ) { - // oh crap, something went wrong. Try to unwind. - return $this->doOps( $this->unwind( $ops, $i ), 1); - } - } - - return 'STATUS OBJECT'; - } - - /** - * Unwinds an array of operations, attempting to reverse their action. - * @param Array $ops - Array of arrays containing N operations to execute IN ORDER - * @param Integer $i - index of first operation that failed. - * @return Array - */ - protected function unwind( $ops, $i ) { - $outops = array(); - - foreach( $ops AS $k => $op ) { - $newop = null; - switch ( $op['operation'] ) { - case 'move': - $newop = $op; - $newop['source'] = $op['source']; - $newop['dest'] = $op['dest']; - break; - case 'delete': - // sigh. - break; - case 'copy': - $newop = $op; - $newop['operation'] = 'delete'; - $newop['source'] = $op['dest']; - break; - } - if ($newop) { - array_unshift($outops, $newop); - } - } - return $outops; - } - - /** - * Validates that the data store is infact of the proper type and then adds - * it to the registered data store property - * - * @param AbstractDataStore $dataStoreObject - */ - public function registerDataStore($dataStoreObject) { - if(is_subclassof($dataStoreObject, 'AbstractDataStore')) { - $this->registeredDataStore[] = $dataSourceObject; - } - } -} // end class - Added: branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php =================================================================== --- branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php (rev 0) +++ branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php 2011-11-18 00:09:30 UTC (rev 103530) @@ -0,0 +1,91 @@ +<?php +/** + * Base class for all file backend classes + * + * @file + * @ingroup FileRepo + */ + +/** + * This class defines the methods as abstract that should be implemented in + * child classes if the target store supports the operation. + */ +class FSFileBackend extends FileBackend { + public function store( array $params ) { + $status = Status::newGood(); + + wfMakeDirParents( $params['dest'] ); + + if ( file_exists( $params['dest'] ) ) { + if ( $params['overwriteDest'] ) { + unlink( $params['dest'] ); + } elseif( $params['overwriteSame'] ) { + if ( // check size first since it's faster + filesize( $params['dest'] ) != filesize( $params['source'] ) || + sha1_file( $params['dest'] ) != sha1_file( $params['source'] ) + ) { + // error out + } + } + } + wfSuppressWarnings(); + copy( $params['source'], $params['dest'] ); + wfRestoreWarnings(); + + return $status; + } + + public function copy( array $params ) { + $status = Status::newGood(); + + wfMakeDirParents( $params['dest'] ); + + wfSuppressWarnings(); + copy( $params['source'], $params['dest'] ); + wfRestoreWarnings(); + + return $status; + } + + public function delete( array $params ) { + $status = Status::newGood(); + + wfSuppressWarnings(); + unlink( $params['dest'] ); + wfRestoreWarnings(); + + return $status; + } + + public function concatenate( array $params ) { + $status = Status::newGood(); + + wfMakeDirParents( $params['dest'] ); + + wfSuppressWarnings(); + $destHandle = fopen( $params['dest'], 'a' ); + + wfRestoreWarnings(); + + return $status; + } + + public function fileExists( array $params ) { + + } + + public function getFileProps( array $params ) { + return File::getPropsFromPath( $params['source'] ); + } + + public function getLocalCopy( array $params ) { + + } + + public function lockFile( $source ) { + } + + public function unlockFile( $source ) { + + } +} Property changes on: branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php ___________________________________________________________________ Added: svn:eol-style + native Added: branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php =================================================================== --- branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php (rev 0) +++ branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php 2011-11-18 00:09:30 UTC (rev 103530) @@ -0,0 +1,599 @@ +<?php +/** + * Base class for all file backend classes + * + * @file + * @ingroup FileRepo + */ + +/** + * This class defines the methods as abstract that + * must be implemented in all file backend classes. + * + * All "storage paths" and "storage directories" may be real file system + * paths or just virtual paths such as object names in Swift. + */ +interface IFileBackend { + /** + * We may have multiple different backends of the same type. + * For example, we can have two Swift backends using different proxies. + * + * @return string + */ + public function getName(); + + /** + * This is the main entry point into the file system back end. Callers will + * supply a list of operations to be performed (almost like a script) as an + * array. This class will then handle handing the operations off to the + * correct file store module. + * + * Using $ops + * $ops is an array of arrays. The first array holds a list of operations. + * The inner array contains the parameters, E.G: + * <code> + * $ops = array( + * array( + * 'operation' => 'store', + * 'src' => '/tmp/uploads/picture.png', + * 'dest' => 'zone/uploadedFilename.png' + * ) + * ); + * </code> + * + * @param Array $ops Array of arrays containing N operations to execute IN ORDER + * @return Status + */ + public function doOperations( array $ops ); + + /** + * Return a list of FileOp objects from a list of operations. + * An exception is thrown if an unsupported operation is requested. + * + * @param Array $ops Same format as doOperations() + * @return Array + * @throws MWException + */ + public function getOperations( array $ops ); + + /** + * Store a file into the backend from a file on disk. + * Do not call these from places other than FileOp. + * $params include: + * source : source path on disk + * dest : destination storage path + * overwriteDest : do nothing and pass if an identical file exists at destination + * overwriteSame : override any existing file at destination + * + * @param Array $params + * @return Status + */ + public function store( array $params ); + + /** + * Copy a file from one storage path to another in the backend. + * Do not call these from places other than FileOp. + * $params include: + * source : source storage path + * dest : destination storage path + * overwriteDest : do nothing and pass if an identical file exists at destination + * overwriteSame : override any existing file at destination + * + * @param Array $params + * @return Status + */ + public function copy( array $params ); + + /** + * Delete a file at the storage path. + * Do not call these from places other than FileOp. + * $params include: + * source : source storage path + * ignoreMissingSource : don't return an error if the file does not exist + * + * @param Array $params + * @return Status + */ + public function delete( array $params ); + + /** + * Combines files from severals storage paths into a new file in the backend. + * Do not call these from places other than FileOp. + * $params include: + * source : source storage path + * dest : destination storage path + * overwriteDest : do nothing and pass if an identical file exists at destination + * overwriteSame : override any existing file at destination + * + * @param Array $params + * @return Status + */ + public function concatenate( array $params ); + + /** + * Check if a file exits at a storage path in the backend. + * Do not call these from places other than FileOp. + * $params include: + * source : source storage path + * + * @param Array $params + * @return bool + */ + public function fileExists( array $params ); + + /** + * Get the properties of the file that exists at a storage path in the backend + * $params include: + * source : source storage path + * + * @param Array $params + * @return Array|null Gives null if the file does not exist + */ + public function getFileProps( array $params ); + + /** + * Get a local copy on dist of the file at a storage path in the backend + * $params include: + * source : source storage path + * + * @param Array $params + * @return string|null Path to temporary file or null on failure + */ + public function getLocalCopy( array $params ); + + /** + * Lock the file at a storage path in the backend. + * Do not call these from places other than FileOp. + * + * @param $source string Source storage path + * @return Status + */ + public function lockFile( $source ); + + /** + * Unlock the file at a storage path in the backend. + * Do not call these from places other than FileOp. + * + * @param $source string Source storage path + * @return Status + */ + public function unlockFile( $source ); +} + +/** + * This class defines the methods as abstract that should be + * implemented in child classes that represent a single-write backend. + */ +abstract class FileBackend implements IFileBackend { + protected $name; + /** @var FileLockManager */ + protected $lockManager; + + public function getName() { + return $this->name; + } + + /** + * Build a new object from configuration. + * This should only be called from within FileRepo classes. + * + * @param $config Array + */ + public function __construct( array $config ) { + $this->name = $config['name']; + $this->lockManager = $config['lockManger']; + } + + /** + * Get the list of supported operations and their corresponding FileOp classes. + * Subclasses should implement these using FileOp subclasses + * + * @return Array + */ + protected function supportedOperations() { + return array( + 'store' => 'FileStoreOp', + 'copy' => 'FileCopyOp', + 'delete' => 'FileDeleteOp', + 'move' => 'FileMoveOp', + 'concatenate' => 'FileConcatenateOp' + ); + } + + final public function getOperations( array $ops ) { + $supportedOps = $this->supportedOperations(); + + $performOps = array(); // array of FileOp objects + //// Build up ordered array of FileOps... + foreach ( $ops as $operation ) { + $opName = $operation['operation']; + if ( isset( $supportedOps[$opName] ) ) { + $class = $supportedOps[$opName]; + // Get params for this operation + $params = $operation; + unset( $params['operation'] ); // don't need this + // Add operation on to the list of things to do + $performOps[] = new $class( $params ); + } else { + throw new MWException( "Operation `$opName` is not supported." ); + } + } + return $performOps; + } + + final public function doOperations( array $ops ) { + $status = Status::newGood(); + // Build up a list of FileOps... + $performOps = $this->getOperations( $ops ); + // Attempt each operation; abort on failure... + foreach ( $performOps as $index => $transaction ) { + $tStatus = $transaction->attempt(); + if ( !$tStatus->isOk() ) { + // merge $tStatus with $status + // Revert everything done so far and error out + $tStatus = $this->revertOperations( $performOps, $index ); + // merge $tStatus with $status + return $status; + } + } + // Finish each operation... + foreach ( $performOps as $index => $transaction ) { + $tStatus = $transaction->finish(); + // merge $tStatus with $status + } + return $status; + } + + /** + * Revert a series of operations in reverse order. + * If $index is passed, then we revert all items in + * $ops from 0 to $index (inclusive). + * + * @param $ops Array List of FileOp objects + * @param $index integer + * @return Status + */ + private function revertOperations( array $ops, $index = false ) { + $status = Status::newGood(); + if ( $index === false ) { + $pos = count( $ops ) - 1; // last element (or -1) + } else { + $pos = $index; + } + while ( $pos >= 0 ) { + $tStatus = $ops[$pos]->revert(); + // merge $tStatus with $status + $pos--; + } + return $status; + } + + final public function lockFile( array $path ) { + // Locks should be specific to this backend location + $backendKey = get_class( $this ) . '-' . $this->getName(); + return $this->lockManager->lockFile( $backendKey, $path ); // not supported + } + + final public function unlockFile( array $path ) { + // Locks should be specific to this backend location + $backendKey = get_class( $this ) . '-' . $this->getName(); + return $this->lockManager->unlockFile( $backendKey, $path ); // not supported + } +} + +/** + * Helper class for representing operations with transaction support. + * FileBackend::doOperations() will require these classes for supported operations. + * + * Access use of large fields should be avoided as we want to be able to support + * potentially many FileOp classes in large arrays in memory. + */ +class FileOp { + protected $state; + /** $var Array */ + protected $params; + /** $var FileBackend */ + protected $backend; + + const STATE_NEW = 1; + const STATE_ATTEMPTED = 2; + const STATE_DONE = 3; + + /** + * Build a new file operation transaction + * @params $backend FileBackend + * @params $params Array + */ + final public function __construct( FileBackend $backend, array $params ) { + $this->backend = $backend; + $this->params = $params; + $this->state = self::STATE_NEW; + $this->initialize(); + } + + /** + * Set any custom fields on construction + * @return void + */ + protected function initialize() {} + + /** + * Get a list of storage paths to lock for this operation + * @return Array + */ + protected function storagePathsToLock() { + return array(); + } + + /** + * Attempt the operation, maintaining the source file + * @return Status + */ + final public function attempt() { + if ( $this->state !== self::STATE_NEW ) { + throw new MWException( "Cannot attempt operation called twice." ); + } + $this->state = self::STATE_ATTEMPTED; + $status = $this->setLocks(); + if ( $status->isOk() ) { + $status = $this->doAttempt(); + } + return $status; + } + + /** + * Revert the operation + * @return Status + */ + final public function revert() { + if ( $this->state !== self::STATE_ATTEMPTED ) { + throw new MWException( "Cannot rollback an unstarted or finished operation." ); + } + $this->state = self::STATE_DONE; + $status = $this->doRevert(); + $this->unsetLocks(); + return $status; + } + + /** + * Finish the operation, altering original files + * @return Status + */ + final public function finish() { + if ( $this->state !== self::STATE_ATTEMPTED ) { + throw new MWException( "Cannot cleanup an unstarted or finished operation." ); + } + $this->state = self::STATE_DONE; + $status = $this->doFinish(); + $this->unsetLocks(); + return $status; + } + + /** + * Try to lock any files before changing them + * @return Status + */ + private function setLocks() { + $status = Status::newGood(); + $lockedFiles = array(); // files actually locked + foreach ( $this->storagePathsToLock() as $file ) { + $lockStatus = $this->backend->lockFile( $file ); + if ( $lockStatus->isOk() ) { + $lockedFiles[] = $file; + } else { + foreach ( $lockedFiles as $file ) { + $this->backend->unlockFile( $file ); + } + return $lockStatus; // abort + } + } + return $status; + } + + /** + * Try to unlock any files that this locked + * @return Status + */ + private function unsetLocks() { + $status = Status::newGood(); + foreach ( $this->storagePathsToLock() as $file ) { + $lockStatus = $this->backend->unlockFile( $file ); + if ( !$lockStatus->isOk() ) { + // append $lockStatus to $status + } + } + return $status; + } + + /** + * @return Status + */ + abstract protected function doAttempt(); + + /** + * @return Status + */ + abstract protected function doRevert(); + + /** + * @return Status + */ + abstract protected function doFinish(); +} + +/** + * Store a file into the backend from a file on disk. + * $params include: + * source : source path on disk + * dest : destination storage path + * overwriteDest : do nothing and pass if an identical file exists at destination + * overwriteSame : override any existing file at destination + */ +class FileStoreOp extends FileOp { + protected $tmpDestPath; // temp copy of existing destination file + + function doAttempt() { + // Check if a file already exists at the destination + if ( $this->backend->fileExists( $this->params['dest'] ) ) { + if ( $this->params['overwriteDest'] ) { + $this->tmpDestPath = $this->getLocalCopy( $this->params['dest'] ); + if ( $this->tmpDestPath === null ) { + // error out + } + } + } + $status = $this->backend->store( $this->params ); + return $status; + } + + function doRevert() { + $status = Status::newGood(); + // Remove the file saved to the destination + $params = array( 'source' => $this->params['dest'] ); + $subStatus = $this->backend->delete( $params ); + // merge $subStatus with $status + // Restore any file that was at the destination + if ( $this->tmpDestPath !== null ) { + $params = array( + 'source' => $this->tmpDestPath, + 'dest' => $this->params['dest'] + ); + $subStatus = $this->backend->store( $params ); + // merge $subStatus with $status + } + return $status; + } + + function storagePathsToLock() { + return array( $this->params['dest'] ); + } +} + +/** + * Copy a file from one storage path to another in the backend. + * $params include: + * source : source storage path + * dest : destination storage path + * overwriteDest : do nothing and pass if an identical file exists at destination + * overwriteSame : override any existing file at destination + */ +class FileCopyOp extends FileOp { + protected $tmpDestPath; // temp copy of existing destination file + + function doAttempt() { + // Check if a file already exists at the destination + if ( $this->backend->fileExists( $this->params['dest'] ) ) { + if ( $this->params['overwriteDest'] ) { + $this->tmpDestPath = $this->getLocalCopy( $this->params['dest'] ); + if ( $this->tmpDestPath === null ) { + // error out + } + } + } + $status = $this->backend->copy( $this->params ); + return $status; + } + + function doRevert() { + $status = Status::newGood(); + // Remove the file saved to the destination + $params = array( 'source' => $this->params['dest'] ); + $subStatus = $this->backend->delete( $params ); + // merge $subStatus with $status + // Restore any file that was at the destination + if ( $this->tmpDestPath !== null ) { + $params = array( + 'source' => $this->tmpDestPath, + 'dest' => $this->params['dest'] + ); + $subStatus = $this->backend->store( $params ); + // merge $subStatus with $status + } + return $status; + } + + function storagePathsToLock() { + return array( $this->params['source'], $this->params['dest'] ); + } +} + +/** + * Move a file from one storage path to another in the backend. + * $params include: + * source : source storage path + * dest : destination storage path + * overwriteDest : do nothing and pass if an identical file exists at destination + * overwriteSame : override any existing file at destination + */ +class FileMoveOp extends FileCopyOp { + function doFinish() { + $params = array( 'source' => $this->params['source'] ); + $status = $this->backend->delete( $params ); + return $status; + } +} + +/** + * Delete a file at the storage path. + * $params include: + * source : source storage path + * ignoreMissingSource : don't return an error if the file does not exist + */ +class FileDeleteOp extends FileOp { + function doFinish() { + $status = $this->fileBackend->delete( $this->params ); + return $status; + } + + function storagePathsToLock() { + return array( $this->params['source'] ); + } +} + +/** + * Combines files from severals storage paths into a new file in the backend. + * $params include: + * source : source storage path + * dest : destination storage path + * overwriteDest : do nothing and pass if an identical file exists at destination + * overwriteSame : override any existing file at destination + */ +class FileConcatenateOp extends FileOp { + protected $tmpDestPath; // temp copy of existing destination file + + function doAttempt() { + // Check if a file already exists at the destination + if ( $this->backend->fileExists( $this->params['dest'] ) ) { + if ( $this->params['overwriteDest'] ) { + $this->tmpDestPath = $this->getLocalCopy( $this->params['dest'] ); + if ( $this->tmpDestPath === null ) { + // error out + } + } + } + $status = $this->backend->concatenate( $this->params ); + return $status; + } + + function doRevert() { + $status = Status::newGood(); + // Remove the file saved to the destination + $params = array( 'source' => $this->params['dest'] ); + $subStatus = $this->backend->delete( $params ); + // merge $subStatus with $status + // Restore any file that was at the destination + if ( $this->tmpDestPath !== null ) { + $params = array( + 'source' => $this->tmpDestPath, + 'dest' => $this->params['dest'] + ); + $subStatus = $this->backend->store( $params ); + // merge $subStatus with $status + } + return $status; + } + + function storagePathsToLock() { + return array_merge( $this->params['sources'], $this->params['dest'] ); + } +} Property changes on: branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php ___________________________________________________________________ Added: svn:eol-style + native Added: branches/FileBackend/phase3/includes/filerepo/backend/FileBackendMultiWrite.php =================================================================== --- branches/FileBackend/phase3/includes/filerepo/backend/FileBackendMultiWrite.php (rev 0) +++ branches/FileBackend/phase3/includes/filerepo/backend/FileBackendMultiWrite.php 2011-11-18 00:09:30 UTC (rev 103530) @@ -0,0 +1,162 @@ +<?php +/** + * Prioritized list of file repositories + * + * @file + * @ingroup FileRepo + */ + +/** + * This class defines the methods as abstract that should be + * implemented in child classes that represent a mutli-write backend. + * + * The order that the backends are defined sets the priority of which + * backend is read from or written to first. + * + * All write operations are performed on all backends. + * If an operation fails on one backend it will be rolled back from the others. + * + * Functions like fileExists() and getFileProps() will return information + * based on the first backend that has the file (normally both should have it anyway). + */ +class FileBackendMultiWrite implements IFileBackend { + protected $name; + + public function __construct( array $config ) { + $this->name = $config['name']; + } + + public function getName() { + return $this->name; + } + + /** + * This should be an array of FileBackend classes + * @var Array + */ + protected $fileBackends = array(); + + final public function doOperations( array $ops ) { + $status = Status::newGood(); + // Build up a list of FileOps. The list will have all the ops + // for one backend, then all the ops for the next, and so on. + $performOps = array(); + foreach ( $this->fileBackends as $backend ) { + $performOps = array_merge( $performOps, $backend->getOperations( $ops ) ); + } + // Attempt each operation; abort on failure... + foreach ( $performOps as $index => $transaction ) { + $tStatus = $transaction->attempt(); + if ( !$tStatus->isOk() ) { + // merge $tStatus with $status + // Revert everything done so far and error out + $tStatus = $this->revertOperations( $performOps, $index ); + // merge $tStatus with $status + return $status; + } + } + // Finish each operation... + foreach ( $performOps as $index => $transaction ) { + $tStatus = $transaction->finish(); + // merge $tStatus with $status + } + return $status; + } + + /** + * Revert a series of operations in reverse order. + * If $index is passed, then we revert all items in + * $ops from 0 to $index (inclusive). + * + * @param $ops Array List of FileOp objects + * @param $index integer + * @return Status + */ + private function revertOperations( array $ops, $index = false ) { + $status = Status::newGood(); + if ( $index === false ) { + $pos = count( $ops ) - 1; // last element (or -1) + } else { + $pos = $index; + } + while ( $pos >= 0 ) { + $tStatus = $ops[$pos]->revert(); + // merge $tStatus with $status + $pos--; + } + return $status; + } + + public function store( array $params ) { + $op = array( 'operation' => 'store' ) + $params; + return $this->doOperation( array( $op ) ); + } + + public function copy( array $params ) { + $op = array( 'operation' => 'copy' ) + $params; + return $this->doOperation( array( $op ) ); + } + + public function delete( array $params ){ + $op = array( 'operation' => 'delete' ) + $params; + return $this->doOperation( array( $op ) ); + } + + public function concatenate( array $params ){ + $op = array( 'operation' => 'concatenate' ) + $params; + return $this->doOperation( array( $op ) ); + } + + public function fileExists( array $params ) { + foreach ( $this->backends as $backend ) { + if ( $backend->fileExists( $params ) ) { + return true; + } + } + return false; + } + + public function getLocalCopy( array $params ) { + foreach ( $this->backends as $backend ) { + $tmpPath = $backend->getLocalCopy( $params ); + if ( $tmpPath !== null ) { + return $tmpPath; + } + } + return null; + } + + public function getFileProps( array $params ) { + foreach ( $this->backends as $backend ) { + $props = $backend->getFileProps( $params ); + if ( $props !== null ) { + return $props; + } + } + return null; + } + + public function lockFile( array $path ) { + $status = Status::newGood(); + foreach ( $this->backends as $index => $backend ) { + $lockStatus = $backend->lockFile( $path ); + // merge $lockStatus with $status + if ( !$lockStatus->isOk() ) { + for ( $i=0; $i < $index; $i++ ) { + $lockStatus = $this->unlockFile( $path ); + // merge $lockStatus with $status + } + } + } + return $status; + } + + public function unlockFile( array $path ) { + $status = Status::newGood(); + foreach ( $this->backends as $backend ) { + $lockStatus = $backend->unlockFile( $path ); + // merge $lockStatus with $status + } + return $status; + } +} Property changes on: branches/FileBackend/phase3/includes/filerepo/backend/FileBackendMultiWrite.php ___________________________________________________________________ Added: svn:eol-style + native Added: branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php =================================================================== --- branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php (rev 0) +++ branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php 2011-11-18 00:09:30 UTC (rev 103530) @@ -0,0 +1,35 @@ +<?php +/** + * FileBackend helper class for handling file locking. + * Implemenations can rack of what locked in the process cache. + * This can reduce hits to external resources for lock()/unlock() calls. + */ +interface IFileLockManager { + /** + * Lock the file at a storage path for a backend + * + * @param $backendKey string + * @param $name string + * @return Status + */ + public function lock( $backendKey, $name ); + + /** + * Unlock the file at a storage path for a backend + * + * @param $backendKey string + * @param $name string + * @return Status + */ + public function unlock( $backendKey, $name ); +} + +class NullFileLockManager implements IFileLockManager { + public function lock( $backendKey, $name ) { + return Status::newGood(); + } + + public function unlock( $backendKey, $name ) { + return Status::newGood(); + } +} Property changes on: branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php ___________________________________________________________________ Added: svn:eol-style + native _______________________________________________ MediaWiki-CVS mailing list MediaWiki-CVS@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs