Merge "Ensure users are able to edit the page after changing the content model"
[lhc/web/wiklou.git] / includes / filerepo / file / LocalFile.php
index 40141c9..60cfdac 100644 (file)
 
 use \MediaWiki\Logger\LoggerFactory;
 
-/**
- * Bump this number when serialized cache records may be incompatible.
- */
-define( 'MW_FILE_VERSION', 9 );
-
 /**
  * Class to represent a local file in the wiki's own database
  *
@@ -46,6 +41,8 @@ define( 'MW_FILE_VERSION', 9 );
  * @ingroup FileAbstraction
  */
 class LocalFile extends File {
+       const VERSION = 10; // cache version
+
        const CACHE_FIELD_MAX_LEN = 1000;
 
        /** @var bool Does the file exist on disk? (loadFromXxx) */
@@ -240,77 +237,71 @@ class LocalFile extends File {
         * @return string|bool
         */
        function getCacheKey() {
-               $hashedName = md5( $this->getName() );
-
-               return $this->repo->getSharedCacheKey( 'file', $hashedName );
+               return $this->repo->getSharedCacheKey( 'file', sha1( $this->getName() ) );
        }
 
        /**
-        * Try to load file metadata from memcached. Returns true on success.
-        * @return bool
+        * Try to load file metadata from memcached, falling back to the database
         */
        private function loadFromCache() {
                $this->dataLoaded = false;
                $this->extraDataLoaded = false;
-               $key = $this->getCacheKey();
 
+               $key = $this->getCacheKey();
                if ( !$key ) {
-                       return false;
-               }
+                       $this->loadFromDB( self::READ_NORMAL );
 
-               $cache = ObjectCache::getMainWANInstance();
-               $cachedValues = $cache->get( $key );
-
-               // Check if the key existed and belongs to this version of MediaWiki
-               if ( is_array( $cachedValues ) && $cachedValues['version'] == MW_FILE_VERSION ) {
-                       $this->fileExists = $cachedValues['fileExists'];
-                       if ( $this->fileExists ) {
-                               $this->setProps( $cachedValues );
-                       }
-                       $this->dataLoaded = true;
-                       $this->extraDataLoaded = true;
-                       foreach ( $this->getLazyCacheFields( '' ) as $field ) {
-                               $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
-                       }
+                       return;
                }
 
-               return $this->dataLoaded;
-       }
-
-       /**
-        * Save the file metadata to memcached
-        */
-       private function saveToCache() {
-               $this->load();
+               $cache = ObjectCache::getMainWANInstance();
+               $cachedValues = $cache->getWithSetCallback(
+                       $key,
+                       $cache::TTL_WEEK,
+                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
+                               $setOpts += Database::getCacheSetOptions( $this->repo->getSlaveDB() );
+
+                               $this->loadFromDB( self::READ_NORMAL );
+
+                               $fields = $this->getCacheFields( '' );
+                               $cacheVal['fileExists'] = $this->fileExists;
+                               if ( $this->fileExists ) {
+                                       foreach ( $fields as $field ) {
+                                               $cacheVal[$field] = $this->$field;
+                                       }
+                               }
+                               // Strip off excessive entries from the subset of fields that can become large.
+                               // If the cache value gets to large it will not fit in memcached and nothing will
+                               // get cached at all, causing master queries for any file access.
+                               foreach ( $this->getLazyCacheFields( '' ) as $field ) {
+                                       if ( isset( $cacheVal[$field] )
+                                               && strlen( $cacheVal[$field] ) > 100 * 1024
+                                       ) {
+                                               unset( $cacheVal[$field] ); // don't let the value get too big
+                                       }
+                               }
 
-               $key = $this->getCacheKey();
-               if ( !$key ) {
-                       return;
-               }
+                               if ( $this->fileExists ) {
+                                       $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
+                               } else {
+                                       $ttl = $cache::TTL_DAY;
+                               }
 
-               $fields = $this->getCacheFields( '' );
-               $cacheVal = [ 'version' => MW_FILE_VERSION ];
-               $cacheVal['fileExists'] = $this->fileExists;
+                               return $cacheVal;
+                       },
+                       [ 'version' => self::VERSION ]
+               );
 
+               $this->fileExists = $cachedValues['fileExists'];
                if ( $this->fileExists ) {
-                       foreach ( $fields as $field ) {
-                               $cacheVal[$field] = $this->$field;
-                       }
+                       $this->setProps( $cachedValues );
                }
 
-               // Strip off excessive entries from the subset of fields that can become large.
-               // If the cache value gets to large it will not fit in memcached and nothing will
-               // get cached at all, causing master queries for any file access.
+               $this->dataLoaded = true;
+               $this->extraDataLoaded = true;
                foreach ( $this->getLazyCacheFields( '' ) as $field ) {
-                       if ( isset( $cacheVal[$field] ) && strlen( $cacheVal[$field] ) > 100 * 1024 ) {
-                               unset( $cacheVal[$field] ); // don't let the value get too big
-                       }
+                       $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
                }
-
-               // Cache presence for 1 week and negatives for 1 day
-               $ttl = $this->fileExists ? 86400 * 7 : 86400;
-               $opts = Database::getCacheSetOptions( $this->repo->getSlaveDB() );
-               ObjectCache::getMainWANInstance()->set( $key, $cacheVal, $ttl, $opts );
        }
 
        /**
@@ -322,9 +313,12 @@ class LocalFile extends File {
                        return;
                }
 
-               $this->repo->getMasterDB()->onTransactionPreCommitOrIdle( function() use ( $key ) {
-                       ObjectCache::getMainWANInstance()->delete( $key );
-               } );
+               $this->repo->getMasterDB()->onTransactionPreCommitOrIdle(
+                       function () use ( $key ) {
+                               ObjectCache::getMainWANInstance()->delete( $key );
+                       },
+                       __METHOD__
+               );
        }
 
        /**
@@ -440,16 +434,18 @@ class LocalFile extends File {
        private function loadFieldsWithTimestamp( $dbr, $fname ) {
                $fieldMap = false;
 
-               $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
-                       [ 'img_name' => $this->getName(), 'img_timestamp' => $this->getTimestamp() ],
-                       $fname );
+               $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ), [
+                               'img_name' => $this->getName(),
+                               'img_timestamp' => $dbr->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_' ),
-                               [ 'oi_name' => $this->getName(), 'oi_timestamp' => $this->getTimestamp() ],
-                               $fname );
+                       $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ), [
+                                       'oi_name' => $this->getName(),
+                                       'oi_timestamp' => $dbr->timestamp( $this->getTimestamp() )
+                               ], $fname );
                        if ( $row ) {
                                $fieldMap = $this->unprefixRow( $row, 'oi_' );
                        }
@@ -545,12 +541,13 @@ class LocalFile extends File {
         */
        function load( $flags = 0 ) {
                if ( !$this->dataLoaded ) {
-                       if ( ( $flags & self::READ_LATEST ) || !$this->loadFromCache() ) {
+                       if ( $flags & self::READ_LATEST ) {
                                $this->loadFromDB( $flags );
-                               $this->saveToCache();
+                       } else {
+                               $this->loadFromCache();
                        }
-                       $this->dataLoaded = true;
                }
+
                if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
                        // @note: loads on name/timestamp to reduce race condition problems
                        $this->loadExtraFromDB();
@@ -766,7 +763,7 @@ class LocalFile extends File {
 
                if ( $type == 'text' ) {
                        return $this->user_text;
-               } elseif ( $type == 'id' ) {
+               } else { // id
                        return (int)$this->user;
                }
        }
@@ -1182,7 +1179,8 @@ class LocalFile extends File {
                        ) {
                                $props = $this->repo->getFileProps( $srcPath );
                        } else {
-                               $props = FSFile::getPropsFromPath( $srcPath );
+                               $mwProps = new MWFileProps( MimeMagic::singleton() );
+                               $props = $mwProps->getPropsFromPath( $srcPath, true );
                        }
                }
 
@@ -1483,8 +1481,10 @@ class LocalFile extends File {
                                                );
 
                                                if ( isset( $status->value['revision'] ) ) {
+                                                       /** @var $rev Revision */
+                                                       $rev = $status->value['revision'];
                                                        // Associate new page revision id
-                                                       $logEntry->setAssociatedRevId( $status->value['revision']->getId() );
+                                                       $logEntry->setAssociatedRevId( $rev->getId() );
                                                }
                                                // This relies on the resetArticleID() call in WikiPage::insertOn(),
                                                // which is triggered on $descTitle by doEditContent() above.
@@ -1621,7 +1621,9 @@ class LocalFile extends File {
                        $sha1 = $repo->isVirtualUrl( $srcPath )
                                ? $repo->getFileSha1( $srcPath )
                                : FSFile::getSha1Base36FromPath( $srcPath );
-                       $dst = $repo->getBackend()->getPathForSHA1( $sha1 );
+                       /** @var FileBackendDBRepoWrapper $wrapperBackend */
+                       $wrapperBackend = $repo->getBackend();
+                       $dst = $wrapperBackend->getPathForSHA1( $sha1 );
                        $status = $repo->quickImport( $src, $dst );
                        if ( $flags & File::DELETE_SOURCE ) {
                                unlink( $srcPath );
@@ -2006,12 +2008,15 @@ class LocalFile extends File {
                        }
                        // Release the lock *after* commit to avoid row-level contention.
                        // Make sure it triggers on rollback() as well as commit() (T132921).
-                       $dbw->onTransactionResolution( function () use ( $logger ) {
-                               $status = $this->releaseFileLock();
-                               if ( !$status->isGood() ) {
-                                       $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
-                               }
-                       } );
+                       $dbw->onTransactionResolution(
+                               function () use ( $logger ) {
+                                       $status = $this->releaseFileLock();
+                                       if ( !$status->isGood() ) {
+                                               $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
+                                       }
+                               },
+                               __METHOD__
+                       );
                        // Callers might care if the SELECT snapshot is safely fresh
                        $this->lockedOwnTrx = $makesTransaction;
                }
@@ -2492,6 +2497,7 @@ class LocalFileRestoreBatch {
         * @return FileRepoStatus
         */
        public function execute() {
+               /** @var Language */
                global $wgLang;
 
                $repo = $this->file->getRepo();
@@ -2689,7 +2695,7 @@ class LocalFileRestoreBatch {
                                // Even if some files could be copied, fail entirely as that is the
                                // easiest thing to do without data loss
                                $this->cleanupFailedBatch( $storeStatus, $storeBatch );
-                               $status->ok = false;
+                               $status->setOK( false );
                                $this->file->unlock();
 
                                return $status;
@@ -2949,7 +2955,7 @@ class LocalFileMoveBatch {
                if ( !$statusDb->isGood() ) {
                        $destFile->unlock();
                        $this->file->unlock();
-                       $statusDb->ok = false;
+                       $statusDb->setOK( false );
 
                        return $statusDb;
                }
@@ -2968,7 +2974,7 @@ class LocalFileMoveBatch {
                                $this->file->unlock();
                                wfDebugLog( 'imagemove', "Error in moving files: "
                                        . $statusMove->getWikiText( false, false, 'en' ) );
-                               $statusMove->ok = false;
+                               $statusMove->setOK( false );
 
                                return $statusMove;
                        }