}
/**
- * Set the callback
+ * Set a read callback to accept data read from the HTTP request.
+ * By default, data is appended to an internal buffer which can be
+ * retrieved through $req->getContent().
+ *
+ * To handle data as it comes in -- especially for large files that
+ * would not fit in memory -- you can instead set your own callback,
+ * in the form function($resource, $buffer) where the first parameter
+ * is the low-level resource being read (implementation specific),
+ * and the second parameter is the data buffer.
+ *
+ * You MUST return the number of bytes handled in the buffer; if fewer
+ * bytes are reported handled than were passed to you, the HTTP fetch
+ * will be aborted.
*
* @param $callback Callback
*/
public function setCallback( $callback ) {
+ if ( !is_callable( $callback ) ) {
+ throw new MWException( 'Invalid MwHttpRequest callback' );
+ }
$this->callback = $callback;
}
protected function makeTemporaryFile() {
return tempnam( wfTempDir(), 'URL' );
}
+
/**
- * Save the result of a HTTP request to the temporary file
+ * Callback: save a chunk of the result of a HTTP request to the temporary file
*
- * @param $req MWHttpRequest
- * @return Status
+ * @param $req mixed
+ * @param $buffer string
+ * @return int number of bytes handled
*/
- private function saveTempFile( $req ) {
- if ( $this->mTempPath === false ) {
- return Status::newFatal( 'tmp-create-error' );
+ public function saveTempFileChunk( $req, $buffer ) {
+ $nbytes = fwrite( $this->mTmpHandle, $buffer );
+
+ if ( $nbytes == strlen( $buffer ) ) {
+ $this->mFileSize += $nbytes;
+ } else {
+ // Well... that's not good!
+ fclose( $this->mTmpHandle );
+ $this->mTmpHandle = false;
}
- if ( file_put_contents( $this->mTempPath, $req->getContent() ) === false ) {
- return Status::newFatal( 'tmp-write-error' );
- }
-
- $this->mFileSize = filesize( $this->mTempPath );
- return Status::newGood();
+ return $nbytes;
}
+
/**
* Download the file, save it to the temporary file and update the file
* size and set $mRemoveTempFile to true.
*/
protected function reallyFetchFile() {
+ if ( $this->mTempPath === false ) {
+ return Status::newFatal( 'tmp-create-error' );
+ }
+
+ // Note the temporary file should already be created by makeTemporaryFile()
+ $this->mTmpHandle = fopen( $this->mTempPath, 'wb' );
+ if ( !$this->mTmpHandle ) {
+ return Status::newFatal( 'tmp-create-error' );
+ }
+
+ $this->mRemoveTempFile = true;
+ $this->mFileSize = 0;
+
$req = MWHttpRequest::factory( $this->mUrl );
+ $req->setCallback( array( $this, 'saveTempFileChunk' ) );
$status = $req->execute();
- if ( !$status->isOk() ) {
- return $status;
+ if ( $this->mTmpHandle ) {
+ // File got written ok...
+ fclose( $this->mTmpHandle );
+ $this->mTmpHandle = null;
+ } else {
+ // We encountered a write error during the download...
+ return Status::newFatal( 'tmp-write-error' );
}
- $status = $this->saveTempFile( $req );
- if ( !$status->isGood() ) {
+ if ( !$status->isOk() ) {
return $status;
}
- $this->mRemoveTempFile = true;
return $status;
}