Make phpcs-strict pass on includes/ (4/~10)
[lhc/web/wiklou.git] / includes / filerepo / file / LocalFile.php
index dbf6918..73ab04d 100644 (file)
@@ -121,7 +121,13 @@ class LocalFile extends File {
        /** @var bool True if file is not present in file system. Not to be cached in memcached */
        private $missing;
 
+       /** @var int UNIX timestamp of last markVolatile() call */
+       private $lastMarkedVolatile = 0;
+
        const LOAD_ALL = 1; // integer; load all the lazy fields too (like metadata)
+       const LOAD_VIA_SLAVE = 2; // integer; use a slave to load the data
+
+       const VOLATILE_TTL = 300; // integer; seconds
 
        /**
         * Create a LocalFile from a title
@@ -226,7 +232,7 @@ class LocalFile extends File {
        /**
         * Get the memcached key for the main data for this file, or false if
         * there is no access to the shared cache.
-        * @return bool
+        * @return string|bool
         */
        function getCacheKey() {
                $hashedName = md5( $this->getName() );
@@ -374,7 +380,7 @@ class LocalFile extends File {
        /**
         * Load file metadata from the DB
         */
-       function loadFromDB() {
+       function loadFromDB( $flags = 0 ) {
                # Polymorphic function name to distinguish foreign and local fetches
                $fname = get_class( $this ) . '::' . __FUNCTION__;
                wfProfileIn( $fname );
@@ -383,7 +389,10 @@ class LocalFile extends File {
                $this->dataLoaded = true;
                $this->extraDataLoaded = true;
 
-               $dbr = $this->repo->getMasterDB();
+               $dbr = ( $flags & self::LOAD_VIA_SLAVE )
+                       ? $this->repo->getSlaveDB()
+                       : $this->repo->getMasterDB();
+
                $row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
                        array( 'img_name' => $this->getName() ), $fname );
 
@@ -408,19 +417,13 @@ class LocalFile extends File {
                # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
                $this->extraDataLoaded = true;
 
-               $dbr = $this->repo->getSlaveDB();
-               // In theory the file could have just been renamed/deleted...oh well
-               $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
-                       array( 'img_name' => $this->getName() ), $fname );
-
-               if ( !$row ) { // fallback to master
-                       $dbr = $this->repo->getMasterDB();
-                       $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
-                               array( 'img_name' => $this->getName() ), $fname );
+               $fieldMap = $this->loadFieldsWithTimestamp( $this->repo->getSlaveDB(), $fname );
+               if ( !$fieldMap ) {
+                       $fieldMap = $this->loadFieldsWithTimestamp( $this->repo->getMasterDB(), $fname );
                }
 
-               if ( $row ) {
-                       foreach ( $this->unprefixRow( $row, 'img_' ) as $name => $value ) {
+               if ( $fieldMap ) {
+                       foreach ( $fieldMap as $name => $value ) {
                                $this->$name = $value;
                        }
                } else {
@@ -431,6 +434,32 @@ class LocalFile extends File {
                wfProfileOut( $fname );
        }
 
+       /**
+        * @param DatabaseBase $dbr
+        * @param string $fname
+        * @return array|false
+        */
+       private function loadFieldsWithTimestamp( $dbr, $fname ) {
+               $fieldMap = false;
+
+               $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
+                       array( 'img_name' => $this->getName(), 'img_timestamp' => $this->getTimestamp() ),
+                       $fname );
+               if ( $row ) {
+                       $fieldMap = $this->unprefixRow( $row, 'img_' );
+               } else {
+                       # File may have been uploaded over in the meantime; check the old versions
+                       $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ),
+                               array( 'oi_name' => $this->getName(), 'oi_timestamp' => $this->getTimestamp() ),
+                               $fname );
+                       if ( $row ) {
+                               $fieldMap = $this->unprefixRow( $row, 'oi_' );
+                       }
+               }
+
+               return $fieldMap;
+       }
+
        /**
         * @param array $row Row
         * @param string $prefix
@@ -467,6 +496,8 @@ class LocalFile extends File {
 
                $decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
 
+               $decoded['metadata'] = $this->repo->getSlaveDB()->decodeBlob( $decoded['metadata'] );
+
                if ( empty( $decoded['major_mime'] ) ) {
                        $decoded['mime'] = 'unknown/unknown';
                } else {
@@ -509,7 +540,7 @@ class LocalFile extends File {
        function load( $flags = 0 ) {
                if ( !$this->dataLoaded ) {
                        if ( !$this->loadFromCache() ) {
-                               $this->loadFromDB();
+                               $this->loadFromDB( $this->isVolatile() ? 0 : self::LOAD_VIA_SLAVE );
                                $this->saveToCache();
                        }
                        $this->dataLoaded = true;
@@ -793,41 +824,6 @@ class LocalFile extends File {
        /** createThumb inherited */
        /** transform inherited */
 
-       /**
-        * Fix thumbnail files from 1.4 or before, with extreme prejudice
-        * @todo Do we still care about this? Perhaps a maintenance script
-        *   can be made instead. Enabling this code results in a serious
-        *   RTT regression for wikis without 404 handling.
-        *
-        * @param string $thumbName
-        */
-       function migrateThumbFile( $thumbName ) {
-               /* Old code for bug 2532
-               $thumbDir = $this->getThumbPath();
-               $thumbPath = "$thumbDir/$thumbName";
-               if ( is_dir( $thumbPath ) ) {
-                       // Directory where file should be
-                       // This happened occasionally due to broken migration code in 1.5
-                       // Rename to broken-*
-                       for ( $i = 0; $i < 100; $i++ ) {
-                               $broken = $this->repo->getZonePath( 'public' ) . "/broken-$i-$thumbName";
-                               if ( !file_exists( $broken ) ) {
-                                       rename( $thumbPath, $broken );
-                                       break;
-                               }
-                       }
-                       // Doesn't exist anymore
-                       clearstatcache();
-               }
-               */
-               /*
-               if ( $this->repo->fileExists( $thumbDir ) ) {
-                       // Delete file where directory should be
-                       $this->repo->cleanupBatch( array( $thumbDir ) );
-               }
-               */
-       }
-
        /** getHandler inherited */
        /** iconThumb inherited */
        /** getLastError inherited */
@@ -1395,7 +1391,8 @@ class LocalFile extends File {
                                $dbw,
                                $descTitle->getArticleID(),
                                $editSummary,
-                               false
+                               false,
+                               $user
                        );
                        if ( !is_null( $nullRevision ) ) {
                                $nullRevision->insertOn( $dbw );
@@ -1449,7 +1446,6 @@ class LocalFile extends File {
 
                wfProfileOut( __METHOD__ . '-edit' );
 
-
                if ( $reupload ) {
                        # Delete old thumbnails
                        wfProfileIn( __METHOD__ . '-purge' );
@@ -1603,14 +1599,15 @@ class LocalFile extends File {
         *
         * @param string $reason
         * @param bool $suppress
+        * @param User|null $user
         * @return FileRepoStatus
         */
-       function delete( $reason, $suppress = false ) {
+       function delete( $reason, $suppress = false, $user = null ) {
                if ( $this->getRepo()->getReadOnlyReason() !== false ) {
                        return $this->readOnlyFatalStatus();
                }
 
-               $batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
+               $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
 
                $this->lock(); // begin
                $batch->addCurrent();
@@ -1660,16 +1657,17 @@ class LocalFile extends File {
         * @param string $archiveName
         * @param string $reason
         * @param bool $suppress
+        * @param User|null $user
         * @throws MWException Exception on database or file store failure
         * @return FileRepoStatus
         */
-       function deleteOld( $archiveName, $reason, $suppress = false ) {
+       function deleteOld( $archiveName, $reason, $suppress = false, $user = null ) {
                global $wgUseSquid;
                if ( $this->getRepo()->getReadOnlyReason() !== false ) {
                        return $this->readOnlyFatalStatus();
                }
 
-               $batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
+               $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
 
                $this->lock(); // begin
                $batch->addOld( $archiveName );
@@ -1851,6 +1849,8 @@ class LocalFile extends File {
                        } );
                }
 
+               $this->markVolatile(); // file may change soon
+
                return $dbw->selectField( 'image', '1',
                        array( 'img_name' => $this->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
        }
@@ -1870,6 +1870,48 @@ class LocalFile extends File {
                }
        }
 
+       /**
+        * Mark a file as about to be changed
+        *
+        * This sets a cache key that alters master/slave DB loading behavior
+        *
+        * @return bool Success
+        */
+       protected function markVolatile() {
+               global $wgMemc;
+
+               $key = $this->repo->getSharedCacheKey( 'file-volatile', md5( $this->getName() ) );
+               if ( $key ) {
+                       $this->lastMarkedVolatile = time();
+                       return $wgMemc->set( $key, $this->lastMarkedVolatile, self::VOLATILE_TTL );
+               }
+
+               return true;
+       }
+
+       /**
+        * Check if a file is about to be changed or has been changed recently
+        *
+        * @see LocalFile::isVolatile()
+        * @return bool Whether the file is volatile
+        */
+       protected function isVolatile() {
+               global $wgMemc;
+
+               $key = $this->repo->getSharedCacheKey( 'file-volatile', md5( $this->getName() ) );
+               if ( !$key ) {
+                       // repo unavailable; bail.
+                       return false;
+               }
+
+               if ( $this->lastMarkedVolatile === 0 ) {
+                       $this->lastMarkedVolatile = $wgMemc->get( $key ) ?: 0;
+               }
+
+               $volatileDuration = time() - $this->lastMarkedVolatile;
+               return $volatileDuration <= self::VOLATILE_TTL;
+       }
+
        /**
         * Roll back the DB transaction and mark the image unlocked
         */
@@ -1924,15 +1966,25 @@ class LocalFileDeleteBatch {
        /** @var FileRepoStatus */
        private $status;
 
+       /** @var User */
+       private $user;
+
        /**
         * @param File $file
         * @param string $reason
         * @param bool $suppress
+        * @param User|null $user
         */
-       function __construct( File $file, $reason = '', $suppress = false ) {
+       function __construct( File $file, $reason = '', $suppress = false, $user = null ) {
                $this->file = $file;
                $this->reason = $reason;
                $this->suppress = $suppress;
+               if ( $user ) {
+                       $this->user = $user;
+               } else {
+                       global $wgUser;
+                       $this->user = $wgUser;
+               }
                $this->status = $file->repo->newGood();
        }
 
@@ -2046,11 +2098,9 @@ class LocalFileDeleteBatch {
        }
 
        function doDBInserts() {
-               global $wgUser;
-
                $dbw = $this->file->repo->getMasterDB();
                $encTimestamp = $dbw->addQuotes( $dbw->timestamp() );
-               $encUserId = $dbw->addQuotes( $wgUser->getId() );
+               $encUserId = $dbw->addQuotes( $this->user->getId() );
                $encReason = $dbw->addQuotes( $this->reason );
                $encGroup = $dbw->addQuotes( 'deleted' );
                $ext = $this->file->getExtension();
@@ -2076,7 +2126,11 @@ class LocalFileDeleteBatch {
                        $dbw->insertSelect( 'filearchive', 'image',
                                array(
                                        'fa_storage_group' => $encGroup,
-                                       'fa_storage_key' => "CASE WHEN img_sha1='' THEN '' ELSE $concat END",
+                                       'fa_storage_key' => $dbw->conditional(
+                                               array( 'img_sha1' => '' ),
+                                               $dbw->addQuotes( '' ),
+                                               $concat
+                                       ),
                                        'fa_deleted_user' => $encUserId,
                                        'fa_deleted_timestamp' => $encTimestamp,
                                        'fa_deleted_reason' => $encReason,
@@ -2108,7 +2162,11 @@ class LocalFileDeleteBatch {
                        $dbw->insertSelect( 'filearchive', 'oldimage',
                                array(
                                        'fa_storage_group' => $encGroup,
-                                       'fa_storage_key' => "CASE WHEN oi_sha1='' THEN '' ELSE $concat END",
+                                       'fa_storage_key' => $dbw->conditional(
+                                               array( 'oi_sha1' => '' ),
+                                               $dbw->addQuotes( '' ),
+                                               $concat
+                                       ),
                                        'fa_deleted_user' => $encUserId,
                                        'fa_deleted_timestamp' => $encTimestamp,
                                        'fa_deleted_reason' => $encReason,