Skip to content

Instantly share code, notes, and snippets.

@srohde
Created January 5, 2010 20:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save srohde/269720 to your computer and use it in GitHub Desktop.
Save srohde/269720 to your computer and use it in GitHub Desktop.
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