r92009 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r92008‎ | r92009 | r92010 >
Date:21:11, 12 July 2011
Author:raindrift
Status:resolved (Comments)
Tags:
Comment:
Refactored UploadStash and related classes to use the database for file metadata storage instead of the session, see bug 26179

Tweaked the UploadWizard to work properly with the new backend code, updated tests
Modified paths:
  • /trunk/extensions/UploadWizard/UploadWizard.i18n.php (modified) (history)
  • /trunk/extensions/UploadWizard/UploadWizardHooks.php (modified) (history)
  • /trunk/extensions/UploadWizard/resources/mw.Api.js (modified) (history)
  • /trunk/extensions/UploadWizard/resources/mw.UploadWizard.js (modified) (history)
  • /trunk/extensions/UploadWizard/resources/mw.UploadWizardDetails.js (modified) (history)
  • /trunk/phase3/includes/api/ApiQueryStashImageInfo.php (modified) (history)
  • /trunk/phase3/includes/api/ApiUpload.php (modified) (history)
  • /trunk/phase3/includes/installer/MysqlUpdater.php (modified) (history)
  • /trunk/phase3/includes/installer/SqliteUpdater.php (modified) (history)
  • /trunk/phase3/includes/upload/UploadBase.php (modified) (history)
  • /trunk/phase3/includes/upload/UploadFromStash.php (modified) (history)
  • /trunk/phase3/includes/upload/UploadStash.php (modified) (history)
  • /trunk/phase3/maintenance/archives/patch-uploadstash.sql (added) (history)
  • /trunk/phase3/maintenance/tables.sql (modified) (history)
  • /trunk/phase3/tests/phpunit/includes/api/ApiUploadTest.php (modified) (history)
  • /trunk/phase3/tests/phpunit/includes/upload/UploadStashTest.php (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/archives/patch-uploadstash.sql
@@ -0,0 +1,44 @@
 2+--
 3+-- Store information about newly uploaded files before they're
 4+-- moved into the actual filestore
 5+--
 6+CREATE TABLE /*_*/uploadstash (
 7+ us_id int unsigned NOT NULL PRIMARY KEY auto_increment,
 8+
 9+ -- the user who uploaded the file.
 10+ us_user int unsigned NOT NULL,
 11+
 12+ -- file key. this is how applications actually search for the file.
 13+ -- this might go away, or become the primary key.
 14+ us_key varchar(255) NOT NULL,
 15+
 16+ -- the original path
 17+ us_orig_path varchar(255) NOT NULL,
 18+
 19+ -- the temporary path at which the file is actually stored
 20+ us_path varchar(255) NOT NULL,
 21+
 22+ -- which type of upload the file came from (sometimes)
 23+ us_source_type varchar(50),
 24+
 25+ -- the date/time on which the file was added
 26+ us_timestamp varchar(14) not null,
 27+
 28+ -- file properties from File::getPropsFromPath. these may prove unnecessary.
 29+ --
 30+ us_size int unsigned NOT NULL,
 31+ -- this hash comes from File::sha1Base36(), and is 31 characters
 32+ us_sha1 varchar(31) NOT NULL,
 33+ us_mime varchar(255),
 34+ us_media_type varchar(255),
 35+ -- image-specific properties
 36+ us_image_width smallint unsigned,
 37+ us_image_height smallint unsigned,
 38+ us_image_bits smallint unsigned
 39+
 40+) /*$wgDBTableOptions*/;
 41+
 42+-- sometimes there's a delete for all of a user's stuff.
 43+CREATE INDEX /*i*/us_user ON /*_*/uploadstash (us_user);
 44+-- pick out files by key, enforce key uniqueness
 45+CREATE UNIQUE INDEX /*i*/us_key ON /*_*/uploadstash (us_key);
Property changes on: trunk/phase3/maintenance/archives/patch-uploadstash.sql
___________________________________________________________________
Added: svn:eol-style
146 + native
Index: trunk/phase3/maintenance/tables.sql
@@ -939,6 +939,52 @@
940940
941941
942942 --
 943+-- Store information about newly uploaded files before they're
 944+-- moved into the actual filestore
 945+--
 946+CREATE TABLE /*_*/uploadstash (
 947+ us_id int unsigned NOT NULL PRIMARY KEY auto_increment,
 948+
 949+ -- the user who uploaded the file.
 950+ us_user int unsigned NOT NULL,
 951+
 952+ -- file key. this is how applications actually search for the file.
 953+ -- this might go away, or become the primary key.
 954+ us_key varchar(255) NOT NULL,
 955+
 956+ -- the original path
 957+ us_orig_path varchar(255) NOT NULL,
 958+
 959+ -- the temporary path at which the file is actually stored
 960+ us_path varchar(255) NOT NULL,
 961+
 962+ -- which type of upload the file came from (sometimes)
 963+ us_source_type varchar(50),
 964+
 965+ -- the date/time on which the file was added
 966+ us_timestamp varchar(14) not null,
 967+
 968+ -- file properties from File::getPropsFromPath. these may prove unnecessary.
 969+ --
 970+ us_size int unsigned NOT NULL,
 971+ -- this hash comes from File::sha1Base36(), and is 31 characters
 972+ us_sha1 varchar(31) NOT NULL,
 973+ us_mime varchar(255),
 974+ us_media_type varchar(255),
 975+ -- image-specific properties
 976+ us_image_width smallint unsigned,
 977+ us_image_height smallint unsigned,
 978+ us_image_bits smallint unsigned
 979+
 980+) /*$wgDBTableOptions*/;
 981+
 982+-- sometimes there's a delete for all of a user's stuff.
 983+CREATE INDEX /*i*/us_user ON /*_*/uploadstash (us_user);
 984+-- pick out files by key, enforce key uniqueness
 985+CREATE UNIQUE INDEX /*i*/us_key ON /*_*/uploadstash (us_key);
 986+
 987+
 988+--
943989 -- Primarily a summary table for Special:Recentchanges,
944990 -- this table contains some additional info on edits from
945991 -- the last few days, see Article::editUpdates()
Index: trunk/phase3/tests/phpunit/includes/upload/UploadStashTest.php
@@ -1,20 +1,38 @@
22 <?php
33
44 class UploadStashTest extends MediaWikiTestCase {
 5+ /**
 6+ * @var Array of UploadStashTestUser
 7+ */
 8+ public static $users;
 9+
510 public function setUp() {
611 parent::setUp();
712
8 - // Setup a fake session if necessary
9 - if ( !isset( $_SESSION ) ) {
10 - $GLOBALS['_SESSION'] = array();
11 - }
12 -
1313 // Setup a file for bug 29408
1414 $this->bug29408File = dirname( __FILE__ ) . '/bug29408';
1515 file_put_contents( $this->bug29408File, "\x00" );
 16+
 17+ self::$users = array(
 18+ 'sysop' => new ApiTestUser(
 19+ 'Uploadstashtestsysop',
 20+ 'Upload Stash Test Sysop',
 21+ 'upload_stash_test_sysop@sample.com',
 22+ array( 'sysop' )
 23+ ),
 24+ 'uploader' => new ApiTestUser(
 25+ 'Uploadstashtestuser',
 26+ 'Upload Stash Test User',
 27+ 'upload_stash_test_user@sample.com',
 28+ array()
 29+ )
 30+ );
1631 }
1732
1833 public function testBug29408() {
 34+ global $wgUser;
 35+ $wgUser = self::$users['uploader']->user;
 36+
1937 $repo = RepoGroup::singleton()->getLocalRepo();
2038 $stash = new UploadStash( $repo );
2139
@@ -23,7 +41,7 @@
2442 // We'll never reach this point if we hit bug 29408
2543 $this->assertTrue( true, 'Unrecognized file without extension' );
2644
27 - $file->remove();
 45+ $stash->removeFile( $file->getFileKey() );
2846 }
2947
3048 public function tearDown() {
Index: trunk/phase3/tests/phpunit/includes/api/ApiUploadTest.php
@@ -88,7 +88,7 @@
8989 ), $session );
9090 } catch ( UsageException $e ) {
9191 $exception = true;
92 - $this->assertEquals( "One of the parameters sessionkey, file, url, statuskey is required",
 92+ $this->assertEquals( "One of the parameters filekey, file, url, statuskey is required",
9393 $e->getMessage() );
9494 }
9595 $this->assertTrue( $exception, "Got exception" );
@@ -398,8 +398,9 @@
399399 $this->assertEquals( 'Success', $result['upload']['result'] );
400400 $this->assertEquals( $fileSize, ( int )$result['upload']['imageinfo']['size'] );
401401 $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] );
402 - $this->assertTrue( isset( $result['upload']['sessionkey'] ) );
403 - $sessionkey = $result['upload']['sessionkey'];
 402+ $this->assertTrue( isset( $result['upload']['filekey'] ) );
 403+ $this->assertEquals( $result['upload']['sessionkey'], $result['upload']['filekey'] );
 404+ $filekey = $result['upload']['filekey'];
