FileCache.php 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. <?php
  2. /**
  3. * Cache of file objects, wrapping some RepoGroup functions to avoid redundant
  4. * queries. Loosely inspired by the LinkCache / LinkBatch classes for titles.
  5. *
  6. * ISSUE: Merge with RepoGroup?
  7. *
  8. * @ingroup FileRepo
  9. */
  10. class FileCache {
  11. var $repoGroup;
  12. var $cache = array(), $notFound = array();
  13. protected static $instance;
  14. /**
  15. * Get a FileCache instance. Typically, only one instance of FileCache
  16. * is needed in a MediaWiki invocation.
  17. */
  18. static function singleton() {
  19. if ( self::$instance ) {
  20. return self::$instance;
  21. }
  22. self::$instance = new FileCache( RepoGroup::singleton() );
  23. return self::$instance;
  24. }
  25. /**
  26. * Destroy the singleton instance, so that a new one will be created next
  27. * time singleton() is called.
  28. */
  29. static function destroySingleton() {
  30. self::$instance = null;
  31. }
  32. /**
  33. * Set the singleton instance to a given object
  34. */
  35. static function setSingleton( $instance ) {
  36. self::$instance = $instance;
  37. }
  38. /**
  39. * Construct a group of file repositories.
  40. * @param RepoGroup $repoGroup
  41. */
  42. function __construct( $repoGroup ) {
  43. $this->repoGroup = $repoGroup;
  44. }
  45. /**
  46. * Add some files to the cache. This is a fairly low-level function,
  47. * which most users should not need to call. Note that any existing
  48. * entries for the same keys will not be replaced. Call clearFiles()
  49. * first if you need that.
  50. * @param array $files array of File objects, indexed by DB key
  51. */
  52. function addFiles( $files ) {
  53. wfDebug( "FileCache adding ".count( $files )." files\n" );
  54. $this->cache += $files;
  55. }
  56. /**
  57. * Remove some files from the cache, so that their existence will be
  58. * rechecked. This is a fairly low-level function, which most users
  59. * should not need to call.
  60. * @param array $remove array indexed by DB keys to remove (the values are ignored)
  61. */
  62. function clearFiles( $remove ) {
  63. wfDebug( "FileCache clearing data for ".count( $remove )." files\n" );
  64. $this->cache = array_diff_keys( $this->cache, $remove );
  65. $this->notFound = array_diff_keys( $this->notFound, $remove );
  66. }
  67. /**
  68. * Mark some DB keys as nonexistent. This is a fairly low-level
  69. * function, which most users should not need to call.
  70. * @param array $dbkeys array of DB keys
  71. */
  72. function markNotFound( $dbkeys ) {
  73. wfDebug( "FileCache marking ".count( $dbkeys )." files as not found\n" );
  74. $this->notFound += array_fill_keys( $dbkeys, true );
  75. }
  76. /**
  77. * Search the cache for a file.
  78. * @param mixed $title Title object or string
  79. * @return File object or false if it is not found
  80. * @todo Implement searching for old file versions(?)
  81. */
  82. function findFile( $title ) {
  83. if( !( $title instanceof Title ) ) {
  84. $title = Title::makeTitleSafe( NS_FILE, $title );
  85. }
  86. if( !$title ) {
  87. return false; // invalid title?
  88. }
  89. $dbkey = $title->getDBkey();
  90. if( array_key_exists( $dbkey, $this->cache ) ) {
  91. wfDebug( "FileCache HIT for $dbkey\n" );
  92. return $this->cache[$dbkey];
  93. }
  94. if( array_key_exists( $dbkey, $this->notFound ) ) {
  95. wfDebug( "FileCache negative HIT for $dbkey\n" );
  96. return false;
  97. }
  98. // Not in cache, fall back to a direct query
  99. $file = $this->repoGroup->findFile( $title );
  100. if( $file ) {
  101. wfDebug( "FileCache MISS for $dbkey\n" );
  102. $this->cache[$dbkey] = $file;
  103. } else {
  104. wfDebug( "FileCache negative MISS for $dbkey\n" );
  105. $this->notFound[$dbkey] = true;
  106. }
  107. return $file;
  108. }
  109. /**
  110. * Search the cache for multiple files.
  111. * @param array $titles Title objects or strings to search for
  112. * @return array of File objects, indexed by DB key
  113. */
  114. function findFiles( $titles ) {
  115. $titleObjs = array();
  116. foreach ( $titles as $title ) {
  117. if ( !( $title instanceof Title ) ) {
  118. $title = Title::makeTitleSafe( NS_FILE, $title );
  119. }
  120. if ( $title ) {
  121. $titleObjs[$title->getDBkey()] = $title;
  122. }
  123. }
  124. $result = array_intersect_key( $this->cache, $titleObjs );
  125. $unsure = array_diff_key( $titleObjs, $result, $this->notFound );
  126. if( $unsure ) {
  127. wfDebug( "FileCache MISS for ".count( $unsure )." files out of ".count( $titleObjs )."...\n" );
  128. // XXX: We assume the array returned by findFiles() is
  129. // indexed by DBkey; this appears to be true, but should
  130. // be explicitly documented.
  131. $found = $this->repoGroup->findFiles( $unsure );
  132. $result += $found;
  133. $this->addFiles( $found );
  134. $this->markNotFound( array_keys( array_diff_key( $unsure, $found ) ) );
  135. }
  136. wfDebug( "FileCache found ".count( $result )." files out of ".count( $titleObjs )."\n" );
  137. return $result;
  138. }
  139. }