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 |
1 | 46 | + native |
Index: trunk/phase3/maintenance/tables.sql |
— | — | @@ -939,6 +939,52 @@ |
940 | 940 | |
941 | 941 | |
942 | 942 | -- |
| 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 | +-- |
943 | 989 | -- Primarily a summary table for Special:Recentchanges, |
944 | 990 | -- this table contains some additional info on edits from |
945 | 991 | -- the last few days, see Article::editUpdates() |
Index: trunk/phase3/tests/phpunit/includes/upload/UploadStashTest.php |
— | — | @@ -1,20 +1,38 @@ |
2 | 2 | <?php |
3 | 3 | |
4 | 4 | class UploadStashTest extends MediaWikiTestCase { |
| 5 | + /** |
| 6 | + * @var Array of UploadStashTestUser |
| 7 | + */ |
| 8 | + public static $users; |
| 9 | + |
5 | 10 | public function setUp() { |
6 | 11 | parent::setUp(); |
7 | 12 | |
8 | | - // Setup a fake session if necessary |
9 | | - if ( !isset( $_SESSION ) ) { |
10 | | - $GLOBALS['_SESSION'] = array(); |
11 | | - } |
12 | | - |
13 | 13 | // Setup a file for bug 29408 |
14 | 14 | $this->bug29408File = dirname( __FILE__ ) . '/bug29408'; |
15 | 15 | 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 | + ); |
16 | 31 | } |
17 | 32 | |
18 | 33 | public function testBug29408() { |
| 34 | + global $wgUser; |
| 35 | + $wgUser = self::$users['uploader']->user; |
| 36 | + |
19 | 37 | $repo = RepoGroup::singleton()->getLocalRepo(); |
20 | 38 | $stash = new UploadStash( $repo ); |
21 | 39 | |
— | — | @@ -23,7 +41,7 @@ |
24 | 42 | // We'll never reach this point if we hit bug 29408 |
25 | 43 | $this->assertTrue( true, 'Unrecognized file without extension' ); |
26 | 44 | |
27 | | - $file->remove(); |
| 45 | + $stash->removeFile( $file->getFileKey() ); |
28 | 46 | } |
29 | 47 | |
30 | 48 | public function tearDown() { |
Index: trunk/phase3/tests/phpunit/includes/api/ApiUploadTest.php |
— | — | @@ -88,7 +88,7 @@ |
89 | 89 | ), $session ); |
90 | 90 | } catch ( UsageException $e ) { |
91 | 91 | $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", |
93 | 93 | $e->getMessage() ); |
94 | 94 | } |
95 | 95 | $this->assertTrue( $exception, "Got exception" ); |
— | — | @@ -398,8 +398,9 @@ |
399 | 399 | $this->assertEquals( 'Success', $result['upload']['result'] ); |
400 | 400 | $this->assertEquals( $fileSize, ( int )$result['upload']['imageinfo']['size'] ); |
401 | 401 | $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']; |
404 | 405 | |
405 | 406 | // it should be visible from Special:UploadStash |
406 | 407 | // XXX ...but how to test this, with a fake WebRequest with the session? |
— | — | @@ -407,12 +408,11 @@ |
408 | 409 | // now we should try to release the file from stash |
409 | 410 | $params = array( |
410 | 411 | 'action' => 'upload', |
411 | | - 'sessionkey' => $sessionkey, |
| 412 | + 'filekey' => $filekey, |
412 | 413 | 'filename' => $fileName, |
413 | 414 | 'comment' => 'dummy comment', |
414 | 415 | 'text' => "This is the page text for $fileName, altered", |
415 | 416 | ); |
416 | | - $session[ UploadBase::getSessionKeyname() ] = $_SESSION[ UploadBase::getSessionKeyname() ]; |
417 | 417 | |
418 | 418 | $this->clearFakeUploads(); |
419 | 419 | $exception = false; |
Index: trunk/phase3/includes/upload/UploadFromStash.php |
— | — | @@ -8,17 +8,30 @@ |
9 | 9 | */ |
10 | 10 | |
11 | 11 | 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 | + } |
12 | 24 | |
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; |
22 | 30 | } |
| 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 | + } |
23 | 36 | |
24 | 37 | /** |
25 | 38 | * @param $request WebRequest |
— | — | @@ -26,45 +39,40 @@ |
27 | 40 | * @return Boolean |
28 | 41 | */ |
29 | 42 | 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' ) ); |
35 | 44 | } |
36 | 45 | |
37 | | - public function initialize( $name, $sessionKey, $sessionData ) { |
| 46 | + public function initialize( $key, $name = 'upload_file' ) { |
38 | 47 | /** |
39 | 48 | * Confirming a temporarily stashed upload. |
40 | 49 | * We don't want path names to be forged, so we keep |
41 | 50 | * them in the session on the server and just give |
42 | 51 | * an opaque key to the user agent. |
43 | | - */ |
44 | | - |
| 52 | + */ |
| 53 | + $metadata = $this->stash->getMetadata( $key ); |
45 | 54 | $this->initializePathInfo( $name, |
46 | | - $this->getRealPath ( $sessionData['mTempPath'] ), |
47 | | - $sessionData['mFileSize'], |
| 55 | + $this->getRealPath ( $metadata['us_path'] ), |
| 56 | + $metadata['us_size'], |
48 | 57 | false |
49 | 58 | ); |
50 | 59 | |
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']; |
56 | 64 | } |
57 | 65 | |
58 | 66 | /** |
59 | 67 | * @param $request WebRequest |
60 | 68 | */ |
61 | 69 | public function initializeFromRequest( &$request ) { |
62 | | - $sessionKey = $request->getText( 'wpSessionKey' ); |
63 | | - $sessionData = $request->getSessionData( UploadBase::SESSION_KEYNAME ); |
| 70 | + $fileKey = $request->getText( 'wpFileKey' ) || $request->getText( 'wpSessionKey' ); |
64 | 71 | |
65 | 72 | $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 ); |
69 | 77 | } |
70 | 78 | |
71 | 79 | public function getSourceType() { |
— | — | @@ -83,21 +91,26 @@ |
84 | 92 | /** |
85 | 93 | * There is no need to stash the image twice |
86 | 94 | */ |
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; |
90 | 98 | } |
91 | | - return parent::stashSession(); |
| 99 | + return parent::stashFileGetKey(); |
92 | 100 | } |
93 | 101 | |
94 | 102 | /** |
| 103 | + * Alias for stashFile |
| 104 | + */ |
| 105 | + public function stashSession( $key = null ) { |
| 106 | + return $this->stashFile( $key ); |
| 107 | + } |
| 108 | + |
| 109 | + /** |
95 | 110 | * Remove a temporarily kept file stashed by saveTempUploadedFile(). |
96 | 111 | * @return success |
97 | 112 | */ |
98 | 113 | public function unsaveUploadedFile() { |
99 | | - $repo = RepoGroup::singleton()->getLocalRepo(); |
100 | | - $success = $repo->freeTemp( $this->mVirtualTempPath ); |
101 | | - return $success; |
| 114 | + return $stash->removeFile( $this->mFileKey ); |
102 | 115 | } |
103 | 116 | |
104 | 117 | } |
\ No newline at end of file |
Index: trunk/phase3/includes/upload/UploadBase.php |
— | — | @@ -38,13 +38,6 @@ |
39 | 39 | const FILE_TOO_LARGE = 12; |
40 | 40 | const WINDOWS_NONASCII_FILENAME = 13; |
41 | 41 | |
42 | | - const SESSION_VERSION = 2; |
43 | | - const SESSION_KEYNAME = 'wsUploadData'; |
44 | | - |
45 | | - public static function getSessionKeyname() { |
46 | | - return self::SESSION_KEYNAME; |
47 | | - } |
48 | | - |
49 | 42 | public function getVerificationErrorCode( $error ) { |
50 | 43 | $code_to_status = array(self::EMPTY_FILE => 'empty-file', |
51 | 44 | self::FILE_TOO_LARGE => 'file-too-large', |
— | — | @@ -745,32 +738,40 @@ |
746 | 739 | * by design) then we may want to stash the file temporarily, get more information, and publish the file later. |
747 | 740 | * |
748 | 741 | * 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 |
751 | 744 | * API request to find this stashed file again. |
752 | 745 | * |
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. |
754 | 747 | * @return UploadStashFile stashed file |
755 | 748 | */ |
756 | | - public function stashSessionFile( $key = null ) { |
| 749 | + public function stashFile( $key = null ) { |
| 750 | + // was stashSessionFile |
757 | 751 | $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 ); |
763 | 754 | $this->mLocalFile = $file; |
764 | 755 | return $file; |
765 | 756 | } |
766 | 757 | |
767 | 758 | /** |
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(). |
769 | 760 | * |
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 |
772 | 763 | */ |
| 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 | + */ |
773 | 774 | public function stashSession( $key = null ) { |
774 | | - return $this->stashSessionFile( $key )->getSessionKey(); |
| 775 | + return $this->stashFileGetKey( $key ); |
775 | 776 | } |
776 | 777 | |
777 | 778 | /** |
Index: trunk/phase3/includes/upload/UploadStash.php |
— | — | @@ -5,16 +5,23 @@ |
6 | 6 | * - Several parts of MediaWiki do this in similar ways: UploadBase, UploadWizard, and FirefoggChunkedExtension |
7 | 7 | * And there are several that reimplement stashing from scratch, in idiosyncratic ways. The idea is to unify them all here. |
8 | 8 | * 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. |
10 | 10 | * - 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. |
13 | 13 | * |
| 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 |
14 | 17 | */ |
15 | 18 | class UploadStash { |
16 | 19 | |
17 | 20 | // Format of the key for files -- has to be suitable as a filename itself (e.g. ab12cd34ef.jpg) |
18 | 21 | 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; |
19 | 26 | |
20 | 27 | /** |
21 | 28 | * repository that this uses to store temp files |
— | — | @@ -24,92 +31,133 @@ |
25 | 32 | */ |
26 | 33 | public $repo; |
27 | 34 | |
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(); |
30 | 43 | |
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 | | - |
35 | 44 | /** |
36 | | - * Represents the session which contains temporarily stored files. |
| 45 | + * Represents a temporary filestore, with metadata in the database. |
37 | 46 | * Designed to be compatible with the session stashing code in UploadBase (should replace it eventually) |
38 | 47 | * |
39 | 48 | * @param $repo FileRepo |
40 | 49 | */ |
41 | 50 | public function __construct( $repo ) { |
42 | | - |
43 | 51 | // this might change based on wiki's configuration. |
44 | 52 | $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 | | - |
54 | 53 | } |
55 | 54 | |
56 | 55 | /** |
57 | 56 | * 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. |
59 | 57 | * |
60 | | - * @param $key Integer: key |
| 58 | + * @param $key String: key under which file information is stored |
61 | 59 | * @throws UploadStashFileNotFoundException |
62 | | - * @throws UploadStashBadVersionException |
| 60 | + * @throws UploadStashNotLoggedInException |
| 61 | + * @throws UploadStashWrongOwnerException |
| 62 | + * @throws UploadStashBadPathException |
63 | 63 | * @return UploadStashFile |
64 | 64 | */ |
65 | 65 | public function getFile( $key ) { |
| 66 | + global $wgUser; |
| 67 | + |
66 | 68 | if ( ! preg_match( self::KEY_FORMAT_REGEX, $key ) ) { |
67 | 69 | throw new UploadStashBadPathException( "key '$key' is not in a proper format" ); |
68 | 70 | } |
69 | 71 | |
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] ) ) { |
72 | 98 | throw new UploadStashFileNotFoundException( "key '$key' not found in stash" ); |
73 | 99 | } |
74 | 100 | |
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 ); |
80 | 103 | |
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 ); |
88 | 108 | } |
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 | + } |
90 | 115 | |
91 | | - } |
92 | 116 | return $this->files[$key]; |
93 | 117 | } |
94 | 118 | |
95 | 119 | /** |
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. |
99 | 121 | * |
| 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 | + * |
100 | 144 | * @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 |
103 | 147 | * @throws UploadStashBadPathException |
104 | 148 | * @throws UploadStashFileException |
| 149 | + * @throws UploadStashNotLoggedInException |
105 | 150 | * @return UploadStashFile: file, or null on failure |
106 | 151 | */ |
107 | | - public function stashFile( $path, $data = array(), $key = null ) { |
| 152 | + public function stashFile( $path, $sourceType = null, $key = null ) { |
| 153 | + global $wgUser; |
108 | 154 | 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" ); |
110 | 156 | throw new UploadStashBadPathException( "path doesn't exist" ); |
111 | 157 | } |
112 | 158 | $fileProps = File::getPropsFromPath( $path ); |
113 | 159 | |
| 160 | + wfDebug( __METHOD__ . " stashing file at '$path'\n" ); |
| 161 | + |
114 | 162 | // we will be initializing from some tmpnam files that don't have extensions. |
115 | 163 | // most of MediaWiki assumes all uploaded files have good extensions. So, we fix this. |
116 | 164 | $extension = self::getExtensionForPath( $path ); |
— | — | @@ -127,23 +175,27 @@ |
128 | 176 | $key = $fileProps['sha1'] . "." . $extension; |
129 | 177 | } |
130 | 178 | |
| 179 | + $this->fileProps[$key] = $fileProps; |
| 180 | + |
131 | 181 | if ( ! preg_match( self::KEY_FORMAT_REGEX, $key ) ) { |
132 | 182 | throw new UploadStashBadPathException( "key '$key' is not in a proper format" ); |
133 | 183 | } |
| 184 | + |
| 185 | + wfDebug( __METHOD__ . " key for '$path': $key\n" ); |
134 | 186 | |
135 | 187 | // 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 ); |
137 | 189 | |
138 | | - if( ! $status->isOK() ) { |
| 190 | + if( ! $storeResult->isOK() ) { |
139 | 191 | // It is a convention in MediaWiki to only return one error per API exception, even if multiple errors |
140 | 192 | // 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 |
142 | 194 | // 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(); |
145 | 197 | $error = reset( $error ); |
146 | 198 | if ( ! count( $error ) ) { |
147 | | - $error = $status->getWarningsArray(); |
| 199 | + $error = $storeResult->getWarningsArray(); |
148 | 200 | $error = reset( $error ); |
149 | 201 | if ( ! count( $error ) ) { |
150 | 202 | $error = array( 'unknown', 'no error recorded' ); |
— | — | @@ -151,52 +203,158 @@ |
152 | 204 | } |
153 | 205 | throw new UploadStashFileException( "error storing file in '$path': " . implode( '; ', $error ) ); |
154 | 206 | } |
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 | + } |
156 | 214 | |
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 ) |
165 | 229 | ); |
| 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 | + ); |
166 | 239 | |
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(); |
171 | 242 | |
| 243 | + # create the UploadStashFile object for this file. |
| 244 | + $this->initFile( $key ); |
| 245 | + |
172 | 246 | return $this->getFile( $key ); |
173 | 247 | } |
174 | 248 | |
175 | 249 | /** |
176 | 250 | * Remove all files from the stash. |
177 | 251 | * Does not clean up files in the repo, just the record of them. |
| 252 | + * |
| 253 | + * @throws UploadStashNotLoggedInException |
178 | 254 | * @return boolean: success |
179 | 255 | */ |
180 | 256 | 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 | + |
182 | 276 | return true; |
183 | 277 | } |
184 | 278 | |
185 | 279 | |
186 | 280 | /** |
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 |
189 | 284 | * @return boolean: success |
190 | 285 | */ |
191 | 286 | 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 | + |
193 | 323 | return true; |
194 | 324 | } |
195 | 325 | |
196 | 326 | /** |
197 | 327 | * List all files in the stash. |
| 328 | + * |
| 329 | + * @throws UploadStashNotLoggedInException |
| 330 | + * @return Array |
198 | 331 | */ |
199 | 332 | 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; |
201 | 359 | } |
202 | 360 | |
203 | 361 | /** |
— | — | @@ -229,12 +387,65 @@ |
230 | 388 | return File::normalizeExtension( $extension ); |
231 | 389 | } |
232 | 390 | |
| 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 | + } |
233 | 446 | } |
234 | 447 | |
235 | 448 | class UploadStashFile extends UnregisteredLocalFile { |
236 | | - private $sessionStash; |
237 | | - private $sessionKey; |
238 | | - private $sessionData; |
| 449 | + private $fileKey; |
239 | 450 | private $urlName; |
240 | 451 | protected $url; |
241 | 452 | |
— | — | @@ -242,18 +453,14 @@ |
243 | 454 | * A LocalFile wrapper around a file that has been temporarily stashed, so we can do things like create thumbnails for it |
244 | 455 | * Arguably UnregisteredLocalFile should be handling its own file repo but that class is a bit retarded currently |
245 | 456 | * |
246 | | - * @param $stash UploadStash: useful for obtaining config, stashing transformed files |
247 | 457 | * @param $repo FSRepo: repository where we should find the path |
248 | 458 | * @param $path String: path to file |
249 | 459 | * @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 |
251 | 460 | * @throws UploadStashBadPathException |
252 | 461 | * @throws UploadStashFileNotFoundException |
253 | 462 | */ |
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; |
258 | 465 | |
259 | 466 | // resolve mwrepo:// urls |
260 | 467 | if ( $repo->isVirtualUrl( $path ) ) { |
— | — | @@ -333,7 +540,7 @@ |
334 | 541 | * Get a URL to access the thumbnail |
335 | 542 | * This is required because the model of how files work requires that |
336 | 543 | * 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) |
338 | 545 | * |
339 | 546 | * @param $thumbName String: basename of thumbnail file -- however, we don't want to use the file exactly |
340 | 547 | * @return String: URL to access thumbnail, or URL with partial path |
— | — | @@ -351,7 +558,7 @@ |
352 | 559 | */ |
353 | 560 | public function getUrlName() { |
354 | 561 | if ( ! $this->urlName ) { |
355 | | - $this->urlName = $this->sessionKey; |
| 562 | + $this->urlName = $this->fileKey; |
356 | 563 | } |
357 | 564 | return $this->urlName; |
358 | 565 | } |
— | — | @@ -380,12 +587,12 @@ |
381 | 588 | } |
382 | 589 | |
383 | 590 | /** |
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) |
385 | 592 | * |
386 | | - * @return String: session key |
| 593 | + * @return String: file key |
387 | 594 | */ |
388 | | - public function getSessionKey() { |
389 | | - return $this->sessionKey; |
| 595 | + public function getFileKey() { |
| 596 | + return $this->fileKey; |
390 | 597 | } |
391 | 598 | |
392 | 599 | /** |
— | — | @@ -393,6 +600,11 @@ |
394 | 601 | * @return Status: success |
395 | 602 | */ |
396 | 603 | 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 | + |
397 | 609 | return $this->repo->freeTemp( $this->path ); |
398 | 610 | } |
399 | 611 | |
— | — | @@ -401,7 +613,8 @@ |
402 | 614 | class UploadStashNotAvailableException extends MWException {}; |
403 | 615 | class UploadStashFileNotFoundException extends MWException {}; |
404 | 616 | class UploadStashBadPathException extends MWException {}; |
405 | | -class UploadStashBadVersionException extends MWException {}; |
406 | 617 | class UploadStashFileException extends MWException {}; |
407 | 618 | 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 @@ |
185 | 185 | |
186 | 186 | // 1.19 |
187 | 187 | array( 'addTable', 'config', 'patch-config.sql' ), |
| 188 | + array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ), |
188 | 189 | ); |
189 | 190 | } |
190 | 191 | |
Index: trunk/phase3/includes/installer/SqliteUpdater.php |
— | — | @@ -61,6 +61,7 @@ |
62 | 62 | |
63 | 63 | // 1.19 |
64 | 64 | array( 'addTable', 'config', 'patch-config.sql' ), |
| 65 | + array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ), |
65 | 66 | ); |
66 | 67 | } |
67 | 68 | |
Index: trunk/phase3/includes/api/ApiQueryStashImageInfo.php |
— | — | @@ -51,7 +51,7 @@ |
52 | 52 | $result->addValue( array( 'query', $this->getModuleName() ), null, $imageInfo ); |
53 | 53 | $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), $modulePrefix ); |
54 | 54 | } |
55 | | - |
| 55 | + //TODO: update exception handling here to understand current getFile exceptions |
56 | 56 | } catch ( UploadStashNotAvailableException $e ) { |
57 | 57 | $this->dieUsage( "Session not available: " . $e->getMessage(), "nosession" ); |
58 | 58 | } catch ( UploadStashFileNotFoundException $e ) { |
Index: trunk/phase3/includes/api/ApiUpload.php |
— | — | @@ -58,6 +58,11 @@ |
59 | 59 | $request = $this->getMain()->getRequest(); |
60 | 60 | // Add the uploaded file to the params array |
61 | 61 | $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 | + } |
62 | 67 | |
63 | 68 | // Select an upload module |
64 | 69 | if ( !$this->selectUploadModule() ) { |
— | — | @@ -103,7 +108,8 @@ |
104 | 109 | // in case the warnings can be fixed with some further user action, let's stash this upload |
105 | 110 | // and return a key they can use to restart it |
106 | 111 | try { |
107 | | - $result['sessionkey'] = $this->performStash(); |
| 112 | + $result['filekey'] = $this->performStash(); |
| 113 | + $result['sessionkey'] = $result['filekey']; // backwards compatibility |
108 | 114 | } catch ( MWException $e ) { |
109 | 115 | $result['warnings']['stashfailed'] = $e->getMessage(); |
110 | 116 | } |
— | — | @@ -112,7 +118,8 @@ |
113 | 119 | // In this case, a failure to stash ought to be fatal |
114 | 120 | try { |
115 | 121 | $result['result'] = 'Success'; |
116 | | - $result['sessionkey'] = $this->performStash(); |
| 122 | + $result['filekey'] = $this->performStash(); |
| 123 | + $result['sessionkey'] = $result['filekey']; // backwards compatibility |
117 | 124 | } catch ( MWException $e ) { |
118 | 125 | $this->dieUsage( $e->getMessage(), 'stashfailed' ); |
119 | 126 | } |
— | — | @@ -133,18 +140,20 @@ |
134 | 141 | } |
135 | 142 | |
136 | 143 | /** |
137 | | - * Stash the file and return the session key |
| 144 | + * Stash the file and return the file key |
138 | 145 | * Also re-raises exceptions with slightly more informative message strings (useful for API) |
139 | 146 | * @throws MWException |
140 | | - * @return String session key |
| 147 | + * @return String file key |
141 | 148 | */ |
142 | 149 | function performStash() { |
143 | 150 | try { |
144 | | - $sessionKey = $this->mUpload->stashSessionFile()->getSessionKey(); |
| 151 | + $fileKey = $this->mUpload->stashFile()->getFileKey(); |
145 | 152 | } 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 ); |
147 | 156 | } |
148 | | - return $sessionKey; |
| 157 | + return $fileKey; |
149 | 158 | } |
150 | 159 | |
151 | 160 | /** |
— | — | @@ -158,7 +167,8 @@ |
159 | 168 | */ |
160 | 169 | function dieRecoverableError( $error, $parameter, $data = array() ) { |
161 | 170 | try { |
162 | | - $data['sessionkey'] = $this->performStash(); |
| 171 | + $data['filekey'] = $this->performStash(); |
| 172 | + $data['sessionkey'] = $data['filekey']; |
163 | 173 | } catch ( MWException $e ) { |
164 | 174 | $data['stashfailed'] = $e->getMessage(); |
165 | 175 | } |
— | — | @@ -180,7 +190,7 @@ |
181 | 191 | |
182 | 192 | // One and only one of the following parameters is needed |
183 | 193 | $this->requireOnlyOneParameter( $this->mParams, |
184 | | - 'sessionkey', 'file', 'url', 'statuskey' ); |
| 194 | + 'filekey', 'file', 'url', 'statuskey' ); |
185 | 195 | |
186 | 196 | if ( $this->mParams['statuskey'] ) { |
187 | 197 | $this->checkAsyncDownloadEnabled(); |
— | — | @@ -204,17 +214,14 @@ |
205 | 215 | $this->dieUsageMsg( array( 'missingparam', 'filename' ) ); |
206 | 216 | } |
207 | 217 | |
208 | | - if ( $this->mParams['sessionkey'] ) { |
| 218 | + if ( $this->mParams['filekey'] ) { |
209 | 219 | // 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' ); |
213 | 222 | } |
214 | 223 | |
215 | 224 | $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'] ); |
219 | 226 | |
220 | 227 | } elseif ( isset( $this->mParams['file'] ) ) { |
221 | 228 | $this->mUpload = new UploadFromFile(); |
— | — | @@ -463,7 +470,11 @@ |
464 | 471 | 'ignorewarnings' => false, |
465 | 472 | 'file' => null, |
466 | 473 | 'url' => null, |
467 | | - 'sessionkey' => null, |
| 474 | + 'filekey' => null, |
| 475 | + 'sessionkey' => array( |
| 476 | + ApiBase::PARAM_DFLT => null, |
| 477 | + ApiBase::PARAM_DEPRECATED => true, |
| 478 | + ), |
468 | 479 | 'stash' => false, |
469 | 480 | |
470 | 481 | 'asyncdownload' => false, |
— | — | @@ -485,12 +496,13 @@ |
486 | 497 | 'ignorewarnings' => 'Ignore any warnings', |
487 | 498 | 'file' => 'File contents', |
488 | 499 | '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.', |
490 | 502 | 'stash' => 'If set, the server will not add the file to the repository and stash it temporarily.', |
491 | 503 | |
492 | 504 | 'asyncdownload' => 'Make fetching a URL asynchronous', |
493 | 505 | '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', |
495 | 507 | ); |
496 | 508 | |
497 | 509 | return $params; |
— | — | @@ -502,20 +514,18 @@ |
503 | 515 | 'Upload a file, or get the status of pending uploads. Several methods are available:', |
504 | 516 | ' * Upload file contents directly, using the "file" parameter', |
505 | 517 | ' * 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', |
507 | 519 | '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' |
511 | 521 | ); |
512 | 522 | } |
513 | 523 | |
514 | 524 | public function getPossibleErrors() { |
515 | 525 | return array_merge( parent::getPossibleErrors(), |
516 | | - $this->getRequireOnlyOneParameterErrorMessages( array( 'sessionkey', 'file', 'url', 'statuskey' ) ), |
| 526 | + $this->getRequireOnlyOneParameterErrorMessages( array( 'filekey', 'file', 'url', 'statuskey' ) ), |
517 | 527 | array( |
518 | 528 | array( 'uploaddisabled' ), |
519 | | - array( 'invalid-session-key' ), |
| 529 | + array( 'invalid-file-key' ), |
520 | 530 | array( 'uploaddisabled' ), |
521 | 531 | array( 'mustbeloggedin', 'upload' ), |
522 | 532 | array( 'badaccess-groups' ), |
— | — | @@ -545,7 +555,7 @@ |
546 | 556 | 'Upload from a URL:', |
547 | 557 | ' api.php?action=upload&filename=Wiki.png&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png', |
548 | 558 | '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', |
550 | 560 | ); |
551 | 561 | } |
552 | 562 | |
Index: trunk/extensions/UploadWizard/UploadWizardHooks.php |
— | — | @@ -121,7 +121,7 @@ |
122 | 122 | 'mwe-upwiz-api-error-stashfailed', |
123 | 123 | 'mwe-upwiz-api-error-missingresult', |
124 | 124 | 'mwe-upwiz-api-error-missingparam', |
125 | | - 'mwe-upwiz-api-error-invalid-session-key', |
| 125 | + 'mwe-upwiz-api-error-invalid-file-key', |
126 | 126 | 'mwe-upwiz-api-error-copyuploaddisabled', |
127 | 127 | 'mwe-upwiz-api-error-mustbeloggedin', |
128 | 128 | 'mwe-upwiz-api-error-empty-file', |
Index: trunk/extensions/UploadWizard/UploadWizard.i18n.php |
— | — | @@ -4089,7 +4089,7 @@ |
4090 | 4090 | 'mwe-upwiz-api-error-stashfailed' => 'Sisäinen virhe: välikaikaisen tiedoston tallentaminen epäonnistui.', |
4091 | 4091 | 'mwe-upwiz-api-error-missingresult' => 'Sisäinen virhe: ei voitu varmistaa, että tallennus onnistui.', |
4092 | 4092 | '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.', |
4094 | 4094 | 'mwe-upwiz-api-error-copyuploaddisabled' => 'Tallentaminen URL-osoitteesta ei ole käytössä.', |
4095 | 4095 | 'mwe-upwiz-api-error-mustbeloggedin' => 'Sinun pitää olla kirjautunut sisään, jotta voisit tallentaa tiedostoja.', |
4096 | 4096 | '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 @@ |
180 | 180 | 'stashfailed', |
181 | 181 | 'missingresult', |
182 | 182 | 'missingparam', |
183 | | - 'invalid-session-key', |
| 183 | + 'invalid-file-key', |
184 | 184 | 'copyuploaddisabled', |
185 | 185 | 'mustbeloggedin', |
186 | 186 | 'empty-file', |
Index: trunk/extensions/UploadWizard/resources/mw.UploadWizard.js |
— | — | @@ -23,7 +23,7 @@ |
24 | 24 | this.extension = undefined; |
25 | 25 | this.filename = undefined; |
26 | 26 | |
27 | | - this.sessionKey = undefined; |
| 27 | + this.fileKey = undefined; |
28 | 28 | |
29 | 29 | // this should be moved to the interface, if we even keep this |
30 | 30 | this.transportWeight = 1; // default all same |
— | — | @@ -375,8 +375,8 @@ |
376 | 376 | */ |
377 | 377 | extractUploadInfo: function( resultUpload ) { |
378 | 378 | |
379 | | - if ( resultUpload.sessionkey ) { |
380 | | - this.sessionKey = resultUpload.sessionkey; |
| 379 | + if ( resultUpload.filekey ) { |
| 380 | + this.fileKey = resultUpload.filekey; |
381 | 381 | } |
382 | 382 | |
383 | 383 | if ( resultUpload.imageinfo ) { |
— | — | @@ -451,7 +451,7 @@ |
452 | 452 | |
453 | 453 | var params = { |
454 | 454 | 'prop': 'stashimageinfo', |
455 | | - 'siisessionkey': _this.sessionKey, |
| 455 | + 'siifilekey': _this.fileKey, |
456 | 456 | 'siiprop': props.join( '|' ) |
457 | 457 | }; |
458 | 458 | |
Index: trunk/extensions/UploadWizard/resources/mw.UploadWizardDetails.js |
— | — | @@ -714,7 +714,7 @@ |
715 | 715 | |
716 | 716 | var params = { |
717 | 717 | action: 'upload', |
718 | | - sessionkey: _this.upload.sessionKey, |
| 718 | + filekey: _this.upload.fileKey, |
719 | 719 | filename: _this.upload.title.getMain(), |
720 | 720 | text: wikiText, |
721 | 721 | summary: "User created page with " + mw.UploadWizard.userAgent |