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