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

Reply via email to