Index: trunk/phase3/includes/GlobalFunctions.php |
— | — | @@ -2719,10 +2719,15 @@ |
2720 | 2720 | * current version. An image object will be returned which |
2721 | 2721 | * was created at the specified time. |
2722 | 2722 | * @param mixed $flags FileRepo::FIND_ flags |
| 2723 | + * @param boolean $bypass Bypass the file cache even if it could be used |
2723 | 2724 | * @return File, or false if the file does not exist |
2724 | 2725 | */ |
2725 | | -function wfFindFile( $title, $time = false, $flags = 0 ) { |
2726 | | - return RepoGroup::singleton()->findFile( $title, $time, $flags ); |
| 2726 | +function wfFindFile( $title, $time = false, $flags = 0, $bypass = false ) { |
| 2727 | + if( !$time && !$flags && !$bypass ) { |
| 2728 | + return FileCache::singleton()->findFile( $title ); |
| 2729 | + } else { |
| 2730 | + return RepoGroup::singleton()->findFile( $title, $time, $flags ); |
| 2731 | + } |
2727 | 2732 | } |
2728 | 2733 | |
2729 | 2734 | /** |
Index: trunk/phase3/includes/filerepo/FileCache.php |
— | — | @@ -0,0 +1,156 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * Cache of file objects, wrapping some RepoGroup functions to avoid redundant |
| 5 | + * queries. Loosely inspired by the LinkCache / LinkBatch classes for titles. |
| 6 | + * |
| 7 | + * ISSUE: Merge with RepoGroup? |
| 8 | + * |
| 9 | + * @ingroup FileRepo |
| 10 | + */ |
| 11 | +class FileCache { |
| 12 | + var $repoGroup; |
| 13 | + var $cache = array(), $notFound = array(); |
| 14 | + |
| 15 | + protected static $instance; |
| 16 | + |
| 17 | + /** |
| 18 | + * Get a FileCache instance. Typically, only one instance of FileCache |
| 19 | + * is needed in a MediaWiki invocation. |
| 20 | + */ |
| 21 | + static function singleton() { |
| 22 | + if ( self::$instance ) { |
| 23 | + return self::$instance; |
| 24 | + } |
| 25 | + self::$instance = new FileCache( RepoGroup::singleton() ); |
| 26 | + return self::$instance; |
| 27 | + } |
| 28 | + |
| 29 | + /** |
| 30 | + * Destroy the singleton instance, so that a new one will be created next |
| 31 | + * time singleton() is called. |
| 32 | + */ |
| 33 | + static function destroySingleton() { |
| 34 | + self::$instance = null; |
| 35 | + } |
| 36 | + |
| 37 | + /** |
| 38 | + * Set the singleton instance to a given object |
| 39 | + */ |
| 40 | + static function setSingleton( $instance ) { |
| 41 | + self::$instance = $instance; |
| 42 | + } |
| 43 | + |
| 44 | + /** |
| 45 | + * Construct a group of file repositories. |
| 46 | + * @param RepoGroup $repoGroup |
| 47 | + */ |
| 48 | + function __construct( $repoGroup ) { |
| 49 | + $this->repoGroup = $repoGroup; |
| 50 | + } |
| 51 | + |
| 52 | + |
| 53 | + /** |
| 54 | + * Add some files to the cache. This is a fairly low-level function, |
| 55 | + * which most users should not need to call. Note that any existing |
| 56 | + * entries for the same keys will not be replaced. Call clearFiles() |
| 57 | + * first if you need that. |
| 58 | + * @param array $files array of File objects, indexed by DB key |
| 59 | + */ |
| 60 | + function addFiles( $files ) { |
| 61 | + wfDebug( "FileCache adding ".count( $files )." files\n" ); |
| 62 | + $this->cache += $files; |
| 63 | + } |
| 64 | + |
| 65 | + /** |
| 66 | + * Remove some files from the cache, so that their existence will be |
| 67 | + * rechecked. This is a fairly low-level function, which most users |
| 68 | + * should not need to call. |
| 69 | + * @param array $remove array indexed by DB keys to remove (the values are ignored) |
| 70 | + */ |
| 71 | + function clearFiles( $remove ) { |
| 72 | + wfDebug( "FileCache clearing data for ".count( $remove )." files\n" ); |
| 73 | + $this->cache = array_diff_keys( $this->cache, $remove ); |
| 74 | + $this->notFound = array_diff_keys( $this->notFound, $remove ); |
| 75 | + } |
| 76 | + |
| 77 | + /** |
| 78 | + * Mark some DB keys as nonexistent. This is a fairly low-level |
| 79 | + * function, which most users should not need to call. |
| 80 | + * @param array $dbkeys array of DB keys |
| 81 | + */ |
| 82 | + function markNotFound( $dbkeys ) { |
| 83 | + wfDebug( "FileCache marking ".count( $dbkeys )." files as not found\n" ); |
| 84 | + $this->notFound += array_fill_keys( $dbkeys, true ); |
| 85 | + } |
| 86 | + |
| 87 | + |
| 88 | + /** |
| 89 | + * Search the cache for a file. |
| 90 | + * @param mixed $title Title object or string |
| 91 | + * @return File object or false if it is not found |
| 92 | + * @todo Implement searching for old file versions(?) |
| 93 | + */ |
| 94 | + function findFile( $title ) { |
| 95 | + if( !( $title instanceof Title ) ) { |
| 96 | + $title = Title::makeTitleSafe( NS_FILE, $title ); |
| 97 | + } |
| 98 | + if( !$title ) { |
| 99 | + return false; // invalid title? |
| 100 | + } |
| 101 | + |
| 102 | + $dbkey = $title->getDBkey(); |
| 103 | + if( array_key_exists( $dbkey, $this->cache ) ) { |
| 104 | + wfDebug( "FileCache HIT for $dbkey\n" ); |
| 105 | + return $this->cache[$dbkey]; |
| 106 | + } |
| 107 | + if( array_key_exists( $dbkey, $this->notFound ) ) { |
| 108 | + wfDebug( "FileCache negative HIT for $dbkey\n" ); |
| 109 | + return false; |
| 110 | + } |
| 111 | + |
| 112 | + // Not in cache, fall back to a direct query |
| 113 | + $file = $this->repoGroup->findFile( $title ); |
| 114 | + if( $file ) { |
| 115 | + wfDebug( "FileCache MISS for $dbkey\n" ); |
| 116 | + $this->cache[$dbkey] = $file; |
| 117 | + } else { |
| 118 | + wfDebug( "FileCache negative MISS for $dbkey\n" ); |
| 119 | + $this->notFound[$dbkey] = true; |
| 120 | + } |
| 121 | + return $file; |
| 122 | + } |
| 123 | + |
| 124 | + /** |
| 125 | + * Search the cache for multiple files. |
| 126 | + * @param array $titles Title objects or strings to search for |
| 127 | + * @return array of File objects, indexed by DB key |
| 128 | + */ |
| 129 | + function findFiles( $titles ) { |
| 130 | + $titleObjs = array(); |
| 131 | + foreach ( $titles as $title ) { |
| 132 | + if ( !( $title instanceof Title ) ) { |
| 133 | + $title = Title::makeTitleSafe( NS_FILE, $title ); |
| 134 | + } |
| 135 | + if ( $title ) { |
| 136 | + $titleObjs[$title->getDBkey()] = $title; |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + $result = array_intersect_key( $this->cache, $titleObjs ); |
| 141 | + |
| 142 | + $unsure = array_diff_key( $titleObjs, $result, $this->notFound ); |
| 143 | + if( $unsure ) { |
| 144 | + wfDebug( "FileCache MISS for ".count( $unsure )." files out of ".count( $titleObjs )."...\n" ); |
| 145 | + // XXX: We assume the array returned by findFiles() is |
| 146 | + // indexed by DBkey; this appears to be true, but should |
| 147 | + // be explicitly documented. |
| 148 | + $found = $this->repoGroup->findFiles( $unsure ); |
| 149 | + $result += $found; |
| 150 | + $this->addFiles( $found ); |
| 151 | + $this->markNotFound( array_keys( array_diff_key( $unsure, $found ) ) ); |
| 152 | + } |
| 153 | + |
| 154 | + wfDebug( "FileCache found ".count( $result )." files out of ".count( $titleObjs )."\n" ); |
| 155 | + return $result; |
| 156 | + } |
| 157 | +} |
Index: trunk/phase3/includes/AutoLoader.php |
— | — | @@ -365,6 +365,7 @@ |
366 | 366 | # includes/filerepo |
367 | 367 | 'ArchivedFile' => 'includes/filerepo/ArchivedFile.php', |
368 | 368 | 'File' => 'includes/filerepo/File.php', |
| 369 | + 'FileCache' => 'includes/filerepo/FileCache.php', |
369 | 370 | 'FileRepo' => 'includes/filerepo/FileRepo.php', |
370 | 371 | 'FileRepoStatus' => 'includes/filerepo/FileRepoStatus.php', |
371 | 372 | 'ForeignAPIFile' => 'includes/filerepo/ForeignAPIFile.php', |
Index: trunk/phase3/RELEASE-NOTES |
— | — | @@ -229,6 +229,7 @@ |
230 | 230 | your language) instead of Wikipedia. |
231 | 231 | * (bug 16635) The "view and edit watchlist" page (Special:Watchlist/edit) now |
232 | 232 | includes a table of contents |
| 233 | +* File objects returned by wfFindFile() are now cached by default |
233 | 234 | |
234 | 235 | === Bug fixes in 1.14 === |
235 | 236 | |