summaryrefslogtreecommitdiff
path: root/includes/filerepo/FileCache.php
blob: 7840d1a3b4935bd4329323e8ee56a7209a321851 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
<?php
/**
 * Cache of file objects, wrapping some RepoGroup functions to avoid redundant
 * queries.  Loosely inspired by the LinkCache / LinkBatch classes for titles.
 *
 * ISSUE: Merge with RepoGroup?
 *
 * @ingroup FileRepo
 */
class FileCache {
	var $repoGroup;
	var $cache = array(), $notFound = array();

	protected static $instance;

	/**
	 * Get a FileCache instance.  Typically, only one instance of FileCache
	 * is needed in a MediaWiki invocation.
	 */
	static function singleton() {
		if ( self::$instance ) {
			return self::$instance;
		}
		self::$instance = new FileCache( RepoGroup::singleton() );
		return self::$instance;
	}

	/**
	 * Destroy the singleton instance, so that a new one will be created next
	 * time singleton() is called.
	 */
	static function destroySingleton() {
		self::$instance = null;
	}

	/**
	 * Set the singleton instance to a given object
	 */
	static function setSingleton( $instance ) {
		self::$instance = $instance;
	}

	/**
	 * Construct a group of file repositories.
	 * @param RepoGroup $repoGroup
	 */
	function __construct( $repoGroup ) {
		$this->repoGroup = $repoGroup;
	}


	/**
	 * Add some files to the cache.  This is a fairly low-level function,
	 * which most users should not need to call.  Note that any existing
	 * entries for the same keys will not be replaced.  Call clearFiles()
	 * first if you need that.
	 * @param array $files array of File objects, indexed by DB key
	 */
	function addFiles( $files ) {
		wfDebug( "FileCache adding ".count( $files )." files\n" );
		$this->cache += $files;
	}

	/**
	 * Remove some files from the cache, so that their existence will be
	 * rechecked.  This is a fairly low-level function, which most users
	 * should not need to call.
	 * @param array $remove array indexed by DB keys to remove (the values are ignored)
	 */
	function clearFiles( $remove ) {
		wfDebug( "FileCache clearing data for ".count( $remove )." files\n" );
		$this->cache = array_diff_keys( $this->cache, $remove );
		$this->notFound = array_diff_keys( $this->notFound, $remove );
	}

	/**
	 * Mark some DB keys as nonexistent.  This is a fairly low-level
	 * function, which most users should not need to call.
	 * @param array $dbkeys array of DB keys
	 */
	function markNotFound( $dbkeys ) {
		wfDebug( "FileCache marking ".count( $dbkeys )." files as not found\n" );
		$this->notFound += array_fill_keys( $dbkeys, true );
	}


	/**
	 * Search the cache for a file.
	 * @param mixed $title Title object or string
	 * @return File object or false if it is not found
	 * @todo Implement searching for old file versions(?)
	 */
	function findFile( $title ) {
		if( !( $title instanceof Title ) ) {
			$title = Title::makeTitleSafe( NS_FILE, $title );
		}
		if( !$title ) {
			return false;  // invalid title?
		}

		$dbkey = $title->getDBkey();
		if( array_key_exists( $dbkey, $this->cache ) ) {
			wfDebug( "FileCache HIT for $dbkey\n" );
			return $this->cache[$dbkey];
		}
		if( array_key_exists( $dbkey, $this->notFound ) ) {
			wfDebug( "FileCache negative HIT for $dbkey\n" );
			return false;
		}

		// Not in cache, fall back to a direct query
		$file = $this->repoGroup->findFile( $title );
		if( $file ) {
			wfDebug( "FileCache MISS for $dbkey\n" );
			$this->cache[$dbkey] = $file;
		} else {
			wfDebug( "FileCache negative MISS for $dbkey\n" );
			$this->notFound[$dbkey] = true;
		}
		return $file;
	}

	/**
	 * Search the cache for multiple files.
	 * @param array $titles Title objects or strings to search for
	 * @return array of File objects, indexed by DB key
	 */
	function findFiles( $titles ) {
		$titleObjs = array();
		foreach ( $titles as $title ) {
			if ( !( $title instanceof Title ) ) {
				$title = Title::makeTitleSafe( NS_FILE, $title );
			}
			if ( $title ) {
				$titleObjs[$title->getDBkey()] = $title;
			}
		}

		$result = array_intersect_key( $this->cache, $titleObjs );

		$unsure = array_diff_key( $titleObjs, $result, $this->notFound );
		if( $unsure ) {
			wfDebug( "FileCache MISS for ".count( $unsure )." files out of ".count( $titleObjs )."...\n" );
			// XXX: We assume the array returned by findFiles() is
			// indexed by DBkey; this appears to be true, but should
			// be explicitly documented.
			$found = $this->repoGroup->findFiles( $unsure );
			$result += $found;
			$this->addFiles( $found );
			$this->markNotFound( array_keys( array_diff_key( $unsure, $found ) ) );
		}

		wfDebug( "FileCache found ".count( $result )." files out of ".count( $titleObjs )."\n" );
		return $result;
	}
}