Kill "* @return void"
[lhc/web/wiklou.git] / includes / filerepo / LocalRepo.php
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 * @file
7 * @ingroup FileRepo
8 */
9
10 /**
11 * A repository that stores files in the local filesystem and registers them
12 * in the wiki's own database. This is the most commonly used repository class.
13 *
14 * @ingroup FileRepo
15 */
16 class LocalRepo extends FileRepo {
17 var $fileFactory = array( 'LocalFile', 'newFromTitle' );
18 var $fileFactoryKey = array( 'LocalFile', 'newFromKey' );
19 var $oldFileFactory = array( 'OldLocalFile', 'newFromTitle' );
20 var $oldFileFactoryKey = array( 'OldLocalFile', 'newFromKey' );
21 var $fileFromRowFactory = array( 'LocalFile', 'newFromRow' );
22 var $oldFileFromRowFactory = array( 'OldLocalFile', 'newFromRow' );
23
24 /**
25 * @throws MWException
26 * @param $row
27 * @return File
28 */
29 function newFileFromRow( $row ) {
30 if ( isset( $row->img_name ) ) {
31 return call_user_func( $this->fileFromRowFactory, $row, $this );
32 } elseif ( isset( $row->oi_name ) ) {
33 return call_user_func( $this->oldFileFromRowFactory, $row, $this );
34 } else {
35 throw new MWException( __METHOD__.': invalid row' );
36 }
37 }
38
39 /**
40 * @param $title
41 * @param $archiveName
42 * @return OldLocalFile
43 */
44 function newFromArchiveName( $title, $archiveName ) {
45 return OldLocalFile::newFromArchiveName( $title, $this, $archiveName );
46 }
47
48 /**
49 * Delete files in the deleted directory if they are not referenced in the
50 * filearchive table. This needs to be done in the repo because it needs to
51 * interleave database locks with file operations, which is potentially a
52 * remote operation.
53 *
54 * @param $storageKeys array
55 *
56 * @return FileRepoStatus
57 */
58 function cleanupDeletedBatch( $storageKeys ) {
59 $backend = $this->backend; // convenience
60 $root = $this->getZonePath( 'deleted' );
61 $dbw = $this->getMasterDB();
62 $status = $this->newGood();
63 $storageKeys = array_unique( $storageKeys );
64 foreach ( $storageKeys as $key ) {
65 $hashPath = $this->getDeletedHashPath( $key );
66 $path = "$root/$hashPath$key";
67 $dbw->begin();
68 // Check for usage in deleted/hidden files and pre-emptively
69 // lock the key to avoid any future use until we are finished.
70 $deleted = $this->deletedFileHasKey( $key, 'lock' );
71 $hidden = $this->hiddenFileHasKey( $key, 'lock' );
72 if ( !$deleted && !$hidden ) { // not in use now
73 wfDebug( __METHOD__ . ": deleting $key\n" );
74 $op = array( 'op' => 'delete', 'src' => $path );
75 if ( !$backend->doOperation( $op )->isOK() ) {
76 $status->error( 'undelete-cleanup-error', $path );
77 $status->failCount++;
78 }
79 } else {
80 wfDebug( __METHOD__ . ": $key still in use\n" );
81 $status->successCount++;
82 }
83 $dbw->commit();
84 }
85 return $status;
86 }
87
88 /**
89 * Check if a deleted (filearchive) file has this sha1 key
90 *
91 * @param $key String File storage key (base-36 sha1 key with file extension)
92 * @param $lock String|null Use "lock" to lock the row via FOR UPDATE
93 * @return bool File with this key is in use
94 */
95 protected function deletedFileHasKey( $key, $lock = null ) {
96 $options = ( $lock === 'lock' ) ? array( 'FOR UPDATE' ) : array();
97
98 $dbw = $this->getMasterDB();
99 return (bool)$dbw->selectField( 'filearchive', '1',
100 array( 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ),
101 __METHOD__, $options
102 );
103 }
104
105 /**
106 * Check if a hidden (revision delete) file has this sha1 key
107 *
108 * @param $key String File storage key (base-36 sha1 key with file extension)
109 * @param $lock String|null Use "lock" to lock the row via FOR UPDATE
110 * @return bool File with this key is in use
111 */
112 protected function hiddenFileHasKey( $key, $lock = null ) {
113 $options = ( $lock === 'lock' ) ? array( 'FOR UPDATE' ) : array();
114
115 $sha1 = self::getHashFromKey( $key );
116 $ext = File::normalizeExtension( substr( $key, strcspn( $key, '.' ) + 1 ) );
117
118 $dbw = $this->getMasterDB();
119 return (bool)$dbw->selectField( 'oldimage', '1',
120 array( 'oi_sha1' => $sha1,
121 'oi_archive_name ' . $dbw->buildLike( $dbw->anyString(), ".$ext" ),
122 $dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE ),
123 __METHOD__, $options
124 );
125 }
126
127 /**
128 * Gets the SHA1 hash from a storage key
129 *
130 * @param string $key
131 * @return string
132 */
133 public static function getHashFromKey( $key ) {
134 return strtok( $key, '.' );
135 }
136
137 /**
138 * Checks if there is a redirect named as $title
139 *
140 * @param $title Title of file
141 * @return bool
142 */
143 function checkRedirect( Title $title ) {
144 global $wgMemc;
145
146 $title = File::normalizeTitle( $title, 'exception' );
147
148 $memcKey = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
149 if ( $memcKey === false ) {
150 $memcKey = $this->getLocalCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
151 $expiry = 300; // no invalidation, 5 minutes
152 } else {
153 $expiry = 86400; // has invalidation, 1 day
154 }
155 $cachedValue = $wgMemc->get( $memcKey );
156 if ( $cachedValue === ' ' || $cachedValue === '' ) {
157 // Does not exist
158 return false;
159 } elseif ( strval( $cachedValue ) !== '' ) {
160 return Title::newFromText( $cachedValue, NS_FILE );
161 } // else $cachedValue is false or null: cache miss
162
163 $id = $this->getArticleID( $title );
164 if( !$id ) {
165 $wgMemc->set( $memcKey, " ", $expiry );
166 return false;
167 }
168 $dbr = $this->getSlaveDB();
169 $row = $dbr->selectRow(
170 'redirect',
171 array( 'rd_title', 'rd_namespace' ),
172 array( 'rd_from' => $id ),
173 __METHOD__
174 );
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
187 /**
188 * Function link Title::getArticleID().
189 * We can't say Title object, what database it should use, so we duplicate that function here.
190 *
191 * @param $title Title
192 */
193 protected function getArticleID( $title ) {
194 if( !$title instanceof Title ) {
195 return 0;
196 }
197 $dbr = $this->getSlaveDB();
198 $id = $dbr->selectField(
199 'page', // Table
200 'page_id', //Field
201 array( //Conditions
202 'page_namespace' => $title->getNamespace(),
203 'page_title' => $title->getDBkey(),
204 ),
205 __METHOD__ //Function name
206 );
207 return $id;
208 }
209
210 /**
211 * Get an array or iterator of file objects for files that have a given
212 * SHA-1 content hash.
213 *
214 * @param string
215 * @return Array
216 */
217 function findBySha1( $hash ) {
218 $dbr = $this->getSlaveDB();
219 $res = $dbr->select(
220 'image',
221 LocalFile::selectFields(),
222 array( 'img_sha1' => $hash )
223 );
224
225 $result = array();
226 foreach ( $res as $row ) {
227 $result[] = $this->newFileFromRow( $row );
228 }
229 $res->free();
230
231 return $result;
232 }
233
234 /**
235 * Get a connection to the slave DB
236 */
237 function getSlaveDB() {
238 return wfGetDB( DB_SLAVE );
239 }
240
241 /**
242 * Get a connection to the master DB
243 */
244 function getMasterDB() {
245 return wfGetDB( DB_MASTER );
246 }
247
248 /**
249 * Get a key on the primary cache for this repository.
250 * Returns false if the repository's cache is not accessible at this site.
251 * The parameters are the parts of the key, as for wfMemcKey().
252 *
253 * @return string
254 */
255 function getSharedCacheKey( /*...*/ ) {
256 $args = func_get_args();
257 return call_user_func_array( 'wfMemcKey', $args );
258 }
259
260 /**
261 * Invalidates image redirect cache related to that image
262 *
263 * @param $title Title of page
264 */
265 function invalidateImageRedirect( Title $title ) {
266 global $wgMemc;
267 $memcKey = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
268 if ( $memcKey ) {
269 $wgMemc->delete( $memcKey );
270 }
271 }
272 }
273