Index: branches/FileBackend/phase3/includes/filerepo/backend/SwiftFileBackend.php |
— | — | @@ -16,6 +16,7 @@ |
17 | 17 | * All of the library classes must be registed in $wgAutoloadClasses. |
18 | 18 | * |
19 | 19 | * @TODO: update MessagesEn for status errors. |
| 20 | + * @TODO: handle 'latest' param as "X-Newest: true". |
20 | 21 | * |
21 | 22 | * @ingroup FileBackend |
22 | 23 | */ |
— | — | @@ -67,7 +68,7 @@ |
68 | 69 | function doStoreInternal( array $params ) { |
69 | 70 | $status = Status::newGood(); |
70 | 71 | |
71 | | - list( $dstCont, $destRel ) = $this->resolveStoragePath( $params['dst'] ); |
| 72 | + list( $dstCont, $destRel ) = $this->resolveStoragePathReal( $params['dst'] ); |
72 | 73 | if ( $destRel === null ) { |
73 | 74 | $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); |
74 | 75 | return $status; |
— | — | @@ -91,6 +92,7 @@ |
92 | 93 | return $status; |
93 | 94 | } catch ( Exception $e ) { // some other exception? |
94 | 95 | $status->fatal( 'backend-fail-internal' ); |
| 96 | + $this->logException( $e, __METHOD__, $params ); |
95 | 97 | return $status; |
96 | 98 | } |
97 | 99 | |
— | — | @@ -109,6 +111,7 @@ |
110 | 112 | return $status; |
111 | 113 | } catch ( Exception $e ) { // some other exception? |
112 | 114 | $status->fatal( 'backend-fail-internal' ); |
| 115 | + $this->logException( $e, __METHOD__, $params ); |
113 | 116 | return $status; |
114 | 117 | } |
115 | 118 | |
— | — | @@ -124,6 +127,7 @@ |
125 | 128 | $status->fatal( 'backend-fail-connect' ); |
126 | 129 | } catch ( Exception $e ) { // some other exception? |
127 | 130 | $status->fatal( 'backend-fail-internal' ); |
| 131 | + $this->logException( $e, __METHOD__, $params ); |
128 | 132 | } |
129 | 133 | |
130 | 134 | return $status; |
— | — | @@ -135,13 +139,13 @@ |
136 | 140 | function doCopyInternal( array $params ) { |
137 | 141 | $status = Status::newGood(); |
138 | 142 | |
139 | | - list( $srcCont, $srcRel ) = $this->resolveStoragePath( $params['src'] ); |
| 143 | + list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); |
140 | 144 | if ( $srcRel === null ) { |
141 | 145 | $status->fatal( 'backend-fail-invalidpath', $params['src'] ); |
142 | 146 | return $status; |
143 | 147 | } |
144 | 148 | |
145 | | - list( $dstCont, $destRel ) = $this->resolveStoragePath( $params['dst'] ); |
| 149 | + list( $dstCont, $destRel ) = $this->resolveStoragePathReal( $params['dst'] ); |
146 | 150 | if ( $destRel === null ) { |
147 | 151 | $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); |
148 | 152 | return $status; |
— | — | @@ -166,6 +170,7 @@ |
167 | 171 | return $status; |
168 | 172 | } catch ( Exception $e ) { // some other exception? |
169 | 173 | $status->fatal( 'backend-fail-internal' ); |
| 174 | + $this->logException( $e, __METHOD__, $params ); |
170 | 175 | return $status; |
171 | 176 | } |
172 | 177 | |
— | — | @@ -184,16 +189,20 @@ |
185 | 190 | return $status; |
186 | 191 | } catch ( Exception $e ) { // some other exception? |
187 | 192 | $status->fatal( 'backend-fail-internal' ); |
| 193 | + $this->logException( $e, __METHOD__, $params ); |
188 | 194 | return $status; |
189 | 195 | } |
190 | 196 | |
191 | 197 | // (d) Actually copy the file to the destination |
192 | 198 | try { |
193 | | - $this->swiftcopy( $sContObj, $srcRel, $dContObj, $destRel ); |
| 199 | + $sContObj->copy_object_to( $srcRel, $dContObj, $destRel ); |
| 200 | + } catch ( NoSuchObjectException $e ) { // source object does not exist |
| 201 | + $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); |
194 | 202 | } catch ( InvalidResponseException $e ) { |
195 | 203 | $status->fatal( 'backend-fail-connect' ); |
196 | 204 | } catch ( Exception $e ) { // some other exception? |
197 | 205 | $status->fatal( 'backend-fail-internal' ); |
| 206 | + $this->logException( $e, __METHOD__, $params ); |
198 | 207 | } |
199 | 208 | |
200 | 209 | return $status; |
— | — | @@ -205,7 +214,7 @@ |
206 | 215 | function doDeleteInternal( array $params ) { |
207 | 216 | $status = Status::newGood(); |
208 | 217 | |
209 | | - list( $srcCont, $srcRel ) = $this->resolveStoragePath( $params['src'] ); |
| 218 | + list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); |
210 | 219 | if ( $srcRel === null ) { |
211 | 220 | $status->fatal( 'backend-fail-invalidpath', $params['src'] ); |
212 | 221 | return $status; |
— | — | @@ -229,6 +238,7 @@ |
230 | 239 | return $status; |
231 | 240 | } catch ( Exception $e ) { // some other exception? |
232 | 241 | $status->fatal( 'backend-fail-internal' ); |
| 242 | + $this->logException( $e, __METHOD__, $params ); |
233 | 243 | return $status; |
234 | 244 | } |
235 | 245 | |
— | — | @@ -243,6 +253,7 @@ |
244 | 254 | $status->fatal( 'backend-fail-connect' ); |
245 | 255 | } catch ( Exception $e ) { // some other exception? |
246 | 256 | $status->fatal( 'backend-fail-internal' ); |
| 257 | + $this->logException( $e, __METHOD__, $params ); |
247 | 258 | } |
248 | 259 | |
249 | 260 | return $status; |
— | — | @@ -254,7 +265,7 @@ |
255 | 266 | function doCreateInternal( array $params ) { |
256 | 267 | $status = Status::newGood(); |
257 | 268 | |
258 | | - list( $dstCont, $destRel ) = $this->resolveStoragePath( $params['dst'] ); |
| 269 | + list( $dstCont, $destRel ) = $this->resolveStoragePathReal( $params['dst'] ); |
259 | 270 | if ( $destRel === null ) { |
260 | 271 | $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); |
261 | 272 | return $status; |
— | — | @@ -278,6 +289,7 @@ |
279 | 290 | return $status; |
280 | 291 | } catch ( Exception $e ) { // some other exception? |
281 | 292 | $status->fatal( 'backend-fail-internal' ); |
| 293 | + $this->logException( $e, __METHOD__, $params ); |
282 | 294 | return $status; |
283 | 295 | } |
284 | 296 | |
— | — | @@ -296,6 +308,7 @@ |
297 | 309 | return $status; |
298 | 310 | } catch ( Exception $e ) { // some other exception? |
299 | 311 | $status->fatal( 'backend-fail-internal' ); |
| 312 | + $this->logException( $e, __METHOD__, $params ); |
300 | 313 | return $status; |
301 | 314 | } |
302 | 315 | |
— | — | @@ -309,6 +322,7 @@ |
310 | 323 | $status->fatal( 'backend-fail-connect' ); |
311 | 324 | } catch ( Exception $e ) { // some other exception? |
312 | 325 | $status->fatal( 'backend-fail-internal' ); |
| 326 | + $this->logException( $e, __METHOD__, $params ); |
313 | 327 | } |
314 | 328 | |
315 | 329 | return $status; |
— | — | @@ -317,15 +331,9 @@ |
318 | 332 | /** |
319 | 333 | * @see FileBackend::prepate() |
320 | 334 | */ |
321 | | - function prepare( array $params ) { |
| 335 | + function doPrepare( $fullCont, $dir, array $params ) { |
322 | 336 | $status = Status::newGood(); |
323 | 337 | |
324 | | - list( $dstCont, $destRel ) = $this->resolveStoragePath( $params['dir'] ); |
325 | | - if ( $destRel === null ) { |
326 | | - $status->fatal( 'backend-fail-invalidpath', $params['dir'] ); |
327 | | - return $status; |
328 | | - } |
329 | | - |
330 | 338 | // (a) Get a swift proxy connection |
331 | 339 | $conn = $this->getConnection(); |
332 | 340 | if ( !$conn ) { |
— | — | @@ -335,9 +343,10 @@ |
336 | 344 | |
337 | 345 | // (b) Create the destination container |
338 | 346 | try { |
339 | | - $conn->create_container( $dstCont ); |
| 347 | + $conn->create_container( $fullCont ); |
340 | 348 | } catch ( Exception $e ) { // some other exception? |
341 | 349 | $status->fatal( 'backend-fail-internal' ); |
| 350 | + $this->logException( $e, __METHOD__, $params ); |
342 | 351 | } |
343 | 352 | |
344 | 353 | return $status; |
— | — | @@ -346,17 +355,17 @@ |
347 | 356 | /** |
348 | 357 | * @see FileBackend::secure() |
349 | 358 | */ |
350 | | - function secure( array $params ) { |
| 359 | + function doSecure( $fullCont, $dir, array $params ) { |
351 | 360 | $status = Status::newGood(); |
352 | 361 | // @TODO: restrict container from $this->swiftProxyUser |
353 | 362 | return $status; // badgers? We don't need no steenking badgers! |
354 | 363 | } |
355 | 364 | |
356 | 365 | /** |
357 | | - * @see FileBackend::fileExists() |
| 366 | + * @see FileBackend::doFileExists() |
358 | 367 | */ |
359 | | - function fileExists( array $params ) { |
360 | | - list( $srcCont, $srcRel ) = $this->resolveStoragePath( $params['src'] ); |
| 368 | + function doFileExists( array $params ) { |
| 369 | + list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); |
361 | 370 | if ( $srcRel === null ) { |
362 | 371 | return false; // invalid storage path |
363 | 372 | } |
— | — | @@ -376,16 +385,17 @@ |
377 | 386 | $exists = false; |
378 | 387 | } catch ( Exception $e ) { // some other exception? |
379 | 388 | $exists = false; // fail vs not exists? |
| 389 | + $this->logException( $e, __METHOD__, $params ); |
380 | 390 | } |
381 | 391 | |
382 | 392 | return $exists; |
383 | 393 | } |
384 | 394 | |
385 | 395 | /** |
386 | | - * @see FileBackend::getFileTimestamp() |
| 396 | + * @see FileBackend::doGetFileTimestamp() |
387 | 397 | */ |
388 | | - function getFileTimestamp( array $params ) { |
389 | | - list( $srcCont, $srcRel ) = $this->resolveStoragePath( $params['src'] ); |
| 398 | + function doGetFileTimestamp( array $params ) { |
| 399 | + list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); |
390 | 400 | if ( $srcRel === null ) { |
391 | 401 | return false; // invalid storage path |
392 | 402 | } |
— | — | @@ -405,6 +415,7 @@ |
406 | 416 | $obj = NULL; |
407 | 417 | } catch ( Exception $e ) { // some other exception? |
408 | 418 | $obj = NULL; // fail vs not exists? |
| 419 | + $this->logException( $e, __METHOD__, $params ); |
409 | 420 | } |
410 | 421 | |
411 | 422 | if ( $obj ) { |
— | — | @@ -417,32 +428,70 @@ |
418 | 429 | } |
419 | 430 | |
420 | 431 | /** |
421 | | - * @see FileBackend::getFileList() |
| 432 | + * @see FileBackendBase::getFileContents() |
422 | 433 | */ |
423 | | - function getFileList( array $params ) { |
424 | | - list( $dirc, $dir ) = $this->resolveStoragePath( $params['dir'] ); |
425 | | - if ( $dir === null ) { // invalid storage path |
426 | | - return array(); // empty result |
| 434 | + public function getFileContents( array $params ) { |
| 435 | + list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); |
| 436 | + if ( $srcRel === null ) { |
| 437 | + return false; // invalid storage path |
427 | 438 | } |
428 | 439 | |
429 | 440 | $conn = $this->getConnection(); |
430 | 441 | if ( !$conn ) { |
| 442 | + $status->fatal( 'backend-fail-connect' ); |
| 443 | + return $status; |
| 444 | + } |
| 445 | + |
| 446 | + try { |
| 447 | + $container = $conn->get_container( $srcCont); |
| 448 | + $obj = $container->get_object( $srcRel ); |
| 449 | + $data = $obj->read(); |
| 450 | + } catch ( NoSuchContainerException $e ) { |
| 451 | + $data = false; |
| 452 | + } catch ( NoSuchObjectException $e ) { |
| 453 | + $data = false; |
| 454 | + } catch ( Exception $e ) { // some other exception? |
| 455 | + $data = false; // fail vs not exists? |
| 456 | + $this->logException( $e, __METHOD__, $params ); |
| 457 | + } |
| 458 | + |
| 459 | + return $data; |
| 460 | + } |
| 461 | + |
| 462 | + /** |
| 463 | + * @see FileBackend::getFileListInternal() |
| 464 | + */ |
| 465 | + function getFileListInternal( $fullCont, $dir, array $params ) { |
| 466 | + return new SwiftFileIterator( $this, $fullCont, $dir ); |
| 467 | + } |
| 468 | + |
| 469 | + /** |
| 470 | + * Do not call this function outside of SwiftFileIterator |
| 471 | + * |
| 472 | + * @param $fullCont string Resolved container name |
| 473 | + * @param $dir string Resolved storage directory |
| 474 | + * @param $after string Storage path of file to list items after |
| 475 | + * @param $limit integer Max number of items to list |
| 476 | + * @return Array |
| 477 | + */ |
| 478 | + public function getFileListPageInternal( $fullCont, $dir, $after, $limit ) { |
| 479 | + $conn = $this->getConnection(); |
| 480 | + if ( !$conn ) { |
431 | 481 | return null; |
432 | 482 | } |
433 | 483 | |
434 | | - // @TODO: return an Iterator class that pages via list_objects() |
435 | 484 | try { |
436 | | - $container = $conn->get_container( $dirc ); |
437 | | - $files = $container->list_objects( 0, NULL, $dir ); |
| 485 | + $container = $conn->get_container( $fullCont ); |
| 486 | + $files = $container->list_objects( $limit, $after, $dir ); |
438 | 487 | } catch ( NoSuchContainerException $e ) { |
439 | 488 | $files = array(); |
440 | 489 | } catch ( NoSuchObjectException $e ) { |
441 | 490 | $files = array(); |
442 | 491 | } catch ( Exception $e ) { // some other exception? |
443 | | - $files = null; |
| 492 | + $files = array(); |
| 493 | + $this->logException( $e, __METHOD__, $params ); |
444 | 494 | } |
445 | 495 | |
446 | | - // if there are no files matching the prefix, return empty array |
447 | 496 | return $files; |
448 | 497 | } |
449 | 498 | |
— | — | @@ -450,7 +499,7 @@ |
451 | 500 | * @see FileBackend::getLocalCopy() |
452 | 501 | */ |
453 | 502 | function getLocalCopy( array $params ) { |
454 | | - list( $srcCont, $srcRel ) = $this->resolveStoragePath( $params['src'] ); |
| 503 | + list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); |
455 | 504 | if ( $srcRel === null ) { |
456 | 505 | return null; |
457 | 506 | } |
— | — | @@ -480,6 +529,7 @@ |
481 | 530 | $tmpFile = null; |
482 | 531 | } catch ( Exception $e ) { // some other exception? |
483 | 532 | $tmpFile = null; |
| 533 | + $this->logException( $e, __METHOD__, $params ); |
484 | 534 | } |
485 | 535 | |
486 | 536 | return $tmpFile; |
— | — | @@ -511,49 +561,83 @@ |
512 | 562 | } |
513 | 563 | |
514 | 564 | /** |
515 | | - * Copy a file from one place to another place |
516 | | - * |
517 | | - * @param $srcContainer CF_Container |
518 | | - * @param $srcRel String: relative path to the source file. |
519 | | - * @param $dstContainer CF_Container |
520 | | - * @param $dstRel String: relative path to the destination. |
| 565 | + * Log an unexpected exception for this backend |
| 566 | + * |
| 567 | + * @param $e Exception |
| 568 | + * @param $func string |
| 569 | + * @param $params Array |
| 570 | + * @return void |
521 | 571 | */ |
522 | | - protected function swiftcopy( $srcContainer, $srcRel, $dstContainer, $dstRel ) { |
523 | | - // The destination must exist already. |
524 | | - $obj = $dstContainer->create_object( $dstRel ); |
525 | | - $obj->content_type = 'text/plain'; // overwritten by source object. |
| 572 | + protected function logException( Exception $e, $func, array $params ) { |
| 573 | + wfDebugLog( 'SwiftBackend', |
| 574 | + get_class( $e ) . " in '{$this->name}': '{$func}' with " . serialize( $params ) |
| 575 | + ); |
| 576 | + } |
| 577 | +} |
526 | 578 | |
527 | | - try { |
528 | | - $obj->write( '.' ); |
529 | | - } catch ( SyntaxException $e ) { |
530 | | - throw new MWException( "Write failed: $e" ); |
531 | | - } catch ( BadContentTypeException $e ) { |
532 | | - throw new MWException( "Missing Content-Type: $e" ); |
533 | | - } catch ( MisMatchedChecksumException $e ) { |
534 | | - throw new MWException( __METHOD__ . "should not happen: '$e'" ); |
535 | | - } |
| 579 | +/** |
| 580 | + * SwiftFileBackend helper class to page through object listings. |
| 581 | + * Swift also has a listing limit of 10,000 objects for sanity. |
| 582 | + * |
| 583 | + * @ingroup FileBackend |
| 584 | + */ |
| 585 | +class SwiftFileIterator implements Iterator { |
| 586 | + /** @var Array */ |
| 587 | + protected $bufferIter = array(); |
| 588 | + protected $bufferAfter = ''; // string; list items *after* this path |
| 589 | + protected $pos = 0; // integer |
536 | 590 | |
537 | | - try { |
538 | | - $obj = $dstContainer->get_object( $dstRel ); |
539 | | - } catch ( NoSuchObjectException $e ) { |
540 | | - throw new MWException( 'The object we just created does not exist: ' . |
541 | | - $dstContainer->name . "/$dstRel: $e" ); |
542 | | - } |
| 591 | + /** @var SwiftFileBackend */ |
| 592 | + protected $backend; |
| 593 | + protected $container; // |
| 594 | + protected $dir; // string storage directory |
543 | 595 | |
544 | | - try { |
545 | | - $srcObj = $srcContainer->get_object( $srcRel ); |
546 | | - } catch ( NoSuchObjectException $e ) { |
547 | | - throw new MWException( 'Source file does not exist: ' . |
548 | | - $srcContainer->name . "/$srcRel: $e" ); |
549 | | - } |
| 596 | + const PAGE_SIZE = 5000; // file listing buffer size |
550 | 597 | |
551 | | - try { |
552 | | - $dstContainer->copy_object_from($srcObj,$srcContainer,$dstRel); |
553 | | - } catch ( SyntaxException $e ) { |
554 | | - throw new MWException( 'Source file does not exist: ' . |
555 | | - $srcContainer->name . "/$srcRel: $e" ); |
556 | | - } catch ( MisMatchedChecksumException $e ) { |
557 | | - throw new MWException( "Checksums do not match: $e" ); |
| 598 | + /** |
| 599 | + * Get an FSFileIterator from a file system directory |
| 600 | + * |
| 601 | + * @param $backend SwiftFileBackend |
| 602 | + * @param $fullCont string Resolved container name |
| 603 | + * @param $dir string Resolved relateive path |
| 604 | + */ |
| 605 | + public function __construct( SwiftFileBackend $backend, $fullCont, $dir ) { |
| 606 | + $this->container = $fullCont; |
| 607 | + $this->dir = $dir; |
| 608 | + $this->backend = $backend; |
| 609 | + } |
| 610 | + |
| 611 | + public function current() { |
| 612 | + return current( $this->bufferIter ); |
| 613 | + } |
| 614 | + |
| 615 | + public function key() { |
| 616 | + return $this->pos; |
| 617 | + } |
| 618 | + |
| 619 | + public function next() { |
| 620 | + // Advance to the next file in the page |
| 621 | + next( $this->bufferIter ); |
| 622 | + ++$this->pos; |
| 623 | + // Check if there are no files left in this page and |
| 624 | + // advance to the next page if this page was not empty. |
| 625 | + if ( !$this->valid() && count( $this->bufferIter ) ) { |
| 626 | + $this->bufferAfter = end( $this->bufferIter ); |
| 627 | + $this->bufferIter = $this->backend->getFileListPageInternal( |
| 628 | + $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE |
| 629 | + ); |
558 | 630 | } |
559 | 631 | } |
| 632 | + |
| 633 | + public function rewind() { |
| 634 | + $this->pos = 0; |
| 635 | + $this->bufferAfter = ''; |
| 636 | + $this->bufferIter = $this->backend->getFileListPageInternal( |
| 637 | + $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE |
| 638 | + ); |
| 639 | + } |
| 640 | + |
| 641 | + public function valid() { |
| 642 | + return ( current( $this->bufferIter ) !== false ); // no paths can have this value |
| 643 | + } |
560 | 644 | } |