/** * Record a file upload in the upload log and the image table */ function recordUpload2($oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null) { if (is_null($user)) { global $wgUser; $user = $wgUser; } $dbw = $this->repo->getMasterDB(); $dbw->begin(); if (!$props) { $props = $this->repo->getFileProps($this->getVirtualUrl()); } $props['description'] = $comment; $props['user'] = $user->getId(); $props['user_text'] = $user->getName(); $props['timestamp'] = wfTimestamp(TS_MW); $this->setProps($props); // Delete thumbnails and refresh the metadata cache $this->purgeThumbnails(); $this->saveToCache(); SquidUpdate::purge(array($this->getURL())); /* Wikia change begin - @author: Marooned, see RT#44185 */ global $wgLogo; if ($this->url == $wgLogo) { SquidUpdate::purge(array($this->url)); } /* Wikia change end */ // Fail now if the file isn't there if (!$this->fileExists) { wfDebug(__METHOD__ . ": File " . $this->getPath() . " went missing!\n"); return false; } $reupload = false; if ($timestamp === false) { $timestamp = $dbw->timestamp(); } # Test to see if the row exists using INSERT IGNORE # This avoids race conditions by locking the row until the commit, and also # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition. $dbw->insert('image', array('img_name' => $this->getName(), 'img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $user->getId(), 'img_user_text' => $user->getName(), 'img_metadata' => $this->metadata, 'img_sha1' => $this->sha1), __METHOD__, 'IGNORE'); if ($dbw->affectedRows() == 0) { $reupload = true; # Collision, this is an update of a file # Insert previous contents into oldimage $dbw->insertSelect('oldimage', 'image', array('oi_name' => 'img_name', 'oi_archive_name' => $dbw->addQuotes($oldver), 'oi_size' => 'img_size', 'oi_width' => 'img_width', 'oi_height' => 'img_height', 'oi_bits' => 'img_bits', 'oi_timestamp' => 'img_timestamp', 'oi_description' => 'img_description', 'oi_user' => 'img_user', 'oi_user_text' => 'img_user_text', 'oi_metadata' => 'img_metadata', 'oi_media_type' => 'img_media_type', 'oi_major_mime' => 'img_major_mime', 'oi_minor_mime' => 'img_minor_mime', 'oi_sha1' => 'img_sha1'), array('img_name' => $this->getName()), __METHOD__); # Update the current image row $dbw->update('image', array('img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $user->getId(), 'img_user_text' => $user->getName(), 'img_metadata' => $this->metadata, 'img_sha1' => $this->sha1), array('img_name' => $this->getName()), __METHOD__); } else { # This is a new file # Update the image count $site_stats = $dbw->tableName('site_stats'); $dbw->query("UPDATE {$site_stats} SET ss_images=ss_images+1", __METHOD__); } # Commit the transaction now, in case something goes wrong later # The most important thing is that files don't get lost, especially archives $dbw->commit(); # Invalidate cache for all pages using this file $update = new HTMLCacheUpdate($this->getTitle(), 'imagelinks'); $update->doUpdate(); return true; }
private function purge($url, $var, $typeVal, $val, $likeVal) { $wikis = $this->getListOfWikisWithVar($var, $typeVal, $val, $likeVal); $purgeUrls = array(); foreach ($wikis as $cityId => $wikiData) { $purgeUrls[] = $wikiData['u'] . $url; } SquidUpdate::purge($purgeUrls); return $purgeUrls; }
/** @todo document */ function benchSquid($urls, $trials = 1) { $start = wfTime(); for ($i = 0; $i < $trials; $i++) { SquidUpdate::purge($urls); } $delta = wfTime() - $start; $pertrial = $delta / $trials; $pertitle = $pertrial / count($urls); return sprintf("%4d titles in %6.2fms (%6.2fms each)", count($urls), $pertrial * 1000.0, $pertitle * 1000.0); }
/** * Remove the thumb from DFS and purge it from CDN * * @param string $thumb */ private function purgeThumb($thumb) { $url = sprintf("http://images.wikia.com/%s%s/%s", $this->storage->getContainerName(), $this->storage->getPathPrefix(), $thumb); #$this->output(sprintf("%s: %s <%s>...", __METHOD__, $thumb, $url)); $this->output(sprintf("%s: '%s'... ", __METHOD__, $thumb)); if ($this->isDryRun) { $this->output("dry run!\n"); } else { $this->storage->remove($thumb); SquidUpdate::purge([$url]); $this->output("done\n"); } }
public function execute() { $this->dryRun = $this->hasOption('dry-run'); $this->verbose = $this->hasOption('verbose'); $wikiId = $this->getOption('wikiId', ''); if (empty($wikiId)) { die("Error: Empty wiki id.\n"); } $dbname = WikiFactory::IDtoDB($wikiId); if (empty($dbname)) { die("Error: Cannot find dbname.\n"); } $pageLimit = 20000; $totalLimit = $this->getOption('limit', $pageLimit); if (empty($totalLimit) || $totalLimit < -1) { die("Error: invalid limit.\n"); } if ($totalLimit == -1) { $totalLimit = $this->getTotalPages($dbname); } $maxSet = ceil($totalLimit / $pageLimit); $limit = $totalLimit > $pageLimit ? $pageLimit : $totalLimit; $totalPages = 0; for ($set = 1; $set <= $maxSet; $set++) { $cnt = 0; if ($set == $maxSet) { $limit = $totalLimit - $pageLimit * ($set - 1); } $offset = ($set - 1) * $pageLimit; $pages = $this->getAllPages($dbname, $limit, $offset); $total = count($pages); foreach ($pages as $page) { $cnt++; echo "Wiki {$wikiId} - Page {$page['id']} [{$cnt} of {$total}, set {$set} of {$maxSet}]: "; $title = GlobalTitle::newFromId($page['id'], $wikiId); if ($title instanceof GlobalTitle) { $url = $title->getFullURL(); echo "{$url}\n"; if (!$this->dryRun) { SquidUpdate::purge([$url]); } $this->success++; } else { echo "ERROR: Cannot find global title for {$page['title']}\n"; } } $totalPages = $totalPages + $total; } echo "\nWiki {$wikiId}: Total pages: {$totalPages}, Success: {$this->success}, Failed: " . ($totalPages - $this->success) . "\n\n"; }
/** * This is obsolete, use SquidUpdate::purge() * @deprecated */ function wfPurgeSquidServers($urlArr) { SquidUpdate::purge($urlArr); }
/** * @author Maciej Błaszkowski <marooned at wikia-inc.com> */ static function purgeCommunityWidgetInVarnish($title) { global $wgScript, $wgContentNamespaces, $wgContLang, $wgMemc; if (in_array($title->getNamespace(), $wgContentNamespaces)) { $lang = $wgContLang->getCode(); $key = wfMemcKey('community_widget_v1', $lang); $wgMemc->delete($key); SquidUpdate::purge(array($wgScript . "?action=ajax&rs=CommunityWidgetAjax&uselang={$lang}")); } return true; }
/** * Delete an old version of the file. * * Moves the file into an archive directory (or deletes it) * and removes the database row. * * Cache purging is done; logging is caller's responsibility. * * @param string $archiveName * @param string $reason * @param bool $suppress * @throws MWException Exception on database or file store failure * @return FileRepoStatus */ function deleteOld($archiveName, $reason, $suppress = false) { global $wgUseSquid; if ($this->getRepo()->getReadOnlyReason() !== false) { return $this->readOnlyFatalStatus(); } $batch = new LocalFileDeleteBatch($this, $reason, $suppress); $this->lock(); // begin $batch->addOld($archiveName); $status = $batch->execute(); $this->unlock(); // done $this->purgeOldThumbnails($archiveName); if ($status->isOK()) { $this->purgeDescription(); $this->purgeHistory(); } if ($wgUseSquid) { // Purge the squid SquidUpdate::purge(array($this->getArchiveUrl($archiveName))); } return $status; }
/** * Run the transaction */ function execute() { global $wgUseSquid; wfProfileIn(__METHOD__); $this->file->lock(); // Leave private files alone $privateFiles = array(); list($oldRels, $deleteCurrent) = $this->getOldRels(); $dbw = $this->file->repo->getMasterDB(); if (!empty($oldRels)) { $res = $dbw->select('oldimage', array('oi_archive_name'), array('oi_name' => $this->file->getName(), 'oi_archive_name IN (' . $dbw->makeList(array_keys($oldRels)) . ')', $dbw->bitAnd('oi_deleted', File::DELETED_FILE) => File::DELETED_FILE), __METHOD__); foreach ($res as $row) { $privateFiles[$row->oi_archive_name] = 1; } } // Prepare deletion batch $hashes = $this->getHashes(); $this->deletionBatch = array(); $ext = $this->file->getExtension(); $dotExt = $ext === '' ? '' : ".{$ext}"; foreach ($this->srcRels as $name => $srcRel) { // Skip files that have no hash (missing source). // Keep private files where they are. if (isset($hashes[$name]) && !array_key_exists($name, $privateFiles)) { $hash = $hashes[$name]; $key = $hash . $dotExt; $dstRel = $this->file->repo->getDeletedHashPath($key) . $key; $this->deletionBatch[$name] = array($srcRel, $dstRel); } } // Lock the filearchive rows so that the files don't get deleted by a cleanup operation // We acquire this lock by running the inserts now, before the file operations. // // This potentially has poor lock contention characteristics -- an alternative // scheme would be to insert stub filearchive entries with no fa_name and commit // them in a separate transaction, then run the file ops, then update the fa_name fields. $this->doDBInserts(); // Removes non-existent file from the batch, so we don't get errors. $this->deletionBatch = $this->removeNonexistentFiles($this->deletionBatch); // Execute the file deletion batch $status = $this->file->repo->deleteBatch($this->deletionBatch); if (!$status->isGood()) { $this->status->merge($status); } if (!$this->status->ok) { // Critical file deletion error // Roll back inserts, release lock and abort // TODO: delete the defunct filearchive rows if we are using a non-transactional DB $this->file->unlockAndRollback(); wfProfileOut(__METHOD__); return $this->status; } // Purge squid if ($wgUseSquid) { $urls = array(); foreach ($this->srcRels as $srcRel) { $urlRel = str_replace('%2F', '/', rawurlencode($srcRel)); $urls[] = $this->file->repo->getZoneUrl('public') . '/' . $urlRel; } SquidUpdate::purge($urls); } // Delete image/oldimage rows $this->doDBDeletes(); // Commit and return $this->file->unlock(); wfProfileOut(__METHOD__); return $this->status; }
/** * Purges squids and invalidates caches of pages that link to $title * interwiki. */ public static function PurgeReferringPages($title) { global $wgDBname, $wgInterwikiIntegrationPrefix; $mDb = wfGetDB(DB_MASTER); $titleName = str_replace(' ', '_', $title->getFullText()); $prefix = array(); $prefix = array_keys($wgInterwikiIntegrationPrefix, $wgDBname); $purgeArray = array(); foreach ($prefix as $thisPrefix) { $thisPrefix = ucfirst($thisPrefix); $result = $mDb->selectRow(array('integration_iwlinks'), array('integration_iwl_from_url', 'integration_iwl_from_db', 'integration_iwl_from'), array('integration_iwl_prefix' => $thisPrefix, 'integration_iwl_title' => $titleName)); if ($result) { $referringPage = $result->integration_iwl_from; $purgeArray[] = $result->integration_iwl_from_url; $referringDb = $result->integration_iwl_from_db; $dbwReferring = wfGetDB(DB_MASTER, array(), $referringDb); $dbwReferring->update('page', array('page_touched' => $dbwReferring->timestamp()), array('page_id' => $referringPage)); } } if ($result) { SquidUpdate::purge($purgeArray); } return true; }
function purgeUrl() { // Purge the avatar URL and the proportions commonly used in Oasis. global $wgUseSquid; if ($wgUseSquid) { // FIXME: is there a way to know what sizes will be used w/o hardcoding them here? $urls = array($this->getPurgeUrl(), $this->getThumbnailPurgeUrl(20), $this->getThumbnailPurgeUrl(50), $this->getThumbnailPurgeUrl(100), $this->getThumbnailPurgeUrl(200)); SquidUpdate::purge($urls); } }
/** * Purges Varnish and Memcached data mapping to the specified set of paramenters * * @param array $options @see getMultiTypePackage */ public function purgeMultiTypePackageCache(array $options) { SquidUpdate::purge(array(F::build('AssetsManager', array(), 'getInstance')->getMultiTypePackageURL($options))); }
/** * @param $title Title * @param $user User */ public function purge($title, $user) { global $wgScript, $wgServer; AttributionCache::getInstance()->updateArticleContribs($title, $user); // invalidate varnish cache if ($user->getId() != 0) { SquidUpdate::purge(array("{$wgServer}{$wgScript}?action=ajax&rs=wfAnswersGetEditPointsAjax&userId={$user->getId()}")); } }
/** * @brief remove thumbnails for avatar by cleaning up whole folder * * @author Krzysztof Krzyżaniak (eloy) <*****@*****.**> * @access private * * @return boolean status of operation */ private function purgeThumbnails() { global $wgAvatarsUseSwiftStorage, $wgBlogAvatarPath, $wgBlogAvatarSwiftContainer, $wgBlogAvatarSwiftPathPrefix; // get path to thumbnail folder wfProfileIn(__METHOD__); // dirty hack, should work in this case if (!empty($wgAvatarsUseSwiftStorage)) { $swift = $this->getSwiftStorage(); $backend = FileBackendGroup::singleton()->get('swift-backend'); $dir = sprintf('mwstore://swift-backend/%s%s%s', $wgBlogAvatarSwiftContainer, $wgBlogAvatarSwiftPathPrefix, $this->getLocalPath()); $dir = $this->getThumbPath($dir); $avatarRemotePath = sprintf("thumb%s", $this->getLocalPath()); $urls = []; $files = []; $iterator = $backend->getFileList(array('dir' => $dir)); foreach ($iterator as $file) { $files[] = sprintf("%s/%s", $avatarRemotePath, $file); } // deleting files on file system and creating an array of URLs to purge if (!empty($files)) { foreach ($files as $file) { $status = $swift->remove($file); if (!$status->isOk()) { wfDebugLog("avatar", __METHOD__ . ": {$file} exists but cannot be removed.\n", true); } else { $urls[] = wfReplaceImageServer($wgBlogAvatarPath) . "/{$file}"; wfDebugLog("avatar", __METHOD__ . ": {$file} removed.\n", true); } } } wfDebugLog("avatar", __METHOD__ . ": all thumbs removed.\n", true); } else { $dir = $this->getFullPath(); $dir = $this->getThumbPath($dir); if (is_dir($dir)) { $urls = []; $files = []; // copied from LocalFile->getThumbnails $handle = opendir($dir); if ($handle) { while (false !== ($file = readdir($handle))) { if ($file[0] != '.') { $files[] = $file; } } closedir($handle); } // partialy copied from LocalFile->purgeThumbnails() foreach ($files as $file) { // deleting files on file system @unlink("{$dir}/{$file}"); $urls[] = $this->getPurgeUrl('/thumb/') . "/{$file}"; wfDebugLog("avatar", __METHOD__ . ": removing {$dir}/{$file}\n", true); } } else { wfDebugLog("avatar", __METHOD__ . ": {$dir} exists but is not directory so not removed.\n", true); } wfDebugLog("avatar", __METHOD__ . ": all thumbs removed.\n", true); } // purging avatars urls SquidUpdate::purge($urls); wfProfileOut(__METHOD__); }
/** * @brief Whenever data is saved in GG Content Managment Tool * purge Varnish cache for it * * @return bool */ static function onGameGuidesContentSave() { $app = F::app(); SquidUpdate::purge(array($app->wf->AppendQuery($app->wf->ExpandUrl($app->wg->Server . $app->wg->ScriptPath . '/wikia.php'), array('controller' => __CLASS__, 'method' => 'getTags')))); return true; }
/** * Purges the list of URLs passed to the constructor */ function doUpdate() { SquidUpdate::purge($this->urlArr); }
/** * Record a file upload in the upload log and the image table * @param $oldver * @param $comment string * @param $pageText string * @param $props bool|array * @param $timestamp bool|string * @param $user null|User * @return bool */ function recordUpload2($oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null) { wfProfileIn(__METHOD__); if (is_null($user)) { global $wgUser; $user = $wgUser; } $dbw = $this->repo->getMasterDB(); $dbw->begin(__METHOD__); if (!$props) { wfProfileIn(__METHOD__ . '-getProps'); $props = $this->repo->getFileProps($this->getVirtualUrl()); wfProfileOut(__METHOD__ . '-getProps'); } if ($timestamp === false) { $timestamp = $dbw->timestamp(); } $props['description'] = $comment; $props['user'] = $user->getId(); $props['user_text'] = $user->getName(); $props['timestamp'] = wfTimestamp(TS_MW, $timestamp); // DB -> TS_MW $this->setProps($props); # Fail now if the file isn't there if (!$this->fileExists) { wfDebug(__METHOD__ . ": File " . $this->getRel() . " went missing!\n"); wfProfileOut(__METHOD__); return false; } $reupload = false; # Test to see if the row exists using INSERT IGNORE # This avoids race conditions by locking the row until the commit, and also # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition. $dbw->insert('image', array('img_name' => $this->getName(), 'img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $user->getId(), 'img_user_text' => $user->getName(), 'img_metadata' => $this->metadata, 'img_sha1' => $this->sha1), __METHOD__, 'IGNORE'); if ($dbw->affectedRows() == 0) { # (bug 34993) Note: $oldver can be empty here, if the previous # version of the file was broken. Allow registration of the new # version to continue anyway, because that's better than having # an image that's not fixable by user operations. $reupload = true; # Collision, this is an update of a file # Insert previous contents into oldimage $dbw->insertSelect('oldimage', 'image', array('oi_name' => 'img_name', 'oi_archive_name' => $dbw->addQuotes($oldver), 'oi_size' => 'img_size', 'oi_width' => 'img_width', 'oi_height' => 'img_height', 'oi_bits' => 'img_bits', 'oi_timestamp' => 'img_timestamp', 'oi_description' => 'img_description', 'oi_user' => 'img_user', 'oi_user_text' => 'img_user_text', 'oi_metadata' => 'img_metadata', 'oi_media_type' => 'img_media_type', 'oi_major_mime' => 'img_major_mime', 'oi_minor_mime' => 'img_minor_mime', 'oi_sha1' => 'img_sha1'), array('img_name' => $this->getName()), __METHOD__); # Update the current image row $dbw->update('image', array('img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $user->getId(), 'img_user_text' => $user->getName(), 'img_metadata' => $this->metadata, 'img_sha1' => $this->sha1), array('img_name' => $this->getName()), __METHOD__); } else { # This is a new file, so update the image count DeferredUpdates::addUpdate(SiteStatsUpdate::factory(array('images' => 1))); } $descTitle = $this->getTitle(); $wikiPage = new WikiFilePage($descTitle); $wikiPage->setFile($this); # Add the log entry $log = new LogPage('upload'); $action = $reupload ? 'overwrite' : 'upload'; $logId = $log->addEntry($action, $descTitle, $comment, array(), $user); wfProfileIn(__METHOD__ . '-edit'); $exists = $descTitle->exists(); if ($exists) { # Create a null revision $latest = $descTitle->getLatestRevID(); $nullRevision = Revision::newNullRevision($dbw, $descTitle->getArticleID(), $log->getRcComment(), false); if (!is_null($nullRevision)) { $nullRevision->insertOn($dbw); wfRunHooks('NewRevisionFromEditComplete', array($wikiPage, $nullRevision, $latest, $user)); $wikiPage->updateRevisionOn($dbw, $nullRevision); } } # Commit the transaction now, in case something goes wrong later # The most important thing is that files don't get lost, especially archives # NOTE: once we have support for nested transactions, the commit may be moved # to after $wikiPage->doEdit has been called. $dbw->commit(__METHOD__); if ($exists) { # Invalidate the cache for the description page $descTitle->invalidateCache(); $descTitle->purgeSquid(); } else { # New file; create the description page. # There's already a log entry, so don't make a second RC entry # Squid and file cache for the description page are purged by doEditContent. $content = ContentHandler::makeContent($pageText, $descTitle); $status = $wikiPage->doEditContent($content, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user); if (isset($status->value['revision'])) { // XXX; doEdit() uses a transaction $dbw->begin(__METHOD__); $dbw->update('logging', array('log_page' => $status->value['revision']->getPage()), array('log_id' => $logId), __METHOD__); $dbw->commit(__METHOD__); // commit before anything bad can happen } } wfProfileOut(__METHOD__ . '-edit'); # Save to cache and purge the squid # We shall not saveToCache before the commit since otherwise # in case of a rollback there is an usable file from memcached # which in fact doesn't really exist (bug 24978) $this->saveToCache(); if ($reupload) { # Delete old thumbnails wfProfileIn(__METHOD__ . '-purge'); $this->purgeThumbnails(); wfProfileOut(__METHOD__ . '-purge'); # Remove the old file from the squid cache SquidUpdate::purge(array($this->getURL())); } # Hooks, hooks, the magic of hooks... wfProfileIn(__METHOD__ . '-hooks'); wfRunHooks('FileUpload', array($this, $reupload, $descTitle->exists())); wfProfileOut(__METHOD__ . '-hooks'); # Invalidate cache for all pages using this file $update = new HTMLCacheUpdate($this->getTitle(), 'imagelinks'); $update->doUpdate(); # Invalidate cache for all pages that redirects on this page $redirs = $this->getTitle()->getRedirectsHere(); foreach ($redirs as $redir) { $update = new HTMLCacheUpdate($redir, 'imagelinks'); $update->doUpdate(); } wfProfileOut(__METHOD__); return true; }
/** * doSubmit * * parse tag attributes * * @author Krzysztof Krzyżaniak <*****@*****.**> * @access public * * @param object $request WebRequest object * * @return array rendered HTML answer and status of operation */ public function doSubmit(&$request) { global $wgTitle, $wgMemc; wfProfileIn(__METHOD__); $status = false; $vote = $request->getVal("wpPollRadio" . $this->mId, null); if (!is_null($vote)) { if ($this->doVote($vote)) { $status = wfMsg("ajaxpoll-thankyou"); // invalidate cache $wgTitle->invalidateCache(); // Send purge for the article (don't purge its history page - PLATFORM-92) SquidUpdate::purge([$wgTitle->getFullURL()]); } else { $status = wfMsg("ajaxpoll-error"); } } list($votes, $total) = $this->getVotes(true); //true because we need DB_MASTER and we don't want to use memcache here $response = array("id" => $this->mId, "votes" => $votes, "total" => $total, "status" => $status); // Purge the vote stats. $memcKey = $this->getVotesMemKey(); $wgMemc->delete($memcKey); wfProfileOut(__METHOD__); return $response; }
/** * Record a file upload in the upload log and the image table */ function recordUpload2($oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null) { if (is_null($user)) { global $wgUser; $user = $wgUser; } $dbw = $this->repo->getMasterDB(); $dbw->begin(); if (!$props) { $props = $this->repo->getFileProps($this->getVirtualUrl()); } $props['description'] = $comment; $props['user'] = $user->getId(); $props['user_text'] = $user->getName(); $props['timestamp'] = wfTimestamp(TS_MW); $this->setProps($props); // Delete thumbnails and refresh the metadata cache $this->purgeThumbnails(); $this->saveToCache(); SquidUpdate::purge(array($this->getURL())); // Fail now if the file isn't there if (!$this->fileExists) { wfDebug(__METHOD__ . ": File " . $this->getPath() . " went missing!\n"); return false; } $reupload = false; if ($timestamp === false) { $timestamp = $dbw->timestamp(); } # Test to see if the row exists using INSERT IGNORE # This avoids race conditions by locking the row until the commit, and also # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition. $dbw->insert('image', array('img_name' => $this->getName(), 'img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $user->getId(), 'img_user_text' => $user->getName(), 'img_metadata' => $this->metadata, 'img_sha1' => $this->sha1), __METHOD__, 'IGNORE'); if ($dbw->affectedRows() == 0) { $reupload = true; # Collision, this is an update of a file # Insert previous contents into oldimage $dbw->insertSelect('oldimage', 'image', array('oi_name' => 'img_name', 'oi_archive_name' => $dbw->addQuotes($oldver), 'oi_size' => 'img_size', 'oi_width' => 'img_width', 'oi_height' => 'img_height', 'oi_bits' => 'img_bits', 'oi_timestamp' => 'img_timestamp', 'oi_description' => 'img_description', 'oi_user' => 'img_user', 'oi_user_text' => 'img_user_text', 'oi_metadata' => 'img_metadata', 'oi_media_type' => 'img_media_type', 'oi_major_mime' => 'img_major_mime', 'oi_minor_mime' => 'img_minor_mime', 'oi_sha1' => 'img_sha1'), array('img_name' => $this->getName()), __METHOD__); # Update the current image row $dbw->update('image', array('img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $user->getId(), 'img_user_text' => $user->getName(), 'img_metadata' => $this->metadata, 'img_sha1' => $this->sha1), array('img_name' => $this->getName()), __METHOD__); } else { # This is a new file # Update the image count $site_stats = $dbw->tableName('site_stats'); $dbw->query("UPDATE {$site_stats} SET ss_images=ss_images+1", __METHOD__); } $descTitle = $this->getTitle(); $wikiPage = new WikiFilePage($descTitle); $wikiPage->setFile($this); # Add the log entry... $action = $reupload ? 'overwrite' : 'upload'; $logEntry = new ManualLogEntry('upload', $action); $logEntry->setTimestamp($this->timestamp); $logEntry->setPerformer($user); $logEntry->setComment($comment); $logEntry->setTarget($descTitle); // Allow people using the api to associate log entries with the upload. // Log has a timestamp, but sometimes different from upload timestamp. $logEntry->setParameters(array('img_sha1' => $this->sha1, 'img_timestamp' => $timestamp)); // Note we keep $logId around since during new image // creation, page doesn't exist yet, so log_page = 0 // but we want it to point to the page we're making, // so we later modify the log entry. // For a similar reason, we avoid making an RC entry // now and wait until the page exists. $logId = $logEntry->insert(); $exists = $descTitle->exists(); if ($exists) { // Page exists, do RC entry now (otherwise we wait for later). $logEntry->publish($logId); // TODO: same if statement twice? pointless? //} //if ( $exists ) { // Create a null revision $latest = $descTitle->getLatestRevID(); // Use own context to get the action text in content language $formatter = LogFormatter::newFromEntry($logEntry); $formatter->setContext(RequestContext::newExtraneousContext($descTitle)); $editSummary = $formatter->getPlainActionText(); $nullRevision = Revision::newNullRevision($dbw, $descTitle->getArticleID(), $editSummary, false, $user); if (!is_null($nullRevision)) { $nullRevision->insertOn($dbw); Hooks::run('NewRevisionFromEditComplete', array($wikiPage, $nullRevision, $nullRevision->getParentId(), $user)); $wikiPage->updateRevisionOn($dbw, $nullRevision); } } # Commit the transaction now, in case something goes wrong later # The most important thing is that files don't get lost, especially archives # NOTE: once we have support for nested transactions, the commit may be moved # to after $wikiPage->doEdit has been called. $dbw->commit(__METHOD__); # Update memcache after the commit $this->invalidateCache(); if ($exists) { # Invalidate the cache for the description page $descTitle->invalidateCache(); $descTitle->purgeSquid(); } else { # New file; create the description page. # There's already a log entry, so don't make a second RC entry # Squid and file cache for the description page are purged by doEditContent. $content = ContentHandler::makeContent($pageText, $descTitle); $status = $wikiPage->doEditContent($content, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user); $dbw->begin(__METHOD__); // XXX; doEdit() uses a transaction // Now that the page exists, make an RC entry. $logEntry->publish($logId); if (isset($status->value['revision'])) { $dbw->update('logging', array('log_page' => $status->value['revision']->getPage()), array('log_id' => $logId), __METHOD__); } $dbw->commit(__METHOD__); // commit before anything bad can happen } if ($reupload) { # Delete old thumbnails $this->purgeThumbnails(); # Remove the old file from the squid cache SquidUpdate::purge(array($this->getURL())); } # Hooks, hooks, the magic of hooks... Hooks::run('FileUpload', array($this, $reupload, $descTitle->exists())); # Invalidate cache for all pages using this file $update = new HTMLCacheUpdate($this->getTitle(), 'imagelinks'); $update->doUpdate(); if (!$reupload) { LinksUpdate::queueRecursiveJobsForTable($this->getTitle(), 'imagelinks'); } return true; }
/** * The only thing changed here is to strip NS from the file name * Delete cached transformed files */ function purgeThumbnails($options = array()) { global $wgUseSquid; // Delete thumbnails $files = $this->getThumbnails(); $dir = $this->getThumbPath(); $urls = array(); foreach ($files as $file) { # Check that the base file name is part of the thumb name # This is a basic sanity check to avoid erasing unrelated directories /* This is the part that changed from LocalFile */ if (strpos($file, $this->getFileNameStripped($this->getName())) !== false) { /* End of changes */ $url = $this->getThumbUrl($file); $urls[] = $url; wfSuppressWarnings(); unlink("{$dir}/{$file}"); wfRestoreWarnings(); } } // Purge the squid if ($wgUseSquid) { SquidUpdate::purge($urls); } }
/** * Run the transaction */ function execute() { global $wgUser, $wgUseSquid; wfProfileIn(__METHOD__); $this->file->lock(); // Prepare deletion batch $hashes = $this->getHashes(); $this->deletionBatch = array(); $ext = $this->file->getExtension(); $dotExt = $ext === '' ? '' : ".{$ext}"; foreach ($this->srcRels as $name => $srcRel) { // Skip files that have no hash (missing source) if (isset($hashes[$name])) { $hash = $hashes[$name]; $key = $hash . $dotExt; $dstRel = $this->file->repo->getDeletedHashPath($key) . $key; $this->deletionBatch[$name] = array($srcRel, $dstRel); } } // Lock the filearchive rows so that the files don't get deleted by a cleanup operation // We acquire this lock by running the inserts now, before the file operations. // // This potentially has poor lock contention characteristics -- an alternative // scheme would be to insert stub filearchive entries with no fa_name and commit // them in a separate transaction, then run the file ops, then update the fa_name fields. $this->doDBInserts(); // Execute the file deletion batch $status = $this->file->repo->deleteBatch($this->deletionBatch); if (!$status->isGood()) { $this->status->merge($status); } if (!$this->status->ok) { // Critical file deletion error // Roll back inserts, release lock and abort // TODO: delete the defunct filearchive rows if we are using a non-transactional DB $this->file->unlockAndRollback(); return $this->status; } // Purge squid if ($wgUseSquid) { $urls = array(); foreach ($this->srcRels as $srcRel) { $urlRel = str_replace('%2F', '/', rawurlencode($srcRel)); $urls[] = $this->file->repo->getZoneUrl('public') . '/' . $urlRel; } SquidUpdate::purge($urls); } // Delete image/oldimage rows $this->doDBDeletes(); // Commit and return $this->file->unlock(); wfProfileOut(__METHOD__); return $this->status; }
/** * Generates a thumbnail according to the given parameters and saves it to storage * @param TempFSFile $tmpFile Temporary file where the rendered thumbnail will be saved * @param array $transformParams * @param int $flags * @return bool|MediaTransformOutput */ public function generateAndSaveThumb($tmpFile, $transformParams, $flags) { global $wgUseSquid, $wgIgnoreImageErrors; $handler = $this->getHandler(); $normalisedParams = $transformParams; $handler->normaliseParams($this, $normalisedParams); $thumbName = $this->thumbName($normalisedParams); $thumbUrl = $this->getThumbUrl($thumbName); $thumbPath = $this->getThumbPath($thumbName); // final thumb path $tmpThumbPath = $tmpFile->getPath(); if ($handler->supportsBucketing()) { $this->generateBucketsIfNeeded($normalisedParams, $flags); } // Actually render the thumbnail... $thumb = $handler->doTransform($this, $tmpThumbPath, $thumbUrl, $transformParams); $tmpFile->bind($thumb); // keep alive with $thumb if (!$thumb) { // bad params? $thumb = false; } elseif ($thumb->isError()) { // transform error $this->lastError = $thumb->toText(); // Ignore errors if requested if ($wgIgnoreImageErrors && !($flags & self::RENDER_NOW)) { $thumb = $handler->getTransform($this, $tmpThumbPath, $thumbUrl, $transformParams); } } elseif ($this->repo && $thumb->hasFile() && !$thumb->fileIsSource()) { // Copy the thumbnail from the file system into storage... $disposition = $this->getThumbDisposition($thumbName); $status = $this->repo->quickImport($tmpThumbPath, $thumbPath, $disposition); if ($status->isOK()) { $thumb->setStoragePath($thumbPath); } else { $thumb = $this->transformErrorOutput($thumbPath, $thumbUrl, $transformParams, $flags); } // Give extensions a chance to do something with this thumbnail... Hooks::run('FileTransformed', array($this, $thumb, $tmpThumbPath, $thumbPath)); } // Purge. Useful in the event of Core -> Squid connection failure or squid // purge collisions from elsewhere during failure. Don't keep triggering for // "thumbs" which have the main image URL though (bug 13776) if ($wgUseSquid) { if (!$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL()) { SquidUpdate::purge(array($thumbUrl)); } } return $thumb; }
/** * Transform a media file * * @param array $params An associative array of handler-specific parameters. Typical * keys are width, height and page. * @param integer $flags A bitfield, may contain self::RENDER_NOW to force rendering * @return MediaTransformOutput */ function transform($params, $flags = 0) { global $wgUseSquid, $wgIgnoreImageErrors; wfProfileIn(__METHOD__); do { if (!$this->canRender()) { // not a bitmap or renderable image, don't try. $thumb = $this->iconThumb(); break; } $script = $this->getTransformScript(); if ($script && !($flags & self::RENDER_NOW)) { // Use a script to transform on client request, if possible $thumb = $this->handler->getScriptedTransform($this, $script, $params); if ($thumb) { break; } } $normalisedParams = $params; $this->handler->normaliseParams($this, $normalisedParams); $thumbName = $this->thumbName($normalisedParams); $thumbPath = $this->getThumbPath($thumbName); $thumbUrl = $this->getThumbUrl($thumbName); if ($this->repo->canTransformVia404() && !($flags & self::RENDER_NOW)) { $thumb = $this->handler->getTransform($this, $thumbPath, $thumbUrl, $params); break; } wfDebug(__METHOD__ . ": Doing stat for {$thumbPath}\n"); $this->migrateThumbFile($thumbName); if (file_exists($thumbPath)) { $thumb = $this->handler->getTransform($this, $thumbPath, $thumbUrl, $params); break; } $thumb = $this->handler->doTransform($this, $thumbPath, $thumbUrl, $params); // Ignore errors if requested if (!$thumb) { $thumb = null; } elseif ($thumb->isError()) { $this->lastError = $thumb->toText(); if ($wgIgnoreImageErrors && !($flags & self::RENDER_NOW)) { $thumb = $this->handler->getTransform($this, $thumbPath, $thumbUrl, $params); } } // Purge. Useful in the event of Core -> Squid connection failure or squid // purge collisions from elsewhere during failure. Don't keep triggering for // "thumbs" which have the main image URL though (bug 13776) if ($wgUseSquid && (!$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL())) { SquidUpdate::purge(array($thumbUrl)); } } while (false); wfProfileOut(__METHOD__); return $thumb; }
public function doPostCommitUpdates() { $file = wfLocalFile($this->title); $file->purgeCache(); $file->purgeDescription(); $purgeUrls = array(); foreach ($this->ids as $timestamp) { $archiveName = $timestamp . '!' . $this->title->getDBkey(); $file->purgeOldThumbnails($archiveName); $purgeUrls[] = $file->getArchiveUrl($archiveName); } if ($this->getConfig()->get('UseSquid')) { // purge full images from cache SquidUpdate::purge($purgeUrls); } return Status::newGood(); }
/** * Transform a media file * * @param array $params an associative array of handler-specific parameters. * Typical keys are width, height and page. * @param $flags Integer: a bitfield, may contain self::RENDER_NOW to force rendering * @return MediaTransformOutput|bool False on failure */ function transform($params, $flags = 0) { global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch; wfProfileIn(__METHOD__); do { if (!$this->canRender()) { $thumb = $this->iconThumb(); break; // not a bitmap or renderable image, don't try } // Get the descriptionUrl to embed it as comment into the thumbnail. Bug 19791. $descriptionUrl = $this->getDescriptionUrl(); if ($descriptionUrl) { $params['descriptionUrl'] = wfExpandUrl($descriptionUrl, PROTO_CANONICAL); } $handler = $this->getHandler(); $script = $this->getTransformScript(); if ($script && !($flags & self::RENDER_NOW)) { // Use a script to transform on client request, if possible $thumb = $handler->getScriptedTransform($this, $script, $params); if ($thumb) { break; } } $normalisedParams = $params; $handler->normaliseParams($this, $normalisedParams); $thumbName = $this->thumbName($normalisedParams); $thumbUrl = $this->getThumbUrl($thumbName); $thumbPath = $this->getThumbPath($thumbName); // final thumb path if ($this->repo) { // Defer rendering if a 404 handler is set up... if ($this->repo->canTransformVia404() && !($flags & self::RENDER_NOW)) { wfDebug(__METHOD__ . " transformation deferred."); // XXX: Pass in the storage path even though we are not rendering anything // and the path is supposed to be an FS path. This is due to getScalerType() // getting called on the path and clobbering $thumb->getUrl() if it's false. $thumb = $handler->getTransform($this, $thumbPath, $thumbUrl, $params); break; } // Clean up broken thumbnails as needed $this->migrateThumbFile($thumbName); // Check if an up-to-date thumbnail already exists... wfDebug(__METHOD__ . ": Doing stat for {$thumbPath}\n"); if (!($flags & self::RENDER_FORCE) && $this->repo->fileExists($thumbPath)) { $timestamp = $this->repo->getFileTimestamp($thumbPath); if ($timestamp !== false && $timestamp >= $wgThumbnailEpoch) { // XXX: Pass in the storage path even though we are not rendering anything // and the path is supposed to be an FS path. This is due to getScalerType() // getting called on the path and clobbering $thumb->getUrl() if it's false. $thumb = $handler->getTransform($this, $thumbPath, $thumbUrl, $params); $thumb->setStoragePath($thumbPath); break; } } elseif ($flags & self::RENDER_FORCE) { wfDebug(__METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n"); } } // If the backend is ready-only, don't keep generating thumbnails // only to return transformation errors, just return the error now. if ($this->repo->getReadOnlyReason() !== false) { $thumb = $this->transformErrorOutput($thumbPath, $thumbUrl, $params, $flags); break; } // Create a temp FS file with the same extension and the thumbnail $thumbExt = FileBackend::extensionFromPath($thumbPath); $tmpFile = TempFSFile::factory('transform_', $thumbExt); if (!$tmpFile) { $thumb = $this->transformErrorOutput($thumbPath, $thumbUrl, $params, $flags); break; } $tmpThumbPath = $tmpFile->getPath(); // path of 0-byte temp file // Actually render the thumbnail... wfProfileIn(__METHOD__ . '-doTransform'); $thumb = $handler->doTransform($this, $tmpThumbPath, $thumbUrl, $params); wfProfileOut(__METHOD__ . '-doTransform'); $tmpFile->bind($thumb); // keep alive with $thumb if (!$thumb) { // bad params? $thumb = null; } elseif ($thumb->isError()) { // transform error $this->lastError = $thumb->toText(); // Ignore errors if requested if ($wgIgnoreImageErrors && !($flags & self::RENDER_NOW)) { $thumb = $handler->getTransform($this, $tmpThumbPath, $thumbUrl, $params); } } elseif ($this->repo && $thumb->hasFile() && !$thumb->fileIsSource()) { // Copy the thumbnail from the file system into storage... $disposition = $this->getThumbDisposition($thumbName); $status = $this->repo->quickImport($tmpThumbPath, $thumbPath, $disposition); if ($status->isOK()) { $thumb->setStoragePath($thumbPath); } else { $thumb = $this->transformErrorOutput($thumbPath, $thumbUrl, $params, $flags); } // Give extensions a chance to do something with this thumbnail... wfRunHooks('FileTransformed', array($this, $thumb, $tmpThumbPath, $thumbPath)); } // Purge. Useful in the event of Core -> Squid connection failure or squid // purge collisions from elsewhere during failure. Don't keep triggering for // "thumbs" which have the main image URL though (bug 13776) if ($wgUseSquid) { if (!$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL()) { SquidUpdate::purge(array($thumbUrl)); } } } while (false); wfProfileOut(__METHOD__); return is_object($thumb) ? $thumb : false; }
/** * Transform a media file * * @param $params Array: an associative array of handler-specific parameters. * Typical keys are width, height and page. * @param $flags Integer: a bitfield, may contain self::RENDER_NOW to force rendering * @return MediaTransformOutput | false */ function transform($params, $flags = 0) { global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch, $wgServer; wfProfileIn(__METHOD__); do { if (!$this->canRender()) { // not a bitmap or renderable image, don't try. $thumb = $this->iconThumb(); break; } // Get the descriptionUrl to embed it as comment into the thumbnail. Bug 19791. $descriptionUrl = $this->getDescriptionUrl(); if ($descriptionUrl) { $params['descriptionUrl'] = wfExpandUrl($descriptionUrl, PROTO_CANONICAL); } $script = $this->getTransformScript(); if ($script && !($flags & self::RENDER_NOW)) { // Use a script to transform on client request, if possible $thumb = $this->handler->getScriptedTransform($this, $script, $params); if ($thumb) { break; } } $normalisedParams = $params; $this->handler->normaliseParams($this, $normalisedParams); $thumbName = $this->thumbName($normalisedParams); $thumbPath = $this->getThumbPath($thumbName); $thumbUrl = $this->getThumbUrl($thumbName); if ($this->repo && $this->repo->canTransformVia404() && !($flags & self::RENDER_NOW)) { $thumb = $this->handler->getTransform($this, $thumbPath, $thumbUrl, $params); break; } wfDebug(__METHOD__ . ": Doing stat for {$thumbPath}\n"); $this->migrateThumbFile($thumbName); if (file_exists($thumbPath)) { $thumbTime = filemtime($thumbPath); if ($thumbTime !== FALSE && gmdate('YmdHis', $thumbTime) >= $wgThumbnailEpoch) { $thumb = $this->handler->getTransform($this, $thumbPath, $thumbUrl, $params); break; } } $thumb = $this->handler->doTransform($this, $thumbPath, $thumbUrl, $params); // Ignore errors if requested if (!$thumb) { $thumb = null; } elseif ($thumb->isError()) { $this->lastError = $thumb->toText(); if ($wgIgnoreImageErrors && !($flags & self::RENDER_NOW)) { $thumb = $this->handler->getTransform($this, $thumbPath, $thumbUrl, $params); } } // Purge. Useful in the event of Core -> Squid connection failure or squid // purge collisions from elsewhere during failure. Don't keep triggering for // "thumbs" which have the main image URL though (bug 13776) if ($wgUseSquid && (!$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL())) { SquidUpdate::purge(array($thumbUrl)); } } while (false); wfProfileOut(__METHOD__); return is_object($thumb) ? $thumb : false; }