Index: branches/FileBackend/phase3/includes/filerepo/ForeignAPIRepo.php |
— | — | @@ -280,9 +280,9 @@ |
281 | 281 | $localFilename = $localPath . "/" . $fileName; |
282 | 282 | $localUrl = $this->getZoneUrl( 'thumb' ) . "/" . $this->getHashPath( $name ) . rawurlencode( $name ) . "/" . rawurlencode( $fileName ); |
283 | 283 | |
284 | | - if( file_exists( $localFilename ) && isset( $metadata['timestamp'] ) ) { |
| 284 | + if( $this->repo->fileExists( $localFilename ) && isset( $metadata['timestamp'] ) ) { |
285 | 285 | wfDebug( __METHOD__ . " Thumbnail was already downloaded before\n" ); |
286 | | - $modified = filemtime( $localFilename ); |
| 286 | + $modified = $this->repo->getFileTimestamp( $localFilename ); |
287 | 287 | $remoteModified = strtotime( $metadata['timestamp'] ); |
288 | 288 | $current = time(); |
289 | 289 | $diff = abs( $modified - $current ); |
— | — | @@ -299,16 +299,12 @@ |
300 | 300 | wfDebug( __METHOD__ . " Could not download thumb\n" ); |
301 | 301 | return false; |
302 | 302 | } |
303 | | - if ( !is_dir($localPath) ) { |
304 | | - if( !wfMkdirParents( $localPath, null, __METHOD__ ) ) { |
305 | | - wfDebug( __METHOD__ . " could not create directory $localPath for thumb\n" ); |
306 | | - return $foreignUrl; |
307 | | - } |
308 | | - } |
309 | 303 | |
310 | 304 | # @todo FIXME: Delete old thumbs that aren't being used. Maintenance script? |
311 | 305 | wfSuppressWarnings(); |
312 | | - if( !file_put_contents( $localFilename, $thumb ) ) { |
| 306 | + $backend = $this->repo->getBackend(); |
| 307 | + $op = array( 'op' => 'create', 'dest' => $localFilename, 'content' => $thumb ); |
| 308 | + if( !$backend->doOperation( $op )->isOK() ) { |
313 | 309 | wfRestoreWarnings(); |
314 | 310 | wfDebug( __METHOD__ . " could not write to thumb path\n" ); |
315 | 311 | return $foreignUrl; |
Index: branches/FileBackend/phase3/includes/filerepo/file/LocalFile.php |
— | — | @@ -604,8 +604,8 @@ |
605 | 605 | |
606 | 606 | if ( $this->repo->fileExists( $thumbDir, FileRepo::FILES_ONLY ) ) { |
607 | 607 | // File where directory should be |
608 | | - $op = array( 'operation' => 'delete', 'source' => $thumbDir ); |
609 | | - $this->repo->getBackend()->doOperations( array( $op ) ); |
| 608 | + $op = array( 'op' => 'delete', 'source' => $thumbDir ); |
| 609 | + $this->repo->getBackend()->doOperation( $op ); |
610 | 610 | } |
611 | 611 | } |
612 | 612 | |
— | — | @@ -749,8 +749,8 @@ |
750 | 750 | # Check that the base file name is part of the thumb name |
751 | 751 | # This is a basic sanity check to avoid erasing unrelated directories |
752 | 752 | if ( strpos( $file, $this->getName() ) !== false ) { |
753 | | - $op = array( 'operation' => 'delete', 'source' => "$dir/$file" ); |
754 | | - $this->repo->getBackend()->doOperations( array( $op ) ); |
| 753 | + $op = array( 'op' => 'delete', 'source' => "$dir/$file" ); |
| 754 | + $this->repo->getBackend()->doOperation( $op ); |
755 | 755 | } |
756 | 756 | } |
757 | 757 | } |
Index: branches/FileBackend/phase3/includes/filerepo/file/UnregisteredLocalFile.php |
— | — | @@ -69,6 +69,7 @@ |
70 | 70 | if ( $path ) { |
71 | 71 | $this->path = $path; |
72 | 72 | } else { |
| 73 | + $this->assertRepoDefined(); |
73 | 74 | $this->path = $repo->getRootDirectory() . '/' . |
74 | 75 | $repo->getHashPath( $this->name ) . $this->name; |
75 | 76 | } |
Index: branches/FileBackend/phase3/includes/filerepo/file/File.php |
— | — | @@ -179,8 +179,7 @@ |
180 | 180 | static function checkExtensionCompatibility( File $old, $new ) { |
181 | 181 | $oldMime = $old->getMimeType(); |
182 | 182 | $n = strrpos( $new, '.' ); |
183 | | - $newExt = self::normalizeExtension( |
184 | | - $n ? substr( $new, $n + 1 ) : '' ); |
| 183 | + $newExt = self::normalizeExtension( $n ? substr( $new, $n + 1 ) : '' ); |
185 | 184 | $mimeMagic = MimeMagic::singleton(); |
186 | 185 | return $mimeMagic->isMatchingExtension( $newExt, $oldMime ); |
187 | 186 | } |
— | — | @@ -238,7 +237,9 @@ |
239 | 238 | * Return the associated title object |
240 | 239 | * @return Title|false |
241 | 240 | */ |
242 | | - public function getTitle() { return $this->title; } |
| 241 | + public function getTitle() { |
| 242 | + return $this->title; |
| 243 | + } |
243 | 244 | |
244 | 245 | /** |
245 | 246 | * Return the title used to find this file |
— | — | @@ -409,7 +410,7 @@ |
410 | 411 | public function convertMetadataVersion($metadata, $version) { |
411 | 412 | $handler = $this->getHandler(); |
412 | 413 | if ( !is_array( $metadata ) ) { |
413 | | - //just to make the return type consistant |
| 414 | + // Just to make the return type consistent |
414 | 415 | $metadata = unserialize( $metadata ); |
415 | 416 | } |
416 | 417 | if ( $handler ) { |
— | — | @@ -454,7 +455,9 @@ |
455 | 456 | * Overridden by LocalFile, |
456 | 457 | * STUB |
457 | 458 | */ |
458 | | - function getMediaType() { return MEDIATYPE_UNKNOWN; } |
| 459 | + function getMediaType() { |
| 460 | + return MEDIATYPE_UNKNOWN; |
| 461 | + } |
459 | 462 | |
460 | 463 | /** |
461 | 464 | * Checks if the output of transform() for this file is likely |
— | — | @@ -540,6 +543,8 @@ |
541 | 544 | * @return bool |
542 | 545 | */ |
543 | 546 | protected function _getIsSafeFile() { |
| 547 | + global $wgTrustedMediaFormats; |
| 548 | + |
544 | 549 | if ( $this->allowInlineDisplay() ) { |
545 | 550 | return true; |
546 | 551 | } |
— | — | @@ -547,8 +552,6 @@ |
548 | 553 | return true; |
549 | 554 | } |
550 | 555 | |
551 | | - global $wgTrustedMediaFormats; |
552 | | - |
553 | 556 | $type = $this->getMediaType(); |
554 | 557 | $mime = $this->getMimeType(); |
555 | 558 | #wfDebug("LocalFile::isSafeFile: type= $type, mime= $mime\n"); |
— | — | @@ -731,10 +734,10 @@ |
732 | 735 | wfDebug( __METHOD__.": Doing stat for $thumbPath\n" ); |
733 | 736 | $this->migrateThumbFile( $thumbName ); |
734 | 737 | if ( $this->repo->fileExists( $thumbPath ) && !($flags & self::RENDER_FORCE) ) { |
735 | | - $thumbTime = filemtime( $thumbPath ); |
736 | | - if ( $thumbTime !== FALSE && |
737 | | - gmdate( 'YmdHis', $thumbTime ) >= $wgThumbnailEpoch ) { |
738 | | - |
| 738 | + $thumbTime = $this->repo->getFileTimestamp( $thumbPath ); |
| 739 | + if ( $thumbTime !== false |
| 740 | + && gmdate( 'YmdHis', $thumbTime ) >= $wgThumbnailEpoch ) |
| 741 | + { |
739 | 742 | return $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); |
740 | 743 | } |
741 | 744 | } elseif( $flags & self::RENDER_FORCE ) { |
— | — | @@ -1456,7 +1459,7 @@ |
1457 | 1460 | */ |
1458 | 1461 | function getTimestamp() { |
1459 | 1462 | $this->assertRepoDefined(); |
1460 | | - return $this->repo->getBackend()->getFileTimestamp( $this->getPath() ); |
| 1463 | + return $this->repo->getFileTimestamp( $this->getVirtualUrl() ); |
1461 | 1464 | } |
1462 | 1465 | |
1463 | 1466 | /** |
— | — | @@ -1466,11 +1469,7 @@ |
1467 | 1470 | */ |
1468 | 1471 | function getSha1() { |
1469 | 1472 | $this->assertRepoDefined(); |
1470 | | - $tmpFile = $this->repo->getBackend()->getLocalCopy( $this->getPath() ); |
1471 | | - if ( !$tmpFile ) { |
1472 | | - return false; |
1473 | | - } |
1474 | | - return $tmpFile->sha1Base36(); |
| 1473 | + return $this->repo->getFileSha1( $this->getVirtualUrl() ); |
1475 | 1474 | } |
1476 | 1475 | |
1477 | 1476 | /** |
Index: branches/FileBackend/phase3/includes/filerepo/file/ForeignAPIFile.php |
— | — | @@ -13,7 +13,6 @@ |
14 | 14 | * @ingroup FileRepo |
15 | 15 | */ |
16 | 16 | class ForeignAPIFile extends File { |
17 | | - |
18 | 17 | private $mExists; |
19 | 18 | |
20 | 19 | protected $repoClass = 'ForeignApiRepo'; |
— | — | @@ -149,16 +148,16 @@ |
150 | 149 | } |
151 | 150 | |
152 | 151 | function getSha1() { |
153 | | - return isset( $this->mInfo['sha1'] ) ? |
154 | | - wfBaseConvert( strval( $this->mInfo['sha1'] ), 16, 36, 31 ) : |
155 | | - null; |
| 152 | + return isset( $this->mInfo['sha1'] ) |
| 153 | + ? wfBaseConvert( strval( $this->mInfo['sha1'] ), 16, 36, 31 ) |
| 154 | + : null; |
156 | 155 | } |
157 | 156 | |
158 | 157 | function getTimestamp() { |
159 | 158 | return wfTimestamp( TS_MW, |
160 | | - isset( $this->mInfo['timestamp'] ) ? |
161 | | - strval( $this->mInfo['timestamp'] ) : |
162 | | - null |
| 159 | + isset( $this->mInfo['timestamp'] ) |
| 160 | + ? strval( $this->mInfo['timestamp'] ) |
| 161 | + : null |
163 | 162 | ); |
164 | 163 | } |
165 | 164 | |
— | — | @@ -198,19 +197,14 @@ |
199 | 198 | } |
200 | 199 | |
201 | 200 | function getThumbnails() { |
202 | | - $files = array(); |
203 | 201 | $dir = $this->getThumbPath( $this->getName() ); |
204 | | - if ( is_dir( $dir ) ) { |
205 | | - $handle = opendir( $dir ); |
206 | | - if ( $handle ) { |
207 | | - while ( false !== ( $file = readdir($handle) ) ) { |
208 | | - if ( $file[0] != '.' ) { |
209 | | - $files[] = $file; |
210 | | - } |
211 | | - } |
212 | | - closedir( $handle ); |
213 | | - } |
| 202 | + $iter = $this->repo->getBackend()->getFileList( array( 'directory' => $dir ) ); |
| 203 | + |
| 204 | + $files = array(); |
| 205 | + foreach ( $iter as $file ) { |
| 206 | + $files[] = $file; |
214 | 207 | } |
| 208 | + |
215 | 209 | return $files; |
216 | 210 | } |
217 | 211 | |
— | — | @@ -221,22 +215,26 @@ |
222 | 216 | |
223 | 217 | function purgeDescriptionPage() { |
224 | 218 | global $wgMemc, $wgContLang; |
| 219 | + |
225 | 220 | $url = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgContLang->getCode() ); |
226 | 221 | $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', md5($url) ); |
| 222 | + |
227 | 223 | $wgMemc->delete( $key ); |
228 | 224 | } |
229 | 225 | |
230 | 226 | function purgeThumbnails() { |
231 | 227 | global $wgMemc; |
| 228 | + |
232 | 229 | $key = $this->repo->getLocalCacheKey( 'ForeignAPIRepo', 'ThumbUrl', $this->getName() ); |
233 | 230 | $wgMemc->delete( $key ); |
| 231 | + |
| 232 | + $backend = $this->repo->getBackend(); |
234 | 233 | $files = $this->getThumbnails(); |
235 | 234 | $dir = $this->getThumbPath( $this->getName() ); |
236 | 235 | foreach ( $files as $file ) { |
237 | | - unlink( $dir . $file ); |
| 236 | + $op = array( 'op' => 'delete', 'source' => "{$dir}{$file}" ); |
| 237 | + $backend->doOperation( $op ); |
238 | 238 | } |
239 | | - if ( is_dir( $dir ) ) { |
240 | | - rmdir( $dir ); // Might have already gone away, spews errors if we don't. |
241 | | - } |
| 239 | + $backend->clean( array( 'directory' => $dir ) ); |
242 | 240 | } |
243 | 241 | } |
Index: branches/FileBackend/phase3/includes/filerepo/file/FSFile.php |
— | — | @@ -49,7 +49,7 @@ |
50 | 50 | public function getTimestamp() { |
51 | 51 | $timestamp = filemtime( $this->path ); |
52 | 52 | if ( $timestamp !== false ) { |
53 | | - $timestamp = wfTimestamp( TS_MW, $ts ); |
| 53 | + $timestamp = wfTimestamp( TS_MW, $timestamp ); |
54 | 54 | } |
55 | 55 | return $timestamp; |
56 | 56 | } |
Index: branches/FileBackend/phase3/includes/filerepo/LocalRepo.php |
— | — | @@ -71,8 +71,8 @@ |
72 | 72 | $hidden = $this->hiddenFileHasKey( $key, 'lock' ); |
73 | 73 | if ( !$deleted && !$hidden ) { // not in use now |
74 | 74 | wfDebug( __METHOD__ . ": deleting $key\n" ); |
75 | | - $op = array( 'operation' => 'delete', 'source' => $path ); |
76 | | - if ( !$backend->doOperations( array( $op ) )->isOK() ) { |
| 75 | + $op = array( 'op' => 'delete', 'source' => $path ); |
| 76 | + if ( !$backend->doOperation( $op )->isOK() ) { |
77 | 77 | $status->error( 'undelete-cleanup-error', $path ); |
78 | 78 | $status->failCount++; |
79 | 79 | } |
Index: branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php |
— | — | @@ -355,6 +355,21 @@ |
356 | 356 | return $status; |
357 | 357 | } |
358 | 358 | |
| 359 | + function clean( array $params ) { |
| 360 | + $status = Status::newGood(); |
| 361 | + list( $c, $dir ) = $this->resolveStoragePath( $params['directory'] ); |
| 362 | + if ( $dir === null ) { |
| 363 | + $status->fatal( 'backend-fail-invalidpath', $params['directory'] ); |
| 364 | + return $status; // invalid storage path |
| 365 | + } |
| 366 | + wfSuppressWarnings(); |
| 367 | + if ( is_dir( $dir ) ) { |
| 368 | + rmdir( $dir ); // Might have already gone away, spews errors if we don't. |
| 369 | + } |
| 370 | + wfRestoreWarnings(); |
| 371 | + return $status; |
| 372 | + } |
| 373 | + |
359 | 374 | function fileExists( array $params ) { |
360 | 375 | list( $c, $source ) = $this->resolveStoragePath( $params['source'] ); |
361 | 376 | if ( $source === null ) { |
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php |
— | — | @@ -63,8 +63,8 @@ |
64 | 64 | * <code> |
65 | 65 | * $ops = array( |
66 | 66 | * array( |
67 | | - * 'operation' => 'store', |
68 | | - * 'src' => '/tmp/uploads/picture.png', |
| 67 | + * 'op' => 'store', |
| 68 | + * 'source' => '/tmp/uploads/picture.png', |
69 | 69 | * 'dest' => 'mwstore://container/uploadedFilename.png' |
70 | 70 | * ) |
71 | 71 | * ); |
— | — | @@ -78,11 +78,21 @@ |
79 | 79 | abstract public function doOperations( array $ops ); |
80 | 80 | |
81 | 81 | /** |
| 82 | + * Same as doOperations() except it takes a single operation array |
| 83 | + * |
| 84 | + * @param $op Array |
| 85 | + * @return Status |
| 86 | + */ |
| 87 | + final public function doOperation( $op ) { |
| 88 | + return $this->doOperation( $op ); |
| 89 | + } |
| 90 | + |
| 91 | + /** |
82 | 92 | * Prepare a storage path for usage. This will create containers |
83 | 93 | * that don't yet exists or, on FS backends, create parent directories. |
84 | 94 | * Do not call this function from places outside FileBackend and FileOp. |
85 | 95 | * $params include: |
86 | | - * directory : destination storage path |
| 96 | + * directory : storage directory |
87 | 97 | * |
88 | 98 | * @param Array $params |
89 | 99 | * @return Status |
— | — | @@ -94,7 +104,7 @@ |
95 | 105 | * This is not guaranteed to actually do anything. |
96 | 106 | * Do not call this function from places outside FileBackend and FileOp. |
97 | 107 | * $params include: |
98 | | - * directory : destination storage path |
| 108 | + * directory : storage directory |
99 | 109 | * noAccess : try to deny file access |
100 | 110 | * noListing : try to deny file listing |
101 | 111 | * |
— | — | @@ -104,6 +114,18 @@ |
105 | 115 | abstract public function secure( array $params ); |
106 | 116 | |
107 | 117 | /** |
| 118 | + * Clean up an empty storage directory. |
| 119 | + * On FS backends, the directory will be deleted. Others may do nothing. |
| 120 | + * Do not call this function from places outside FileBackend and FileOp. |
| 121 | + * $params include: |
| 122 | + * directory : storage directory |
| 123 | + * |
| 124 | + * @param Array $params |
| 125 | + * @return Status |
| 126 | + */ |
| 127 | + abstract public function clean( array $params ); |
| 128 | + |
| 129 | + /** |
108 | 130 | * Check if a file exits at a storage path in the backend. |
109 | 131 | * Do not call this function from places outside FileBackend and FileOp. |
110 | 132 | * $params include: |
— | — | @@ -310,6 +332,10 @@ |
311 | 333 | return Status::newGood(); |
312 | 334 | } |
313 | 335 | |
| 336 | + public function clean( array $params ) { |
| 337 | + return Status::newGood(); |
| 338 | + } |
| 339 | + |
314 | 340 | /** |
315 | 341 | * Whether this backend implements move() and is applies to a potential |
316 | 342 | * move from one storage path to another. No backends hits are required. |
— | — | @@ -366,12 +392,12 @@ |
367 | 393 | $performOps = array(); // array of FileOp objects |
368 | 394 | // Build up ordered array of FileOps... |
369 | 395 | foreach ( $ops as $operation ) { |
370 | | - $opName = $operation['operation']; |
| 396 | + $opName = $operation['op']; |
371 | 397 | if ( isset( $supportedOps[$opName] ) ) { |
372 | 398 | $class = $supportedOps[$opName]; |
373 | 399 | // Get params for this operation |
374 | 400 | $params = $operation; |
375 | | - unset( $params['operation'] ); // don't need this |
| 401 | + unset( $params['op'] ); // don't need this |
376 | 402 | unset( $params['ignoreErrors'] ); // don't need this |
377 | 403 | // Append the FileOp class |
378 | 404 | $performOps[] = new $class( $params ); |
Index: branches/FileBackend/phase3/includes/filerepo/FileRepo.php |
— | — | @@ -287,7 +287,7 @@ |
288 | 288 | * @param $zone string |
289 | 289 | * @return Array (container, base path) or (null, null) |
290 | 290 | */ |
291 | | - function getZoneLocation( $zone ) { |
| 291 | + protected function getZoneLocation( $zone ) { |
292 | 292 | if ( !isset( $this->zones[$zone] ) ) { |
293 | 293 | return array( null, null ); // bogus |
294 | 294 | } |
— | — | @@ -464,6 +464,16 @@ |
465 | 465 | } |
466 | 466 | |
467 | 467 | /** |
| 468 | + * Get an array or iterator of file objects for files that have a given |
| 469 | + * SHA-1 content hash. |
| 470 | + * |
| 471 | + * STUB |
| 472 | + */ |
| 473 | + public function findBySha1( $hash ) { |
| 474 | + return array(); |
| 475 | + } |
| 476 | + |
| 477 | + /** |
468 | 478 | * Get the public root URL of the repository |
469 | 479 | * |
470 | 480 | * @return string |
— | — | @@ -518,25 +528,7 @@ |
519 | 529 | } |
520 | 530 | |
521 | 531 | /** |
522 | | - * @param $name |
523 | | - * @param $levels |
524 | | - * @return string |
525 | | - */ |
526 | | - static function getHashPathForLevel( $name, $levels ) { |
527 | | - if ( $levels == 0 ) { |
528 | | - return ''; |
529 | | - } else { |
530 | | - $hash = md5( $name ); |
531 | | - $path = ''; |
532 | | - for ( $i = 1; $i <= $levels; $i++ ) { |
533 | | - $path .= substr( $hash, 0, $i ) . '/'; |
534 | | - } |
535 | | - return $path; |
536 | | - } |
537 | | - } |
538 | | - |
539 | | - /** |
540 | | - * Get the public zone root directory of the repository. |
| 532 | + * Get the public zone root storage directory of the repository |
541 | 533 | * |
542 | 534 | * @return string |
543 | 535 | */ |
— | — | @@ -556,6 +548,24 @@ |
557 | 549 | } |
558 | 550 | |
559 | 551 | /** |
| 552 | + * @param $name |
| 553 | + * @param $levels |
| 554 | + * @return string |
| 555 | + */ |
| 556 | + static function getHashPathForLevel( $name, $levels ) { |
| 557 | + if ( $levels == 0 ) { |
| 558 | + return ''; |
| 559 | + } else { |
| 560 | + $hash = md5( $name ); |
| 561 | + $path = ''; |
| 562 | + for ( $i = 1; $i <= $levels; $i++ ) { |
| 563 | + $path .= substr( $hash, 0, $i ) . '/'; |
| 564 | + } |
| 565 | + return $path; |
| 566 | + } |
| 567 | + } |
| 568 | + |
| 569 | + /** |
560 | 570 | * Get the name of this repository, as specified by $info['name]' to the constructor |
561 | 571 | * |
562 | 572 | * @return string |
— | — | @@ -739,7 +749,7 @@ |
740 | 750 | } |
741 | 751 | } |
742 | 752 | $operations[] = array( |
743 | | - 'operation' => $opName, |
| 753 | + 'op' => $opName, |
744 | 754 | 'source' => $srcPath, |
745 | 755 | 'dest' => $dstPath, |
746 | 756 | 'overwriteDest' => $flags & self::OVERWRITE, |
— | — | @@ -761,9 +771,9 @@ |
762 | 772 | } |
763 | 773 | |
764 | 774 | /** |
765 | | - * Deletes a batch of files. Each file can be a (zone, rel) pairs, a |
766 | | - * virtual url or a real path. It will try to delete each file, but |
767 | | - * ignores any errors that may occur |
| 775 | + * Deletes a batch of files. |
| 776 | + * Each file can be a (zone, rel) pair, virtual url, storage path, or FS path. |
| 777 | + * It will try to delete each file, but ignores any errors that may occur. |
768 | 778 | * |
769 | 779 | * @param $pairs array List of files to delete |
770 | 780 | * @return void |
— | — | @@ -789,8 +799,8 @@ |
790 | 800 | // Get a file operation if needed |
791 | 801 | if ( FileBackend::isStoragePath( $path ) ) { |
792 | 802 | $operations[] = array( |
793 | | - 'operation' => 'delete', |
794 | | - 'source' => $path, |
| 803 | + 'op' => 'delete', |
| 804 | + 'source' => $path, |
795 | 805 | 'ignoreErrors' => true |
796 | 806 | ); |
797 | 807 | } else { |
— | — | @@ -829,6 +839,7 @@ |
830 | 840 | |
831 | 841 | /** |
832 | 842 | * Remove a temporary file or mark it for garbage collection |
| 843 | + * |
833 | 844 | * @param $virtualUrl String: the virtual URL returned by storeTemp |
834 | 845 | * @return Boolean: true on success, false on failure |
835 | 846 | */ |
— | — | @@ -839,13 +850,13 @@ |
840 | 851 | return false; |
841 | 852 | } |
842 | 853 | $path = $this->resolveVirtualUrl( $virtualUrl ); |
843 | | - $op = array( 'operation' => 'delete', 'source' => $path ); |
844 | | - $status = $this->backend->doOperations( array( $op ) ); |
| 854 | + $op = array( 'op' => 'delete', 'source' => $path ); |
| 855 | + $status = $this->backend->doOperation( $op ); |
845 | 856 | return $status->isOK(); |
846 | 857 | } |
847 | 858 | |
848 | 859 | /** |
849 | | - * Copy or move a file either from the local filesystem or from an mwrepo:// |
| 860 | + * Copy or move a file either from a storage path or from a mwrepo:// |
850 | 861 | * virtual URL, into this repository at the specified destination location. |
851 | 862 | * |
852 | 863 | * Returns a FileRepoStatus object. On success, the value contains "new" or |
— | — | @@ -928,11 +939,11 @@ |
929 | 940 | // publishBatch's caller should prevent races. In Windows there's no |
930 | 941 | // problem because the rename primitive fails if the destination exists. |
931 | 942 | if ( $backend->fileExists( array( 'source' => $archivePath ) ) ) { |
932 | | - $operations[] = array( 'operation' => 'null' ); |
| 943 | + $operations[] = array( 'op' => 'null' ); |
933 | 944 | continue; |
934 | 945 | } else { |
935 | 946 | $operations[] = array( |
936 | | - 'operation' => 'move', |
| 947 | + 'op' => 'move', |
937 | 948 | 'source' => $dstPath, |
938 | 949 | 'dest' => $archivePath, |
939 | 950 | 'ignoreErrors' => true |
— | — | @@ -944,14 +955,14 @@ |
945 | 956 | } |
946 | 957 | if ( $flags & self::DELETE_SOURCE ) { |
947 | 958 | $operations[] = array( |
948 | | - 'operation' => 'move', |
| 959 | + 'op' => 'move', |
949 | 960 | 'source' => $srcPath, |
950 | 961 | 'dest' => $dstPath, |
951 | 962 | 'ignoreErrors' => true |
952 | 963 | ); |
953 | 964 | } else { |
954 | 965 | $operations[] = array( |
955 | | - 'operation' => 'copy', |
| 966 | + 'op' => 'copy', |
956 | 967 | 'source' => $srcPath, |
957 | 968 | 'dest' => $dstPath, |
958 | 969 | 'ignoreErrors' => true |
— | — | @@ -1086,12 +1097,12 @@ |
1087 | 1098 | |
1088 | 1099 | if ( $backend->fileExists( array( 'source' => $archivePath ) ) ) { |
1089 | 1100 | $operations[] = array( |
1090 | | - 'operation' => 'delete', |
| 1101 | + 'op' => 'delete', |
1091 | 1102 | 'source' => $srcPath |
1092 | 1103 | ); |
1093 | 1104 | } else { |
1094 | 1105 | $operations[] = array( |
1095 | | - 'operation' => 'move', |
| 1106 | + 'op' => 'move', |
1096 | 1107 | 'source' => $srcPath, |
1097 | 1108 | 'dest' => $archivePath |
1098 | 1109 | ); |
— | — | @@ -1121,19 +1132,58 @@ |
1122 | 1133 | } |
1123 | 1134 | |
1124 | 1135 | /** |
1125 | | - * Get properties of a file with a given virtual URL |
1126 | | - * The virtual URL must refer to this repo |
1127 | | - * Properties should ultimately be obtained via FSFile::getProps() |
| 1136 | + * If a path is a virtual URL, resolve it to a storage path. |
| 1137 | + * Otherwise, just return the path as it is. |
1128 | 1138 | * |
| 1139 | + * @return string |
| 1140 | + * @throws MWException |
| 1141 | + */ |
| 1142 | + protected function resolveToStoragePath( $path ) { |
| 1143 | + if ( $this->isVirtualUrl( $path ) ) { |
| 1144 | + return $this->resolveVirtualUrl( $path ); |
| 1145 | + } |
| 1146 | + return $path; |
| 1147 | + } |
| 1148 | + |
| 1149 | + /** |
| 1150 | + * Get properties of a file with a given virtual URL/storage path. |
| 1151 | + * Properties should ultimately be obtained via FSFile::getProps(). |
| 1152 | + * |
1129 | 1153 | * @param $virtualUrl string |
1130 | 1154 | * @return Array |
1131 | 1155 | */ |
1132 | 1156 | public function getFileProps( $virtualUrl ) { |
1133 | | - $path = $this->resolveVirtualUrl( $virtualUrl ); |
1134 | | - return $this->backend->getFileProps( $path ); |
| 1157 | + $path = $this->resolveToStoragePath( $virtualUrl ); |
| 1158 | + return $this->backend->getFileProps( array( 'source' => $path ) ); |
1135 | 1159 | } |
1136 | 1160 | |
1137 | 1161 | /** |
| 1162 | + * Get the timestamp of a file with a given virtual URL/storage path |
| 1163 | + * |
| 1164 | + * @param $virtualUrl |
| 1165 | + * @return string|false |
| 1166 | + */ |
| 1167 | + public function getFileTimestamp( $virtualUrl ) { |
| 1168 | + $path = $this->resolveToStoragePath( $virtualUrl ); |
| 1169 | + return $this->backend->getFileTimestamp( array( 'source' => $path ) ); |
| 1170 | + } |
| 1171 | + |
| 1172 | + /** |
| 1173 | + * Get the sha1 of a file with a given virtual URL/storage path |
| 1174 | + * |
| 1175 | + * @param $virtualUrl |
| 1176 | + * @return string|false |
| 1177 | + */ |
| 1178 | + public function getFileSha1( $virtualUrl ) { |
| 1179 | + $path = $this->resolveToStoragePath( $virtualUrl ); |
| 1180 | + $tmpFile = $this->backend->getLocalCopy( array( 'source' => $path ) ); |
| 1181 | + if ( !$tmpFile ) { |
| 1182 | + return false; |
| 1183 | + } |
| 1184 | + return $tmpFile->sha1Base36(); |
| 1185 | + } |
| 1186 | + |
| 1187 | + /** |
1138 | 1188 | * Call a callback function for every public file in the repository. |
1139 | 1189 | * May use either the database or the filesystem. |
1140 | 1190 | * |
— | — | @@ -1309,16 +1359,6 @@ |
1310 | 1360 | public function invalidateImageRedirect( Title $title ) {} |
1311 | 1361 | |
1312 | 1362 | /** |
1313 | | - * Get an array or iterator of file objects for files that have a given |
1314 | | - * SHA-1 content hash. |
1315 | | - * |
1316 | | - * STUB |
1317 | | - */ |
1318 | | - public function findBySha1( $hash ) { |
1319 | | - return array(); |
1320 | | - } |
1321 | | - |
1322 | | - /** |
1323 | 1363 | * Get the human-readable name of the repo |
1324 | 1364 | * |
1325 | 1365 | * @return string |