[Upload] Moved async upload stuff to the job queue.
* (bug 44080) Also carry-over the IP and HTTP header info. * This adds a RequestContext::importScopedSession() function. Change-Id: Ie9c0a4d78fb719569c8149b9cc8a5430f0ac5673
This commit is contained in:
parent
3e06fef308
commit
fbf34d84ab
7 changed files with 197 additions and 114 deletions
|
|
@ -675,6 +675,8 @@ $wgAutoloadLocalClasses = array(
|
||||||
'RefreshLinksJob' => 'includes/job/jobs/RefreshLinksJob.php',
|
'RefreshLinksJob' => 'includes/job/jobs/RefreshLinksJob.php',
|
||||||
'RefreshLinksJob2' => 'includes/job/jobs/RefreshLinksJob.php',
|
'RefreshLinksJob2' => 'includes/job/jobs/RefreshLinksJob.php',
|
||||||
'UploadFromUrlJob' => 'includes/job/jobs/UploadFromUrlJob.php',
|
'UploadFromUrlJob' => 'includes/job/jobs/UploadFromUrlJob.php',
|
||||||
|
'AssembleUploadChunksJob' => 'includes/job/jobs/AssembleUploadChunksJob.php',
|
||||||
|
'PublishStashedFileJob' => 'includes/job/jobs/PublishStashedFileJob.php',
|
||||||
|
|
||||||
# includes/json
|
# includes/json
|
||||||
'FormatJson' => 'includes/json/FormatJson.php',
|
'FormatJson' => 'includes/json/FormatJson.php',
|
||||||
|
|
|
||||||
|
|
@ -311,6 +311,13 @@ $wgUploadStashMaxAge = 6 * 3600; // 6 hours
|
||||||
/** Allows to move images and other media files */
|
/** Allows to move images and other media files */
|
||||||
$wgAllowImageMoving = true;
|
$wgAllowImageMoving = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable deferred upload tasks that use the job queue.
|
||||||
|
* Only enable this if job runners are set up for both the
|
||||||
|
* 'AssembleUploadChunks' and 'PublishStashedFile' job types.
|
||||||
|
*/
|
||||||
|
$wgEnableAsyncUploads = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* These are additional characters that should be replaced with '-' in filenames
|
* These are additional characters that should be replaced with '-' in filenames
|
||||||
*/
|
*/
|
||||||
|
|
@ -5513,6 +5520,8 @@ $wgJobClasses = array(
|
||||||
'enotifNotify' => 'EnotifNotifyJob',
|
'enotifNotify' => 'EnotifNotifyJob',
|
||||||
'fixDoubleRedirect' => 'DoubleRedirectJob',
|
'fixDoubleRedirect' => 'DoubleRedirectJob',
|
||||||
'uploadFromUrl' => 'UploadFromUrlJob',
|
'uploadFromUrl' => 'UploadFromUrlJob',
|
||||||
|
'AssembleUploadChunks' => 'AssembleUploadChunksJob',
|
||||||
|
'PublishStashedFile' => 'PublishStashedFileJob',
|
||||||
'null' => 'NullJob'
|
'null' => 'NullJob'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -5526,7 +5535,7 @@ $wgJobClasses = array(
|
||||||
* - Jobs that you want to run on specialized machines ( like transcoding, or a particular
|
* - Jobs that you want to run on specialized machines ( like transcoding, or a particular
|
||||||
* machine on your cluster has 'outside' web access you could restrict uploadFromUrl )
|
* machine on your cluster has 'outside' web access you could restrict uploadFromUrl )
|
||||||
*/
|
*/
|
||||||
$wgJobTypesExcludedFromDefaultQueue = array();
|
$wgJobTypesExcludedFromDefaultQueue = array( 'AssembleUploadChunks', 'PublishStashedFile' );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of job types to configuration arrays.
|
* Map of job types to configuration arrays.
|
||||||
|
|
@ -6200,7 +6209,7 @@ $wgMaxShellTime = 180;
|
||||||
$wgMaxShellWallClockTime = 180;
|
$wgMaxShellWallClockTime = 180;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Under Linux: a cgroup directory used to constrain memory usage of shell
|
* Under Linux: a cgroup directory used to constrain memory usage of shell
|
||||||
* commands. The directory must be writable by the user which runs MediaWiki.
|
* commands. The directory must be writable by the user which runs MediaWiki.
|
||||||
*
|
*
|
||||||
* If specified, this is used instead of ulimit, which is inaccurate, and
|
* If specified, this is used instead of ulimit, which is inaccurate, and
|
||||||
|
|
@ -6208,7 +6217,7 @@ $wgMaxShellWallClockTime = 180;
|
||||||
* them segfault or deadlock.
|
* them segfault or deadlock.
|
||||||
*
|
*
|
||||||
* A wrapper script will create a cgroup for each shell command that runs, as
|
* A wrapper script will create a cgroup for each shell command that runs, as
|
||||||
* a subgroup of the specified cgroup. If the memory limit is exceeded, the
|
* a subgroup of the specified cgroup. If the memory limit is exceeded, the
|
||||||
* kernel will send a SIGKILL signal to a process in the subgroup.
|
* kernel will send a SIGKILL signal to a process in the subgroup.
|
||||||
*
|
*
|
||||||
* @par Example:
|
* @par Example:
|
||||||
|
|
@ -6218,7 +6227,7 @@ $wgMaxShellWallClockTime = 180;
|
||||||
* echo '$wgShellCgroup = "/sys/fs/cgroup/memory/mediawiki/job";' >> LocalSettings.php
|
* echo '$wgShellCgroup = "/sys/fs/cgroup/memory/mediawiki/job";' >> LocalSettings.php
|
||||||
* @endcode
|
* @endcode
|
||||||
*
|
*
|
||||||
* The reliability of cgroup cleanup can be improved by installing a
|
* The reliability of cgroup cleanup can be improved by installing a
|
||||||
* notify_on_release script in the root cgroup, see e.g.
|
* notify_on_release script in the root cgroup, see e.g.
|
||||||
* https://gerrit.wikimedia.org/r/#/c/40784
|
* https://gerrit.wikimedia.org/r/#/c/40784
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1124,6 +1124,30 @@ HTML;
|
||||||
$this->ip = $ip;
|
$this->ip = $ip;
|
||||||
return $ip;
|
return $ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $ip
|
||||||
|
* @return void
|
||||||
|
* @since 1.21
|
||||||
|
*/
|
||||||
|
public function setIP( $ip ) {
|
||||||
|
$this->ip = $ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export the resolved user IP, HTTP headers, and session ID.
|
||||||
|
* The result will be reasonably sized to allow for serialization.
|
||||||
|
*
|
||||||
|
* @return Array
|
||||||
|
* @since 1.21
|
||||||
|
*/
|
||||||
|
public function exportUserSession() {
|
||||||
|
return array(
|
||||||
|
'ip' => $this->getIP(),
|
||||||
|
'headers' => $this->getAllHeaders(),
|
||||||
|
'sessionId' => session_id()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1263,8 +1287,9 @@ class FauxRequest extends WebRequest {
|
||||||
throw new MWException( "FauxRequest() got bogus data" );
|
throw new MWException( "FauxRequest() got bogus data" );
|
||||||
}
|
}
|
||||||
$this->wasPosted = $wasPosted;
|
$this->wasPosted = $wasPosted;
|
||||||
if( $session )
|
if( $session ) {
|
||||||
$this->session = $session;
|
$this->session = $session;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ class ApiUpload extends ApiBase {
|
||||||
protected $mParams;
|
protected $mParams;
|
||||||
|
|
||||||
public function execute() {
|
public function execute() {
|
||||||
|
global $wgEnableAsyncUploads;
|
||||||
|
|
||||||
// Check whether upload is enabled
|
// Check whether upload is enabled
|
||||||
if ( !UploadBase::isEnabled() ) {
|
if ( !UploadBase::isEnabled() ) {
|
||||||
$this->dieUsageMsg( 'uploaddisabled' );
|
$this->dieUsageMsg( 'uploaddisabled' );
|
||||||
|
|
@ -47,9 +49,8 @@ class ApiUpload extends ApiBase {
|
||||||
// Parameter handling
|
// Parameter handling
|
||||||
$this->mParams = $this->extractRequestParams();
|
$this->mParams = $this->extractRequestParams();
|
||||||
$request = $this->getMain()->getRequest();
|
$request = $this->getMain()->getRequest();
|
||||||
// Check if async mode is actually supported
|
// Check if async mode is actually supported (jobs done in cli mode)
|
||||||
$this->mParams['async'] = ( $this->mParams['async'] && !wfIsWindows() );
|
$this->mParams['async'] = ( $this->mParams['async'] && $wgEnableAsyncUploads );
|
||||||
$this->mParams['async'] = false; // XXX: disabled per bug 44080
|
|
||||||
// Add the uploaded file to the params array
|
// Add the uploaded file to the params array
|
||||||
$this->mParams['file'] = $request->getFileName( 'file' );
|
$this->mParams['file'] = $request->getFileName( 'file' );
|
||||||
$this->mParams['chunk'] = $request->getFileName( 'chunk' );
|
$this->mParams['chunk'] = $request->getFileName( 'chunk' );
|
||||||
|
|
@ -205,8 +206,8 @@ class ApiUpload extends ApiBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check we added the last chunk:
|
// Check we added the last chunk:
|
||||||
if( $this->mParams['offset'] + $chunkSize == $this->mParams['filesize'] ) {
|
if ( $this->mParams['offset'] + $chunkSize == $this->mParams['filesize'] ) {
|
||||||
if ( $this->mParams['async'] && !wfIsWindows() ) {
|
if ( $this->mParams['async'] ) {
|
||||||
$progress = UploadBase::getSessionStatus( $this->mParams['filekey'] );
|
$progress = UploadBase::getSessionStatus( $this->mParams['filekey'] );
|
||||||
if ( $progress && $progress['result'] === 'Poll' ) {
|
if ( $progress && $progress['result'] === 'Poll' ) {
|
||||||
$this->dieUsage( "Chunk assembly already in progress.", 'stashfailed' );
|
$this->dieUsage( "Chunk assembly already in progress.", 'stashfailed' );
|
||||||
|
|
@ -216,22 +217,16 @@ class ApiUpload extends ApiBase {
|
||||||
array( 'result' => 'Poll',
|
array( 'result' => 'Poll',
|
||||||
'stage' => 'queued', 'status' => Status::newGood() )
|
'stage' => 'queued', 'status' => Status::newGood() )
|
||||||
);
|
);
|
||||||
$retVal = 1;
|
$ok = JobQueueGroup::singleton()->push( new AssembleUploadChunksJob(
|
||||||
$cmd = wfShellWikiCmd(
|
Title::makeTitle( NS_FILE, $this->mParams['filekey'] ),
|
||||||
"$IP/includes/upload/AssembleUploadChunks.php",
|
|
||||||
array(
|
array(
|
||||||
'--wiki', wfWikiID(),
|
'filename' => $this->mParams['filename'],
|
||||||
'--filename', $this->mParams['filename'],
|
'filekey' => $this->mParams['filekey'],
|
||||||
'--filekey', $this->mParams['filekey'],
|
'session' => $this->getRequest()->exportUserSession(),
|
||||||
'--userid', $this->getUser()->getId(),
|
'userid' => $this->getUser()->getId()
|
||||||
'--sessionid', session_id(),
|
|
||||||
'--quiet'
|
|
||||||
)
|
)
|
||||||
) . " < " . wfGetNull() . " > " . wfGetNull() . " 2>&1 &";
|
) );
|
||||||
// Start a process in the background. Enforce the time limits via PHP
|
if ( $ok ) {
|
||||||
// since ulimit4.sh seems to often not work for this particular usage.
|
|
||||||
wfShellExec( $cmd, $retVal, array(), array( 'time' => 0, 'memory' => 0 ) );
|
|
||||||
if ( $retVal == 0 ) {
|
|
||||||
$result['result'] = 'Poll';
|
$result['result'] = 'Poll';
|
||||||
} else {
|
} else {
|
||||||
UploadBase::setSessionStatus( $this->mParams['filekey'], false );
|
UploadBase::setSessionStatus( $this->mParams['filekey'], false );
|
||||||
|
|
@ -596,25 +591,19 @@ class ApiUpload extends ApiBase {
|
||||||
$this->mParams['filekey'],
|
$this->mParams['filekey'],
|
||||||
array( 'result' => 'Poll', 'stage' => 'queued', 'status' => Status::newGood() )
|
array( 'result' => 'Poll', 'stage' => 'queued', 'status' => Status::newGood() )
|
||||||
);
|
);
|
||||||
$retVal = 1;
|
$ok = JobQueueGroup::singleton()->push( new PublishStashedFileJob(
|
||||||
$cmd = wfShellWikiCmd(
|
Title::makeTitle( NS_FILE, $this->mParams['filename'] ),
|
||||||
"$IP/includes/upload/PublishStashedFile.php",
|
|
||||||
array(
|
array(
|
||||||
'--wiki', wfWikiID(),
|
'filename' => $this->mParams['filename'],
|
||||||
'--filename', $this->mParams['filename'],
|
'filekey' => $this->mParams['filekey'],
|
||||||
'--filekey', $this->mParams['filekey'],
|
'comment' => $this->mParams['comment'],
|
||||||
'--userid', $this->getUser()->getId(),
|
'text' => $this->mParams['text'],
|
||||||
'--comment', $this->mParams['comment'],
|
'watch' => $watch,
|
||||||
'--text', $this->mParams['text'],
|
'session' => $this->getRequest()->exportUserSession(),
|
||||||
'--watch', $watch,
|
'userid' => $this->getUser()->getId()
|
||||||
'--sessionid', session_id(),
|
|
||||||
'--quiet'
|
|
||||||
)
|
)
|
||||||
) . " < " . wfGetNull() . " > " . wfGetNull() . " 2>&1 &";
|
) );
|
||||||
// Start a process in the background. Enforce the time limits via PHP
|
if ( $ok ) {
|
||||||
// since ulimit4.sh seems to often not work for this particular usage.
|
|
||||||
wfShellExec( $cmd, $retVal, array(), array( 'time' => 0, 'memory' => 0 ) );
|
|
||||||
if ( $retVal == 0 ) {
|
|
||||||
$result['result'] = 'Poll';
|
$result['result'] = 'Poll';
|
||||||
} else {
|
} else {
|
||||||
UploadBase::setSessionStatus( $this->mParams['filekey'], false );
|
UploadBase::setSessionStatus( $this->mParams['filekey'], false );
|
||||||
|
|
|
||||||
|
|
@ -392,6 +392,63 @@ class RequestContext implements IContextSource {
|
||||||
return $instance;
|
return $instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import the resolved user IP, HTTP headers, and session ID.
|
||||||
|
* This sets the current session and sets $wgUser and $wgRequest.
|
||||||
|
* Once the return value falls out of scope, the old context is restored.
|
||||||
|
* This function can only be called within CLI mode scripts.
|
||||||
|
*
|
||||||
|
* This will setup the session from the given ID. This is useful when
|
||||||
|
* background scripts inherit some context when acting on behalf of a user.
|
||||||
|
*
|
||||||
|
* $param array $params Result of WebRequest::exportUserSession()
|
||||||
|
* @return ScopedCallback
|
||||||
|
* @throws MWException
|
||||||
|
* @since 1.21
|
||||||
|
*/
|
||||||
|
public static function importScopedSession( array $params ) {
|
||||||
|
if ( PHP_SAPI !== 'cli' ) {
|
||||||
|
// Don't send random private cookie headers to other random users
|
||||||
|
throw new MWException( "Sessions can only be imported in cli mode." );
|
||||||
|
}
|
||||||
|
|
||||||
|
$importSessionFunction = function( array $params ) {
|
||||||
|
global $wgRequest, $wgUser;
|
||||||
|
|
||||||
|
// Write and close any current session
|
||||||
|
session_write_close(); // persist
|
||||||
|
session_id( '' ); // detach
|
||||||
|
$_SESSION = array(); // clear in-memory array
|
||||||
|
// Load the new session from the session ID
|
||||||
|
if ( strlen( $params['sessionId'] ) ) {
|
||||||
|
wfSetupSession( $params['sessionId'] ); // sets $_SESSION
|
||||||
|
}
|
||||||
|
// Build the new WebRequest object
|
||||||
|
$request = new FauxRequest( array(), false, $_SESSION );
|
||||||
|
$request->setIP( $params['ip'] );
|
||||||
|
foreach ( $params['headers'] as $name => $value ) {
|
||||||
|
$request->setHeader( $name, $value );
|
||||||
|
}
|
||||||
|
|
||||||
|
$context = RequestContext::getMain();
|
||||||
|
// Set the current context to use the new WebRequest
|
||||||
|
$context->setRequest( $request );
|
||||||
|
$wgRequest = $context->getRequest(); // b/c
|
||||||
|
// Set the current user based on the new session and WebRequest
|
||||||
|
$context->setUser( User::newFromSession( $request ) ); // uses $_SESSION
|
||||||
|
$wgUser = $context->getUser(); // b/c
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stash the old session and load in the new one
|
||||||
|
$oldParams = self::getMain()->getRequest()->exportUserSession();
|
||||||
|
$importSessionFunction( $params );
|
||||||
|
|
||||||
|
// Set callback to save and close the new session and reload the old one
|
||||||
|
return new ScopedCallback( function() use ( $importSessionFunction, $oldParams ) {
|
||||||
|
$importSessionFunction( $oldParams );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new extraneous context. The context is filled with information
|
* Create a new extraneous context. The context is filled with information
|
||||||
* external to the current session.
|
* external to the current session.
|
||||||
|
|
|
||||||
|
|
@ -18,65 +18,58 @@
|
||||||
* http://www.gnu.org/copyleft/gpl.html
|
* http://www.gnu.org/copyleft/gpl.html
|
||||||
*
|
*
|
||||||
* @file
|
* @file
|
||||||
* @ingroup Maintenance
|
* @ingroup Upload
|
||||||
*/
|
*/
|
||||||
require_once( __DIR__ . '/../../maintenance/Maintenance.php' );
|
|
||||||
set_time_limit( 3600 ); // 1 hour
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assemble the segments of a chunked upload.
|
* Assemble the segments of a chunked upload.
|
||||||
*
|
*
|
||||||
* @ingroup Maintenance
|
* @ingroup Upload
|
||||||
*/
|
*/
|
||||||
class AssembleUploadChunks extends Maintenance {
|
class AssembleUploadChunksJob extends Job {
|
||||||
public function __construct() {
|
public function __construct( $title, $params, $id = 0 ) {
|
||||||
parent::__construct();
|
parent::__construct( 'AssembleUploadChunks', $title, $params, $id );
|
||||||
$this->mDescription = "Re-assemble the segments of a chunked upload into a single file";
|
$this->removeDuplicates = true;
|
||||||
$this->addOption( 'filename', "Desired file name", true, true );
|
|
||||||
$this->addOption( 'filekey', "Upload stash file key", true, true );
|
|
||||||
$this->addOption( 'userid', "Upload owner user ID", true, true );
|
|
||||||
$this->addOption( 'sessionid', "Upload owner session ID", true, true );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute() {
|
public function run() {
|
||||||
$e = null;
|
$scope = RequestContext::importScopedSession( $this->params['session'] );
|
||||||
wfDebug( "Started assembly for file {$this->getOption( 'filename' )}\n" );
|
$context = RequestContext::getMain();
|
||||||
wfSetupSession( $this->getOption( 'sessionid' ) );
|
|
||||||
try {
|
try {
|
||||||
$user = User::newFromId( $this->getOption( 'userid' ) );
|
$user = $context->getUser();
|
||||||
if ( !$user ) {
|
if ( !$user->isLoggedIn() || $user->getId() != $this->params['userid'] ) {
|
||||||
throw new MWException( "No user with ID " . $this->getOption( 'userid' ) . "." );
|
$this->setLastError( "Could not load the author user from session." );
|
||||||
|
return true; // no retries
|
||||||
}
|
}
|
||||||
|
|
||||||
UploadBase::setSessionStatus(
|
UploadBase::setSessionStatus(
|
||||||
$this->getOption( 'filekey' ),
|
$this->params['filekey'],
|
||||||
array( 'result' => 'Poll', 'stage' => 'assembling', 'status' => Status::newGood() )
|
array( 'result' => 'Poll', 'stage' => 'assembling', 'status' => Status::newGood() )
|
||||||
);
|
);
|
||||||
|
|
||||||
$upload = new UploadFromChunks( $user );
|
$upload = new UploadFromChunks( $user );
|
||||||
$upload->continueChunks(
|
$upload->continueChunks(
|
||||||
$this->getOption( 'filename' ),
|
$this->params['filename'],
|
||||||
$this->getOption( 'filekey' ),
|
$this->params['filekey'],
|
||||||
// @TODO: set User?
|
$context->getRequest()
|
||||||
RequestContext::getMain()->getRequest() // dummy request
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Combine all of the chunks into a local file and upload that to a new stash file
|
// Combine all of the chunks into a local file and upload that to a new stash file
|
||||||
$status = $upload->concatenateChunks();
|
$status = $upload->concatenateChunks();
|
||||||
if ( !$status->isGood() ) {
|
if ( !$status->isGood() ) {
|
||||||
UploadBase::setSessionStatus(
|
UploadBase::setSessionStatus(
|
||||||
$this->getOption( 'filekey' ),
|
$this->params['filekey'],
|
||||||
array( 'result' => 'Failure', 'stage' => 'assembling', 'status' => $status )
|
array( 'result' => 'Failure', 'stage' => 'assembling', 'status' => $status )
|
||||||
);
|
);
|
||||||
session_write_close();
|
$this->setLastError( $status->getWikiText() );
|
||||||
$this->error( $status->getWikiText() . "\n", 1 ); // die
|
return true; // no retries
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have a new filekey for the fully concatenated file
|
// We have a new filekey for the fully concatenated file
|
||||||
$newFileKey = $upload->getLocalFile()->getFileKey();
|
$newFileKey = $upload->getLocalFile()->getFileKey();
|
||||||
|
|
||||||
// Remove the old stash file row and first chunk file
|
// Remove the old stash file row and first chunk file
|
||||||
$upload->stash->removeFileNoAuth( $this->getOption( 'filekey' ) );
|
$upload->stash->removeFileNoAuth( $this->params['filekey'] );
|
||||||
|
|
||||||
// Build the image info array while we have the local reference handy
|
// Build the image info array while we have the local reference handy
|
||||||
$apiMain = new ApiMain(); // dummy object (XXX)
|
$apiMain = new ApiMain(); // dummy object (XXX)
|
||||||
|
|
@ -87,7 +80,7 @@ class AssembleUploadChunks extends Maintenance {
|
||||||
|
|
||||||
// Cache the info so the user doesn't have to wait forever to get the final info
|
// Cache the info so the user doesn't have to wait forever to get the final info
|
||||||
UploadBase::setSessionStatus(
|
UploadBase::setSessionStatus(
|
||||||
$this->getOption( 'filekey' ),
|
$this->params['filekey'],
|
||||||
array(
|
array(
|
||||||
'result' => 'Success',
|
'result' => 'Success',
|
||||||
'stage' => 'assembling',
|
'stage' => 'assembling',
|
||||||
|
|
@ -98,21 +91,26 @@ class AssembleUploadChunks extends Maintenance {
|
||||||
);
|
);
|
||||||
} catch ( MWException $e ) {
|
} catch ( MWException $e ) {
|
||||||
UploadBase::setSessionStatus(
|
UploadBase::setSessionStatus(
|
||||||
$this->getOption( 'filekey' ),
|
$this->params['filekey'],
|
||||||
array(
|
array(
|
||||||
'result' => 'Failure',
|
'result' => 'Failure',
|
||||||
'stage' => 'assembling',
|
'stage' => 'assembling',
|
||||||
'status' => Status::newFatal( 'api-error-stashfailed' )
|
'status' => Status::newFatal( 'api-error-stashfailed' )
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
$this->setLastError( get_class( $e ) . ": " . $e->getText() );
|
||||||
}
|
}
|
||||||
session_write_close();
|
return true; // returns true on success and erro (no retries)
|
||||||
if ( $e ) {
|
}
|
||||||
throw $e;
|
|
||||||
|
/**
|
||||||
|
* @return Array
|
||||||
|
*/
|
||||||
|
public function getDeduplicationInfo() {
|
||||||
|
$info = parent::getDeduplicationInfo();
|
||||||
|
if ( is_array( $info['params'] ) ) {
|
||||||
|
$info['params'] = array( 'filekey' => $info['params']['filekey'] );
|
||||||
}
|
}
|
||||||
wfDebug( "Finished assembly for file {$this->getOption( 'filename' )}\n" );
|
return $info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$maintClass = "AssembleUploadChunks";
|
|
||||||
require_once( RUN_MAINTENANCE_IF_MAIN );
|
|
||||||
|
|
@ -18,39 +18,32 @@
|
||||||
* http://www.gnu.org/copyleft/gpl.html
|
* http://www.gnu.org/copyleft/gpl.html
|
||||||
*
|
*
|
||||||
* @file
|
* @file
|
||||||
* @ingroup Maintenance
|
* @ingroup Upload
|
||||||
*/
|
*/
|
||||||
require_once( __DIR__ . '/../../maintenance/Maintenance.php' );
|
|
||||||
set_time_limit( 3600 ); // 1 hour
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload a file from the upload stash into the local file repo.
|
* Upload a file from the upload stash into the local file repo.
|
||||||
*
|
*
|
||||||
* @ingroup Maintenance
|
* @ingroup Upload
|
||||||
*/
|
*/
|
||||||
class PublishStashedFile extends Maintenance {
|
class PublishStashedFileJob extends Job {
|
||||||
public function __construct() {
|
public function __construct( $title, $params, $id = 0 ) {
|
||||||
parent::__construct();
|
parent::__construct( 'PublishStashedFile', $title, $params, $id );
|
||||||
$this->mDescription = "Upload stashed file into the local file repo";
|
$this->removeDuplicates = true;
|
||||||
$this->addOption( 'filename', "Desired file name", true, true );
|
|
||||||
$this->addOption( 'filekey', "Upload stash file key", true, true );
|
|
||||||
$this->addOption( 'userid', "Upload owner user ID", true, true );
|
|
||||||
$this->addOption( 'comment', "Upload comment", true, true );
|
|
||||||
$this->addOption( 'text', "Upload description", true, true );
|
|
||||||
$this->addOption( 'watch', "Whether the uploader should watch the page", true, true );
|
|
||||||
$this->addOption( 'sessionid', "Upload owner session ID", true, true );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute() {
|
public function run() {
|
||||||
wfSetupSession( $this->getOption( 'sessionid' ) );
|
$scope = RequestContext::importScopedSession( $this->params['session'] );
|
||||||
|
$context = RequestContext::getMain();
|
||||||
try {
|
try {
|
||||||
$user = User::newFromId( $this->getOption( 'userid' ) );
|
$user = $context->getUser();
|
||||||
if ( !$user ) {
|
if ( !$user->isLoggedIn() || $user->getId() != $this->params['userid'] ) {
|
||||||
throw new MWException( "No user with ID " . $this->getOption( 'userid' ) . "." );
|
$this->setLastError( "Could not load the author user from session." );
|
||||||
|
return true; // no retries
|
||||||
}
|
}
|
||||||
|
|
||||||
UploadBase::setSessionStatus(
|
UploadBase::setSessionStatus(
|
||||||
$this->getOption( 'filekey' ),
|
$this->params['filekey'],
|
||||||
array( 'result' => 'Poll', 'stage' => 'publish', 'status' => Status::newGood() )
|
array( 'result' => 'Poll', 'stage' => 'publish', 'status' => Status::newGood() )
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -59,7 +52,7 @@ class PublishStashedFile extends Maintenance {
|
||||||
// checks and anything else to the stash stage (which includes concatenation and
|
// checks and anything else to the stash stage (which includes concatenation and
|
||||||
// the local file is thus already there). That way, instead of GET+PUT, there could
|
// the local file is thus already there). That way, instead of GET+PUT, there could
|
||||||
// just be a COPY operation from the stash to the public zone.
|
// just be a COPY operation from the stash to the public zone.
|
||||||
$upload->initialize( $this->getOption( 'filekey' ), $this->getOption( 'filename' ) );
|
$upload->initialize( $this->params['filekey'], $this->params['filename'] );
|
||||||
|
|
||||||
// Check if the local file checks out (this is generally a no-op)
|
// Check if the local file checks out (this is generally a no-op)
|
||||||
$verification = $upload->verifyUpload();
|
$verification = $upload->verifyUpload();
|
||||||
|
|
@ -67,25 +60,27 @@ class PublishStashedFile extends Maintenance {
|
||||||
$status = Status::newFatal( 'verification-error' );
|
$status = Status::newFatal( 'verification-error' );
|
||||||
$status->value = array( 'verification' => $verification );
|
$status->value = array( 'verification' => $verification );
|
||||||
UploadBase::setSessionStatus(
|
UploadBase::setSessionStatus(
|
||||||
$this->getOption( 'filekey' ),
|
$this->params['filekey'],
|
||||||
array( 'result' => 'Failure', 'stage' => 'publish', 'status' => $status )
|
array( 'result' => 'Failure', 'stage' => 'publish', 'status' => $status )
|
||||||
);
|
);
|
||||||
$this->error( "Could not verify upload.\n", 1 ); // die
|
$this->setLastError( "Could not verify upload." );
|
||||||
|
return true; // no retries
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload the stashed file to a permanent location
|
// Upload the stashed file to a permanent location
|
||||||
$status = $upload->performUpload(
|
$status = $upload->performUpload(
|
||||||
$this->getOption( 'comment' ),
|
$this->params['comment'],
|
||||||
$this->getOption( 'text' ),
|
$this->params['text'],
|
||||||
$this->getOption( 'watch' ),
|
$this->params['watch'],
|
||||||
$user
|
$user
|
||||||
);
|
);
|
||||||
if ( !$status->isGood() ) {
|
if ( !$status->isGood() ) {
|
||||||
UploadBase::setSessionStatus(
|
UploadBase::setSessionStatus(
|
||||||
$this->getOption( 'filekey' ),
|
$this->params['filekey'],
|
||||||
array( 'result' => 'Failure', 'stage' => 'publish', 'status' => $status )
|
array( 'result' => 'Failure', 'stage' => 'publish', 'status' => $status )
|
||||||
);
|
);
|
||||||
$this->error( $status->getWikiText() . "\n", 1 ); // die
|
$this->setLastError( $status->getWikiText() );
|
||||||
|
return true; // no retries
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the image info array while we have the local reference handy
|
// Build the image info array while we have the local reference handy
|
||||||
|
|
@ -97,7 +92,7 @@ class PublishStashedFile extends Maintenance {
|
||||||
|
|
||||||
// Cache the info so the user doesn't have to wait forever to get the final info
|
// Cache the info so the user doesn't have to wait forever to get the final info
|
||||||
UploadBase::setSessionStatus(
|
UploadBase::setSessionStatus(
|
||||||
$this->getOption( 'filekey' ),
|
$this->params['filekey'],
|
||||||
array(
|
array(
|
||||||
'result' => 'Success',
|
'result' => 'Success',
|
||||||
'stage' => 'publish',
|
'stage' => 'publish',
|
||||||
|
|
@ -108,18 +103,26 @@ class PublishStashedFile extends Maintenance {
|
||||||
);
|
);
|
||||||
} catch ( MWException $e ) {
|
} catch ( MWException $e ) {
|
||||||
UploadBase::setSessionStatus(
|
UploadBase::setSessionStatus(
|
||||||
$this->getOption( 'filekey' ),
|
$this->params['filekey'],
|
||||||
array(
|
array(
|
||||||
'result' => 'Failure',
|
'result' => 'Failure',
|
||||||
'stage' => 'publish',
|
'stage' => 'publish',
|
||||||
'status' => Status::newFatal( 'api-error-publishfailed' )
|
'status' => Status::newFatal( 'api-error-publishfailed' )
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
throw $e;
|
$this->setLastError( get_class( $e ) . ": " . $e->getText() );
|
||||||
}
|
}
|
||||||
session_write_close();
|
return true; // returns true on success and erro (no retries)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Array
|
||||||
|
*/
|
||||||
|
public function getDeduplicationInfo() {
|
||||||
|
$info = parent::getDeduplicationInfo();
|
||||||
|
if ( is_array( $info['params'] ) ) {
|
||||||
|
$info['params'] = array( 'filekey' => $info['params']['filekey'] );
|
||||||
|
}
|
||||||
|
return $info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$maintClass = "PublishStashedFile";
|
|
||||||
require_once( RUN_MAINTENANCE_IF_MAIN );
|
|
||||||
Loading…
Reference in a new issue