404405
405406 // it should be visible from Special:UploadStash
406407 // XXX ...but how to test this, with a fake WebRequest with the session?
@@ -407,12 +408,11 @@
408409 // now we should try to release the file from stash
409410 $params = array(
410411 'action' => 'upload',
411 - 'sessionkey' => $sessionkey,
 412+ 'filekey' => $filekey,
412413 'filename' => $fileName,
413414 'comment' => 'dummy comment',
414415 'text' => "This is the page text for $fileName, altered",
415416 );
416 - $session[ UploadBase::getSessionKeyname() ] = $_SESSION[ UploadBase::getSessionKeyname() ];
417417
418418 $this->clearFakeUploads();
419419 $exception = false;
Index: trunk/phase3/includes/upload/UploadFromStash.php
@@ -8,17 +8,30 @@
99 */
1010
1111 class UploadFromStash extends UploadBase {
 12+ protected $mFileKey, $mVirtualTempPath, $mFileProps, $mSourceType;
 13+
 14+ // an instance of UploadStash
 15+ private $stash;
 16+
 17+ //LocalFile repo
 18+ private $repo;
 19+
 20+ public function __construct( $stash = false, $repo = false ) {
 21+ if( !$this->repo ) {
 22+ $this->repo = RepoGroup::singleton()->getLocalRepo();
 23+ }
1224
13 - protected $initializePathInfo, $mSessionKey, $mVirtualTempPath,
14 - $mFileProps, $mSourceType;
15 -
16 - public static function isValidSessionKey( $key, $sessionData ) {
17 - return !empty( $key ) &&
18 - is_array( $sessionData ) &&
19 - isset( $sessionData[$key] ) &&
20 - isset( $sessionData[$key]['version'] ) &&
21 - $sessionData[$key]['version'] == UploadBase::SESSION_VERSION;
 25+ if( !$this->stash ) {
 26+ $this->stash = new UploadStash( $this->repo );
 27+ }
 28+
 29+ return true;
2230 }
 31+
 32+ public static function isValidKey( $key ) {
 33+ // this is checked in more detail in UploadStash
 34+ return preg_match( UploadStash::KEY_FORMAT_REGEX, $key );
 35+ }
2336
2437 /**
2538 * @param $request WebRequest
@@ -26,45 +39,40 @@
2740 * @return Boolean
2841 */
2942 public static function isValidRequest( $request ) {
30 - $sessionData = $request->getSessionData( UploadBase::SESSION_KEYNAME );
31 - return self::isValidSessionKey(
32 - $request->getText( 'wpSessionKey' ),
33 - $sessionData
34 - );
 43+ return self::isValidKey( $request->getText( 'wpFileKey' ) || $request->getText( 'wpSessionKey' ) );
3544 }
3645
37 - public function initialize( $name, $sessionKey, $sessionData ) {
 46+ public function initialize( $key, $name = 'upload_file' ) {
3847 /**
3948 * Confirming a temporarily stashed upload.
4049 * We don't want path names to be forged, so we keep
4150 * them in the session on the server and just give
4251 * an opaque key to the user agent.
43 - */
44 -
 52+ */
 53+ $metadata = $this->stash->getMetadata( $key );
4554 $this->initializePathInfo( $name,
46 - $this->getRealPath ( $sessionData['mTempPath'] ),
47 - $sessionData['mFileSize'],
 55+ $this->getRealPath ( $metadata['us_path'] ),
 56+ $metadata['us_size'],
4857 false
4958 );
5059
51 - $this->mSessionKey = $sessionKey;
52 - $this->mVirtualTempPath = $sessionData['mTempPath'];
53 - $this->mFileProps = $sessionData['mFileProps'];
54 - $this->mSourceType = isset( $sessionData['mSourceType'] ) ?
55 - $sessionData['mSourceType'] : null;
 60+ $this->mFileKey = $key;
 61+ $this->mVirtualTempPath = $metadata['us_path'];
 62+ $this->mFileProps = $this->stash->getFileProps( $key );
 63+ $this->mSourceType = $metadata['us_source_type'];
5664 }
5765
5866 /**
5967 * @param $request WebRequest
6068 */
6169 public function initializeFromRequest( &$request ) {
62 - $sessionKey = $request->getText( 'wpSessionKey' );
63 - $sessionData = $request->getSessionData( UploadBase::SESSION_KEYNAME );
 70+ $fileKey = $request->getText( 'wpFileKey' ) || $request->getText( 'wpSessionKey' );
6471
6572 $desiredDestName = $request->getText( 'wpDestFile' );
66 - if( !$desiredDestName )
67 - $desiredDestName = $request->getText( 'wpUploadFile' );
68 - return $this->initialize( $desiredDestName, $sessionKey, $sessionData[$sessionKey] );
 73+ if( !$desiredDestName ) {
 74+ $desiredDestName = $request->getText( 'wpUploadFile' ) || $request->getText( 'filename' );
 75+ }
 76+ return $this->initialize( $fileKey, $desiredDestName );
6977 }
7078
7179 public function getSourceType() {
@@ -83,21 +91,26 @@
8492 /**
8593 * There is no need to stash the image twice
8694 */
87 - public function stashSession( $key = null ) {
88 - if ( !empty( $this->mSessionKey ) ) {
89 - return $this->mSessionKey;
 95+ public function stashFile( $key = null ) {
 96+ if ( !empty( $this->mFileKey ) ) {
 97+ return $this->mFileKey;
9098 }
91 - return parent::stashSession();
 99+ return parent::stashFileGetKey();
92100 }
93101
94102 /**
 103+ * Alias for stashFile
 104+ */
 105+ public function stashSession( $key = null ) {
 106+ return $this->stashFile( $key );
 107+ }
 108+
 109+ /**
95110 * Remove a temporarily kept file stashed by saveTempUploadedFile().
96111 * @return success
97112 */
98113 public function unsaveUploadedFile() {
99 - $repo = RepoGroup::singleton()->getLocalRepo();
100 - $success = $repo->freeTemp( $this->mVirtualTempPath );
101 - return $success;
 114+ return $stash->removeFile( $this->mFileKey );
102115 }
103116
104117 }
\ No newline at end of file
Index: trunk/phase3/includes/upload/UploadBase.php
@@ -38,13 +38,6 @@
3939 const FILE_TOO_LARGE = 12;
4040 const WINDOWS_NONASCII_FILENAME = 13;
4141
42 - const SESSION_VERSION = 2;
43 - const SESSION_KEYNAME = 'wsUploadData';
44 -
45 - public static function getSessionKeyname() {
46 - return self::SESSION_KEYNAME;
47 - }
48 -
4942 public function getVerificationErrorCode( $error ) {
5043 $code_to_status = array(self::EMPTY_FILE => 'empty-file',
5144 self::FILE_TOO_LARGE => 'file-too-large',
@@ -745,32 +738,40 @@
746739 * by design) then we may want to stash the file temporarily, get more information, and publish the file later.
747740 *
748741 * This method will stash a file in a temporary directory for later processing, and save the necessary descriptive info
749 - * into the user's session.
750 - * This method returns the file object, which also has a 'sessionKey' property which can be passed through a form or
 742+ * into the database.
 743+ * This method returns the file object, which also has a 'fileKey' property which can be passed through a form or
751744 * API request to find this stashed file again.
752745 *
753 - * @param $key String: (optional) the session key used to find the file info again. If not supplied, a key will be autogenerated.
 746+ * @param $key String: (optional) the file key used to find the file info again. If not supplied, a key will be autogenerated.
754747 * @return UploadStashFile stashed file
755748 */
756 - public function stashSessionFile( $key = null ) {
 749+ public function stashFile( $key = null ) {
 750+ // was stashSessionFile
757751 $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
758 - $data = array(
759 - 'mFileProps' => $this->mFileProps,
760 - 'mSourceType' => $this->getSourceType(),
761 - );
762 - $file = $stash->stashFile( $this->mTempPath, $data, $key );
 752+
 753+ $file = $stash->stashFile( $this->mTempPath, $this->getSourceType(), $key );
763754 $this->mLocalFile = $file;
764755 return $file;
765756 }
766757
767758 /**
768 - * Stash a file in a temporary directory, returning a key which can be used to find the file again. See stashSessionFile().
 759+ * Stash a file in a temporary directory, returning a key which can be used to find the file again. See stashFile().
769760 *
770 - * @param $key String: (optional) the session key used to find the file info again. If not supplied, a key will be autogenerated.
771 - * @return String: session key
 761+ * @param $key String: (optional) the file key used to find the file info again. If not supplied, a key will be autogenerated.
 762+ * @return String: file key
772763 */
 764+ public function stashFileGetKey( $key = null ) {
 765+ return $this->stashFile( $key )->getFileKey();
 766+ }
 767+
 768+ /**
 769+ * alias for stashFileGetKey, for backwards compatibility
 770+ *
 771+ * @param $key String: (optional) the file key used to find the file info again. If not supplied, a key will be autogenerated.
 772+ * @return String: file key
 773+ */
773774 public function stashSession( $key = null ) {
774 - return $this->stashSessionFile( $key )->getSessionKey();
 775+ return $this->stashFileGetKey( $key );
775776 }
776777
777778 /**
Index: trunk/phase3/includes/upload/UploadStash.php
@@ -5,16 +5,23 @@
66 * - Several parts of MediaWiki do this in similar ways: UploadBase, UploadWizard, and FirefoggChunkedExtension
77 * And there are several that reimplement stashing from scratch, in idiosyncratic ways. The idea is to unify them all here.
88 * Mostly all of them are the same except for storing some custom fields, which we subsume into the data array.
9 - * - enable applications to find said files later, as long as the session or temp files haven't been purged.
 9+ * - enable applications to find said files later, as long as the db table or temp files haven't been purged.
1010 * - enable the uploading user (and *ONLY* the uploading user) to access said files, and thumbnails of said files, via a URL.
11 - * We accomplish this by making the session serve as a URL->file mapping, on the assumption that nobody else can access
12 - * the session, even the uploading user. See SpecialUploadStash, which implements a web interface to some files stored this way.
 11+ * We accomplish this using a database table, with ownership checking as you might expect. See SpecialUploadStash, which
 12+ * implements a web interface to some files stored this way.
1313 *
 14+ * UploadStash represents the entire stash of temporary files.
 15+ * UploadStashFile is a filestore for the actual physical disk files.
 16+ * UploadFromStash extends UploadBase, and represents a single stashed file as it is moved from the stash to the regular file repository
1417 */
1518 class UploadStash {
1619
1720 // Format of the key for files -- has to be suitable as a filename itself (e.g. ab12cd34ef.jpg)
1821 const KEY_FORMAT_REGEX = '/^[\w-]+\.\w*$/';
 22+
 23+ // When a given stashed file can't be loaded, wait for the slaves to catch up. If they're more than MAX_LAG
 24+ // behind, throw an exception instead. (at what point is broken better than slow?)
 25+ const MAX_LAG = 30;
1926
2027 /**
2128 * repository that this uses to store temp files
@@ -24,92 +31,133 @@
2532 */
2633 public $repo;
2734
28 - // array of initialized objects obtained from session (lazily initialized upon getFile())
29 - private $files = array();
 35+ // array of initialized repo objects
 36+ protected $files = array();
 37+
 38+ // cache of the file metadata that's stored in the database
 39+ protected $fileMetadata = array();
 40+
 41+ // fileprops cache
 42+ protected $fileProps = array();
3043
31 - // TODO: Once UploadBase starts using this, switch to use these constants rather than UploadBase::SESSION*
32 - // const SESSION_VERSION = 2;
33 - // const SESSION_KEYNAME = 'wsUploadData';
34 -
3544 /**
36 - * Represents the session which contains temporarily stored files.
 45+ * Represents a temporary filestore, with metadata in the database.
3746 * Designed to be compatible with the session stashing code in UploadBase (should replace it eventually)
3847 *
3948 * @param $repo FileRepo
4049 */
4150 public function __construct( $repo ) {
42 -
4351 // this might change based on wiki's configuration.
4452 $this->repo = $repo;
45 -
46 - if ( ! isset( $_SESSION ) ) {
47 - throw new UploadStashNotAvailableException( 'no session variable' );
48 - }
49 -
50 - if ( !isset( $_SESSION[UploadBase::SESSION_KEYNAME] ) ) {
51 - $_SESSION[UploadBase::SESSION_KEYNAME] = array();
52 - }
53 -
5453 }
5554
5655 /**
5756 * Get a file and its metadata from the stash.
58 - * May throw exception if session data cannot be parsed due to schema change, or key not found.
5957 *
60 - * @param $key Integer: key
 58+ * @param $key String: key under which file information is stored
6159 * @throws UploadStashFileNotFoundException
62 - * @throws UploadStashBadVersionException
 60+ * @throws UploadStashNotLoggedInException
 61+ * @throws UploadStashWrongOwnerException
 62+ * @throws UploadStashBadPathException
6363 * @return UploadStashFile
6464 */
6565 public function getFile( $key ) {
 66+ global $wgUser;
 67+
6668 if ( ! preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
6769 throw new UploadStashBadPathException( "key '$key' is not in a proper format" );
6870 }
6971
70 - if ( !isset( $this->files[$key] ) ) {
71 - if ( !isset( $_SESSION[UploadBase::SESSION_KEYNAME][$key] ) ) {
 72+ $userId = $wgUser->getId();
 73+ if( !$userId ) {
 74+ throw new UploadStashNotLoggedInException( 'No user is logged in, files must belong to users' );
 75+ }
 76+
 77+ if ( !isset( $this->fileMetadata[$key] ) ) {
 78+ // try this first. if it fails to find the row, check for lag, wait, try again. if its still missing, throw an exception.
 79+ // this more complex solution keeps things moving for page loads with many requests
 80+ // (ie. validating image ownership) when replag is high
 81+ if( !$this->fetchFileMetadata($key) ) {
 82+ $lag = $dbr->getLag();
 83+ if( $lag > 0 && $lag <= self::MAX_LAG ) {
 84+ // if there's not too much replication lag, just wait for the slave to catch up to our last insert.
 85+ sleep( ceil( $lag ) );
 86+ } elseif($lag > self::MAX_LAG ) {
 87+ // that's a lot of lag to introduce into the middle of the UI.
 88+ throw new UploadStashMaxLagExceededException(
 89+ 'Couldn\'t load stashed file metadata, and replication lag is above threshold: (MAX_LAG=' . self::MAX_LAG . ')'
 90+ );
 91+ }
 92+
 93+ // now that the waiting has happened, try again
 94+ $this->fetchFileMetadata($key);
 95+ }
 96+
 97+ if ( !isset( $this->fileMetadata[$key] ) ) {
7298 throw new UploadStashFileNotFoundException( "key '$key' not found in stash" );
7399 }
74100
75 - $data = $_SESSION[UploadBase::SESSION_KEYNAME][$key];
76 - // guards against PHP class changing while session data doesn't
77 - if ($data['version'] !== UploadBase::SESSION_VERSION ) {
78 - throw new UploadStashBadVersionException( $data['version'] . " does not match current version " . UploadBase::SESSION_VERSION );
79 - }
 101+ // create $this->files[$key]
 102+ $this->initFile( $key );
80103
81 - // separate the stashData into the path, and then the rest of the data
82 - $path = $data['mTempPath'];
83 - unset( $data['mTempPath'] );
84 -
85 - $file = new UploadStashFile( $this, $this->repo, $path, $key, $data );
86 - if ( $file->getSize() === 0 ) {
87 - throw new UploadStashZeroLengthFileException( "File is zero length" );
 104+ // fetch fileprops
 105+ $path = $this->fileMetadata[$key]['us_path'];
 106+ if ( $this->repo->isVirtualUrl( $path ) ) {
 107+ $path = $this->repo->resolveVirtualUrl( $path );
88108 }
89 - $this->files[$key] = $file;
 109+ $this->fileProps[$key] = File::getPropsFromPath( $path );
 110+ }
 111+
 112+ if( $this->fileMetadata[$key]['us_user'] != $userId ) {
 113+ throw new UploadStashWrongOwnerException( "This file ($key) doesn't belong to the current user." );
 114+ }
90115
91 - }
92116 return $this->files[$key];
93117 }
94118
95119 /**
96 - * Stash a file in a temp directory and record that we did this in the session, along with other metadata.
97 - * We store data in a flat key-val namespace because that's how UploadBase did it. This also means we have to
98 - * ensure that the key-val pairs in $data do not overwrite other required fields.
 120+ * Getter for file metadata.
99121 *
 122+ * @param key String: key under which file information is stored
 123+ * @return Array
 124+ */
 125+ public function getMetadata ( $key ) {
 126+ $this->getFile( $key );
 127+ return $this->fileMetadata[$key];
 128+ }
 129+
 130+ /**
 131+ * Getter for fileProps
 132+ *
 133+ * @param key String: key under which file information is stored
 134+ * @return Array
 135+ */
 136+ public function getFileProps ( $key ) {
 137+ $this->getFile( $key );
 138+ return $this->fileProps[$key];
 139+ }
 140+
 141+ /**
 142+ * Stash a file in a temp directory and record that we did this in the database, along with other metadata.
 143+ *
100144 * @param $path String: path to file you want stashed
101 - * @param $data Array: optional, other data you want associated with the file. Do not use 'mTempPath', 'mFileProps', 'mFileSize', or 'version' as keys here
102 - * @param $key String: optional, unique key for this file in this session. Used for directory hashing when storing, otherwise not important
 145+ * @param $sourceType String: the type of upload that generated this file (currently, I believe, 'file' or null)
 146+ * @param $key String: optional, unique key for this file. Used for directory hashing when storing, otherwise not important
103147 * @throws UploadStashBadPathException
104148 * @throws UploadStashFileException
 149+ * @throws UploadStashNotLoggedInException
105150 * @return UploadStashFile: file, or null on failure
106151 */
107 - public function stashFile( $path, $data = array(), $key = null ) {
 152+ public function stashFile( $path, $sourceType = null, $key = null ) {
 153+ global $wgUser;
108154 if ( ! file_exists( $path ) ) {
109 - wfDebug( "UploadStash: tried to stash file at '$path', but it doesn't exist\n" );
 155+ wfDebug( __METHOD__ . " tried to stash file at '$path', but it doesn't exist\n" );
110156 throw new UploadStashBadPathException( "path doesn't exist" );
111157 }
112158 $fileProps = File::getPropsFromPath( $path );
113159
 160+ wfDebug( __METHOD__ . " stashing file at '$path'\n" );
 161+
114162 // we will be initializing from some tmpnam files that don't have extensions.
115163 // most of MediaWiki assumes all uploaded files have good extensions. So, we fix this.
116164 $extension = self::getExtensionForPath( $path );
@@ -127,23 +175,27 @@
128176 $key = $fileProps['sha1'] . "." . $extension;
129177 }
130178
 179+ $this->fileProps[$key] = $fileProps;
 180+
131181 if ( ! preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
132182 throw new UploadStashBadPathException( "key '$key' is not in a proper format" );
133183 }
 184+
 185+ wfDebug( __METHOD__ . " key for '$path': $key\n" );
134186
135187 // if not already in a temporary area, put it there
136 - $status = $this->repo->storeTemp( basename( $path ), $path );
 188+ $storeResult = $this->repo->storeTemp( basename( $path ), $path );
137189
138 - if( ! $status->isOK() ) {
 190+ if( ! $storeResult->isOK() ) {
139191 // It is a convention in MediaWiki to only return one error per API exception, even if multiple errors
140192 // are available. We use reset() to pick the "first" thing that was wrong, preferring errors to warnings.
141 - // This is a bit lame, as we may have more info in the $status and we're throwing it away, but to fix it means
 193+ // This is a bit lame, as we may have more info in the $storeResult and we're throwing it away, but to fix it means
142194 // redesigning API errors significantly.
143 - // $status->value just contains the virtual URL (if anything) which is probably useless to the caller
144 - $error = $status->getErrorsArray();
 195+ // $storeResult->value just contains the virtual URL (if anything) which is probably useless to the caller
 196+ $error = $storeResult->getErrorsArray();
145197 $error = reset( $error );
146198 if ( ! count( $error ) ) {
147 - $error = $status->getWarningsArray();
 199+ $error = $storeResult->getWarningsArray();
148200 $error = reset( $error );
149201 if ( ! count( $error ) ) {
150202 $error = array( 'unknown', 'no error recorded' );
@@ -151,52 +203,158 @@
152204 }
153205 throw new UploadStashFileException( "error storing file in '$path': " . implode( '; ', $error ) );
154206 }
155 - $stashPath = $status->value;
 207+ $stashPath = $storeResult->value;
 208+
 209+ // fetch the current user ID
 210+ $userId = $wgUser->getId();
 211+ if( !$userId ) {
 212+ throw new UploadStashNotLoggedInException( "No user is logged in, files must belong to users" );
 213+ }
156214
157 - // required info we always store. Must trump any other application info in $data
158 - // 'mTempPath', 'mFileSize', and 'mFileProps' are arbitrary names
159 - // chosen for compatibility with UploadBase's way of doing this.
160 - $requiredData = array(
161 - 'mTempPath' => $stashPath,
162 - 'mFileSize' => $fileProps['size'],
163 - 'mFileProps' => $fileProps,
164 - 'version' => UploadBase::SESSION_VERSION
 215+ $this->fileMetadata[$key] = array(
 216+ 'us_user' => $userId,
 217+ 'us_key' => $key,
 218+ 'us_orig_path' => $path,
 219+ 'us_path' => $stashPath,
 220+ 'us_size' => $fileProps['size'],
 221+ 'us_sha1' => $fileProps['sha1'],
 222+ 'us_mime' => $fileProps['mime'],
 223+ 'us_media_type' => $fileProps['media_type'],
 224+ 'us_image_width' => $fileProps['width'],
 225+ 'us_image_height' => $fileProps['height'],
 226+ 'us_image_bits' => $fileProps['bits'],
 227+ 'us_source_type' => $sourceType,
 228+ 'us_timestamp' => wfTimestamp( TS_MW )
165229 );
 230+
 231+ // insert the file metadata into the db.
 232+ wfDebug( __METHOD__ . " inserting $stashPath under $key\n" );
 233+ $dbw = wfGetDB( DB_MASTER );
 234+ $dbw->insert(
 235+ 'uploadstash',
 236+ $this->fileMetadata[$key],
 237+ __METHOD__
 238+ );
166239
167 - // now, merge required info and extra data into the session. (The extra data changes from application to application.
168 - // UploadWizard wants different things than say FirefoggChunkedUpload.)
169 - wfDebug( __METHOD__ . " storing under $key\n" );
170 - $_SESSION[UploadBase::SESSION_KEYNAME][$key] = array_merge( $data, $requiredData );
 240+ // store the insertid in the class variable so immediate retrieval (possibly laggy) isn't necesary.
 241+ $this->fileMetadata[$key]['us_id'] = $dbw->insertId();
171242
 243+ # create the UploadStashFile object for this file.
 244+ $this->initFile( $key );
 245+
172246 return $this->getFile( $key );
173247 }
174248
175249 /**
176250 * Remove all files from the stash.
177251 * Does not clean up files in the repo, just the record of them.
 252+ *
 253+ * @throws UploadStashNotLoggedInException
178254 * @return boolean: success
179255 */
180256 public function clear() {
181 - $_SESSION[UploadBase::SESSION_KEYNAME] = array();
 257+ global $wgUser;
 258+
 259+ $userId = $wgUser->getId();
 260+ if( !$userId ) {
 261+ throw new UploadStashNotLoggedInException( 'No user is logged in, files must belong to users' );
 262+ }
 263+
 264+ wfDebug( __METHOD__ . " clearing all rows for user $userId\n" );
 265+ $dbw = wfGetDB( DB_MASTER );
 266+ $dbw->delete(
 267+ 'uploadstash',
 268+ array( 'us_user' => $userId ),
 269+ __METHOD__
 270+ );
 271+
 272+ # destroy objects.
 273+ $this->files = array();
 274+ $this->fileMetadata = array();
 275+
182276 return true;
183277 }
184278
185279
186280 /**
187 - * Remove a particular file from the stash.
188 - * Does not clean up file in the repo, just the record of it.
 281+ * Remove a particular file from the stash. Also removes it from the repo.
 282+ *
 283+ * @throws UploadStashNotLoggedInException
189284 * @return boolean: success
190285 */
191286 public function removeFile( $key ) {
192 - unset ( $_SESSION[UploadBase::SESSION_KEYNAME][$key] );
 287+ global $wgUser;
 288+
 289+ $userId = $wgUser->getId();
 290+ if( !$userId ) {
 291+ throw new UploadStashNotLoggedInException( 'No user is logged in, files must belong to users' );
 292+ }
 293+
 294+ wfDebug( __METHOD__ . " clearing row $key for user $userId\n" );
 295+ $dbw = wfGetDB( DB_MASTER );
 296+
 297+ // this is a cheap query. it runs on the master so that this function still works when there's lag.
 298+ // it won't be called all that often.
 299+ $row = $dbw->selectRow(
 300+ 'uploadstash',
 301+ 'us_user',
 302+ array('us_key' => $key),
 303+ __METHOD__
 304+ );
 305+
 306+ if( $row->us_user != $userId ) {
 307+ throw new UploadStashWrongOwnerException( "Can't delete: the file ($key) doesn't belong to this user." );
 308+ }
 309+
 310+ $dbw->delete(
 311+ 'uploadstash',
 312+ array( 'us_key' => $key, 'us_user' => $userId ),
 313+ __METHOD__
 314+ );
 315+
 316+ // TODO: look into UnregisteredLocalFile and find out why the rv here is sometimes wrong (false when file was removed)
 317+ // for now, ignore.
 318+ $this->files[$key]->remove();
 319+
 320+ unset( $this->files[$key] );
 321+ unset( $this->fileMetadata[$key] );
 322+
193323 return true;
194324 }
195325
196326 /**
197327 * List all files in the stash.
 328+ *
 329+ * @throws UploadStashNotLoggedInException
 330+ * @return Array
198331 */
199332 public function listFiles() {
200 - return array_keys( $_SESSION[UploadBase::SESSION_KEYNAME] );
 333+ global $wgUser;
 334+
 335+ $userId = $wgUser->getId();
 336+ if( !$userId ) {
 337+ throw new UploadStashNotLoggedInException( 'No user is logged in, files must belong to users' );
 338+ }
 339+
 340+ $dbw = wfGetDB( DB_SLAVE );
 341+ $res = $dbr->select(
 342+ 'uploadstash',
 343+ 'us_key',
 344+ array('us_key' => $key),
 345+ __METHOD__
 346+ );
 347+
 348+ if( !is_object( $res ) ) {
 349+ // nothing there.
 350+ return false;
 351+ }
 352+
 353+ $keys = array();
 354+ while( $row = $dbr->fetchRow( $res ) ) {
 355+ array_push( $keys, $row['us_key'] );
 356+ }
 357+
 358+ return $keys;
201359 }
202360
203361 /**
@@ -229,12 +387,65 @@
230388 return File::normalizeExtension( $extension );
231389 }
232390
 391+ /**
 392+ * Helper function: do the actual database query to fetch file metadata.
 393+ *
 394+ * @param $key String: key
 395+ * @return boolean
 396+ */
 397+ protected function fetchFileMetadata( $key ) {
 398+ // populate $fileMetadata[$key]
 399+ $dbr = wfGetDB( DB_SLAVE );
 400+ $row = $dbr->selectRow(
 401+ 'uploadstash',
 402+ '*',
 403+ array('us_key' => $key),
 404+ __METHOD__
 405+ );
 406+
 407+ if( !is_object( $row ) ) {
 408+ // key wasn't present in the database. this will happen sometimes.
 409+ return false;
 410+ }
 411+
 412+ $this->fileMetadata[$key] = array(
 413+ 'us_user' => $row->us_user,
 414+ 'us_key' => $row->us_key,
 415+ 'us_orig_path' => $row->us_orig_path,
 416+ 'us_path' => $row->us_path,
 417+ 'us_size' => $row->us_size,
 418+ 'us_sha1' => $row->us_sha1,
 419+ 'us_mime' => $row->us_mime,
 420+ 'us_media_type' => $row->us_media_type,
 421+ 'us_image_width' => $row->us_image_width,
 422+ 'us_image_height' => $row->us_image_height,
 423+ 'us_image_bits' => $row->us_image_bits,
 424+ 'us_source_type' => $row->us_source_type
 425+ );
 426+
 427+ return true;
 428+ }
 429+
 430+ /**
 431+ * Helper function: Initialize the UploadStashFile for a given file.
 432+ *
 433+ * @param $path String: path to file
 434+ * @param $key String: key under which to store the object
 435+ * @throws UploadStashZeroLengthFileException
 436+ * @return bool
 437+ */
 438+ protected function initFile( $key ) {
 439+ $file = new UploadStashFile( $this->repo, $this->fileMetadata[$key]['us_path'], $key );
 440+ if ( $file->getSize() === 0 ) {
 441+ throw new UploadStashZeroLengthFileException( "File is zero length" );
 442+ }
 443+ $this->files[$key] = $file;
 444+ return true;
 445+ }
233446 }
234447
235448 class UploadStashFile extends UnregisteredLocalFile {
236 - private $sessionStash;
237 - private $sessionKey;
238 - private $sessionData;
 449+ private $fileKey;
239450 private $urlName;
240451 protected $url;
241452
@@ -242,18 +453,14 @@
243454 * A LocalFile wrapper around a file that has been temporarily stashed, so we can do things like create thumbnails for it
244455 * Arguably UnregisteredLocalFile should be handling its own file repo but that class is a bit retarded currently
245456 *
246 - * @param $stash UploadStash: useful for obtaining config, stashing transformed files
247457 * @param $repo FSRepo: repository where we should find the path
248458 * @param $path String: path to file
249459 * @param $key String: key to store the path and any stashed data under
250 - * @param $data String: any other data we want stored with this file
251460 * @throws UploadStashBadPathException
252461 * @throws UploadStashFileNotFoundException
253462 */
254 - public function __construct( $stash, $repo, $path, $key, $data ) {
255 - $this->sessionStash = $stash;
256 - $this->sessionKey = $key;
257 - $this->sessionData = $data;
 463+ public function __construct( $repo, $path, $key ) {
 464+ $this->fileKey = $key;
258465
259466 // resolve mwrepo:// urls
260467 if ( $repo->isVirtualUrl( $path ) ) {
@@ -333,7 +540,7 @@
334541 * Get a URL to access the thumbnail
335542 * This is required because the model of how files work requires that
336543 * the thumbnail urls be predictable. However, in our model the URL is not based on the filename
337 - * (that's hidden in the session)
 544+ * (that's hidden in the db)
338545 *
339546 * @param $thumbName String: basename of thumbnail file -- however, we don't want to use the file exactly
340547 * @return String: URL to access thumbnail, or URL with partial path
@@ -351,7 +558,7 @@
352559 */
353560 public function getUrlName() {
354561 if ( ! $this->urlName ) {
355 - $this->urlName = $this->sessionKey;
 562+ $this->urlName = $this->fileKey;
356563 }
357564 return $this->urlName;
358565 }
@@ -380,12 +587,12 @@
381588 }
382589
383590 /**
384 - * Getter for session key (the session-unique id by which this file's location & metadata is stored in the session)
 591+ * Getter for file key (the unique id by which this file's location & metadata is stored in the db)
385592 *
386 - * @return String: session key
 593+ * @return String: file key
387594 */
388 - public function getSessionKey() {
389 - return $this->sessionKey;
 595+ public function getFileKey() {
 596+ return $this->fileKey;
390597 }
391598
392599 /**
@@ -393,6 +600,11 @@
394601 * @return Status: success
395602 */
396603 public function remove() {
 604+ if( !$this->repo->fileExists( $this->path, FileRepo::FILES_ONLY ) ) {
 605+ // Maybe the file's already been removed? This could totally happen in UploadBase.
 606+ return true;
 607+ }
 608+
397609 return $this->repo->freeTemp( $this->path );
398610 }
399611
@@ -401,7 +613,8 @@
402614 class UploadStashNotAvailableException extends MWException {};
403615 class UploadStashFileNotFoundException extends MWException {};
404616 class UploadStashBadPathException extends MWException {};
405 -class UploadStashBadVersionException extends MWException {};
406617 class UploadStashFileException extends MWException {};
407618 class UploadStashZeroLengthFileException extends MWException {};
408 -
 619+class UploadStashNotLoggedInException extends MWException {};
 620+class UploadStashWrongOwnerException extends MWException {};
 621+class UploadStashMaxLagExceededException extends MWException {};
Index: trunk/phase3/includes/installer/MysqlUpdater.php
@@ -184,6 +184,7 @@
185185
186186 // 1.19
187187 array( 'addTable', 'config', 'patch-config.sql' ),
 188+ array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
188189 );
189190 }
190191
Index: trunk/phase3/includes/installer/SqliteUpdater.php
@@ -61,6 +61,7 @@
6262
6363 // 1.19
6464 array( 'addTable', 'config', 'patch-config.sql' ),
 65+ array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
6566 );
6667 }
6768
Index: trunk/phase3/includes/api/ApiQueryStashImageInfo.php
@@ -51,7 +51,7 @@
5252 $result->addValue( array( 'query', $this->getModuleName() ), null, $imageInfo );
5353 $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), $modulePrefix );
5454 }
55 -
 55+ //TODO: update exception handling here to understand current getFile exceptions
5656 } catch ( UploadStashNotAvailableException $e ) {
5757 $this->dieUsage( "Session not available: " . $e->getMessage(), "nosession" );
5858 } catch ( UploadStashFileNotFoundException $e ) {
Index: trunk/phase3/includes/api/ApiUpload.php
@@ -58,6 +58,11 @@
5959 $request = $this->getMain()->getRequest();
6060 // Add the uploaded file to the params array
6161 $this->mParams['file'] = $request->getFileName( 'file' );
 62+
 63+ // Copy the session key to the file key, for backward compatibility.
 64+ if( !$this->mParams['filekey'] && $this->mParams['sessionkey'] ) {
 65+ $this->mParams['filekey'] = $this->mParams['sessionkey'];
 66+ }
6267
6368 // Select an upload module
6469 if ( !$this->selectUploadModule() ) {
@@ -103,7 +108,8 @@
104109 // in case the warnings can be fixed with some further user action, let's stash this upload
105110 // and return a key they can use to restart it
106111 try {
107 - $result['sessionkey'] = $this->performStash();
 112+ $result['filekey'] = $this->performStash();
 113+ $result['sessionkey'] = $result['filekey']; // backwards compatibility
108114 } catch ( MWException $e ) {
109115 $result['warnings']['stashfailed'] = $e->getMessage();
110116 }
@@ -112,7 +118,8 @@
113119 // In this case, a failure to stash ought to be fatal
114120 try {
115121 $result['result'] = 'Success';
116 - $result['sessionkey'] = $this->performStash();
 122+ $result['filekey'] = $this->performStash();
 123+ $result['sessionkey'] = $result['filekey']; // backwards compatibility
117124 } catch ( MWException $e ) {
118125 $this->dieUsage( $e->getMessage(), 'stashfailed' );
119126 }
@@ -133,18 +140,20 @@
134141 }
135142
136143 /**
137 - * Stash the file and return the session key
 144+ * Stash the file and return the file key
138145 * Also re-raises exceptions with slightly more informative message strings (useful for API)
139146 * @throws MWException
140 - * @return String session key
 147+ * @return String file key
141148 */
142149 function performStash() {
143150 try {
144 - $sessionKey = $this->mUpload->stashSessionFile()->getSessionKey();
 151+ $fileKey = $this->mUpload->stashFile()->getFileKey();
145152 } catch ( MWException $e ) {
146 - throw new MWException( 'Stashing temporary file failed: ' . get_class( $e ) . ' ' . $e->getMessage() );
 153+ $message = 'Stashing temporary file failed: ' . get_class( $e ) . ' ' . $e->getMessage();
 154+ wfDebug( __METHOD__ . ' ' . $message . "\n");
 155+ throw new MWException( $message );
147156 }
148 - return $sessionKey;
 157+ return $fileKey;
149158 }
150159
151160 /**
@@ -158,7 +167,8 @@
159168 */
160169 function dieRecoverableError( $error, $parameter, $data = array() ) {
161170 try {
162 - $data['sessionkey'] = $this->performStash();
 171+ $data['filekey'] = $this->performStash();
 172+ $data['sessionkey'] = $data['filekey'];
163173 } catch ( MWException $e ) {
164174 $data['stashfailed'] = $e->getMessage();
165175 }
@@ -180,7 +190,7 @@
181191
182192 // One and only one of the following parameters is needed
183193 $this->requireOnlyOneParameter( $this->mParams,
184 - 'sessionkey', 'file', 'url', 'statuskey' );
 194+ 'filekey', 'file', 'url', 'statuskey' );
185195
186196 if ( $this->mParams['statuskey'] ) {
187197 $this->checkAsyncDownloadEnabled();
@@ -204,17 +214,14 @@
205215 $this->dieUsageMsg( array( 'missingparam', 'filename' ) );
206216 }
207217
208 - if ( $this->mParams['sessionkey'] ) {
 218+ if ( $this->mParams['filekey'] ) {
209219 // Upload stashed in a previous request
210 - $sessionData = $request->getSessionData( UploadBase::getSessionKeyName() );
211 - if ( !UploadFromStash::isValidSessionKey( $this->mParams['sessionkey'], $sessionData ) ) {
212 - $this->dieUsageMsg( 'invalid-session-key' );
 220+ if ( !UploadFromStash::isValidKey( $this->mParams['filekey'] ) ) {
 221+ $this->dieUsageMsg( 'invalid-file-key' );
213222 }
214223
215224 $this->mUpload = new UploadFromStash();
216 - $this->mUpload->initialize( $this->mParams['filename'],
217 - $this->mParams['sessionkey'],
218 - $sessionData[$this->mParams['sessionkey']] );
 225+ $this->mUpload->initialize( $this->mParams['filekey'], $this->mParams['filename'] );
219226
220227 } elseif ( isset( $this->mParams['file'] ) ) {
221228 $this->mUpload = new UploadFromFile();
@@ -463,7 +470,11 @@
464471 'ignorewarnings' => false,
465472 'file' => null,
466473 'url' => null,
467 - 'sessionkey' => null,
 474+ 'filekey' => null,
 475+ 'sessionkey' => array(
 476+ ApiBase::PARAM_DFLT => null,
 477+ ApiBase::PARAM_DEPRECATED => true,
 478+ ),
468479 'stash' => false,
469480
470481 'asyncdownload' => false,
@@ -485,12 +496,13 @@
486497 'ignorewarnings' => 'Ignore any warnings',
487498 'file' => 'File contents',
488499 'url' => 'Url to fetch the file from',
489 - 'sessionkey' => 'Session key that identifies a previous upload that was stashed temporarily.',
 500+ 'filekey' => 'Key that identifies a previous upload that was stashed temporarily.',
 501+ 'sessionkey' => 'Same as filekey, maintained for backward compatibility.',
490502 'stash' => 'If set, the server will not add the file to the repository and stash it temporarily.',
491503
492504 'asyncdownload' => 'Make fetching a URL asynchronous',
493505 'leavemessage' => 'If asyncdownload is used, leave a message on the user talk page if finished',
494 - 'statuskey' => 'Fetch the upload status for this session key',
 506+ 'statuskey' => 'Fetch the upload status for this file key',
495507 );
496508
497509 return $params;
@@ -502,20 +514,18 @@
503515 'Upload a file, or get the status of pending uploads. Several methods are available:',
504516 ' * Upload file contents directly, using the "file" parameter',
505517 ' * Have the MediaWiki server fetch a file from a URL, using the "url" parameter',
506 - ' * Complete an earlier upload that failed due to warnings, using the "sessionkey" parameter',
 518+ ' * Complete an earlier upload that failed due to warnings, using the "filekey" parameter',
507519 'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when',
508 - 'sending the "file". Note also that queries using session keys must be',
509 - 'done in the same login session as the query that originally returned the key (i.e. do not',
510 - 'log out and then log back in). Also you must get and send an edit token before doing any upload stuff'
 520+ 'sending the "file". Also you must get and send an edit token before doing any upload stuff'
511521 );
512522 }
513523
514524 public function getPossibleErrors() {
515525 return array_merge( parent::getPossibleErrors(),
516 - $this->getRequireOnlyOneParameterErrorMessages( array( 'sessionkey', 'file', 'url', 'statuskey' ) ),
 526+ $this->getRequireOnlyOneParameterErrorMessages( array( 'filekey', 'file', 'url', 'statuskey' ) ),
517527 array(
518528 array( 'uploaddisabled' ),
519 - array( 'invalid-session-key' ),
 529+ array( 'invalid-file-key' ),
520530 array( 'uploaddisabled' ),
521531 array( 'mustbeloggedin', 'upload' ),
522532 array( 'badaccess-groups' ),
@@ -545,7 +555,7 @@
546556 'Upload from a URL:',
547557 ' api.php?action=upload&filename=Wiki.png&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png',
548558 'Complete an upload that failed due to warnings:',
549 - ' api.php?action=upload&filename=Wiki.png&sessionkey=sessionkey&ignorewarnings=1',
 559+ ' api.php?action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1',
550560 );
551561 }
552562
Index: trunk/extensions/UploadWizard/UploadWizardHooks.php
@@ -121,7 +121,7 @@
122122 'mwe-upwiz-api-error-stashfailed',
123123 'mwe-upwiz-api-error-missingresult',
124124 'mwe-upwiz-api-error-missingparam',
125 - 'mwe-upwiz-api-error-invalid-session-key',
 125+ 'mwe-upwiz-api-error-invalid-file-key',
126126 'mwe-upwiz-api-error-copyuploaddisabled',
127127 'mwe-upwiz-api-error-mustbeloggedin',
128128 'mwe-upwiz-api-error-empty-file',
Index: trunk/extensions/UploadWizard/UploadWizard.i18n.php
@@ -4089,7 +4089,7 @@
40904090 'mwe-upwiz-api-error-stashfailed' => 'Sisäinen virhe: välikaikaisen tiedoston tallentaminen epäonnistui.',
40914091 'mwe-upwiz-api-error-missingresult' => 'Sisäinen virhe: ei voitu varmistaa, että tallennus onnistui.',
40924092 'mwe-upwiz-api-error-missingparam' => 'Sisäinen virhe: pyynnöstä puutuu parametrejä.',
4093 - 'mwe-upwiz-api-error-invalid-session-key' => 'Sisäinen virhe: tiedostoa ei löytynyt välikaisvarastosta.',
 4093+ 'mwe-upwiz-api-error-invalid-file-key' => 'Sisäinen virhe: tiedostoa ei löytynyt välikaisvarastosta.',
40944094 'mwe-upwiz-api-error-copyuploaddisabled' => 'Tallentaminen URL-osoitteesta ei ole käytössä.',
40954095 'mwe-upwiz-api-error-mustbeloggedin' => 'Sinun pitää olla kirjautunut sisään, jotta voisit tallentaa tiedostoja.',
40964096 'mwe-upwiz-api-error-empty-file' => 'Määrittämäsi tiedosto on tyhjä.',
Index: trunk/extensions/UploadWizard/resources/mw.Api.js
@@ -179,7 +179,7 @@
180180 'stashfailed',
181181 'missingresult',
182182 'missingparam',
183 - 'invalid-session-key',
 183+ 'invalid-file-key',
184184 'copyuploaddisabled',
185185 'mustbeloggedin',
186186 'empty-file',
Index: trunk/extensions/UploadWizard/resources/mw.UploadWizard.js
@@ -23,7 +23,7 @@
2424 this.extension = undefined;
2525 this.filename = undefined;
2626
27 - this.sessionKey = undefined;
 27+ this.fileKey = undefined;
2828
2929 // this should be moved to the interface, if we even keep this
3030 this.transportWeight = 1; // default all same
@@ -375,8 +375,8 @@
376376 */
377377 extractUploadInfo: function( resultUpload ) {
378378
379 - if ( resultUpload.sessionkey ) {
380 - this.sessionKey = resultUpload.sessionkey;
 379+ if ( resultUpload.filekey ) {
 380+ this.fileKey = resultUpload.filekey;
381381 }
382382
383383 if ( resultUpload.imageinfo ) {
@@ -451,7 +451,7 @@
452452
453453 var params = {
454454 'prop': 'stashimageinfo',
455 - 'siisessionkey': _this.sessionKey,
 455+ 'siifilekey': _this.fileKey,
456456 'siiprop': props.join( '|' )
457457 };
458458
Index: trunk/extensions/UploadWizard/resources/mw.UploadWizardDetails.js
@@ -714,7 +714,7 @@
715715
716716 var params = {
717717 action: 'upload',
718 - sessionkey: _this.upload.sessionKey,
 718+ filekey: _this.upload.fileKey,
719719 filename: _this.upload.title.getMain(),
720720 text: wikiText,
721721 summary: "User created page with " + mw.UploadWizard.userAgent

Follow-up revisions

RevisionCommit summaryAuthorDate
r92035changing smallint to int for image sizes, re bug 26179 comment 13raindrift00:21, 13 July 2011
r92043Turning simultaneous multiple file upload back on, re bug 26179raindrift01:03, 13 July 2011
r92081Added us_status column for future expansion re Bryan's suggestion...raindrift19:08, 13 July 2011
r93065Changed storeResult to storeStatus (more informative), removed leftover stack...raindrift16:55, 25 July 2011
r94526updated mwe-upwiz-invalid-session-key error in i18n for all languages, follow...raindrift17:02, 15 August 2011
r94536Fixed incorrect usage of || operator, added test...raindrift18:10, 15 August 2011
r94539cleaned up database query, doesn't have to be aware of column names anymore (...raindrift18:17, 15 August 2011
r94592Removed complex replag handling, now just query master when record isn't pres...raindrift23:40, 15 August 2011

Comments

#Comment by Brion VIBBER (talk | contribs)   22:04, 12 July 2011

Don't use 'smallint' for things like width/height; there are plenty of formats allowing larger images than 32k x 32k... and we can expect to be the ones to get them. ;)

It looks like there's no actual need to change the API responses either; this should be entirely an internally-facing change that clients need not do anything about.

#Comment by Raindrift (talk | contribs)   01:12, 13 July 2011

Fixed the smallint thing in r92035.

Regarding the API and sessionkey referring to an upload session, here's what I wrote in the email thread on wikitech-l (pasted here because it makes more sense here):

Neilk and I talked about that, but it seemed misleading. While it could be argued that the UploadWizard implements the concept of an upload session, it doesn't really carry through to the API. Each key is linked to a specific file, whereas I'd expect an upload session to be an abstraction that could contain multiple files. Previously, sessionkey was really referring to the file key that was stored in the session.

We decided that changing it made sense since it was possible to deprecate the old name without removing it, which gave us both clarity and functionality. Had that been impossible, leaving it unchanged was the next-best option.

#Comment by Bryan (talk | contribs)   09:28, 13 July 2011
+       public function __construct( $stash = false, $repo = false ) {
+               if( !$this->repo ) {
+                       $this->repo = RepoGroup::singleton()->getLocalRepo();
+               }

This is not what you meant I think. You want if ( $repo ) { $this->repo = $repo } else { $this->repo = RepoGroup::singleton()->getLocalRepo(); }

       public function getFile( $key ) {
+               global $wgUser;
<pre>
We should avoid new uses of $wgUser. Pass a user object as parameter to getFile().

<pre>
+                               $lag = $dbr->getLag();

$dbr is undefined at this point.

+                       'us_timestamp' => wfTimestamp( TS_MW )

This breaks postgres, use $dbw->timestamp()

+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->insert(
+                       'uploadstash',
+                       $this->fileMetadata[$key],
+                       __METHOD__
+               );

The uploadstash table is repobound, so you should use $repo->getMasterDb().

+               $dbw->delete(
+                       'uploadstash',
+                       array( 'us_key' => $key, 'us_user' => $userId ),
+                       __METHOD__
+               );

us_key is a UNIQUE INDEX, so you don't need the condition on us_user.

+               while( $row = $dbr->fetchRow( $res ) ) {
+                       array_push( $keys, $row['us_key'] );
+               }

fetchRow returns objects, not arrays iirc. You should not use while {}, but iterate over $res with foreach.

+       us_media_type varchar(255),

This is an enum in image, so for consistency this should also be an enum, but on the other hand enums suck. Hmmm...

#Comment by Raindrift (talk | contribs)   19:20, 13 July 2011

Thanks for the info. I'm now pulling user info from ApiBase::createContext (though if it's truly deprecated, Manual:$wgUser should probably be updated). I'm still using $wgUser when called from Filerepo::getUploadStash, since Filerepo doesn't seem to be aware of the current user.

I went ahead and changed the type of us_media_type for consistency... I figure if it ever changes to a not-enum, it won't be hard to do it in both places.

Timestamp generation is corrected (and I'm glad to see the move to a function that might make datetime columns possible someday). However, Manual:WfTimestamp should probably mention that this is the right way to do things. I'd make the documentation changes myself, but I'm still a bit unsure about what the correct conventions are at this point.

Updates are in r92081

#Comment by Reedy (talk | contribs)   16:57, 18 July 2011

Basic documentation added at Manual:Uploadstash_table

#Comment by NeilK (talk | contribs)   04:04, 23 July 2011

In includes/upload/UploadStash.php line 189 onwards, I wish you hadn't changed $status to $storeResult -- it's a hint that this is a Status object (includes/Status.php). If you felt the status needed a more descriptive name, maybe $storeStatus ?

includes/upload/UploadStash.php#230:

  wfDebugCallstack(); -- we shouldn't have this in production...

In includes/api/ApiQueryStashImageInfo.php#63 you added a TODO for some new exceptions -- fix that?

  • UploadStashNotLoggedInException
  • UploadStashMaxLagExceededException

(others?)

In general this change was too big, and it incorporates a name change to one API parameter, along with more substantive changes. It would be easier to review if those (at least) were separate.

#Comment by Catrope (talk | contribs)   10:14, 15 August 2011

The rename from mwe-upwiz-api-error-invalid-session-key to mwe-upwiz-api-error-invalid-file-key is incomplete: in the i18n file, you only changed the message name for Finnish (fi) and left all other languages, including English (!!) alone.

#Comment by Raindrift (talk | contribs)   17:02, 15 August 2011

You are totally right. So much for search/replace, I guess. Fixed in r94526

#Comment by Catrope (talk | contribs)   12:37, 15 August 2011
+		return self::isValidKey( $request->getText( 'wpFileKey' ) || $request->getText( 'wpSessionKey' ) );
...
+		$fileKey = $request->getText( 'wpFileKey' ) || $request->getText( 'wpSessionKey' );
...
+			$desiredDestName = $request->getText( 'wpUploadFile' ) || $request->getText( 'filename' );

This doesn't work. The || operator in PHP doesn't behave like the one in JavaScript or Python or whatever, but more like the one in C:

> $request = new FauxRequest(array('wpFileKey' => 'foo', 'wpSessionKey' => 'bar'));

> var_dump($request->getText( 'wpSessionKey' ) || $request->getText( 'wpSessionKey' ))
bool(true)

Instead, what you want to use is the second parameter to getText() (and most of the other getters in WebRequest), which is a default value that is returned if the key you asked for isn't set. So something like $request->getText( 'wpFileKey', $request->getText( 'wpSessionKey', 'defaultValueIfNeitherIsSet,DefaultsToEmptyString' ) ) would do what you want.

I'm confused, though: I don't have a very good understanding of the code, but it seems to me like this accidental creation of booleans that then get converted back to strings must break something, somewhere. Is there some devious reason why this doesn't break things, or at least doesn't appear to during basic testing?

+		if ( !empty( $this->mLocalFile ) ) {

This looks a lot like an inappropriate use of empty().

The waiting-for-lag behavior is unnecessarily complex, scary and fragile. If your data doesn't appear due to lag, fetch it from the master instead. This probably involves adding a parameter to fetchFileMetadata() to use the master instead of the slave.

	public function listFiles() {
...
			array( 'us_key' => $key ),

$key is undefined, and this condition probably breaks listFiles() entirely.

+		$this->fileMetadata[$key] = array(
+			'us_user' => $row->us_user,
+			'us_key' => $row->us_key,
+			'us_orig_path' => $row->us_orig_path,
+			'us_path' => $row->us_path,
+			'us_size' => $row->us_size,
+			'us_sha1' => $row->us_sha1,
+			'us_mime' => $row->us_mime,
+			'us_media_type' => $row->us_media_type,
+			'us_image_width' => $row->us_image_width,
+			'us_image_height' => $row->us_image_height,
+			'us_image_bits' => $row->us_image_bits,
+			'us_source_type' => $row->us_source_type,
+			'us_timestamp' => $row->us_timestamp,
+			'us_status' => $row->us_status
+		);

You can also use $this->fileMetadata[$key] = (array)$row; .

#Comment by NeilK (talk | contribs)   14:09, 15 August 2011

In UploadStash.php line 50, the function is returning a boolean value. So that's correct to use ||.

The other cases (line 77,81) I agree. That is perplexing. Not sure why this even works now.

#Comment by Catrope (talk | contribs)   14:14, 15 August 2011

You're misreading the parentheses on line 50. The || is used to compute the parameter to the function instead of being applied to the return value.

#Comment by NeilK (talk | contribs)   14:22, 15 August 2011

You're right, my fault.

#Comment by NeilK (talk | contribs)   14:35, 15 August 2011

Ian - you can check if the listFiles() function is broken by uploading a couple of files, and then not proceeding to describe them. In another tab open: http://yoursite/wiki/Special:UploadStash

I think I understand the problem with initializeFromRequest. That code path isn't even touched by UploadWizard, since it uses the API. Only Special:Upload uses it. To trigger it, you have to craft a problem with the file that won't be caught by Special:Upload, which will cause it to be stashed. An example might be to try a file like SANY_12345.JPG, because (if you copied the Commons config) that's a Blacklisted title but isn't caught by any hardcoded checks. Then, even if you fix the stash, nothing seems to happen; you get sent back to an "empty" upload form.

#Comment by Raindrift (talk | contribs)   18:11, 15 August 2011

re: || operator, I have no idea where I got the notion that such a thing would work. Looking at it myself, I'm like, "WTF?" Fixed. I'm not entirely sure why this didn't cause serious problems either, but I've added some tests to cover this code since we needed them anyway.

regarding usage of empty(): I was just duplicating the behavior that was there before (Reedy's, I believe--all I changed is the name of the variable). However, I think you're right, and I've changed it to !.

re: listfiles(), it should be checking against the user. Fixed.

Regarding lag, I based my decision on the following statement in Database Access/Working with lag:

"To avoid swamping the master every time the slaves lag, use of this approach should be kept to a minimum. In most cases you should just read from the slave and let the user deal with the delay."

UploadWizard could generate a page that requires a select for each of many images. I'm concerned about the load that could place on the master. I'll totally change it if you think it's best, but are you sure?

Anyway, this is all implemented in r94536 except the lag stuff.

#Comment by Catrope (talk | contribs)   19:24, 15 August 2011

What you did is not what we meant with "let the user deal with the delay". You're supposed to either present the user with a page with the caveat that it might be out of date, or read from the slave first and if you find the data is out of date in a nasty way (like, something doesn't exist yet but you suspect it should), read from the master.

#Comment by Raindrift (talk | contribs)   23:41, 15 August 2011

Got it. Fixed in r94592.

Status & tagging log