r103530 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r103529‎ | r103530 | r103531 >
Date:00:09, 18 November 2011
Author:aaron
Status:deferred
Tags:
Comment:
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 :)
Modified paths:
  • /branches/FileBackend/phase3/includes/filerepo/AbstractFileStore.php (deleted) (history)
  • /branches/FileBackend/phase3/includes/filerepo/FileBackend.php (deleted) (history)
  • /branches/FileBackend/phase3/includes/filerepo/backend (added) (history)
  • /branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php (added) (history)
  • /branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php (added) (history)
  • /branches/FileBackend/phase3/includes/filerepo/backend/FileBackendMultiWrite.php (added) (history)
  • /branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php (added) (history)

Diff [purge]

Index: branches/FileBackend/phase3/includes/filerepo/AbstractFileStore.php
@@ -1,131 +0,0 @@
2 -<?php
3 -
4 -/**
5 - * This class defines the methods as abstract that should be implemented in
6 - * child classes if the target store supports the operation.
7 - */
8 -abstract class AbstractFileStore {
9 -
10 - /**
11 - * Array of operations that are supported by this data store
12 - *
13 - * This could potentially be populated via reflection at some point
14 - *
15 - * <code>
16 - * $supportedOperations = array('store', 'copy', 'delete')
17 - * </code>
18 - *
19 - * @var Array
20 - */
21 - protected $supportedOperations;
22 -
23 - /**
24 - * Setup the FileStore. The operations in this constructor are required for
25 - * the object to operate properly. If any subclasses use a constructor they
26 - * must call this parent constructor as well
27 - */
28 - public function __construct() {
29 - // Make sure the getFileProps is in the supportedOperations array
30 - // The support for this only exists in this parent class
31 - $this->supportedOperations[] = 'getFileProps';
32 - }
33 -
34 - /**
35 - * Returns a list of supported operations
36 - *
37 - * @return Array
38 - */
39 - public function getSupportedOperations() {
40 - return $this->supportedOperations;
41 - }
42 -
43 - /**
44 - * Use this function in subclasses to define the specific FileStore
45 - * store functionality
46 - *
47 - * @param Array $params
48 - */
49 - abstract function store( $params ) {
50 - return -1;
51 - }
52 -
53 - /**
54 - * Use this function in subclasses to define the specific FileStore
55 - * relocate functionality
56 - *
57 - * @param Array $params
58 - */
59 - abstract function relocate( $params ) {
60 - return -1;
61 - }
62 -
63 - /**
64 - * Use this function in subclasses to define the specific FileStore
65 - * copy functionality
66 - *
67 - * @param Array $params
68 - */
69 - abstract function copy( $params ) {
70 - return -1;
71 - }
72 -
73 - /**
74 - * Use this function in subclasses to define the specific FileStore
75 - * delete functionality
76 - *
77 - * @param Array $params
78 - */
79 - abstract function delete( $params ) {
80 - return -1;
81 - }
82 -
83 - /**
84 - * Use this function in subclasses to define the specific FileStore
85 - * move functionality
86 - *
87 - * @param Array $params
88 - */
89 - abstract function move( $params ) {
90 - return -1;
91 - }
92 -
93 - /**
94 - * Use this function in subclasses to define the specific FileStore
95 - * concatinate functionality
96 - *
97 - * @param Array $params
98 - */
99 - abstract function concatinate( $params ) {
100 - return -1;
101 - }
102 -
103 - /**
104 - * Use this function in subclasses to define the specific FileStore
105 - * file exists functionality
106 - *
107 - * @param Array $params
108 - */
109 - abstract function fileExists( $params ) {
110 - return -1;
111 - }
112 -
113 - /**
114 - * Use this function in subclasses to define the specific FileStore
115 - * retreive functionality
116 - *
117 - * @param Array $params
118 - */
119 - abstract function getLocalCopy( $params ) {
120 - return -1;
121 - }
122 -
123 - /**
124 - * Get the properties of a file
125 - *
126 - * @param Array $params
127 - * @return Array
128 - */
129 - public function getFileProps( $params ) {
130 - throw new MWException(__METHOD__ . " has not yet been implemented in AbstractFileStore");
131 - }
132 -} // end class
\ No newline at end of file
Index: branches/FileBackend/phase3/includes/filerepo/FileBackend.php
@@ -1,158 +0,0 @@
2 -<?php
3 -
4 -/**
5 - * Handles all operations on real files. The FileBackend should be the single
6 - * point of interface to the filesystem in all core and extension code. To manipulate
7 - * files contained within the current MediaWiki installation the dev/extension
8 - * writer need only to interact with the doOps() method in this class
9 - *
10 - * PLEASE PLEASE leave feedback on improving this file abstraction layer
11 - *
12 - * @todo Implement streamFile
13 - * @todo Implement enumFiles
14 - * @todo Design and implement smart file chunking uploads
15 - */
16 -abstract class FileBackend {
17 -
18 - /**
19 - * This should be a subclass of the AbstractFileStore and specific to a
20 - * single data store
21 - * @var AbstractFileStore
22 - */
23 - private $registeredDataStore = array();
24 -
25 - /**
26 - * Constructor
27 - * Loads the required data store object for interacting with any data store.
28 - * The store is loaded via a config object that is loosely structured as
29 - *
30 - * <code>
31 - * $conf = array( 'store' => 'LocalFileSystemDataStore' )
32 - * </code>
33 - *
34 - * The only required array item is 'store' which specifies which data store
35 - * object to load. Additional options may be in that array that are needed
36 - * for various configuration details of the specific store
37 - *
38 - * @param Array $conf
39 - */
40 - public function __construct($conf) {
41 - if( !is_array( $conf ) ) {
42 - // Ensure there is configuration data present
43 - throw new MWException( __METHOD__ . ': no data store configuration available' );
44 - } elseif( !isset( $conf['store'] ) ) {
45 - // If a store is not specified use the LocalFileSystemDataStore
46 - $conf['store'] = 'LocalFileSystemDataStore';
47 - }
48 - if( !file_exists( __DIR__ . "/dataStores/{$conf['store']}.php" ) ) {
49 - // Ensure store object exists
50 - throw new MWException( __METHOD__ . ": {$conf['store']} data store object does not exist" );
51 - }
52 -
53 - // Load the dataStore object
54 - require_once "dataStore/{$conf['store']}.php";
55 - $this->registerDataStore( new $conf['store']($conf) );
56 - }
57 -
58 -
59 -
60 - /**
61 - * This is the main entry point into the file system back end. Callers will
62 - * supply a list of operations to be performed (almost like a script) as an
63 - * array. This class will then handle handing the operations off to the
64 - * correct file store module
65 - *
66 - * For more information about a specific operation see the abstract method
67 - * definitions in AbstractFileStore or the extended class
68 - *
69 - * Using $ops
70 - * $ops is an array of arrays. The first array holds a list of operations.
71 - * The inner array contains the parameters, E.G:
72 - * <code>
73 - * $ops = array(
74 - * array('operation' => 'store',
75 - * 'src' => '/tmp/uploads/picture.png',
76 - * 'dest' => 'zone/uploadedFilename.png'
77 - * )
78 - * );
79 - * </code>
80 - * @param Array $ops - Array of arrays containing N operations to execute IN ORDER
81 - * @return Status
82 - */
83 - public function doOps( $ops, $reversed = 0 ) {
84 - if(!is_array($ops)) {
85 - throw new MWException(__METHOD__ . " not provided with an operations array");
86 - }
87 -
88 - $st = ''; // Status message string
89 - $statusObject = ''; // some sort of status object that can hold other status objects
90 -
91 - foreach( $ops AS $i => $op ) {
92 - if( in_array( $op['operation'], $this->registeredDataStore->getSupportedOperations() ) ) {
93 - // Ensure that the operation is supported before attempting to do it
94 - $st = $this->registeredDataStore->$op['operation']();
95 - } else {
96 - $st = "{$op['operation']} is not supported by the current data store";
97 - }
98 -
99 -
100 - $statusObject->append( $st );
101 - if ( $st && $reversed) {
102 - // oh noes! Something went wrong AGAIN.
103 - // pray to gods.
104 - return 'STATUS OBJECT';
105 - } elseif ( $st ) {
106 - // oh crap, something went wrong. Try to unwind.
107 - return $this->doOps( $this->unwind( $ops, $i ), 1);
108 - }
109 - }
110 -
111 - return 'STATUS OBJECT';
112 - }
113 -
114 - /**
115 - * Unwinds an array of operations, attempting to reverse their action.
116 - * @param Array $ops - Array of arrays containing N operations to execute IN ORDER
117 - * @param Integer $i - index of first operation that failed.
118 - * @return Array
119 - */
120 - protected function unwind( $ops, $i ) {
121 - $outops = array();
122 -
123 - foreach( $ops AS $k => $op ) {
124 - $newop = null;
125 - switch ( $op['operation'] ) {
126 - case 'move':
127 - $newop = $op;
128 - $newop['source'] = $op['source'];
129 - $newop['dest'] = $op['dest'];
130 - break;
131 - case 'delete':
132 - // sigh.
133 - break;
134 - case 'copy':
135 - $newop = $op;
136 - $newop['operation'] = 'delete';
137 - $newop['source'] = $op['dest'];
138 - break;
139 - }
140 - if ($newop) {
141 - array_unshift($outops, $newop);
142 - }
143 - }
144 - return $outops;
145 - }
146 -
147 - /**
148 - * Validates that the data store is infact of the proper type and then adds
149 - * it to the registered data store property
150 - *
151 - * @param AbstractDataStore $dataStoreObject
152 - */
153 - public function registerDataStore($dataStoreObject) {
154 - if(is_subclassof($dataStoreObject, 'AbstractDataStore')) {
155 - $this->registeredDataStore[] = $dataSourceObject;
156 - }
157 - }
158 -} // end class
159 -
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileBackendMultiWrite.php
@@ -0,0 +1,162 @@
 2+<?php
 3+/**
 4+ * Prioritized list of file repositories
 5+ *
 6+ * @file
 7+ * @ingroup FileRepo
 8+ */
 9+
 10+/**
 11+ * This class defines the methods as abstract that should be
 12+ * implemented in child classes that represent a mutli-write backend.
 13+ *
 14+ * The order that the backends are defined sets the priority of which
 15+ * backend is read from or written to first.
 16+ *
 17+ * All write operations are performed on all backends.
 18+ * If an operation fails on one backend it will be rolled back from the others.
 19+ *
 20+ * Functions like fileExists() and getFileProps() will return information
 21+ * based on the first backend that has the file (normally both should have it anyway).
 22+ */
 23+class FileBackendMultiWrite implements IFileBackend {
 24+ protected $name;
 25+
 26+ public function __construct( array $config ) {
 27+ $this->name = $config['name'];
 28+ }
 29+
 30+ public function getName() {
 31+ return $this->name;
 32+ }
 33+
 34+ /**
 35+ * This should be an array of FileBackend classes
 36+ * @var Array
 37+ */
 38+ protected $fileBackends = array();
 39+
 40+ final public function doOperations( array $ops ) {
 41+ $status = Status::newGood();
 42+ // Build up a list of FileOps. The list will have all the ops
 43+ // for one backend, then all the ops for the next, and so on.
 44+ $performOps = array();
 45+ foreach ( $this->fileBackends as $backend ) {
 46+ $performOps = array_merge( $performOps, $backend->getOperations( $ops ) );
 47+ }
 48+ // Attempt each operation; abort on failure...
 49+ foreach ( $performOps as $index => $transaction ) {
 50+ $tStatus = $transaction->attempt();
 51+ if ( !$tStatus->isOk() ) {
 52+ // merge $tStatus with $status
 53+ // Revert everything done so far and error out
 54+ $tStatus = $this->revertOperations( $performOps, $index );
 55+ // merge $tStatus with $status
 56+ return $status;
 57+ }
 58+ }
 59+ // Finish each operation...
 60+ foreach ( $performOps as $index => $transaction ) {
 61+ $tStatus = $transaction->finish();
 62+ // merge $tStatus with $status
 63+ }
 64+ return $status;
 65+ }
 66+
 67+ /**
 68+ * Revert a series of operations in reverse order.
 69+ * If $index is passed, then we revert all items in
 70+ * $ops from 0 to $index (inclusive).
 71+ *
 72+ * @param $ops Array List of FileOp objects
 73+ * @param $index integer
 74+ * @return Status
 75+ */
 76+ private function revertOperations( array $ops, $index = false ) {
 77+ $status = Status::newGood();
 78+ if ( $index === false ) {
 79+ $pos = count( $ops ) - 1; // last element (or -1)
 80+ } else {
 81+ $pos = $index;
 82+ }
 83+ while ( $pos >= 0 ) {
 84+ $tStatus = $ops[$pos]->revert();
 85+ // merge $tStatus with $status
 86+ $pos--;
 87+ }
 88+ return $status;
 89+ }
 90+
 91+ public function store( array $params ) {
 92+ $op = array( 'operation' => 'store' ) + $params;
 93+ return $this->doOperation( array( $op ) );
 94+ }
 95+
 96+ public function copy( array $params ) {
 97+ $op = array( 'operation' => 'copy' ) + $params;
 98+ return $this->doOperation( array( $op ) );
 99+ }
 100+
 101+ public function delete( array $params ){
 102+ $op = array( 'operation' => 'delete' ) + $params;
 103+ return $this->doOperation( array( $op ) );
 104+ }
 105+
 106+ public function concatenate( array $params ){
 107+ $op = array( 'operation' => 'concatenate' ) + $params;
 108+ return $this->doOperation( array( $op ) );
 109+ }
 110+
 111+ public function fileExists( array $params ) {
 112+ foreach ( $this->backends as $backend ) {
 113+ if ( $backend->fileExists( $params ) ) {
 114+ return true;
 115+ }
 116+ }
 117+ return false;
 118+ }
 119+
 120+ public function getLocalCopy( array $params ) {
 121+ foreach ( $this->backends as $backend ) {
 122+ $tmpPath = $backend->getLocalCopy( $params );
 123+ if ( $tmpPath !== null ) {
 124+ return $tmpPath;
 125+ }
 126+ }
 127+ return null;
 128+ }
 129+
 130+ public function getFileProps( array $params ) {
 131+ foreach ( $this->backends as $backend ) {
 132+ $props = $backend->getFileProps( $params );
 133+ if ( $props !== null ) {
 134+ return $props;
 135+ }
 136+ }
 137+ return null;
 138+ }
 139+
 140+ public function lockFile( array $path ) {
 141+ $status = Status::newGood();
 142+ foreach ( $this->backends as $index => $backend ) {
 143+ $lockStatus = $backend->lockFile( $path );
 144+ // merge $lockStatus with $status
 145+ if ( !$lockStatus->isOk() ) {
 146+ for ( $i=0; $i < $index; $i++ ) {
 147+ $lockStatus = $this->unlockFile( $path );
 148+ // merge $lockStatus with $status
 149+ }
 150+ }
 151+ }
 152+ return $status;
 153+ }
 154+
 155+ public function unlockFile( array $path ) {
 156+ $status = Status::newGood();
 157+ foreach ( $this->backends as $backend ) {
 158+ $lockStatus = $backend->unlockFile( $path );
 159+ // merge $lockStatus with $status
 160+ }
 161+ return $status;
 162+ }
 163+}
