Merge "Update list item newline handling to follow Parsoid's model"
[lhc/web/wiklou.git] / includes / filerepo / file / LocalFile.php
index a8fa8bd..300e68e 100644 (file)
@@ -1248,19 +1248,13 @@ class LocalFile extends File {
                        wfProfileOut( __METHOD__ . '-getProps' );
                }
 
+               # Imports or such might force a certain timestamp; otherwise we generate
+               # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
                if ( $timestamp === false ) {
-                       // Use FOR UPDATE in case lock()/unlock() did not start the transaction
-                       $ltimestamp = $dbw->selectField( 'image', 'img_timestamp',
-                               array( 'img_name' => $this->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
-                       $ltime = $ltimestamp ? wfTimestamp( TS_UNIX, $ltimestamp ) : false;
-                       $ctime = time();
-                       // Avoid a timestamp that is not newer than the last version
-                       if ( $ctime > $ltime ) {
-                               $timestamp = $dbw->timestamp( $ctime );
-                       } else {
-                               sleep( 1 ); // fast enough uploads will go in to the future otherwise
-                               $timestamp = $dbw->timestamp( $ltime + 1 );
-                       }
+                       $timestamp = $dbw->timestamp();
+                       $allowTimeKludge = true;
+               } else {
+                       $allowTimeKludge = false;
                }
 
                $props['description'] = $comment;
@@ -1303,6 +1297,20 @@ class LocalFile extends File {
                        'IGNORE'
                );
                if ( $dbw->affectedRows() == 0 ) {
+                       if ( $allowTimeKludge ) {
+                               # Use FOR UPDATE to ignore any transaction snapshotting
+                               $ltimestamp = $dbw->selectField( 'image', 'img_timestamp',
+                                       array( 'img_name' => $this->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
+                               $lUnixtime = $ltimestamp ? wfTimestamp( TS_UNIX, $ltimestamp ) : false;
+                               # Avoid a timestamp that is not newer than the last version
+                               # TODO: the image/oldimage tables should be like page/revision with an ID field
+                               if ( $lUnixtime && wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
+                                       sleep( 1 ); // fast enough re-uploads would go far in the future otherwise
+                                       $timestamp = $dbw->timestamp( $lUnixtime + 1 );
+                                       $this->timestamp = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
+                               }
+                       }
+
                        # (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
@@ -1836,8 +1844,8 @@ class LocalFile extends File {
        /**
         * Start a transaction and lock the image for update
         * Increments a reference counter if the lock is already held
-        * @throws MWException
-        * @return bool True if the image exists, false otherwise
+        * @throws MWException Throws an error if the lock was not acquired
+        * @return bool success
         */
        function lock() {
                $dbw = $this->repo->getMasterDB();
@@ -1849,21 +1857,22 @@ class LocalFile extends File {
                        }
                        $this->locked++;
                        // Bug 54736: use simple lock to handle when the file does not exist.
-                       // SELECT FOR UPDATE only locks records not the gaps where there are none.
-                       $cache = wfGetMainCache();
-                       $key = $this->getCacheKey();
-                       if ( !$cache->lock( $key, 5 ) ) {
+                       // SELECT FOR UPDATE prevents changes, not other SELECTs with FOR UPDATE.
+                       // Also, that would cause contention on INSERT of similarly named rows.
+                       $backend = $this->getRepo()->getBackend();
+                       $lockPaths = array( $this->getPath() ); // represents all versions of the file
+                       $status = $backend->lockFiles( $lockPaths, LockManager::LOCK_EX, 5 );
+                       if ( !$status->isGood() ) {
                                throw new MWException( "Could not acquire lock for '{$this->getName()}.'" );
                        }
-                       $dbw->onTransactionIdle( function () use ( $cache, $key ) {
-                               $cache->unlock( $key ); // release on commit
+                       $dbw->onTransactionIdle( function () use ( $backend, $lockPaths ) {
+                               $backend->unlockFiles( $lockPaths, LockManager::LOCK_EX ); // release on commit
                        } );
                }
 
                $this->markVolatile(); // file may change soon
 
-               return $dbw->selectField( 'image', '1',
-                       array( 'img_name' => $this->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
+               return true;
        }
 
        /**
@@ -2394,10 +2403,14 @@ class LocalFileRestoreBatch {
                        return $this->file->repo->newGood();
                }
 
-               $exists = $this->file->lock();
+               $this->file->lock();
+
                $dbw = $this->file->repo->getMasterDB();
                $status = $this->file->repo->newGood();
 
+               $exists = (bool)$dbw->selectField( 'image', '1',
+                       array( 'img_name' => $this->file->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
+
                // Fetch all or selected archived revisions for the file,
                // sorted from the most recent to the oldest.
                $conditions = array( 'fa_name' => $this->file->getName() );
@@ -2758,7 +2771,8 @@ class LocalFileMoveBatch {
                $result = $this->db->select( 'oldimage',
                        array( 'oi_archive_name', 'oi_deleted' ),
                        array( 'oi_name' => $this->oldName ),
-                       __METHOD__
+                       __METHOD__,
+                       array( 'FOR UPDATE' ) // ignore snapshot
                );
 
                foreach ( $result as $row ) {