From 40fa6de40887333840ea72c0e3aafffe4fc2d852 Mon Sep 17 00:00:00 2001 From: timb Date: Thu, 11 Feb 2010 00:37:11 -0800 Subject: initial import of webcam actionscriptz --- webcam/ru/inspirit/net/MultipartURLLoader.as | 582 +++++++++++++++++++++++++++ 1 file changed, 582 insertions(+) create mode 100644 webcam/ru/inspirit/net/MultipartURLLoader.as (limited to 'webcam/ru') diff --git a/webcam/ru/inspirit/net/MultipartURLLoader.as b/webcam/ru/inspirit/net/MultipartURLLoader.as new file mode 100644 index 0000000..bfd3db2 --- /dev/null +++ b/webcam/ru/inspirit/net/MultipartURLLoader.as @@ -0,0 +1,582 @@ +package ru.inspirit.net +{ + import flash.errors.IllegalOperationError; + 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.net.URLLoader; + import flash.net.URLLoaderDataFormat; + import flash.net.URLRequest; + import flash.net.URLRequestHeader; + import flash.net.URLRequestMethod; + import flash.utils.ByteArray; + import flash.utils.Dictionary; + import flash.utils.Endian; + import flash.utils.setTimeout; + import flash.utils.clearInterval; + import ru.inspirit.net.events.MultipartURLLoaderEvent; + + /** + * Multipart URL Loader + * + * Original idea by Marston Development Studio - http://marstonstudio.com/?p=36 + * + * History + * 2009.01.15 version 1.0 + * Initial release + * + * 2009.01.19 version 1.1 + * Added options for MIME-types (default is application/octet-stream) + * + * 2009.01.20 version 1.2 + * Added clearVariables and clearFiles methods + * Small code refactoring + * Public methods documentaion + * + * 2009.02.09 version 1.2.1 + * Changed 'useWeakReference' to false (thanx to zlatko) + * It appears that on some servers setting 'useWeakReference' to true + * completely disables this event + * + * 2009.03.05 version 1.3 + * Added Async property. Now you can prepare data asynchronous before sending it. + * It will prevent flash player from freezing while constructing request data. + * You can specify the amount of bytes to write per iteration through BLOCK_SIZE static property. + * Added events for asynchronous method. + * Added dataFormat property for returned server data. + * Removed 'Cache-Control' from headers and added custom requestHeaders array property. + * Added getter for the URLLoader class used to send data. + * + * @author Eugene Zatepyakin + * @version 1.3 + * @link http://blog.inspirit.ru/ + */ + public class MultipartURLLoader extends EventDispatcher + { + public static var BLOCK_SIZE:uint = 64 * 1024; + + private var _loader:URLLoader; + private var _boundary:String; + private var _variableNames:Array; + private var _fileNames:Array; + private var _variables:Dictionary; + private var _files:Dictionary; + + private var _async:Boolean = false; + private var _path:String; + private var _data:ByteArray; + + private var _prepared:Boolean = false; + private var asyncWriteTimeoutId:Number; + private var asyncFilePointer:uint = 0; + private var totalFilesSize:uint = 0; + private var writtenBytes:uint = 0; + + public var requestHeaders:Array; + + public function MultipartURLLoader() + { + _fileNames = new Array(); + _files = new Dictionary(); + _variableNames = new Array(); + _variables = new Dictionary(); + _loader = new URLLoader(); + requestHeaders = new Array(); + } + + /** + * Start uploading data to specified path + * + * @param path The server script path + * @param async Set to true if you are uploading huge amount of data + */ + public function load(path:String, async:Boolean = false):void + { + if (path == null || path == '') throw new IllegalOperationError('You cant load without specifing PATH'); + + _path = path; + _async = async; + + if (_async) { + if(!_prepared){ + constructPostDataAsync(); + } else { + doSend(); + } + } else { + _data = constructPostData(); + doSend(); + } + } + + /** + * Start uploading data after async prepare + */ + public function startLoad():void + { + if ( _path == null || _path == '' || _async == false ) throw new IllegalOperationError('You can use this method only if loading asynchronous.'); + if ( !_prepared && _async ) throw new IllegalOperationError('You should prepare data before sending when using asynchronous.'); + + doSend(); + } + + /** + * Prepare data before sending (only if you use asynchronous) + */ + public function prepareData():void + { + constructPostDataAsync(); + } + + /** + * Stop loader action + */ + public function close():void + { + try { + _loader.close(); + } catch( e:Error ) { } + } + + /** + * Add string variable to loader + * If you have already added variable with the same name it will be overwritten + * + * @param name Variable name + * @param value Variable value + */ + public function addVariable(name:String, value:Object = ''):void + { + if (_variableNames.indexOf(name) == -1) { + _variableNames.push(name); + } + _variables[name] = value; + _prepared = false; + } + + /** + * Add file part to loader + * If you have already added file with the same fileName it will be overwritten + * + * @param fileContent File content encoded to ByteArray + * @param fileName Name of the file + * @param dataField Name of the field containg file data + * @param contentType MIME type of the uploading file + */ + public function addFile(fileContent:ByteArray, fileName:String, dataField:String = 'Filedata', contentType:String = 'application/octet-stream'):void + { + if (_fileNames.indexOf(fileName) == -1) { + _fileNames.push(fileName); + _files[fileName] = new FilePart(fileContent, fileName, dataField, contentType); + totalFilesSize += fileContent.length; + } else { + var f:FilePart = _files[fileName] as FilePart; + totalFilesSize -= f.fileContent.length; + f.fileContent = fileContent; + f.fileName = fileName; + f.dataField = dataField; + f.contentType = contentType; + totalFilesSize += fileContent.length; + } + + _prepared = false; + } + + /** + * Remove all variable parts + */ + public function clearVariables():void + { + _variableNames = new Array(); + _variables = new Dictionary(); + _prepared = false; + } + + /** + * Remove all file parts + */ + public function clearFiles():void + { + for each(var name:String in _fileNames) + { + (_files[name] as FilePart).dispose(); + } + _fileNames = new Array(); + _files = new Dictionary(); + totalFilesSize = 0; + _prepared = false; + } + + /** + * Dispose all class instance objects + */ + public function dispose(): void + { + clearInterval(asyncWriteTimeoutId); + removeListener(); + close(); + + _loader = null; + _boundary = null; + _variableNames = null; + _variables = null; + _fileNames = null; + _files = null; + requestHeaders = null; + _data = null; + } + + /** + * Generate random boundary + * @return Random boundary + */ + public function getBoundary():String + { + if (_boundary == null) { + _boundary = ''; + for (var i:int = 0; i < 0x20; i++ ) { + _boundary += String.fromCharCode( int( 97 + Math.random() * 25 ) ); + } + } + return _boundary; + } + + public function get ASYNC():Boolean + { + return _async; + } + + public function get PREPARED():Boolean + { + return _prepared; + } + + public function get dataFormat():String + { + return _loader.dataFormat; + } + + public function set dataFormat(format:String):void + { + if (format != URLLoaderDataFormat.BINARY && format != URLLoaderDataFormat.TEXT && format != URLLoaderDataFormat.VARIABLES) { + throw new IllegalOperationError('Illegal URLLoader Data Format'); + } + _loader.dataFormat = format; + } + + public function get loader():URLLoader + { + return _loader; + } + + private function doSend():void + { + var urlRequest:URLRequest = new URLRequest(); + urlRequest.url = _path; + urlRequest.contentType = 'multipart/form-data; boundary=' + getBoundary(); + urlRequest.method = URLRequestMethod.POST; + urlRequest.data = _data; + + if (requestHeaders.length && requestHeaders != null){ + urlRequest.requestHeaders = requestHeaders.concat(); + } + + addListener(); + + _loader.load(urlRequest); + } + + private function constructPostDataAsync():void + { + clearInterval(asyncWriteTimeoutId); + + _data = new ByteArray(); + _data.endian = Endian.BIG_ENDIAN; + + _data = constructVariablesPart(_data); + + asyncFilePointer = 0; + writtenBytes = 0; + _prepared = false; + if (_fileNames.length) { + nextAsyncLoop(); + } else { + _data = closeDataObject(_data); + _prepared = true; + dispatchEvent( new MultipartURLLoaderEvent(MultipartURLLoaderEvent.DATA_PREPARE_COMPLETE) ); + } + } + + private function constructPostData():ByteArray + { + var postData:ByteArray = new ByteArray(); + postData.endian = Endian.BIG_ENDIAN; + + postData = constructVariablesPart(postData); + postData = constructFilesPart(postData); + + postData = closeDataObject(postData); + + return postData; + } + + private function closeDataObject(postData:ByteArray):ByteArray + { + postData = BOUNDARY(postData); + postData = DOUBLEDASH(postData); + return postData; + } + + private function constructVariablesPart(postData:ByteArray):ByteArray + { + var i:uint; + var bytes:String; + + for each(var name:String in _variableNames) + { + postData = BOUNDARY(postData); + postData = LINEBREAK(postData); + bytes = 'Content-Disposition: form-data; name="' + name + '"'; + for ( i = 0; i < bytes.length; i++ ) { + postData.writeByte( bytes.charCodeAt(i) ); + } + postData = LINEBREAK(postData); + postData = LINEBREAK(postData); + postData.writeUTFBytes(_variables[name]); + postData = LINEBREAK(postData); + } + + return postData; + } + + private function constructFilesPart(postData:ByteArray):ByteArray + { + var i:uint; + var bytes:String; + + if(_fileNames.length){ + for each(var name:String in _fileNames) + { + postData = getFilePartHeader(postData, _files[name] as FilePart); + postData = getFilePartData(postData, _files[name] as FilePart); + postData = LINEBREAK(postData); + } + postData = closeFilePartsData(postData); + } + + return postData; + } + + private function closeFilePartsData(postData:ByteArray):ByteArray + { + var i:uint; + var bytes:String; + + postData = LINEBREAK(postData); + postData = BOUNDARY(postData); + postData = LINEBREAK(postData); + bytes = 'Content-Disposition: form-data; name="Upload"'; + for ( i = 0; i < bytes.length; i++ ) { + postData.writeByte( bytes.charCodeAt(i) ); + } + postData = LINEBREAK(postData); + postData = LINEBREAK(postData); + bytes = 'Submit Query'; + for ( i = 0; i < bytes.length; i++ ) { + postData.writeByte( bytes.charCodeAt(i) ); + } + postData = LINEBREAK(postData); + + return postData; + } + + private function getFilePartHeader(postData:ByteArray, part:FilePart):ByteArray + { + var i:uint; + var bytes:String; + + postData = BOUNDARY(postData); + postData = LINEBREAK(postData); + bytes = 'Content-Disposition: form-data; name="Filename"'; + for ( i = 0; i < bytes.length; i++ ) { + postData.writeByte( bytes.charCodeAt(i) ); + } + postData = LINEBREAK(postData); + postData = LINEBREAK(postData); + postData.writeUTFBytes(part.fileName); + postData = LINEBREAK(postData); + + postData = BOUNDARY(postData); + postData = LINEBREAK(postData); + bytes = 'Content-Disposition: form-data; name="' + part.dataField + '"; filename="'; + for ( i = 0; i < bytes.length; i++ ) { + postData.writeByte( bytes.charCodeAt(i) ); + } + postData.writeUTFBytes(part.fileName); + postData = QUOTATIONMARK(postData); + postData = LINEBREAK(postData); + bytes = 'Content-Type: ' + part.contentType; + for ( i = 0; i < bytes.length; i++ ) { + postData.writeByte( bytes.charCodeAt(i) ); + } + postData = LINEBREAK(postData); + postData = LINEBREAK(postData); + + return postData; + } + + private function getFilePartData(postData:ByteArray, part:FilePart):ByteArray + { + postData.writeBytes(part.fileContent, 0, part.fileContent.length); + + return postData; + } + + private function onProgress( event: ProgressEvent ): void + { + dispatchEvent( event ); + } + + private function onComplete( event: Event ): void + { + removeListener(); + dispatchEvent( event ); + } + + private function onIOError( event: IOErrorEvent ): void + { + removeListener(); + dispatchEvent( event ); + } + + private function onSecurityError( event: SecurityErrorEvent ): void + { + removeListener(); + dispatchEvent( event ); + } + + private function onHTTPStatus( event: HTTPStatusEvent ): void + { + dispatchEvent( event ); + } + + private function addListener(): void + { + _loader.addEventListener( Event.COMPLETE, onComplete, false, 0, false ); + _loader.addEventListener( ProgressEvent.PROGRESS, onProgress, false, 0, false ); + _loader.addEventListener( IOErrorEvent.IO_ERROR, onIOError, false, 0, false ); + _loader.addEventListener( HTTPStatusEvent.HTTP_STATUS, onHTTPStatus, false, 0, false ); + _loader.addEventListener( SecurityErrorEvent.SECURITY_ERROR, onSecurityError, false, 0, false ); + } + + private function removeListener(): void + { + _loader.removeEventListener( Event.COMPLETE, onComplete ); + _loader.removeEventListener( ProgressEvent.PROGRESS, onProgress ); + _loader.removeEventListener( IOErrorEvent.IO_ERROR, onIOError ); + _loader.removeEventListener( HTTPStatusEvent.HTTP_STATUS, onHTTPStatus ); + _loader.removeEventListener( SecurityErrorEvent.SECURITY_ERROR, onSecurityError ); + } + + private function BOUNDARY(p:ByteArray):ByteArray + { + var l:int = getBoundary().length; + p = DOUBLEDASH(p); + for (var i:int = 0; i < l; i++ ) { + p.writeByte( _boundary.charCodeAt( i ) ); + } + return p; + } + + private function LINEBREAK(p:ByteArray):ByteArray + { + p.writeShort(0x0d0a); + return p; + } + + private function QUOTATIONMARK(p:ByteArray):ByteArray + { + p.writeByte(0x22); + return p; + } + + private function DOUBLEDASH(p:ByteArray):ByteArray + { + p.writeShort(0x2d2d); + return p; + } + + private function nextAsyncLoop():void + { + var fp:FilePart; + + if (asyncFilePointer < _fileNames.length) { + + fp = _files[_fileNames[asyncFilePointer]] as FilePart; + _data = getFilePartHeader(_data, fp); + + asyncWriteTimeoutId = setTimeout(writeChunkLoop, 10, _data, fp.fileContent, 0); + + asyncFilePointer ++; + } else { + _data = closeFilePartsData(_data); + _data = closeDataObject(_data); + + _prepared = true; + + dispatchEvent( new MultipartURLLoaderEvent(MultipartURLLoaderEvent.DATA_PREPARE_PROGRESS, totalFilesSize, totalFilesSize) ); + dispatchEvent( new MultipartURLLoaderEvent(MultipartURLLoaderEvent.DATA_PREPARE_COMPLETE) ); + } + } + + private function writeChunkLoop(dest:ByteArray, data:ByteArray, p:uint = 0):void + { + var len:uint = Math.min(BLOCK_SIZE, data.length - p); + dest.writeBytes(data, p, len); + + if (len < BLOCK_SIZE || p + len >= data.length) { + // Finished writing file bytearray + dest = LINEBREAK(dest); + nextAsyncLoop(); + return; + } + + p += len; + writtenBytes += len; + if ( writtenBytes % BLOCK_SIZE * 2 == 0 ) { + dispatchEvent( new MultipartURLLoaderEvent(MultipartURLLoaderEvent.DATA_PREPARE_PROGRESS, writtenBytes, totalFilesSize) ); + } + + asyncWriteTimeoutId = setTimeout(writeChunkLoop, 10, dest, data, p); + } + + } +} + +internal class FilePart +{ + + public var fileContent:flash.utils.ByteArray; + public var fileName:String; + public var dataField:String; + public var contentType:String; + + public function FilePart(fileContent:flash.utils.ByteArray, fileName:String, dataField:String = 'Filedata', contentType:String = 'application/octet-stream') + { + this.fileContent = fileContent; + this.fileName = fileName; + this.dataField = dataField; + this.contentType = contentType; + } + + public function dispose():void + { + fileContent = null; + fileName = null; + dataField = null; + contentType = null; + } +} \ No newline at end of file -- cgit v1.2.3-70-g09d2