Index: branches/new-upload/phase3/includes/filerepo/FSRepo.php |
— | — | @@ -211,7 +211,22 @@ |
212 | 212 | } |
213 | 213 | return $status; |
214 | 214 | } |
215 | | - |
| 215 | + function append( $srcPath, $toAppendPath ){ |
| 216 | + $status = $this->newGood(); |
| 217 | + //make sure files are there: |
| 218 | + if ( !is_file( $srcPath ) ) |
| 219 | + $status->fatal( 'filenotfound', $srcPath ); |
| 220 | + |
| 221 | + if ( !is_file( $toAppendPath ) ) |
| 222 | + $status->fatal( 'filenotfound', $toAppendPath ); |
| 223 | + |
| 224 | + //I assume fopen($src, 'a') fopen ($$toAppend .. etc would be faster / more memory friendly |
| 225 | + //but ideally we don't append "big" files so it does not matter |
| 226 | + if( ! file_put_contents( $srcPath, file_get_contents( $toAppendPath ), FILE_APPEND ) ) |
| 227 | + $status->fatal( 'fileappenderror', $toAppendPath, $srcPath); |
| 228 | + |
| 229 | + return $status; |
| 230 | + } |
216 | 231 | /** |
217 | 232 | * Take all available measures to prevent web accessibility of new deleted |
218 | 233 | * directories, in case the user has not configured offline storage |
— | — | @@ -243,7 +258,15 @@ |
244 | 259 | $result->value = $this->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel; |
245 | 260 | return $result; |
246 | 261 | } |
| 262 | + /*append to a temporary file (used in chunks uploads) */ |
| 263 | + function appendToTemp( $srcPath, $appendDataPath ) { |
| 264 | + //open the source file |
247 | 265 | |
| 266 | + $result = $this->append( $srcPath, $appendDataPath ); |
| 267 | + $result->value = $this->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel; |
| 268 | + return $result; |
| 269 | + } |
| 270 | + |
248 | 271 | /** |
249 | 272 | * Remove a temporary file or mark it for garbage collection |
250 | 273 | * @param string $virtualUrl The virtual URL returned by storeTemp |
Index: branches/new-upload/phase3/includes/filerepo/FileRepo.php |
— | — | @@ -336,7 +336,7 @@ |
337 | 337 | } |
338 | 338 | return $status; |
339 | 339 | } |
340 | | - |
| 340 | + |
341 | 341 | /** |
342 | 342 | * Store a batch of files |
343 | 343 | * |
Index: branches/new-upload/phase3/includes/filerepo/RepoGroup.php |
— | — | @@ -148,7 +148,7 @@ |
149 | 149 | $this->initialiseRepos(); |
150 | 150 | } |
151 | 151 | if ( $index === 'local' ) { |
152 | | - return $this->localRepo; |
| 152 | + return $this->localRepo; |
153 | 153 | } elseif ( isset( $this->foreignRepos[$index] ) ) { |
154 | 154 | return $this->foreignRepos[$index]; |
155 | 155 | } else { |
Index: branches/new-upload/phase3/includes/api/ApiUpload.php |
— | — | @@ -32,7 +32,7 @@ |
33 | 33 | * @ingroup API |
34 | 34 | */ |
35 | 35 | class ApiUpload extends ApiBase { |
36 | | - |
| 36 | + |
37 | 37 | public function __construct($main, $action) { |
38 | 38 | parent :: __construct($main, $action); |
39 | 39 | } |
— | — | @@ -43,7 +43,7 @@ |
44 | 44 | $this->mParams = $this->extractRequestParams(); |
45 | 45 | $request = $this->getMain()->getRequest(); |
46 | 46 | // Add the uploaded file to the params array |
47 | | - $this->mParams['file'] = $request->getFileName( 'file' ); |
| 47 | + $this->mParams['file'] = $request->getFileName( 'file' ); |
48 | 48 | |
49 | 49 | // Check whether upload is enabled |
50 | 50 | if( !UploadBase::isEnabled() ) |
— | — | @@ -52,12 +52,21 @@ |
53 | 53 | // One and only one of the following parameters is needed |
54 | 54 | $this->requireOnlyOneParameter( $this->mParams, |
55 | 55 | 'sessionkey', 'file', 'url', 'enablechunks' ); |
| 56 | + |
| 57 | + if( $this->mParams['enablechunks'] ){ |
| 58 | + //chunks upload enabled |
| 59 | + $this->mUpload = new UploadFromChunks(); |
| 60 | + $this->mUpload->initializeFromParams( $this->mParams ); |
56 | 61 | |
57 | | - if( $this->mParams['sessionkey'] ) { |
| 62 | + if( isset( $this->mUpload->status[ 'error' ] ) ) |
| 63 | + $this->dieUsageMsg( $this->mUpload->status[ 'error' ] ); |
| 64 | + |
| 65 | + } else if( $this->mParams['sessionkey'] ) { |
58 | 66 | // Stashed upload |
59 | 67 | $this->mUpload = new UploadFromStash(); |
60 | 68 | $this->mUpload->initialize( $this->mParams['sessionkey'] ); |
61 | | - } else { |
| 69 | + |
| 70 | + }else{ |
62 | 71 | // Upload from url or file or start a chunks request |
63 | 72 | |
64 | 73 | // Parameter filename is required |
— | — | @@ -75,11 +84,12 @@ |
76 | 85 | } elseif( isset( $this->mParams['url'] ) ) { |
77 | 86 | $this->mUpload = new UploadFromUrl(); |
78 | 87 | $this->mUpload->initialize( $this->mParams['filename'], $this->mParams['url'] ); |
79 | | - }elseif (isset( $this->mParams['enablechunks'])) { |
80 | | - $this->mUpload = new UploadFromChunks(); |
81 | | - $this->mUpload->initializeFromParams( $this->mParams ); |
82 | 88 | } |
83 | | - } |
| 89 | + } |
| 90 | + if( !isset( $this->mUpload ) ) |
| 91 | + $this->dieUsageMsg( array( 'no upload module set' ) ); |
| 92 | + |
| 93 | + |
84 | 94 | // Check whether the user has the appropriate permissions to upload anyway |
85 | 95 | $permission = $this->mUpload->isAllowed( $wgUser ); |
86 | 96 | |
— | — | @@ -115,35 +125,35 @@ |
116 | 126 | $result['result'] = 'Failure'; |
117 | 127 | $result['error'] = 'permission-denied'; |
118 | 128 | return $result; |
119 | | - } |
| 129 | + } |
120 | 130 | |
121 | 131 | $verification = $this->mUpload->verifyUpload( $resultDetails ); |
122 | | - if( $verification != UploadFromBase::OK ) { |
| 132 | + if( $verification != UploadBase::OK ) { |
123 | 133 | $result['result'] = 'Failure'; |
124 | 134 | switch( $verification ) { |
125 | | - case UploadFromBase::EMPTY_FILE: |
| 135 | + case UploadBase::EMPTY_FILE: |
126 | 136 | $result['error'] = 'empty-file'; |
127 | 137 | break; |
128 | | - case UploadFromBase::FILETYPE_MISSING: |
| 138 | + case UploadBase::FILETYPE_MISSING: |
129 | 139 | $result['error'] = 'filetype-missing'; |
130 | 140 | break; |
131 | | - case UploadFromBase::FILETYPE_BADTYPE: |
| 141 | + case UploadBase::FILETYPE_BADTYPE: |
132 | 142 | global $wgFileExtensions; |
133 | 143 | $result['error'] = 'filetype-banned'; |
134 | 144 | $result['filetype'] = $resultDetails['finalExt']; |
135 | 145 | $result['allowed-filetypes'] = $wgFileExtensions; |
136 | 146 | break; |
137 | | - case UploadFromBase::MIN_LENGHT_PARTNAME: |
| 147 | + case UploadBase::MIN_LENGHT_PARTNAME: |
138 | 148 | $result['error'] = 'filename-tooshort'; |
139 | 149 | break; |
140 | | - case UploadFromBase::ILLEGAL_FILENAME: |
| 150 | + case UploadBase::ILLEGAL_FILENAME: |
141 | 151 | $result['error'] = 'illegal-filename'; |
142 | 152 | $result['filename'] = $resultDetails['filtered']; |
143 | 153 | break; |
144 | | - case UploadFromBase::OVERWRITE_EXISTING_FILE: |
| 154 | + case UploadBase::OVERWRITE_EXISTING_FILE: |
145 | 155 | $result['error'] = 'overwrite'; |
146 | 156 | break; |
147 | | - case UploadFromBase::VERIFICATION_ERROR: |
| 157 | + case UploadBase::VERIFICATION_ERROR: |
148 | 158 | $result['error'] = 'verification-error'; |
149 | 159 | $args = $resultDetails['veri']; |
150 | 160 | $code = array_shift( $args ); |
— | — | @@ -151,7 +161,7 @@ |
152 | 162 | $result['args'] = $args; |
153 | 163 | $this->getResult()->setIndexedTagName( $result['args'], 'arg' ); |
154 | 164 | break; |
155 | | - case UploadFromBase::UPLOAD_VERIFICATION_ERROR: |
| 165 | + case UploadBase::UPLOAD_VERIFICATION_ERROR: |
156 | 166 | $result['error'] = 'upload-verification-error'; |
157 | 167 | $result['upload-verification-error'] = $resultDetails['error']; |
158 | 168 | break; |
— | — | @@ -178,8 +188,14 @@ |
179 | 189 | $result['sessionkey'] = $sessionKey; |
180 | 190 | return $result; |
181 | 191 | } |
182 | | - } |
| 192 | + } |
183 | 193 | |
| 194 | + //check for special API upload response: |
| 195 | + $upApiResult = $this->mUpload->getAPIresult( $this->mParams['comment'], $this->mParams['watch'] ); |
| 196 | + if( $upApiResult != UploadBase::OK ) //if we have a result override return it |
| 197 | + return $upApiResult; |
| 198 | + |
| 199 | + //do the upload |
184 | 200 | $status = $this->mUpload->performUpload( $this->mParams['comment'], |
185 | 201 | $this->mParams['comment'], $this->mParams['watch'], $wgUser ); |
186 | 202 | |
— | — | @@ -231,8 +247,7 @@ |
232 | 248 | 'comment' => 'Upload comment or initial page text', |
233 | 249 | 'watch' => 'Watch the page', |
234 | 250 | 'ignorewarnings' => 'Ignore any warnings', |
235 | | - 'enablechunks' => 'Boolean If we are in chunk mode; accepts many small file POSTs', |
236 | | - 'chunk_inx'=> 'The index of the chunk being uploaded. Used to order the build of a single file', |
| 251 | + 'enablechunks' => 'Boolean If we are in chunk mode; accepts many small file POSTs', |
237 | 252 | 'done' => 'When used with "chunks", Is sent to notify the api The last chunk is being uploaded.', |
238 | 253 | 'sessionkey' => 'Session key in case there were any warnings, or uploading chunks' |
239 | 254 | ); |
Index: branches/new-upload/phase3/includes/UploadFromChunks.php |
— | — | @@ -9,23 +9,154 @@ |
10 | 10 | * more info at: http://firefogg.org/dev/chunk_post.html |
11 | 11 | */ |
12 | 12 | class UploadFromChunks extends UploadBase { |
13 | | - var $chunk_state; //init, chunk, done |
14 | | - function initializeFromParams( $param ) { |
15 | | - //start of a chunk request init the upload check destination file name |
16 | | - //setup chunk folder |
17 | | - if( !$param['sessionkey'] && !$param['chunk_inx'] && !$parm['done'] ){ |
18 | | - |
| 13 | + |
| 14 | + var $chunk_mode; //init, chunk, done |
| 15 | + var $mSessionKey = false; |
| 16 | + var $status = array(); |
| 17 | + |
| 18 | + const INIT = 1; |
| 19 | + const CHUNK = 2; |
| 20 | + const DONE = 3; |
| 21 | + |
| 22 | + function initializeFromParams( $param ) { |
| 23 | + $this->initFromSessionKey( $param['sessionkey'] ); |
| 24 | + |
| 25 | + //set the chunk mode: |
| 26 | + if( !$this->mSessionKey && !$parm['done'] ){ |
| 27 | + //session key not set init the chunk upload system: |
| 28 | + $this->chunk_mode = UploadFromChunks::INIT; |
| 29 | + }else if( $this->mSessionKey && !$parm['done']){ |
| 30 | + //this is a chunk piece |
| 31 | + $this->chunk_mode = UploadFromChunks::CHUNK; |
| 32 | + |
| 33 | + }else if( $this->mSessionKey && $parm['done']){ |
| 34 | + //this is the last chunk |
| 35 | + $this->chunk_mode = UploadFromChunks::DONE; |
| 36 | + } |
| 37 | + return $this->status; |
| 38 | + } |
| 39 | + |
| 40 | + function initFromSessionKey( $sessionKey ){ |
| 41 | + if( !$sessionKey || empty( $sessionKey ) ){ |
| 42 | + return false; |
19 | 43 | } |
| 44 | + $this->mSessionKey = $sessionKey; |
| 45 | + if( isset( $_SESSION['wsUploadData'][$this->mSessionKey]['version'] ) && |
| 46 | + $_SESSION['wsUploadData'][$this->mSessionKey]['version'] == self::SESSION_VERSION ) { |
| 47 | + //update the local object from the session |
| 48 | + $this->mComment = $_SESSION[ 'wsUploadData' ][ $this->mSessionKey ][ 'mComment' ]; |
| 49 | + $this->mWatch = $_SESSION[ 'wsUploadData' ][ $this->mSessionKey ][ 'mWatch' ]; |
| 50 | + $this->mFilteredName = $_SESSION[ 'wsUploadData' ][ $this->mSessionKey ][ 'mFilteredName' ]; |
| 51 | + $this->mTempAppendPath = $_SESSION[ 'wsUploadData' ][ $this->mSessionKey ][ 'mTempAppendPath' ]; |
| 52 | + }else{ |
| 53 | + $this->status = Array( 'error'=> 'missing session data'); |
| 54 | + return false; |
| 55 | + } |
20 | 56 | |
21 | | - //we are receiving a chunk process as an upload and stash it the folder with its index number. |
22 | | - if( $param['sessionkey'] && $param['chunk_inx'] && !$parm['done']){ |
| 57 | + } |
| 58 | + static function isValidRequest( $request ) { |
| 59 | + $sessionData = $request->getSessionData('wsUploadData'); |
| 60 | + if(! self::isValidSessionKey( |
| 61 | + $request->getInt( 'wpSessionKey' ), |
| 62 | + $sessionData) ) |
| 63 | + return false; |
| 64 | + //check for the file: |
| 65 | + return (bool)$request->getFileTempName( 'file' ); |
| 66 | + } |
| 67 | + |
| 68 | + /* check warnings depending on chunk_mode*/ |
| 69 | + function checkWarnings(){ |
| 70 | + $warning = array(); |
| 71 | + return $warning; |
| 72 | + } |
| 73 | + |
| 74 | + /* Verify whether the upload is sane. |
| 75 | + * Returns self::OK or else an array with error information |
| 76 | + */ |
| 77 | + function verifyUpload( $resultDetails ) { |
| 78 | + /* |
| 79 | + * check the internal chunk mode for alternative Verify path |
| 80 | + * (for now just return "OK" |
| 81 | + */ |
| 82 | + if( $this->chunk_mode == UploadFromChunks::INIT) |
| 83 | + return self::OK; |
| 84 | + |
| 85 | + return parent::verifyUpload( $resultDetails ); |
| 86 | + } |
23 | 87 | |
| 88 | + function setupChunkSession( $comment, $watch ) { |
| 89 | + $key = $this->getSessionKey(); |
| 90 | + //since we can't pass things along in POST store them in the Session: |
| 91 | + $_SESSION['wsUploadData'][$key] = array( |
| 92 | + 'mComment' => $comment, |
| 93 | + 'mWatch' => $watch, |
| 94 | + 'mFilteredName' => $this->mFilteredName, |
| 95 | + 'mTempAppendPath' => null, |
| 96 | + 'version' => self::SESSION_VERSION, |
| 97 | + ); |
| 98 | + return $key; |
| 99 | + } |
| 100 | + |
| 101 | + //lets us return an api result (as flow for chunk uploads is kind of different than others. |
| 102 | + function getAPIresult($comment, $watch){ |
| 103 | + if( $this->chunk_mode == UploadFromChunks::INIT ){ |
| 104 | + //verifyUpload & checkWarnings have already run .. just create the upload store return the upload session key |
| 105 | + return array( |
| 106 | + 'sessionkey'=> $this->setupChunkSession( $comment, $watch ) |
| 107 | + ); |
| 108 | + }else if( $this->chunk_mode == UploadFromChunks::CHUNK ){ |
| 109 | + |
| 110 | + $this->doChunkAppend(); |
| 111 | + |
| 112 | + //return success: |
| 113 | + return array( |
| 114 | + 'result' => 1 |
| 115 | + ); |
| 116 | + |
| 117 | + }else if( $this->chunk_mode == UploadFromChunks::DONE ){ |
| 118 | + //append the last chunk: |
| 119 | + $this->doChunkAppend(); |
| 120 | + //process the upload normally: |
| 121 | + return UploadFrom::OK; |
| 122 | + } |
| 123 | + } |
| 124 | + //append the given chunk to the temporary uploaded file. (if no temporary uploaded file exists created it. |
| 125 | + function doChunkAppend(){ |
| 126 | + //if we don't have a mTempAppendPath to append to generate that: |
| 127 | + if( ! $this->mTempAppendPath ){ |
| 128 | + //make a chunk store path. (append tmp file to chunk) |
| 129 | + print "save Temp: " . $this->mTempPath . ' '. $this->mDestName . "\n"; |
| 130 | + if( isset( $this->mDestName ) ){ |
| 131 | + $stash = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath ); |
| 132 | + if( !$stash ) { |
| 133 | + # Couldn't save the file. |
| 134 | + return false; |
| 135 | + } |
| 136 | + //update the mDestName |
| 137 | + $this->mTempAppendPath = $stash; |
| 138 | + $_SESSION[ 'wsUploadData' ][ $this->mSessionKey ] = $this->mTempAppendPath; |
| 139 | + } |
| 140 | + }else{ |
| 141 | + //make sure the file exists: |
| 142 | + if( is_file( $this->mTempAppendPath ) ){ |
| 143 | + print "append: " . $this->mTempPath . ' to ' . $this->mTempAppendPath . "\n"; |
| 144 | + $this->appendToUploadFile( $this->mTempAppendPath, $this->mTempPath ); |
| 145 | + } |
24 | 146 | } |
| 147 | + //@@todo api should allow for "comment vs page text" |
25 | 148 | |
26 | | - //this is the last chunk |
27 | | - if( $param['sessionkey'] && $param['chunk_inx'] && $parm['done']){ |
28 | | - |
| 149 | + |
| 150 | + //do the chunk append: |
| 151 | + |
| 152 | + //do the actual upload: |
| 153 | + |
| 154 | + } |
| 155 | + |
| 156 | + function checkAPIresultOverride(){ |
| 157 | + if( $this->chunk_mode == UploadFromChunks::INIT ){ |
| 158 | + return true; |
| 159 | + }else{ |
| 160 | + return false; |
29 | 161 | } |
30 | | - |
31 | 162 | } |
32 | 163 | } |
Index: branches/new-upload/phase3/includes/UploadBase.php |
— | — | @@ -21,12 +21,13 @@ |
22 | 22 | const UPLOAD_VERIFICATION_ERROR = 11; |
23 | 23 | const UPLOAD_WARNING = 12; |
24 | 24 | const INTERNAL_ERROR = 13; |
| 25 | + const MIN_LENGHT_PARTNAME = 14; |
25 | 26 | |
26 | 27 | const SESSION_VERSION = 2; |
27 | 28 | |
28 | 29 | /** |
29 | 30 | * Returns true if uploads are enabled. |
30 | | - * Can be overriden by subclasses. |
| 31 | + * Can be override by subclasses. |
31 | 32 | */ |
32 | 33 | static function isEnabled() { |
33 | 34 | global $wgEnableUploads; |
— | — | @@ -319,9 +320,8 @@ |
320 | 321 | $status = $this->mLocalFile->upload( $this->mTempPath, $comment, $pageText, |
321 | 322 | File::DELETE_SOURCE, $this->mFileProps, false, $user ); |
322 | 323 | |
323 | | - if( $status->isGood() && $watch ) { |
324 | | - $user->addWatch( $this->mLocalFile->getTitle() ); |
325 | | - } |
| 324 | + if( $status->isGood() && $watch ) |
| 325 | + $user->addWatch( $this->mLocalFile->getTitle() ); |
326 | 326 | |
327 | 327 | if( $status->isGood() ) |
328 | 328 | wfRunHooks( 'UploadComplete', array( &$this ) ); |
— | — | @@ -418,12 +418,17 @@ |
419 | 419 | * @return string - full path the stashed file, or false on failure |
420 | 420 | * @access private |
421 | 421 | */ |
422 | | - function saveTempUploadedFile( $saveName, $tempName ) { |
423 | | - global $wgOut; |
| 422 | + function saveTempUploadedFile( $saveName, $tempName ) { |
424 | 423 | $repo = RepoGroup::singleton()->getLocalRepo(); |
425 | 424 | $status = $repo->storeTemp( $saveName, $tempName ); |
426 | 425 | return $status; |
427 | 426 | } |
| 427 | + /* append to a stached file */ |
| 428 | + function appendToUploadFile($srcPath, $toAppendPath ){ |
| 429 | + $repo = RepoGroup::singleton()->getLocalRepo(); |
| 430 | + $status = $repo->append($srcPath, $toAppendPath); |
| 431 | + return $status; |
| 432 | + } |
428 | 433 | |
429 | 434 | /** |
430 | 435 | * Stash a file in a temporary directory for later processing, |
— | — | @@ -435,20 +440,29 @@ |
436 | 441 | * @access private |
437 | 442 | */ |
438 | 443 | function stashSession() { |
439 | | - $status = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath ); |
| 444 | + $stash = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath ); |
440 | 445 | |
441 | | - if( !$status->isGood() ) { |
| 446 | + if( !$stash ) { |
442 | 447 | # Couldn't save the file. |
443 | 448 | return false; |
444 | 449 | } |
445 | 450 | |
446 | | - return array( |
447 | | - 'mTempPath' => $status->value, |
| 451 | + $key = $this->getSessionKey (); |
| 452 | + $_SESSION['wsUploadData'][$key] = array( |
| 453 | + 'mTempPath' => $stash, |
448 | 454 | 'mFileSize' => $this->mFileSize, |
| 455 | + 'mSrcName' => $this->mSrcName, |
449 | 456 | 'mFileProps' => $this->mFileProps, |
450 | 457 | 'version' => self::SESSION_VERSION, |
451 | 458 | ); |
| 459 | + return $key; |
452 | 460 | } |
| 461 | + //pull session Key gen from stash in cases where we want to start an upload without much information |
| 462 | + function getSessionKey(){ |
| 463 | + $key = mt_rand( 0, 0x7fffffff ); |
| 464 | + $_SESSION['wsUploadData'][$key] = array(); |
| 465 | + return $key; |
| 466 | + } |
453 | 467 | |
454 | 468 | /** |
455 | 469 | * Remove a temporarily kept file stashed by saveTempUploadedFile(). |
— | — | @@ -844,6 +858,10 @@ |
845 | 859 | return true; |
846 | 860 | |
847 | 861 | } |
| 862 | + /* allow for getAPIresult override (normally just return UploadFrom::OK to continue form processing */ |
| 863 | + function getAPIresult(){ |
| 864 | + return UploadFrom::OK; |
| 865 | + } |
848 | 866 | |
849 | 867 | /** |
850 | 868 | * Check if a user is the last uploader |
Index: branches/new-upload/phase3/languages/messages/MessagesEn.php |
— | — | @@ -823,6 +823,7 @@ |
824 | 824 | 'readonly_lag' => 'The database has been automatically locked while the slave database servers catch up to the master', |
825 | 825 | 'internalerror' => 'Internal error', |
826 | 826 | 'internalerror_info' => 'Internal error: $1', |
| 827 | +'fileappenderror' => 'Could not append $1 to $2', |
827 | 828 | 'filecopyerror' => 'Could not copy file "$1" to "$2".', |
828 | 829 | 'filerenameerror' => 'Could not rename file "$1" to "$2".', |
829 | 830 | 'filedeleteerror' => 'Could not delete file "$1".', |