Property changes on: branches/FileBackend/phase3/includes/filerepo/backend/FileBackendMultiWrite.php
___________________________________________________________________
Added: svn:eol-style
1164 + native
Index: branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php
@@ -0,0 +1,91 @@
 2+<?php
 3+/**
 4+ * Base class for all file backend classes
 5+ *
 6+ * @file
 7+ * @ingroup FileRepo
 8+ */
 9+
 10+/**
 11+ * This class defines the methods as abstract that should be implemented in
 12+ * child classes if the target store supports the operation.
 13+ */
 14+class FSFileBackend extends FileBackend {
 15+ public function store( array $params ) {
 16+ $status = Status::newGood();
 17+
 18+ wfMakeDirParents( $params['dest'] );
 19+
 20+ if ( file_exists( $params['dest'] ) ) {
 21+ if ( $params['overwriteDest'] ) {
 22+ unlink( $params['dest'] );
 23+ } elseif( $params['overwriteSame'] ) {
 24+ if ( // check size first since it's faster
 25+ filesize( $params['dest'] ) != filesize( $params['source'] ) ||
 26+ sha1_file( $params['dest'] ) != sha1_file( $params['source'] )
 27+ ) {
 28+ // error out
 29+ }
 30+ }
 31+ }
 32+ wfSuppressWarnings();
 33+ copy( $params['source'], $params['dest'] );
 34+ wfRestoreWarnings();
 35+
 36+ return $status;
 37+ }
 38+
 39+ public function copy( array $params ) {
 40+ $status = Status::newGood();
 41+
 42+ wfMakeDirParents( $params['dest'] );
 43+
 44+ wfSuppressWarnings();
 45+ copy( $params['source'], $params['dest'] );
 46+ wfRestoreWarnings();
 47+
 48+ return $status;
 49+ }
 50+
 51+ public function delete( array $params ) {
 52+ $status = Status::newGood();
 53+
 54+ wfSuppressWarnings();
 55+ unlink( $params['dest'] );
 56+ wfRestoreWarnings();
 57+
 58+ return $status;
 59+ }
 60+
 61+ public function concatenate( array $params ) {
 62+ $status = Status::newGood();
 63+
 64+ wfMakeDirParents( $params['dest'] );
 65+
 66+ wfSuppressWarnings();
 67+ $destHandle = fopen( $params['dest'], 'a' );
 68+
 69+ wfRestoreWarnings();
 70+
 71+ return $status;
 72+ }
 73+
 74+ public function fileExists( array $params ) {
 75+
 76+ }
 77+
 78+ public function getFileProps( array $params ) {
 79+ return File::getPropsFromPath( $params['source'] );
 80+ }
 81+
 82+ public function getLocalCopy( array $params ) {
 83+
 84+ }
 85+
 86+ public function lockFile( $source ) {
 87+ }
 88+
 89+ public function unlockFile( $source ) {
 90+
 91+ }
 92+}
