MediaWiki
REL1_19
|
00001 <?php 00008 class UploadFromChunks extends UploadFromFile { 00009 protected $mOffset, $mChunkIndex, $mFileKey, $mVirtualTempPath; 00010 00018 public function __construct( $user = false, $stash = false, $repo = false ) { 00019 // user object. sometimes this won't exist, as when running from cron. 00020 $this->user = $user; 00021 00022 if( $repo ) { 00023 $this->repo = $repo; 00024 } else { 00025 $this->repo = RepoGroup::singleton()->getLocalRepo(); 00026 } 00027 00028 if( $stash ) { 00029 $this->stash = $stash; 00030 } else { 00031 if( $user ) { 00032 wfDebug( __METHOD__ . " creating new UploadFromChunks instance for " . $user->getId() . "\n" ); 00033 } else { 00034 wfDebug( __METHOD__ . " creating new UploadFromChunks instance with no user\n" ); 00035 } 00036 $this->stash = new UploadStash( $this->repo, $this->user ); 00037 } 00038 00039 return true; 00040 } 00046 public function stashFile() { 00047 // Stash file is the called on creating a new chunk session: 00048 $this->mChunkIndex = 0; 00049 $this->mOffset = 0; 00050 00051 $this->verifyChunk(); 00052 // Create a local stash target 00053 $this->mLocalFile = parent::stashFile(); 00054 // Update the initial file offset ( based on file size ) 00055 $this->mOffset = $this->mLocalFile->getSize(); 00056 $this->mFileKey = $this->mLocalFile->getFileKey(); 00057 00058 // Output a copy of this first to chunk 0 location: 00059 $status = $this->outputChunk( $this->mLocalFile->getPath() ); 00060 00061 // Update db table to reflect initial "chunk" state 00062 $this->updateChunkStatus(); 00063 return $this->mLocalFile; 00064 } 00065 00069 public function continueChunks( $name, $key, $webRequestUpload ) { 00070 $this->mFileKey = $key; 00071 $this->mUpload = $webRequestUpload; 00072 // Get the chunk status form the db: 00073 $this->getChunkStatus(); 00074 00075 $metadata = $this->stash->getMetadata( $key ); 00076 $this->initializePathInfo( $name, 00077 $this->getRealPath( $metadata['us_path'] ), 00078 $metadata['us_size'], 00079 false 00080 ); 00081 } 00082 00087 public function concatenateChunks() { 00088 wfDebug( __METHOD__ . " concatenate {$this->mChunkIndex} chunks:" . 00089 $this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" ); 00090 00091 // Concatenate all the chunks to mVirtualTempPath 00092 $fileList = Array(); 00093 // The first chunk is stored at the mVirtualTempPath path so we start on "chunk 1" 00094 for( $i = 0; $i <= $this->getChunkIndex(); $i++ ){ 00095 $fileList[] = $this->getVirtualChunkLocation( $i ); 00096 } 00097 00098 // Get the file extension from the last chunk 00099 $ext = FileBackend::extensionFromPath( $this->mVirtualTempPath ); 00100 // Get a 0-byte temp file to perform the concatenation at 00101 $tmpFile = TempFSFile::factory( 'chunkedupload_', $ext ); 00102 $tmpPath = $tmpFile 00103 ? $tmpFile->getPath() 00104 : false; // fail in concatenate() 00105 // Concatenate the chunks at the temp file 00106 $status = $this->repo->concatenate( $fileList, $tmpPath, FileRepo::DELETE_SOURCE ); 00107 if( !$status->isOk() ){ 00108 return $status; 00109 } 00110 00111 $this->mTempPath = $tmpPath; // file system path 00112 $this->mFileSize = filesize( $this->mTempPath ); //Since this was set for the last chunk previously 00113 $ret = $this->verifyUpload(); 00114 if ( $ret['status'] !== UploadBase::OK ) { 00115 wfDebugLog( 'fileconcatenate', "Verification failed for chunked upload" ); 00116 $status->fatal( $this->getVerificationErrorCode( $ret['status'] ) ); 00117 return $status; 00118 } 00119 00120 // Update the mTempPath and mLocalFile 00121 // ( for FileUpload or normal Stash to take over ) 00122 $this->mLocalFile = parent::stashFile(); 00123 00124 return $status; 00125 } 00126 00135 public function performUpload( $comment, $pageText, $watch, $user ) { 00136 $rv = parent::performUpload( $comment, $pageText, $watch, $user ); 00137 return $rv; 00138 } 00139 00144 function getVirtualChunkLocation( $index ){ 00145 return $this->repo->getVirtualUrl( 'temp' ) . 00146 '/' . 00147 $this->repo->getHashPath( 00148 $this->getChunkFileKey( $index ) 00149 ) . 00150 $this->getChunkFileKey( $index ); 00151 } 00160 public function addChunk( $chunkPath, $chunkSize, $offset ) { 00161 // Get the offset before we add the chunk to the file system 00162 $preAppendOffset = $this->getOffset(); 00163 00164 if ( $preAppendOffset + $chunkSize > $this->getMaxUploadSize()) { 00165 $status = Status::newFatal( 'file-too-large' ); 00166 } else { 00167 // Make sure the client is uploading the correct chunk with a matching offset. 00168 if ( $preAppendOffset == $offset ) { 00169 // Update local chunk index for the current chunk 00170 $this->mChunkIndex++; 00171 try { 00172 # For some reason mTempPath is set to first part 00173 $oldTemp = $this->mTempPath; 00174 $this->mTempPath = $chunkPath; 00175 $this->verifyChunk(); 00176 $this->mTempPath = $oldTemp; 00177 } catch ( UploadChunkVerificationException $e ) { 00178 return Status::newFatal( $e->getMessage() ); 00179 } 00180 $status = $this->outputChunk( $chunkPath ); 00181 if( $status->isGood() ){ 00182 // Update local offset: 00183 $this->mOffset = $preAppendOffset + $chunkSize; 00184 // Update chunk table status db 00185 $this->updateChunkStatus(); 00186 } 00187 } else { 00188 $status = Status::newFatal( 'invalid-chunk-offset' ); 00189 } 00190 } 00191 return $status; 00192 } 00193 00197 private function updateChunkStatus(){ 00198 wfDebug( __METHOD__ . " update chunk status for {$this->mFileKey} offset:" . 00199 $this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" ); 00200 00201 $dbw = $this->repo->getMasterDb(); 00202 $dbw->update( 00203 'uploadstash', 00204 array( 00205 'us_status' => 'chunks', 00206 'us_chunk_inx' => $this->getChunkIndex(), 00207 'us_size' => $this->getOffset() 00208 ), 00209 array( 'us_key' => $this->mFileKey ), 00210 __METHOD__ 00211 ); 00212 } 00216 private function getChunkStatus(){ 00217 // get Master db to avoid race conditions. 00218 // Otherwise, if chunk upload time < replag there will be spurious errors 00219 $dbw = $this->repo->getMasterDb(); 00220 $row = $dbw->selectRow( 00221 'uploadstash', 00222 array( 00223 'us_chunk_inx', 00224 'us_size', 00225 'us_path', 00226 ), 00227 array( 'us_key' => $this->mFileKey ), 00228 __METHOD__ 00229 ); 00230 // Handle result: 00231 if ( $row ) { 00232 $this->mChunkIndex = $row->us_chunk_inx; 00233 $this->mOffset = $row->us_size; 00234 $this->mVirtualTempPath = $row->us_path; 00235 } 00236 } 00241 private function getChunkIndex(){ 00242 if( $this->mChunkIndex !== null ){ 00243 return $this->mChunkIndex; 00244 } 00245 return 0; 00246 } 00247 00252 private function getOffset(){ 00253 if ( $this->mOffset !== null ){ 00254 return $this->mOffset; 00255 } 00256 return 0; 00257 } 00258 00264 private function outputChunk( $chunkPath ){ 00265 // Key is fileKey + chunk index 00266 $fileKey = $this->getChunkFileKey(); 00267 00268 // Store the chunk per its indexed fileKey: 00269 $hashPath = $this->repo->getHashPath( $fileKey ); 00270 $storeStatus = $this->repo->store( $chunkPath, 'temp', "$hashPath$fileKey" ); 00271 00272 // Check for error in stashing the chunk: 00273 if ( ! $storeStatus->isOK() ) { 00274 $error = $storeStatus->getErrorsArray(); 00275 $error = reset( $error ); 00276 if ( ! count( $error ) ) { 00277 $error = $storeStatus->getWarningsArray(); 00278 $error = reset( $error ); 00279 if ( ! count( $error ) ) { 00280 $error = array( 'unknown', 'no error recorded' ); 00281 } 00282 } 00283 throw new UploadChunkFileException( "error storing file in '$chunkPath': " . implode( '; ', $error ) ); 00284 } 00285 return $storeStatus; 00286 } 00287 private function getChunkFileKey( $index = null ){ 00288 if( $index === null ){ 00289 $index = $this->getChunkIndex(); 00290 } 00291 return $this->mFileKey . '.' . $index ; 00292 } 00293 00299 private function verifyChunk() { 00300 // Rest mDesiredDestName here so we verify the name as if it were mFileKey 00301 $oldDesiredDestName = $this->mDesiredDestName; 00302 $this->mDesiredDestName = $this->mFileKey; 00303 $this->mTitle = false; 00304 $res = $this->verifyPartialFile(); 00305 $this->mDesiredDestName = $oldDesiredDestName; 00306 $this->mTitle = false; 00307 if( is_array( $res ) ) { 00308 throw new UploadChunkVerificationException( $res[0] ); 00309 } 00310 } 00311 } 00312 00313 class UploadChunkZeroLengthFileException extends MWException {}; 00314 class UploadChunkFileException extends MWException {}; 00315 class UploadChunkVerificationException extends MWException {};