MediaWiki
REL1_20
|
00001 <?php 00030 class UploadFromChunks extends UploadFromFile { 00031 protected $mOffset, $mChunkIndex, $mFileKey, $mVirtualTempPath; 00032 00040 public function __construct( $user = false, $stash = false, $repo = false ) { 00041 // user object. sometimes this won't exist, as when running from cron. 00042 $this->user = $user; 00043 00044 if( $repo ) { 00045 $this->repo = $repo; 00046 } else { 00047 $this->repo = RepoGroup::singleton()->getLocalRepo(); 00048 } 00049 00050 if( $stash ) { 00051 $this->stash = $stash; 00052 } else { 00053 if( $user ) { 00054 wfDebug( __METHOD__ . " creating new UploadFromChunks instance for " . $user->getId() . "\n" ); 00055 } else { 00056 wfDebug( __METHOD__ . " creating new UploadFromChunks instance with no user\n" ); 00057 } 00058 $this->stash = new UploadStash( $this->repo, $this->user ); 00059 } 00060 00061 return true; 00062 } 00068 public function stashFile() { 00069 // Stash file is the called on creating a new chunk session: 00070 $this->mChunkIndex = 0; 00071 $this->mOffset = 0; 00072 00073 $this->verifyChunk(); 00074 // Create a local stash target 00075 $this->mLocalFile = parent::stashFile(); 00076 // Update the initial file offset ( based on file size ) 00077 $this->mOffset = $this->mLocalFile->getSize(); 00078 $this->mFileKey = $this->mLocalFile->getFileKey(); 00079 00080 // Output a copy of this first to chunk 0 location: 00081 $status = $this->outputChunk( $this->mLocalFile->getPath() ); 00082 00083 // Update db table to reflect initial "chunk" state 00084 $this->updateChunkStatus(); 00085 return $this->mLocalFile; 00086 } 00087 00091 public function continueChunks( $name, $key, $webRequestUpload ) { 00092 $this->mFileKey = $key; 00093 $this->mUpload = $webRequestUpload; 00094 // Get the chunk status form the db: 00095 $this->getChunkStatus(); 00096 00097 $metadata = $this->stash->getMetadata( $key ); 00098 $this->initializePathInfo( $name, 00099 $this->getRealPath( $metadata['us_path'] ), 00100 $metadata['us_size'], 00101 false 00102 ); 00103 } 00104 00109 public function concatenateChunks() { 00110 wfDebug( __METHOD__ . " concatenate {$this->mChunkIndex} chunks:" . 00111 $this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" ); 00112 00113 // Concatenate all the chunks to mVirtualTempPath 00114 $fileList = Array(); 00115 // The first chunk is stored at the mVirtualTempPath path so we start on "chunk 1" 00116 for( $i = 0; $i <= $this->getChunkIndex(); $i++ ){ 00117 $fileList[] = $this->getVirtualChunkLocation( $i ); 00118 } 00119 00120 // Get the file extension from the last chunk 00121 $ext = FileBackend::extensionFromPath( $this->mVirtualTempPath ); 00122 // Get a 0-byte temp file to perform the concatenation at 00123 $tmpFile = TempFSFile::factory( 'chunkedupload_', $ext ); 00124 $tmpPath = $tmpFile 00125 ? $tmpFile->getPath() 00126 : false; // fail in concatenate() 00127 // Concatenate the chunks at the temp file 00128 $status = $this->repo->concatenate( $fileList, $tmpPath, FileRepo::DELETE_SOURCE ); 00129 if( !$status->isOk() ){ 00130 return $status; 00131 } 00132 00133 $this->mTempPath = $tmpPath; // file system path 00134 $this->mFileSize = filesize( $this->mTempPath ); //Since this was set for the last chunk previously 00135 $ret = $this->verifyUpload(); 00136 if ( $ret['status'] !== UploadBase::OK ) { 00137 wfDebugLog( 'fileconcatenate', "Verification failed for chunked upload" ); 00138 $status->fatal( $this->getVerificationErrorCode( $ret['status'] ) ); 00139 return $status; 00140 } 00141 00142 // Update the mTempPath and mLocalFile 00143 // ( for FileUpload or normal Stash to take over ) 00144 $this->mLocalFile = parent::stashFile(); 00145 00146 return $status; 00147 } 00148 00157 public function performUpload( $comment, $pageText, $watch, $user ) { 00158 $rv = parent::performUpload( $comment, $pageText, $watch, $user ); 00159 return $rv; 00160 } 00161 00167 function getVirtualChunkLocation( $index ){ 00168 return $this->repo->getVirtualUrl( 'temp' ) . 00169 '/' . 00170 $this->repo->getHashPath( 00171 $this->getChunkFileKey( $index ) 00172 ) . 00173 $this->getChunkFileKey( $index ); 00174 } 00175 00184 public function addChunk( $chunkPath, $chunkSize, $offset ) { 00185 // Get the offset before we add the chunk to the file system 00186 $preAppendOffset = $this->getOffset(); 00187 00188 if ( $preAppendOffset + $chunkSize > $this->getMaxUploadSize()) { 00189 $status = Status::newFatal( 'file-too-large' ); 00190 } else { 00191 // Make sure the client is uploading the correct chunk with a matching offset. 00192 if ( $preAppendOffset == $offset ) { 00193 // Update local chunk index for the current chunk 00194 $this->mChunkIndex++; 00195 try { 00196 # For some reason mTempPath is set to first part 00197 $oldTemp = $this->mTempPath; 00198 $this->mTempPath = $chunkPath; 00199 $this->verifyChunk(); 00200 $this->mTempPath = $oldTemp; 00201 } catch ( UploadChunkVerificationException $e ) { 00202 return Status::newFatal( $e->getMessage() ); 00203 } 00204 $status = $this->outputChunk( $chunkPath ); 00205 if( $status->isGood() ){ 00206 // Update local offset: 00207 $this->mOffset = $preAppendOffset + $chunkSize; 00208 // Update chunk table status db 00209 $this->updateChunkStatus(); 00210 } 00211 } else { 00212 $status = Status::newFatal( 'invalid-chunk-offset' ); 00213 } 00214 } 00215 return $status; 00216 } 00217 00221 private function updateChunkStatus(){ 00222 wfDebug( __METHOD__ . " update chunk status for {$this->mFileKey} offset:" . 00223 $this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" ); 00224 00225 $dbw = $this->repo->getMasterDb(); 00226 $dbw->update( 00227 'uploadstash', 00228 array( 00229 'us_status' => 'chunks', 00230 'us_chunk_inx' => $this->getChunkIndex(), 00231 'us_size' => $this->getOffset() 00232 ), 00233 array( 'us_key' => $this->mFileKey ), 00234 __METHOD__ 00235 ); 00236 } 00237 00241 private function getChunkStatus(){ 00242 // get Master db to avoid race conditions. 00243 // Otherwise, if chunk upload time < replag there will be spurious errors 00244 $dbw = $this->repo->getMasterDb(); 00245 $row = $dbw->selectRow( 00246 'uploadstash', 00247 array( 00248 'us_chunk_inx', 00249 'us_size', 00250 'us_path', 00251 ), 00252 array( 'us_key' => $this->mFileKey ), 00253 __METHOD__ 00254 ); 00255 // Handle result: 00256 if ( $row ) { 00257 $this->mChunkIndex = $row->us_chunk_inx; 00258 $this->mOffset = $row->us_size; 00259 $this->mVirtualTempPath = $row->us_path; 00260 } 00261 } 00262 00267 private function getChunkIndex(){ 00268 if( $this->mChunkIndex !== null ){ 00269 return $this->mChunkIndex; 00270 } 00271 return 0; 00272 } 00273 00278 private function getOffset(){ 00279 if ( $this->mOffset !== null ){ 00280 return $this->mOffset; 00281 } 00282 return 0; 00283 } 00284 00292 private function outputChunk( $chunkPath ){ 00293 // Key is fileKey + chunk index 00294 $fileKey = $this->getChunkFileKey(); 00295 00296 // Store the chunk per its indexed fileKey: 00297 $hashPath = $this->repo->getHashPath( $fileKey ); 00298 $storeStatus = $this->repo->quickImport( $chunkPath, 00299 $this->repo->getZonePath( 'temp' ) . "/{$hashPath}{$fileKey}" ); 00300 00301 // Check for error in stashing the chunk: 00302 if ( ! $storeStatus->isOK() ) { 00303 $error = $storeStatus->getErrorsArray(); 00304 $error = reset( $error ); 00305 if ( ! count( $error ) ) { 00306 $error = $storeStatus->getWarningsArray(); 00307 $error = reset( $error ); 00308 if ( ! count( $error ) ) { 00309 $error = array( 'unknown', 'no error recorded' ); 00310 } 00311 } 00312 throw new UploadChunkFileException( "error storing file in '$chunkPath': " . implode( '; ', $error ) ); 00313 } 00314 return $storeStatus; 00315 } 00316 00317 private function getChunkFileKey( $index = null ){ 00318 if( $index === null ){ 00319 $index = $this->getChunkIndex(); 00320 } 00321 return $this->mFileKey . '.' . $index ; 00322 } 00323 00329 private function verifyChunk() { 00330 // Rest mDesiredDestName here so we verify the name as if it were mFileKey 00331 $oldDesiredDestName = $this->mDesiredDestName; 00332 $this->mDesiredDestName = $this->mFileKey; 00333 $this->mTitle = false; 00334 $res = $this->verifyPartialFile(); 00335 $this->mDesiredDestName = $oldDesiredDestName; 00336 $this->mTitle = false; 00337 if( is_array( $res ) ) { 00338 throw new UploadChunkVerificationException( $res[0] ); 00339 } 00340 } 00341 } 00342 00343 class UploadChunkZeroLengthFileException extends MWException {}; 00344 class UploadChunkFileException extends MWException {}; 00345 class UploadChunkVerificationException extends MWException {};