r62806 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r62805‎ | r62806 | r62807 >
Date:02:15, 22 February 2010
Author:mah
Status:deferred (Comments)
Tags:
Comment:
follow up r62231, r61779, r62175
* Fix up messages
* For new FileRepo::append(), use flags to determine whether to delete or not
* Add more error checking for appending
* Fix a couple of places in Revision.php and LogPage.php where DB errors were produced when comment was null
* Remove bogus checking for !$comment, etc on the DONE phase of chunked uploading
* Don't pretend to return a value when raising an exception
* Add more tests for chunked uploads
* Verify that Status::getErrorsArray() (at least where it is used in ApiUpload::execute()) returns an array that we can pass to dieUsageMessage()
* Ensure that checkWarnings(), etc work only on the complete file
Modified paths:
  • /trunk/phase3/includes/LogPage.php (modified) (history)
  • /trunk/phase3/includes/Revision.php (modified) (history)
  • /trunk/phase3/includes/api/ApiBase.php (modified) (history)
  • /trunk/phase3/includes/api/ApiUpload.php (modified) (history)
  • /trunk/phase3/includes/filerepo/FSRepo.php (modified) (history)
  • /trunk/phase3/includes/filerepo/FileRepo.php (modified) (history)
  • /trunk/phase3/includes/filerepo/ForeignAPIRepo.php (modified) (history)
  • /trunk/phase3/includes/filerepo/NullRepo.php (modified) (history)
  • /trunk/phase3/includes/upload/UploadFromChunks.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesEn.php (modified) (history)
  • /trunk/phase3/maintenance/tests/UploadFromChunksTest.php (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/tests/UploadFromChunksTest.php
@@ -1,27 +1,30 @@
22 <?php
33
4 -require_once("ApiSetup.php");
 4+require_once( "ApiSetup.php" );
55
66 class UploadFromChunksTest extends ApiSetup {
77
88 function setUp() {
99 global $wgEnableUploads;
1010
11 - $wgEnableUploads=true;
12 - ini_set('file_loads', true);
 11+ $wgEnableUploads = true;
1312 parent::setup();
1413
 14+ ini_set( 'file_loads', 1 );
 15+ ini_set( 'log_errors', 1 );
 16+ ini_set( 'error_reporting', 1 );
 17+ ini_set( 'display_errors', 1 );
1518 }
1619
17 - function makeChunk() {
 20+ function makeChunk( $content ) {
1821 $file = tempnam( wfTempDir(), "" );
19 - $fh = fopen($file, "w");
20 - if($fh == false) {
21 - $this->markTestIncomplete("Couldn't open $file!\n");
 22+ $fh = fopen( $file, "w" );
 23+ if ( $fh == false ) {
 24+ $this->markTestIncomplete( "Couldn't open $file!\n" );
2225 return;
2326 }
24 - fwrite($fh, "123");
25 - fclose($fh);
 27+ fwrite( $fh, $content );
 28+ fclose( $fh );
2629
2730 $_FILES['chunk']['tmp_name'] = $file;
2831 $_FILES['chunk']['size'] = 3;
@@ -30,133 +33,286 @@
3134 }
3235
3336 function cleanChunk() {
34 - if(file_exists($_FILES['chunk']['tmp_name']))
35 - unlink($_FILES['chunk']['tmp_name']);
 37+ if ( file_exists( $_FILES['chunk']['tmp_name'] ) )
 38+ unlink( $_FILES['chunk']['tmp_name'] );
3639 }
3740
38 - function doApiRequest($params) {
39 - $session = isset( $_SESSION ) ? $_SESSION : array();
40 - $req = new FauxRequest($params, true, $session);
41 - $module = new ApiMain($req, true);
 41+ function doApiRequest( $params, $data = null ) {
 42+ $session = isset( $data[2] ) ? $data[2] : array();
 43+ $_SESSION = $session;
 44+
 45+ $req = new FauxRequest( $params, true, $session );
 46+ $module = new ApiMain( $req, true );
4247 $module->execute();
4348
44 - return $module->getResultData();
 49+ return array( $module->getResultData(), $req, $_SESSION );
4550 }
4651
4752 function testGetTitle() {
4853 $filename = tempnam( wfTempDir(), "" );
4954 $c = new UploadFromChunks();
50 - $c->initialize(false, "temp.txt", null, $filename, 0, null);
51 - $this->assertEquals(null, $c->getTitle());
 55+ $c->initialize( false, "temp.txt", null, $filename, 0, null );
 56+ $this->assertEquals( null, $c->getTitle() );
5257
5358 $c = new UploadFromChunks();
54 - $c->initialize(false, "temp.png", null, $filename, 0, null);
55 - $this->assertEquals(Title::makeTitleSafe(NS_FILE, "Temp.png"), $c->getTitle());
 59+ $c->initialize( false, "temp.png", null, $filename, 0, null );
 60+ $this->assertEquals( Title::makeTitleSafe( NS_FILE, "Temp.png" ), $c->getTitle() );
5661 }
5762
5863 function testLogin() {
59 - $data = $this->doApiRequest(array('action' => 'login',
60 - "lgname" => self::$userName,
61 - "lgpassword" => self::$passWord ) );
62 - $this->assertArrayHasKey("login", $data);
63 - $this->assertArrayHasKey("result", $data['login']);
64 - $this->assertEquals("Success", $data['login']['result']);
 64+ $data = $this->doApiRequest( array(
 65+ 'action' => 'login',
 66+ 'lgname' => self::$userName,
 67+ 'lgpassword' => self::$passWord ) );
 68+ $this->assertArrayHasKey( "login", $data[0] );
 69+ $this->assertArrayHasKey( "result", $data[0]['login'] );
 70+ $this->assertEquals( "Success", $data[0]['login']['result'] );
 71+ $this->assertArrayHasKey( 'lgtoken', $data[0]['login'] );
6572
6673 return $data;
6774 }
6875
69 -
7076 /**
7177 * @depends testLogin
7278 */
73 - function testGetEditToken($data) {
 79+ function testSetupChunkSession( $data ) {
7480 global $wgUser;
75 - $wgUser = User::newFromName(self::$userName);
 81+ $wgUser = User::newFromName( self::$userName );
7682 $wgUser->load();
 83+ $data[2]['wsEditToken'] = $data[2]['wsToken'];
 84+ $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX;
 85+ $exception = false;
7786
78 - $data = $this->doApiRequest(array('action' => 'query',
79 - 'prop' => 'info',
80 - 'intoken' => 'edit'));
81 - }
 87+ $data = $this->doApiRequest( array(
 88+ 'filename' => 'tmp.txt',
 89+ 'action' => 'upload',
 90+ 'enablechunks' => true,
 91+ 'token' => $token ), $data );
 92+ $this->assertArrayHasKey( 'uploadUrl', $data[0] );
 93+ $this->assertRegexp( '/action=upload/', $data[0]['uploadUrl'] );
 94+ $this->assertRegexp( '/enablechunks=true/', $data[0]['uploadUrl'] );
 95+ $this->assertRegexp( '/format=json/', $data[0]['uploadUrl'] );
 96+ $this->assertRegexp( '/chunksession=/', $data[0]['uploadUrl'] );
 97+ $this->assertRegexp( '/token=/', $data[0]['uploadUrl'] );
8298
83 - function testSetupChunkSession() {
 99+ return $data;
84100 }
85101
86 -
87 - /**
88 - * @expectedException UsageException
89 - */
90 - function testPerformUploadInitError() {
 102+ /**
 103+ * @depends testSetupChunkSession
 104+ */
 105+ function testAppendChunkTypeBanned( $data ) {
91106 global $wgUser;
92 - $wgUser = User::newFromId(1);
 107+ $wgUser = User::newFromName( self::$userName );
93108
94 - $req = new FauxRequest(
95 - array('action' => 'upload',
96 - 'enablechunks' => '1',
97 - 'filename' => 'test.png',
98 - ));
99 - $module = new ApiMain($req, true);
100 - $module->execute();
 109+ $url = $data[0]['uploadUrl'];
 110+ $params = wfCgiToArray( substr( $url, strpos( $url, "?" ) ) );
 111+
 112+ $size = 0;
 113+ for ( $i = 0; $i < 4; $i++ ) {
 114+ $this->makeChunk( "123" );
 115+ $size += $_FILES['chunk']['size'];
 116+
 117+ $data = $this->doApiRequest( $params, $data );
 118+ $this->assertArrayHasKey( "result", $data[0] );
 119+ $this->assertTrue( (bool)$data[0]["result"] );
 120+
 121+ $this->assertArrayHasKey( "filesize", $data[0] );
 122+ $this->assertEquals( $size, $data[0]['filesize'] );
 123+
 124+ $this->cleanChunk();
 125+ }
 126+
 127+ $data['param'] = $params;
 128+ return $data;
101129 }
102130
103131 /**
104132 * @depends testLogin
105133 */
106 - function testPerformUploadInitSuccess($login) {
 134+ function testInvalidSessionKey( $data ) {
107135 global $wgUser;
 136+ $wgUser = User::newFromName( self::$userName );
 137+ $wgUser->load();
 138+ $data[2]['wsEditToken'] = $data[2]['wsToken'];
 139+ $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX;
 140+ $exception = false;
108141
109 - $wgUser = User::newFromName(self::$userName);
110 - $token = $wgUser->editToken();
 142+ try {
 143+ $this->doApiRequest( array(
 144+ 'action' => 'upload',
 145+ 'enablechunks' => true,
 146+ 'token' => $token,
 147+ 'chunksession' => 'bogus' ), $data );
 148+ } catch ( UsageException $e ) {
 149+ $exception = true;
 150+ $this->assertEquals( "Not a valid session key", $e->getMessage() );
 151+ }
111152
112 - $data = $this->doApiRequest(
113 - array('action' => 'upload',
114 - 'enablechunks' => '1',
115 - 'filename' => 'test.png',
116 - 'token' => $token,
117 - ));
 153+ $this->assertTrue( $exception, "Got exception" );
 154+ }
118155
119 - $this->assertArrayHasKey("uploadUrl", $data);
 156+ function testPerformUploadInitError() {
 157+ global $wgUser;
 158+ $wgUser = User::newFromId( 1 );
120159
121 - return array('data' => $data, 'session' => $_SESSION, 'token' => $token);
 160+ $req = new FauxRequest(
 161+ array(
 162+ 'action' => 'upload',
 163+ 'enablechunks' => 'false',
 164+ 'sessionkey' => '1',
 165+ 'filename' => 'test.png',
 166+ ) );
 167+ $module = new ApiMain( $req, true );
 168+ $gotException = false;
 169+ try {
 170+ $module->execute();
 171+ } catch ( UsageException $e ) {
 172+ $this->assertEquals( "The token parameter must be set", $e->getMessage() );
 173+ $gotException = true;
 174+ }
 175+
 176+ $this->assertTrue( $gotException );
122177 }
123178
124179 /**
125 - * @depends testPerformUploadInitSuccess
 180+ * @depends testAppendChunkTypeBanned
126181 */
127 - function testAppendChunk($combo) {
 182+ function testUploadChunkDoneTypeBanned( $data ) {
128183 global $wgUser;
129 - $data = $combo['data'];
130 - $_SESSION = $combo['session'];
131 - $wgUser = User::newFromName(self::$userName);
 184+ $wgUser = User::newFromName( self::$userName );
132185 $token = $wgUser->editToken();
 186+ $params = $data['param'];
 187+ $params['done'] = 1;
133188
134 - $url = $data['uploadUrl'];
135 - $params = wfCgiToArray(substr($url, strpos($url, "?")));
 189+ $this->makeChunk( "123" );
136190
137 - for($i=0;$i<10;$i++) {
138 - $this->makeChunk();
139 - $data = $this->doApiRequest($params);
 191+ $gotException = false;
 192+ try {
 193+ $data = $this->doApiRequest( $params, $data );
 194+ } catch ( UsageException $e ) {
 195+ $this->assertEquals( "This type of file is banned",
 196+ $e->getMessage() );
 197+ $gotException = true;
 198+ }
 199+ $this->cleanChunk();
 200+ $this->assertTrue( $gotException );
 201+ }
 202+
 203+ /**
 204+ * @depends testLogin
 205+ */
 206+ function testUploadChunkDoneDuplicate( $data ) {
 207+ global $wgUser, $wgVerifyMimeType;
 208+
 209+ $wgVerifyMimeType = false;
 210+ $wgUser = User::newFromName( self::$userName );
 211+ $data[2]['wsEditToken'] = $data[2]['wsToken'];
 212+ $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX;
 213+ $data = $this->doApiRequest( array(
 214+ 'filename' => 'tmp.png',
 215+ 'action' => 'upload',
 216+ 'enablechunks' => true,
 217+ 'token' => $token ), $data );
 218+
 219+ $url = $data[0]['uploadUrl'];
 220+ $params = wfCgiToArray( substr( $url, strpos( $url, "?" ) ) );
 221+ $size = 0;
 222+ for ( $i = 0; $i < 4; $i++ ) {
 223+ $this->makeChunk( "123" );
 224+ $size += $_FILES['chunk']['size'];
 225+
 226+ $data = $this->doApiRequest( $params, $data );
 227+ $this->assertArrayHasKey( "result", $data[0] );
 228+ $this->assertTrue( (bool)$data[0]["result"] );
 229+
 230+ $this->assertArrayHasKey( "filesize", $data[0] );
 231+ $this->assertEquals( $size, $data[0]['filesize'] );
 232+
140233 $this->cleanChunk();
141234 }
142235
143 - return array('data' => $data, 'session' => $_SESSION, 'token' => $token, 'params' => $params);
 236+ $params['done'] = true;
 237+
 238+ $this->makeChunk( "123" );
 239+ $data = $this->doApiRequest( $params, $data );
 240+ $this->cleanChunk();
 241+
 242+ $this->assertArrayHasKey( 'upload', $data[0] );
 243+ $this->assertArrayHasKey( 'result', $data[0]['upload'] );
 244+ $this->assertEquals( 'Warning', $data[0]['upload']['result'] );
 245+
 246+ $this->assertArrayHasKey( 'warnings', $data[0]['upload'] );
 247+ $this->assertArrayHasKey( 'exists',
 248+ $data[0]['upload']['warnings'] );
 249+ $this->assertEquals( 'Tmp.png',
 250+ $data[0]['upload']['warnings']['exists'] );
 251+
144252 }
145253
146254 /**
147 - * @depends testAppendChunk
 255+ * @depends testLogin
148256 */
149 - function testUploadChunkDone($combo) {
150 - global $wgUser;
151 - $data = $combo['data'];
152 - $params = $combo['params'];
153 - $_SESSION = $combo['session'];
154 - $wgUser = User::newFromName(self::$userName);
155 - $token = $wgUser->editToken();
 257+ function testUploadChunkDoneGood( $data ) {
 258+ global $wgUser, $wgVerifyMimeType;
 259+ $wgVerifyMimeType = false;
156260
157 - $params['done'] = 1;
 261+ $id = Title::newFromText( "Twar.png", NS_FILE )->getArticleID();
158262
159 - $this->makeChunk();
160 - $data = $this->doApiRequest($params);
 263+ $oldFile = Article::newFromID( $id );
 264+ if ( $oldFile ) {
 265+ $oldFile->doDeleteArticle();
 266+ $oldFile->doPurge();
 267+ }
 268+ $oldFile = wfFindFile( "Twar.png" );
 269+ if ( $oldFile ) {
 270+ $oldFile->delete();
 271+ }
 272+
 273+ $wgUser = User::newFromName( self::$userName );
 274+ $data[2]['wsEditToken'] = $data[2]['wsToken'];
 275+ $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX;
 276+ $data = $this->doApiRequest( array(
 277+ 'filename' => 'twar.png',
 278+ 'action' => 'upload',
 279+ 'enablechunks' => true,
 280+ 'token' => $token ), $data );
 281+
 282+ $url = $data[0]['uploadUrl'];
 283+ $params = wfCgiToArray( substr( $url, strpos( $url, "?" ) ) );
 284+ $size = 0;
 285+ for ( $i = 0; $i < 5; $i++ ) {
 286+ $this->makeChunk( "123" );
 287+ $size += $_FILES['chunk']['size'];
 288+
 289+ $data = $this->doApiRequest( $params, $data );
 290+ $this->assertArrayHasKey( "result", $data[0] );
 291+ $this->assertTrue( (bool)$data[0]["result"] );
 292+
 293+ $this->assertArrayHasKey( "filesize", $data[0] );
 294+ $this->assertEquals( $size, $data[0]['filesize'] );
 295+
 296+ $this->cleanChunk();
 297+ }
 298+
 299+ $params['done'] = true;
 300+
 301+ $this->makeChunk( "456" );
 302+ $data = $this->doApiRequest( $params, $data );
 303+
161304 $this->cleanChunk();
 305+
 306+ if ( isset( $data[0]['upload'] ) ) {
 307+ $this->markTestSkipped( "Please run 'php maintenance/deleteArchivedFiles.php --delete --force' and 'php maintenance/deleteArchivedRevisions.php --delete'" );
 308+ }
 309+
 310+ $this->assertArrayHasKey( 'result', $data[0] );
 311+ $this->assertEquals( 1, $data[0]['result'] );
 312+
 313+ $this->assertArrayHasKey( 'done', $data[0] );
 314+ $this->assertEquals( 1, $data[0]['done'] );
 315+
 316+ $this->assertArrayHasKey( 'resultUrl', $data[0] );
 317+ $this->assertRegExp( '/File:Twar.png/', $data[0]['resultUrl'] );
162318 }
163319 }
Index: trunk/phase3/includes/upload/UploadFromChunks.php
@@ -37,23 +37,26 @@
3838 }
3939
4040 public function initialize( $done, $filename, $sessionKey, $path, $fileSize, $sessionData ) {
41 - global $wgTmpDirectory;
42 - $this->status = new Status;
 41+ $this->status = Status::newGood();
4342
44 - $this->initFromSessionKey( $sessionKey, $sessionData );
 43+ $this->initializePathInfo( $filename, $path, 0, true );
 44+ if ( $sessionKey !== null ) {
 45+ $this->initFromSessionKey( $sessionKey, $sessionData, $fileSize );
4546
46 - if ( !$this->sessionKey && !$done ) {
 47+ if ( $done ) {
 48+ $this->chunkMode = self::DONE;
 49+ } else {
 50+ $this->mTempPath = $path;
 51+ $this->chunkMode = self::CHUNK;
 52+ }
 53+ } else {
4754 // session key not set, init the chunk upload system:
4855 $this->chunkMode = self::INIT;
49 - $this->initializePathInfo( $filename, $path, 0, true);
50 - } else if ( $this->sessionKey && !$done ) {
51 - $this->chunkMode = self::CHUNK;
52 - } else if ( $this->sessionKey && $done ) {
53 - $this->chunkMode = self::DONE;
5456 }
55 - if ( $this->chunkMode == self::CHUNK || $this->chunkMode == self::DONE ) {
56 - $this->mTempPath = $path;
57 - $this->mFileSize += $fileSize;
 57+
 58+ if ( $this->status->isOk()
 59+ && ( $this->mDesiredDestName === null || $this->mFileSize === null ) ) {
 60+ $this->status = Status::newFatal( 'chunk-init-error' );
5861 }
5962 }
6063
@@ -66,16 +69,26 @@
6770 * @returns string the session key for this chunked upload
6871 */
6972 protected function setupChunkSession( $comment, $pageText, $watch ) {
70 - $this->sessionKey = $this->getSessionKey();
71 - $_SESSION['wsUploadData'][$this->sessionKey] = array(
72 - 'comment' => $comment,
73 - 'pageText' => $pageText,
74 - 'watch' => $watch,
75 - 'mFilteredName' => $this->mFilteredName,
76 - 'repoPath' => null,
77 - 'mDesiredDestName' => $this->mDesiredDestName,
78 - 'version' => self::SESSION_VERSION,
79 - );
 73+ if ( !isset( $this->sessionKey ) ) {
 74+ $this->sessionKey = $this->getSessionKey();
 75+ }
 76+ foreach ( array( 'mFilteredName', 'repoPath', 'mFileSize', 'mDesiredDestName' )
 77+ as $key ) {
 78+ if ( isset( $this->$key ) ) {
 79+ $_SESSION['wsUploadData'][$this->sessionKey][$key] = $this->$key;
 80+ }
 81+ }
 82+ if ( isset( $comment ) ) {
 83+ $_SESSION['wsUploadData'][$this->sessionKey]['commment'] = $comment;
 84+ }
 85+ if ( isset( $pageText ) ) {
 86+ $_SESSION['wsUploadData'][$this->sessionKey]['pageText'] = $pageText;
 87+ }
 88+ if ( isset( $watch ) ) {
 89+ $_SESSION['wsUploadData'][$this->sessionKey]['watch'] = $watch;
 90+ }
 91+ $_SESSION['wsUploadData'][$this->sessionKey]['version'] = self::SESSION_VERSION;
 92+
8093 return $this->sessionKey;
8194 }
8295
@@ -83,26 +96,26 @@
8497 * Initialize a continuation of a chunked upload from a session key
8598 * @param $sessionKey string
8699 * @param $request WebRequest
 100+ * @param $fileSize int Size of this chunk
87101 *
88102 * @returns void
89103 */
90 - protected function initFromSessionKey( $sessionKey, $sessionData ) {
 104+ protected function initFromSessionKey( $sessionKey, $sessionData, $fileSize ) {
91105 // testing against null because we don't want to cause obscure
92106 // bugs when $sessionKey is full of "0"
93 - if ( $sessionKey === null ) {
94 - return;
95 - }
96107 $this->sessionKey = $sessionKey;
97108
98109 if ( isset( $sessionData[$this->sessionKey]['version'] )
99110 && $sessionData[$this->sessionKey]['version'] == self::SESSION_VERSION )
100111 {
101 - $this->comment = $sessionData[$this->sessionKey]['comment'];
102 - $this->pageText = $sessionData[$this->sessionKey]['pageText'];
103 - $this->watch = $sessionData[$this->sessionKey]['watch'];
104 - $this->mFilteredName = $sessionData[$this->sessionKey]['mFilteredName'];
105 - $this->repoPath = $sessionData[$this->sessionKey]['repoPath'];
106 - $this->mDesiredDestName = $sessionData[$this->sessionKey]['mDesiredDestName'];
 112+ foreach ( array( 'comment', 'pageText', 'watch', 'mFilteredName', 'repoPath', 'mFileSize', 'mDesiredDestName' )
 113+ as $key ) {
 114+ if ( isset( $sessionData[$this->sessionKey][$key] ) ) {
 115+ $this->$key = $sessionData[$this->sessionKey][$key];
 116+ }
 117+ }
 118+
 119+ $this->mFileSize += $fileSize;
107120 } else {
108121 $this->status = Status::newFatal( 'invalid-session-key' );
109122 }
@@ -127,14 +140,17 @@
128141 // b) should only happen over POST
129142 // c) we need the token to validate chunks are coming from a non-xss request
130143 return Status::newGood(
131 - array('uploadUrl' => wfExpandUrl( wfScript( 'api' ) ) . "?" .
132 - wfArrayToCGI(array('action' => 'upload',
133 - 'token' => $wgUser->editToken(),
134 - 'format' => 'json',
135 - 'filename' => $pageText,
136 - 'enablechunks' => 'true',
137 - 'chunksession' => $this->setupChunkSession( $comment, $pageText, $watch ) ) ) ) );
 144+ array( 'uploadUrl' => wfExpandUrl( wfScript( 'api' ) ) . "?" .
 145+ wfArrayToCGI( array(
 146+ 'action' => 'upload',
 147+ 'token' => $wgUser->editToken(),
 148+ 'format' => 'json',
 149+ 'filename' => $this->mDesiredDestName,
 150+ 'enablechunks' => 'true',
 151+ 'chunksession' =>
 152+ $this->setupChunkSession( $comment, $pageText, $watch ) ) ) ) );
138153 } else if ( $this->chunkMode == self::CHUNK ) {
 154+ $this->setupChunkSession();
139155 $this->appendChunk();
140156 if ( !$this->status->isOK() ) {
141157 return $this->status;
@@ -146,16 +162,10 @@
147163 array( 'result' => 1, 'filesize' => $this->mFileSize )
148164 );
149165 } else if ( $this->chunkMode == self::DONE ) {
150 - if ( !$comment )
151 - $comment = $this->comment;
 166+ $this->finalizeFile();
 167+ // We ignore the passed-in parameters because these were set on the first contact.
 168+ $status = parent::performUpload( $this->comment, $this->pageText, $this->watch, $user );
152169
153 - if ( !$pageText )
154 - $pageText = $this->pageText;
155 -
156 - if ( !$watch )
157 - $watch = $this->watch;
158 -
159 - $status = parent::performUpload( $comment, $pageText, $watch, $user );
160170 if ( !$status->isGood() ) {
161171 return $status;
162172 }
@@ -164,7 +174,7 @@
165175 // firefogg expects a specific result
166176 // http://www.firefogg.org/dev/chunk_post.html
167177 return Status::newGood(
168 - array('result' => 1, 'done' => 1, 'resultUrl' => wfExpandUrl( $file->getDescriptionUrl() ) )
 178+ array( 'result' => 1, 'done' => 1, 'resultUrl' => wfExpandUrl( $file->getDescriptionUrl() ) )
169179 );
170180 }
171181
@@ -203,16 +213,27 @@
204214 }
205215 if ( $this->getRealPath( $this->repoPath ) ) {
206216 $this->status = $this->appendToUploadFile( $this->repoPath, $this->mTempPath );
 217+
 218+ if ( $this->mFileSize > $wgMaxUploadSize )
 219+ $this->status = Status::newFatal( 'largefileserver' );
 220+
207221 } else {
208222 $this->status = Status::newFatal( 'filenotfound', $this->repoPath );
209223 }
210 - if ( $this->mFileSize > $wgMaxUploadSize )
211 - $this->status = Status::newFatal( 'largefileserver' );
212224 }
213225
 226+ /**
 227+ * Append the final chunk and ready file for parent::performUpload()
 228+ * @return void
 229+ */
 230+ protected function finalizeFile() {
 231+ $this->appendChunk();
 232+ $this->mTempPath = $this->getRealPath( $this->repoPath );
 233+ }
 234+
214235 public function verifyUpload() {
215236 if ( $this->chunkMode != self::DONE ) {
216 - return array('status' => UploadBase::OK);
 237+ return array( 'status' => UploadBase::OK );
217238 }
218239 return parent::verifyUpload();
219240 }
Index: trunk/phase3/includes/filerepo/FSRepo.php
@@ -227,7 +227,7 @@
228228 return $status;
229229 }
230230
231 - function append( $srcPath, $toAppendPath ) {
 231+ function append( $srcPath, $toAppendPath, $flags = 0 ) {
232232 $status = $this->newGood();
233233
234234 // Resolve the virtual URL
@@ -236,21 +236,31 @@
237237 }
238238 // Make sure the files are there
239239 if ( !is_file( $srcPath ) )
240 - $status->fatal( 'append-src-filenotfound', $srcPath );
 240+ $status->fatal( 'filenotfound', $srcPath );
241241
242242 if ( !is_file( $toAppendPath ) )
243 - $status->fatal( 'append-toappend-filenotfound', $toAppendPath );
 243+ $status->fatal( 'filenotfound', $toAppendPath );
244244
 245+ if ( !$status->isOk() ) return $status;
 246+
245247 // Do the append
246 - if( file_put_contents( $srcPath, file_get_contents( $toAppendPath ), FILE_APPEND ) ) {
247 - $status->value = $srcPath;
248 - } else {
249 - $status->fatal( 'fileappenderror', $toAppendPath, $srcPath);
 248+ $chunk = file_get_contents( $toAppendPath );
 249+ if( $chunk === false ) {
 250+ $status->fatal( 'fileappenderrorread', $toAppendPath );
250251 }
251252
252 - // Remove the source file
253 - unlink( $toAppendPath );
 253+ if( $status->isOk() ) {
 254+ if ( file_put_contents( $srcPath, $chunk, FILE_APPEND ) ) {
 255+ $status->value = $srcPath;
 256+ } else {
 257+ $status->fatal( 'fileappenderror', $toAppendPath, $srcPath);
 258+ }
 259+ }
254260
 261+ if ( $flags & self::DELETE_SOURCE ) {
 262+ unlink( $toAppendPath );
 263+ }
 264+
255265 return $status;
256266 }
257267
Index: trunk/phase3/includes/filerepo/NullRepo.php
@@ -14,7 +14,7 @@
1515 function storeTemp( $originalName, $srcPath ) {
1616 return false;
1717 }
18 - function append( $srcPath, $toAppendPath ){
 18+ function append( $srcPath, $toAppendPath, $flags = 0 ){
1919 return false;
2020 }
2121 function publishBatch( $triplets, $flags = 0 ) {
Index: trunk/phase3/includes/filerepo/FileRepo.php
@@ -402,9 +402,11 @@
403403 * Append the contents of the source path to the given file.
404404 * @param $srcPath string location of the source file
405405 * @param $toAppendPath string path to append to.
 406+ * @param $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate
 407+ * that the source file should be deleted if possible
406408 * @return mixed Status or false
407409 */
408 - abstract function append( $srcPath, $toAppendPath );
 410+ abstract function append( $srcPath, $toAppendPath, $flags );
409411
410412 /**
411413 * Remove a temporary file or mark it for garbage collection
Index: trunk/phase3/includes/filerepo/ForeignAPIRepo.php
@@ -63,7 +63,7 @@
6464 function storeTemp( $originalName, $srcPath ) {
6565 return false;
6666 }
67 - function append( $srcPath, $toAppendPath ){
 67+ function append( $srcPath, $toAppendPath, $flags = 0 ){
6868 return false;
6969 }
7070 function publishBatch( $triplets, $flags = 0 ) {
Index: trunk/phase3/includes/api/ApiBase.php
@@ -921,6 +921,7 @@
922922 'nouploadmodule' => array( 'code' => 'nouploadmodule', 'info' => 'No upload module set' ),
923923 'uploaddisabled' => array( 'code' => 'uploaddisabled', 'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true' ),
924924 'chunked-error' => array( 'code' => 'chunked-error', 'info' => 'There was a problem initializing the chunked upload.' ),
 925+ 'chunk-init-error' => array( 'code' => 'chunk-init-error', 'info' => 'Insufficient information for initialization.' ),
925926 );
926927
927928 /**
Index: trunk/phase3/includes/api/ApiUpload.php
@@ -68,7 +68,7 @@
6969 );
7070
7171 if ( !$this->mUpload->status->isOK() ) {
72 - return $this->dieUsageMsg( $this->mUpload->status->getErrorsArray() );
 72+ $this->dieUsageMsg( $this->mUpload->status->getErrorsArray() );
7373 }
7474 } elseif ( $this->mParams['sessionkey'] ) {
7575 /**
@@ -76,7 +76,7 @@
7777 */
7878 // Check the session key
7979 if ( !isset( $_SESSION['wsUploadData'][$this->mParams['sessionkey']] ) )
80 - return $this->dieUsageMsg( array( 'invalid-session-key' ) );
 80+ $this->dieUsageMsg( array( 'invalid-session-key' ) );
8181
8282 $this->mUpload = new UploadFromStash();
8383 $this->mUpload->initialize( $this->mParams['filename'],
@@ -109,7 +109,7 @@
110110
111111 $status = $this->mUpload->fetchFile();
112112 if ( !$status->isOK() ) {
113 - return $this->dieUsage( $status->getWikiText(), 'fetchfileerror' );
 113+ $this->dieUsage( $status->getWikiText(), 'fetchfileerror' );
114114 }
115115 }
116116 } else $this->dieUsageMsg( array( 'missingparam', 'filename' ) );
Index: trunk/phase3/includes/Revision.php
@@ -835,6 +835,8 @@
836836 $this->mTextId = $dbw->insertId();
837837 }
838838
 839+ if ( $this->mComment === null ) $this->mComment = "";
 840+
839841 # Record the edit in revisions
840842 $rev_id = isset( $this->mId )
841843 ? $this->mId
Index: trunk/phase3/includes/LogPage.php
@@ -378,6 +378,8 @@
379379 $params = array( $params );
380380 }
381381
 382+ if ( $comment === null ) $comment = "";
 383+
382384 $this->action = $action;
383385 $this->target = $target;
384386 $this->comment = $comment;
Index: trunk/phase3/languages/messages/MessagesEn.php
@@ -979,6 +979,8 @@
980980 'readonly_lag' => 'The database has been automatically locked while the slave database servers catch up to the master',
981981 'internalerror' => 'Internal error',
982982 'internalerror_info' => 'Internal error: $1',
 983+'fileappenderrorread' => 'Could not read "$1" during append.',
 984+'fileappenderror' => 'Could not append "$1" to "$2".',
983985 'filecopyerror' => 'Could not copy file "$1" to "$2".',
984986 'filerenameerror' => 'Could not rename file "$1" to "$2".',
985987 'filedeleteerror' => 'Could not delete file "$1".',

Follow-up revisions

RevisionCommit summaryAuthorDate
r62815Follow-up r62806: Add new message keyraymond07:33, 22 February 2010
r62901follow up r62806 - use “wb” for windowsmah00:45, 24 February 2010

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r61779follow up r61355, initial, incomplete dealing with TimStarlings CRmah07:05, 1 February 2010
r62175* Fix up ApiTest a bit, cookie handling works...mah08:37, 9 February 2010
r62231* new FauxResponse class to help with unit testing...mah10:36, 10 February 2010

Comments

#Comment by Raymond (talk | contribs)   07:39, 22 February 2010

Seen at translatewiki:

PHP Fatal error: Declaration of FSRepo::append() must be compatible with that of FileRepo::append() in /www/w/includes/filerepo/FSRepo.php on line 8

#Comment by Raymond (talk | contribs)   13:32, 22 February 2010

Fixed with r62824 by demon. Thanks :)

#Comment by Bryan (talk | contribs)   22:19, 23 February 2010

fopen should open in wb mode for Windows compatibility.

#Comment by Raymond (talk | contribs)   06:53, 24 February 2010

Changing status back to "new" as long as this commit isn't reviewed properly.

#Comment by Tim Starling (talk | contribs)   18:50, 10 March 2010

In UploadFromChunksTest.php:

ini_set( 'file_loads', 1 );

Presumably this is meant to be file_uploads. Either way, it won't do anything in CLI mode.

In UploadFromChunks.php:

$this->status = Status::newFatal( 'chunk-init-error' );

I'm getting tired of saying this: this message does not exist, you need to add it to MessagesEn.php. When you write a line of code which uses a new message, like Status::newFatal(), do not make a mental note to add the message later. You need to either add the message immediately, or make a note in some more reliable location than your head stating that it needs to be done later. Then when you are ready to commit, generate a diff and review it. For each new message reference in the diff, check it against the message additions in MessagesEn.php.

Mental notes are for poor people who can't afford real notes.

$this->setupChunkSession();

This is a PHP warning, not enough parameters.

  • When a file is uploaded via chunk upload, when done=1 and there are warnings, the file isn't stashed properly. Only the last chunk is stashed.
 		// testing against null because we don't want to cause obscure
 		// bugs when $sessionKey is full of "0"
-		if ( $sessionKey === null ) {
-			return;
-		}

Comment above doesn't make sense anymore

ApiUpload.php:

"Verify that Status::getErrorsArray() (at least where it is used in ApiUpload::execute()) returns an array that we can pass to dieUsageMessage()"

What are you talking about? Due to a fragile accident, it produces an output which looks vaguely like an appropriate internal error, but not without issuing a PHP warning "Illegal offset type in isset or empty in .../includes/api/ApiBase.php on line 959". If you want it to work beyond the next minor refactor, you should follow the documentation.


FSRepo.php:

+		if ( $flags & self::DELETE_SOURCE ) {
 		unlink( $toAppendPath );
+		}


Good, but you didn't update the caller in UploadFromChunks::appendToUploadFile(), so the temporary file won't be deleted anymore (except by PHP on request shutdown but it's OK to delete it here as well).

After looking at the Firefogg protocol in more detail, I'm pretty convinced that it doesn't belong in action=upload. The Firefogg protocol is idiosyncratic and has several serious design flaws. I think the code here should be split out to an extension, in a separate API module (say action=firefogg). Since I want to release 1.16 within a day or two, pulling this code out of the 1.16 branch, and aiming for an extension that works against 1.17, seems like a good approach. All of the work you've done on it will still be applicable, there will just be a few tweaks to make it work as a standalone module.

The remaining API upload code has some issues as well, but maybe we can sort them out before a 1.16 release.

#Comment by MarkAHershberger (talk | contribs)   21:05, 11 March 2010

re: 'chunk-init-error' and 'invalid-session' (for which no key exists in MessagesEn.php)

The messages don't exist there because the resulting Status object is just passed to dieUsageMessage which uses a key from ApiBase.php to look up the (untranslated) message.

With the problem in dieUsageMsg() & getErrorsArray(), I will change this (when it is moved to an extension) so that error reporting from initialization doesn't use Status objects.

#Comment by MarkAHershberger (talk | contribs)   17:29, 5 April 2010

The code has been backed out and put into an extension that I'm working on. See r63638, r63621, r64425, and r64419.

Status & tagging log