LocalRepo.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. <?php
  2. /**
  3. * A repository that stores files in the local filesystem and registers them
  4. * in the wiki's own database. This is the most commonly used repository class.
  5. * @ingroup FileRepo
  6. */
  7. class LocalRepo extends FSRepo {
  8. var $fileFactory = array( 'LocalFile', 'newFromTitle' );
  9. var $oldFileFactory = array( 'OldLocalFile', 'newFromTitle' );
  10. var $fileFromRowFactory = array( 'LocalFile', 'newFromRow' );
  11. var $oldFileFromRowFactory = array( 'OldLocalFile', 'newFromRow' );
  12. function newFileFromRow( $row ) {
  13. if ( isset( $row->img_name ) ) {
  14. return call_user_func( $this->fileFromRowFactory, $row, $this );
  15. } elseif ( isset( $row->oi_name ) ) {
  16. return call_user_func( $this->oldFileFromRowFactory, $row, $this );
  17. } else {
  18. throw new MWException( __METHOD__.': invalid row' );
  19. }
  20. }
  21. function newFromArchiveName( $title, $archiveName ) {
  22. return OldLocalFile::newFromArchiveName( $title, $this, $archiveName );
  23. }
  24. /**
  25. * Delete files in the deleted directory if they are not referenced in the
  26. * filearchive table. This needs to be done in the repo because it needs to
  27. * interleave database locks with file operations, which is potentially a
  28. * remote operation.
  29. * @return FileRepoStatus
  30. */
  31. function cleanupDeletedBatch( $storageKeys ) {
  32. $root = $this->getZonePath( 'deleted' );
  33. $dbw = $this->getMasterDB();
  34. $status = $this->newGood();
  35. $storageKeys = array_unique($storageKeys);
  36. foreach ( $storageKeys as $key ) {
  37. $hashPath = $this->getDeletedHashPath( $key );
  38. $path = "$root/$hashPath$key";
  39. $dbw->begin();
  40. $inuse = $dbw->selectField( 'filearchive', '1',
  41. array( 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ),
  42. __METHOD__, array( 'FOR UPDATE' ) );
  43. if( !$inuse ) {
  44. $sha1 = substr( $key, 0, strcspn( $key, '.' ) );
  45. $ext = substr( $key, strcspn($key,'.') + 1 );
  46. $ext = File::normalizeExtension($ext);
  47. $inuse = $dbw->selectField( 'oldimage', '1',
  48. array( 'oi_sha1' => $sha1,
  49. "oi_archive_name LIKE '%.{$ext}'",
  50. 'oi_deleted & '.File::DELETED_FILE => File::DELETED_FILE ),
  51. __METHOD__, array( 'FOR UPDATE' ) );
  52. }
  53. if ( !$inuse ) {
  54. wfDebug( __METHOD__ . ": deleting $key\n" );
  55. if ( !@unlink( $path ) ) {
  56. $status->error( 'undelete-cleanup-error', $path );
  57. $status->failCount++;
  58. }
  59. } else {
  60. wfDebug( __METHOD__ . ": $key still in use\n" );
  61. $status->successCount++;
  62. }
  63. $dbw->commit();
  64. }
  65. return $status;
  66. }
  67. /**
  68. * Checks if there is a redirect named as $title
  69. *
  70. * @param Title $title Title of image
  71. */
  72. function checkRedirect( $title ) {
  73. global $wgMemc;
  74. if( is_string( $title ) ) {
  75. $title = Title::newFromTitle( $title );
  76. }
  77. if( $title instanceof Title && $title->getNamespace() == NS_MEDIA ) {
  78. $title = Title::makeTitle( NS_FILE, $title->getText() );
  79. }
  80. $memcKey = $this->getMemcKey( "image_redirect:" . md5( $title->getPrefixedDBkey() ) );
  81. $cachedValue = $wgMemc->get( $memcKey );
  82. if( $cachedValue ) {
  83. return Title::newFromDbKey( $cachedValue );
  84. } elseif( $cachedValue == ' ' ) { # FIXME: ugly hack, but BagOStuff caching seems to be weird and return false if !cachedValue, not only if it doesn't exist
  85. return false;
  86. }
  87. $id = $this->getArticleID( $title );
  88. if( !$id ) {
  89. $wgMemc->set( $memcKey, " ", 9000 );
  90. return false;
  91. }
  92. $dbr = $this->getSlaveDB();
  93. $row = $dbr->selectRow(
  94. 'redirect',
  95. array( 'rd_title', 'rd_namespace' ),
  96. array( 'rd_from' => $id ),
  97. __METHOD__
  98. );
  99. if( $row ) $targetTitle = Title::makeTitle( $row->rd_namespace, $row->rd_title );
  100. $wgMemc->set( $memcKey, ($row ? $targetTitle->getPrefixedDBkey() : " "), 9000 );
  101. if( !$row ) {
  102. return false;
  103. }
  104. return $targetTitle;
  105. }
  106. /**
  107. * Function link Title::getArticleID().
  108. * We can't say Title object, what database it should use, so we duplicate that function here.
  109. */
  110. protected function getArticleID( $title ) {
  111. if( !$title instanceof Title ) {
  112. return 0;
  113. }
  114. $dbr = $this->getSlaveDB();
  115. $id = $dbr->selectField(
  116. 'page', // Table
  117. 'page_id', //Field
  118. array( //Conditions
  119. 'page_namespace' => $title->getNamespace(),
  120. 'page_title' => $title->getDBKey(),
  121. ),
  122. __METHOD__ //Function name
  123. );
  124. return $id;
  125. }
  126. function findBySha1( $hash ) {
  127. $dbr = $this->getSlaveDB();
  128. $res = $dbr->select(
  129. 'image',
  130. LocalFile::selectFields(),
  131. array( 'img_sha1' => $hash )
  132. );
  133. $result = array();
  134. while ( $row = $res->fetchObject() )
  135. $result[] = $this->newFileFromRow( $row );
  136. $res->free();
  137. return $result;
  138. }
  139. /*
  140. * Find many files using one query
  141. */
  142. function findFiles( $titles ) {
  143. // FIXME: Only accepts a $titles array where the keys are the sanitized
  144. // file names.
  145. if ( count( $titles ) == 0 ) return array();
  146. $dbr = $this->getSlaveDB();
  147. $res = $dbr->select(
  148. 'image',
  149. LocalFile::selectFields(),
  150. array( 'img_name' => array_keys( $titles ) )
  151. );
  152. $result = array();
  153. while ( $row = $res->fetchObject() ) {
  154. $result[$row->img_name] = $this->newFileFromRow( $row );
  155. }
  156. $res->free();
  157. return $result;
  158. }
  159. }