Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
| 1 | <?php |
||
| 30 | class UploadFromChunks extends UploadFromFile { |
||
| 31 | protected $mOffset; |
||
| 32 | protected $mChunkIndex; |
||
| 33 | protected $mFileKey; |
||
| 34 | protected $mVirtualTempPath; |
||
| 35 | /** @var LocalRepo */ |
||
| 36 | private $repo; |
||
| 37 | |||
| 38 | /** |
||
| 39 | * Setup local pointers to stash, repo and user (similar to UploadFromStash) |
||
| 40 | * |
||
| 41 | * @param User $user |
||
| 42 | * @param UploadStash|bool $stash Default: false |
||
| 43 | * @param FileRepo|bool $repo Default: false |
||
| 44 | */ |
||
| 45 | View Code Duplication | public function __construct( User $user, $stash = false, $repo = false ) { |
|
| 46 | $this->user = $user; |
||
|
|
|||
| 47 | |||
| 48 | if ( $repo ) { |
||
| 49 | $this->repo = $repo; |
||
| 50 | } else { |
||
| 51 | $this->repo = RepoGroup::singleton()->getLocalRepo(); |
||
| 52 | } |
||
| 53 | |||
| 54 | if ( $stash ) { |
||
| 55 | $this->stash = $stash; |
||
| 56 | } else { |
||
| 57 | if ( $user ) { |
||
| 58 | wfDebug( __METHOD__ . " creating new UploadFromChunks instance for " . $user->getId() . "\n" ); |
||
| 59 | } else { |
||
| 60 | wfDebug( __METHOD__ . " creating new UploadFromChunks instance with no user\n" ); |
||
| 61 | } |
||
| 62 | $this->stash = new UploadStash( $this->repo, $this->user ); |
||
| 63 | } |
||
| 64 | } |
||
| 65 | |||
| 66 | /** |
||
| 67 | * Calls the parent doStashFile and updates the uploadsession table to handle "chunks" |
||
| 68 | * |
||
| 69 | * @param User|null $user |
||
| 70 | * @return UploadStashFile Stashed file |
||
| 71 | */ |
||
| 72 | protected function doStashFile( User $user = null ) { |
||
| 73 | // Stash file is the called on creating a new chunk session: |
||
| 74 | $this->mChunkIndex = 0; |
||
| 75 | $this->mOffset = 0; |
||
| 76 | |||
| 77 | $this->verifyChunk(); |
||
| 78 | // Create a local stash target |
||
| 79 | $this->mStashFile = parent::doStashFile( $user ); |
||
| 80 | // Update the initial file offset (based on file size) |
||
| 81 | $this->mOffset = $this->mStashFile->getSize(); |
||
| 82 | $this->mFileKey = $this->mStashFile->getFileKey(); |
||
| 83 | |||
| 84 | // Output a copy of this first to chunk 0 location: |
||
| 85 | $this->outputChunk( $this->mStashFile->getPath() ); |
||
| 86 | |||
| 87 | // Update db table to reflect initial "chunk" state |
||
| 88 | $this->updateChunkStatus(); |
||
| 89 | |||
| 90 | return $this->mStashFile; |
||
| 91 | } |
||
| 92 | |||
| 93 | /** |
||
| 94 | * Continue chunk uploading |
||
| 95 | * |
||
| 96 | * @param string $name |
||
| 97 | * @param string $key |
||
| 98 | * @param WebRequestUpload $webRequestUpload |
||
| 99 | */ |
||
| 100 | public function continueChunks( $name, $key, $webRequestUpload ) { |
||
| 113 | |||
| 114 | /** |
||
| 115 | * Append the final chunk and ready file for parent::performUpload() |
||
| 116 | * @return FileRepoStatus |
||
| 117 | */ |
||
| 118 | public function concatenateChunks() { |
||
| 119 | $chunkIndex = $this->getChunkIndex(); |
||
| 120 | wfDebug( __METHOD__ . " concatenate {$this->mChunkIndex} chunks:" . |
||
| 121 | $this->getOffset() . ' inx:' . $chunkIndex . "\n" ); |
||
| 122 | |||
| 123 | // Concatenate all the chunks to mVirtualTempPath |
||
| 124 | $fileList = []; |
||
| 125 | // The first chunk is stored at the mVirtualTempPath path so we start on "chunk 1" |
||
| 126 | for ( $i = 0; $i <= $chunkIndex; $i++ ) { |
||
| 127 | $fileList[] = $this->getVirtualChunkLocation( $i ); |
||
| 128 | } |
||
| 129 | |||
| 130 | // Get the file extension from the last chunk |
||
| 131 | $ext = FileBackend::extensionFromPath( $this->mVirtualTempPath ); |
||
| 132 | // Get a 0-byte temp file to perform the concatenation at |
||
| 133 | $tmpFile = TempFSFile::factory( 'chunkedupload_', $ext, wfTempDir() ); |
||
| 134 | $tmpPath = false; // fail in concatenate() |
||
| 135 | if ( $tmpFile ) { |
||
| 136 | // keep alive with $this |
||
| 137 | $tmpPath = $tmpFile->bind( $this )->getPath(); |
||
| 138 | } |
||
| 139 | |||
| 140 | // Concatenate the chunks at the temp file |
||
| 141 | $tStart = microtime( true ); |
||
| 142 | $status = $this->repo->concatenate( $fileList, $tmpPath, FileRepo::DELETE_SOURCE ); |
||
| 143 | $tAmount = microtime( true ) - $tStart; |
||
| 144 | if ( !$status->isOK() ) { |
||
| 145 | return $status; |
||
| 146 | } |
||
| 147 | |||
| 148 | wfDebugLog( 'fileconcatenate', "Combined $i chunks in $tAmount seconds." ); |
||
| 149 | |||
| 150 | // File system path of the actual full temp file |
||
| 151 | $this->setTempFile( $tmpPath ); |
||
| 152 | |||
| 153 | $ret = $this->verifyUpload(); |
||
| 154 | if ( $ret['status'] !== UploadBase::OK ) { |
||
| 155 | wfDebugLog( 'fileconcatenate', "Verification failed for chunked upload" ); |
||
| 156 | $status->fatal( $this->getVerificationErrorCode( $ret['status'] ) ); |
||
| 157 | |||
| 158 | return $status; |
||
| 159 | } |
||
| 160 | |||
| 161 | // Update the mTempPath and mStashFile |
||
| 162 | // (for FileUpload or normal Stash to take over) |
||
| 163 | $tStart = microtime( true ); |
||
| 164 | // This is a re-implementation of UploadBase::tryStashFile(), we can't call it because we |
||
| 165 | // override doStashFile() with completely different functionality in this class... |
||
| 166 | $error = $this->runUploadStashFileHook( $this->user ); |
||
| 167 | if ( $error ) { |
||
| 168 | call_user_func_array( [ $status, 'fatal' ], $error ); |
||
| 169 | return $status; |
||
| 170 | } |
||
| 171 | try { |
||
| 172 | $this->mStashFile = parent::doStashFile( $this->user ); |
||
| 173 | } catch ( UploadStashException $e ) { |
||
| 174 | $status->fatal( 'uploadstash-exception', get_class( $e ), $e->getMessage() ); |
||
| 175 | return $status; |
||
| 176 | } |
||
| 177 | |||
| 178 | $tAmount = microtime( true ) - $tStart; |
||
| 179 | $this->mStashFile->setLocalReference( $tmpFile ); // reuse (e.g. for getImageInfo()) |
||
| 180 | wfDebugLog( 'fileconcatenate', "Stashed combined file ($i chunks) in $tAmount seconds." ); |
||
| 181 | |||
| 182 | return $status; |
||
| 183 | } |
||
| 184 | |||
| 185 | /** |
||
| 186 | * Returns the virtual chunk location: |
||
| 187 | * @param int $index |
||
| 188 | * @return string |
||
| 189 | */ |
||
| 190 | function getVirtualChunkLocation( $index ) { |
||
| 198 | |||
| 199 | /** |
||
| 200 | * Add a chunk to the temporary directory |
||
| 201 | * |
||
| 202 | * @param string $chunkPath Path to temporary chunk file |
||
| 203 | * @param int $chunkSize Size of the current chunk |
||
| 204 | * @param int $offset Offset of current chunk ( mutch match database chunk offset ) |
||
| 205 | * @return Status |
||
| 206 | */ |
||
| 207 | public function addChunk( $chunkPath, $chunkSize, $offset ) { |
||
| 241 | |||
| 242 | /** |
||
| 243 | * Update the chunk db table with the current status: |
||
| 244 | */ |
||
| 245 | private function updateChunkStatus() { |
||
| 246 | wfDebug( __METHOD__ . " update chunk status for {$this->mFileKey} offset:" . |
||
| 247 | $this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" ); |
||
| 248 | |||
| 249 | $dbw = $this->repo->getMasterDB(); |
||
| 250 | $dbw->update( |
||
| 251 | 'uploadstash', |
||
| 252 | [ |
||
| 253 | 'us_status' => 'chunks', |
||
| 254 | 'us_chunk_inx' => $this->getChunkIndex(), |
||
| 255 | 'us_size' => $this->getOffset() |
||
| 256 | ], |
||
| 257 | [ 'us_key' => $this->mFileKey ], |
||
| 258 | __METHOD__ |
||
| 259 | ); |
||
| 260 | } |
||
| 261 | |||
| 262 | /** |
||
| 263 | * Get the chunk db state and populate update relevant local values |
||
| 264 | */ |
||
| 265 | private function getChunkStatus() { |
||
| 286 | |||
| 287 | /** |
||
| 288 | * Get the current Chunk index |
||
| 289 | * @return int Index of the current chunk |
||
| 290 | */ |
||
| 291 | private function getChunkIndex() { |
||
| 298 | |||
| 299 | /** |
||
| 300 | * Get the offset at which the next uploaded chunk will be appended to |
||
| 301 | * @return int Current byte offset of the chunk file set |
||
| 302 | */ |
||
| 303 | public function getOffset() { |
||
| 310 | |||
| 311 | /** |
||
| 312 | * Output the chunk to disk |
||
| 313 | * |
||
| 314 | * @param string $chunkPath |
||
| 315 | * @throws UploadChunkFileException |
||
| 316 | * @return FileRepoStatus |
||
| 317 | */ |
||
| 318 | private function outputChunk( $chunkPath ) { |
||
| 344 | |||
| 345 | private function getChunkFileKey( $index = null ) { |
||
| 352 | |||
| 353 | /** |
||
| 354 | * Verify that the chunk isn't really an evil html file |
||
| 355 | * |
||
| 356 | * @throws UploadChunkVerificationException |
||
| 357 | */ |
||
| 358 | private function verifyChunk() { |
||
| 370 | } |
||
| 371 | |||
| 372 | class UploadChunkZeroLengthFileException extends MWException { |
||
| 373 | } |
||
| 374 | |||
| 380 |
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: