Index: trunk/extensions/SwiftMedia/LocalSettings.php |
— | — | @@ -129,7 +129,7 @@ |
130 | 130 | require_once( "$IP/extensions/SwiftMedia/SwiftMedia.php" ); |
131 | 131 | |
132 | 132 | $wgUploadDirectory = "$IP/images/swift"; |
133 | | -$wgDeletedDirectory = "{$wgUploadDirectory}/deleted"; |
| 133 | +// we don't need this and will ignore it. $wgDeletedDirectory = "{$wgUploadDirectory}/deleted"; |
134 | 134 | $wgUploadPath = "http://alsted.wikimedia.org/images/swift"; |
135 | 135 | |
136 | 136 | $wgLocalFileRepo = array( |
— | — | @@ -148,9 +148,10 @@ |
149 | 149 | 'hashLevels' => $wgHashedUploadDirectory ? 2 : 0, |
150 | 150 | 'thumbScriptUrl' => $wgThumbnailScriptPath, |
151 | 151 | 'transformVia404' => !$wgGenerateThumbnailOnParse, |
152 | | - 'deletedDir' => $wgDeletedDirectory, |
| 152 | + #'deletedDir' => $wgDeletedDirectory, |
153 | 153 | 'deletedHashLevels' => 3 |
154 | 154 | ); |
155 | 155 | |
156 | 156 | $wgDebugLogFile = "/var/www/debug/abcd"; |
| 157 | +$wgDebugTimestamps = true; |
157 | 158 | $wgShowExceptionDetails = true; |
Index: trunk/extensions/SwiftMedia/TODO |
— | — | @@ -1,10 +1,83 @@ |
2 | | -5) The Upload seems to take more time than I expect, but that could be a function of generating the six thumbnails. |
| 2 | +Pending: |
| 3 | + |
3 | 4 | 6) There's no 404 handler to generate missing thumbnails. |
4 | 5 | 7) There's no support for remote thumbnailing. |
| 6 | +7.1) The SpecialUploadStash, when it calls the remote scaler, is actually just relying on the 404 handler on upload.wm.org. |
| 7 | + It's just executing thumb.php (is it thumb??) to make a thumbnail on a temporary name. Note that it's using a temp/ |
| 8 | + directory in the thumbs container, not a directory in the temp container. |
| 9 | +7.2) The scaler cluster is simply some machines with access to the originals and thumb storage. The 404 handler running on the Apache |
| 10 | + front-ends forwards the request to a thumb.php running on the scaler cluster. Thumb.php takes care of creating the thumbnail. |
| 11 | +6&7) Basically, the code which currently fetches 404 thumbs from upload.wikimedia.org needs to be changed slightly so that it inserts |
| 12 | + thumb.php with the appropriate parameters and fetches from the scaler cluster. |
5 | 13 | 8) Test cases (but of course that could be done until the cows come home). |
6 | 14 | 9) Read through the code and look for anything which is insane. |
7 | 15 | 10) Remove directory from $wgLocalFileRepo, to make sure that there's no references to it. Ditto for wgDeletedDirectory and deletedDir. |
8 | 16 | 11) Determine what to do about the one remaining core change needed for Swift. |
| 17 | +12) Why is anybody calling resolveVirtualUrl()? It's defined in the Repo, but getPath() is defined against a file. |
| 18 | +Why is UploadStashFile() being called with a virtual URL? Once the file has been stashed() it has an object name. The container name is implicit. |
| 19 | +Should UploadStashFile *always* (in our case) be called with a virtual URL? |
| 20 | + |
| 21 | +Resolved: |
| 22 | + |
| 23 | +5) The Upload seems to take more time than I expect, but that could be a function of generating the six thumbnails. |
| 24 | + It *is* a function of generating the seven (we generate 800x600 twice) thumbnails. Each one takes 1/2 second. |
| 25 | +10) Remove directory from $wgLocalFileRepo, to make sure that there's no references to it. Ditto for wgDeletedDirectory and deletedDir. |
| 26 | + wgDeletedDirectory and deletedDir can be removed. |
9 | 27 | 12) Implement repo->freeTemp() - needed by several extensions and UploadFromStash. |
10 | 28 | 13) Do we need $wgLocalRepo->ThumbUrl to be configurable given that the Python middleware presumes it? |
| 29 | + We currently have no need for it to be configurable in Swift. I'll just hard-code it to .../thumb with a note saying |
| 30 | + that if it gets changed here, it needs to be changed in the Swift middleware as well. |
11 | 31 | |
| 32 | + |
| 33 | +neilk_: okay, the moment when an upload passes from being a temp file into something else is at $upload->processUpload() |
| 34 | +neilk_: in the old design, in essence, all this does is move a file into an NFS directory, and creates the matching database entry which creates a wiki page. |
| 35 | +neilk_: so far I don't think this should be news? |
| 36 | +nelson____: right |
| 37 | +neilk_: So that's includes/specials/SpecialUpload.php |
| 38 | +neilk_: then there's includes/api/ApiUpload.php |
| 39 | +neilk_: which is similar but not quite the same |
| 40 | + |
| 41 | +Watch for that! |
| 42 | + |
| 43 | +neilk_: in ApiUpload.php there is the option to stash explicitly |
| 44 | +neilk_: so the path is a bit convoluted in ApiUpload.php. Also if I remember right the file is accessed a bit differently |
| 45 | +neilk_: it is possible to have stashing in ALL of these cases |
| 46 | +neilk_: but in SpecialUpload, stashing occurs if there's a recoverable error with the file, like a bad file name |
| 47 | +neilk_: in ApiUpload, stashing can happen for that reason, or it can happens if you ask for it explicitly (which is how UploadWizard works). |
| 48 | +neilk_: nelson__: anyway is this answering your question yet? |
| 49 | +nelson____: yes. |
| 50 | +neilk_: ok so that's the overview of the upload methods & stashing, what else |
| 51 | +nelson____: I think that part of the problem is that various parts of the system feel free to make the jump from "stored" to "accessible as full path". |
| 52 | +neilk_: yes |
| 53 | +neilk_: it drove me nuts too |
| 54 | +neilk_: and the code intentionally conflates a number of cases, because MediaWiki at heart just wants to throw a number of files into a directory, not manage millions of them |
| 55 | +nelson____: I gotta figure out how to mark the difference, so that something is either 1) a locally stored file, or 2) a blind token from the repo. |
| 56 | +neilk_: can't you subclass FileRepo then? |
| 57 | +nelson____: cuz if you have the blind token, then you need to turn it into a File and then call getPath() on it. |
| 58 | +nelson____: but there's times when the upload code expects to be able to access a file without creating a File first. |
| 59 | +neilk_: when does upload code access a file that isn't a File? |
| 60 | +nelson____: sec |
| 61 | +nelson____: neilk_: UploadStashFile does this in its __construct: $path = $repo->resolveVirtualUrl( $path ); |
| 62 | +neilk_: yes |
| 63 | +nelson____: But maybe the key thing for me to know is that when it's SwiftMedia, $path on the right is *always* a mwrepo/ |
| 64 | +nelson____: If that's the case, then I think I'm okay. I'm just having trouble following the code up and out and then back down. |
| 65 | +neilk_: there isn't any code, to my knowledge, which assumes that $path is a "physical" path. It uses the repo methods. |
| 66 | +neilk_: I don't blame you if you're having trouble. |
| 67 | +neilk_: isn't any code in UploadStashFile, I mean. |
| 68 | +nelson____: maybe ... what I should do is throw an exception if SwiftRepo::resolveVirtualUrl ever gets called without a mwrepo url, and then just go test everything. |
| 69 | +nelson____: I think maybe I'm trying to overanalyze the code. |
| 70 | +neilk_: I sympathize |
| 71 | +nelson____: I should trust the code more. |
| 72 | +neilk_: hm, I think not |
| 73 | +nelson____: Trust but verify. |
| 74 | +neilk_: also, this is sad but stashing is done in two slightly different ways, too. |
| 75 | +nelson____: I saw. |
| 76 | +neilk_: but compatible |
| 77 | +neilk_: I wanted UploadStash to absorb the other one. |
| 78 | +neilk_: We can still do that. |
| 79 | +nelson____: agreed. |
| 80 | +neilk_: When I was in the middle of Upload code, I always felt like I was cramped in some access area between two walls, with all the pipes and electrical work. |
| 81 | +nelson____: interesting metaphor. |
| 82 | +nelson____: yeah, I think part of that problem is you're always stuck between the database and the filestore. |
| 83 | +nelson____: they both have opinions about how things work, and you have to keep them consistent. |
| 84 | +nelson____: Okay, I'm gonna take some notes then go home. taking the weekend off for a bicycle road trip. |
Index: trunk/extensions/SwiftMedia/SwiftMedia.body.php |
— | — | @@ -87,297 +87,9 @@ |
88 | 88 | |
89 | 89 | parent::__construct( $title, $repo ); |
90 | 90 | |
91 | | - $this->metadata = ''; |
92 | | - $this->historyLine = 0; |
93 | | - $this->historyRes = null; |
94 | | - $this->dataLoaded = false; |
| 91 | + $this->temp_path = false; // Points to our local copy. |
95 | 92 | } |
96 | 93 | |
97 | | - /** |
98 | | - * Get the memcached key for the main data for this file, or false if |
99 | | - * there is no access to the shared cache. |
100 | | - */ |
101 | | - function getCacheKey() { |
102 | | - $hashedName = md5( $this->getName() ); |
103 | | - |
104 | | - return $this->repo->getSharedCacheKey( 'file', $hashedName ); |
105 | | - } |
106 | | - |
107 | | - /** |
108 | | - * Try to load file metadata from memcached. Returns true on success. |
109 | | - */ |
110 | | - function loadFromCache() { |
111 | | - global $wgMemc; |
112 | | - |
113 | | - wfProfileIn( __METHOD__ ); |
114 | | - $this->dataLoaded = false; |
115 | | - $key = $this->getCacheKey(); |
116 | | - |
117 | | - if ( !$key ) { |
118 | | - wfProfileOut( __METHOD__ ); |
119 | | - return false; |
120 | | - } |
121 | | - |
122 | | - $cachedValues = $wgMemc->get( $key ); |
123 | | - |
124 | | - // Check if the key existed and belongs to this version of MediaWiki |
125 | | - if ( isset( $cachedValues['version'] ) && ( $cachedValues['version'] == MW_FILE_VERSION ) ) { |
126 | | - wfDebug( "Pulling file metadata from cache key $key\n" ); |
127 | | - $this->fileExists = $cachedValues['fileExists']; |
128 | | - if ( $this->fileExists ) { |
129 | | - $this->setProps( $cachedValues ); |
130 | | - } |
131 | | - $this->dataLoaded = true; |
132 | | - } |
133 | | - |
134 | | - if ( $this->dataLoaded ) { |
135 | | - wfIncrStats( 'image_cache_hit' ); |
136 | | - } else { |
137 | | - wfIncrStats( 'image_cache_miss' ); |
138 | | - } |
139 | | - |
140 | | - wfProfileOut( __METHOD__ ); |
141 | | - return $this->dataLoaded; |
142 | | - } |
143 | | - |
144 | | - /** |
145 | | - * Save the file metadata to memcached |
146 | | - */ |
147 | | - function saveToCache() { |
148 | | - global $wgMemc; |
149 | | - |
150 | | - $this->load(); |
151 | | - $key = $this->getCacheKey(); |
152 | | - |
153 | | - if ( !$key ) { |
154 | | - return; |
155 | | - } |
156 | | - |
157 | | - $fields = $this->getCacheFields( '' ); |
158 | | - $cache = array( 'version' => MW_FILE_VERSION ); |
159 | | - $cache['fileExists'] = $this->fileExists; |
160 | | - |
161 | | - if ( $this->fileExists ) { |
162 | | - foreach ( $fields as $field ) { |
163 | | - $cache[$field] = $this->$field; |
164 | | - } |
165 | | - } |
166 | | - |
167 | | - $wgMemc->set( $key, $cache, 60 * 60 * 24 * 7 ); // A week |
168 | | - } |
169 | | - |
170 | | - /** |
171 | | - * Load metadata from the file itself |
172 | | - */ |
173 | | - function loadFromFile() { |
174 | | - wfDebug( __METHOD__ . $this->getPath() . "\n" ); |
175 | | - $this->setProps( self::getPropsFromPath( $this->getPath() ) ); |
176 | | - } |
177 | | - |
178 | | - function getCacheFields( $prefix = 'img_' ) { |
179 | | - static $fields = array( 'size', 'width', 'height', 'bits', 'media_type', |
180 | | - 'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'user', 'user_text', 'description' ); |
181 | | - static $results = array(); |
182 | | - |
183 | | - if ( $prefix == '' ) { |
184 | | - return $fields; |
185 | | - } |
186 | | - |
187 | | - if ( !isset( $results[$prefix] ) ) { |
188 | | - $prefixedFields = array(); |
189 | | - foreach ( $fields as $field ) { |
190 | | - $prefixedFields[] = $prefix . $field; |
191 | | - } |
192 | | - $results[$prefix] = $prefixedFields; |
193 | | - } |
194 | | - |
195 | | - return $results[$prefix]; |
196 | | - } |
197 | | - |
198 | | - /** |
199 | | - * Load file metadata from the DB |
200 | | - */ |
201 | | - function loadFromDB() { |
202 | | - # Polymorphic function name to distinguish foreign and local fetches |
203 | | - $fname = get_class( $this ) . '::' . __FUNCTION__; |
204 | | - wfProfileIn( $fname ); |
205 | | - |
206 | | - # Unconditionally set loaded=true, we don't want the accessors constantly rechecking |
207 | | - $this->dataLoaded = true; |
208 | | - |
209 | | - $dbr = $this->repo->getMasterDB(); |
210 | | - |
211 | | - $row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ), |
212 | | - array( 'img_name' => $this->getName() ), $fname ); |
213 | | - |
214 | | - if ( $row ) { |
215 | | - $this->loadFromRow( $row ); |
216 | | - } else { |
217 | | - $this->fileExists = false; |
218 | | - } |
219 | | - |
220 | | - wfProfileOut( $fname ); |
221 | | - } |
222 | | - |
223 | | - /** |
224 | | - * Decode a row from the database (either object or array) to an array |
225 | | - * with timestamps and MIME types decoded, and the field prefix removed. |
226 | | - */ |
227 | | - function decodeRow( $row, $prefix = 'img_' ) { |
228 | | - $array = (array)$row; |
229 | | - $prefixLength = strlen( $prefix ); |
230 | | - |
231 | | - // Sanity check prefix once |
232 | | - if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) { |
233 | | - throw new MWException( __METHOD__ . ": incorrect $prefix parameter" ); |
234 | | - } |
235 | | - |
236 | | - $decoded = array(); |
237 | | - |
238 | | - foreach ( $array as $name => $value ) { |
239 | | - $decoded[substr( $name, $prefixLength )] = $value; |
240 | | - } |
241 | | - |
242 | | - $decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] ); |
243 | | - |
244 | | - if ( empty( $decoded['major_mime'] ) ) { |
245 | | - $decoded['mime'] = 'unknown/unknown'; |
246 | | - } else { |
247 | | - if ( !$decoded['minor_mime'] ) { |
248 | | - $decoded['minor_mime'] = 'unknown'; |
249 | | - } |
250 | | - $decoded['mime'] = $decoded['major_mime'] . '/' . $decoded['minor_mime']; |
251 | | - } |
252 | | - |
253 | | - # Trim zero padding from char/binary field |
254 | | - $decoded['sha1'] = rtrim( $decoded['sha1'], "\0" ); |
255 | | - |
256 | | - return $decoded; |
257 | | - } |
258 | | - |
259 | | - /** |
260 | | - * Load file metadata from a DB result row |
261 | | - */ |
262 | | - function loadFromRow( $row, $prefix = 'img_' ) { |
263 | | - $this->dataLoaded = true; |
264 | | - $array = $this->decodeRow( $row, $prefix ); |
265 | | - |
266 | | - foreach ( $array as $name => $value ) { |
267 | | - $this->$name = $value; |
268 | | - } |
269 | | - |
270 | | - $this->fileExists = true; |
271 | | - $this->maybeUpgradeRow(); |
272 | | - } |
273 | | - |
274 | | - /** |
275 | | - * Load file metadata from cache or DB, unless already loaded |
276 | | - */ |
277 | | - function load() { |
278 | | - if ( !$this->dataLoaded ) { |
279 | | - if ( !$this->loadFromCache() ) { |
280 | | - $this->loadFromDB(); |
281 | | - $this->saveToCache(); |
282 | | - } |
283 | | - $this->dataLoaded = true; |
284 | | - } |
285 | | - } |
286 | | - |
287 | | - /** |
288 | | - * Upgrade a row if it needs it |
289 | | - */ |
290 | | - function maybeUpgradeRow() { |
291 | | - if ( wfReadOnly() ) { |
292 | | - return; |
293 | | - } |
294 | | - |
295 | | - if ( is_null( $this->media_type ) || |
296 | | - $this->mime == 'image/svg' |
297 | | - ) { |
298 | | - $this->upgradeRow(); |
299 | | - $this->upgraded = true; |
300 | | - } else { |
301 | | - $handler = $this->getHandler(); |
302 | | - if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) { |
303 | | - $this->upgradeRow(); |
304 | | - $this->upgraded = true; |
305 | | - } |
306 | | - } |
307 | | - } |
308 | | - |
309 | | - function getUpgraded() { |
310 | | - return $this->upgraded; |
311 | | - } |
312 | | - |
313 | | - /** |
314 | | - * Fix assorted version-related problems with the image row by reloading it from the file |
315 | | - */ |
316 | | - function upgradeRow() { |
317 | | - wfProfileIn( __METHOD__ ); |
318 | | - |
319 | | - $this->loadFromFile(); |
320 | | - |
321 | | - # Don't destroy file info of missing files |
322 | | - if ( !$this->fileExists ) { |
323 | | - wfDebug( __METHOD__ . ": file does not exist, aborting\n" ); |
324 | | - wfProfileOut( __METHOD__ ); |
325 | | - return; |
326 | | - } |
327 | | - |
328 | | - $dbw = $this->repo->getMasterDB(); |
329 | | - list( $major, $minor ) = self::splitMime( $this->mime ); |
330 | | - |
331 | | - if ( wfReadOnly() ) { |
332 | | - wfProfileOut( __METHOD__ ); |
333 | | - return; |
334 | | - } |
335 | | - wfDebug( __METHOD__ . ': upgrading ' . $this->getName() . " to the current schema\n" ); |
336 | | - |
337 | | - $dbw->update( 'image', |
338 | | - array( |
339 | | - 'img_width' => $this->width, |
340 | | - 'img_height' => $this->height, |
341 | | - 'img_bits' => $this->bits, |
342 | | - 'img_media_type' => $this->media_type, |
343 | | - 'img_major_mime' => $major, |
344 | | - 'img_minor_mime' => $minor, |
345 | | - 'img_metadata' => $this->metadata, |
346 | | - 'img_sha1' => $this->sha1, |
347 | | - ), array( 'img_name' => $this->getName() ), |
348 | | - __METHOD__ |
349 | | - ); |
350 | | - |
351 | | - $this->saveToCache(); |
352 | | - wfProfileOut( __METHOD__ ); |
353 | | - } |
354 | | - |
355 | | - /** |
356 | | - * Set properties in this object to be equal to those given in the |
357 | | - * associative array $info. Only cacheable fields can be set. |
358 | | - * |
359 | | - * If 'mime' is given, it will be split into major_mime/minor_mime. |
360 | | - * If major_mime/minor_mime are given, $this->mime will also be set. |
361 | | - */ |
362 | | - function setProps( $info ) { |
363 | | - $this->dataLoaded = true; |
364 | | - $fields = $this->getCacheFields( '' ); |
365 | | - $fields[] = 'fileExists'; |
366 | | - |
367 | | - foreach ( $fields as $field ) { |
368 | | - if ( isset( $info[$field] ) ) { |
369 | | - $this->$field = $info[$field]; |
370 | | - } |
371 | | - } |
372 | | - |
373 | | - // Fix up mime fields |
374 | | - if ( isset( $info['major_mime'] ) ) { |
375 | | - $this->mime = "{$info['major_mime']}/{$info['minor_mime']}"; |
376 | | - } elseif ( isset( $info['mime'] ) ) { |
377 | | - $this->mime = $info['mime']; |
378 | | - list( $this->major_mime, $this->minor_mime ) = self::splitMime( $this->mime ); |
379 | | - } |
380 | | - } |
381 | | - |
382 | 94 | /** splitMime inherited */ |
383 | 95 | /** getName inherited */ |
384 | 96 | /** getTitle inherited */ |
— | — | @@ -403,6 +115,39 @@ |
404 | 116 | return $this->getLocalCopy($this->repo->getZoneContainer('thumb'), $path); |
405 | 117 | } |
406 | 118 | |
| 119 | + /** |
| 120 | + * Get a local path corresponding to a virtual URL |
| 121 | + */ |
| 122 | + function getContainerRel( $url ) { |
| 123 | + if ( substr( $url, 0, 9 ) != 'mwrepo://' ) { |
| 124 | + throw new MWException( __METHOD__.": unknown protocol" ); |
| 125 | + } |
| 126 | + |
| 127 | + $bits = explode( '/', substr( $url, 9 ), 3 ); |
| 128 | + if ( count( $bits ) != 3 ) { |
| 129 | + throw new MWException( __METHOD__.": invalid mwrepo URL: $url" ); |
| 130 | + } |
| 131 | + list( $repo, $zone, $rel ) = $bits; |
| 132 | + if ( $repo !== $this->name ) { |
| 133 | + throw new MWException( __METHOD__.": fetching from a foreign repo is not supported" ); |
| 134 | + } |
| 135 | + $container = $this->getZoneContainer( $zone ); |
| 136 | + if ( $container === false) { |
| 137 | + throw new MWException( __METHOD__.": invalid zone: $zone" ); |
| 138 | + } |
| 139 | + return array($container, rawurldecode( $rel )); |
| 140 | + } |
| 141 | + |
| 142 | + /** |
| 143 | + * Called from elsewhere to turn a virtual URL into a path. |
| 144 | + */ |
| 145 | + function resolveVirtualUrl( $url ) { |
| 146 | + $path = getContainerRel( $url ); |
| 147 | + list($c, $r) = $path; |
| 148 | + return $this->getLocalCopy($c, $r); |
| 149 | + } |
| 150 | + |
| 151 | + |
407 | 152 | /** Given a container and relative path, return an absolute path pointing at a copy of the file */ |
408 | 153 | function getLocalCopy($container, $rel) { |
409 | 154 | // if we already have a local copy, return it. |
— | — | @@ -441,122 +186,6 @@ |
442 | 187 | } |
443 | 188 | } |
444 | 189 | |
445 | | - function isMissing() { |
446 | | - if ( $this->missing === null ) { |
447 | | - list( $fileExists ) = $this->repo->fileExistsBatch( array( $this->getVirtualUrl() ), FileRepo::FILES_ONLY ); |
448 | | - $this->missing = !$fileExists; |
449 | | - } |
450 | | - return $this->missing; |
451 | | - } |
452 | | - |
453 | | - /** |
454 | | - * Return the width of the image |
455 | | - * |
456 | | - * Returns false on error |
457 | | - */ |
458 | | - public function getWidth( $page = 1 ) { |
459 | | - $this->load(); |
460 | | - |
461 | | - if ( $this->isMultipage() ) { |
462 | | - $dim = $this->getHandler()->getPageDimensions( $this, $page ); |
463 | | - if ( $dim ) { |
464 | | - return $dim['width']; |
465 | | - } else { |
466 | | - return false; |
467 | | - } |
468 | | - } else { |
469 | | - return $this->width; |
470 | | - } |
471 | | - } |
472 | | - |
473 | | - /** |
474 | | - * Return the height of the image |
475 | | - * |
476 | | - * Returns false on error |
477 | | - */ |
478 | | - public function getHeight( $page = 1 ) { |
479 | | - $this->load(); |
480 | | - |
481 | | - if ( $this->isMultipage() ) { |
482 | | - $dim = $this->getHandler()->getPageDimensions( $this, $page ); |
483 | | - if ( $dim ) { |
484 | | - return $dim['height']; |
485 | | - } else { |
486 | | - return false; |
487 | | - } |
488 | | - } else { |
489 | | - return $this->height; |
490 | | - } |
491 | | - } |
492 | | - |
493 | | - /** |
494 | | - * Returns ID or name of user who uploaded the file |
495 | | - * |
496 | | - * @param $type string 'text' or 'id' |
497 | | - */ |
498 | | - function getUser( $type = 'text' ) { |
499 | | - $this->load(); |
500 | | - |
501 | | - if ( $type == 'text' ) { |
502 | | - return $this->user_text; |
503 | | - } elseif ( $type == 'id' ) { |
504 | | - return $this->user; |
505 | | - } |
506 | | - } |
507 | | - |
508 | | - /** |
509 | | - * Get handler-specific metadata |
510 | | - */ |
511 | | - function getMetadata() { |
512 | | - $this->load(); |
513 | | - return $this->metadata; |
514 | | - } |
515 | | - |
516 | | - function getBitDepth() { |
517 | | - $this->load(); |
518 | | - return $this->bits; |
519 | | - } |
520 | | - |
521 | | - /** |
522 | | - * Return the size of the image file, in bytes |
523 | | - */ |
524 | | - public function getSize() { |
525 | | - $this->load(); |
526 | | - return $this->size; |
527 | | - } |
528 | | - |
529 | | - /** |
530 | | - * Returns the mime type of the file. |
531 | | - */ |
532 | | - function getMimeType() { |
533 | | - $this->load(); |
534 | | - return $this->mime; |
535 | | - } |
536 | | - |
537 | | - /** |
538 | | - * Return the type of the media in the file. |
539 | | - * Use the value returned by this function with the MEDIATYPE_xxx constants. |
540 | | - */ |
541 | | - function getMediaType() { |
542 | | - $this->load(); |
543 | | - return $this->media_type; |
544 | | - } |
545 | | - |
546 | | - /** canRender inherited */ |
547 | | - /** mustRender inherited */ |
548 | | - /** allowInlineDisplay inherited */ |
549 | | - /** isSafeFile inherited */ |
550 | | - /** isTrustedFile inherited */ |
551 | | - |
552 | | - /** |
553 | | - * Returns true if the file file exists on disk. |
554 | | - * @return boolean Whether file file exist on disk. |
555 | | - */ |
556 | | - public function exists() { |
557 | | - $this->load(); |
558 | | - return $this->fileExists; |
559 | | - } |
560 | | - |
561 | 190 | /** getTransformScript inherited */ |
562 | 191 | /** getUnscaledThumb inherited */ |
563 | 192 | /** thumbName inherited */ |
— | — | @@ -651,43 +280,6 @@ |
652 | 281 | } |
653 | 282 | |
654 | 283 | /** |
655 | | - * Refresh metadata in memcached, but don't touch thumbnails or squid |
656 | | - */ |
657 | | - function purgeMetadataCache() { |
658 | | - $this->loadFromDB(); |
659 | | - $this->saveToCache(); |
660 | | - $this->purgeHistory(); |
661 | | - } |
662 | | - |
663 | | - /** |
664 | | - * Purge the shared history (OldLocalFile) cache |
665 | | - */ |
666 | | - function purgeHistory() { |
667 | | - global $wgMemc; |
668 | | - |
669 | | - $hashedName = md5( $this->getName() ); |
670 | | - $oldKey = $this->repo->getSharedCacheKey( 'oldfile', $hashedName ); |
671 | | - |
672 | | - if ( $oldKey ) { |
673 | | - $wgMemc->delete( $oldKey ); |
674 | | - } |
675 | | - } |
676 | | - |
677 | | - /** |
678 | | - * Delete all previously generated thumbnails, refresh metadata in memcached and purge the squid |
679 | | - */ |
680 | | - function purgeCache() { |
681 | | - // Refresh metadata cache |
682 | | - $this->purgeMetadataCache(); |
683 | | - |
684 | | - // Delete thumbnails |
685 | | - $this->purgeThumbnails(); |
686 | | - |
687 | | - // Purge squid cache for this file |
688 | | - SquidUpdate::purge( array( $this->getURL() ) ); |
689 | | - } |
690 | | - |
691 | | - /** |
692 | 284 | * Delete cached transformed files |
693 | 285 | */ |
694 | 286 | function purgeThumbnails() { |
— | — | @@ -717,1517 +309,11 @@ |
718 | 310 | } |
719 | 311 | } |
720 | 312 | |
721 | | - /** purgeDescription inherited */ |
722 | | - /** purgeEverything inherited */ |
723 | | - |
724 | | - function getHistory( $limit = null, $start = null, $end = null, $inc = true ) { |
725 | | - $dbr = $this->repo->getSlaveDB(); |
726 | | - $tables = array( 'oldimage' ); |
727 | | - $fields = OldSwiftFile::selectFields(); |
728 | | - $conds = $opts = $join_conds = array(); |
729 | | - $eq = $inc ? '=' : ''; |
730 | | - $conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBkey() ); |
731 | | - |
732 | | - if ( $start ) { |
733 | | - $conds[] = "oi_timestamp <$eq " . $dbr->addQuotes( $dbr->timestamp( $start ) ); |
734 | | - } |
735 | | - |
736 | | - if ( $end ) { |
737 | | - $conds[] = "oi_timestamp >$eq " . $dbr->addQuotes( $dbr->timestamp( $end ) ); |
738 | | - } |
739 | | - |
740 | | - if ( $limit ) { |
741 | | - $opts['LIMIT'] = $limit; |
742 | | - } |
743 | | - |
744 | | - // Search backwards for time > x queries |
745 | | - $order = ( !$start && $end !== null ) ? 'ASC' : 'DESC'; |
746 | | - $opts['ORDER BY'] = "oi_timestamp $order"; |
747 | | - $opts['USE INDEX'] = array( 'oldimage' => 'oi_name_timestamp' ); |
748 | | - |
749 | | - wfRunHooks( 'SwiftFile::getHistory', array( &$this, &$tables, &$fields, |
750 | | - &$conds, &$opts, &$join_conds ) ); |
751 | | - |
752 | | - $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds ); |
753 | | - $r = array(); |
754 | | - |
755 | | - foreach ( $res as $row ) { |
756 | | - if ( $this->repo->oldFileFromRowFactory ) { |
757 | | - $r[] = call_user_func( $this->repo->oldFileFromRowFactory, $row, $this->repo ); |
758 | | - } else { |
759 | | - $r[] = OldSwiftFile::newFromRow( $row, $this->repo ); |
760 | | - } |
761 | | - } |
762 | | - |
763 | | - if ( $order == 'ASC' ) { |
764 | | - $r = array_reverse( $r ); // make sure it ends up descending |
765 | | - } |
766 | | - |
767 | | - return $r; |
768 | | - } |
769 | | - |
770 | | - /** |
771 | | - * Return the history of this file, line by line. |
772 | | - * starts with current version, then old versions. |
773 | | - * uses $this->historyLine to check which line to return: |
774 | | - * 0 return line for current version |
775 | | - * 1 query for old versions, return first one |
776 | | - * 2, ... return next old version from above query |
777 | | - */ |
778 | | - public function nextHistoryLine() { |
779 | | - # Polymorphic function name to distinguish foreign and local fetches |
780 | | - $fname = get_class( $this ) . '::' . __FUNCTION__; |
781 | | - |
782 | | - $dbr = $this->repo->getSlaveDB(); |
783 | | - |
784 | | - if ( $this->historyLine == 0 ) {// called for the first time, return line from cur |
785 | | - $this->historyRes = $dbr->select( 'image', |
786 | | - array( |
787 | | - '*', |
788 | | - "'' AS oi_archive_name", |
789 | | - '0 as oi_deleted', |
790 | | - 'img_sha1' |
791 | | - ), |
792 | | - array( 'img_name' => $this->title->getDBkey() ), |
793 | | - $fname |
794 | | - ); |
795 | | - |
796 | | - if ( 0 == $dbr->numRows( $this->historyRes ) ) { |
797 | | - $this->historyRes = null; |
798 | | - return false; |
799 | | - } |
800 | | - } elseif ( $this->historyLine == 1 ) { |
801 | | - $this->historyRes = $dbr->select( 'oldimage', '*', |
802 | | - array( 'oi_name' => $this->title->getDBkey() ), |
803 | | - $fname, |
804 | | - array( 'ORDER BY' => 'oi_timestamp DESC' ) |
805 | | - ); |
806 | | - } |
807 | | - $this->historyLine ++; |
808 | | - |
809 | | - return $dbr->fetchObject( $this->historyRes ); |
810 | | - } |
811 | | - |
812 | | - /** |
813 | | - * Reset the history pointer to the first element of the history |
814 | | - */ |
815 | | - public function resetHistory() { |
816 | | - $this->historyLine = 0; |
817 | | - |
818 | | - if ( !is_null( $this->historyRes ) ) { |
819 | | - $this->historyRes = null; |
820 | | - } |
821 | | - } |
822 | | - |
823 | | - /** getFullPath inherited */ |
824 | | - /** getHashPath inherited */ |
825 | | - /** getRel inherited */ |
826 | | - /** getUrlRel inherited */ |
827 | | - /** getArchiveRel inherited */ |
828 | | - /** getArchiveUrl inherited */ |
829 | | - /** getThumbUrl inherited */ |
830 | | - /** getArchiveVirtualUrl inherited */ |
831 | | - /** getThumbVirtualUrl inherited */ |
832 | | - /** isHashed inherited */ |
833 | | - |
834 | | - /** |
835 | | - * Upload a file and record it in the DB |
836 | | - * @param $srcPath String: source path or virtual URL |
837 | | - * @param $comment String: upload description |
838 | | - * @param $pageText String: text to use for the new description page, |
839 | | - * if a new description page is created |
840 | | - * @param $flags Integer: flags for publish() |
841 | | - * @param $props Array: File properties, if known. This can be used to reduce the |
842 | | - * upload time when uploading virtual URLs for which the file info |
843 | | - * is already known |
844 | | - * @param $timestamp String: timestamp for img_timestamp, or false to use the current time |
845 | | - * @param $user Mixed: User object or null to use $wgUser |
846 | | - * |
847 | | - * @return FileRepoStatus object. On success, the value member contains the |
848 | | - * archive name, or an empty string if it was a new file. |
849 | | - */ |
850 | | - function upload( $srcPath, $comment, $pageText, $flags = 0, $props = false, $timestamp = false, $user = null ) { |
851 | | - $this->lock(); |
852 | | - $status = $this->publish( $srcPath, $flags ); |
853 | | - |
854 | | - if ( $status->ok ) { |
855 | | - if ( !$this->recordUpload2( $status->value, $comment, $pageText, $props, $timestamp, $user ) ) { |
856 | | - $status->fatal( 'filenotfound', $srcPath ); |
857 | | - } |
858 | | - } |
859 | | - |
860 | | - $this->unlock(); |
861 | | - |
862 | | - return $status; |
863 | | - } |
864 | | - |
865 | | - /** |
866 | | - * Record a file upload in the upload log and the image table |
867 | | - * @deprecated use upload() |
868 | | - */ |
869 | | - function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', |
870 | | - $watch = false, $timestamp = false ) |
871 | | - { |
872 | | - $pageText = SpecialUpload::getInitialPageText( $desc, $license, $copyStatus, $source ); |
873 | | - |
874 | | - if ( !$this->recordUpload2( $oldver, $desc, $pageText ) ) { |
875 | | - return false; |
876 | | - } |
877 | | - |
878 | | - if ( $watch ) { |
879 | | - global $wgUser; |
880 | | - $wgUser->addWatch( $this->getTitle() ); |
881 | | - } |
882 | | - return true; |
883 | | - |
884 | | - } |
885 | | - |
886 | | - /** |
887 | | - * Record a file upload in the upload log and the image table |
888 | | - */ |
889 | | - function recordUpload2( $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null ) |
890 | | - { |
891 | | - if ( is_null( $user ) ) { |
892 | | - global $wgUser; |
893 | | - $user = $wgUser; |
894 | | - } |
895 | | - |
896 | | - $dbw = $this->repo->getMasterDB(); |
897 | | - $dbw->begin(); |
898 | | - |
899 | | - if ( !$props ) { |
900 | | - $props = $this->repo->getFileProps( $this->getVirtualUrl() ); |
901 | | - } |
902 | | - |
903 | | - $props['description'] = $comment; |
904 | | - $props['user'] = $user->getId(); |
905 | | - $props['user_text'] = $user->getName(); |
906 | | - $props['timestamp'] = wfTimestamp( TS_MW ); |
907 | | - $this->setProps( $props ); |
908 | | - |
909 | | - # Delete thumbnails |
910 | | - $this->purgeThumbnails(); |
911 | | - |
912 | | - # The file is already on its final location, remove it from the squid cache |
913 | | - SquidUpdate::purge( array( $this->getURL() ) ); |
914 | | - |
915 | | - # Fail now if the file isn't there |
916 | | - if ( !$this->fileExists ) { |
917 | | - wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!\n" ); |
918 | | - return false; |
919 | | - } |
920 | | - |
921 | | - $reupload = false; |
922 | | - |
923 | | - if ( $timestamp === false ) { |
924 | | - $timestamp = $dbw->timestamp(); |
925 | | - } |
926 | | - |
927 | | - # Test to see if the row exists using INSERT IGNORE |
928 | | - # This avoids race conditions by locking the row until the commit, and also |
929 | | - # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition. |
930 | | - $dbw->insert( 'image', |
931 | | - array( |
932 | | - 'img_name' => $this->getName(), |
933 | | - 'img_size' => $this->size, |
934 | | - 'img_width' => intval( $this->width ), |
935 | | - 'img_height' => intval( $this->height ), |
936 | | - 'img_bits' => $this->bits, |
937 | | - 'img_media_type' => $this->media_type, |
938 | | - 'img_major_mime' => $this->major_mime, |
939 | | - 'img_minor_mime' => $this->minor_mime, |
940 | | - 'img_timestamp' => $timestamp, |
941 | | - 'img_description' => $comment, |
942 | | - 'img_user' => $user->getId(), |
943 | | - 'img_user_text' => $user->getName(), |
944 | | - 'img_metadata' => $this->metadata, |
945 | | - 'img_sha1' => $this->sha1 |
946 | | - ), |
947 | | - __METHOD__, |
948 | | - 'IGNORE' |
949 | | - ); |
950 | | - |
951 | | - if ( $dbw->affectedRows() == 0 ) { |
952 | | - $reupload = true; |
953 | | - |
954 | | - # Collision, this is an update of a file |
955 | | - # Insert previous contents into oldimage |
956 | | - $dbw->insertSelect( 'oldimage', 'image', |
957 | | - array( |
958 | | - 'oi_name' => 'img_name', |
959 | | - 'oi_archive_name' => $dbw->addQuotes( $oldver ), |
960 | | - 'oi_size' => 'img_size', |
961 | | - 'oi_width' => 'img_width', |
962 | | - 'oi_height' => 'img_height', |
963 | | - 'oi_bits' => 'img_bits', |
964 | | - 'oi_timestamp' => 'img_timestamp', |
965 | | - 'oi_description' => 'img_description', |
966 | | - 'oi_user' => 'img_user', |
967 | | - 'oi_user_text' => 'img_user_text', |
968 | | - 'oi_metadata' => 'img_metadata', |
969 | | - 'oi_media_type' => 'img_media_type', |
970 | | - 'oi_major_mime' => 'img_major_mime', |
971 | | - 'oi_minor_mime' => 'img_minor_mime', |
972 | | - 'oi_sha1' => 'img_sha1' |
973 | | - ), array( 'img_name' => $this->getName() ), __METHOD__ |
974 | | - ); |
975 | | - |
976 | | - # Update the current image row |
977 | | - $dbw->update( 'image', |
978 | | - array( /* SET */ |
979 | | - 'img_size' => $this->size, |
980 | | - 'img_width' => intval( $this->width ), |
981 | | - 'img_height' => intval( $this->height ), |
982 | | - 'img_bits' => $this->bits, |
983 | | - 'img_media_type' => $this->media_type, |
984 | | - 'img_major_mime' => $this->major_mime, |
985 | | - 'img_minor_mime' => $this->minor_mime, |
986 | | - 'img_timestamp' => $timestamp, |
987 | | - 'img_description' => $comment, |
988 | | - 'img_user' => $user->getId(), |
989 | | - 'img_user_text' => $user->getName(), |
990 | | - 'img_metadata' => $this->metadata, |
991 | | - 'img_sha1' => $this->sha1 |
992 | | - ), array( /* WHERE */ |
993 | | - 'img_name' => $this->getName() |
994 | | - ), __METHOD__ |
995 | | - ); |
996 | | - } else { |
997 | | - # This is a new file |
998 | | - # Update the image count |
999 | | - $site_stats = $dbw->tableName( 'site_stats' ); |
1000 | | - $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ ); |
1001 | | - } |
1002 | | - |
1003 | | - $descTitle = $this->getTitle(); |
1004 | | - $article = new ImagePage( $descTitle ); |
1005 | | - $article->setFile( $this ); |
1006 | | - |
1007 | | - # Add the log entry |
1008 | | - $log = new LogPage( 'upload' ); |
1009 | | - $action = $reupload ? 'overwrite' : 'upload'; |
1010 | | - $log->addEntry( $action, $descTitle, $comment, array(), $user ); |
1011 | | - |
1012 | | - if ( $descTitle->exists() ) { |
1013 | | - # Create a null revision |
1014 | | - $latest = $descTitle->getLatestRevID(); |
1015 | | - $nullRevision = Revision::newNullRevision( |
1016 | | - $dbw, |
1017 | | - $descTitle->getArticleId(), |
1018 | | - $log->getRcComment(), |
1019 | | - false |
1020 | | - ); |
1021 | | - $nullRevision->insertOn( $dbw ); |
1022 | | - |
1023 | | - wfRunHooks( 'NewRevisionFromEditComplete', array( $article, $nullRevision, $latest, $user ) ); |
1024 | | - $article->updateRevisionOn( $dbw, $nullRevision ); |
1025 | | - |
1026 | | - # Invalidate the cache for the description page |
1027 | | - $descTitle->invalidateCache(); |
1028 | | - $descTitle->purgeSquid(); |
1029 | | - } else { |
1030 | | - # New file; create the description page. |
1031 | | - # There's already a log entry, so don't make a second RC entry |
1032 | | - # Squid and file cache for the description page are purged by doEdit. |
1033 | | - $article->doEdit( $pageText, $comment, EDIT_NEW | EDIT_SUPPRESS_RC ); |
1034 | | - } |
1035 | | - |
1036 | | - # Commit the transaction now, in case something goes wrong later |
1037 | | - # The most important thing is that files don't get lost, especially archives |
1038 | | - $dbw->commit(); |
1039 | | - |
1040 | | - # Save to cache and purge the squid |
1041 | | - # We shall not saveToCache before the commit since otherwise |
1042 | | - # in case of a rollback there is an usable file from memcached |
1043 | | - # which in fact doesn't really exist (bug 24978) |
1044 | | - $this->saveToCache(); |
1045 | | - |
1046 | | - # Hooks, hooks, the magic of hooks... |
1047 | | - wfRunHooks( 'FileUpload', array( $this, $reupload, $descTitle->exists() ) ); |
1048 | | - |
1049 | | - # Invalidate cache for all pages using this file |
1050 | | - $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' ); |
1051 | | - $update->doUpdate(); |
1052 | | - |
1053 | | - # Invalidate cache for all pages that redirects on this page |
1054 | | - $redirs = $this->getTitle()->getRedirectsHere(); |
1055 | | - |
1056 | | - foreach ( $redirs as $redir ) { |
1057 | | - $update = new HTMLCacheUpdate( $redir, 'imagelinks' ); |
1058 | | - $update->doUpdate(); |
1059 | | - } |
1060 | | - |
1061 | | - return true; |
1062 | | - } |
1063 | | - |
1064 | | - /** |
1065 | | - * Move or copy a file to its public location. If a file exists at the |
1066 | | - * destination, move it to an archive. Returns a FileRepoStatus object with |
1067 | | - * the archive name in the "value" member on success. |
1068 | | - * |
1069 | | - * The archive name should be passed through to recordUpload for database |
1070 | | - * registration. |
1071 | | - * |
1072 | | - * @param $srcPath String: local filesystem path to the source image |
1073 | | - * @param $flags Integer: a bitwise combination of: |
1074 | | - * File::DELETE_SOURCE Delete the source file, i.e. move |
1075 | | - * rather than copy |
1076 | | - * @return FileRepoStatus object. On success, the value member contains the |
1077 | | - * archive name, or an empty string if it was a new file. |
1078 | | - */ |
1079 | | - function publish( $srcPath, $flags = 0 ) { |
1080 | | - $this->lock(); |
1081 | | - |
1082 | | - $dstRel = $this->getRel(); |
1083 | | - $archiveName = gmdate( 'YmdHis' ) . '!' . $this->getName(); |
1084 | | - $archiveRel = 'archive/' . $this->getHashPath() . $archiveName; |
1085 | | - $flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0; |
1086 | | - $status = $this->repo->publish( $srcPath, $dstRel, $archiveRel, $flags ); |
1087 | | - |
1088 | | - if ( $status->value == 'new' ) { |
1089 | | - $status->value = ''; |
1090 | | - } else { |
1091 | | - $status->value = $archiveName; |
1092 | | - } |
1093 | | - |
1094 | | - $this->unlock(); |
1095 | | - |
1096 | | - return $status; |
1097 | | - } |
1098 | | - |
1099 | | - /** getLinksTo inherited */ |
1100 | | - /** getExifData inherited */ |
1101 | | - /** isLocal inherited */ |
1102 | | - /** wasDeleted inherited */ |
1103 | | - |
1104 | | - /** |
1105 | | - * Move file to the new title |
1106 | | - * |
1107 | | - * Move current, old version and all thumbnails |
1108 | | - * to the new filename. Old file is deleted. |
1109 | | - * |
1110 | | - * Cache purging is done; checks for validity |
1111 | | - * and logging are caller's responsibility |
1112 | | - * |
1113 | | - * @param $target Title New file name |
1114 | | - * @return FileRepoStatus object. |
1115 | | - */ |
1116 | | - function move( $target ) { |
1117 | | - wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() ); |
1118 | | - $this->lock(); |
1119 | | - |
1120 | | - $batch = new SwiftFileMoveBatch( $this, $target ); |
1121 | | - $batch->addCurrent(); |
1122 | | - $batch->addOlds(); |
1123 | | - |
1124 | | - $status = $batch->execute(); |
1125 | | - wfDebugLog( 'imagemove', "Finished moving {$this->name}" ); |
1126 | | - |
1127 | | - $this->purgeEverything(); |
1128 | | - $this->unlock(); |
1129 | | - |
1130 | | - if ( $status->isOk() ) { |
1131 | | - // Now switch the object |
1132 | | - $this->title = $target; |
1133 | | - // Force regeneration of the name and hashpath |
1134 | | - unset( $this->name ); |
1135 | | - unset( $this->hashPath ); |
1136 | | - // Purge the new image |
1137 | | - $this->purgeEverything(); |
1138 | | - } |
1139 | | - |
1140 | | - return $status; |
1141 | | - } |
1142 | | - |
1143 | | - /** |
1144 | | - * Delete all versions of the file. |
1145 | | - * |
1146 | | - * Moves the files into an archive directory (or deletes them) |
1147 | | - * and removes the database rows. |
1148 | | - * |
1149 | | - * Cache purging is done; logging is caller's responsibility. |
1150 | | - * |
1151 | | - * @param $reason |
1152 | | - * @param $suppress |
1153 | | - * @return FileRepoStatus object. |
1154 | | - */ |
1155 | | - function delete( $reason, $suppress = false ) { |
1156 | | - $this->lock(); |
1157 | | - |
1158 | | - $batch = new SwiftFileDeleteBatch( $this, $reason, $suppress ); |
1159 | | - $batch->addCurrent(); |
1160 | | - |
1161 | | - # Get old version relative paths |
1162 | | - $dbw = $this->repo->getMasterDB(); |
1163 | | - $result = $dbw->select( 'oldimage', |
1164 | | - array( 'oi_archive_name' ), |
1165 | | - array( 'oi_name' => $this->getName() ) ); |
1166 | | - foreach ( $result as $row ) { |
1167 | | - $batch->addOld( $row->oi_archive_name ); |
1168 | | - } |
1169 | | - //wfDebug(__METHOD__ . " deleting these files: " . var_export($batch, true) . "\n"); |
1170 | | - $status = $batch->execute(); |
1171 | | - |
1172 | | - if ( $status->ok ) { |
1173 | | - // Update site_stats |
1174 | | - $site_stats = $dbw->tableName( 'site_stats' ); |
1175 | | - $dbw->query( "UPDATE $site_stats SET ss_images=ss_images-1", __METHOD__ ); |
1176 | | - $this->purgeEverything(); |
1177 | | - } |
1178 | | - |
1179 | | - $this->unlock(); |
1180 | | - |
1181 | | - return $status; |
1182 | | - } |
1183 | | - |
1184 | | - /** |
1185 | | - * Delete an old version of the file. |
1186 | | - * |
1187 | | - * Moves the file into an archive directory (or deletes it) |
1188 | | - * and removes the database row. |
1189 | | - * |
1190 | | - * Cache purging is done; logging is caller's responsibility. |
1191 | | - * |
1192 | | - * @param $archiveName String |
1193 | | - * @param $reason String |
1194 | | - * @param $suppress Boolean |
1195 | | - * @throws MWException or FSException on database or file store failure |
1196 | | - * @return FileRepoStatus object. |
1197 | | - */ |
1198 | | - function deleteOld( $archiveName, $reason, $suppress = false ) { |
1199 | | - $this->lock(); |
1200 | | - |
1201 | | - $batch = new SwiftFileDeleteBatch( $this, $reason, $suppress ); |
1202 | | - $batch->addOld( $archiveName ); |
1203 | | - $status = $batch->execute(); |
1204 | | - |
1205 | | - $this->unlock(); |
1206 | | - |
1207 | | - if ( $status->ok ) { |
1208 | | - $this->purgeDescription(); |
1209 | | - $this->purgeHistory(); |
1210 | | - } |
1211 | | - |
1212 | | - return $status; |
1213 | | - } |
1214 | | - |
1215 | | - /** |
1216 | | - * Restore all or specified deleted revisions to the given file. |
1217 | | - * Permissions and logging are left to the caller. |
1218 | | - * |
1219 | | - * May throw database exceptions on error. |
1220 | | - * |
1221 | | - * @param $versions set of record ids of deleted items to restore, |
1222 | | - * or empty to restore all revisions. |
1223 | | - * @param $unsuppress Boolean |
1224 | | - * @return FileRepoStatus |
1225 | | - */ |
1226 | | - function restore( $versions = array(), $unsuppress = false ) { |
1227 | | - $batch = new SwiftFileRestoreBatch( $this, $unsuppress ); |
1228 | | - |
1229 | | - if ( !$versions ) { |
1230 | | - $batch->addAll(); |
1231 | | - } else { |
1232 | | - $batch->addIds( $versions ); |
1233 | | - } |
1234 | | - |
1235 | | - $status = $batch->execute(); |
1236 | | - |
1237 | | - if ( !$status->isGood() ) { |
1238 | | - return $status; |
1239 | | - } |
1240 | | - |
1241 | | - $cleanupStatus = $batch->cleanup(); |
1242 | | - $cleanupStatus->successCount = 0; |
1243 | | - $cleanupStatus->failCount = 0; |
1244 | | - $status->merge( $cleanupStatus ); |
1245 | | - |
1246 | | - return $status; |
1247 | | - } |
1248 | | - |
1249 | | - /** isMultipage inherited */ |
1250 | | - /** pageCount inherited */ |
1251 | | - /** scaleHeight inherited */ |
1252 | | - /** getImageSize inherited */ |
1253 | | - |
1254 | | - /** |
1255 | | - * Get the URL of the file description page. |
1256 | | - */ |
1257 | | - function getDescriptionUrl() { |
1258 | | - return $this->title->getLocalUrl(); |
1259 | | - } |
1260 | | - |
1261 | | - /** |
1262 | | - * Get the HTML text of the description page |
1263 | | - * This is not used by ImagePage for local files, since (among other things) |
1264 | | - * it skips the parser cache. |
1265 | | - */ |
1266 | | - function getDescriptionText() { |
1267 | | - global $wgParser; |
1268 | | - $revision = Revision::newFromTitle( $this->title ); |
1269 | | - if ( !$revision ) return false; |
1270 | | - $text = $revision->getText(); |
1271 | | - if ( !$text ) return false; |
1272 | | - $pout = $wgParser->parse( $text, $this->title, new ParserOptions() ); |
1273 | | - return $pout->getText(); |
1274 | | - } |
1275 | | - |
1276 | | - function getDescription() { |
1277 | | - $this->load(); |
1278 | | - return $this->description; |
1279 | | - } |
1280 | | - |
1281 | | - function getTimestamp() { |
1282 | | - $this->load(); |
1283 | | - return $this->timestamp; |
1284 | | - } |
1285 | | - |
1286 | | - function getSha1() { |
1287 | | - $this->load(); |
1288 | | - // Initialise now if necessary |
1289 | | - if ( $this->sha1 == '' && $this->fileExists ) { |
1290 | | - $this->sha1 = File::sha1Base36( $this->getPath() ); |
1291 | | - if ( !wfReadOnly() && strval( $this->sha1 ) != '' ) { |
1292 | | - $dbw = $this->repo->getMasterDB(); |
1293 | | - $dbw->update( 'image', |
1294 | | - array( 'img_sha1' => $this->sha1 ), |
1295 | | - array( 'img_name' => $this->getName() ), |
1296 | | - __METHOD__ ); |
1297 | | - $this->saveToCache(); |
1298 | | - } |
1299 | | - } |
1300 | | - |
1301 | | - return $this->sha1; |
1302 | | - } |
1303 | | - |
1304 | | - /** |
1305 | | - * Start a transaction and lock the image for update |
1306 | | - * Increments a reference counter if the lock is already held |
1307 | | - * @return boolean True if the image exists, false otherwise |
1308 | | - */ |
1309 | | - function lock() { |
1310 | | - $dbw = $this->repo->getMasterDB(); |
1311 | | - |
1312 | | - if ( !$this->locked ) { |
1313 | | - $dbw->begin(); |
1314 | | - $this->locked++; |
1315 | | - } |
1316 | | - |
1317 | | - return $dbw->selectField( 'image', '1', array( 'img_name' => $this->getName() ), __METHOD__ ); |
1318 | | - } |
1319 | | - |
1320 | | - /** |
1321 | | - * Decrement the lock reference count. If the reference count is reduced to zero, commits |
1322 | | - * the transaction and thereby releases the image lock. |
1323 | | - */ |
1324 | | - function unlock() { |
1325 | | - if ( $this->locked ) { |
1326 | | - --$this->locked; |
1327 | | - if ( !$this->locked ) { |
1328 | | - $dbw = $this->repo->getMasterDB(); |
1329 | | - $dbw->commit(); |
1330 | | - } |
1331 | | - } |
1332 | | - } |
1333 | | - |
1334 | | - /** |
1335 | | - * Roll back the DB transaction and mark the image unlocked |
1336 | | - */ |
1337 | | - function unlockAndRollback() { |
1338 | | - $this->locked = false; |
1339 | | - $dbw = $this->repo->getMasterDB(); |
1340 | | - $dbw->rollback(); |
1341 | | - } |
1342 | 313 | } // SwiftFile class |
1343 | 314 | |
1344 | 315 | # ------------------------------------------------------------------------------ |
1345 | 316 | |
1346 | 317 | /** |
1347 | | - * Helper class for file deletion |
1348 | | - * @ingroup FileRepo |
1349 | | - */ |
1350 | | -class SwiftFileDeleteBatch { |
1351 | | - |
1352 | | - /** |
1353 | | - * @var SwiftFile |
1354 | | - */ |
1355 | | - var $file; |
1356 | | - |
1357 | | - var $reason, $srcRels = array(), $archiveUrls = array(), $deletionBatch, $suppress; |
1358 | | - var $status; |
1359 | | - |
1360 | | - function __construct( File $file, $reason = '', $suppress = false ) { |
1361 | | - $this->file = $file; |
1362 | | - $this->reason = $reason; |
1363 | | - $this->suppress = $suppress; |
1364 | | - $this->status = $file->repo->newGood(); |
1365 | | - } |
1366 | | - |
1367 | | - function addCurrent() { |
1368 | | - $this->srcRels['.'] = $this->file->getRel(); |
1369 | | - } |
1370 | | - |
1371 | | - function addOld( $oldName ) { |
1372 | | - $this->srcRels[$oldName] = $this->file->getArchiveRel( $oldName ); |
1373 | | - $this->archiveUrls[] = $this->file->getArchiveUrl( $oldName ); |
1374 | | - } |
1375 | | - |
1376 | | - function getOldRels() { |
1377 | | - if ( !isset( $this->srcRels['.'] ) ) { |
1378 | | - $oldRels =& $this->srcRels; |
1379 | | - $deleteCurrent = false; |
1380 | | - } else { |
1381 | | - $oldRels = $this->srcRels; |
1382 | | - unset( $oldRels['.'] ); |
1383 | | - $deleteCurrent = true; |
1384 | | - } |
1385 | | - |
1386 | | - return array( $oldRels, $deleteCurrent ); |
1387 | | - } |
1388 | | - |
1389 | | - /*protected*/ function getHashes() { |
1390 | | - $hashes = array(); |
1391 | | - list( $oldRels, $deleteCurrent ) = $this->getOldRels(); |
1392 | | - |
1393 | | - if ( $deleteCurrent ) { |
1394 | | - $hashes['.'] = $this->file->getSha1(); |
1395 | | - } |
1396 | | - |
1397 | | - if ( count( $oldRels ) ) { |
1398 | | - $dbw = $this->file->repo->getMasterDB(); |
1399 | | - $res = $dbw->select( |
1400 | | - 'oldimage', |
1401 | | - array( 'oi_archive_name', 'oi_sha1' ), |
1402 | | - 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')', |
1403 | | - __METHOD__ |
1404 | | - ); |
1405 | | - |
1406 | | - foreach ( $res as $row ) { |
1407 | | - if ( rtrim( $row->oi_sha1, "\0" ) === '' ) { |
1408 | | - // Get the hash from the file |
1409 | | - $oldUrl = $this->file->getArchiveVirtualUrl( $row->oi_archive_name ); |
1410 | | - $props = $this->file->repo->getFileProps( $oldUrl ); |
1411 | | - |
1412 | | - if ( $props['fileExists'] ) { |
1413 | | - // Upgrade the oldimage row |
1414 | | - $dbw->update( 'oldimage', |
1415 | | - array( 'oi_sha1' => $props['sha1'] ), |
1416 | | - array( 'oi_name' => $this->file->getName(), 'oi_archive_name' => $row->oi_archive_name ), |
1417 | | - __METHOD__ ); |
1418 | | - $hashes[$row->oi_archive_name] = $props['sha1']; |
1419 | | - } else { |
1420 | | - $hashes[$row->oi_archive_name] = false; |
1421 | | - } |
1422 | | - } else { |
1423 | | - $hashes[$row->oi_archive_name] = $row->oi_sha1; |
1424 | | - } |
1425 | | - } |
1426 | | - } |
1427 | | - |
1428 | | - $missing = array_diff_key( $this->srcRels, $hashes ); |
1429 | | - |
1430 | | - foreach ( $missing as $name => $rel ) { |
1431 | | - $this->status->error( 'filedelete-old-unregistered', $name ); |
1432 | | - } |
1433 | | - |
1434 | | - foreach ( $hashes as $name => $hash ) { |
1435 | | - if ( !$hash ) { |
1436 | | - $this->status->error( 'filedelete-missing', $this->srcRels[$name] ); |
1437 | | - unset( $hashes[$name] ); |
1438 | | - } |
1439 | | - } |
1440 | | - |
1441 | | - return $hashes; |
1442 | | - } |
1443 | | - |
1444 | | - function doDBInserts() { |
1445 | | - global $wgUser; |
1446 | | - |
1447 | | - $dbw = $this->file->repo->getMasterDB(); |
1448 | | - $encTimestamp = $dbw->addQuotes( $dbw->timestamp() ); |
1449 | | - $encUserId = $dbw->addQuotes( $wgUser->getId() ); |
1450 | | - $encReason = $dbw->addQuotes( $this->reason ); |
1451 | | - $encGroup = $dbw->addQuotes( 'deleted' ); |
1452 | | - $ext = $this->file->getExtension(); |
1453 | | - $dotExt = $ext === '' ? '' : ".$ext"; |
1454 | | - $encExt = $dbw->addQuotes( $dotExt ); |
1455 | | - list( $oldRels, $deleteCurrent ) = $this->getOldRels(); |
1456 | | - |
1457 | | - // Bitfields to further suppress the content |
1458 | | - if ( $this->suppress ) { |
1459 | | - $bitfield = 0; |
1460 | | - // This should be 15... |
1461 | | - $bitfield |= Revision::DELETED_TEXT; |
1462 | | - $bitfield |= Revision::DELETED_COMMENT; |
1463 | | - $bitfield |= Revision::DELETED_USER; |
1464 | | - $bitfield |= Revision::DELETED_RESTRICTED; |
1465 | | - } else { |
1466 | | - $bitfield = 'oi_deleted'; |
1467 | | - } |
1468 | | - |
1469 | | - if ( $deleteCurrent ) { |
1470 | | - $concat = $dbw->buildConcat( array( "img_sha1", $encExt ) ); |
1471 | | - $where = array( 'img_name' => $this->file->getName() ); |
1472 | | - $dbw->insertSelect( 'filearchive', 'image', |
1473 | | - array( |
1474 | | - 'fa_storage_group' => $encGroup, |
1475 | | - 'fa_storage_key' => "CASE WHEN img_sha1='' THEN '' ELSE $concat END", |
1476 | | - 'fa_deleted_user' => $encUserId, |
1477 | | - 'fa_deleted_timestamp' => $encTimestamp, |
1478 | | - 'fa_deleted_reason' => $encReason, |
1479 | | - 'fa_deleted' => $this->suppress ? $bitfield : 0, |
1480 | | - |
1481 | | - 'fa_name' => 'img_name', |
1482 | | - 'fa_archive_name' => 'NULL', |
1483 | | - 'fa_size' => 'img_size', |
1484 | | - 'fa_width' => 'img_width', |
1485 | | - 'fa_height' => 'img_height', |
1486 | | - 'fa_metadata' => 'img_metadata', |
1487 | | - 'fa_bits' => 'img_bits', |
1488 | | - 'fa_media_type' => 'img_media_type', |
1489 | | - 'fa_major_mime' => 'img_major_mime', |
1490 | | - 'fa_minor_mime' => 'img_minor_mime', |
1491 | | - 'fa_description' => 'img_description', |
1492 | | - 'fa_user' => 'img_user', |
1493 | | - 'fa_user_text' => 'img_user_text', |
1494 | | - 'fa_timestamp' => 'img_timestamp' |
1495 | | - ), $where, __METHOD__ ); |
1496 | | - } |
1497 | | - |
1498 | | - if ( count( $oldRels ) ) { |
1499 | | - $concat = $dbw->buildConcat( array( "oi_sha1", $encExt ) ); |
1500 | | - $where = array( |
1501 | | - 'oi_name' => $this->file->getName(), |
1502 | | - 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')' ); |
1503 | | - $dbw->insertSelect( 'filearchive', 'oldimage', |
1504 | | - array( |
1505 | | - 'fa_storage_group' => $encGroup, |
1506 | | - 'fa_storage_key' => "CASE WHEN oi_sha1='' THEN '' ELSE $concat END", |
1507 | | - 'fa_deleted_user' => $encUserId, |
1508 | | - 'fa_deleted_timestamp' => $encTimestamp, |
1509 | | - 'fa_deleted_reason' => $encReason, |
1510 | | - 'fa_deleted' => $this->suppress ? $bitfield : 'oi_deleted', |
1511 | | - |
1512 | | - 'fa_name' => 'oi_name', |
1513 | | - 'fa_archive_name' => 'oi_archive_name', |
1514 | | - 'fa_size' => 'oi_size', |
1515 | | - 'fa_width' => 'oi_width', |
1516 | | - 'fa_height' => 'oi_height', |
1517 | | - 'fa_metadata' => 'oi_metadata', |
1518 | | - 'fa_bits' => 'oi_bits', |
1519 | | - 'fa_media_type' => 'oi_media_type', |
1520 | | - 'fa_major_mime' => 'oi_major_mime', |
1521 | | - 'fa_minor_mime' => 'oi_minor_mime', |
1522 | | - 'fa_description' => 'oi_description', |
1523 | | - 'fa_user' => 'oi_user', |
1524 | | - 'fa_user_text' => 'oi_user_text', |
1525 | | - 'fa_timestamp' => 'oi_timestamp', |
1526 | | - 'fa_deleted' => $bitfield |
1527 | | - ), $where, __METHOD__ ); |
1528 | | - } |
1529 | | - } |
1530 | | - |
1531 | | - function doDBDeletes() { |
1532 | | - $dbw = $this->file->repo->getMasterDB(); |
1533 | | - list( $oldRels, $deleteCurrent ) = $this->getOldRels(); |
1534 | | - |
1535 | | - if ( count( $oldRels ) ) { |
1536 | | - $dbw->delete( 'oldimage', |
1537 | | - array( |
1538 | | - 'oi_name' => $this->file->getName(), |
1539 | | - 'oi_archive_name' => array_keys( $oldRels ) |
1540 | | - ), __METHOD__ ); |
1541 | | - } |
1542 | | - |
1543 | | - if ( $deleteCurrent ) { |
1544 | | - $dbw->delete( 'image', array( 'img_name' => $this->file->getName() ), __METHOD__ ); |
1545 | | - } |
1546 | | - } |
1547 | | - |
1548 | | - /** |
1549 | | - * Run the transaction |
1550 | | - */ |
1551 | | - function execute() { |
1552 | | - global $wgUseSquid; |
1553 | | - wfProfileIn( __METHOD__ ); |
1554 | | - |
1555 | | - $this->file->lock(); |
1556 | | - // Leave private files alone |
1557 | | - $privateFiles = array(); |
1558 | | - list( $oldRels, $deleteCurrent ) = $this->getOldRels(); |
1559 | | - $dbw = $this->file->repo->getMasterDB(); |
1560 | | - |
1561 | | - if ( !empty( $oldRels ) ) { |
1562 | | - $res = $dbw->select( 'oldimage', |
1563 | | - array( 'oi_archive_name' ), |
1564 | | - array( 'oi_name' => $this->file->getName(), |
1565 | | - 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')', |
1566 | | - $dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE ), |
1567 | | - __METHOD__ ); |
1568 | | - |
1569 | | - foreach ( $res as $row ) { |
1570 | | - $privateFiles[$row->oi_archive_name] = 1; |
1571 | | - } |
1572 | | - } |
1573 | | - // Prepare deletion batch |
1574 | | - $hashes = $this->getHashes(); |
1575 | | - $this->deletionBatch = array(); |
1576 | | - $ext = $this->file->getExtension(); |
1577 | | - $dotExt = $ext === '' ? '' : ".$ext"; |
1578 | | - |
1579 | | - foreach ( $this->srcRels as $name => $srcRel ) { |
1580 | | - // Skip files that have no hash (missing source). |
1581 | | - // Keep private files where they are. |
1582 | | - if ( isset( $hashes[$name] ) && !array_key_exists( $name, $privateFiles ) ) { |
1583 | | - $hash = $hashes[$name]; |
1584 | | - $key = $hash . $dotExt; |
1585 | | - $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key; |
1586 | | - $this->deletionBatch[$name] = array( $srcRel, $dstRel ); |
1587 | | - } |
1588 | | - } |
1589 | | - |
1590 | | - // Lock the filearchive rows so that the files don't get deleted by a cleanup operation |
1591 | | - // We acquire this lock by running the inserts now, before the file operations. |
1592 | | - // |
1593 | | - // This potentially has poor lock contention characteristics -- an alternative |
1594 | | - // scheme would be to insert stub filearchive entries with no fa_name and commit |
1595 | | - // them in a separate transaction, then run the file ops, then update the fa_name fields. |
1596 | | - $this->doDBInserts(); |
1597 | | - |
1598 | | - // Removes non-existent file from the batch, so we don't get errors. |
1599 | | - $this->deletionBatch = $this->removeNonexistentFiles( $this->deletionBatch ); |
1600 | | - |
1601 | | - // Execute the file deletion batch |
1602 | | - $status = $this->file->repo->deleteBatch( $this->deletionBatch ); |
1603 | | - |
1604 | | - if ( !$status->isGood() ) { |
1605 | | - $this->status->merge( $status ); |
1606 | | - } |
1607 | | - |
1608 | | - if ( !$this->status->ok ) { |
1609 | | - // Critical file deletion error |
1610 | | - // Roll back inserts, release lock and abort |
1611 | | - // TODO: delete the defunct filearchive rows if we are using a non-transactional DB |
1612 | | - $this->file->unlockAndRollback(); |
1613 | | - wfProfileOut( __METHOD__ ); |
1614 | | - return $this->status; |
1615 | | - } |
1616 | | - |
1617 | | - // Purge squid |
1618 | | - if ( $wgUseSquid ) { |
1619 | | - $urls = array(); |
1620 | | - |
1621 | | - foreach ( $this->srcRels as $srcRel ) { |
1622 | | - $urlRel = str_replace( '%2F', '/', rawurlencode( $srcRel ) ); |
1623 | | - $urls[] = $this->file->repo->getZoneUrl( 'public' ) . '/' . $urlRel; |
1624 | | - } |
1625 | | - SquidUpdate::purge( $urls ); |
1626 | | - } |
1627 | | - |
1628 | | - // Delete image/oldimage rows |
1629 | | - $this->doDBDeletes(); |
1630 | | - |
1631 | | - // Commit and return |
1632 | | - $this->file->unlock(); |
1633 | | - wfProfileOut( __METHOD__ ); |
1634 | | - |
1635 | | - return $this->status; |
1636 | | - } |
1637 | | - |
1638 | | - /** |
1639 | | - * Removes non-existent files from a deletion batch. |
1640 | | - */ |
1641 | | - function removeNonexistentFiles( $batch ) { |
1642 | | - $files = $newBatch = array(); |
1643 | | - |
1644 | | - foreach ( $batch as $batchItem ) { |
1645 | | - list( $src, $dest ) = $batchItem; |
1646 | | - $files[$src] = $this->file->repo->getVirtualUrl( 'public' ) . '/' . rawurlencode( $src ); |
1647 | | - } |
1648 | | - |
1649 | | - $result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY ); |
1650 | | - |
1651 | | - foreach ( $batch as $batchItem ) { |
1652 | | - if ( $result[$batchItem[0]] ) { |
1653 | | - $newBatch[] = $batchItem; |
1654 | | - } |
1655 | | - } |
1656 | | - |
1657 | | - return $newBatch; |
1658 | | - } |
1659 | | -} |
1660 | | - |
1661 | | -# ------------------------------------------------------------------------------ |
1662 | | - |
1663 | | -/** |
1664 | | - * Helper class for file undeletion |
1665 | | - * @ingroup FileRepo |
1666 | | - */ |
1667 | | -class SwiftFileRestoreBatch { |
1668 | | - /** |
1669 | | - * @var SwiftFile |
1670 | | - */ |
1671 | | - var $file; |
1672 | | - |
1673 | | - var $cleanupBatch, $ids, $all, $unsuppress = false; |
1674 | | - |
1675 | | - function __construct( File $file, $unsuppress = false ) { |
1676 | | - $this->file = $file; |
1677 | | - $this->cleanupBatch = $this->ids = array(); |
1678 | | - $this->ids = array(); |
1679 | | - $this->unsuppress = $unsuppress; |
1680 | | - } |
1681 | | - |
1682 | | - /** |
1683 | | - * Add a file by ID |
1684 | | - */ |
1685 | | - function addId( $fa_id ) { |
1686 | | - $this->ids[] = $fa_id; |
1687 | | - } |
1688 | | - |
1689 | | - /** |
1690 | | - * Add a whole lot of files by ID |
1691 | | - */ |
1692 | | - function addIds( $ids ) { |
1693 | | - $this->ids = array_merge( $this->ids, $ids ); |
1694 | | - } |
1695 | | - |
1696 | | - /** |
1697 | | - * Add all revisions of the file |
1698 | | - */ |
1699 | | - function addAll() { |
1700 | | - $this->all = true; |
1701 | | - } |
1702 | | - |
1703 | | - /** |
1704 | | - * Run the transaction, except the cleanup batch. |
1705 | | - * The cleanup batch should be run in a separate transaction, because it locks different |
1706 | | - * rows and there's no need to keep the image row locked while it's acquiring those locks |
1707 | | - * The caller may have its own transaction open. |
1708 | | - * So we save the batch and let the caller call cleanup() |
1709 | | - */ |
1710 | | - function execute() { |
1711 | | - global $wgLang; |
1712 | | - |
1713 | | - if ( !$this->all && !$this->ids ) { |
1714 | | - // Do nothing |
1715 | | - return $this->file->repo->newGood(); |
1716 | | - } |
1717 | | - |
1718 | | - $exists = $this->file->lock(); |
1719 | | - $dbw = $this->file->repo->getMasterDB(); |
1720 | | - $status = $this->file->repo->newGood(); |
1721 | | - |
1722 | | - // Fetch all or selected archived revisions for the file, |
1723 | | - // sorted from the most recent to the oldest. |
1724 | | - $conditions = array( 'fa_name' => $this->file->getName() ); |
1725 | | - |
1726 | | - if ( !$this->all ) { |
1727 | | - $conditions[] = 'fa_id IN (' . $dbw->makeList( $this->ids ) . ')'; |
1728 | | - } |
1729 | | - |
1730 | | - $result = $dbw->select( 'filearchive', '*', |
1731 | | - $conditions, |
1732 | | - __METHOD__, |
1733 | | - array( 'ORDER BY' => 'fa_timestamp DESC' ) |
1734 | | - ); |
1735 | | - |
1736 | | - $idsPresent = array(); |
1737 | | - $storeBatch = array(); |
1738 | | - $insertBatch = array(); |
1739 | | - $insertCurrent = false; |
1740 | | - $deleteIds = array(); |
1741 | | - $first = true; |
1742 | | - $archiveNames = array(); |
1743 | | - |
1744 | | - foreach ( $result as $row ) { |
1745 | | - $idsPresent[] = $row->fa_id; |
1746 | | - |
1747 | | - if ( $row->fa_name != $this->file->getName() ) { |
1748 | | - $status->error( 'undelete-filename-mismatch', $wgLang->timeanddate( $row->fa_timestamp ) ); |
1749 | | - $status->failCount++; |
1750 | | - continue; |
1751 | | - } |
1752 | | - |
1753 | | - if ( $row->fa_storage_key == '' ) { |
1754 | | - // Revision was missing pre-deletion |
1755 | | - $status->error( 'undelete-bad-store-key', $wgLang->timeanddate( $row->fa_timestamp ) ); |
1756 | | - $status->failCount++; |
1757 | | - continue; |
1758 | | - } |
1759 | | - |
1760 | | - $deletedRel = $this->file->repo->getDeletedHashPath( $row->fa_storage_key ) . $row->fa_storage_key; |
1761 | | - $deletedUrl = $this->file->repo->getVirtualUrl() . '/deleted/' . $deletedRel; |
1762 | | - |
1763 | | - $sha1 = substr( $row->fa_storage_key, 0, strcspn( $row->fa_storage_key, '.' ) ); |
1764 | | - |
1765 | | - # Fix leading zero |
1766 | | - if ( strlen( $sha1 ) == 32 && $sha1[0] == '0' ) { |
1767 | | - $sha1 = substr( $sha1, 1 ); |
1768 | | - } |
1769 | | - |
1770 | | - if ( is_null( $row->fa_major_mime ) || $row->fa_major_mime == 'unknown' |
1771 | | - || is_null( $row->fa_minor_mime ) || $row->fa_minor_mime == 'unknown' |
1772 | | - || is_null( $row->fa_media_type ) || $row->fa_media_type == 'UNKNOWN' |
1773 | | - || is_null( $row->fa_metadata ) ) { |
1774 | | - // Refresh our metadata |
1775 | | - // Required for a new current revision; nice for older ones too. :) |
1776 | | - $props = RepoGroup::singleton()->getFileProps( $deletedUrl ); |
1777 | | - } else { |
1778 | | - $props = array( |
1779 | | - 'minor_mime' => $row->fa_minor_mime, |
1780 | | - 'major_mime' => $row->fa_major_mime, |
1781 | | - 'media_type' => $row->fa_media_type, |
1782 | | - 'metadata' => $row->fa_metadata |
1783 | | - ); |
1784 | | - } |
1785 | | - |
1786 | | - if ( $first && !$exists ) { |
1787 | | - // This revision will be published as the new current version |
1788 | | - $destRel = $this->file->getRel(); |
1789 | | - $insertCurrent = array( |
1790 | | - 'img_name' => $row->fa_name, |
1791 | | - 'img_size' => $row->fa_size, |
1792 | | - 'img_width' => $row->fa_width, |
1793 | | - 'img_height' => $row->fa_height, |
1794 | | - 'img_metadata' => $props['metadata'], |
1795 | | - 'img_bits' => $row->fa_bits, |
1796 | | - 'img_media_type' => $props['media_type'], |
1797 | | - 'img_major_mime' => $props['major_mime'], |
1798 | | - 'img_minor_mime' => $props['minor_mime'], |
1799 | | - 'img_description' => $row->fa_description, |
1800 | | - 'img_user' => $row->fa_user, |
1801 | | - 'img_user_text' => $row->fa_user_text, |
1802 | | - 'img_timestamp' => $row->fa_timestamp, |
1803 | | - 'img_sha1' => $sha1 |
1804 | | - ); |
1805 | | - |
1806 | | - // The live (current) version cannot be hidden! |
1807 | | - if ( !$this->unsuppress && $row->fa_deleted ) { |
1808 | | - $storeBatch[] = array( $deletedUrl, 'public', $destRel ); |
1809 | | - $this->cleanupBatch[] = $row->fa_storage_key; |
1810 | | - } |
1811 | | - } else { |
1812 | | - $archiveName = $row->fa_archive_name; |
1813 | | - |
1814 | | - if ( $archiveName == '' ) { |
1815 | | - // This was originally a current version; we |
1816 | | - // have to devise a new archive name for it. |
1817 | | - // Format is <timestamp of archiving>!<name> |
1818 | | - $timestamp = wfTimestamp( TS_UNIX, $row->fa_deleted_timestamp ); |
1819 | | - |
1820 | | - do { |
1821 | | - $archiveName = wfTimestamp( TS_MW, $timestamp ) . '!' . $row->fa_name; |
1822 | | - $timestamp++; |
1823 | | - } while ( isset( $archiveNames[$archiveName] ) ); |
1824 | | - } |
1825 | | - |
1826 | | - $archiveNames[$archiveName] = true; |
1827 | | - $destRel = $this->file->getArchiveRel( $archiveName ); |
1828 | | - $insertBatch[] = array( |
1829 | | - 'oi_name' => $row->fa_name, |
1830 | | - 'oi_archive_name' => $archiveName, |
1831 | | - 'oi_size' => $row->fa_size, |
1832 | | - 'oi_width' => $row->fa_width, |
1833 | | - 'oi_height' => $row->fa_height, |
1834 | | - 'oi_bits' => $row->fa_bits, |
1835 | | - 'oi_description' => $row->fa_description, |
1836 | | - 'oi_user' => $row->fa_user, |
1837 | | - 'oi_user_text' => $row->fa_user_text, |
1838 | | - 'oi_timestamp' => $row->fa_timestamp, |
1839 | | - 'oi_metadata' => $props['metadata'], |
1840 | | - 'oi_media_type' => $props['media_type'], |
1841 | | - 'oi_major_mime' => $props['major_mime'], |
1842 | | - 'oi_minor_mime' => $props['minor_mime'], |
1843 | | - 'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted, |
1844 | | - 'oi_sha1' => $sha1 ); |
1845 | | - } |
1846 | | - |
1847 | | - $deleteIds[] = $row->fa_id; |
1848 | | - |
1849 | | - if ( !$this->unsuppress && $row->fa_deleted & File::DELETED_FILE ) { |
1850 | | - // private files can stay where they are |
1851 | | - $status->successCount++; |
1852 | | - } else { |
1853 | | - $storeBatch[] = array( $deletedUrl, 'public', $destRel ); |
1854 | | - $this->cleanupBatch[] = $row->fa_storage_key; |
1855 | | - } |
1856 | | - |
1857 | | - $first = false; |
1858 | | - } |
1859 | | - |
1860 | | - unset( $result ); |
1861 | | - |
1862 | | - // Add a warning to the status object for missing IDs |
1863 | | - $missingIds = array_diff( $this->ids, $idsPresent ); |
1864 | | - |
1865 | | - foreach ( $missingIds as $id ) { |
1866 | | - $status->error( 'undelete-missing-filearchive', $id ); |
1867 | | - } |
1868 | | - |
1869 | | - // Remove missing files from batch, so we don't get errors when undeleting them |
1870 | | - $storeBatch = $this->removeNonexistentFiles( $storeBatch ); |
1871 | | - |
1872 | | - // Run the store batch |
1873 | | - // Use the OVERWRITE_SAME flag to smooth over a common error |
1874 | | - $storeStatus = $this->file->repo->storeBatch( $storeBatch, FileRepo::OVERWRITE_SAME ); |
1875 | | - $status->merge( $storeStatus ); |
1876 | | - |
1877 | | - if ( !$status->isGood() ) { |
1878 | | - // Even if some files could be copied, fail entirely as that is the |
1879 | | - // easiest thing to do without data loss |
1880 | | - $this->cleanupFailedBatch( $storeStatus, $storeBatch ); |
1881 | | - $status->ok = false; |
1882 | | - $this->file->unlock(); |
1883 | | - |
1884 | | - return $status; |
1885 | | - } |
1886 | | - |
1887 | | - // Run the DB updates |
1888 | | - // Because we have locked the image row, key conflicts should be rare. |
1889 | | - // If they do occur, we can roll back the transaction at this time with |
1890 | | - // no data loss, but leaving unregistered files scattered throughout the |
1891 | | - // public zone. |
1892 | | - // This is not ideal, which is why it's important to lock the image row. |
1893 | | - if ( $insertCurrent ) { |
1894 | | - $dbw->insert( 'image', $insertCurrent, __METHOD__ ); |
1895 | | - } |
1896 | | - |
1897 | | - if ( $insertBatch ) { |
1898 | | - $dbw->insert( 'oldimage', $insertBatch, __METHOD__ ); |
1899 | | - } |
1900 | | - |
1901 | | - if ( $deleteIds ) { |
1902 | | - $dbw->delete( 'filearchive', |
1903 | | - array( 'fa_id IN (' . $dbw->makeList( $deleteIds ) . ')' ), |
1904 | | - __METHOD__ ); |
1905 | | - } |
1906 | | - |
1907 | | - // If store batch is empty (all files are missing), deletion is to be considered successful |
1908 | | - if ( $status->successCount > 0 || !$storeBatch ) { |
1909 | | - if ( !$exists ) { |
1910 | | - wfDebug( __METHOD__ . " restored {$status->successCount} items, creating a new current\n" ); |
1911 | | - |
1912 | | - // Update site_stats |
1913 | | - $site_stats = $dbw->tableName( 'site_stats' ); |
1914 | | - $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ ); |
1915 | | - |
1916 | | - $this->file->purgeEverything(); |
1917 | | - } else { |
1918 | | - wfDebug( __METHOD__ . " restored {$status->successCount} as archived versions\n" ); |
1919 | | - $this->file->purgeDescription(); |
1920 | | - $this->file->purgeHistory(); |
1921 | | - } |
1922 | | - } |
1923 | | - |
1924 | | - $this->file->unlock(); |
1925 | | - |
1926 | | - return $status; |
1927 | | - } |
1928 | | - |
1929 | | - /** |
1930 | | - * Removes non-existent files from a store batch. |
1931 | | - */ |
1932 | | - function removeNonexistentFiles( $triplets ) { |
1933 | | - $files = $filteredTriplets = array(); |
1934 | | - foreach ( $triplets as $file ) |
1935 | | - $files[$file[0]] = $file[0]; |
1936 | | - |
1937 | | - $result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY ); |
1938 | | - |
1939 | | - foreach ( $triplets as $file ) { |
1940 | | - if ( $result[$file[0]] ) { |
1941 | | - $filteredTriplets[] = $file; |
1942 | | - } |
1943 | | - } |
1944 | | - |
1945 | | - return $filteredTriplets; |
1946 | | - } |
1947 | | - |
1948 | | - /** |
1949 | | - * Removes non-existent files from a cleanup batch. |
1950 | | - */ |
1951 | | - function removeNonexistentFromCleanup( $batch ) { |
1952 | | - $files = $newBatch = array(); |
1953 | | - $repo = $this->file->repo; |
1954 | | - |
1955 | | - foreach ( $batch as $file ) { |
1956 | | - $files[$file] = $repo->getVirtualUrl( 'deleted' ) . '/' . |
1957 | | - rawurlencode( $repo->getDeletedHashPath( $file ) . $file ); |
1958 | | - } |
1959 | | - |
1960 | | - $result = $repo->fileExistsBatch( $files, FSRepo::FILES_ONLY ); |
1961 | | - |
1962 | | - foreach ( $batch as $file ) { |
1963 | | - if ( $result[$file] ) { |
1964 | | - $newBatch[] = $file; |
1965 | | - } |
1966 | | - } |
1967 | | - |
1968 | | - return $newBatch; |
1969 | | - } |
1970 | | - |
1971 | | - /** |
1972 | | - * Delete unused files in the deleted zone. |
1973 | | - * This should be called from outside the transaction in which execute() was called. |
1974 | | - */ |
1975 | | - function cleanup() { |
1976 | | - if ( !$this->cleanupBatch ) { |
1977 | | - return $this->file->repo->newGood(); |
1978 | | - } |
1979 | | - |
1980 | | - $this->cleanupBatch = $this->removeNonexistentFromCleanup( $this->cleanupBatch ); |
1981 | | - |
1982 | | - $status = $this->file->repo->cleanupDeletedBatch( $this->cleanupBatch ); |
1983 | | - |
1984 | | - return $status; |
1985 | | - } |
1986 | | - |
1987 | | - function cleanupFailedBatch( $storeStatus, $storeBatch ) { |
1988 | | - $cleanupBatch = array(); |
1989 | | - |
1990 | | - foreach ( $storeStatus->success as $i => $success ) { |
1991 | | - if ( $success ) { |
1992 | | - $cleanupBatch[] = array( $storeBatch[$i][1], $storeBatch[$i][1] ); |
1993 | | - } |
1994 | | - } |
1995 | | - $this->file->repo->cleanupBatch( $cleanupBatch ); |
1996 | | - } |
1997 | | -} |
1998 | | - |
1999 | | -# ------------------------------------------------------------------------------ |
2000 | | - |
2001 | | -/** |
2002 | | - * Helper class for file movement |
2003 | | - * @ingroup FileRepo |
2004 | | - */ |
2005 | | -class SwiftFileMoveBatch { |
2006 | | - var $file, $cur, $olds, $oldCount, $archive, $target, $db; |
2007 | | - |
2008 | | - function __construct( File $file, Title $target ) { |
2009 | | - $this->file = $file; |
2010 | | - $this->target = $target; |
2011 | | - $this->oldHash = $this->file->repo->getHashPath( $this->file->getName() ); |
2012 | | - $this->newHash = $this->file->repo->getHashPath( $this->target->getDBkey() ); |
2013 | | - $this->oldName = $this->file->getName(); |
2014 | | - $this->newName = $this->file->repo->getNameFromTitle( $this->target ); |
2015 | | - $this->oldRel = $this->oldHash . $this->oldName; |
2016 | | - $this->newRel = $this->newHash . $this->newName; |
2017 | | - $this->db = $file->repo->getMasterDb(); |
2018 | | - } |
2019 | | - |
2020 | | - /** |
2021 | | - * Add the current image to the batch |
2022 | | - */ |
2023 | | - function addCurrent() { |
2024 | | - $this->cur = array( $this->oldRel, $this->newRel ); |
2025 | | - } |
2026 | | - |
2027 | | - /** |
2028 | | - * Add the old versions of the image to the batch |
2029 | | - */ |
2030 | | - function addOlds() { |
2031 | | - $archiveBase = 'archive'; |
2032 | | - $this->olds = array(); |
2033 | | - $this->oldCount = 0; |
2034 | | - |
2035 | | - $result = $this->db->select( 'oldimage', |
2036 | | - array( 'oi_archive_name', 'oi_deleted' ), |
2037 | | - array( 'oi_name' => $this->oldName ), |
2038 | | - __METHOD__ |
2039 | | - ); |
2040 | | - |
2041 | | - foreach ( $result as $row ) { |
2042 | | - $oldName = $row->oi_archive_name; |
2043 | | - $bits = explode( '!', $oldName, 2 ); |
2044 | | - |
2045 | | - if ( count( $bits ) != 2 ) { |
2046 | | - wfDebug( "Old file name missing !: '$oldName' \n" ); |
2047 | | - continue; |
2048 | | - } |
2049 | | - |
2050 | | - list( $timestamp, $filename ) = $bits; |
2051 | | - |
2052 | | - if ( $this->oldName != $filename ) { |
2053 | | - wfDebug( "Old file name doesn't match: '$oldName' \n" ); |
2054 | | - continue; |
2055 | | - } |
2056 | | - |
2057 | | - $this->oldCount++; |
2058 | | - |
2059 | | - // Do we want to add those to oldCount? |
2060 | | - if ( $row->oi_deleted & File::DELETED_FILE ) { |
2061 | | - continue; |
2062 | | - } |
2063 | | - |
2064 | | - $this->olds[] = array( |
2065 | | - "{$archiveBase}/{$this->oldHash}{$oldName}", |
2066 | | - "{$archiveBase}/{$this->newHash}{$timestamp}!{$this->newName}" |
2067 | | - ); |
2068 | | - } |
2069 | | - } |
2070 | | - |
2071 | | - /** |
2072 | | - * Perform the move. |
2073 | | - */ |
2074 | | - function execute() { |
2075 | | - $repo = $this->file->repo; |
2076 | | - $status = $repo->newGood(); |
2077 | | - $triplets = $this->getMoveTriplets(); |
2078 | | - |
2079 | | - $triplets = $this->removeNonexistentFiles( $triplets ); |
2080 | | - |
2081 | | - // Copy the files into their new location |
2082 | | - $statusMove = $repo->storeBatch( $triplets ); |
2083 | | - wfDebugLog( 'imagemove', "Moved files for {$this->file->name}: {$statusMove->successCount} successes, {$statusMove->failCount} failures" ); |
2084 | | - if ( !$statusMove->isGood() ) { |
2085 | | - wfDebugLog( 'imagemove', "Error in moving files: " . $statusMove->getWikiText() ); |
2086 | | - $this->cleanupTarget( $triplets ); |
2087 | | - $statusMove->ok = false; |
2088 | | - return $statusMove; |
2089 | | - } |
2090 | | - |
2091 | | - $this->db->begin(); |
2092 | | - $statusDb = $this->doDBUpdates(); |
2093 | | - wfDebugLog( 'imagemove', "Renamed {$this->file->name} in database: {$statusDb->successCount} successes, {$statusDb->failCount} failures" ); |
2094 | | - if ( !$statusDb->isGood() ) { |
2095 | | - $this->db->rollback(); |
2096 | | - // Something went wrong with the DB updates, so remove the target files |
2097 | | - $this->cleanupTarget( $triplets ); |
2098 | | - $statusDb->ok = false; |
2099 | | - return $statusDb; |
2100 | | - } |
2101 | | - $this->db->commit(); |
2102 | | - |
2103 | | - // Everything went ok, remove the source files |
2104 | | - $this->cleanupSource( $triplets ); |
2105 | | - |
2106 | | - $status->merge( $statusDb ); |
2107 | | - $status->merge( $statusMove ); |
2108 | | - |
2109 | | - return $status; |
2110 | | - } |
2111 | | - |
2112 | | - /** |
2113 | | - * Do the database updates and return a new FileRepoStatus indicating how |
2114 | | - * many rows where updated. |
2115 | | - * |
2116 | | - * @return FileRepoStatus |
2117 | | - */ |
2118 | | - function doDBUpdates() { |
2119 | | - $repo = $this->file->repo; |
2120 | | - $status = $repo->newGood(); |
2121 | | - $dbw = $this->db; |
2122 | | - |
2123 | | - // Update current image |
2124 | | - $dbw->update( |
2125 | | - 'image', |
2126 | | - array( 'img_name' => $this->newName ), |
2127 | | - array( 'img_name' => $this->oldName ), |
2128 | | - __METHOD__ |
2129 | | - ); |
2130 | | - |
2131 | | - if ( $dbw->affectedRows() ) { |
2132 | | - $status->successCount++; |
2133 | | - } else { |
2134 | | - $status->failCount++; |
2135 | | - $status->fatal( 'imageinvalidfilename' ); |
2136 | | - return $status; |
2137 | | - } |
2138 | | - |
2139 | | - // Update old images |
2140 | | - $dbw->update( |
2141 | | - 'oldimage', |
2142 | | - array( |
2143 | | - 'oi_name' => $this->newName, |
2144 | | - 'oi_archive_name = ' . $dbw->strreplace( 'oi_archive_name', $dbw->addQuotes( $this->oldName ), $dbw->addQuotes( $this->newName ) ), |
2145 | | - ), |
2146 | | - array( 'oi_name' => $this->oldName ), |
2147 | | - __METHOD__ |
2148 | | - ); |
2149 | | - |
2150 | | - $affected = $dbw->affectedRows(); |
2151 | | - $total = $this->oldCount; |
2152 | | - $status->successCount += $affected; |
2153 | | - $status->failCount += $total - $affected; |
2154 | | - if ( $status->failCount ) { |
2155 | | - $status->error( 'imageinvalidfilename' ); |
2156 | | - } |
2157 | | - |
2158 | | - return $status; |
2159 | | - } |
2160 | | - |
2161 | | - /** |
2162 | | - * Generate triplets for storeBatch(). |
2163 | | - */ |
2164 | | - function getMoveTriplets() { |
2165 | | - $moves = array_merge( array( $this->cur ), $this->olds ); |
2166 | | - $triplets = array(); // The format is: (srcUrl, destZone, destUrl) |
2167 | | - |
2168 | | - foreach ( $moves as $move ) { |
2169 | | - // $move: (oldRelativePath, newRelativePath) |
2170 | | - $srcUrl = $this->file->repo->getVirtualUrl() . '/public/' . rawurlencode( $move[0] ); |
2171 | | - $triplets[] = array( $srcUrl, 'public', $move[1] ); |
2172 | | - wfDebugLog( 'imagemove', "Generated move triplet for {$this->file->name}: {$srcUrl} :: public :: {$move[1]}" ); |
2173 | | - } |
2174 | | - |
2175 | | - return $triplets; |
2176 | | - } |
2177 | | - |
2178 | | - /** |
2179 | | - * Removes non-existent files from move batch. |
2180 | | - */ |
2181 | | - function removeNonexistentFiles( $triplets ) { |
2182 | | - $files = array(); |
2183 | | - |
2184 | | - foreach ( $triplets as $file ) { |
2185 | | - $files[$file[0]] = $file[0]; |
2186 | | - } |
2187 | | - |
2188 | | - $result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY ); |
2189 | | - $filteredTriplets = array(); |
2190 | | - |
2191 | | - foreach ( $triplets as $file ) { |
2192 | | - if ( $result[$file[0]] ) { |
2193 | | - $filteredTriplets[] = $file; |
2194 | | - } else { |
2195 | | - wfDebugLog( 'imagemove', "File {$file[0]} does not exist" ); |
2196 | | - } |
2197 | | - } |
2198 | | - |
2199 | | - return $filteredTriplets; |
2200 | | - } |
2201 | | - |
2202 | | - /** |
2203 | | - * Cleanup a partially moved array of triplets by deleting the target |
2204 | | - * files. Called if something went wrong half way. |
2205 | | - */ |
2206 | | - function cleanupTarget( $triplets ) { |
2207 | | - // Create dest pairs from the triplets |
2208 | | - $pairs = array(); |
2209 | | - foreach ( $triplets as $triplet ) { |
2210 | | - $pairs[] = array( $triplet[1], $triplet[2] ); |
2211 | | - } |
2212 | | - |
2213 | | - $this->file->repo->cleanupBatch( $pairs ); |
2214 | | - } |
2215 | | - |
2216 | | - /** |
2217 | | - * Cleanup a fully moved array of triplets by deleting the source files. |
2218 | | - * Called at the end of the move process if everything else went ok. |
2219 | | - */ |
2220 | | - function cleanupSource( $triplets ) { |
2221 | | - // Create source file names from the triplets |
2222 | | - $files = array(); |
2223 | | - foreach ( $triplets as $triplet ) { |
2224 | | - $files[] = $triplet[0]; |
2225 | | - } |
2226 | | - |
2227 | | - $this->file->repo->cleanupBatch( $files ); |
2228 | | - } |
2229 | | -} |
2230 | | - |
2231 | | -/** |
2232 | 318 | * Repository that stores files in Swift and registers them |
2233 | 319 | * in the wiki's own database. |
2234 | 320 | * |
— | — | @@ -2236,6 +322,9 @@ |
2237 | 323 | */ |
2238 | 324 | |
2239 | 325 | class SwiftRepo extends LocalRepo { |
| 326 | + // The public interface to SwiftFile is through SwiftRepo's findFile and |
| 327 | + // newFile. They call into the repo's NewFile and FindFile, which call |
| 328 | + // one of these factories to create the File object. |
2240 | 329 | var $fileFactory = array( 'SwiftFile', 'newFromTitle' ); |
2241 | 330 | var $fileFactoryKey = array( 'SwiftFile', 'newFromKey' ); |
2242 | 331 | var $fileFromRowFactory = array( 'SwiftFile', 'newFromRow' ); |
— | — | @@ -2244,6 +333,8 @@ |
2245 | 334 | var $oldFileFromRowFactory = array( 'OldSwiftFile', 'newFromRow' ); |
2246 | 335 | |
2247 | 336 | function __construct( $info ) { |
| 337 | + // We don't call parent::_construct because it requires $this->directory, |
| 338 | + // which doesn't exist in Swift. |
2248 | 339 | FileRepo::__construct( $info ); |
2249 | 340 | |
2250 | 341 | // Required settings |
— | — | @@ -2254,12 +345,14 @@ |
2255 | 346 | $this->deletedHashLevels = isset( $info['deletedHashLevels'] ) ? |
2256 | 347 | $info['deletedHashLevels'] : $this->hashLevels; |
2257 | 348 | |
2258 | | - if ( isset( $info['thumbUrl'] ) ) { |
2259 | | - $this->thumbUrl = $info['thumbUrl']; |
2260 | | - } else { |
2261 | | - $this->thumbUrl = "{$this->url}/thumb"; |
2262 | | - } |
| 349 | + // This relationship is also hard-coded in rewrite.py, another part of this |
| 350 | + // extension. If you want to change this here, you might have to change it |
| 351 | + // there, too. |
| 352 | + $this->thumbUrl = "{$this->url}/thumb"; |
2263 | 353 | |
| 354 | + // we don't have directories |
| 355 | + $this->deletedDir = false; |
| 356 | + |
2264 | 357 | // Required settings |
2265 | 358 | $this->swiftuser= $info['user']; |
2266 | 359 | $this->swiftkey= $info['key']; |
— | — | @@ -2399,7 +492,7 @@ |
2400 | 493 | |
2401 | 494 | // Where are we copying this from? |
2402 | 495 | if (self::isVirtualUrl( $srcPath )) { |
2403 | | - $src = $this->resolveVirtualUrl( $srcPath ); |
| 496 | + $src = $this->getContainerRel( $srcPath ); |
2404 | 497 | list ($srcContainer, $srcRel) = $src; |
2405 | 498 | $srcc = $this->get_container($conn, $srcContainer); |
2406 | 499 | |
— | — | @@ -2417,7 +510,7 @@ |
2418 | 511 | |
2419 | 512 | if ( !( $flags & self::SKIP_VALIDATION ) ) { |
2420 | 513 | // FIXME: Swift will return the MD5 of the data written. |
2421 | | - if (0) { // ( $hashDest === false || $hashSource !== $hashDest ) { |
| 514 | + if (0) { // ( $hashDest === false || $hashSource !== $hashDest ) |
2422 | 515 | wfDebug( __METHOD__ . ': File copy validation failed: ' . |
2423 | 516 | "$srcPath ($hashSource) to $dstPath ($hashDest)\n" ); |
2424 | 517 | |
— | — | @@ -2436,25 +529,6 @@ |
2437 | 530 | } |
2438 | 531 | |
2439 | 532 | /** |
2440 | | - * Pick a random name in the temp zone and store a file to it. |
2441 | | - * @param $originalName String: the base name of the file as specified |
2442 | | - * by the user. The file extension will be maintained. |
2443 | | - * @param $srcPath String: the current location of the file. |
2444 | | - * @return FileRepoStatus object with the URL in the value. |
2445 | | - */ |
2446 | | - function storeTemp( $originalName, $srcPath ) { |
2447 | | - $date = gmdate( "YmdHis" ); |
2448 | | - $hashPath = $this->getHashPath( $originalName ); |
2449 | | - $dstRel = "$hashPath$date!$originalName"; |
2450 | | - $dstUrlRel = $hashPath . $date . '!' . rawurlencode( $originalName ); |
2451 | | - |
2452 | | - $result = $this->store( $srcPath, 'temp', $dstRel ); |
2453 | | - $result->value = $this->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel; |
2454 | | - return $result; |
2455 | | - } |
2456 | | - |
2457 | | - |
2458 | | - /** |
2459 | 533 | * Append the contents of the source path to the given file, OR queue |
2460 | 534 | * the appending operation in anticipation of a later appendFinish() call. |
2461 | 535 | * @param $srcPath String: location of the source file |
— | — | @@ -2562,7 +636,7 @@ |
2563 | 637 | if ( !self::isVirtualUrl( $file ) ) { |
2564 | 638 | throw new MWException( __METHOD__ . " requires a virtual URL, not '$file'"); |
2565 | 639 | } |
2566 | | - $rvu = $this->resolveVirtualUrl( $file ); |
| 640 | + $rvu = $this->getContainerRel( $file ); |
2567 | 641 | list ($cont, $rel) = $rvu; |
2568 | 642 | $container = $this->get_container($conn,$cont); |
2569 | 643 | try { |
— | — | @@ -2707,7 +781,7 @@ |
2708 | 782 | } else { |
2709 | 783 | if ( self::isVirtualUrl( $file ) ) { |
2710 | 784 | // This is a virtual url, resolve it |
2711 | | - $path = $this->resolveVirtualUrl( $file ); |
| 785 | + $path = $this->getContainerRel( $file ); |
2712 | 786 | list( $cont, $rel) = $path; |
2713 | 787 | } else { |
2714 | 788 | // FIXME: This is a full file name |
— | — | @@ -2790,31 +864,37 @@ |
2791 | 865 | } |
2792 | 866 | } |
2793 | 867 | /** |
2794 | | - * Get the ($container, $object) corresponding to a virtual URL |
| 868 | + * Remove a temporary file or mark it for garbage collection |
| 869 | + * @param $virtualUrl String: the virtual URL returned by storeTemp |
| 870 | + * @return Boolean: true on success, false on failure |
2795 | 871 | */ |
2796 | | - function resolveVirtualUrl( $url ) { |
2797 | | - if ( substr( $url, 0, 9 ) != 'mwrepo://' ) { |
2798 | | - throw new MWException( __METHOD__.": unknown protocol" ); |
| 872 | + function freeTemp( $virtualUrl ) { |
| 873 | + $temp = "mwrepo://{$this->name}/temp"; |
| 874 | + if ( substr( $virtualUrl, 0, strlen( $temp ) ) != $temp ) { |
| 875 | + wfDebug( __METHOD__.": Invalid virtual URL\n" ); |
| 876 | + return false; |
2799 | 877 | } |
| 878 | + $path = $this->getContainerRel( $virtualUrl ); |
| 879 | + list ($c, $r) = $path; |
| 880 | + $conn = $this->connect(); |
| 881 | + $container = $this->get_container($conn,$c); |
| 882 | + $this->swift_delete($container, $r); |
| 883 | + } |
2800 | 884 | |
2801 | | - $bits = explode( '/', substr( $url, 9 ), 3 ); |
2802 | | - if ( count( $bits ) != 3 ) { |
2803 | | - throw new MWException( __METHOD__.": invalid mwrepo URL: $url" ); |
2804 | | - } |
2805 | | - list( $repo, $zone, $rel ) = $bits; |
2806 | | - if ( $repo !== $this->name ) { |
2807 | | - throw new MWException( __METHOD__.": fetching from a foreign repo is not supported" ); |
2808 | | - } |
2809 | | - $container = $this->getZoneContainer( $zone ); |
2810 | | - if ( $container === false) { |
2811 | | - throw new MWException( __METHOD__.": invalid zone: $zone" ); |
2812 | | - } |
2813 | | - return array($container, rawurldecode( $rel )); |
| 885 | + /** |
| 886 | + * Get an UploadStash associated with this repo. |
| 887 | + * |
| 888 | + * @return UploadStash |
| 889 | + */ |
| 890 | + function getUploadStash() { |
| 891 | + return new SwiftStash( $this ); |
2814 | 892 | } |
| 893 | +} |
2815 | 894 | |
| 895 | +class SwiftStash extends UploadStash { |
| 896 | +} |
2816 | 897 | |
2817 | | - |
2818 | | - |
| 898 | +class SwiftStashFile extends UploadStashFile { |
2819 | 899 | } |
2820 | 900 | |
2821 | 901 | /** |
— | — | @@ -3031,90 +1111,3 @@ |
3032 | 1112 | return Revision::userCanBitfield( $this->deleted, $field ); |
3033 | 1113 | } |
3034 | 1114 | } |
3035 | | - |
3036 | | - |
3037 | | -class Junkyjunk { |
3038 | | - /** |
3039 | | - * Remove a temporary file or mark it for garbage collection |
3040 | | - * @param $virtualUrl String: the virtual URL returned by storeTemp |
3041 | | - * @return Boolean: true on success, false on failure |
3042 | | - */ |
3043 | | - function freeTemp( $virtualUrl ) { |
3044 | | - $temp = "mwrepo://{$this->name}/temp"; |
3045 | | - if ( substr( $virtualUrl, 0, strlen( $temp ) ) != $temp ) { |
3046 | | - wfDebug( __METHOD__.": Invalid virtual URL\n" ); |
3047 | | - return false; |
3048 | | - } |
3049 | | - $path = $this->resolveVirtualUrl( $virtualUrl ); |
3050 | | - $success = unlink( $path ); |
3051 | | - return $success; |
3052 | | - } |
3053 | | - |
3054 | | - |
3055 | | - |
3056 | | - /** |
3057 | | - * Removes non-existent files from move batch. |
3058 | | - */ |
3059 | | - function move_removeNonexistentFiles( $triplets ) { |
3060 | | - $files = array(); |
3061 | | - |
3062 | | - foreach ( $triplets as $file ) { |
3063 | | - $files[$file[0]] = $file[0]; |
3064 | | - } |
3065 | | - |
3066 | | - $result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY ); |
3067 | | - $filteredTriplets = array(); |
3068 | | - |
3069 | | - foreach ( $triplets as $file ) { |
3070 | | - if ( $result[$file[0]] ) { |
3071 | | - $filteredTriplets[] = $file; |
3072 | | - } else { |
3073 | | - wfDebugLog( 'imagemove', "File {$file[0]} does not exist" ); |
3074 | | - } |
3075 | | - } |
3076 | | - |
3077 | | - return $filteredTriplets; |
3078 | | - } |
3079 | | - /** |
3080 | | - * Removes non-existent files from a store batch. |
3081 | | - */ |
3082 | | - function store_removeNonexistentFiles( $triplets ) { |
3083 | | - $files = $filteredTriplets = array(); |
3084 | | - |
3085 | | - foreach ( $triplets as $file ) |
3086 | | - $files[$file[0]] = $file[0]; |
3087 | | - |
3088 | | - $result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY ); |
3089 | | - |
3090 | | - foreach ( $triplets as $file ) { |
3091 | | - if ( $result[$file[0]] ) { |
3092 | | - $filteredTriplets[] = $file; |
3093 | | - } |
3094 | | - } |
3095 | | - |
3096 | | - return $filteredTriplets; |
3097 | | - } |
3098 | | - |
3099 | | - /** |
3100 | | - * Removes non-existent files from a deletion batch. |
3101 | | - */ |
3102 | | - function deletion_removeNonexistentFiles( $triplets ) { |
3103 | | - $files = $filteredTriplets = array(); |
3104 | | - |
3105 | | - foreach ( $triplets as $file) { |
3106 | | - list( $src, $dest ) = $file; |
3107 | | - $files[$src] = $this->file->repo->getVirtualUrl( 'public' ) . '/' . rawurlencode( $src ); |
3108 | | - } |
3109 | | - |
3110 | | - $result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY ); |
3111 | | - |
3112 | | - foreach ( $triplets as $file ) { |
3113 | | - if ( $result[$file[0]] ) { |
3114 | | - $filteredTriplets[] = $file; |
3115 | | - } |
3116 | | - } |
3117 | | - |
3118 | | - return $filteredTriplets; |
3119 | | - } |
3120 | | - |
3121 | | -} |