Property changes on: branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php
___________________________________________________________________
Added: svn:eol-style
193 + native
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php
@@ -0,0 +1,35 @@
 2+<?php
 3+/**
 4+ * FileBackend helper class for handling file locking.
 5+ * Implemenations can rack of what locked in the process cache.
 6+ * This can reduce hits to external resources for lock()/unlock() calls.
 7+ */
 8+interface IFileLockManager {
 9+ /**
 10+ * Lock the file at a storage path for a backend
 11+ *
 12+ * @param $backendKey string
 13+ * @param $name string
 14+ * @return Status
 15+ */
 16+ public function lock( $backendKey, $name );
 17+
 18+ /**
 19+ * Unlock the file at a storage path for a backend
 20+ *
 21+ * @param $backendKey string
 22+ * @param $name string
 23+ * @return Status
 24+ */
 25+ public function unlock( $backendKey, $name );
 26+}
 27+
 28+class NullFileLockManager implements IFileLockManager {
 29+ public function lock( $backendKey, $name ) {
 30+ return Status::newGood();
 31+ }
 32+
 33+ public function unlock( $backendKey, $name ) {
 34+ return Status::newGood();
 35+ }
 36+}
Property changes on: branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php
___________________________________________________________________
Added: svn:eol-style
137 + native
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php
@@ -0,0 +1,599 @@
 2+<?php
 3+/**
 4+ * Base class for all file backend classes
 5+ *
 6+ * @file
 7+ * @ingroup FileRepo
 8+ */
 9+
 10+/**
 11+ * This class defines the methods as abstract that
 12+ * must be implemented in all file backend classes.
 13+ *
 14+ * All "storage paths" and "storage directories" may be real file system
 15+ * paths or just virtual paths such as object names in Swift.
 16+ */
 17+interface IFileBackend {
 18+ /**
 19+ * We may have multiple different backends of the same type.
 20+ * For example, we can have two Swift backends using different proxies.
 21+ *
 22+ * @return string
 23+ */
 24+ public function getName();
 25+
 26+ /**
 27+ * This is the main entry point into the file system back end. Callers will
 28+ * supply a list of operations to be performed (almost like a script) as an
 29+ * array. This class will then handle handing the operations off to the
 30+ * correct file store module.
 31+ *
 32+ * Using $ops
 33+ * $ops is an array of arrays. The first array holds a list of operations.
 34+ * The inner array contains the parameters, E.G:
 35+ * <code>
 36+ * $ops = array(
 37+ * array(
 38+ * 'operation' => 'store',
 39+ * 'src' => '/tmp/uploads/picture.png',
 40+ * 'dest' => 'zone/uploadedFilename.png'
 41+ * )
 42+ * );
 43+ * </code>
 44+ *
 45+ * @param Array $ops Array of arrays containing N operations to execute IN ORDER
 46+ * @return Status
 47+ */
 48+ public function doOperations( array $ops );
 49+
 50+ /**
 51+ * Return a list of FileOp objects from a list of operations.
 52+ * An exception is thrown if an unsupported operation is requested.
 53+ *
 54+ * @param Array $ops Same format as doOperations()
 55+ * @return Array
 56+ * @throws MWException
 57+ */
 58+ public function getOperations( array $ops );
 59+
 60+ /**
 61+ * Store a file into the backend from a file on disk.
 62+ * Do not call these from places other than FileOp.
 63+ * $params include:
 64+ * source : source path on disk
 65+ * dest : destination storage path
 66+ * overwriteDest : do nothing and pass if an identical file exists at destination
 67+ * overwriteSame : override any existing file at destination
 68+ *
 69+ * @param Array $params
 70+ * @return Status
 71+ */
 72+ public function store( array $params );
 73+
 74+ /**
 75+ * Copy a file from one storage path to another in the backend.
 76+ * Do not call these from places other than FileOp.
 77+ * $params include:
 78+ * source : source storage path
 79+ * dest : destination storage path
 80+ * overwriteDest : do nothing and pass if an identical file exists at destination
 81+ * overwriteSame : override any existing file at destination
 82+ *
 83+ * @param Array $params
 84+ * @return Status
 85+ */
 86+ public function copy( array $params );
 87+
 88+ /**
 89+ * Delete a file at the storage path.
 90+ * Do not call these from places other than FileOp.
 91+ * $params include:
 92+ * source : source storage path
 93+ * ignoreMissingSource : don't return an error if the file does not exist
 94+ *
 95+ * @param Array $params
 96+ * @return Status
 97+ */
 98+ public function delete( array $params );
 99+
 100+ /**
 101+ * Combines files from severals storage paths into a new file in the backend.
 102+ * Do not call these from places other than FileOp.
 103+ * $params include:
 104+ * source : source storage path
 105+ * dest : destination storage path
 106+ * overwriteDest : do nothing and pass if an identical file exists at destination
 107+ * overwriteSame : override any existing file at destination
 108+ *
 109+ * @param Array $params
 110+ * @return Status
 111+ */
 112+ public function concatenate( array $params );
 113+
 114+ /**
 115+ * Check if a file exits at a storage path in the backend.
 116+ * Do not call these from places other than FileOp.
 117+ * $params include:
 118+ * source : source storage path
 119+ *
 120+ * @param Array $params
 121+ * @return bool
 122+ */
 123+ public function fileExists( array $params );
 124+
 125+ /**
 126+ * Get the properties of the file that exists at a storage path in the backend
 127+ * $params include:
 128+ * source : source storage path
 129+ *
 130+ * @param Array $params
 131+ * @return Array|null Gives null if the file does not exist
 132+ */
 133+ public function getFileProps( array $params );
 134+
 135+ /**
 136+ * Get a local copy on dist of the file at a storage path in the backend
 137+ * $params include:
 138+ * source : source storage path
 139+ *
 140+ * @param Array $params
 141+ * @return string|null Path to temporary file or null on failure
 142+ */
 143+ public function getLocalCopy( array $params );
 144+
 145+ /**
 146+ * Lock the file at a storage path in the backend.
 147+ * Do not call these from places other than FileOp.
 148+ *
 149+ * @param $source string Source storage path
 150+ * @return Status
 151+ */
 152+ public function lockFile( $source );
 153+
 154+ /**
 155+ * Unlock the file at a storage path in the backend.
 156+ * Do not call these from places other than FileOp.
 157+ *
 158+ * @param $source string Source storage path
 159+ * @return Status
 160+ */
 161+ public function unlockFile( $source );
 162+}
 163+
 164+/**
 165+ * This class defines the methods as abstract that should be
 166+ * implemented in child classes that represent a single-write backend.
 167+ */
 168+abstract class FileBackend implements IFileBackend {
 169+ protected $name;
 170+ /** @var FileLockManager */
 171+ protected $lockManager;
 172+
 173+ public function getName() {
 174+ return $this->name;
 175+ }
 176+
 177+ /**
 178+ * Build a new object from configuration.
 179+ * This should only be called from within FileRepo classes.
 180+ *
 181+ * @param $config Array
 182+ */
 183+ public function __construct( array $config ) {
 184+ $this->name = $config['name'];
 185+ $this->lockManager = $config['lockManger'];
 186+ }
 187+
 188+ /**
 189+ * Get the list of supported operations and their corresponding FileOp classes.
 190+ * Subclasses should implement these using FileOp subclasses
 191+ *
 192+ * @return Array
 193+ */
 194+ protected function supportedOperations() {
 195+ return array(
 196+ 'store' => 'FileStoreOp',
 197+ 'copy' => 'FileCopyOp',
 198+ 'delete' => 'FileDeleteOp',
 199+ 'move' => 'FileMoveOp',
 200+ 'concatenate' => 'FileConcatenateOp'
 201+ );
 202+ }
 203+
 204+ final public function getOperations( array $ops ) {
 205+ $supportedOps = $this->supportedOperations();
 206+
 207+ $performOps = array(); // array of FileOp objects
 208+ //// Build up ordered array of FileOps...
 209+ foreach ( $ops as $operation ) {
 210+ $opName = $operation['operation'];
 211+ if ( isset( $supportedOps[$opName] ) ) {
 212+ $class = $supportedOps[$opName];
 213+ // Get params for this operation
 214+ $params = $operation;
 215+ unset( $params['operation'] ); // don't need this
 216+ // Add operation on to the list of things to do
 217+ $performOps[] = new $class( $params );
 218+ } else {
 219+ throw new MWException( "Operation `$opName` is not supported." );
 220+ }
 221+ }
 222+ return $performOps;
 223+ }
 224+
 225+ final public function doOperations( array $ops ) {
 226+ $status = Status::newGood();
 227+ // Build up a list of FileOps...
 228+ $performOps = $this->getOperations( $ops );
 229+ // Attempt each operation; abort on failure...
 230+ foreach ( $performOps as $index => $transaction ) {
 231+ $tStatus = $transaction->attempt();
 232+ if ( !$tStatus->isOk() ) {
 233+ // merge $tStatus with $status
 234+ // Revert everything done so far and error out
 235+ $tStatus = $this->revertOperations( $performOps, $index );
 236+ // merge $tStatus with $status
 237+ return $status;
 238+ }
 239+ }
 240+ // Finish each operation...
 241+ foreach ( $performOps as $index => $transaction ) {
 242+ $tStatus = $transaction->finish();
 243+ // merge $tStatus with $status
 244+ }
 245+ return $status;
 246+ }
 247+
 248+ /**
 249+ * Revert a series of operations in reverse order.
 250+ * If $index is passed, then we revert all items in
 251+ * $ops from 0 to $index (inclusive).
 252+ *
 253+ * @param $ops Array List of FileOp objects
 254+ * @param $index integer
 255+ * @return Status
 256+ */
 257+ private function revertOperations( array $ops, $index = false ) {
 258+ $status = Status::newGood();
 259+ if ( $index === false ) {
 260+ $pos = count( $ops ) - 1; // last element (or -1)
 261+ } else {
 262+ $pos = $index;
 263+ }
 264+ while ( $pos >= 0 ) {
 265+ $tStatus = $ops[$pos]->revert();
 266+ // merge $tStatus with $status
 267+ $pos--;
 268+ }
 269+ return $status;
 270+ }
 271+
 272+ final public function lockFile( array $path ) {
 273+ // Locks should be specific to this backend location
 274+ $backendKey = get_class( $this ) . '-' . $this->getName();
 275+ return $this->lockManager->lockFile( $backendKey, $path ); // not supported
 276+ }
 277+
 278+ final public function unlockFile( array $path ) {
 279+ // Locks should be specific to this backend location
 280+ $backendKey = get_class( $this ) . '-' . $this->getName();
 281+ return $this->lockManager->unlockFile( $backendKey, $path ); // not supported
 282+ }
 283+}
 284+
 285+/**
 286+ * Helper class for representing operations with transaction support.
 287+ * FileBackend::doOperations() will require these classes for supported operations.
 288+ *
 289+ * Access use of large fields should be avoided as we want to be able to support
 290+ * potentially many FileOp classes in large arrays in memory.
 291+ */
 292+class FileOp {
 293+ protected $state;
 294+ /** $var Array */
 295+ protected $params;
 296+ /** $var FileBackend */
 297+ protected $backend;
 298+
 299+ const STATE_NEW = 1;
 300+ const STATE_ATTEMPTED = 2;
 301+ const STATE_DONE = 3;
 302+
 303+ /**
 304+ * Build a new file operation transaction
 305+ * @params $backend FileBackend
 306+ * @params $params Array
 307+ */
 308+ final public function __construct( FileBackend $backend, array $params ) {
 309+ $this->backend = $backend;
 310+ $this->params = $params;
 311+ $this->state = self::STATE_NEW;
 312+ $this->initialize();
 313+ }
 314+
 315+ /**
 316+ * Set any custom fields on construction
 317+ * @return void
 318+ */
 319+ protected function initialize() {}
 320+
 321+ /**
 322+ * Get a list of storage paths to lock for this operation
 323+ * @return Array
 324+ */
 325+ protected function storagePathsToLock() {
 326+ return array();
 327+ }
 328+
 329+ /**
 330+ * Attempt the operation, maintaining the source file
 331+ * @return Status
 332+ */
 333+ final public function attempt() {
 334+ if ( $this->state !== self::STATE_NEW ) {
 335+ throw new MWException( "Cannot attempt operation called twice." );
 336+ }
 337+ $this->state = self::STATE_ATTEMPTED;
 338+ $status = $this->setLocks();
 339+ if ( $status->isOk() ) {
 340+ $status = $this->doAttempt();
 341+ }
 342+ return $status;
 343+ }
 344+
 345+ /**
 346+ * Revert the operation
 347+ * @return Status
 348+ */
 349+ final public function revert() {
 350+ if ( $this->state !== self::STATE_ATTEMPTED ) {
 351+ throw new MWException( "Cannot rollback an unstarted or finished operation." );
 352+ }
 353+ $this->state = self::STATE_DONE;
 354+ $status = $this->doRevert();
 355+ $this->unsetLocks();
 356+ return $status;
 357+ }
 358+
 359+ /**
 360+ * Finish the operation, altering original files
 361+ * @return Status
 362+ */
 363+ final public function finish() {
 364+ if ( $this->state !== self::STATE_ATTEMPTED ) {
 365+ throw new MWException( "Cannot cleanup an unstarted or finished operation." );
 366+ }
 367+ $this->state = self::STATE_DONE;
 368+ $status = $this->doFinish();
 369+ $this->unsetLocks();
 370+ return $status;
 371+ }
 372+
 373+ /**
 374+ * Try to lock any files before changing them
 375+ * @return Status
 376+ */
 377+ private function setLocks() {
 378+ $status = Status::newGood();
 379+ $lockedFiles = array(); // files actually locked
 380+ foreach ( $this->storagePathsToLock() as $file ) {
 381+ $lockStatus = $this->backend->lockFile( $file );
 382+ if ( $lockStatus->isOk() ) {
 383+ $lockedFiles[] = $file;
 384+ } else {
 385+ foreach ( $lockedFiles as $file ) {
 386+ $this->backend->unlockFile( $file );
 387+ }
 388+ return $lockStatus; // abort
 389+ }
 390+ }
 391+ return $status;
 392+ }
 393+
 394+ /**
 395+ * Try to unlock any files that this locked
 396+ * @return Status
 397+ */
 398+ private function unsetLocks() {
 399+ $status = Status::newGood();
 400+ foreach ( $this->storagePathsToLock() as $file ) {
 401+ $lockStatus = $this->backend->unlockFile( $file );
 402+ if ( !$lockStatus->isOk() ) {
 403+ // append $lockStatus to $status
 404+ }
 405+ }
 406+ return $status;
 407+ }
 408+
 409+ /**
 410+ * @return Status
 411+ */
 412+ abstract protected function doAttempt();
 413+
 414+ /**
 415+ * @return Status
 416+ */
 417+ abstract protected function doRevert();
 418+
 419+ /**
 420+ * @return Status
 421+ */
 422+ abstract protected function doFinish();
 423+}
 424+
 425+/**
 426+ * Store a file into the backend from a file on disk.
 427+ * $params include:
 428+ * source : source path on disk
 429+ * dest : destination storage path
 430+ * overwriteDest : do nothing and pass if an identical file exists at destination
 431+ * overwriteSame : override any existing file at destination
 432+ */
 433+class FileStoreOp extends FileOp {
 434+ protected $tmpDestPath; // temp copy of existing destination file
 435+
 436+ function doAttempt() {
 437+ // Check if a file already exists at the destination
 438+ if ( $this->backend->fileExists( $this->params['dest'] ) ) {
 439+ if ( $this->params['overwriteDest'] ) {
 440+ $this->tmpDestPath = $this->getLocalCopy( $this->params['dest'] );
 441+ if ( $this->tmpDestPath === null ) {
 442+ // error out
 443+ }
 444+ }
 445+ }
 446+ $status = $this->backend->store( $this->params );
 447+ return $status;
 448+ }
 449+
 450+ function doRevert() {
 451+ $status = Status::newGood();
 452+ // Remove the file saved to the destination
 453+ $params = array( 'source' => $this->params['dest'] );
 454+ $subStatus = $this->backend->delete( $params );
 455+ // merge $subStatus with $status
 456+ // Restore any file that was at the destination
 457+ if ( $this->tmpDestPath !== null ) {
 458+ $params = array(
 459+ 'source' => $this->tmpDestPath,
 460+ 'dest' => $this->params['dest']
 461+ );
 462+ $subStatus = $this->backend->store( $params );
 463+ // merge $subStatus with $status
 464+ }
 465+ return $status;
 466+ }
 467+
 468+ function storagePathsToLock() {
 469+ return array( $this->params['dest'] );
 470+ }
 471+}
 472+
 473+/**
 474+ * Copy a file from one storage path to another in the backend.
 475+ * $params include:
 476+ * source : source storage path
 477+ * dest : destination storage path
 478+ * overwriteDest : do nothing and pass if an identical file exists at destination
 479+ * overwriteSame : override any existing file at destination
 480+ */
 481+class FileCopyOp extends FileOp {
 482+ protected $tmpDestPath; // temp copy of existing destination file
 483+
 484+ function doAttempt() {
 485+ // Check if a file already exists at the destination
 486+ if ( $this->backend->fileExists( $this->params['dest'] ) ) {
 487+ if ( $this->params['overwriteDest'] ) {
 488+ $this->tmpDestPath = $this->getLocalCopy( $this->params['dest'] );
 489+ if ( $this->tmpDestPath === null ) {
 490+ // error out
 491+ }
 492+ }
 493+ }
 494+ $status = $this->backend->copy( $this->params );
 495+ return $status;
 496+ }
 497+
 498+ function doRevert() {
 499+ $status = Status::newGood();
 500+ // Remove the file saved to the destination
 501+ $params = array( 'source' => $this->params['dest'] );
 502+ $subStatus = $this->backend->delete( $params );
 503+ // merge $subStatus with $status
 504+ // Restore any file that was at the destination
 505+ if ( $this->tmpDestPath !== null ) {
 506+ $params = array(
 507+ 'source' => $this->tmpDestPath,
 508+ 'dest' => $this->params['dest']
 509+ );
 510+ $subStatus = $this->backend->store( $params );
 511+ // merge $subStatus with $status
 512+ }
 513+ return $status;
 514+ }
 515+
 516+ function storagePathsToLock() {
 517+ return array( $this->params['source'], $this->params['dest'] );
 518+ }
 519+}
 520+
 521+/**
 522+ * Move a file from one storage path to another in the backend.
 523+ * $params include:
 524+ * source : source storage path
 525+ * dest : destination storage path
 526+ * overwriteDest : do nothing and pass if an identical file exists at destination
 527+ * overwriteSame : override any existing file at destination
 528+ */
 529+class FileMoveOp extends FileCopyOp {
 530+ function doFinish() {
 531+ $params = array( 'source' => $this->params['source'] );
 532+ $status = $this->backend->delete( $params );
 533+ return $status;
 534+ }
 535+}
 536+
 537+/**
 538+ * Delete a file at the storage path.
 539+ * $params include:
 540+ * source : source storage path
 541+ * ignoreMissingSource : don't return an error if the file does not exist
 542+ */
 543+class FileDeleteOp extends FileOp {
 544+ function doFinish() {
 545+ $status = $this->fileBackend->delete( $this->params );
 546+ return $status;
 547+ }
 548+
 549+ function storagePathsToLock() {
 550+ return array( $this->params['source'] );
 551+ }
 552+}
 553+
 554+/**
 555+ * Combines files from severals storage paths into a new file in the backend.
 556+ * $params include:
 557+ * source : source storage path
 558+ * dest : destination storage path
 559+ * overwriteDest : do nothing and pass if an identical file exists at destination
 560+ * overwriteSame : override any existing file at destination
 561+ */
 562+class FileConcatenateOp extends FileOp {
 563+ protected $tmpDestPath; // temp copy of existing destination file
 564+
 565+ function doAttempt() {
 566+ // Check if a file already exists at the destination
 567+ if ( $this->backend->fileExists( $this->params['dest'] ) ) {
 568+ if ( $this->params['overwriteDest'] ) {
 569+ $this->tmpDestPath = $this->getLocalCopy( $this->params['dest'] );
 570+ if ( $this->tmpDestPath === null ) {
 571+ // error out
 572+ }
 573+ }
 574+ }
 575+ $status = $this->backend->concatenate( $this->params );
 576+ return $status;
 577+ }
 578+
 579+ function doRevert() {
 580+ $status = Status::newGood();
 581+ // Remove the file saved to the destination
 582+ $params = array( 'source' => $this->params['dest'] );
 583+ $subStatus = $this->backend->delete( $params );
 584+ // merge $subStatus with $status
 585+ // Restore any file that was at the destination
 586+ if ( $this->tmpDestPath !== null ) {
 587+ $params = array(
 588+ 'source' => $this->tmpDestPath,
 589+ 'dest' => $this->params['dest']
 590+ );
 591+ $subStatus = $this->backend->store( $params );
 592+ // merge $subStatus with $status
 593+ }
 594+ return $status;
 595+ }
 596+
 597+ function storagePathsToLock() {
 598+ return array_merge( $this->params['sources'], $this->params['dest'] );
 599+ }
 600+}
Property changes on: branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php
___________________________________________________________________
Added: svn:eol-style
1601 + native

Status & tagging log