MediaWiki master
AssembleUploadChunksJob.php
Go to the documentation of this file.
1<?php
22
23use Exception;
32use UnexpectedValueException;
33use UploadBase;
35use Wikimedia\ScopedCallback;
36
44 public function __construct( array $params ) {
45 parent::__construct( 'AssembleUploadChunks', $params );
46 $this->removeDuplicates = true;
47 }
48
49 public function run() {
50 $scope = RequestContext::importScopedSession( $this->params['session'] );
51 $this->addTeardownCallback( static function () use ( &$scope ) {
52 ScopedCallback::consume( $scope ); // T126450
53 } );
54
55 $logger = LoggerFactory::getInstance( 'upload' );
56 $context = RequestContext::getMain();
57 $user = $context->getUser();
58 try {
59 if ( !$user->isRegistered() ) {
60 $this->setLastError( "Could not load the author user from session." );
61
62 return false;
63 }
64
65 // TODO add some sort of proper locking maybe
66 $startingStatus = UploadBase::getSessionStatus( $user, $this->params['filekey'] );
67 if (
68 !$startingStatus ||
69 ( $startingStatus['result'] ?? '' ) !== 'Poll' ||
70 ( $startingStatus['stage'] ?? '' ) !== 'queued'
71 ) {
72 $logger->warning( "Tried to assemble upload that is in stage {stage}/{result}",
73 [
74 'stage' => $startingStatus['stage'] ?? '-',
75 'result' => $startingStatus['result'] ?? '-',
76 'status' => (string)( $startingStatus['status'] ?? '-' ),
77 'filekey' => $this->params['filekey'],
78 'filename' => $this->params['filename'],
79 'user' => $user->getName(),
80 ]
81 );
82 // If it is marked as currently in progress, abort. Otherwise
83 // assume it is some sort of replag issue or maybe a retry even
84 // though retries are impossible and just warn.
85 if (
86 $startingStatus &&
87 $startingStatus['stage'] === 'assembling' &&
88 $startingStatus['result'] !== 'Failure'
89 ) {
90 $this->setLastError( __METHOD__ . " already in progress" );
91 return false;
92 }
93 }
94 UploadBase::setSessionStatus(
95 $user,
96 $this->params['filekey'],
97 [ 'result' => 'Poll', 'stage' => 'assembling', 'status' => Status::newGood() ]
98 );
99
100 $upload = new UploadFromChunks( $user );
101 $upload->continueChunks(
102 $this->params['filename'],
103 $this->params['filekey'],
104 new WebRequestUpload( $context->getRequest(), 'null' )
105 );
106 if (
107 isset( $this->params['filesize'] ) &&
108 $this->params['filesize'] !== (int)$upload->getOffset()
109 ) {
110 // Check to make sure we are not executing prior to the API's
111 // transaction being committed. (T350917)
112 throw new UnexpectedValueException(
113 "UploadStash file size does not match job's. Potential mis-nested transaction?"
114 );
115 }
116 // Combine all of the chunks into a local file and upload that to a new stash file
117 $status = $upload->concatenateChunks();
118 if ( !$status->isGood() ) {
119 UploadBase::setSessionStatus(
120 $user,
121 $this->params['filekey'],
122 [ 'result' => 'Failure', 'stage' => 'assembling', 'status' => $status ]
123 );
124 $logger->info( "Chunked upload assembly job failed for {filekey} because {status}",
125 [
126 'filekey' => $this->params['filekey'],
127 'filename' => $this->params['filename'],
128 'user' => $user->getName(),
129 'status' => (string)$status
130 ]
131 );
132 // the chunks did not get assembled, but this should not be considered a job
133 // failure - they simply didn't pass verification for some reason, and that
134 // reason is stored in above session to inform the clients
135 return true;
136 }
137
138 // We can only get warnings like 'duplicate' after concatenating the chunks
139 $status = Status::newGood();
140 $status->value = [
141 'warnings' => UploadBase::makeWarningsSerializable(
142 $upload->checkWarnings( $user )
143 )
144 ];
145
146 // We have a new filekey for the fully concatenated file
147 $newFileKey = $upload->getStashFile()->getFileKey();
148
149 // Remove the old stash file row and first chunk file
150 // Note: This does not delete the chunks, only the stash file
151 // which is same as first chunk but with a different name.
152 $upload->stash->removeFileNoAuth( $this->params['filekey'] );
153
154 // Build the image info array while we have the local reference handy
155 $apiUpload = ApiUpload::getDummyInstance();
156 $imageInfo = $apiUpload->getUploadImageInfo( $upload );
157
158 // Cleanup any temporary local file
159 $upload->cleanupTempFile();
160
161 // Cache the info so the user doesn't have to wait forever to get the final info
162 UploadBase::setSessionStatus(
163 $user,
164 $this->params['filekey'],
165 [
166 'result' => 'Success',
167 'stage' => 'assembling',
168 'filekey' => $newFileKey,
169 'imageinfo' => $imageInfo,
170 'status' => $status
171 ]
172 );
173 $logger->info( "{filekey} successfully assembled into {newkey}",
174 [
175 'filekey' => $this->params['filekey'],
176 'newkey' => $newFileKey,
177 'filename' => $this->params['filename'],
178 'user' => $user->getName(),
179 'status' => (string)$status
180 ]
181 );
182 } catch ( Exception $e ) {
183 UploadBase::setSessionStatus(
184 $user,
185 $this->params['filekey'],
186 [
187 'result' => 'Failure',
188 'stage' => 'assembling',
189 'status' => Status::newFatal( 'api-error-stashfailed' )
190 ]
191 );
192 $this->setLastError( get_class( $e ) . ": " . $e->getMessage() );
193 // To be extra robust.
194 MWExceptionHandler::rollbackPrimaryChangesAndLog( $e );
195
196 return false;
197 }
198
199 return true;
200 }
201
202 public function getDeduplicationInfo() {
203 $info = parent::getDeduplicationInfo();
204 if ( is_array( $info['params'] ) ) {
205 $info['params'] = [ 'filekey' => $info['params']['filekey'] ];
206 }
207
208 return $info;
209 }
210
211 public function allowRetries() {
212 return false;
213 }
214}
215
217class_alias( AssembleUploadChunksJob::class, 'AssembleUploadChunksJob' );
Group all the pieces relevant to the context of a request into one instance.
Handler class for MWExceptions.
Describe and execute a background job.
Definition Job.php:41
array $params
Array of job parameters.
Definition Job.php:46
addTeardownCallback( $callback)
Definition Job.php:357
setLastError( $error)
Definition Job.php:435
Assemble the segments of a chunked upload.
allowRetries()
Whether to retry execution of this job if run() returned false or threw an exception....
getDeduplicationInfo()
Subclasses may need to override this to make duplication detection work.
Create PSR-3 logger objects.
Object to access the $_FILES array.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
UploadBase and subclasses are the backend of MediaWiki's file uploads.
cleanupTempFile()
If we've modified the upload file, then we need to manually remove it on exit to clean up.
checkWarnings( $user=null)
Check for non fatal problems with the file.
Implements uploading from chunks.
Interface for generic jobs only uses the parameters field and are JSON serializable.