PageRenderTime 45ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/includes/filerepo/LocalRepo.php

https://bitbucket.org/ghostfreeman/freeside-wiki
PHP | 326 lines | 177 code | 28 blank | 121 comment | 18 complexity | 7b9af29d2571431a5259c05b0404859f MD5 | raw file
Possible License(s): GPL-2.0, Apache-2.0, LGPL-3.0
  1. <?php
  2. /**
  3. * Local repository that stores files in the local filesystem and registers them
  4. * in the wiki's own database.
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along
  17. * with this program; if not, write to the Free Software Foundation, Inc.,
  18. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. * http://www.gnu.org/copyleft/gpl.html
  20. *
  21. * @file
  22. * @ingroup FileRepo
  23. */
  24. /**
  25. * A repository that stores files in the local filesystem and registers them
  26. * in the wiki's own database. This is the most commonly used repository class.
  27. *
  28. * @ingroup FileRepo
  29. */
  30. class LocalRepo extends FileRepo {
  31. var $fileFactory = array( 'LocalFile' , 'newFromTitle' );
  32. var $fileFactoryKey = array( 'LocalFile' , 'newFromKey' );
  33. var $fileFromRowFactory = array( 'LocalFile' , 'newFromRow' );
  34. var $oldFileFactory = array( 'OldLocalFile', 'newFromTitle' );
  35. var $oldFileFactoryKey = array( 'OldLocalFile', 'newFromKey' );
  36. var $oldFileFromRowFactory = array( 'OldLocalFile', 'newFromRow' );
  37. /**
  38. * @throws MWException
  39. * @param $row
  40. * @return LocalFile
  41. */
  42. function newFileFromRow( $row ) {
  43. if ( isset( $row->img_name ) ) {
  44. return call_user_func( $this->fileFromRowFactory, $row, $this );
  45. } elseif ( isset( $row->oi_name ) ) {
  46. return call_user_func( $this->oldFileFromRowFactory, $row, $this );
  47. } else {
  48. throw new MWException( __METHOD__.': invalid row' );
  49. }
  50. }
  51. /**
  52. * @param $title
  53. * @param $archiveName
  54. * @return OldLocalFile
  55. */
  56. function newFromArchiveName( $title, $archiveName ) {
  57. return OldLocalFile::newFromArchiveName( $title, $this, $archiveName );
  58. }
  59. /**
  60. * Delete files in the deleted directory if they are not referenced in the
  61. * filearchive table. This needs to be done in the repo because it needs to
  62. * interleave database locks with file operations, which is potentially a
  63. * remote operation.
  64. *
  65. * @param $storageKeys array
  66. *
  67. * @return FileRepoStatus
  68. */
  69. function cleanupDeletedBatch( array $storageKeys ) {
  70. $backend = $this->backend; // convenience
  71. $root = $this->getZonePath( 'deleted' );
  72. $dbw = $this->getMasterDB();
  73. $status = $this->newGood();
  74. $storageKeys = array_unique( $storageKeys );
  75. foreach ( $storageKeys as $key ) {
  76. $hashPath = $this->getDeletedHashPath( $key );
  77. $path = "$root/$hashPath$key";
  78. $dbw->begin( __METHOD__ );
  79. // Check for usage in deleted/hidden files and pre-emptively
  80. // lock the key to avoid any future use until we are finished.
  81. $deleted = $this->deletedFileHasKey( $key, 'lock' );
  82. $hidden = $this->hiddenFileHasKey( $key, 'lock' );
  83. if ( !$deleted && !$hidden ) { // not in use now
  84. wfDebug( __METHOD__ . ": deleting $key\n" );
  85. $op = array( 'op' => 'delete', 'src' => $path );
  86. if ( !$backend->doOperation( $op )->isOK() ) {
  87. $status->error( 'undelete-cleanup-error', $path );
  88. $status->failCount++;
  89. }
  90. } else {
  91. wfDebug( __METHOD__ . ": $key still in use\n" );
  92. $status->successCount++;
  93. }
  94. $dbw->commit( __METHOD__ );
  95. }
  96. return $status;
  97. }
  98. /**
  99. * Check if a deleted (filearchive) file has this sha1 key
  100. *
  101. * @param $key String File storage key (base-36 sha1 key with file extension)
  102. * @param $lock String|null Use "lock" to lock the row via FOR UPDATE
  103. * @return bool File with this key is in use
  104. */
  105. protected function deletedFileHasKey( $key, $lock = null ) {
  106. $options = ( $lock === 'lock' ) ? array( 'FOR UPDATE' ) : array();
  107. $dbw = $this->getMasterDB();
  108. return (bool)$dbw->selectField( 'filearchive', '1',
  109. array( 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ),
  110. __METHOD__, $options
  111. );
  112. }
  113. /**
  114. * Check if a hidden (revision delete) file has this sha1 key
  115. *
  116. * @param $key String File storage key (base-36 sha1 key with file extension)
  117. * @param $lock String|null Use "lock" to lock the row via FOR UPDATE
  118. * @return bool File with this key is in use
  119. */
  120. protected function hiddenFileHasKey( $key, $lock = null ) {
  121. $options = ( $lock === 'lock' ) ? array( 'FOR UPDATE' ) : array();
  122. $sha1 = self::getHashFromKey( $key );
  123. $ext = File::normalizeExtension( substr( $key, strcspn( $key, '.' ) + 1 ) );
  124. $dbw = $this->getMasterDB();
  125. return (bool)$dbw->selectField( 'oldimage', '1',
  126. array( 'oi_sha1' => $sha1,
  127. 'oi_archive_name ' . $dbw->buildLike( $dbw->anyString(), ".$ext" ),
  128. $dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE ),
  129. __METHOD__, $options
  130. );
  131. }
  132. /**
  133. * Gets the SHA1 hash from a storage key
  134. *
  135. * @param string $key
  136. * @return string
  137. */
  138. public static function getHashFromKey( $key ) {
  139. return strtok( $key, '.' );
  140. }
  141. /**
  142. * Checks if there is a redirect named as $title
  143. *
  144. * @param $title Title of file
  145. * @return bool
  146. */
  147. function checkRedirect( Title $title ) {
  148. global $wgMemc;
  149. $title = File::normalizeTitle( $title, 'exception' );
  150. $memcKey = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
  151. if ( $memcKey === false ) {
  152. $memcKey = $this->getLocalCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
  153. $expiry = 300; // no invalidation, 5 minutes
  154. } else {
  155. $expiry = 86400; // has invalidation, 1 day
  156. }
  157. $cachedValue = $wgMemc->get( $memcKey );
  158. if ( $cachedValue === ' ' || $cachedValue === '' ) {
  159. // Does not exist
  160. return false;
  161. } elseif ( strval( $cachedValue ) !== '' ) {
  162. return Title::newFromText( $cachedValue, NS_FILE );
  163. } // else $cachedValue is false or null: cache miss
  164. $id = $this->getArticleID( $title );
  165. if( !$id ) {
  166. $wgMemc->set( $memcKey, " ", $expiry );
  167. return false;
  168. }
  169. $dbr = $this->getSlaveDB();
  170. $row = $dbr->selectRow(
  171. 'redirect',
  172. array( 'rd_title', 'rd_namespace' ),
  173. array( 'rd_from' => $id ),
  174. __METHOD__
  175. );
  176. if( $row && $row->rd_namespace == NS_FILE ) {
  177. $targetTitle = Title::makeTitle( $row->rd_namespace, $row->rd_title );
  178. $wgMemc->set( $memcKey, $targetTitle->getDBkey(), $expiry );
  179. return $targetTitle;
  180. } else {
  181. $wgMemc->set( $memcKey, '', $expiry );
  182. return false;
  183. }
  184. }
  185. /**
  186. * Function link Title::getArticleID().
  187. * We can't say Title object, what database it should use, so we duplicate that function here.
  188. *
  189. * @param $title Title
  190. * @return bool|int|mixed
  191. */
  192. protected function getArticleID( $title ) {
  193. if( !$title instanceof Title ) {
  194. return 0;
  195. }
  196. $dbr = $this->getSlaveDB();
  197. $id = $dbr->selectField(
  198. 'page', // Table
  199. 'page_id', //Field
  200. array( //Conditions
  201. 'page_namespace' => $title->getNamespace(),
  202. 'page_title' => $title->getDBkey(),
  203. ),
  204. __METHOD__ //Function name
  205. );
  206. return $id;
  207. }
  208. /**
  209. * Get an array or iterator of file objects for files that have a given
  210. * SHA-1 content hash.
  211. *
  212. * @param $hash String a sha1 hash to look for
  213. * @return Array
  214. */
  215. function findBySha1( $hash ) {
  216. $dbr = $this->getSlaveDB();
  217. $res = $dbr->select(
  218. 'image',
  219. LocalFile::selectFields(),
  220. array( 'img_sha1' => $hash ),
  221. __METHOD__,
  222. array( 'ORDER BY' => 'img_name' )
  223. );
  224. $result = array();
  225. foreach ( $res as $row ) {
  226. $result[] = $this->newFileFromRow( $row );
  227. }
  228. $res->free();
  229. return $result;
  230. }
  231. /**
  232. * Get an array of arrays or iterators of file objects for files that
  233. * have the given SHA-1 content hashes.
  234. *
  235. * Overrides generic implementation in FileRepo for performance reason
  236. *
  237. * @param $hashes array An array of hashes
  238. * @return array An Array of arrays or iterators of file objects and the hash as key
  239. */
  240. function findBySha1s( array $hashes ) {
  241. if( !count( $hashes ) ) {
  242. return array(); //empty parameter
  243. }
  244. $dbr = $this->getSlaveDB();
  245. $res = $dbr->select(
  246. 'image',
  247. LocalFile::selectFields(),
  248. array( 'img_sha1' => $hashes ),
  249. __METHOD__,
  250. array( 'ORDER BY' => 'img_name' )
  251. );
  252. $result = array();
  253. foreach ( $res as $row ) {
  254. $file = $this->newFileFromRow( $row );
  255. $result[$file->getSha1()][] = $file;
  256. }
  257. $res->free();
  258. return $result;
  259. }
  260. /**
  261. * Get a connection to the slave DB
  262. * @return DatabaseBase
  263. */
  264. function getSlaveDB() {
  265. return wfGetDB( DB_SLAVE );
  266. }
  267. /**
  268. * Get a connection to the master DB
  269. * @return DatabaseBase
  270. */
  271. function getMasterDB() {
  272. return wfGetDB( DB_MASTER );
  273. }
  274. /**
  275. * Get a key on the primary cache for this repository.
  276. * Returns false if the repository's cache is not accessible at this site.
  277. * The parameters are the parts of the key, as for wfMemcKey().
  278. *
  279. * @return string
  280. */
  281. function getSharedCacheKey( /*...*/ ) {
  282. $args = func_get_args();
  283. return call_user_func_array( 'wfMemcKey', $args );
  284. }
  285. /**
  286. * Invalidates image redirect cache related to that image
  287. *
  288. * @param $title Title of page
  289. * @return void
  290. */
  291. function invalidateImageRedirect( Title $title ) {
  292. global $wgMemc;
  293. $memcKey = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
  294. if ( $memcKey ) {
  295. $wgMemc->delete( $memcKey );
  296. }
  297. }
  298. }