Created
January 5, 2010 20:59
-
-
Save srohde/269720 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.soenkerohde.net { | |
import com.adobe.crypto.MD5Stream; | |
import flash.events.Event; | |
import flash.events.EventDispatcher; | |
import flash.events.HTTPStatusEvent; | |
import flash.events.IOErrorEvent; | |
import flash.events.ProgressEvent; | |
import flash.events.SecurityErrorEvent; | |
import flash.filesystem.File; | |
import flash.filesystem.FileMode; | |
import flash.filesystem.FileStream; | |
import flash.net.URLLoader; | |
import flash.net.URLRequest; | |
import flash.net.URLRequestMethod; | |
import flash.utils.ByteArray; | |
import mx.logging.ILogger; | |
import mx.logging.Log; | |
public class UploadFile extends EventDispatcher { | |
protected static const LOG:ILogger = Log.getLogger( "UploadFile" ); | |
public static const BOUNDARY:String = "---------------------13286573512345675675865"; | |
// e.g. 1 MB chunks | |
public static const BUFFERSIZE:int = 1024000; | |
protected var file:File; | |
protected var fileStream:FileStream; | |
// current chunk position | |
protected var currentIndex:int = 0; | |
protected var loader:URLLoader; | |
protected var uploadUrl:String; | |
protected var resumeUrl:String; | |
// flag wether to calc MD5 or not | |
protected var doCalcMD5:Boolean; | |
// the MD5 of the current chunk | |
protected var chunkMD5:String; | |
private var _bytesLoaded:Number; | |
private var _bytesTotal:Number; | |
/** | |
* progress in percent for UI | |
*/ | |
[Bindable] | |
public var progress:String; | |
[Bindable] | |
public var complete:Boolean = false; | |
/** | |
* getter for UI | |
* @return filename | |
* | |
*/ | |
public function get name() : String { | |
return file.name; | |
} | |
/** | |
* getter for UI | |
* @return filesize | |
* | |
*/ | |
public function get size() : String { | |
return file.size.toString(); | |
} | |
[Bindable(event="progressChange")] | |
public function get bytesLoaded() : Number { | |
return _bytesLoaded; | |
} | |
[Bindable(event="progressChange")] | |
public function get bytesTotal() : Number { | |
return _bytesTotal; | |
} | |
public function UploadFile( file : File, uploadUrl : String, resumeUrl : String = null, doCalcMD5 : Boolean = false ) { | |
this.file = file; | |
this.uploadUrl = uploadUrl; | |
// check if there is a separate resumeUrl. | |
// if not just take the uploadUrl. | |
if ( resumeUrl != null ) { | |
this.resumeUrl = resumeUrl; | |
} else { | |
this.resumeUrl = uploadUrl; | |
} | |
this.doCalcMD5 = doCalcMD5; | |
// create FileStream in read mode | |
fileStream = new FileStream(); | |
fileStream.open( file, FileMode.READ ); | |
fileStream.position = 0; | |
// initiate upload | |
upload(); | |
} | |
/** | |
* Cancels the current upload and closes the upload and filstream | |
* | |
*/ | |
public function cancel() : void { | |
try { | |
loader.close(); | |
fileStream.close(); | |
} catch ( e : Error ) { | |
LOG.error( "cannot cancel. loader or filestream not available." ); | |
} | |
} | |
protected function upload() : void { | |
// if a loader is already present remove all added event listeners | |
if ( loader != null ) { | |
loader.removeEventListener( Event.COMPLETE, completeHandler ); | |
loader.removeEventListener( IOErrorEvent.IO_ERROR, ioErrorHandler ); | |
loader.removeEventListener( ProgressEvent.PROGRESS, progressHandler ); | |
loader.removeEventListener( SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler ); | |
loader.removeEventListener( HTTPStatusEvent.HTTP_STATUS, httpStatusHandler ); | |
} | |
// check if there are still bytes to upload | |
if ( fileStream.bytesAvailable > 0 ) { | |
var request:URLRequest = createRequest(); | |
loader = new URLLoader( request ); | |
loader.addEventListener( Event.COMPLETE, completeHandler ); | |
loader.addEventListener( IOErrorEvent.IO_ERROR, ioErrorHandler ); | |
loader.addEventListener( ProgressEvent.PROGRESS, progressHandler ); | |
loader.addEventListener( SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler ); | |
loader.addEventListener( HTTPStatusEvent.HTTP_STATUS, httpStatusHandler ); | |
} else { | |
complete = true; | |
fileStream.close(); | |
dispatchEvent( new Event( Event.COMPLETE ) ); | |
} | |
} | |
/** | |
* Creates a URLRequest with the BOUNDARY constant and the given upload or resume URL | |
* | |
* @return URLRequest | |
* | |
*/ | |
protected function createRequest() : URLRequest { | |
var url:String = fileStream.position == 0 ? uploadUrl : resumeUrl; | |
var request:URLRequest = new URLRequest( url ); | |
request.contentType = "multipart/form-data; boundary=" + BOUNDARY; | |
request.method = URLRequestMethod.POST; | |
request.data = createBody(); | |
return request; | |
} | |
protected function calcMD5( ba : ByteArray ) : String { | |
ba.position = 0; | |
var md5Stream:MD5Stream = new MD5Stream(); | |
md5Stream.update( ba ); | |
var hash:String = md5Stream.complete(); | |
return hash; | |
} | |
protected function createBody() : ByteArray { | |
var currentLength:int = Math.min( BUFFERSIZE, fileStream.bytesAvailable ); | |
var body:ByteArray = new ByteArray(); | |
// example how to add a variable with name/value | |
body.writeUTFBytes( createVariable( "foo", "bar" ) ); | |
// position 0 indicated no resume meaning first chunk | |
if ( fileStream.position == 0 ) { | |
// mark as chunk upload | |
if ( file.size > BUFFERSIZE ) { | |
// you could add a variable to mark the chunk incomplete | |
} | |
} | |
// resume | |
else { | |
// example how to add a fileid | |
body.writeUTFBytes( createVariable( "fileid", 123 ) ); | |
// complete | |
if ( fileStream.bytesAvailable == currentLength ) { | |
// example how to mark the chunk complete | |
body.writeUTFBytes( createVariable( "complete", 1 ) ); | |
} | |
} | |
// example how to add the filename | |
body.writeUTFBytes( createVariable( "filename", file.name ) ); | |
// chunk | |
var chunk:ByteArray = new ByteArray(); | |
fileStream.readBytes( chunk, 0, currentLength ); | |
body.writeBytes( chunk ); | |
// calc MD5 | |
if ( doCalcMD5 ) { | |
chunk.position = 0; | |
chunkMD5 = calcMD5( chunk ); | |
} | |
// tail | |
body.writeUTFBytes( "\r\n" ); | |
body.writeUTFBytes( "--" + BOUNDARY + "--" + "\r\n" ); | |
currentIndex += currentLength; | |
return body; | |
} | |
protected function createVariable( name : String, value : * ) : String { | |
var s:String = "--" + BOUNDARY + "\r\n"; | |
s += "content-disposition: form-data; name=\""+name+"\"" + "\r\n"; | |
s += "\r\n"; | |
s += value + "\r\n"; | |
return s; | |
} | |
protected function completeHandler( event : Event ) : void { | |
var data:String = loader.data; | |
LOG.info( "complete: " + data ); | |
// If result contains MD5 hash get it from the loader.data | |
var md5:String; | |
if ( doCalcMD5 && chunkMD5.toLowerCase() != md5.toLowerCase() ) { | |
LOG.error( "md5error. upload will be canceled" ); | |
cancel(); | |
} else { | |
upload(); | |
} | |
} | |
protected function progressHandler( event : ProgressEvent ) : void { | |
var loaded:Number = currentIndex; | |
var total:Number = file.size; | |
LOG.info( "progressHandler " + loaded + "/" + total ); | |
LOG.info( "chunk progress " + event.bytesLoaded + "/" + event.bytesTotal ); | |
var percent:Number = Math.round( ( loaded / total ) * 100 ); | |
progress = percent + "%"; | |
_bytesLoaded = loaded; | |
_bytesTotal = total; | |
dispatchEvent( new Event( "progressChange" ) ); | |
} | |
protected function httpStatusHandler( event : HTTPStatusEvent ) : void { | |
LOG.info( "httpStatus " + event.status ); | |
} | |
protected function securityErrorHandler( event : SecurityErrorEvent ) : void { | |
LOG.error( "securityError " + event.text ); | |
} | |
protected function ioErrorHandler( event : IOErrorEvent ) : void { | |
LOG.error( "ioError " + event.text ); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment