r103796 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r103795‎ | r103796 | r103797 >
Date:07:09, 21 November 2011
Author:aaron
Status:deferred
Tags:
Comment:
* Renamed IFileBackend to FileBackendBase and made it abstract (which allows for some code reuse)
* Added container support to FileBackend
* Added getFileList() function to FileBackend with FSFileBackend using a new FileIterator class
* Various scattered fixes
Modified paths:
  • /branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php (modified) (history)
  • /branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php (modified) (history)
  • /branches/FileBackend/phase3/includes/filerepo/backend/FileBackendMultiWrite.php (modified) (history)
  • /branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php (modified) (history)

Diff [purge]

Index: branches/FileBackend/phase3/includes/filerepo/backend/FileBackendMultiWrite.php
@@ -5,8 +5,8 @@
66 */
77
88 /**
9 - * This class defines the methods as abstract that should be
10 - * implemented in child classes that represent a mutli-write backend.
 9+ * This class defines a multi-write backend. Multiple backends can
 10+ * be registered to this proxy backend it will act as a single backend.
1111 *
1212 * The order that the backends are defined sets the priority of which
1313 * backend is read from or written to first. Functions like fileExists()
@@ -17,10 +17,10 @@
1818 * If an operation fails on one backend it will be rolled back from the others.
1919 *
2020 * To avoid excess overhead, set the the highest priority backend to use
21 - * a generic FileLockManager and the others to use NullLockManager.
 21+ * a generic FileLockManager and the others to use NullLockManager. This can
 22+ * only be done if all access to the backends is through an instance of this class.
2223 */
23 -class FileBackendMultiWrite implements IFileBackend {
24 - protected $name;
 24+class FileBackendMultiWrite extends FileBackendBase {
2525 /** @var Array Prioritized list of FileBackend classes */
2626 protected $fileBackends = array();
2727
@@ -29,10 +29,6 @@
3030 $this->fileBackends = $config['backends'];
3131 }
3232
33 - function getName() {
34 - return $this->name;
35 - }
36 -
3733 function hasNativeMove() {
3834 return true; // this is irrelevant
3935 }
@@ -144,8 +140,8 @@
145141 $status = $backend->streamFile( $params );
146142 if ( $status->isOK() ) {
147143 return $status;
148 - } else {
149 - // @TODO: check if we failed mid-stream and return out if so
 144+ } elseif ( headers_sent() ) {
 145+ return $status; // died mid-stream...so this is already fubar
150146 }
151147 }
152148 return Status::newFatal( "Could not stream file {$params['source']}." );
Index: branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php
@@ -8,28 +8,45 @@
99 * Class for a file-system based file backend
1010 */
1111 class FSFileBackend extends FileBackend {
 12+ /** @var Array Map of container names to paths */
 13+ protected $containerPaths = array();
1214 protected $fileMode; // file permission mode
1315
1416 function __construct( array $config ) {
1517 $this->name = $config['name'];
 18+ $this->containerPaths = (array)$config['containers'];
1619 $this->lockManager = $config['lockManger'];
1720 $this->fileMode = isset( $config['fileMode'] )
1821 ? $config['fileMode']
1922 : 0644;
2023 }
2124
 25+ protected function resolveContainerPath( $container, $relStoragePath ) {
 26+ // Get absolute path given the container base dir
 27+ if ( isset( $this->containerPaths[$container] ) ) {
 28+ return $this->containerPaths[$container] . "/{$relStoragePath}";
 29+ }
 30+ return null;
 31+ }
 32+
2233 function store( array $params ) {
2334 $status = Status::newGood();
2435
25 - if ( file_exists( $params['dest'] ) ) {
 36+ list( $c, $dest ) = $this->resolveVirtualPath( $params['dest'] );
 37+ if ( $dest === null ) {
 38+ $status->fatal( "Invalid storage path {$params['dest']}." );
 39+ return $status;
 40+ }
 41+
 42+ if ( file_exists( $dest ) ) {
2643 if ( isset( $params['overwriteDest'] ) ) {
27 - $ok = unlink( $params['dest'] );
 44+ $ok = unlink( $dest );
2845 if ( !$ok ) {
2946 $status->fatal( "Could not delete destination file." );
3047 return $status;
3148 }
3249 } elseif ( isset( $params['overwriteSame'] ) ) {
33 - if ( !$this->filesAreSame( $params['source'], $params['dest'] ) ) {
 50+ if ( !$this->filesAreSame( $params['source'], $dest ) ) {
3451 $status->fatal( "Non-identical destination file already exists." );
3552 }
3653 return $status; // do nothing; either OK or bad status
@@ -38,18 +55,18 @@
3956 return $status;
4057 }
4158 } else {
42 - wfMakeDirParents( $params['dest'] );
 59+ wfMakeDirParents( $dest );
4360 }
4461
4562 wfSuppressWarnings();
46 - $ok = copy( $params['source'], $params['dest'] );
 63+ $ok = copy( $params['source'], $dest );
4764 wfRestoreWarnings();
4865 if ( !$ok ) {
4966 $status->fatal( "Could not copy file to destination." );
5067 return $status;
5168 }
5269
53 - $this->chmod( $params['dest'] );
 70+ $this->chmod( $dest );
5471
5572 return $status;
5673 }
@@ -61,15 +78,26 @@
6279 function move( array $params ) {
6380 $status = Status::newGood();
6481
65 - if ( file_exists( $params['dest'] ) ) {
 82+ list( $c, $source ) = $this->resolveVirtualPath( $params['source'] );
 83+ if ( $source === null ) {
 84+ $status->fatal( "Invalid storage path {$params['source']}." );
 85+ return $status;
 86+ }
 87+ list( $c, $dest ) = $this->resolveVirtualPath( $params['dest'] );
 88+ if ( $dest === null ) {
 89+ $status->fatal( "Invalid storage path {$params['dest']}." );
 90+ return $status;
 91+ }
 92+
 93+ if ( file_exists( $dest ) ) {
6694 if ( isset( $params['overwriteDest'] ) ) {
67 - $ok = unlink( $params['dest'] );
 95+ $ok = unlink( $dest );
6896 if ( !$ok ) {
6997 $status->fatal( "Could not delete destination file." );
7098 return $status;
7199 }
72100 } elseif ( isset( $params['overwriteSame'] ) ) {
73 - if ( !$this->filesAreSame( $params['source'], $params['dest'] ) ) {
 101+ if ( !$this->filesAreSame( $source, $dest ) ) {
74102 $status->fatal( "Non-identical destination file already exists." );
75103 }
76104 return $status; // do nothing; either OK or bad status
@@ -78,11 +106,11 @@
79107 return $status;
80108 }
81109 } else {
82 - wfMakeDirParents( $params['dest'] );
 110+ wfMakeDirParents( $dest );
83111 }
84112
85113 wfSuppressWarnings();
86 - $ok = rename( $params['source'], $params['dest'] );
 114+ $ok = rename( $source, $dest );
87115 wfRestoreWarnings();
88116 if ( !$ok ) {
89117 $status->fatal( "Could not move file to destination." );
@@ -95,7 +123,13 @@
96124 function delete( array $params ) {
97125 $status = Status::newGood();
98126
99 - if ( !file_exists( $params['source'] ) ) {
 127+ list( $c, $source ) = $this->resolveVirtualPath( $params['source'] );
 128+ if ( $source === null ) {
 129+ $status->fatal( "Invalid storage path {$params['source']}." );
 130+ return $status;
 131+ }
 132+
 133+ if ( !file_exists( $source ) ) {
100134 if ( !$params['ignoreMissingSource'] ) {
101135 $status->fatal( "Could not delete source because it does not exist." );
102136 }
@@ -103,7 +137,7 @@
104138 }
105139
106140 wfSuppressWarnings();
107 - $ok = unlink( $params['dest'] );
 141+ $ok = unlink( $source );
108142 wfRestoreWarnings();
109143 if ( !$ok ) {
110144 $status->fatal( "Could not delete source file." );
@@ -116,8 +150,14 @@
117151 function concatenate( array $params ) {
118152 $status = Status::newGood();
119153
 154+ list( $c, $dest ) = $this->resolveVirtualPath( $params['dest'] );
 155+ if ( $dest === null ) {
 156+ $status->fatal( "Invalid storage path {$params['dest']}." );
 157+ return $status;
 158+ }
 159+
120160 // Check if the destination file exists and we can't handle that
121 - $destExists = file_exists( $params['dest'] );
 161+ $destExists = file_exists( $dest );
122162 if ( $destExists && !$params['overwriteDest'] && !$params['overwriteSame'] ) {
123163 $status->fatal( "Destination file already exists." ); // short-circuit
124164 return $status;
@@ -140,7 +180,12 @@
141181 $status->fatal( "Could not open temporary file $tmpPath." );
142182 return $status;
143183 }
144 - foreach ( $params['sources'] as $source ) {
 184+ foreach ( $params['sources'] as $virtualSource ) {
 185+ list( $c, $source ) = $this->resolveVirtualPath( $virtualSource );
 186+ if ( $source === null ) {
 187+ $status->fatal( "Invalid storage path {$virtualSource}." );
 188+ return $status;
 189+ }
145190 // Load chunk into memory (it should be a small file)
146191 $chunk = file_get_contents( $source );
147192 if ( $chunk === false ) {
@@ -165,51 +210,74 @@
166211 if ( $destExists ) {
167212 if ( isset( $params['overwriteDest'] ) ) {
168213 wfSuppressWarnings();
169 - $ok = unlink( $params['dest'] );
 214+ $ok = unlink( $dest );
170215 wfRestoreWarnings();
171216 if ( !$ok ) {
172217 $status->fatal( "Could not delete destination file." );
173218 return $status;
174219 }
175220 } elseif ( isset( $params['overwriteSame'] ) ) {
176 - if ( !$this->filesAreSame( $params['source'], $params['dest'] ) ) {
 221+ if ( !$this->filesAreSame( $tmpPath, $dest ) ) {
177222 $status->fatal( "Non-identical destination file already exists." );
178223 }
179224 return $status; // do nothing; either OK or bad status
180225 }
181226 } else {
182227 // Make sure destination directory exists
183 - wfMakeDirParents( $params['dest'] );
 228+ wfMakeDirParents( $dest );
184229 }
185230
186231 // Rename the temporary file to the destination path
187232 wfSuppressWarnings();
188 - $ok = rename( $tmpPath, $params['dest'] );
 233+ $ok = rename( $tmpPath, $dest );
189234 wfRestoreWarnings();
190235 if ( !$ok ) {
191236 $status->fatal( "Could not rename temporary file to destination." );
192237 return $status;
193238 }
194239
195 - $this->chmod( $params['dest'] );
 240+ $this->chmod( $dest );
196241
197242 return $status;
198243 }
199244
200245 function fileExists( array $params ) {
201 - return file_exists( $params['source'] );
 246+ list( $c, $source ) = $this->resolveVirtualPath( $params['source'] );
 247+ if ( $source === null ) {
 248+ return false; // invalid storage path
 249+ }
 250+ return file_exists( $source );
202251 }
203252
204253 function getFileProps( array $params ) {
205 - return File::getPropsFromPath( $params['source'] );
 254+ list( $c, $source ) = $this->resolveVirtualPath( $params['source'] );
 255+ if ( $source === null ) {
 256+ return null; // invalid storage path
 257+ }
 258+ return File::getPropsFromPath( $source );
206259 }
207260
 261+ // Not suitable for massive listings
 262+ function getFileList( array $params ) {
 263+ list( $c, $dir ) = $this->resolveVirtualPath( $params['directory'] );
 264+ if ( $dir === null ) { // valid storage path
 265+ return new FileIterator( '' ); // empty result
 266+ }
 267+ return new FileIterator( $dir );
 268+ }
 269+
208270 function streamFile( array $params ) {
209271 $status = Status::newGood();
210272
211 - $ok = StreamFile::stream( $params['source'], array(), false );
 273+ list( $c, $source ) = $this->resolveVirtualPath( $params['source'] );
 274+ if ( $source === null ) {
 275+ $status->fatal( "Invalid storage path {$params['source']}." );
 276+ return $status;
 277+ }
 278+
 279+ $ok = StreamFile::stream( $source, array(), false );
212280 if ( !$ok ) {
213 - $status->fatal( "Unable to stream file {$params['source']}." );
 281+ $status->fatal( "Unable to stream file {$source}." );
214282 return $status;
215283 }
216284
@@ -217,6 +285,11 @@
218286 }
219287
220288 function getLocalCopy( array $params ) {
 289+ list( $c, $source ) = $this->resolveVirtualPath( $params['source'] );
 290+ if ( $source === null ) {
 291+ return null;
 292+ }
 293+
221294 // Create a new temporary file...
222295 wfSuppressWarnings();
223296 $tmpPath = tempnam( wfTempDir(), 'file_localcopy' );
@@ -227,7 +300,7 @@
228301
229302 // Copy the source file over the temp file
230303 wfSuppressWarnings();
231 - $ok = copy( $params['source'], $tmpPath );
 304+ $ok = copy( $source, $tmpPath );
232305 wfRestoreWarnings();
233306 if ( !$ok ) {
234307 return null;
@@ -240,8 +313,9 @@
241314
242315 /**
243316 * Check if two files are identical
244 - * @param $path1 string
245 - * @param $path2 string
 317+ *
 318+ * @param $path1 string Absolute filesystem path
 319+ * @param $path2 string Absolute filesystem path
246320 * @return bool
247321 */
248322 protected function filesAreSame( $path1, $path2 ) {
@@ -253,7 +327,8 @@
254328
255329 /**
256330 * Chmod a file, suppressing the warnings
257 - * @param $path string The path to change
 331+ *
 332+ * @param $path string Absolute file system path
258333 * @return bool Success
259334 */
260335 protected function chmod( $path ) {
@@ -264,3 +339,130 @@
265340 return $ok;
266341 }
267342 }
 343+
 344+/**
 345+ * Simple DFS based file browsing iterator. The highest number of file handles
 346+ * open at any given time is proportional to the height of the directory tree.
 347+ */
 348+class FileIterator implements Iterator {
 349+ private $directory; // starting directory
 350+
 351+ private $position = 0;
 352+ private $currentFile = false;
 353+ private $dirStack = array(); // array of (dir name, dir handle) tuples
 354+
 355+ private $loaded = false;
 356+
 357+ /**
 358+ * Get an iterator to list the files under $directory and its subdirectories
 359+ * @param $directory string
 360+ */
 361+ public function __construct( $directory ) {
 362+ $this->directory = (string)$directory;
 363+ }
 364+
 365+ private function load() {
 366+ if ( !$this->loaded ) {
 367+ $this->loaded = true;
 368+ // If we get an invalid directory then the result is an empty list
 369+ if ( is_dir( $this->directory ) ) {
 370+ $handle = opendir( $this->directory );
 371+ if ( $handle ) {
 372+ $this->pushDirectory( $this->directory, $handle );
 373+ $this->currentFile = $this->nextFile();
 374+ }
 375+ }
 376+ }
 377+ }
 378+
 379+ function rewind() {
 380+ $this->closeDirectories();
 381+ $this->position = 0;
 382+ $this->currentFile = false;
 383+ $this->loaded = false;
 384+ }
 385+
 386+ function current() {
 387+ $this->load();
 388+ return $this->currentFile;
 389+ }
 390+
 391+ function key() {
 392+ $this->load();
 393+ return $this->position;
 394+ }
 395+
 396+ function next() {
 397+ $this->load();
 398+ ++$this->position;
 399+ $this->currentFile = $this->nextFile();
 400+ }
 401+
 402+ function valid() {
 403+ $this->load();
 404+ return ( $this->currentFile !== false );
 405+ }
 406+
 407+ function nextFile() {
 408+ $set = $this->currentDirectory();
 409+ if ( !$set ) {
 410+ return false; // nothing else to scan
 411+ }
 412+ list( $dir, $handle ) = $set;
 413+ while ( false !== ( $file = readdir( $handle ) ) ) {
 414+ // Exclude '.' and '..' as well .svn or .lock type files.
 415+ // Also excludes symlinks and the like so as to avoid cycles.
 416+ if ( $file[0] !== '.' && !is_link( $file ) ) {
 417+ // If the first thing we find is a file, then return it.
 418+ // If the first thing we find is a directory, then return
 419+ // the first file that it contains (via recursion).
 420+ if ( is_dir( "$dir/$file" ) ) {
 421+ $subHandle = opendir( "$dir/$file" );
 422+ if ( $subHandle ) {
 423+ $this->pushDirectory( "{$dir}/{$file}", $subHandle );
 424+ $nextFile = $this->nextFile();
 425+ if ( $nextFile !== false ) {
 426+ return $nextFile; // found the next one!
 427+ }
 428+ }
 429+ } elseif ( is_file( "$dir/$file" ) ) {
 430+ return "$dir/$file"; // found the next one!
 431+ }
 432+ }
 433+ }
 434+ # If we didn't find anything in this directory,
 435+ # then back out and scan the other higher directories
 436+ $this->popDirectory();
 437+ return $this->nextFile();
 438+ }
 439+
 440+ private function currentDirectory() {
 441+ if ( !count( $this->dirStack ) ) {
 442+ return false;
 443+ }
 444+ return $this->dirStack[count( $this->dirStack ) - 1];
 445+ }
 446+
 447+ private function popDirectory() {
 448+ if ( count( $this->dirStack ) ) {
 449+ list( $dir, $handle ) = array_pop( $this->dirStack );
 450+ closedir( $handle );
 451+ }
 452+ }
 453+
 454+ private function pushDirectory( $dir, $handle ) {
 455+ $this->dirStack[] = array( $dir, $handle );
 456+ }
 457+
 458+ private function closeDirectories() {
 459+ foreach ( $this->dirStack as $set ) {
 460+ list( $dir, $handle ) = $set;
 461+ closedir( $handle );
 462+ }
 463+ $this->dirStack = array();
 464+ }
 465+
 466+ private function __destruct() {
 467+ $this->closeDirectories();
 468+ }
 469+}
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php
@@ -91,9 +91,8 @@
9292
9393 $lockedKeys = array(); // files locked in this attempt
9494 foreach ( $keys as $key ) {
95 - $subStatus = $this->doSingleLock( $key );
96 - $status->merge( $subStatus );
97 - if ( $subStatus->isOk() ) {
 95+ $status->merge( $this->doSingleLock( $key ) );
 96+ if ( $status->isOk() ) {
9897 $lockedKeys[] = $key;
9998 } else {
10099 // Abort and unlock everything
@@ -333,7 +332,7 @@
334333 $this->doLockingSelect( $server, $keys ); // SELECT FOR UPDATE
335334 $locksMade = true; // success for this fallback
336335 } catch ( DBError $e ) {
337 - // oh well; best effort
 336+ // oh well; best effort (@TODO: logging?)
338337 }
339338 }
340339 return $locksMade;
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php
@@ -5,21 +5,29 @@
66 */
77
88 /**
9 - * Base class for all file backend classes.
10 - * This class defines the methods as abstract that
11 - * must be implemented in all file backend classes.
 9+ * Base class for all file backend classes (including multi-write backends).
 10+ * This class defines the methods as abstract that must be implemented subclasses.
1211 *
13 - * All "storage paths" and "storage directories" may be real file system
14 - * paths or just virtual paths such as object names in Swift.
 12+ * All "storage paths" are of the format "mwstore://container/path".
 13+ * The paths use typical file system notation, though any particular backend may
 14+ * not actually be using a local filesystem. Therefore, the paths are only virtual.
 15+ *
 16+ * All functions should avoid throwing exceptions at all costs.
 17+ * As a corollary, external dependencies should be kept to a minimal.
1518 */
16 -interface IFileBackend {
 19+abstract class FileBackendBase {
 20+ protected $name; // unique backend name
 21+
1722 /**
1823 * We may have multiple different backends of the same type.
1924 * For example, we can have two Swift backends using different proxies.
 25+ * All backend instances must have unique names.
2026 *
2127 * @return string
2228 */
23 - public function getName();
 29+ final public function getName() {
 30+ return $this->name;
 31+ }
2432
2533 /**
2634 * This is the main entry point into the file system back end. Callers will
@@ -27,7 +35,7 @@
2836 * array. This class will then handle handing the operations off to the
2937 * correct file store module.
3038 *
31 - * Using $ops
 39+ * Using $ops:
3240 * $ops is an array of arrays. The first array holds a list of operations.
3341 * The inner array contains the parameters, E.G:
3442 * <code>
@@ -35,7 +43,7 @@
3644 * array(
3745 * 'operation' => 'store',
3846 * 'src' => '/tmp/uploads/picture.png',
39 - * 'dest' => 'zone/uploadedFilename.png'
 47+ * 'dest' => 'mwstore://container/uploadedFilename.png'
4048 * )
4149 * );
4250 * </code>
@@ -43,7 +51,7 @@
4452 * @param Array $ops Array of arrays containing N operations to execute IN ORDER
4553 * @return Status
4654 */
47 - public function doOperations( array $ops );
 55+ abstract public function doOperations( array $ops );
4856
4957 /**
5058 * Return a list of FileOp objects from a list of operations.
@@ -53,11 +61,11 @@
5462 * @return Array
5563 * @throws MWException
5664 */
57 - public function getOperations( array $ops );
 65+ abstract public function getOperations( array $ops );
5866
5967 /**
6068 * Store a file into the backend from a file on disk.
61 - * Do not call this function from places other than FileOp.
 69+ * Do not call this function from places outside FileBackend and FileOp.
6270 * $params include:
6371 * source : source path on disk
6472 * dest : destination storage path
@@ -67,11 +75,11 @@
6876 * @param Array $params
6977 * @return Status
7078 */
71 - public function store( array $params );
 79+ abstract public function store( array $params );
7280
7381 /**
7482 * Copy a file from one storage path to another in the backend.
75 - * Do not call this function from places other than FileOp.
 83+ * Do not call this function from places outside FileBackend and FileOp.
7684 * $params include:
7785 * source : source storage path
7886 * dest : destination storage path
@@ -81,12 +89,12 @@
8290 * @param Array $params
8391 * @return Status
8492 */
85 - public function copy( array $params );
 93+ abstract public function copy( array $params );
8694
8795 /**
8896 * Copy a file from one storage path to another in the backend.
8997 * This can be left as a dummy function as long as hasMove() returns false.
90 - * Do not call this function from places other than FileOp.
 98+ * Do not call this function from places outside FileBackend and FileOp.
9199 * $params include:
92100 * source : source storage path
93101 * dest : destination storage path
@@ -96,11 +104,11 @@
97105 * @param Array $params
98106 * @return Status
99107 */
100 - public function move( array $params );
 108+ abstract public function move( array $params );
101109
102110 /**
103111 * Delete a file at the storage path.
104 - * Do not call this function from places other than FileOp.
 112+ * Do not call this function from places outside FileBackend and FileOp.
105113 * $params include:
106114 * source : source storage path
107115 * ignoreMissingSource : don't return an error if the file does not exist
@@ -108,11 +116,11 @@
109117 * @param Array $params
110118 * @return Status
111119 */
112 - public function delete( array $params );
 120+ abstract public function delete( array $params );
113121
114122 /**
115123 * Combines files from severals storage paths into a new file in the backend.
116 - * Do not call this function from places other than FileOp.
 124+ * Do not call this function from places outside FileBackend and FileOp.
117125 * $params include:
118126 * source : source storage path
119127 * dest : destination storage path
@@ -122,26 +130,30 @@
123131 * @param Array $params
124132 * @return Status
125133 */
126 - public function concatenate( array $params );
 134+ abstract public function concatenate( array $params );
127135
128136 /**
129 - * Whether this backend has a move() implementation.
130 - * Do not call this function from places other than FileOp.
 137+ * Whether this backend implements move() and can handle this potential move.
 138+ * For example, moving objects accross containers may not be supported.
 139+ * Do not call this function from places outside FileBackend and FileOp.
 140+ * $params include:
 141+ * source : source storage path
 142+ * dest : destination storage path
131143 *
132144 * @return bool
133145 */
134 - public function hasMove();
 146+ abstract public function canMove( array $params );
135147
136148 /**
137149 * Check if a file exits at a storage path in the backend.
138 - * Do not call this function from places other than FileOp.
 150+ * Do not call this function from places outside FileBackend and FileOp.
139151 * $params include:
140152 * source : source storage path
141153 *
142154 * @param Array $params
143155 * @return bool
144156 */
145 - public function fileExists( array $params );
 157+ abstract public function fileExists( array $params );
146158
147159 /**
148160 * Get the properties of the file that exists at a storage path in the backend
@@ -151,12 +163,12 @@
152164 * @param Array $params
153165 * @return Array|null Gives null if the file does not exist
154166 */
155 - public function getFileProps( array $params );
 167+ abstract public function getFileProps( array $params );
156168
157169 /**
158170 * Stream the file that exists at a storage path in the backend.
159171 * Appropriate HTTP headers (Status, Content-Type, Content-Length)
160 - * must be sent on success, while no headers should be sent on failure.
 172+ * must be sent if streaming began, while none should be sent otherwise.
161173 * Implementations should flush the output buffer before sending data.
162174 * $params include:
163175 * source : source storage path
@@ -164,9 +176,21 @@
165177 * @param Array $params
166178 * @return Status
167179 */
168 - public function streamFile( array $params );
 180+ abstract public function streamFile( array $params );
169181
170182 /**
 183+ * Get an iterator to list out all object files under a storage directory.
 184+ * Results should be storage paths relative to the given directory.
 185+ * If the directory is of the form "mwstore://container", then all items
 186+ * in the container should be listed. If of the form "mwstore://container/dir",
 187+ * then all items under that container directory should be listed.
 188+ * $params include:
 189+ * directory : storage path directory.
 190+ * @return Iterator
 191+ */
 192+ abstract public function getFileList( array $params );
 193+
 194+ /**
171195 * Get a local copy on disk of the file at a storage path in the backend
172196 * $params include:
173197 * source : source storage path
@@ -174,40 +198,34 @@
175199 * @param Array $params
176200 * @return TempLocalFile|null Temporary file or null on failure
177201 */
178 - public function getLocalCopy( array $params );
 202+ abstract public function getLocalCopy( array $params );
179203
180204 /**
181205 * Lock the files at the given storage paths in the backend.
182 - * Do not call this function from places other than FileOp.
 206+ * Do not call this function from places outside FileBackend and FileOp.
183207 *
184208 * @param $sources Array Source storage paths
185209 * @return Status
186210 */
187 - public function lockFiles( array $sources );
 211+ abstract public function lockFiles( array $sources );
188212
189213 /**
190214 * Unlock the files at the given storage paths in the backend.
191 - * Do not call this function from places other than FileOp.
 215+ * Do not call this function from places outside FileBackend and FileOp.
192216 *
193217 * @param $sources Array Source storage paths
194218 * @return Status
195219 */
196 - public function unlockFiles( array $sources );
 220+ abstract public function unlockFiles( array $sources );
197221 }
198222
199223 /**
200 - * This class defines the methods as abstract that should be
201 - * implemented in child classes that represent a single-write backend.
 224+ * Base class for all single-write backends
202225 */
203 -abstract class FileBackend implements IFileBackend {
204 - protected $name;
 226+abstract class FileBackend extends FileBackendBase {
205227 /** @var FileLockManager */
206228 protected $lockManager;
207229
208 - final public function getName() {
209 - return $this->name;
210 - }
211 -
212230 /**
213231 * Build a new object from configuration.
214232 * This should only be called from within FileRepo classes.
@@ -222,7 +240,7 @@
223241 $this->lockManager = $config['lockManger'];
224242 }
225243
226 - function hasMove() {
 244+ function canMove( array $params ) {
227245 return false; // not implemented
228246 }
229247
@@ -232,7 +250,6 @@
233251
234252 /**
235253 * Get the list of supported operations and their corresponding FileOp classes.
236 - * Subclasses should implement these using FileOp subclasses
237254 *
238255 * @return Array
239256 */
@@ -327,6 +344,40 @@
328345 $backendKey = get_class( $this ) . '-' . $this->getName();
329346 return $this->lockManager->unlock( $backendKey, $paths ); // not supported
330347 }
 348+
 349+ /**
 350+ * Split a storage path (e.g. "mwstore://container/path/to/object")
 351+ * into a container name and a full object name within that container.
 352+ *
 353+ * @param $storagePath string
 354+ * @return Array (container, object name) or (null, null) if path is invalid
 355+ */
 356+ final protected function resolveVirtualPath( $storagePath ) {
 357+ if ( strpos( $storagePath, 'mwstore://' ) === 0 ) {
 358+ $m = explode( '/', substr( $storagePath, 10 ), 2 );
 359+ if ( count( $m ) == 2 ) {
 360+ list( $container, $relPath ) = $m;
 361+ $relPath = $this->resolveContainerPath( $container, $relPath );
 362+ if ( $relPath !== null ) {
 363+ return array( $container, $relPath ); // (container, path)
 364+ }
 365+ }
 366+ }
 367+ return array( null, null );
 368+ }
 369+
 370+ /**
 371+ * Resolve a storage path relative to a particular container.
 372+ * This is for internal use for backends, such as encoding or
 373+ * perhaps getting absolute paths (e.g. file system based backends).
 374+ *
 375+ * @param $container string
 376+ * @param $relStoragePath string
 377+ * @return string|null Null if path is not valid
 378+ */
 379+ protected function resolveContainerPath( $container, $relStoragePath ) {
 380+ return $relStoragePath;
 381+ }
331382 }
332383
333384 /**
@@ -360,6 +411,7 @@
361412 $this->params = $params;
362413 $this->state = self::STATE_NEW;
363414 $this->failedAttempt = false;
 415+ $this->initialize();
364416 }
365417
366418 /**
@@ -425,6 +477,11 @@
426478 }
427479
428480 /**
 481+ * @return void
 482+ */
 483+ protected function initialize() {}
 484+
 485+ /**
429486 * @return Status
430487 */
431488 abstract protected function doAttempt();
@@ -577,6 +634,13 @@
578635 * overwriteSame : override any existing file at destination
579636 */
580637 class FileMoveOp extends FileStoreOp {
 638+ protected $usingMove = false; // using backend move() function?
 639+
 640+ function initialize() {
 641+ // Use faster, native, move() if applicable
 642+ $this->usingMove = $this->backend->canMove( $this->params );
 643+ }
 644+
581645 function doAttempt() {
582646 // Create a backup copy of any file that exists at destination
583647 $status = $this->backupDest();
@@ -584,7 +648,7 @@
585649 return $status;
586650 }
587651 // Native moves: move the file into the destination
588 - if ( $this->backend->hasMove() ) {
 652+ if ( $this->usingMove ) {
589653 $status = $this->backend->move( $this->params );
590654 // Non-native moves: copy the file into the destination
591655 } else {
@@ -595,7 +659,7 @@
596660
597661 function doRevert() {
598662 // Native moves: move the file back to the source
599 - if ( $this->backend->hasMove() ) {
 663+ if ( $this->usingMove ) {
600664 $params = array(
601665 'source' => $this->params['dest'],
602666 'dest' => $this->params['source']
@@ -619,7 +683,7 @@
620684
621685 function doFinish() {
622686 // Native moves: nothing is at the source anymore
623 - if ( $this->backend->hasMove() ) {
 687+ if ( $this->usingMove ) {
624688 $status = Status::newGood();
625689 // Non-native moves: delete the source file
626690 } else {

Status & tagging log