From 5c6019bdf5db6ecc3d5429c5dfcbf46069125e43 Mon Sep 17 00:00:00 2001 From: Ilya Lebedev Date: Tue, 18 Dec 2012 14:58:20 +0400 Subject: [PATCH 01/12] added FileAPI --- FileAPI.class.php | 38 + FileAPI.exif.js | 59 + FileAPI.flash.image.swf | Bin 0 -> 2027 bytes FileAPI.flash.swf | Bin 0 -> 60061 bytes FileAPI.id3.js | 67 + FileAPI.js | 6 + FileAPI.min.js | 63 + LICENSE | 31 + README.md | 578 +++++++ crossdomain.xml | 19 + flash/.actionScriptProperties | 41 + .../org.eclipse.core.resources.prefs | 3 + flash/html-template/history/history.css | 6 + flash/html-template/history/history.js | 697 +++++++++ flash/html-template/history/historyFrame.html | 29 + flash/html-template/index.template.html | 108 ++ flash/html-template/playerProductInstall.swf | Bin 0 -> 657 bytes flash/html-template/swfobject.js | 777 +++++++++ flash/lib/EnginesLibrary.swc | Bin 0 -> 5064 bytes flash/lib/blooddy_crypto.swc | Bin 0 -> 25332 bytes flash/src/FlashFileAPI.as | 66 + flash/src/net/inspirit/MultipartURLLoader.as | 589 +++++++ .../events/MultipartURLLoaderEvent.as | 27 + .../commands/AbstractUploadFileCommand.as | 88 ++ .../commands/DecodeBytesToBitmapCommand.as | 127 ++ flash/src/ru/mail/commands/LoadFileCommand.as | 120 ++ .../src/ru/mail/commands/ResizeFileCommand.as | 232 +++ flash/src/ru/mail/commands/UploadCommand.as | 212 +++ .../src/ru/mail/commands/UploadFileCommand.as | 133 ++ .../ru/mail/commands/UploadImageCommand.as | 160 ++ .../mail/commands/textloader/ITextLoader.as | 13 + .../commands/textloader/SimpleTextLoader.as | 66 + .../textloader/events/LoaderProgressEvent.as | 29 + .../events/TextLoaderCompleteEvent.as | 38 + .../mail/communication/JSCallbackPresenter.as | 114 ++ flash/src/ru/mail/communication/JSCaller.as | 208 +++ flash/src/ru/mail/controller/AppController.as | 676 ++++++++ .../src/ru/mail/data/AbstractImageFactory.as | 29 + flash/src/ru/mail/data/AttachmentsModel.as | 146 ++ flash/src/ru/mail/data/IImageFactory.as | 28 + flash/src/ru/mail/data/ImageFactory.as | 162 ++ .../mail/data/builder/AbstractDataBuilder.as | 194 +++ .../ru/mail/data/builder/FilesDataBuilder.as | 64 + flash/src/ru/mail/data/vo/BaseFileVO.as | 62 + flash/src/ru/mail/data/vo/ErrorVO.as | 47 + flash/src/ru/mail/data/vo/FileStatesEnum.as | 17 + flash/src/ru/mail/data/vo/FileVO.as | 87 ++ flash/src/ru/mail/data/vo/IFileVO.as | 29 + flash/src/ru/mail/data/vo/ImageTransformVO.as | 39 + flash/src/ru/mail/data/vo/RestoredFileVO.as | 60 + .../engines/chain/AbstractModelJSEngine.as | 47 + .../ru/mail/engines/chain/EnginesFactory.as | 42 + .../ru/mail/engines/chain/IJsCallerHolder.as | 15 + .../src/ru/mail/engines/chain/IModelHolder.as | 15 + .../engines/chain/manage/SelectFilesEngine.as | 165 ++ .../chain/presentation/MouseListenerEngine.as | 105 ++ .../commands/MouseListenerEngineCommand.as | 36 + .../engines/commands/SelectFilesCommand.as | 42 + flash/src/ru/mail/events/CompleteEvent.as | 44 + .../DecodeBytesToBitmapCompleteEvent.as | 35 + .../events/ImageTransformCompleteEvent.as | 34 + .../src/ru/mail/events/UploadCompleteEvent.as | 32 + flash/src/ru/mail/utils/BMPDecoder.as | 564 +++++++ flash/src/ru/mail/utils/ExifReader2.as | 158 ++ flash/src/ru/mail/utils/LoggerJS.as | 18 + html5.png | Bin 0 -> 12814 bytes index.php | 362 +++++ lib/FileAPI.Flash.js | 610 ++++++++ lib/FileAPI.Form.js | 117 ++ lib/FileAPI.Image.js | 280 ++++ lib/FileAPI.XHR.js | 188 +++ lib/FileAPI.core.js | 1382 +++++++++++++++++ lib/canvas-to-blob.js | 91 ++ 73 files changed, 10766 insertions(+) create mode 100644 FileAPI.class.php create mode 100644 FileAPI.exif.js create mode 100644 FileAPI.flash.image.swf create mode 100644 FileAPI.flash.swf create mode 100644 FileAPI.id3.js create mode 100644 FileAPI.js create mode 100644 FileAPI.min.js create mode 100644 LICENSE create mode 100644 README.md create mode 100644 crossdomain.xml create mode 100644 flash/.actionScriptProperties create mode 100644 flash/.settings/org.eclipse.core.resources.prefs create mode 100644 flash/html-template/history/history.css create mode 100644 flash/html-template/history/history.js create mode 100644 flash/html-template/history/historyFrame.html create mode 100644 flash/html-template/index.template.html create mode 100644 flash/html-template/playerProductInstall.swf create mode 100644 flash/html-template/swfobject.js create mode 100644 flash/lib/EnginesLibrary.swc create mode 100644 flash/lib/blooddy_crypto.swc create mode 100644 flash/src/FlashFileAPI.as create mode 100644 flash/src/net/inspirit/MultipartURLLoader.as create mode 100644 flash/src/net/inspirit/events/MultipartURLLoaderEvent.as create mode 100644 flash/src/ru/mail/commands/AbstractUploadFileCommand.as create mode 100644 flash/src/ru/mail/commands/DecodeBytesToBitmapCommand.as create mode 100644 flash/src/ru/mail/commands/LoadFileCommand.as create mode 100644 flash/src/ru/mail/commands/ResizeFileCommand.as create mode 100644 flash/src/ru/mail/commands/UploadCommand.as create mode 100644 flash/src/ru/mail/commands/UploadFileCommand.as create mode 100644 flash/src/ru/mail/commands/UploadImageCommand.as create mode 100644 flash/src/ru/mail/commands/textloader/ITextLoader.as create mode 100644 flash/src/ru/mail/commands/textloader/SimpleTextLoader.as create mode 100644 flash/src/ru/mail/commands/textloader/events/LoaderProgressEvent.as create mode 100644 flash/src/ru/mail/commands/textloader/events/TextLoaderCompleteEvent.as create mode 100644 flash/src/ru/mail/communication/JSCallbackPresenter.as create mode 100644 flash/src/ru/mail/communication/JSCaller.as create mode 100644 flash/src/ru/mail/controller/AppController.as create mode 100644 flash/src/ru/mail/data/AbstractImageFactory.as create mode 100644 flash/src/ru/mail/data/AttachmentsModel.as create mode 100644 flash/src/ru/mail/data/IImageFactory.as create mode 100644 flash/src/ru/mail/data/ImageFactory.as create mode 100644 flash/src/ru/mail/data/builder/AbstractDataBuilder.as create mode 100644 flash/src/ru/mail/data/builder/FilesDataBuilder.as create mode 100644 flash/src/ru/mail/data/vo/BaseFileVO.as create mode 100644 flash/src/ru/mail/data/vo/ErrorVO.as create mode 100644 flash/src/ru/mail/data/vo/FileStatesEnum.as create mode 100644 flash/src/ru/mail/data/vo/FileVO.as create mode 100644 flash/src/ru/mail/data/vo/IFileVO.as create mode 100644 flash/src/ru/mail/data/vo/ImageTransformVO.as create mode 100644 flash/src/ru/mail/data/vo/RestoredFileVO.as create mode 100644 flash/src/ru/mail/engines/chain/AbstractModelJSEngine.as create mode 100644 flash/src/ru/mail/engines/chain/EnginesFactory.as create mode 100644 flash/src/ru/mail/engines/chain/IJsCallerHolder.as create mode 100644 flash/src/ru/mail/engines/chain/IModelHolder.as create mode 100644 flash/src/ru/mail/engines/chain/manage/SelectFilesEngine.as create mode 100644 flash/src/ru/mail/engines/chain/presentation/MouseListenerEngine.as create mode 100644 flash/src/ru/mail/engines/commands/MouseListenerEngineCommand.as create mode 100644 flash/src/ru/mail/engines/commands/SelectFilesCommand.as create mode 100644 flash/src/ru/mail/events/CompleteEvent.as create mode 100644 flash/src/ru/mail/events/DecodeBytesToBitmapCompleteEvent.as create mode 100644 flash/src/ru/mail/events/ImageTransformCompleteEvent.as create mode 100644 flash/src/ru/mail/events/UploadCompleteEvent.as create mode 100644 flash/src/ru/mail/utils/BMPDecoder.as create mode 100644 flash/src/ru/mail/utils/ExifReader2.as create mode 100644 flash/src/ru/mail/utils/LoggerJS.as create mode 100644 html5.png create mode 100644 index.php create mode 100644 lib/FileAPI.Flash.js create mode 100644 lib/FileAPI.Form.js create mode 100644 lib/FileAPI.Image.js create mode 100644 lib/FileAPI.XHR.js create mode 100644 lib/FileAPI.core.js create mode 100644 lib/canvas-to-blob.js diff --git a/FileAPI.class.php b/FileAPI.class.php new file mode 100644 index 00000000..c6d38015 --- /dev/null +++ b/FileAPI.class.php @@ -0,0 +1,38 @@ + $arFileDescriptions ){ + foreach( $arFileDescriptions as $fileDescriptionParam => $mixedValue ){ + self::rRestructuringFilesArray(self::$_files, $firstNameKey, $_FILES[$firstNameKey][$fileDescriptionParam], $fileDescriptionParam); + } + } + } + } + + + private static function rRestructuringFilesArray(&$arrayForFill, $currentKey, $currentMixedValue, $fileDescriptionParam){ + if( is_array($currentMixedValue) ){ + foreach( $currentMixedValue as $nameKey => $mixedValue ){ + self::rRestructuringFilesArray($arrayForFill[$currentKey], + $nameKey, + $mixedValue, + $fileDescriptionParam); + } + } else { + $arrayForFill[$currentKey][$fileDescriptionParam] = $currentMixedValue; + } + } + + + public static function getFiles(){ + self::init(); + return self::$_files; + } + + } diff --git a/FileAPI.exif.js b/FileAPI.exif.js new file mode 100644 index 00000000..7ae3f28c --- /dev/null +++ b/FileAPI.exif.js @@ -0,0 +1,59 @@ +(function (){ +/**! + * Binary Ajax 0.1.10 + * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com, http://blog.nihilogic.dk/ + * Licensed under the MPL License [http://www.nihilogic.dk/licenses/mpl-license.txt] + * + * + * Javascript EXIF Reader 0.1.4 + * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com, http://blog.nihilogic.dk/ + * Licensed under the MPL License [http://www.nihilogic.dk/licenses/mpl-license.txt] + */ + + +var BinaryFile=function(j,k,l){var h=j,i=k||0,b=0;this.getRawData=function(){return h};"string"==typeof j&&(b=l||h.length,this.getByteAt=function(a){return h.charCodeAt(a+i)&255},this.getBytesAt=function(a,b){for(var c=[],f=0;fc&&(c+=65536);return c};this.getSShortAt=function(a,b){var c=this.getShortAt(a,b);return 32767c&&(c+=4294967296);return c};this.getSLongAt=function(a,b){var c=this.getLongAt(a,b);return 2147483647P>F}W=rEW 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 literal 0 HcmV?d00001 diff --git a/FileAPI.flash.swf b/FileAPI.flash.swf new file mode 100644 index 0000000000000000000000000000000000000000..cf8df0ef8bfce13085fba8d5cfe39cb3aa82dd1e GIT binary patch literal 60061 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 avcpE0+pQu=qd3d6UeCuM)BV literal 0 HcmV?d00001 diff --git a/FileAPI.id3.js b/FileAPI.id3.js new file mode 100644 index 00000000..5b74eef9 --- /dev/null +++ b/FileAPI.id3.js @@ -0,0 +1,67 @@ +(function (){ +/** + * JavaScript ID3 Reader + * https://github.com/aadsm/JavaScript-ID3-Reader + * + * Authors + * Jacob Seidelin + * António Afonso + * Joshua Kifer + */ + +var x=!0,y=null;function z(a,b,d){function e(c){c=parseInt(c.getResponseHeader("Content-Length"),10)||-1;b(new h(a,c))}function f(){var c=y;window.XMLHttpRequest?c=new XMLHttpRequest:window.ActiveXObject&&(c=new ActiveXObject("Microsoft.XMLHTTP"));return c}function h(c,a){var b,g;function e(c){var a=~~(c[0]/b)-g,c=~~(c[1]/b)+1+g;0>a&&(a=0);c>=blockTotal&&(c=blockTotal-1);return[a,c]}function h(e,g){function m(c){parseInt(c.getResponseHeader("Content-Length"),10)==a&&(e[0]=0,e[1]=blockTotal-1,i[0]=0,i[1]=a-1);for(var c= +{data:c.U||c.responseText,offset:i[0]},b=e[0];b<=e[1];b++)A[b]=c;l+=i[1]-i[0]+1;g&&g()}for(;A[e[0]];)if(e[0]++,e[0]>e[1]){g&&g();return}for(;A[e[1]];)if(e[1]--,e[0]>e[1]){g&&g();return}var i=[e[0]*b,(e[1]+1)*b-1],w=c,r=d,L=q,I=!!g,n=f();n?("undefined"===typeof I&&(I=x),m&&("undefined"!=typeof n.onload?n.onload=function(){"200"==n.status||"206"==n.status?(n.fileSize=L||n.getResponseHeader("Content-Length"),m(n)):r&&r();n=y}:n.onreadystatechange=function(){4==n.readyState&&("200"==n.status||"206"== +n.status?(n.fileSize=L||n.getResponseHeader("Content-Length"),m(n)):r&&r(),n=y)}),n.open("GET",w,I),n.overrideMimeType&&n.overrideMimeType("text/plain; charset=x-user-defined"),i&&n.setRequestHeader("Range","bytes="+i[0]+"-"+i[1]),n.setRequestHeader("If-Modified-Since","Sat, 1 Jan 1970 00:00:00 GMT"),n.send(y)):r&&r()}var q,l=0,w=new B("",0,a),A=[];b=b||2048;g="undefined"===typeof g?0:g;blockTotal=~~((a-1)/b)+1;for(var r in w)w.hasOwnProperty(r)&&"function"===typeof w[r]&&(this[r]=w[r]);this.a=function(c){var a; +h(e([c,c]));a=A[~~(c/b)];if("string"==typeof a.data)return a.data.charCodeAt(c-a.offset)&255;if("unknown"==typeof a.data)return IEBinary_getByteAt(a.data,c-a.offset)};this.L=function(){return l};this.g=function(c,a){h(e(c),a)}}var c=f();c&&(e&&("undefined"!=typeof c.onload?c.onload=function(){"200"==c.status&&e(this);c=y}:c.onreadystatechange=function(){4==c.readyState&&("200"==c.status&&e(this),c=y)}),c.open("HEAD",a,x),c.send(y))} +function B(a,b,d){var e=a,f=b||0,h=0;this.N=function(){return e};"string"==typeof a?(h=d||e.length,this.a=function(c){return e.charCodeAt(c+f)&255}):"unknown"==typeof a&&(h=d||IEBinary_getLength(e),this.a=function(c){return IEBinary_getByteAt(e,c+f)});this.m=function(c,a){for(var b=Array(a),d=0;db&&(b+=65536);return b};this.Q=function(c,a){var b=this.p(c,a);return 32767b&&(b+=4294967296);return b};this.P=function(c,a){var b=this.h(c,a);return 2147483647a&&(a+=16777216);return a};this.d=function(a,b){for(var d= +[],e=a,g=0;eq||224<=q?b[h]=String.fromCharCode(l):(q=(a[g+f]<<8)+a[g+d],g+=2,b[h]=String.fromCharCode(l,q))}a= +new String(b.join(""));a.f=g;break;case "utf-8":e=0;g=Math.min(g||a.length,a.length);239==a[0]&&(187==a[1]&&191==a[2])&&(e=3);f=[];for(d=0;eb?f[d]=String.fromCharCode(b):194<=b&&224>b?(h=a[e++],f[d]=String.fromCharCode(((b&31)<<6)+(h&63))):224<=b&&240>b?(h=a[e++],l=a[e++],f[d]=String.fromCharCode(((b&255)<<12)+((h&63)<<6)+(l&63))):240<=b&&245>b&&(h=a[e++],l=a[e++],q=a[e++],b=((b&7)<<18)+((h&63)<<12)+((l&63)<<6)+(q&63)-65536,f[d]=String.fromCharCode((b>>10)+55296,(b&1023)+ +56320));a=new String(f.join(""));a.f=e;break;default:g=[];f=f||a.length;for(e=0;e\r\nFunction IEBinary_getByteAt(strBinary, iOffset)\r\n\tIEBinary_getByteAt = AscB(MidB(strBinary,iOffset+1,1))\r\nEnd Function\r\nFunction IEBinary_getLength(strBinary)\r\n\tIEBinary_getLength = LenB(strBinary)\r\nEnd Function\r\n<\/script>\r\n");this.FileAPIReader=function(a){return function(b,d){var e=new FileReader;e.onload=function(a){d(new B(a.target.result))};e.readAsBinaryString(a)}};this.Base64=this.j={i:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",w:function(a){for(var b="",d,e,f,h,c,k,v=0;v>2,d=(d&3)<<4|e>>4,c=(e&15)<<2|f>>6,k=f&63,isNaN(e)?c=k=64:isNaN(f)&&(k=64),b=b+Base64.i.charAt(h)+Base64.i.charAt(d)+Base64.i.charAt(c)+Base64.i.charAt(k);return b}};this.j.encodeBytes=this.j.w;var C=this.s={},E={},F=[0,7];C.B=function(a,b,d){d=d||{};(d.dataReader||z)(a,function(e){e.g(F,function(){var f="ftypM4A"==e.d(4,7)?ID4:"ID3"==e.d(0,3)?ID3v2:ID3v1;f.q(e,function(){var h=d.tags,c=f.r(e,h),h=E[a]||{},k;for(k in c)c.hasOwnProperty(k)&&(h[k]=c[k]);E[a]=h;b&&b()})})})};C.z=function(a){if(!E[a])return y;var b={},d;for(d in E[a])E[a].hasOwnProperty(d)&&(b[d]=E[a][d]);return b};C.A=function(a,b){return!E[a]?y:E[a][b]};this.ID3=this.s;C.loadTags=C.B;C.getAllTags=C.z;C.getTag=C.A; +C.BinaryFile=B;this.ID3v1=this.t={};function G(a,b){var d=b.a(a),e=b.a(a+1),f=b.a(a+2);return b.a(a+3)&127|(f&127)<<7|(e&127)<<14|(d&127)<<21}var H=this.D={};H.b={}; +H.frames={BUF:"Recommended buffer size",CNT:"Play counter",COM:"Comments",CRA:"Audio encryption",CRM:"Encrypted meta frame",ETC:"Event timing codes",EQU:"Equalization",GEO:"General encapsulated object",IPL:"Involved people list",LNK:"Linked information",MCI:"Music CD Identifier",MLL:"MPEG location lookup table",PIC:"Attached picture",POP:"Popularimeter",REV:"Reverb",RVA:"Relative volume adjustment",SLT:"Synchronized lyric/text",STC:"Synced tempo codes",TAL:"Album/Movie/Show title",TBP:"BPM (Beats Per Minute)", +TCM:"Composer",TCO:"Content type",TCR:"Copyright message",TDA:"Date",TDY:"Playlist delay",TEN:"Encoded by",TFT:"File type",TIM:"Time",TKE:"Initial key",TLA:"Language(s)",TLE:"Length",TMT:"Media type",TOA:"Original artist(s)/performer(s)",TOF:"Original filename",TOL:"Original Lyricist(s)/text writer(s)",TOR:"Original release year",TOT:"Original album/Movie/Show title",TP1:"Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group",TP2:"Band/Orchestra/Accompaniment",TP3:"Conductor/Performer refinement", +TP4:"Interpreted, remixed, or otherwise modified by",TPA:"Part of a set",TPB:"Publisher",TRC:"ISRC (International Standard Recording Code)",TRD:"Recording dates",TRK:"Track number/Position in set",TSI:"Size",TSS:"Software/hardware and settings used for encoding",TT1:"Content group description",TT2:"Title/Songname/Content description",TT3:"Subtitle/Description refinement",TXT:"Lyricist/text writer",TXX:"User defined text information frame",TYE:"Year",UFI:"Unique file identifier",ULT:"Unsychronized lyric/text transcription", +WAF:"Official audio file webpage",WAR:"Official artist/performer webpage",WAS:"Official audio source webpage",WCM:"Commercial information",WCP:"Copyright/Legal information",WPB:"Publishers official webpage",WXX:"User defined URL link frame",AENC:"Audio encryption",APIC:"Attached picture",COMM:"Comments",COMR:"Commercial frame",ENCR:"Encryption method registration",EQUA:"Equalization",ETCO:"Event timing codes",GEOB:"General encapsulated object",GRID:"Group identification registration",IPLS:"Involved people list", +LINK:"Linked information",MCDI:"Music CD identifier",MLLT:"MPEG location lookup table",OWNE:"Ownership frame",PRIV:"Private frame",PCNT:"Play counter",POPM:"Popularimeter",POSS:"Position synchronisation frame",RBUF:"Recommended buffer size",RVAD:"Relative volume adjustment",RVRB:"Reverb",SYLT:"Synchronized lyric/text",SYTC:"Synchronized tempo codes",TALB:"Album/Movie/Show title",TBPM:"BPM (beats per minute)",TCOM:"Composer",TCON:"Content type",TCOP:"Copyright message",TDAT:"Date",TDLY:"Playlist delay", +TENC:"Encoded by",TEXT:"Lyricist/Text writer",TFLT:"File type",TIME:"Time",TIT1:"Content group description",TIT2:"Title/songname/content description",TIT3:"Subtitle/Description refinement",TKEY:"Initial key",TLAN:"Language(s)",TLEN:"Length",TMED:"Media type",TOAL:"Original album/movie/show title",TOFN:"Original filename",TOLY:"Original lyricist(s)/text writer(s)",TOPE:"Original artist(s)/performer(s)",TORY:"Original release year",TOWN:"File owner/licensee",TPE1:"Lead performer(s)/Soloist(s)",TPE2:"Band/orchestra/accompaniment", +TPE3:"Conductor/performer refinement",TPE4:"Interpreted, remixed, or otherwise modified by",TPOS:"Part of a set",TPUB:"Publisher",TRCK:"Track number/Position in set",TRDA:"Recording dates",TRSN:"Internet radio station name",TRSO:"Internet radio station owner",TSIZ:"Size",TSRC:"ISRC (international standard recording code)",TSSE:"Software/Hardware and settings used for encoding",TYER:"Year",TXXX:"User defined text information frame",UFID:"Unique file identifier",USER:"Terms of use",USLT:"Unsychronized lyric/text transcription", +WCOM:"Commercial information",WCOP:"Copyright/Legal information",WOAF:"Official audio file webpage",WOAR:"Official artist/performer webpage",WOAS:"Official audio source webpage",WORS:"Official internet radio station homepage",WPAY:"Payment",WPUB:"Publishers official webpage",WXXX:"User defined URL link frame"}; +var J={title:["TIT2","TT2"],artist:["TPE1","TP1"],album:["TALB","TAL"],year:["TYER","TYE"],comment:["COMM","COM"],track:["TRCK","TRK"],genre:["TCON","TCO"],picture:["APIC","PIC"],lyrics:["USLT","ULT"]},K=["title","artist","album","track"];H.q=function(a,b){a.g([0,G(6,a)],b)}; +H.r=function(a,b){var d=0,e=a.a(d+3);if(42.4"};var f=a.a(d+4),h=a.c(d+5,7),c=a.c(d+5,6),k=a.c(d+5,5),v=G(d+6,a),d=d+10;if(c)var p=a.h(d,x),d=d+(p+4);var e={version:"2."+e+"."+f,major:e,revision:f,flags:{unsynchronisation:h,extended_header:c,experimental_indicator:k},size:v},g;if(h)g={};else{for(var v=v-10,h=a,f=b,c={},k=e.major,p=[],m=0,i;i=(f||K)[m];m++)p=p.concat(J[i]||[i]);for(f=p;df.indexOf(g))&&(2 + * @build lib/canvas-to-blob lib/FileAPI.core lib/FileAPI.Image lib/FileAPI.Form lib/FileAPI.XHR lib/FileAPI.Flash + */ diff --git a/FileAPI.min.js b/FileAPI.min.js new file mode 100644 index 00000000..321223dd --- /dev/null +++ b/FileAPI.min.js @@ -0,0 +1,63 @@ +/**! + * FileAPI — a set of tools for working with files + * + * @author RubaXa + * @build lib/canvas-to-blob lib/FileAPI.core lib/FileAPI.Image lib/FileAPI.Form lib/FileAPI.XHR lib/FileAPI.Flash + */ +(function(a){var k=a.HTMLCanvasElement&&a.HTMLCanvasElement.prototype,g;if(g=a.Blob)try{g=Boolean(new Blob)}catch(n){g=!1}var m=g;if(g=m)if(g=a.Uint8Array)try{g=100===(new Blob([new Uint8Array(100)])).size}catch(f){g=!1}var c=g,e=a.BlobBuilder||a.WebKitBlobBuilder||a.MozBlobBuilder||a.MSBlobBuilder,q=(m||e)&&a.atob&&a.ArrayBuffer&&a.Uint8Array&&function(a){var h,g,v,f;h=0<=a.split(",")[0].indexOf("base64")?atob(a.split(",")[1]):decodeURIComponent(a.split(",")[1]);g=new ArrayBuffer(h.length);v=new Uint8Array(g); +for(f=0;f=a&&!c&&h.end()},isFail:function(){return c},fail:function(){!c&&b(c=!0)},end:function(){i||(i=!0,b())}};return h},each:g,afor:function(b,d){var a=0,c=b.length;h(b)&&c--?function B(){d(c!=a&&B,b[a],a++)}():d(!1)},extend:function(b){g(arguments,function(d){g(d,function(d,a){b[a]=d})});return b},isFile:function(b){return d&&b&&b instanceof w},isCanvas:function(b){return b&&M.test(b.nodeName)},getFilesFilter:function(b){return(b="string"== +typeof b?b:b.getAttribute&&b.getAttribute("accept")||"")?RegExp("("+b.replace(/\./g,"\\.").replace(/,/g,"|")+")$","i"):/./},readAsDataURL:function(b,d){j.isCanvas(b)?c(b,d,"load",j.toDataURL(b)):e(b,d,"DataURL")},readAsBinaryString:function(b,d){t&&t.prototype.readAsBinaryString?e(b,d,"BinaryString"):e(b,function(b){if("load"==b.type)try{b.result=j.toBinaryString(b.result)}catch(a){b.type="error",b.message=a.toString()}d(b)},"DataURL")},readAsArrayBuffer:function(b,d){e(b,d,"ArrayBuffer")},readAsText:function(b, +d,a){a||(a=d,d="utf-8");e(b,a,"Text",d)},toDataURL:function(b){if("string"==typeof b)return b;if(b.toDataURL)return b.toDataURL("image/png")},toBinaryString:function(b){return a.atob(j.toDataURL(b).replace(O,""))},readAsImage:function(b,d,a){if(j.isFile(b))if(y){var i=y.createObjectURL(b);i===k?c(b,d,"error"):j.readAsImage(i,d,a)}else j.readAsDataURL(b,function(i){"load"==i.type?j.readAsImage(i.result,d,a):(a||"error"==i.type)&&c(b,d,i,null,{loaded:i.loaded,total:i.total})});else j.isCanvas(b)?c(b, +d,"load",b):D.test(b.nodeName)?b.complete?c(b,d,"load",b):f(b,"error abort load",function B(a){"load"==a.type&&y&&y.revokeObjectURL(b.src);m(b,"error abort load",B);c(b,d,a,b)}):b.iframe?c(b,d,{type:"error"}):(i=new Image,i.src=b.dataURL||b,j.readAsImage(i,d,a))},checkFileObj:function(b){var d={};"object"==typeof b?d=b:d.name=(b+"").split(/(\\|\/)/g).pop();d.type===k&&(d.type=d.name.split(".").pop());g(I,function(b,a){b.test(d.type)&&(d.type=a+"/"+d.type)});return d},getDropFiles:function(b,d){var a= +[],c=(b.originalEvent||b||"").dataTransfer||{},i=h(c.items)&&c.items[0]&&q(c.items[0]),e=j.queue(function(){d(a)});g((i?c.items:c.files)||[],function(b){e.inc();if(i)u(b,function(b,d){!b&&a.push.apply(a,d);e.next()});else{var d=function(d){d&&a.push(b);e.next()};if(!b.type&&0==b.size%4096&&102400>=b.size)if(t)try{var c=new t;f(c,G,function(b){b="error"!=b.type;d(b);b&&c.abort()});c.readAsDataURL(b)}catch(r){d(!1)}else d(null);else d(!0)}});e.check()},getFiles:function(b,a,c){var i=[];if(c)return j.filterFiles(j.getFiles(b), +a,c),null;b.jquery&&(b.each(function(){i=i.concat(j.getFiles(this))}),b=i,i=[]);"string"==typeof a&&(a=j.getFilesFilter(a));b.originalEvent?b=A(b.originalEvent):b.srcElement&&(b=A(b));b.dataTransfer?b=b.dataTransfer:b.target&&(b=b.target);b.files?i=b.files:!d&&F.test(b&&b.tagName)?j.trim(b.value)&&(i=[j.checkFileObj(b.value)],i[0].blob=b,i[0].iframe=!0):h(b)&&(i=b);!a&&F.test(b&&b.tagName)&&(a=j.getFilesFilter(b));return j.filter(i,function(b){return!a||a.test(b.name)})},getInfo:function(b,d){var a= +{},c=J.concat();j.isFile(b)?function B(){var i=c.shift();i?i.test(b.type)?i(b,function(b,c){b?d(b):(j.extend(a,c),B())}):B():d(!1,a)}():d("not_support",a)},addInfoReader:function(b,d){d.test=function(d){return b.test(d)};J.push(d)},addMime:function(b,d){I[b]=RegExp("("+d.replace(/,/g,"|")+")$","i")},filter:function(b,d){for(var a=[],c=0,i=b.length,h;c>2,c=(c&3)<<4|i>>4;isNaN(i)?i=h=64:(i=(i&15)<<2|h>>6,h=isNaN(h)?64:h&63);d+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(u)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(c)+ +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(i)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(h)}return d}};g({image:"png,jpg,jpeg,bmp,gif,ico,tif,tiff,tga,pcx,cbz,cbr",audio:"m4a,flac,aac,rm,mpa,wav,wma,ogg,mp3,mp2,m3u,mod,amf,dmf,dsm,far,gdm,imf,it,m15,med,okt,s3m,stm,sfx,ult,uni,xm,sid,ac3,dts,cue,aif,aiff,wpl,ape,mac,mpc,mpp,shn,wv,nsf,spc,gym,adplug,adx,dsp,adp,ymf,ast,afc,hps,xsp",video:"m4v,3gp,nsv,ts,ty,strm,rm,rmvb,m3u,ifo,mov,qt,divx,xvid,bivx,vob,nrg,img,iso,pva,wmv,asf,asx,ogm,m2v,avi,bin,dat,dvr-ms,mpg,mpeg,mp4,mkv,avc,vp3,svq3,nuv,viv,dv,fli,flv,wpl"}, +function(b,d){j.addMime(d,b)});j.addInfoReader(/^image/,function(b,d){if(!b.__dimensions){var a=b.__dimensions=j.defer();j.readAsImage(b,function(b){var d=b.target;a.resolve("load"==b.type?!1:"error",{width:d.width,height:d.height})})}b.__dimensions.then(d)});j.event.dnd=function(b,d,a){var c,i;a||(a=d,d=j.F);t?(n(b,"dragenter dragleave dragover",function(b){for(var a=((b.originalEvent||b||"").dataTransfer||{}).types,h=a&&a.length;h--;)~a[h].indexOf("File")&&(b.preventDefault(),i!==b.type&&(i=b.type, +"dragleave"!=i&&d.call(b.currentTarget,!0,b),clearTimeout(c),c=setTimeout(function(){d.call(b.currentTarget,"dragleave"!=i,b)},50)))}),n(b,"drop",function(b){b.preventDefault();i=0;d.call(b.currentTarget,!1,b);j.getDropFiles(b,function(d){a.call(b.currentTarget,d,b)})})):j.log("Drag'n'Drop -- not supported")};s&&!s.fn.dnd&&(s.fn.dnd=function(b,d){return this.each(function(){j.event.dnd(this,b,d)})});a.FileAPI=j.extend(j,a.FileAPI)})(window); +(function(a,k,g){function n(a,c){if(!(this instanceof n))return new n(a);this.file=a;this.better=!c;this.matrix={sx:0,sy:0,sw:0,sh:0,dx:0,dy:0,dw:0,dh:0,resize:0,deg:0}}var m=Math.min,f=Math.round,c=!1,e={8:270,3:180,6:90};try{c=-1=p?(p=e,n=p/k):(n=c,p=n*k),p!=e||n!=c)h.sx=~~((e-p)/2),h.sy=~~((c-n)/2),e=p,c=n}else n&&("min"==n?(g=f(p=k?m(e,g):q*p),q=f(p>=k?g/p:m(c,q))));h.sw=e;h.sh=c;h.dw=g;h.dh=q;return h},_trans:function(a){this._load(this.file,function(c,e){c?a(c):this._apply(e,a)})},get:function(c){if(a.support.transform){var h=this;"auto"==h.matrix.deg?a.getInfo(this.file,function(a,f){h.matrix.deg=e[f&& +f.exif&&f.exif.Orientation]||0;h._trans(c)}):h._trans(c)}else c("not_support")},toData:function(a){this.get(a)}};n.transform=function(c,e,f,q){a.getInfo(c,function(k,m){var y={},w=a.queue(function(a){q(a,y)});k?w.fail():a.each(e,function(a,e){if(!w.isFail()){var h=n(m.nodeType?m:c);if("function"==typeof a)a(m,h);else if(a.width)h[a.preview?"preview":"resize"](a.width,a.height,a.type);else a.maxWidth&&(m.width>a.maxWidth||m.height>a.maxHeight)&&h.resize(a.maxWidth,a.maxHeight,"max");a.rotate===g&& +f&&(a.rotate="auto");h.rotate(a.rotate);w.inc();h.toData(function(a,c){a?w.fail():(y[e]=c,w.next())})}})})};a.support.canvas=a.support.transform=c;a.Image=n})(FileAPI,document); +(function(a,k,g){var n=k.encodeURIComponent,m=k.FormData,k=function(){this.items=[]};k.prototype={append:function(a,c,e,g){this.items.push({name:a,blob:c&&c.blob||c,file:e||c.name,type:g||c.type})},each:function(a){for(var c=0,e=this.items.length;c',c.xhr.abort=function(){var a=e.getElementsByName("iframe")[0];if(a)try{a.stop?a.stop():a.contentWindow.stop?a.contentWindow.stop():a.contentWindow.document.execCommand("Stop")}catch(c){}e=null},n=e.getElementsByTagName("form")[0],n.appendChild(f), +k.log(n.parentNode.innerHTML),document.body.appendChild(e),c.xhr.node=e,a[q]=function(a,f,g){c.readyState=4;c.responseText=g;c.end(a,f);e=null},c.readyState=2,n.submit(),n=null):(e=c.xhr=k.getXHR(),e.open("POST",n,!0),e.withCredential="true",(!g.headers||!g.headers["X-Requested-With"])&&e.setRequestHeader("X-Requested-With","XMLHttpRequest"),k.each(g.headers,function(a,c){e.setRequestHeader(c,a)}),e.upload&&e.upload.addEventListener("progress",k.throttle(function(a){g.progress(a,c,g)},100),!1),e.onreadystatechange= +function(){c.status=e.status;c.statusText=e.statusText;c.readyState=e.readyState;if(4==e.readyState){for(var a in{"":1,XML:1,Text:1,Body:1})c["response"+a]=e["response"+a];e.onreadystatechange=null;c.end(e.status);e=null}},k.isArray(f)?(e.setRequestHeader("Content-Type","multipart/form-data; boundary=_"+k.expando),f=f.join("")+"--_"+k.expando+"--",e.sendAsBinary?e.sendAsBinary(f):(q=Array.prototype.map.call(f,function(a){return a.charCodeAt(0)&255}),e.send((new Uint8Array(q)).buffer))):e.send(f))}}; +k.XHR=n})(window,FileAPI); +(function(a,k,g){var n=a.support,m=k.navigator,f=m.mimeTypes,c=!1;if(m.plugins&&"object"==typeof m.plugins["Shockwave Flash"])c=m.plugins["Shockwave Flash"].description&&!(f&&f["application/x-shockwave-flash"]&&!f["application/x-shockwave-flash"].enabledPlugin);else try{c=!(!k.ActiveXObject||!new ActiveXObject("ShockwaveFlash.ShockwaveFlash"))}catch(e){}n.flash=c;if(a.support.flash&&(!a.support.html5||a.cors&&!a.support.cors)){var q=function(a){return('').replace(/#(\w+)#/ig,function(c,e){return a[e]})},u=function(a,c){if(a&&a.style){var e,f;for(e in c){f=c[e];"number"==typeof f&&(f+="px");try{a.style[e]=f}catch(g){}}}},h=function(d,c){a.each(c,function(a,c){var i=d[c];d[c]=function(){this.parent=i;return a.apply(this,arguments)}})},A=function(d){var c=d.wid=a.uid();l._fn[c]=d;return"FileAPI.Flash._fn."+c},v=function(a){try{l._fn[a.wid]=null,delete l._fn[a.wid]}catch(c){}}, +E=function(a){if(!t.test(a)){if(/^\.\//.test(a)||"/"!=a.charAt(0))var c=location.pathname,c=c.substr(0,c.lastIndexOf("/")),a=c+"/"+a.substr("./"==RegExp.$1?2:0);"//"!=a.substr(0,2)&&(a=location.hostname+"/"+a);t.test(a)||(a=location.protocol+"//"+a)}return a},p=a.uid(),y=0,w={},t=/^https?:/i,l={_fn:{},init:function(){var d=g.body&&g.body.firstChild;if(d){do if(1==d.nodeType){a.log("FlashAPI.Flash.inited");var c=g.createElement("div");u(c,{top:1,right:1,width:5,height:5,position:"absolute"});d.parentNode.insertBefore(c, +d);l.publish(c,p);return}while(d=d.nextSibling)}10>y&&setTimeout(l.init,50*++y)},publish:function(d,c){d.innerHTML=q({id:c,src:E(a.staticPath+"FileAPI.flash.swf?r="+a.build),wmode:"transparent",flashvars:"callback=FileAPI.Flash.event&flashId="+c+"&storeKey="+navigator.userAgent.match(/\d/ig).join("")+"_"+a.build+(l.isReady||(a.pingUrl?"&ping="+a.pingUrl:""))})},ready:function(){l.ready=a.F;l.isReady=!0;l.patch();a.event.on(g,"mouseover",l.mouseover);a.event.on(g,"click",function(a){l.mouseover(a)&& +(a.preventDefault?a.preventDefault():a.returnValue=!0)})},getWrapper:function(a){do if(/js-fileapi-wrapper/.test(a.className))return a;while((a=a.parentNode)&&a!==g.body)},mouseover:function(d){d=a.event.fix(d).target;if(/input/i.test(d.nodeName)&&"file"==d.type){var c=d.getAttribute(p);if("i"==c||"r"==c)return!1;if("p"!=c){d.setAttribute(p,"i");var c=g.createElement("div"),e=l.getWrapper(d);if(!e){a.log("flash.mouseover.error: js-fileapi-wrapper not found");return}u(c,{top:0,left:0,width:d.offsetWidth+ +100,height:d.offsetHeight+100,zIndex:"1000000",position:"absolute"});e.appendChild(c);l.publish(c,a.uid());d.setAttribute(p,"p")}return!0}},event:function(d){var c=d.type;if("ready"==c){try{l.getInput(d.flashId).setAttribute(p,"r")}catch(e){}l.ready();setTimeout(function(){l.mouseenter(d)},50);return!0}"ping"===c?a.log("(flash -> js).ping:",[d.status,d.savedStatus],d.error):"log"===c?a.log("(flash -> js).log:",d.target):c in l&&setTimeout(function(){a.log("Flash.event."+d.type+":",d);l[c](d)},1)}, +mouseenter:function(a){var c=l.getInput(a.flashId);c&&(l.cmd(a,"multiple",null!==c.getAttribute("multiple")),l.cmd(a,"accept",(c.getAttribute("accept")||"*").replace(/\./g,"")))},get:function(a){return g[a]||k[a]||g.embeds[a]},getInput:function(a){try{var c=l.getWrapper(l.get(a));if(c)return c.getElementsByTagName("input")[0]}catch(e){}},select:function(d){var c=l.getInput(d.flashId),e=a.uid(c),d=d.target.files;a.each(d,function(d){a.checkFileObj(d)});w[e]=d;g.createEvent?(e=g.createEvent("Event"), +e.initEvent("change",!0,!1),c.dispatchEvent(e)):g.createEventObject&&(e=g.createEventObject(),c.fireEvent("onchange",e))},cmd:function(d,c,e,f){try{return a.log("(js -> flash)."+c+":",e),l.get(d.flashId||d).cmd(c,e)}catch(g){a.log("(js -> flash).onError:",g),f||setTimeout(function(){l.cmd(d,c,e,!0)},50)}},patch:function(){a.flashEngine=a.support.transform=!0;h(a,{getFiles:function(d,c,e){if(e)return a.filterFiles(a.getFiles(d),c,e),null;var f=a.isArray(d)?d:w[a.uid(d.target||d.srcElement||d)];if(!f)return this.parent.apply(this, +arguments);c&&(c=a.getFilesFilter(c),f=a.filter(f,function(a){return c.test(a.name)}));return f},getInfo:function(a,c){if(a&&!a.flashId)this.parent.apply(this,arguments);else{if(!a.__info){var e=a.__info=FileAPI.defer();l.cmd(a,"getFileInfo",{id:a.id,callback:A(function D(c,i){v(D);e.resolve(c,a.info=i)})})}a.__info.then(c)}}});a.support.transform=!0;h(FileAPI.Image.prototype,{get:function(a,c){this.set({scaleMode:c||"noScale"});this.parent(a)},_load:function(d,c){a.log("FileAPI.Image._load:",d); +if(d&&!d.flashId)this.parent.apply(this,arguments);else{var e=this;a.getInfo(d,function(a){c.call(e,a,d)})}},_apply:function(d,c){a.log("FileAPI.Image._apply:",d);if(d&&!d.flashId)this.parent.apply(this,arguments);else{var e=this.getMatrix(d.info);l.cmd(d,"imageTransform",{id:d.id,matrix:e,callback:A(function D(f,h){a.log("FileAPI.Image._apply.callback:",f);v(D);if(f)c(f);else if(!a.support.dataURI||3E4 + ~~~ DEMO + ~~~ user pic + ~~~ +

+ + +## Support + * Multiupload: all browsers that support HTML5 or [Flash](#flash-settings) + * Drag'n'Drop upload: files (HTML5) & directories (Chrome 21+) + * Upload one file: all browsers + * Working with Images: IE6+, FF 3.6+, Chrome 10+, Opera 11.1+, Safari 5.4+ + + crop, resize, preview & rotate (HTML5 or Flash) + + auto orientation by exif (HTML5, if include FileAPI.exif.js or Flash) + + + + +## Example +```html + + + + +
+
+``` +```js +var input = document.getElementById('user-files'); +var previewNode = document.getElementById('preview-list'); + +// Drag'n'Drop +FileAPI.event.dnd(previewNode, function (over){ + $(this).css('background', over ? 'red' : ''); +}, function (files){ + // .. +}); + + + +FileAPI.event.on(input, 'change', function (evt){ + var files = FileAPI.getFiles(evt.target); // or FileAPI.getFiles(evt) + + // filtering + FileAPI.filterFiles(files, function (file, info){ + if( /image/.test(file.type) && info ){ + return info.width >= 320 && info.height >= 240; + } + else { + return file.size > 128; + } + }, function (fileList, ignor){ + if( ignor.length ){ + // ... + } + + if( !fileList.length ){ + // empty file list + return; + } + + + // do preview + var imageList = FileAPI.filter(fileList, function (file){ return /image/.test(file.type); }); + FileAPI.each(imageList, function (imageFile){ + FileAPI.Image(imageFile) + .preview(100, 120) + .get(function (err, image){ + if( err ){ + // ... + } + else { + previewNode.appendChild(image); + } + }) + ; + }); + + + // upload on server + var xhr = FileAPI.upload({ + url: '...', + data: { foo: 'bar' }, // POST-data (iframe, flash, html5) + headers: { 'x-header': '...' }, , // request headers (flash, html5) + files: { + files: FileAPI.filter(fileList, function (file){ return !/image/.test(file.type); }), + pictures: imageList + }, + imageTransform: { + maxWidth: 1024, + maxHeight: 768 + }, + imageAutoOrientation: true, + fileprogress: function (evt){ // (flash, html5) + var percent = evt.loaded/evt.total*100; + // ... + }, + progress: function (evt){ // (flash, html5) + var percent = evt.loaded/evt.total*100; + // ... + }, + complete: function (err, xhr){ + // ... + } + }); + }); +}); +``` + +### HTML structure (templates) + * [Default](#html-default) + * [Button](#html-button) + * [Link](#html-link) + + +### API +* FileAPI.[getFiles](#getFiles)(`source:HTMLInput|Event`)`:Array` +* FileAPI.[getDropFiles](#getDropFiles)(`files:Array`, `callback:Function`) +* FileAPI.[filterFiles](#filterFiles)(`files:Array`, `iterator:Function`, `complete:Function`) +* FileAPI.[upload](#upload)(`options:Object`)`:XMLHttpRequest` +* FileAPI.[getInfo](#getInfo)(`file:File`, `callback:Function`) +* FileAPI.[readAsImage](#readAs)(`file:File`, `callback:function`) +* FileAPI.[readAsDataURL](#readAs)(`file:File`, `callback:function`) +* FileAPI.[readAsBinaryString](#readAs)(`file:File`, `callback:function`) +* FileAPI.[readAsArrayBuffer](#readAs)(`file:File`, `callback:function`) +* FileAPI.[readAsText](#readAs)(`file:File`, `callback:function`) +* FileAPI.[readAsText](#readAs)(`file:File`, `encoding:String`, `callback:function`) + + +### Events +* FileAPI.event.on(`el:HTMLElement`, `eventType:String`, `fn:Function`) +* FileAPI.event.off(`el:HTMLElement`, `eventType:String`, `fn:Function`) +* FileAPI.event.one(`el:HTMLElement`, `eventType:String`, `fn:Function`) +* FileAPI.event.dnd(`el:HTMLElement`, `onHover:Function`, `onDrop:Function`) +* jQuery('#el').dnd(onHover, onDrop) + + +### FileAPI.Image +* .crop(width[, height]) +* .crop(x, y, width[, height]) +* .resize(width[, height]) +* .resize(width, height, `type:Enum(min,max,preview)`) +* .preview(width[, height]) +* .rotate(deg) +* .get(`fn:Function`) + + + +### Utils +* FileAPI.KB +* FileAPI.MB +* FileAPI.GB +* FileAPI.TB +* FileAPI.support.`html5:Boolean` +* FileAPI.support.`cors:Boolean` +* FileAPI.support.`dnd:Boolean` +* FileAPI.support.`flash:Boolean` +* FileAPI.support.`canvas:Boolean` +* FileAPI.support.`dataURI:Boolean` +* FileAPI.each(`obj:Object|Array`, `fn:function`, `context:Mixed`) +* FileAPI.extend(`dst:Object`, `src:Object`)`:Object` +* FileAPI.filter(`list:Array`, `iterator:Function`)`:Array` +* FileAPI.isFile(`file:Mixed`)`:Boolean` +* FileAPI.toBinaryString(`str:Base64`)`:String` + + + +### Flash settings +```html + + +``` + + +--------------------------------------- + + + +### FileAPI.getFiles +```js +FileAPI.event.on('#my-file-1', 'change', onSelect); + +// or jQuery +$('#my-file-2').on('change', onSelect); + +function onSelect(evt/**Event*/){ + // (1) extract fileList from event + var files = FileAPI.getFiles(evt); + + // (2) or so + var files = FileAPI.getFiles(evt.target); +} +``` + + + +### FileAPI.getDropFiles +```js +function onDrop(evt){ + FileAPI.getDropFiles(evt, function (files){ + if( files.length ){ + // ... + } + }); +} + +// OR + +var el = document.getElementById('el'); +FileAPI.event.dnd(el, function (over/**Boolean*/, evt/**Event*/){ + el.style.background = ever ? 'red' : '': +}, function (files/**Array*/, evt/**Event*/){ + // ... +}); +``` + + + +### FileAPI.getInfo +```js +FileAPI.getInfo(imageFile/**File*/, function (err/**Boolean*/, info/**Object*/){ + if( !err ){ + switch( info.exif.Orientation ){ + // ... + } + } +}); + +// ... + +FileAPI.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){ + // http://www.nihilogic.dk/labs/exif/exif.js + // http://www.nihilogic.dk/labs/binaryajax/binaryajax.js + FileAPI.readAsBinaryString(file, function (evt){ + if( evt.type == 'load' ){ + var binaryString = evt.result; + var oFile = new BinaryFile(binaryString, 0, file.size); + var exif = EXIF.readFromBinaryFile(oFile); + callback(false, { 'exif': exif || {} }); + } + else if( evt.type == 'error' ){ + callback('read_as_binary_string'); + } + else if( evt.type == 'progress' ){ + // ... + } + }); +}); +``` + + + +### FileAPI.filterFiles +```js +FileAPI.filterFiles(files, function (file, info){ + if( /image/.test(file.type) && info ){ + return info.width > 320 && info.height > 240; + } + return file.size < 10 * FileAPI.MB; +}, function (result, ignor){ + // ... +}); +``` + + + +### FileAPI.readAsImage, FileAPI.readAsDataURL (FileAPI.readAsBinaryString) +```js +FileAPI.readAsImage(file, function (evt){ + if( evt.type == 'load' ){ + var images = document.getElementById('images'); + images.appendChild(evt.result); + } + else { + // ... + } +}); + + +FileAPI.readAsDataURL(file, function (evt){ + if( evt.type == 'load' ){ + // success + var result = evt.result; + } + else if( evt.type == 'progress' ){ + var pr = evt.loaded/evt.total * 100; + } + else { + // error + } +}); +``` + + + +### FileAPI.upload +```js +var xhr = FileAPI.upload({ + url: '...', + data: { foo: 'bar' }, + headers: { 'x-header': '...' }, + files: { + images: FileAPI.filter(files, function (file){ return /image/.test(file.type); }), + customFile: { file: 'generate.txt', blob: customFileBlob } + }, + imageTransform: { + maxWidth: 1024, + maxHeight: 768 + }, + imageAutoOrientation: true, + upload: function (xhr, options){ + // start uploading + }, + fileupload: function (xhr, options){ + // start file uploading + }, + fileprogress: function (evt){ + // progress file uploading + var filePercent = evt.loaded/evt.total*100; + }, + filecomplete: function (err, xhr){ + if( !err ){ + var response = xhr.responseText; + } + }, + progress: function (evt){ + // total progress uploading + var totalPercent = evt.loaded/evt.total*100; + }, + complete: function (err, xhr){ + if( !err ){ + // Congratulations, the uploading was successful! + } + } +}); +``` + + + +### imageTransform + * width`:Number` + * height`:Number` + * preview`:Boolean` + * maxWidth`:Number` + * maxHeight`:Number` + * rotate`:Number` +```js +FileAPI.upload({ + // .. + imageOriginal: false, // don't send original on server + + imageTransform: { + // (1) Resize to 120x200 + resize: { width: 120, height: 200 } + + // (2) create preview 320x240 + thumb: { width: 320, height: 240, preview: true } + + // (3) Resize by max side + max: { maxWidth: 800, maxHeight: 600 } + + // (4) Custom resize + custom: function (info, transform){ + return transform + .crop(100, 100, 300, 200) + .resize(100, 50) + ; + } + } +}); +``` + + + +### imageAutoOrientation +```js +// (1) all images +FileAPI.upload({ + // .. + imageAutoOrientation: true +}); + +// (2) or so +FileAPI.upload({ + // .. + imageAutoOrientation: true, + imageTransform: { width: .., height: .. } +}); + +// (3) or so +FileAPI.upload({ + // .. + imageTransform: { rotate: 'auto' } +}); + +// (4) only "800x600", original not modified +FileAPI.upload({ + // .. + imageTransform: { + "800x600": { width: 800, height: 600, rotate: 'auto' } + } +}); +``` + + +---- + + + +### File object (https://developer.mozilla.org/en/DOM/File) +```js +{ + name: 'fileName', + type: 'mime-type', + size: 'fileSize' +} +``` + + +### XMLHttpRequest +```js +{ + status: Number, + statusText: Number, + readyState: Number, + response: Blob, + responseXML: XML, + responseText: String, + responseBody: String, + getResponseHeader: function (name/**String*/)/**String*/{}, + getAllResponseHeaders: function ()/**Object*/{}, + abort: function (){} +} +``` + + +### Cross-Domain upload-controller headers +```php + +``` + + +### iframe +#### POST-query +``` +/controller.php + ?foo=bar + &images=... + &callback=... +``` + + +#### POST-response +```php + +``` + +--- + + + +### HTML structure (templates) + + +### Default +```html + + + +``` + + + +### Button +```html + +
+
Upload files
+ +
+``` + + + +### Link +```html + + + Upload photo + + +``` diff --git a/crossdomain.xml b/crossdomain.xml new file mode 100644 index 00000000..a0b1c41f --- /dev/null +++ b/crossdomain.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/flash/.actionScriptProperties b/flash/.actionScriptProperties new file mode 100644 index 00000000..5c1915b8 --- /dev/null +++ b/flash/.actionScriptProperties @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flash/.settings/org.eclipse.core.resources.prefs b/flash/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..f114bbbe --- /dev/null +++ b/flash/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +#Thu Aug 02 18:18:41 MSD 2012 +eclipse.preferences.version=1 +encoding/=utf-8 diff --git a/flash/html-template/history/history.css b/flash/html-template/history/history.css new file mode 100644 index 00000000..dbc47c61 --- /dev/null +++ b/flash/html-template/history/history.css @@ -0,0 +1,6 @@ +/* This CSS stylesheet defines styles used by required elements in a flex application page that supports browser history */ + +#ie_historyFrame { width: 0px; height: 0px; display:none } +#firefox_anchorDiv { width: 0px; height: 0px; display:none } +#safari_formDiv { width: 0px; height: 0px; display:none } +#safari_rememberDiv { width: 0px; height: 0px; display:none } diff --git a/flash/html-template/history/history.js b/flash/html-template/history/history.js new file mode 100644 index 00000000..69353e34 --- /dev/null +++ b/flash/html-template/history/history.js @@ -0,0 +1,697 @@ +BrowserHistoryUtils = { + addEvent: function(elm, evType, fn, useCapture) { + useCapture = useCapture || false; + if (elm.addEventListener) { + elm.addEventListener(evType, fn, useCapture); + return true; + } + else if (elm.attachEvent) { + var r = elm.attachEvent('on' + evType, fn); + return r; + } + else { + elm['on' + evType] = fn; + } + } +} + +BrowserHistory = (function() { + // type of browser + var browser = { + ie: false, + ie8: false, + firefox: false, + safari: false, + opera: false, + version: -1 + }; + + // if setDefaultURL has been called, our first clue + // that the SWF is ready and listening + //var swfReady = false; + + // the URL we'll send to the SWF once it is ready + //var pendingURL = ''; + + // Default app state URL to use when no fragment ID present + var defaultHash = ''; + + // Last-known app state URL + var currentHref = document.location.href; + + // Initial URL (used only by IE) + var initialHref = document.location.href; + + // Initial URL (used only by IE) + var initialHash = document.location.hash; + + // History frame source URL prefix (used only by IE) + var historyFrameSourcePrefix = 'history/historyFrame.html?'; + + // History maintenance (used only by Safari) + var currentHistoryLength = -1; + + var historyHash = []; + + var initialState = createState(initialHref, initialHref + '#' + initialHash, initialHash); + + var backStack = []; + var forwardStack = []; + + var currentObjectId = null; + + //UserAgent detection + var useragent = navigator.userAgent.toLowerCase(); + + if (useragent.indexOf("opera") != -1) { + browser.opera = true; + } else if (useragent.indexOf("msie") != -1) { + browser.ie = true; + browser.version = parseFloat(useragent.substring(useragent.indexOf('msie') + 4)); + if (browser.version == 8) + { + browser.ie = false; + browser.ie8 = true; + } + } else if (useragent.indexOf("safari") != -1) { + browser.safari = true; + browser.version = parseFloat(useragent.substring(useragent.indexOf('safari') + 7)); + } else if (useragent.indexOf("gecko") != -1) { + browser.firefox = true; + } + + if (browser.ie == true && browser.version == 7) { + window["_ie_firstload"] = false; + } + + function hashChangeHandler() + { + currentHref = document.location.href; + var flexAppUrl = getHash(); + //ADR: to fix multiple + if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { + var pl = getPlayers(); + for (var i = 0; i < pl.length; i++) { + pl[i].browserURLChange(flexAppUrl); + } + } else { + getPlayer().browserURLChange(flexAppUrl); + } + } + + // Accessor functions for obtaining specific elements of the page. + function getHistoryFrame() + { + return document.getElementById('ie_historyFrame'); + } + + function getAnchorElement() + { + return document.getElementById('firefox_anchorDiv'); + } + + function getFormElement() + { + return document.getElementById('safari_formDiv'); + } + + function getRememberElement() + { + return document.getElementById("safari_remember_field"); + } + + // Get the Flash player object for performing ExternalInterface callbacks. + // Updated for changes to SWFObject2. + function getPlayer(id) { + var i; + + if (id && document.getElementById(id)) { + var r = document.getElementById(id); + if (typeof r.SetVariable != "undefined") { + return r; + } + else { + var o = r.getElementsByTagName("object"); + var e = r.getElementsByTagName("embed"); + for (i = 0; i < o.length; i++) { + if (typeof o[i].browserURLChange != "undefined") + return o[i]; + } + for (i = 0; i < e.length; i++) { + if (typeof e[i].browserURLChange != "undefined") + return e[i]; + } + } + } + else { + var o = document.getElementsByTagName("object"); + var e = document.getElementsByTagName("embed"); + for (i = 0; i < e.length; i++) { + if (typeof e[i].browserURLChange != "undefined") + { + return e[i]; + } + } + for (i = 0; i < o.length; i++) { + if (typeof o[i].browserURLChange != "undefined") + { + return o[i]; + } + } + } + return undefined; + } + + function getPlayers() { + var i; + var players = []; + if (players.length == 0) { + var tmp = document.getElementsByTagName('object'); + for (i = 0; i < tmp.length; i++) + { + if (typeof tmp[i].browserURLChange != "undefined") + players.push(tmp[i]); + } + } + if (players.length == 0 || players[0].object == null) { + var tmp = document.getElementsByTagName('embed'); + for (i = 0; i < tmp.length; i++) + { + if (typeof tmp[i].browserURLChange != "undefined") + players.push(tmp[i]); + } + } + return players; + } + + function getIframeHash() { + var doc = getHistoryFrame().contentWindow.document; + var hash = String(doc.location.search); + if (hash.length == 1 && hash.charAt(0) == "?") { + hash = ""; + } + else if (hash.length >= 2 && hash.charAt(0) == "?") { + hash = hash.substring(1); + } + return hash; + } + + /* Get the current location hash excluding the '#' symbol. */ + function getHash() { + // It would be nice if we could use document.location.hash here, + // but it's faulty sometimes. + var idx = document.location.href.indexOf('#'); + return (idx >= 0) ? document.location.href.substr(idx+1) : ''; + } + + /* Get the current location hash excluding the '#' symbol. */ + function setHash(hash) { + // It would be nice if we could use document.location.hash here, + // but it's faulty sometimes. + if (hash == '') hash = '#' + document.location.hash = hash; + } + + function createState(baseUrl, newUrl, flexAppUrl) { + return { 'baseUrl': baseUrl, 'newUrl': newUrl, 'flexAppUrl': flexAppUrl, 'title': null }; + } + + /* Add a history entry to the browser. + * baseUrl: the portion of the location prior to the '#' + * newUrl: the entire new URL, including '#' and following fragment + * flexAppUrl: the portion of the location following the '#' only + */ + function addHistoryEntry(baseUrl, newUrl, flexAppUrl) { + + //delete all the history entries + forwardStack = []; + + if (browser.ie) { + //Check to see if we are being asked to do a navigate for the first + //history entry, and if so ignore, because it's coming from the creation + //of the history iframe + if (flexAppUrl == defaultHash && document.location.href == initialHref && window['_ie_firstload']) { + currentHref = initialHref; + return; + } + if ((!flexAppUrl || flexAppUrl == defaultHash) && window['_ie_firstload']) { + newUrl = baseUrl + '#' + defaultHash; + flexAppUrl = defaultHash; + } else { + // for IE, tell the history frame to go somewhere without a '#' + // in order to get this entry into the browser history. + getHistoryFrame().src = historyFrameSourcePrefix + flexAppUrl; + } + setHash(flexAppUrl); + } else { + + //ADR + if (backStack.length == 0 && initialState.flexAppUrl == flexAppUrl) { + initialState = createState(baseUrl, newUrl, flexAppUrl); + } else if(backStack.length > 0 && backStack[backStack.length - 1].flexAppUrl == flexAppUrl) { + backStack[backStack.length - 1] = createState(baseUrl, newUrl, flexAppUrl); + } + + if (browser.safari) { + // for Safari, submit a form whose action points to the desired URL + if (browser.version <= 419.3) { + var file = window.location.pathname.toString(); + file = file.substring(file.lastIndexOf("/")+1); + getFormElement().innerHTML = '
'; + //get the current elements and add them to the form + var qs = window.location.search.substring(1); + var qs_arr = qs.split("&"); + for (var i = 0; i < qs_arr.length; i++) { + var tmp = qs_arr[i].split("="); + var elem = document.createElement("input"); + elem.type = "hidden"; + elem.name = tmp[0]; + elem.value = tmp[1]; + document.forms.historyForm.appendChild(elem); + } + document.forms.historyForm.submit(); + } else { + top.location.hash = flexAppUrl; + } + // We also have to maintain the history by hand for Safari + historyHash[history.length] = flexAppUrl; + _storeStates(); + } else { + // Otherwise, just tell the browser to go there + setHash(flexAppUrl); + } + } + backStack.push(createState(baseUrl, newUrl, flexAppUrl)); + } + + function _storeStates() { + if (browser.safari) { + getRememberElement().value = historyHash.join(","); + } + } + + function handleBackButton() { + //The "current" page is always at the top of the history stack. + var current = backStack.pop(); + if (!current) { return; } + var last = backStack[backStack.length - 1]; + if (!last && backStack.length == 0){ + last = initialState; + } + forwardStack.push(current); + } + + function handleForwardButton() { + //summary: private method. Do not call this directly. + + var last = forwardStack.pop(); + if (!last) { return; } + backStack.push(last); + } + + function handleArbitraryUrl() { + //delete all the history entries + forwardStack = []; + } + + /* Called periodically to poll to see if we need to detect navigation that has occurred */ + function checkForUrlChange() { + + if (browser.ie) { + if (currentHref != document.location.href && currentHref + '#' != document.location.href) { + //This occurs when the user has navigated to a specific URL + //within the app, and didn't use browser back/forward + //IE seems to have a bug where it stops updating the URL it + //shows the end-user at this point, but programatically it + //appears to be correct. Do a full app reload to get around + //this issue. + if (browser.version < 7) { + currentHref = document.location.href; + document.location.reload(); + } else { + if (getHash() != getIframeHash()) { + // this.iframe.src = this.blankURL + hash; + var sourceToSet = historyFrameSourcePrefix + getHash(); + getHistoryFrame().src = sourceToSet; + currentHref = document.location.href; + } + } + } + } + + if (browser.safari) { + // For Safari, we have to check to see if history.length changed. + if (currentHistoryLength >= 0 && history.length != currentHistoryLength) { + //alert("did change: " + history.length + ", " + historyHash.length + "|" + historyHash[history.length] + "|>" + historyHash.join("|")); + var flexAppUrl = getHash(); + if (browser.version < 528.16 /* Anything earlier than Safari 4.0 */) + { + // If it did change and we're running Safari 3.x or earlier, + // then we have to look the old state up in our hand-maintained + // array since document.location.hash won't have changed, + // then call back into BrowserManager. + currentHistoryLength = history.length; + flexAppUrl = historyHash[currentHistoryLength]; + } + + //ADR: to fix multiple + if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { + var pl = getPlayers(); + for (var i = 0; i < pl.length; i++) { + pl[i].browserURLChange(flexAppUrl); + } + } else { + getPlayer().browserURLChange(flexAppUrl); + } + _storeStates(); + } + } + if (browser.firefox) { + if (currentHref != document.location.href) { + var bsl = backStack.length; + + var urlActions = { + back: false, + forward: false, + set: false + } + + if ((window.location.hash == initialHash || window.location.href == initialHref) && (bsl == 1)) { + urlActions.back = true; + // FIXME: could this ever be a forward button? + // we can't clear it because we still need to check for forwards. Ugg. + // clearInterval(this.locationTimer); + handleBackButton(); + } + + // first check to see if we could have gone forward. We always halt on + // a no-hash item. + if (forwardStack.length > 0) { + if (forwardStack[forwardStack.length-1].flexAppUrl == getHash()) { + urlActions.forward = true; + handleForwardButton(); + } + } + + // ok, that didn't work, try someplace back in the history stack + if ((bsl >= 2) && (backStack[bsl - 2])) { + if (backStack[bsl - 2].flexAppUrl == getHash()) { + urlActions.back = true; + handleBackButton(); + } + } + + if (!urlActions.back && !urlActions.forward) { + var foundInStacks = { + back: -1, + forward: -1 + } + + for (var i = 0; i < backStack.length; i++) { + if (backStack[i].flexAppUrl == getHash() && i != (bsl - 2)) { + arbitraryUrl = true; + foundInStacks.back = i; + } + } + for (var i = 0; i < forwardStack.length; i++) { + if (forwardStack[i].flexAppUrl == getHash() && i != (bsl - 2)) { + arbitraryUrl = true; + foundInStacks.forward = i; + } + } + handleArbitraryUrl(); + } + + // Firefox changed; do a callback into BrowserManager to tell it. + currentHref = document.location.href; + var flexAppUrl = getHash(); + //ADR: to fix multiple + if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { + var pl = getPlayers(); + for (var i = 0; i < pl.length; i++) { + pl[i].browserURLChange(flexAppUrl); + } + } else { + getPlayer().browserURLChange(flexAppUrl); + } + } + } + //setTimeout(checkForUrlChange, 50); + } + + var _initialize = function () { + if (browser.ie) + { + var scripts = document.getElementsByTagName('script'); + for (var i = 0, s; s = scripts[i]; i++) { + if (s.src.indexOf("history.js") > -1) { + var iframe_location = (new String(s.src)).replace("history.js", "historyFrame.html"); + } + } + historyFrameSourcePrefix = iframe_location + "?"; + var src = historyFrameSourcePrefix; + + var iframe = document.createElement("iframe"); + iframe.id = 'ie_historyFrame'; + iframe.name = 'ie_historyFrame'; + iframe.src = 'javascript:false;'; + + try { + document.body.appendChild(iframe); + } catch(e) { + setTimeout(function() { + document.body.appendChild(iframe); + }, 0); + } + } + + if (browser.safari) + { + var rememberDiv = document.createElement("div"); + rememberDiv.id = 'safari_rememberDiv'; + document.body.appendChild(rememberDiv); + rememberDiv.innerHTML = ''; + + var formDiv = document.createElement("div"); + formDiv.id = 'safari_formDiv'; + document.body.appendChild(formDiv); + + var reloader_content = document.createElement('div'); + reloader_content.id = 'safarireloader'; + var scripts = document.getElementsByTagName('script'); + for (var i = 0, s; s = scripts[i]; i++) { + if (s.src.indexOf("history.js") > -1) { + html = (new String(s.src)).replace(".js", ".html"); + } + } + reloader_content.innerHTML = ''; + document.body.appendChild(reloader_content); + reloader_content.style.position = 'absolute'; + reloader_content.style.left = reloader_content.style.top = '-9999px'; + iframe = reloader_content.getElementsByTagName('iframe')[0]; + + if (document.getElementById("safari_remember_field").value != "" ) { + historyHash = document.getElementById("safari_remember_field").value.split(","); + } + + } + + if (browser.firefox || browser.ie8) + { + var anchorDiv = document.createElement("div"); + anchorDiv.id = 'firefox_anchorDiv'; + document.body.appendChild(anchorDiv); + } + + if (browser.ie8) + document.body.onhashchange = hashChangeHandler; + //setTimeout(checkForUrlChange, 50); + } + + return { + historyHash: historyHash, + backStack: function() { return backStack; }, + forwardStack: function() { return forwardStack }, + getPlayer: getPlayer, + initialize: function(src) { + _initialize(src); + }, + setURL: function(url) { + document.location.href = url; + }, + getURL: function() { + return document.location.href; + }, + getTitle: function() { + return document.title; + }, + setTitle: function(title) { + try { + backStack[backStack.length - 1].title = title; + } catch(e) { } + //if on safari, set the title to be the empty string. + if (browser.safari) { + if (title == "") { + try { + var tmp = window.location.href.toString(); + title = tmp.substring((tmp.lastIndexOf("/")+1), tmp.lastIndexOf("#")); + } catch(e) { + title = ""; + } + } + } + document.title = title; + }, + setDefaultURL: function(def) + { + defaultHash = def; + def = getHash(); + //trailing ? is important else an extra frame gets added to the history + //when navigating back to the first page. Alternatively could check + //in history frame navigation to compare # and ?. + if (browser.ie) + { + window['_ie_firstload'] = true; + var sourceToSet = historyFrameSourcePrefix + def; + var func = function() { + getHistoryFrame().src = sourceToSet; + window.location.replace("#" + def); + setInterval(checkForUrlChange, 50); + } + try { + func(); + } catch(e) { + window.setTimeout(function() { func(); }, 0); + } + } + + if (browser.safari) + { + currentHistoryLength = history.length; + if (historyHash.length == 0) { + historyHash[currentHistoryLength] = def; + var newloc = "#" + def; + window.location.replace(newloc); + } else { + //alert(historyHash[historyHash.length-1]); + } + //setHash(def); + setInterval(checkForUrlChange, 50); + } + + + if (browser.firefox || browser.opera) + { + var reg = new RegExp("#" + def + "$"); + if (window.location.toString().match(reg)) { + } else { + var newloc ="#" + def; + window.location.replace(newloc); + } + setInterval(checkForUrlChange, 50); + //setHash(def); + } + + }, + + /* Set the current browser URL; called from inside BrowserManager to propagate + * the application state out to the container. + */ + setBrowserURL: function(flexAppUrl, objectId) { + if (browser.ie && typeof objectId != "undefined") { + currentObjectId = objectId; + } + //fromIframe = fromIframe || false; + //fromFlex = fromFlex || false; + //alert("setBrowserURL: " + flexAppUrl); + //flexAppUrl = (flexAppUrl == "") ? defaultHash : flexAppUrl ; + + var pos = document.location.href.indexOf('#'); + var baseUrl = pos != -1 ? document.location.href.substr(0, pos) : document.location.href; + var newUrl = baseUrl + '#' + flexAppUrl; + + if (document.location.href != newUrl && document.location.href + '#' != newUrl) { + currentHref = newUrl; + addHistoryEntry(baseUrl, newUrl, flexAppUrl); + currentHistoryLength = history.length; + } + }, + + browserURLChange: function(flexAppUrl) { + var objectId = null; + if (browser.ie && currentObjectId != null) { + objectId = currentObjectId; + } + pendingURL = ''; + + if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) { + var pl = getPlayers(); + for (var i = 0; i < pl.length; i++) { + try { + pl[i].browserURLChange(flexAppUrl); + } catch(e) { } + } + } else { + try { + getPlayer(objectId).browserURLChange(flexAppUrl); + } catch(e) { } + } + + currentObjectId = null; + }, + getUserAgent: function() { + return navigator.userAgent; + }, + getPlatform: function() { + return navigator.platform; + } + + } + +})(); + +// Initialization + +// Automated unit testing and other diagnostics + +function setURL(url) +{ + document.location.href = url; +} + +function backButton() +{ + history.back(); +} + +function forwardButton() +{ + history.forward(); +} + +function goForwardOrBackInHistory(step) +{ + history.go(step); +} + +//BrowserHistoryUtils.addEvent(window, "load", function() { BrowserHistory.initialize(); }); +(function(i) { + var u =navigator.userAgent;var e=/*@cc_on!@*/false; + var st = setTimeout; + if(/webkit/i.test(u)){ + st(function(){ + var dr=document.readyState; + if(dr=="loaded"||dr=="complete"){i()} + else{st(arguments.callee,10);}},10); + } else if((/mozilla/i.test(u)&&!/(compati)/.test(u)) || (/opera/i.test(u))){ + document.addEventListener("DOMContentLoaded",i,false); + } else if(e){ + (function(){ + var t=document.createElement('doc:rdy'); + try{t.doScroll('left'); + i();t=null; + }catch(e){st(arguments.callee,0);}})(); + } else{ + window.onload=i; + } +})( function() {BrowserHistory.initialize();} ); diff --git a/flash/html-template/history/historyFrame.html b/flash/html-template/history/historyFrame.html new file mode 100644 index 00000000..07e3806f --- /dev/null +++ b/flash/html-template/history/historyFrame.html @@ -0,0 +1,29 @@ + + + + + + + + Hidden frame for Browser History support. + + diff --git a/flash/html-template/index.template.html b/flash/html-template/index.template.html new file mode 100644 index 00000000..c008b26f --- /dev/null +++ b/flash/html-template/index.template.html @@ -0,0 +1,108 @@ + + + + + + ${title} + + + + + + + + + + + + + +
+

+ To view this page ensure that Adobe Flash Player version + ${version_major}.${version_minor}.${version_revision} or greater is installed. +

+ +
+ + + + diff --git a/flash/html-template/playerProductInstall.swf b/flash/html-template/playerProductInstall.swf new file mode 100644 index 0000000000000000000000000000000000000000..bdc3437856cb0ae54bb9423700ba6ec89f35282c GIT binary patch literal 657 zcmV;C0&e|7S5pT30{{ScoOM%8YZE~jel|&yP13aKhpyI8)Pu*a7cZru!IYFhQ!%kE z;zt>GCuViByG~|XgJ(bJp~q+`f){V4c=Tk!n-K9Tcv0|Qh}4-)Z5q%$%)ak4^FGh} zG4mKh>p=QFFsz{%nRVYDpaejJ54{eo7BqCbU1T$6A?_UfdU|tzr7->Edg5~QJ`!s!OO;20fpVHsv=YsdQ{lvI<6(v2yrdS7`6LQi8#uy`}SX?Ni6O)r6`%Pye4YxmY_|vs1r$ zYS62JJit*Qq5)1^TgC)o6KZQEG=%VHP4mC>rXSoh>x{G-y~501s42xbb%oj5teYed zUazfuM*8z%%PQW6Lm}m@hf8V%w#-l*H+45VumzOSNh6MI zfwTnD68J}cRU>5EordGe5i?c`=PYmF$$^*q^20~+G3o1>u%sdEL~sE;XrvE~96_TY z1oQ%v(6hbD$HElG!xl|cjZOyoWIw@E;qK|`oT|Xk&*+o6#W@8T{oeJ8I9m-_V~Lc) rrowHdr=QEu;UGRC%}bhmYZ>GFiMOQQ+m?EJQA~aL;e`Gll9T{P!97I~ literal 0 HcmV?d00001 diff --git a/flash/html-template/swfobject.js b/flash/html-template/swfobject.js new file mode 100644 index 00000000..bf35c07c --- /dev/null +++ b/flash/html-template/swfobject.js @@ -0,0 +1,777 @@ +/*! SWFObject v2.2 + is released under the MIT License +*/ + +var swfobject = function() { + + var UNDEF = "undefined", + OBJECT = "object", + SHOCKWAVE_FLASH = "Shockwave Flash", + SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash", + FLASH_MIME_TYPE = "application/x-shockwave-flash", + EXPRESS_INSTALL_ID = "SWFObjectExprInst", + ON_READY_STATE_CHANGE = "onreadystatechange", + + win = window, + doc = document, + nav = navigator, + + plugin = false, + domLoadFnArr = [main], + regObjArr = [], + objIdArr = [], + listenersArr = [], + storedAltContent, + storedAltContentId, + storedCallbackFn, + storedCallbackObj, + isDomLoaded = false, + isExpressInstallActive = false, + dynamicStylesheet, + dynamicStylesheetMedia, + autoHideShow = true, + + /* Centralized function for browser feature detection + - User agent string detection is only used when no good alternative is possible + - Is executed directly for optimal performance + */ + ua = function() { + var w3cdom = typeof doc.getElementById != UNDEF && typeof doc.getElementsByTagName != UNDEF && typeof doc.createElement != UNDEF, + u = nav.userAgent.toLowerCase(), + p = nav.platform.toLowerCase(), + windows = p ? /win/.test(p) : /win/.test(u), + mac = p ? /mac/.test(p) : /mac/.test(u), + webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false, // returns either the webkit version or false if not webkit + ie = !+"\v1", // feature detection based on Andrea Giammarchi's solution: http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html + playerVersion = [0,0,0], + d = null; + if (typeof nav.plugins != UNDEF && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) { + d = nav.plugins[SHOCKWAVE_FLASH].description; + if (d && !(typeof nav.mimeTypes != UNDEF && nav.mimeTypes[FLASH_MIME_TYPE] && !nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { // navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin indicates whether plug-ins are enabled or disabled in Safari 3+ + plugin = true; + ie = false; // cascaded feature detection for Internet Explorer + d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1"); + playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10); + playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), 10); + playerVersion[2] = /[a-zA-Z]/.test(d) ? parseInt(d.replace(/^.*[a-zA-Z]+(.*)$/, "$1"), 10) : 0; + } + } + else if (typeof win.ActiveXObject != UNDEF) { + try { + var a = new ActiveXObject(SHOCKWAVE_FLASH_AX); + if (a) { // a will return null when ActiveX is disabled + d = a.GetVariable("$version"); + if (d) { + ie = true; // cascaded feature detection for Internet Explorer + d = d.split(" ")[1].split(","); + playerVersion = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; + } + } + } + catch(e) {} + } + return { w3:w3cdom, pv:playerVersion, wk:webkit, ie:ie, win:windows, mac:mac }; + }(), + + /* Cross-browser onDomLoad + - Will fire an event as soon as the DOM of a web page is loaded + - Internet Explorer workaround based on Diego Perini's solution: http://javascript.nwbox.com/IEContentLoaded/ + - Regular onload serves as fallback + */ + onDomLoad = function() { + if (!ua.w3) { return; } + if ((typeof doc.readyState != UNDEF && doc.readyState == "complete") || (typeof doc.readyState == UNDEF && (doc.getElementsByTagName("body")[0] || doc.body))) { // function is fired after onload, e.g. when script is inserted dynamically + callDomLoadFunctions(); + } + if (!isDomLoaded) { + if (typeof doc.addEventListener != UNDEF) { + doc.addEventListener("DOMContentLoaded", callDomLoadFunctions, false); + } + if (ua.ie && ua.win) { + doc.attachEvent(ON_READY_STATE_CHANGE, function() { + if (doc.readyState == "complete") { + doc.detachEvent(ON_READY_STATE_CHANGE, arguments.callee); + callDomLoadFunctions(); + } + }); + if (win == top) { // if not inside an iframe + (function(){ + if (isDomLoaded) { return; } + try { + doc.documentElement.doScroll("left"); + } + catch(e) { + setTimeout(arguments.callee, 0); + return; + } + callDomLoadFunctions(); + })(); + } + } + if (ua.wk) { + (function(){ + if (isDomLoaded) { return; } + if (!/loaded|complete/.test(doc.readyState)) { + setTimeout(arguments.callee, 0); + return; + } + callDomLoadFunctions(); + })(); + } + addLoadEvent(callDomLoadFunctions); + } + }(); + + function callDomLoadFunctions() { + if (isDomLoaded) { return; } + try { // test if we can really add/remove elements to/from the DOM; we don't want to fire it too early + var t = doc.getElementsByTagName("body")[0].appendChild(createElement("span")); + t.parentNode.removeChild(t); + } + catch (e) { return; } + isDomLoaded = true; + var dl = domLoadFnArr.length; + for (var i = 0; i < dl; i++) { + domLoadFnArr[i](); + } + } + + function addDomLoadEvent(fn) { + if (isDomLoaded) { + fn(); + } + else { + domLoadFnArr[domLoadFnArr.length] = fn; // Array.push() is only available in IE5.5+ + } + } + + /* Cross-browser onload + - Based on James Edwards' solution: http://brothercake.com/site/resources/scripts/onload/ + - Will fire an event as soon as a web page including all of its assets are loaded + */ + function addLoadEvent(fn) { + if (typeof win.addEventListener != UNDEF) { + win.addEventListener("load", fn, false); + } + else if (typeof doc.addEventListener != UNDEF) { + doc.addEventListener("load", fn, false); + } + else if (typeof win.attachEvent != UNDEF) { + addListener(win, "onload", fn); + } + else if (typeof win.onload == "function") { + var fnOld = win.onload; + win.onload = function() { + fnOld(); + fn(); + }; + } + else { + win.onload = fn; + } + } + + /* Main function + - Will preferably execute onDomLoad, otherwise onload (as a fallback) + */ + function main() { + if (plugin) { + testPlayerVersion(); + } + else { + matchVersions(); + } + } + + /* Detect the Flash Player version for non-Internet Explorer browsers + - Detecting the plug-in version via the object element is more precise than using the plugins collection item's description: + a. Both release and build numbers can be detected + b. Avoid wrong descriptions by corrupt installers provided by Adobe + c. Avoid wrong descriptions by multiple Flash Player entries in the plugin Array, caused by incorrect browser imports + - Disadvantage of this method is that it depends on the availability of the DOM, while the plugins collection is immediately available + */ + function testPlayerVersion() { + var b = doc.getElementsByTagName("body")[0]; + var o = createElement(OBJECT); + o.setAttribute("type", FLASH_MIME_TYPE); + var t = b.appendChild(o); + if (t) { + var counter = 0; + (function(){ + if (typeof t.GetVariable != UNDEF) { + var d = t.GetVariable("$version"); + if (d) { + d = d.split(" ")[1].split(","); + ua.pv = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; + } + } + else if (counter < 10) { + counter++; + setTimeout(arguments.callee, 10); + return; + } + b.removeChild(o); + t = null; + matchVersions(); + })(); + } + else { + matchVersions(); + } + } + + /* Perform Flash Player and SWF version matching; static publishing only + */ + function matchVersions() { + var rl = regObjArr.length; + if (rl > 0) { + for (var i = 0; i < rl; i++) { // for each registered object element + var id = regObjArr[i].id; + var cb = regObjArr[i].callbackFn; + var cbObj = {success:false, id:id}; + if (ua.pv[0] > 0) { + var obj = getElementById(id); + if (obj) { + if (hasPlayerVersion(regObjArr[i].swfVersion) && !(ua.wk && ua.wk < 312)) { // Flash Player version >= published SWF version: Houston, we have a match! + setVisibility(id, true); + if (cb) { + cbObj.success = true; + cbObj.ref = getObjectById(id); + cb(cbObj); + } + } + else if (regObjArr[i].expressInstall && canExpressInstall()) { // show the Adobe Express Install dialog if set by the web page author and if supported + var att = {}; + att.data = regObjArr[i].expressInstall; + att.width = obj.getAttribute("width") || "0"; + att.height = obj.getAttribute("height") || "0"; + if (obj.getAttribute("class")) { att.styleclass = obj.getAttribute("class"); } + if (obj.getAttribute("align")) { att.align = obj.getAttribute("align"); } + // parse HTML object param element's name-value pairs + var par = {}; + var p = obj.getElementsByTagName("param"); + var pl = p.length; + for (var j = 0; j < pl; j++) { + if (p[j].getAttribute("name").toLowerCase() != "movie") { + par[p[j].getAttribute("name")] = p[j].getAttribute("value"); + } + } + showExpressInstall(att, par, id, cb); + } + else { // Flash Player and SWF version mismatch or an older Webkit engine that ignores the HTML object element's nested param elements: display alternative content instead of SWF + displayAltContent(obj); + if (cb) { cb(cbObj); } + } + } + } + else { // if no Flash Player is installed or the fp version cannot be detected we let the HTML object element do its job (either show a SWF or alternative content) + setVisibility(id, true); + if (cb) { + var o = getObjectById(id); // test whether there is an HTML object element or not + if (o && typeof o.SetVariable != UNDEF) { + cbObj.success = true; + cbObj.ref = o; + } + cb(cbObj); + } + } + } + } + } + + function getObjectById(objectIdStr) { + var r = null; + var o = getElementById(objectIdStr); + if (o && o.nodeName == "OBJECT") { + if (typeof o.SetVariable != UNDEF) { + r = o; + } + else { + var n = o.getElementsByTagName(OBJECT)[0]; + if (n) { + r = n; + } + } + } + return r; + } + + /* Requirements for Adobe Express Install + - only one instance can be active at a time + - fp 6.0.65 or higher + - Win/Mac OS only + - no Webkit engines older than version 312 + */ + function canExpressInstall() { + return !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac) && !(ua.wk && ua.wk < 312); + } + + /* Show the Adobe Express Install dialog + - Reference: http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75 + */ + function showExpressInstall(att, par, replaceElemIdStr, callbackFn) { + isExpressInstallActive = true; + storedCallbackFn = callbackFn || null; + storedCallbackObj = {success:false, id:replaceElemIdStr}; + var obj = getElementById(replaceElemIdStr); + if (obj) { + if (obj.nodeName == "OBJECT") { // static publishing + storedAltContent = abstractAltContent(obj); + storedAltContentId = null; + } + else { // dynamic publishing + storedAltContent = obj; + storedAltContentId = replaceElemIdStr; + } + att.id = EXPRESS_INSTALL_ID; + if (typeof att.width == UNDEF || (!/%$/.test(att.width) && parseInt(att.width, 10) < 310)) { att.width = "310"; } + if (typeof att.height == UNDEF || (!/%$/.test(att.height) && parseInt(att.height, 10) < 137)) { att.height = "137"; } + doc.title = doc.title.slice(0, 47) + " - Flash Player Installation"; + var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn", + fv = "MMredirectURL=" + encodeURI(window.location).toString().replace(/&/g,"%26") + "&MMplayerType=" + pt + "&MMdoctitle=" + doc.title; + if (typeof par.flashvars != UNDEF) { + par.flashvars += "&" + fv; + } + else { + par.flashvars = fv; + } + // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it, + // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work + if (ua.ie && ua.win && obj.readyState != 4) { + var newObj = createElement("div"); + replaceElemIdStr += "SWFObjectNew"; + newObj.setAttribute("id", replaceElemIdStr); + obj.parentNode.insertBefore(newObj, obj); // insert placeholder div that will be replaced by the object element that loads expressinstall.swf + obj.style.display = "none"; + (function(){ + if (obj.readyState == 4) { + obj.parentNode.removeChild(obj); + } + else { + setTimeout(arguments.callee, 10); + } + })(); + } + createSWF(att, par, replaceElemIdStr); + } + } + + /* Functions to abstract and display alternative content + */ + function displayAltContent(obj) { + if (ua.ie && ua.win && obj.readyState != 4) { + // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it, + // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work + var el = createElement("div"); + obj.parentNode.insertBefore(el, obj); // insert placeholder div that will be replaced by the alternative content + el.parentNode.replaceChild(abstractAltContent(obj), el); + obj.style.display = "none"; + (function(){ + if (obj.readyState == 4) { + obj.parentNode.removeChild(obj); + } + else { + setTimeout(arguments.callee, 10); + } + })(); + } + else { + obj.parentNode.replaceChild(abstractAltContent(obj), obj); + } + } + + function abstractAltContent(obj) { + var ac = createElement("div"); + if (ua.win && ua.ie) { + ac.innerHTML = obj.innerHTML; + } + else { + var nestedObj = obj.getElementsByTagName(OBJECT)[0]; + if (nestedObj) { + var c = nestedObj.childNodes; + if (c) { + var cl = c.length; + for (var i = 0; i < cl; i++) { + if (!(c[i].nodeType == 1 && c[i].nodeName == "PARAM") && !(c[i].nodeType == 8)) { + ac.appendChild(c[i].cloneNode(true)); + } + } + } + } + } + return ac; + } + + /* Cross-browser dynamic SWF creation + */ + function createSWF(attObj, parObj, id) { + var r, el = getElementById(id); + if (ua.wk && ua.wk < 312) { return r; } + if (el) { + if (typeof attObj.id == UNDEF) { // if no 'id' is defined for the object element, it will inherit the 'id' from the alternative content + attObj.id = id; + } + if (ua.ie && ua.win) { // Internet Explorer + the HTML object element + W3C DOM methods do not combine: fall back to outerHTML + var att = ""; + for (var i in attObj) { + if (attObj[i] != Object.prototype[i]) { // filter out prototype additions from other potential libraries + if (i.toLowerCase() == "data") { + parObj.movie = attObj[i]; + } + else if (i.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword + att += ' class="' + attObj[i] + '"'; + } + else if (i.toLowerCase() != "classid") { + att += ' ' + i + '="' + attObj[i] + '"'; + } + } + } + var par = ""; + for (var j in parObj) { + if (parObj[j] != Object.prototype[j]) { // filter out prototype additions from other potential libraries + par += ''; + } + } + el.outerHTML = '' + par + ''; + objIdArr[objIdArr.length] = attObj.id; // stored to fix object 'leaks' on unload (dynamic publishing only) + r = getElementById(attObj.id); + } + else { // well-behaving browsers + var o = createElement(OBJECT); + o.setAttribute("type", FLASH_MIME_TYPE); + for (var m in attObj) { + if (attObj[m] != Object.prototype[m]) { // filter out prototype additions from other potential libraries + if (m.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword + o.setAttribute("class", attObj[m]); + } + else if (m.toLowerCase() != "classid") { // filter out IE specific attribute + o.setAttribute(m, attObj[m]); + } + } + } + for (var n in parObj) { + if (parObj[n] != Object.prototype[n] && n.toLowerCase() != "movie") { // filter out prototype additions from other potential libraries and IE specific param element + createObjParam(o, n, parObj[n]); + } + } + el.parentNode.replaceChild(o, el); + r = o; + } + } + return r; + } + + function createObjParam(el, pName, pValue) { + var p = createElement("param"); + p.setAttribute("name", pName); + p.setAttribute("value", pValue); + el.appendChild(p); + } + + /* Cross-browser SWF removal + - Especially needed to safely and completely remove a SWF in Internet Explorer + */ + function removeSWF(id) { + var obj = getElementById(id); + if (obj && obj.nodeName == "OBJECT") { + if (ua.ie && ua.win) { + obj.style.display = "none"; + (function(){ + if (obj.readyState == 4) { + removeObjectInIE(id); + } + else { + setTimeout(arguments.callee, 10); + } + })(); + } + else { + obj.parentNode.removeChild(obj); + } + } + } + + function removeObjectInIE(id) { + var obj = getElementById(id); + if (obj) { + for (var i in obj) { + if (typeof obj[i] == "function") { + obj[i] = null; + } + } + obj.parentNode.removeChild(obj); + } + } + + /* Functions to optimize JavaScript compression + */ + function getElementById(id) { + var el = null; + try { + el = doc.getElementById(id); + } + catch (e) {} + return el; + } + + function createElement(el) { + return doc.createElement(el); + } + + /* Updated attachEvent function for Internet Explorer + - Stores attachEvent information in an Array, so on unload the detachEvent functions can be called to avoid memory leaks + */ + function addListener(target, eventType, fn) { + target.attachEvent(eventType, fn); + listenersArr[listenersArr.length] = [target, eventType, fn]; + } + + /* Flash Player and SWF content version matching + */ + function hasPlayerVersion(rv) { + var pv = ua.pv, v = rv.split("."); + v[0] = parseInt(v[0], 10); + v[1] = parseInt(v[1], 10) || 0; // supports short notation, e.g. "9" instead of "9.0.0" + v[2] = parseInt(v[2], 10) || 0; + return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false; + } + + /* Cross-browser dynamic CSS creation + - Based on Bobby van der Sluis' solution: http://www.bobbyvandersluis.com/articles/dynamicCSS.php + */ + function createCSS(sel, decl, media, newStyle) { + if (ua.ie && ua.mac) { return; } + var h = doc.getElementsByTagName("head")[0]; + if (!h) { return; } // to also support badly authored HTML pages that lack a head element + var m = (media && typeof media == "string") ? media : "screen"; + if (newStyle) { + dynamicStylesheet = null; + dynamicStylesheetMedia = null; + } + if (!dynamicStylesheet || dynamicStylesheetMedia != m) { + // create dynamic stylesheet + get a global reference to it + var s = createElement("style"); + s.setAttribute("type", "text/css"); + s.setAttribute("media", m); + dynamicStylesheet = h.appendChild(s); + if (ua.ie && ua.win && typeof doc.styleSheets != UNDEF && doc.styleSheets.length > 0) { + dynamicStylesheet = doc.styleSheets[doc.styleSheets.length - 1]; + } + dynamicStylesheetMedia = m; + } + // add style rule + if (ua.ie && ua.win) { + if (dynamicStylesheet && typeof dynamicStylesheet.addRule == OBJECT) { + dynamicStylesheet.addRule(sel, decl); + } + } + else { + if (dynamicStylesheet && typeof doc.createTextNode != UNDEF) { + dynamicStylesheet.appendChild(doc.createTextNode(sel + " {" + decl + "}")); + } + } + } + + function setVisibility(id, isVisible) { + if (!autoHideShow) { return; } + var v = isVisible ? "visible" : "hidden"; + if (isDomLoaded && getElementById(id)) { + getElementById(id).style.visibility = v; + } + else { + createCSS("#" + id, "visibility:" + v); + } + } + + /* Filter to avoid XSS attacks + */ + function urlEncodeIfNecessary(s) { + var regex = /[\\\"<>\.;]/; + var hasBadChars = regex.exec(s) != null; + return hasBadChars && typeof encodeURIComponent != UNDEF ? encodeURIComponent(s) : s; + } + + /* Release memory to avoid memory leaks caused by closures, fix hanging audio/video threads and force open sockets/NetConnections to disconnect (Internet Explorer only) + */ + var cleanup = function() { + if (ua.ie && ua.win) { + window.attachEvent("onunload", function() { + // remove listeners to avoid memory leaks + var ll = listenersArr.length; + for (var i = 0; i < ll; i++) { + listenersArr[i][0].detachEvent(listenersArr[i][1], listenersArr[i][2]); + } + // cleanup dynamically embedded objects to fix audio/video threads and force open sockets and NetConnections to disconnect + var il = objIdArr.length; + for (var j = 0; j < il; j++) { + removeSWF(objIdArr[j]); + } + // cleanup library's main closures to avoid memory leaks + for (var k in ua) { + ua[k] = null; + } + ua = null; + for (var l in swfobject) { + swfobject[l] = null; + } + swfobject = null; + }); + } + }(); + + return { + /* Public API + - Reference: http://code.google.com/p/swfobject/wiki/documentation + */ + registerObject: function(objectIdStr, swfVersionStr, xiSwfUrlStr, callbackFn) { + if (ua.w3 && objectIdStr && swfVersionStr) { + var regObj = {}; + regObj.id = objectIdStr; + regObj.swfVersion = swfVersionStr; + regObj.expressInstall = xiSwfUrlStr; + regObj.callbackFn = callbackFn; + regObjArr[regObjArr.length] = regObj; + setVisibility(objectIdStr, false); + } + else if (callbackFn) { + callbackFn({success:false, id:objectIdStr}); + } + }, + + getObjectById: function(objectIdStr) { + if (ua.w3) { + return getObjectById(objectIdStr); + } + }, + + embedSWF: function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj, callbackFn) { + var callbackObj = {success:false, id:replaceElemIdStr}; + if (ua.w3 && !(ua.wk && ua.wk < 312) && swfUrlStr && replaceElemIdStr && widthStr && heightStr && swfVersionStr) { + setVisibility(replaceElemIdStr, false); + addDomLoadEvent(function() { + widthStr += ""; // auto-convert to string + heightStr += ""; + var att = {}; + if (attObj && typeof attObj === OBJECT) { + for (var i in attObj) { // copy object to avoid the use of references, because web authors often reuse attObj for multiple SWFs + att[i] = attObj[i]; + } + } + att.data = swfUrlStr; + att.width = widthStr; + att.height = heightStr; + var par = {}; + if (parObj && typeof parObj === OBJECT) { + for (var j in parObj) { // copy object to avoid the use of references, because web authors often reuse parObj for multiple SWFs + par[j] = parObj[j]; + } + } + if (flashvarsObj && typeof flashvarsObj === OBJECT) { + for (var k in flashvarsObj) { // copy object to avoid the use of references, because web authors often reuse flashvarsObj for multiple SWFs + if (typeof par.flashvars != UNDEF) { + par.flashvars += "&" + k + "=" + flashvarsObj[k]; + } + else { + par.flashvars = k + "=" + flashvarsObj[k]; + } + } + } + if (hasPlayerVersion(swfVersionStr)) { // create SWF + var obj = createSWF(att, par, replaceElemIdStr); + if (att.id == replaceElemIdStr) { + setVisibility(replaceElemIdStr, true); + } + callbackObj.success = true; + callbackObj.ref = obj; + } + else if (xiSwfUrlStr && canExpressInstall()) { // show Adobe Express Install + att.data = xiSwfUrlStr; + showExpressInstall(att, par, replaceElemIdStr, callbackFn); + return; + } + else { // show alternative content + setVisibility(replaceElemIdStr, true); + } + if (callbackFn) { callbackFn(callbackObj); } + }); + } + else if (callbackFn) { callbackFn(callbackObj); } + }, + + switchOffAutoHideShow: function() { + autoHideShow = false; + }, + + ua: ua, + + getFlashPlayerVersion: function() { + return { major:ua.pv[0], minor:ua.pv[1], release:ua.pv[2] }; + }, + + hasFlashPlayerVersion: hasPlayerVersion, + + createSWF: function(attObj, parObj, replaceElemIdStr) { + if (ua.w3) { + return createSWF(attObj, parObj, replaceElemIdStr); + } + else { + return undefined; + } + }, + + showExpressInstall: function(att, par, replaceElemIdStr, callbackFn) { + if (ua.w3 && canExpressInstall()) { + showExpressInstall(att, par, replaceElemIdStr, callbackFn); + } + }, + + removeSWF: function(objElemIdStr) { + if (ua.w3) { + removeSWF(objElemIdStr); + } + }, + + createCSS: function(selStr, declStr, mediaStr, newStyleBoolean) { + if (ua.w3) { + createCSS(selStr, declStr, mediaStr, newStyleBoolean); + } + }, + + addDomLoadEvent: addDomLoadEvent, + + addLoadEvent: addLoadEvent, + + getQueryParamValue: function(param) { + var q = doc.location.search || doc.location.hash; + if (q) { + if (/\?/.test(q)) { q = q.split("?")[1]; } // strip question mark + if (param == null) { + return urlEncodeIfNecessary(q); + } + var pairs = q.split("&"); + for (var i = 0; i < pairs.length; i++) { + if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) { + return urlEncodeIfNecessary(pairs[i].substring((pairs[i].indexOf("=") + 1))); + } + } + } + return ""; + }, + + // For internal usage only + expressInstallCallback: function() { + if (isExpressInstallActive) { + var obj = getElementById(EXPRESS_INSTALL_ID); + if (obj && storedAltContent) { + obj.parentNode.replaceChild(storedAltContent, obj); + if (storedAltContentId) { + setVisibility(storedAltContentId, true); + if (ua.ie && ua.win) { storedAltContent.style.display = "block"; } + } + if (storedCallbackFn) { storedCallbackFn(storedCallbackObj); } + } + isExpressInstallActive = false; + } + } + }; +}(); diff --git a/flash/lib/EnginesLibrary.swc b/flash/lib/EnginesLibrary.swc new file mode 100644 index 0000000000000000000000000000000000000000..2b46616046acfde0f2cfb6101ea94a52f16d9f87 GIT binary patch literal 5064 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 I>A%ze1Fm&v00000 literal 0 HcmV?d00001 diff --git a/flash/lib/blooddy_crypto.swc b/flash/lib/blooddy_crypto.swc new file mode 100644 index 0000000000000000000000000000000000000000..5aaca952fe9cd40cd11580edda026c2cc6c1f061 GIT binary patch literal 25332 zcmV)%K#jjpO9KQH00;mG0Ia-_Jpcdz000000000001E&B0BmVua$$0LE^~Kg07gK$ zze8733M>Et0C)lHeFtD1#nu1LmfP!1cc*U2_FBzaRm-wvU#5G)Do6sJG}Aq|W4ivGVhyLUP@Thx&M z-?DFac4l_w?aZ4uZ{BC)<&y6U<*9Xa&G_MT+>6?#u%e(4Hc zz}i?M+Sj!u+11-qOa-!(yn`1r6M0IUdEeh(0 zHpP4UHn%5Nv~Stc-4$)8;;OIE6P>-$cU|7TEvCo2+Y_D3tH(3aJIStOcWlM#j$R{H z+}s_zqIgm9>hbq^ZZivI?idGjMaE~g)7w?i-p$op`g%LIMgdbCMe&z2U!fwlY&E*O z5}mQW6{mLh_O3m>cWcjDo^d>ZvPJu1?aAILnNrb|rMtan)7JJ)u@&cE$O}k6b4c5h zu@$w8t7_}2>S}6hOi(Cpc{R0gMvGf9=)@I5k$-EhR;#r~wXVw-U65EDt-rXh_HE6z zXVvwr-d4B0N;u@(dh<2=)}6BUu|NFRMPIq_9p8U$t^WmSxv)kY8ykDOQvzbx@RNl< zmEnfJoJXG@tpuXn7b-*|0)nx<+Q650bZoDV_HEyi?5*zFjIXHP(%v0QCS%oSoNERI zzfeUbM1)J$HeK3!d2iplE^TeSxaIt_Hk`Y9&AQgsORZvB&0;Q1^hNdd#4=-hD&bNq zftP=&RsOP6`Bm+ScNp8NtU{{Hyj47Jm6f+CmA87f8Jjl6l52Xqd;1cu*4EV<>RVf@ zw#A~hi(*l9ro6SQC#gg!xqY?;zM`7X=;?@Eaq4ysK<%t+NPT44Y~OICKQghumNM?C4#-{nvl?f8`smf8YV-+HgpF7~(`O!m9$j?6Z*Dv|*e}2af_t01V z_GNMZM}G8#_s6&Y@7K}WY`#$@RpU6ME<#%88jsErJKe-P6>vs=}e}D3-r@dD{^S^sNSAX+%?Vj65zUO)F zz>8n={`GIy{fj;Kxx4qcAAb0boxWSg?z&Ch`^cfA&U^0v-YMwrtE7!eyetKBE zVeco8dO@WG7oQxewkEr}6OL22Cu6Jo`r5a9F7N9~#xA^|nPL)wDX1l(UmTs7Rd~8> zL$a@{XA?`v6y-KcOl(giW19mRbIRJ@&Fx)1p6J%TK1^Tcle>dQo)g>L+qd1;9qZYY z>~!+VdC58FoUy)jZOb{UaXV+-IW6bEt@Z46>rcDjbUTIuA9ntl+PeBh4T~F>ENwTU z7>;gCARQ^l6@%cHxAk^)*jkKtf*L6iF~TBB#3ck&CaI#VN;XwgRne}BPE~fRqDK|I zs_0WizbXb)F{p|mRg9=&wj%h1e)=&*5&9iWVM2eH$1%xI=*vV~fQfQIlz{z`EC`Ay z%8cc0WM?8PJ3KtpBCV(T3pOf;!YIlcH5Dv zP^goh?S|6n7}1oyD6mmyBU5w+`5BI?lPMAWG(iKxr4C!%h{ zk%)Q>XCmq~T#2Yp6Ou>hs~yuN8g|2BI0p>Jt%hr0-$>)2ZW|m#xrSX+lY4N@-Mi0l z@E8Me8X(42`^BAXr>;s1DR=k4DcFJt0%ud6p1(4aP z`Ht08m@1ql%p5=G z%o1ka*G030nbU#dS;EZ6)ZAIZ%<*&HEMexfYyK=@=5#=tCCq$GEjS*`&i#gK|A@x% zq9g^c%n`W&NR&aC_Zv=Rq2<)GLz^?s<9T|C# zm8W3L=J@NFfWH=ud_Jg{1&o|xFPsI8ye%td0V5v?`Yd4N{a$rq7%^n4X8|Bb(3)8Q z$Z1RMECA$#sqVx8Y8-XyX8|K0_={!%BS-UwnPGGq4v^J-`-z?{PC;nnsF(+QNTDLQ z3=mXg(|Gqara*LZT#O)RilEv-ARRm>$FU_T7=0WW1IU@8rY=x)JI~1n!qOCwVUCS1 z~QkHO&T2-iym-11HC`<+Fj44~A1_11BG`D`o>H@5z<3fs>=#s@cHF2gK^x zz{yALsk4ETla@8Jfs>=$+S$O#$Hlst;dFyFB*xIgSwyGd#hqACAMP&V(7~r0a`^9m z95WP6==2y$WfoX`@VEZu;4VYU!H?!F2M1fQTq^?KsrfCQd8EN)hUL8t4}u$Q!W+H; zGdOZXvf_wV9R9)B7;)^woeq>;Ol51u&0Z`VuxMadX){_<(_NGf)mW=*$$O}#qs?E_ z=D$QJ?3DQ0y5I!jr*qez*4)*djP>z#whfCITDcU?owDXLMMO>M{POyi_3H#_!-c1c z7oMxGzG&U~t4~{}oV$A61*f0)dI+wTrE7xdLs{3?*W2e=-M48g$v*j07-Ep~wzhY7 z#k*o1YaruHtZ&~O%UsYplbwCNm-EaEPxw96>G~vytBxz-c}3qhql712UrMyIbV;pT zyJYbaxvqXmgIo{srM#%Nu_k*4&>iUlthKGwShK{Yu!t0KM{*&s#!6PC6|P9~9lpqQ z&>|N?9$Mu3R5?!-^MM{Dy@W6Tcbi`fWCcU~8s^sszh?7m4!;&Km)oU~yhdTo%*iAs zh|QuCi(V!*OHRmoM7ddZ$`X^5X2q${VpwFZwLn@$f^bNZn-0|+W1=wsGKN9Jjv>AA zGErll;xVR(>n@Wtu~Rz6Y|@37$(qzDA7iTAbD5&aoeD4-%Whd&@REIW`=OfAcON=K z7=6!1Timu$jjIce`Zn4RHQQOULn}Go??D zs5GLclRixp(4HOJ7VA!AN@-QQ+~42-aS1YDwZH$LYX8glK$Ij1KRdR1l|DnirKPMB z3*7j644SmGcS6}fFk2swv28MM2EVzIeeFGoE$t+aQMlkoX(bVkV{`izrZiXVi;A0~ zq7fDQy2MRgqS3{=AZ6e~%6UEn@i8+vKG4+GzVk^1p|)<~cd4~6Dz$FH&q&FGgOh>N z7N1CZIoHxUjB6YqWUj8OsaafYYz6u1>aoT^ruf;EJGVmZkd)79Pj)JOP&#zDTQj~I zQuE_W!Y6=QJ>f|`wYRrB*50Eepmm8gzOs_iqGVGpZi&85dBuff%7N1&-s2LlSA=IX z5-&?raOs2=u?U%5SGREZ|ylj0N3G zM;8#eE*%=)HL}Z)gCM&a+u_ITqX|21Pezr|GB7#f=eJdC3qWpJZYT@-#V>9PLOHNe zic1^ixV+%7uUQexM=K5ukL-f_VBw*WPIXjYFvf;7wsZH^sBN?g55wkzI$Di~5%a-5 zT6+j$Qu}D#Aw%tSjMg7A?48chCE6Hdo1@DPZFI$5G&gL3@U-MqvC}hp3U3bo$06e! zUBO$$|6U5O3H|_<8Xxr87W14@@=&3vc!dM<*-?MLY4Ud7;hGO=iMMu{i zii?KFaJTzMPdgMx;Qk800J*By@Km#bcE8;_y3%mnfG#jxBjH=e#zt2dPCP=&IUN3P zJf31W@CeRvDBO<6bI_Uvf-t&%VriOy8d?sOb_P)3`7|9e%$))B^#>Uy&+8eoH)jc8 zC?yGl=Ti(RA43Yh@9;5JA$*LD-iSN_bVv|l=JFw3`uw(_8x`G5uf`*2+0#dFV?(+; zkbHXdlWYhd_LZO$89t2+;!xawI(nb&WTQKgNg2@mdk@9!yAQ=}yGC~*#x|hY?>rQD zAV%GF`{-^qg#H>h6nE~vJ$ZO^4=-aF->{br14z;z@yT2o4Pr9SkLTCWg(mYnKyFUv z-^+55$-Y=Z-L?@&fQ z^_^YnQ#)>WO#b7SZr-In{HdEClLZlT{$G8<5`oVznG=qs@AQ}&kdCgzmhSfL&QrUR zo7=anZBMqF^QPm1?OV)gQ{gaWaH$g+TpP5BV9ZZ6H>SzH|D0GyS9{Nztx1bXdG)H8 zOfGs{EtJVGI5C5r+jT{(yS`4jysIPGY3q!2ZR&)cx4X9|Cig+t;xyY~Q>=Hha&9k} zYH{u<4sZ%Fm?m_0SW}+gR5kS^x;FL1I?m`xI@0Q&3`NjX6_m2({5AD;T;nvOmS}u} zN$ZOy>rU;d&#=RcVGBS@*f;dXlb3@hQoF&&^ysx-4$wJU!DWzAYW_@fbOvgv*7Qg# z30sV#Tc#3fCXU{fQG^R>DhY}zPsASyMG7Jso)?SEQBep6Tg87!SMR60|G_11Wvd59STLG723F|;v! zW>~14Q-wuN6)S0u*pr%Y*Dc%lUsOR!Q03v>B_;T+SW4Iy(1ZGd3!mjuJE z+uZ*{t;6xga9DT)Vq-XfHiplzAEYG09K)}ef`blU|{6Il-K+a6Wd7(7_3i7+H8@yr#!8Yx)doa#=M|qvmMhLGXU|G zOd#F|AOaJBkY@mbPf9bjc1{DPF4Pz@!h=TSU?Q4lu+(R>55mK*CFBmZGP;cq^0 z18LzI(h879?@#w$VX2X86nIcrIRb@W;4HZjA`~_Xj6x3)SBUIzjZD`bVN9$uHcEMB zqx|9JG5`DcFtqbw2#f&v{(ov1&J&Yw+%U8Xe-nn`gqq&eVK|}Y(7$sSWL=|A{TIPe#f*2gE3m)=JhL89dm&LQ4fPYKzhi=ZbRO*#gEt<$VJz@6ulv>92r1381^cNl&N0l4y0$^;cyzZDq8mFNeh9zR)|cjF?_J&li9fv>V+q z%VZkFPw4P}{dcPu6iiv8HI3)CwI=yXXTLUq`?B#8xqLmAzzWgD4b@{Ds*H8Bxe(?? zmKlrM1;>N38Vgfi42!aNvM$@Y9$MRb&OYOU3(j8Gx^Dg2Gghz1(vl*lETLa`4EMd@*C8E>8AN~2##m4)Z|#V2U82-bUqi|?J67SvP^S55&CR$PO@chxt4cmq z@{_r=l%+~A5e=(SM3o9vd9EtWgOr#QYY>G=L11Mzev+W%h`ITB1^haPUyJ4o{H`&t z4>~n^g{@++xfVR8QTcOoY2ljD>h1JN?5f&J-Tp+l3{>aK3X}1ktj-vBo8nYu*8)6f!8}i z{2sJ!im()tUA7#OUGWr1cBK`gOVb544Q*87O5E12n(|QDe*wuuR~a%ScdMxseYjK>4BchQ+Hr`~-xVrw1G|ay*^a-TGf!+j~qTOqi-Q!P)xG zEwN3!s8^&wo@r;tvUF`geHHa}cO|zw$LqC0ngx<%j_v7{b>7S(wAz{EQnS~-We~b? z^5;v}ays@%#QOXuk%aEvYN3D`@%FAwZ*MmnD1%A{&BW8U#^amYdwBC@nNe#%#Wuli zoX4dld=*p3l4ZG&h9XT`xd3-IRBuM>f$=iaq2rRfSJd&-kUNf&m!|7V)4WBs(vrIR zak^JmQ&;OqXPZda7A;vqIy71FM7-HvyN~P8{E>h-t~i5|3<5^zy_~8DYXLU|gU+BT zb0r$>773dc@v4}kin%Z%P{jh(S*VH&RJjy}^CbBbnCj2F7IQjQEOS8qWI-tsi|5Uq z&#xN4mXrznUd}>cmrAO02_^+1$e(kSsDjpI5fvn1V#uU_5WYim6*1{w5fQjEMQ%a> z^%PM>;hg8XFg6jSTa5-abNkc<>z%Xtw)CFQq*KqDk{?qn)66 zT}1QV$rWOOcz|eL@KAG*@eg?y+JN&>Wb$Ae4T0u`8m%K-n!=O z)f+ZA*LFo|D^+{n_DubQ*UWOp`VAMXUcY8t^tG{Vmrek3*)%|6Pz6aV(ANuEmE2B7 zMG39O;t|wK&N=&R7!*jjvtC=Txv7L3L5%BlGMFTVGZQ(}SEDsdquZGP--#)&E85$W zNcLg#WpAI`+;nNgNW&!e0C0PIGzy(YZ=cN!*rJ{7eXEnK&%Ui49Sz#}q*_RX{&gus2&uiV58P^xwI4_( z!wYuB+E;-mM4>0N|7?i#X|JZDX*%Y(1pF0Y-!ue#LhoKWP3 z-v5-9&m7s%`{y_W=E!AUJQgr-F>}mij(NUzhOfQhr@_ioowHSgFlr zrzt|VrU|cJ*e39@Nb?y4g3YF*dXr zrfgoJZnQqey4lT@J}7%&$pA`QP7$|k3)1A1;#H6MD!t7vOg@WZjFQRp;J~)c;%FnD z#9%3AmnGvV@26@;ePocKy#hS`1<1C{%og`j5oX5alQWL4z)R9C!59-pS0a(7 zO)6k?jV2t~=pz7NoDlc#h{}E&6C5#S$RqRVyGGaY;??GWfNvf>9a)viyt~LG01KP) z(K8UC#vvSU&SI5?6d!={t$g$>o^j52#MwNeh`$M$W%=lO{${8-4BeK&yy43N~iPvn%J{ z6~rRLP@rD3w|ffh4!gZhwFO4T#_X!yW+-+gRd*K6_ycm{4z+~`KSH|N2&Ld_SjLAF2;3&93S zqEIBrHlbKh<_hx!*bL1VRA99^!8~86Ud);FXNd3i+UBBnc@|9x$ zj(+>65%rMNe?1!<-E+Y9osaEfcZ<&c4Uu@vFl3@DSs*cCI0{cNRXoT&|c_lGODTzsF9i3MgXY}za2(5xgX%NYA*BS}D1x^Qc_s8$wTP!=sIiu1iz)Qeqh}C zL2Q#?{a|a6rD3dW$mBt6Q(*GoKunu0j?Dx8jw9Gl3?a%;+y(o(jgB)N*ti_E8MZ3c z={9U_(9~l~fuqI8KqPH(zb+ZFp**5Vj|>iW`Y8ql1^6yv8-+UQ8HBdeuFF;8GF?G4 z>9Af{rmKdkiG$0a2v?zRT@IzHitV{gI_R?I7+l7mU3i9N9CqTG>3-w~T&nJ-5++RCgBgpl+TQ`g`Q1CelMKK8fd+2b*}k>+lvdX)143J8xBo9-$Jl z%@R12$PH@^`<;5ufZ@PJgBc)_6;xoiqV40+Dqxq8>_FMMxZ_)O`UVf~`MCRWFCa(@ zsh&BAnGC63q!(vGN-Z#dI{nIk6wy9dQC6G z-G%zgvF}AwvFSwQ#GZ_W~7 z=n*u6FbgDYSO`Ow^6*xZJe#5@WhV!lyi z%%g}>3Lv7wC^qI&L>UDTQErqPWw&qT^{$}sP7{P;G*-D$ffje{+8SMGR2U1bTtLMY z^k`+Q!P9(0Gb_=I1!mz3j1me^>E%1CohYKZ+GUjPGO8)Mvf6EwTj46Bj8~x>C5BFk zH5343wMMN`nX0o4wcydJGY3yKMpY`&Za!LxOYjubuq%s@O0NlD^77b>9kz-Z+UzjB zg2HBp>9rI#J4|0lVY9>ZItrT|rq@&0>@a;1h5xM`1~6ypFnn1?hXL+4p~KKp86Af9 zoX}yb%ty1s(0J5gG%kq>tV(s5WxeBo!I!jWaH-y41%OL+2L4rhys&nt#%3j4y|-^o z=!=c4!3H?BSZ-v|NX|l8-x@>bHzuO>Mm@KwSY*^=C>xkkL+`|@z>ghx4gT|K;o#TU z;9`*XhCKr?vBNwHl_*T$_Ur?xdx*SH2<{4s)N?{u!Ra}Ma~byaiL@8ckJTNNvCgIk zGsxpZ4F)FdoI7A#@*?s#8jA)oq0%?{^(7z{4K%%S^MG*}vfeNlc$IHjZQ4pyMXiF) zyAi=sbe%xFNjU;=Isy~OZGmS%WD%Y9V@4v^XvqIKmYhZ?5nXJ)Nd|wRQkc_ZdH9>5 z32%ms#W0a+G#WL(?_+5GMoqpGeW|fTbcz4JsOEsa6zv-}!f4;6W*>*G_FejV_3=_; zsoBR5E-{uE4UzZbn;Nl+6Of_DImD@64-klZsM9mM|4jnV>i##WHiLH^^<1R#W$NUW zS#fkxU>wl{Zy2H*5pX4XB~991nzWl}(q49Q(mpw96Nh$k(mo!S^#)AZn2P!AeR9%% z6^z5lNt@>JlauzF!omEzCvC5;)1>V?Icc93xzgSmot0?h-hSOKWy$QFBJL{K^?>%I@`p2L7k?Z%*-}`gvo4>wq zzjN%4d#+WkAG-Pbj=K(gPL@BFYSK#N#%~KU%tWd^tWF6zVqWxf98JAm0$bNP3+qrJh)SQ=pT0vD1ZFs-)>f4 z{`udpvG4uR=uaGfeD>J&?gvJGcZc_ykNoXl-h=mE^M4+H=*zc@$G(5ZF>(KO!z1$1 zkq4hset5(4Uss2B{p|tA@2-F30q3JnUf=I}WbC=WyZ&%%|JU7*?E1{-JwpfgE&k>3 z9Xr|8Hy{0$_>BjDJ1YM3M|b~9zVQ=3yG?oO`F#Vndv6{%pnlK{CMqhsgL z*v-!GKK`rwoujvW;bZRIyYIf&ecPe$Uhlc%`d@s-bML<$XDvi+SHfBmvsa-vP#yh{aM4Fi<|IfLualC0jS6E%W!HP z;c-j~a{c(LacaE^yZ&zh0Z5XHkkn?h)1Nl_)3#OG=1;r)quKajkALPCf7;<6E!0`! zF#J#-Ave@AxuI6b4Yf_TC7{5B8q%)AA+>|tKrT89T zZ_0DF&j485b2dWoob5v)9>W8rxXbViwYZt#;r_EdT=VTgMIOt4wx`p<>)@IPTi%ub&u}VeY#%{=s`V859wh& zqG#(ldaj=0^6of<0{kW+VhWqgSgGShh3>e{CjqHK_gMix$C(cGb=9)t6`JS@}7ZGm<`z)o`HY$HtH6EX@Bjjg@(nL+yC0DXF)QGnlnWl#9S}3gj8Nnr{y-+@%_kEa1dbU zX}NoFQIRK4^HLCgpZDV??ZtN2I}(LKKjE~t=LKzAe- z(jAF&=w1Xyf)b1Aj>NfiN8&uX&u;=pghY+*NL)a7B$m*@Zw4Cq4u#qj8|=C7P#(0#Y)$yE={?kN z!hNVmtD(lyRyF%)<32Q4(-tJAN8(IRDRivUGd-(2x?_Fyb>^<9e!KLgg)fasm^r4O zjx8IrS8oQFF^yWMI?$;y-2YFeYn`e4{8S6j)X5J%?8rB5%f?TBz=pn*c^P*0$=JXG zLn->fA&$`R& zs6jsu6D=t&$HDs@8P=J5F6@ELqakRU+fa)QR<}Cqgk4u0&z%ZsLn%De$%|A-S!$MR zk{YFonH=}97qAS2`8MNW+uDEx8cyU)Ml}>7<%(esljaq?@Etsn-;p>R{xYxy^^9~X zgu8nT+rHDeTQ+t=4%u1L<|Jfsab&qMJQK2*R;QL%r0U6jsxo$#IF9)G#lt@CV#{)= zImUopW`rnI%z$4e2le`UMBXTu9BI~uIO{FneMXO6WnEahT`64GYW)*yURjz zSTW=7vQl~GA8Xq?T(NwC@ea8o!U6`CDezupc`7py%H}ke5S!xaKX`9A?U z2<&j-ah_Zy(t^bBxfMjZ$)SA_16UdV9BDU9Fn-M&>5cXOyy z?$~C)Q3L7TFXEfvb&0(4@bAbaTM+(4FvGhI7(GfO`10uqmX<}ZP%QAmZ=+EJ4{zin zr;wU>4!wF%v+tdfBad>RJLXVT2f6j=q%v|T2fA(!eZfI3XKEQalmp#t4lV2&!LWka zk=>XJOnL+Z&0kI2e1TEa;D&2WAW{($20O`Q5MkI*79DKKmHe;>GgC|`i^JbRv*E2K zV-DgO)=_YR1g3tUk-rZS3L<<)V4snNbqz&05aBn1`;5>&BSH}{`vuMxX#w0}RD2M@ zFa-I5RRjXuq!a$73ZS&Z)Cv~aX{1djXP_@y!}Ojvj=mH!2}%ZXQ@av27lAY7 zEuk42zM~No2LWZU(U^z6gWV?}L}WGsVG4lTfETAU%YpWVK{u_AY~ba2vTi4{JsOf2 zkJ*j4ra9Vdybgr4un`XS`IuS5Qe%eKm9mWwiml^25? zOX+OG7XAcmu_g@V6U-I{(x(jNk_kh(biz4 zjmq>;<|&ve@f|uJ%6O~ESZGx8p$yyReMad%qii1{CJp7v$wRr459J)>q@nCFat?Fo`Oq=;$>=+B%qQ7tv*8pUa%T1HRk{Yh;!-V=D$B&w5gjQ0epGKuP> z9OFHKFEEMfq#Xaop2#x__8D{b8O78!L{#&QLXgva#$1XZqUyDJLN&ZrPm~(=VK?nX zG)Wzh;vmdOzql=M*Wbs81bQz5!x*iGn^AcKI~D#@qBSX^$d_T})DObN>7*Nm>24yB zre`Kj9m&-ss3Q�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

mfpQo%K*KxtS^jdH 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 literal 0 HcmV?d00001 diff --git a/flash/src/FlashFileAPI.as b/flash/src/FlashFileAPI.as new file mode 100644 index 00000000..0d3ccc67 --- /dev/null +++ b/flash/src/FlashFileAPI.as @@ -0,0 +1,66 @@ +package +{ + import flash.display.Sprite; + import flash.display.StageAlign; + import flash.display.StageQuality; + import flash.display.StageScaleMode; + import flash.events.Event; + + import ru.mail.controller.AppController; + + /** + * + * @author v.demidov + * + */ + public class FlashFileAPI extends Sprite + { + private var _controller:AppController; + private var _graphicContext:Sprite = new Sprite(); + + public function FlashFileAPI() + { + if (stage) { + init(); + } + else { + addEventListener(Event.ADDED_TO_STAGE, init); + } + } + + /** + * entry point + * @param event + * + */ + protected function init(event:Event = null):void + { + trace ("{FlashFileAPI} - init"); + removeEventListener(Event.ADDED_TO_STAGE, init); + + // config stage + stage.align = StageAlign.TOP_LEFT; + stage.scaleMode = StageScaleMode.NO_SCALE; + stage.quality = StageQuality.BEST; + + // add graphic context + addChild(_graphicContext); + + // initiate controller + _controller = new AppController(_graphicContext, parseFlashVars()); + stage.addEventListener(Event.RESIZE, _controller.onStageResize) + } + + /** + * parse all flashvars into object + */ + private function parseFlashVars():Object + { + var options:Object = new Object(); + for (var s:String in loaderInfo.parameters) { + options[s] = loaderInfo.parameters[s]; + } + return options; + } + } +} \ No newline at end of file diff --git a/flash/src/net/inspirit/MultipartURLLoader.as b/flash/src/net/inspirit/MultipartURLLoader.as new file mode 100644 index 00000000..3ea716f4 --- /dev/null +++ b/flash/src/net/inspirit/MultipartURLLoader.as @@ -0,0 +1,589 @@ +package net.inspirit +{ + 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.clearInterval; + import flash.utils.setTimeout; + + import net.inspirit.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. + * + * 2010.02.23 + * Fixed issue 2 (loading failed if not directly dispatched from mouse event) + * problem and fix reported by gbradley@rocket.co.uk + * + * @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 + * + * // no overwrite when adding file with the same fileName. + * + * @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 + { + var key:String = fileName+_fileNames.length.toString(); // cannot use just fileName, because there can be several files with equal filename + _fileNames.push( key ); + _files[key] = new FilePart(fileContent, fileName, dataField, 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; + + urlRequest.requestHeaders.push( new URLRequestHeader('Content-type', 'multipart/form-data; boundary=' + getBoundary()) ); + + if (requestHeaders.length) + { + urlRequest.requestHeaders = urlRequest.requestHeaders.concat(requestHeaders); + } + + 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); + + if (i != _fileNames.length - 1) + { + postData = LINEBREAK(postData); + } + i++; + + } + 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 diff --git a/flash/src/net/inspirit/events/MultipartURLLoaderEvent.as b/flash/src/net/inspirit/events/MultipartURLLoaderEvent.as new file mode 100644 index 00000000..fe31da66 --- /dev/null +++ b/flash/src/net/inspirit/events/MultipartURLLoaderEvent.as @@ -0,0 +1,27 @@ +package net.inspirit.events +{ + import flash.events.Event; + + /** + * MultipartURLLoader Event for async data prepare tracking + * @author Eugene Zatepyakin + */ + public class MultipartURLLoaderEvent extends Event + { + public static const DATA_PREPARE_PROGRESS:String = 'dataPrepareProgress'; + public static const DATA_PREPARE_COMPLETE:String = 'dataPrepareComplete'; + + public var bytesWritten:uint = 0; + public var bytesTotal:uint = 0; + + public function MultipartURLLoaderEvent(type:String, w:uint = 0, t:uint = 0) + { + super(type); + + bytesTotal = t; + bytesWritten = w; + } + + } + +} \ No newline at end of file diff --git a/flash/src/ru/mail/commands/AbstractUploadFileCommand.as b/flash/src/ru/mail/commands/AbstractUploadFileCommand.as new file mode 100644 index 00000000..53d052f6 --- /dev/null +++ b/flash/src/ru/mail/commands/AbstractUploadFileCommand.as @@ -0,0 +1,88 @@ +package ru.mail.commands +{ + import flash.events.EventDispatcher; + import flash.net.URLRequestHeader; + import flash.net.URLVariables; + + import ru.mail.data.vo.ErrorVO; + import ru.mail.events.UploadCompleteEvent; + + /** + * Base class for UploadFileCommand and UploadImageCommand + * @author v.demidov + * + */ + public class AbstractUploadFileCommand extends EventDispatcher + { + protected var _url:String; + protected var _uploadDataFieldName:String = "Filedata"; + protected var _uploadPostData:URLVariables; + protected var _contentType:String; + protected var _requestHeaders:Array; + + public function AbstractUploadFileCommand(url:String, headers:Object, uploadPostData:Object, uploadDataFieldName:String = "Filedata") + { + super(); + + _url = url; + if (uploadDataFieldName && uploadDataFieldName != "") + _uploadDataFieldName = uploadDataFieldName; + parsePostData(uploadPostData, headers); + } + + public function execute():void {/* do nothing*/} + + public function dispose():void {} + + /** + * it cancels upload and disposes the object + */ + public function cancel():void {} + + /** + * prepare post data, content-type and other request headers for upload + * @param uploadPostData + * @param headers + * + */ + protected function parsePostData(uploadPostData:Object, headers:Object):void + { + // create URLVariables + _uploadPostData = new URLVariables(); + var prop:String; + if (uploadPostData != null) + { + for (prop in uploadPostData) + { + _uploadPostData[prop] = uploadPostData[prop]; + } + } + + if (headers != null) + { + _requestHeaders = new Array(); + + for (prop in headers) + { + if (prop == "Content-Type") + { + // handle Content-Type apart from other headers + _contentType = headers[prop]; + } + else { + _requestHeaders.push( new URLRequestHeader(prop, headers[prop]) ); + } + } + } + } + + protected function complete(isSuccess:Boolean, result:String, error:ErrorVO = null):void + { + dispatchEvent( new UploadCompleteEvent(isSuccess, result, error) ); + + try{ + dispose(); + } catch (e:Error) {}; + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/commands/DecodeBytesToBitmapCommand.as b/flash/src/ru/mail/commands/DecodeBytesToBitmapCommand.as new file mode 100644 index 00000000..665c34aa --- /dev/null +++ b/flash/src/ru/mail/commands/DecodeBytesToBitmapCommand.as @@ -0,0 +1,127 @@ +package ru.mail.commands +{ + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.Loader; + import flash.display.LoaderInfo; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.SecurityErrorEvent; + import flash.events.TimerEvent; + import flash.utils.ByteArray; + import flash.utils.Timer; + + import ru.mail.data.vo.ErrorVO; + import ru.mail.events.DecodeBytesToBitmapCompleteEvent; + import ru.mail.utils.BMPDecoder; + + + /** + * The command decodes bytes to image with the Loader object. png, jpg, bmp and unanimated gif only. + * + * Animated gif - currently there is no check for it, so it will be decoded, but the image might be incorrect + * + * @author ivanova + */ + public class DecodeBytesToBitmapCommand extends EventDispatcher + { + private var bmpDecoder:BMPDecoder = new BMPDecoder(); + private var _bytes:ByteArray ; + private var _loader:Loader = new Loader() ; + private var _terminateTimer:Timer = new Timer( 20 * 1000 ); + private var _isTerminated:Boolean = false; + + /** + * ctor + * @param bytes: the bytes collection to decode + */ + public function DecodeBytesToBitmapCommand( bytes:ByteArray ) + { + if ( null == bytes ) + throw new Error( "DecodeBytesToBitmapCommand bytes is null" ) ; + + _bytes = bytes ; + + _loader.contentLoaderInfo.addEventListener( Event.COMPLETE, _onDecodeImageComplete ) ; + _loader.contentLoaderInfo.addEventListener( IOErrorEvent.IO_ERROR, _onDecodeImageError ) ; + _loader.contentLoaderInfo.addEventListener( SecurityErrorEvent.SECURITY_ERROR, _onDecodeImageError ) ; + + _terminateTimer.addEventListener( TimerEvent.TIMER, _onTerminateTimer); + } + + public function dispose():void + { + _loader.contentLoaderInfo.removeEventListener( Event.COMPLETE, _onDecodeImageComplete ) ; + _loader.contentLoaderInfo.removeEventListener( SecurityErrorEvent.SECURITY_ERROR, _onDecodeImageError ) ; + _loader.contentLoaderInfo.removeEventListener( IOErrorEvent.IO_ERROR, _onDecodeImageError ) ; + + _bytes = null ; + try + { + if ( ( _loader.contentLoaderInfo.content as Bitmap ) != null ) + ( _loader.contentLoaderInfo.content as Bitmap ).bitmapData.dispose() ; + } + catch ( e:Error ) { } + + bmpDecoder = null; + _loader = null ; + _terminateTimer.removeEventListener( TimerEvent.TIMER, _onTerminateTimer); + _terminateTimer = null; + } + + public function execute():void + { + try + { + _terminateTimer.start(); + _loader.loadBytes( _bytes ) ; + } + catch ( e:Error ) + { + complete( false, null, new ErrorVO( e.toString() ) ); ; + } + } + + private function _onDecodeImageComplete( e:Event ):void + { + + try + { + var image:Bitmap = ( e.target as LoaderInfo ).content as Bitmap ; + var isSuccess:Boolean = ( image != null ) ; + complete( isSuccess, image ) ; + } + catch ( e:Error ) { complete( false, null, new ErrorVO( e.toString() ) ); } + } + + private function _onDecodeImageError( e:Event ):void + { + + try { + var bd:BitmapData = bmpDecoder.decode(_bytes); + var image:Bitmap = new Bitmap(bd); + var isSuccess:Boolean = ( image != null && ( !bmpDecoder.decodeError || bmpDecoder.decodeError == "" ) ) ; + complete( isSuccess, image, bmpDecoder.decodeError? new ErrorVO(bmpDecoder.decodeError) : null ) ; + } catch(e:Error) { + complete( false, null, new ErrorVO( e.toString() ) ); + } + } + + private function _onTerminateTimer( e:TimerEvent ):void + { + complete( false, null, new ErrorVO("DecodeBytesToImageCommand timeout") ); + } + + private function complete( isSuccess:Boolean, image:Bitmap, error:ErrorVO = null ):void + { + if ( !_isTerminated ) + { + _isTerminated = true; + _terminateTimer.stop(); + dispatchEvent( new DecodeBytesToBitmapCompleteEvent( isSuccess, image, error ) ) ; + } + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/commands/LoadFileCommand.as b/flash/src/ru/mail/commands/LoadFileCommand.as new file mode 100644 index 00000000..dfa6db1b --- /dev/null +++ b/flash/src/ru/mail/commands/LoadFileCommand.as @@ -0,0 +1,120 @@ +package ru.mail.commands +{ + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.net.FileReference; + import flash.utils.ByteArray; + + import ru.mail.data.vo.ErrorVO; + import ru.mail.data.vo.FileStatesEnum; + import ru.mail.data.vo.FileVO; + import ru.mail.events.ImageTransformCompleteEvent; + + /** + * + * Load bytes from FileReference + * + * @author v.demidov + * + */ + public class LoadFileCommand extends EventDispatcher + { + private var fileRef:FileReference; + private var file:FileVO; + protected var isCancelled:Boolean = false; + + public function LoadFileCommand(file:FileVO) + { + super(); + + if ( null == file ) + throw new Error( "LoadFileCommand fileVO is null" ) ; + + this.file = file; + fileRef = file.fileRef; // shortcut + + fileRef.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); + fileRef.addEventListener(ProgressEvent.PROGRESS, onProgress); + fileRef.addEventListener(Event.COMPLETE, onLoadComplete); + } + + public function execute():void + { + try + { + // change file status to prevent simultanious uploading and loading + file.status = FileStatesEnum.LOADING; + // load + fileRef.load() ; + } + catch ( e:Error ){ + complete( false, null, new ErrorVO(e.toString()) ); + } + } + + /** + * it cancels load and disposes the object + */ + public function cancel():void + { + try{ + isCancelled = true; + + fileRef.cancel(); + + dispose(); + }catch(e:Error){ + trace ("LoadFileCommand cancel() error: "+e.toString()); + } + + file.status = FileStatesEnum.SELECTED; + } + + public function dispose():void + { + fileRef.removeEventListener( Event.COMPLETE, onLoadComplete); + fileRef.removeEventListener( IOErrorEvent.IO_ERROR, onLoadError ); + fileRef.removeEventListener(ProgressEvent.PROGRESS, onProgress); + + fileRef = null; + file = null; + } + + private function onLoadError(event:IOErrorEvent):void + { + complete( false, null, new ErrorVO(event.toString(), IOErrorEvent.IO_ERROR) ); + } + + private function onProgress(event:ProgressEvent):void + { + dispatchEvent(event.clone()); + } + + /** + * step 1 complete, + * step 2: create bitmap + * @param event + * + */ + private function onLoadComplete(event:Event):void + { + if (isCancelled) { + return; + } + else if (file.fileData != null) + { + complete( true, file.fileData ); + } + else { + complete( false, null, new ErrorVO("Error #1009: Loaded data is null.") ); + } + } + + private function complete(isSuccess:Boolean, data:ByteArray, error:ErrorVO = null):void + { + dispatchEvent( new ImageTransformCompleteEvent(isSuccess, data, error) ); + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/commands/ResizeFileCommand.as b/flash/src/ru/mail/commands/ResizeFileCommand.as new file mode 100644 index 00000000..b52a4035 --- /dev/null +++ b/flash/src/ru/mail/commands/ResizeFileCommand.as @@ -0,0 +1,232 @@ +package ru.mail.commands +{ + import by.blooddy.crypto.image.JPEGEncoder; + import by.blooddy.crypto.image.PNG24Encoder; + + import flash.display.BitmapData; + import flash.events.EventDispatcher; + import flash.geom.Matrix; + import flash.utils.ByteArray; + + import ru.mail.data.vo.ErrorVO; + import ru.mail.data.vo.IFileVO; + import ru.mail.data.vo.ImageTransformVO; + import ru.mail.events.ImageTransformCompleteEvent; + + /** + * Resize and rotate image using imageTransform object + * + * file must be loaded before transforming. + * + * Only JPG or PNG images. + * Performing transform on gif or bmp will result in returning the original image data. + * + * The possible solution is to transform them but save as PNG, but we also have to change the uploaded file extension to png. + * + * @author v.demidov + * + */ + public class ResizeFileCommand extends EventDispatcher + { + private var file:IFileVO + private var imageTransform:ImageTransformVO + + public function ResizeFileCommand( file:IFileVO, imageTransform:ImageTransformVO ) + { + super(); + + this.file = file; + this.imageTransform = imageTransform; + } + + public function execute():void + { + if( !file.imageData ) { + complete(false, null, new ErrorVO("ResizeImageCommand - cannot resize file because it has not been succesfully loaded") ); + } + + if (!needResize()) { + complete(true, file.fileData); + } + + var fileType:String = file.fileType; + if (fileType == "gif" || fileType == "bmp") { + // TODO: scale but save jpg + complete(true, file.fileData) + } + + checkTransform(); + + transformImage(); + } + + public function cancel():void + { + + } + + /** + * check imageTransform if it really transforms image + * @return true if we need to transform + */ + private function needResize():Boolean { + if ( imageTransform.sx == imageTransform.sy == 0 + && imageTransform.sw == file.imageData.width + && imageTransform.sh == file.imageData.height + && imageTransform.dw == imageTransform.sw + && imageTransform.dh == imageTransform.sh + && imageTransform.deg == 0 + ) + { + // transformed image equals original + return false; + } + return true; + } + + /** + * Set default values if no value + */ + private function checkTransform():void { + if (imageTransform.sw == 0) + imageTransform.sw = file.imageData.width - imageTransform.sx; + if (imageTransform.sh == 0) + imageTransform.sh = file.imageData.height - imageTransform.sy; + if (imageTransform.dw == 0) + imageTransform.dw = imageTransform.sw; + if (imageTransform.dh == 0) + imageTransform.dh = imageTransform.sh; + } + + private function transformImage():void + { + try + { + var fullImageMap:BitmapData = file.imageData; //shortcut + var matrix:Matrix; + var currentImageMap:BitmapData; + var resizedImageMap:BitmapData; + + // ============= + // crop + if ( imageTransform.sx != 0 || imageTransform.sy != 0 + || imageTransform.sw != fullImageMap.width || imageTransform.sh != fullImageMap.height ) + { + matrix = new Matrix(); + matrix.identity(); + matrix.translate( -1*Math.ceil(imageTransform.sx), -1*Math.ceil(imageTransform.sy) ); + + currentImageMap = new BitmapData(imageTransform.sw, imageTransform.sh); + currentImageMap.draw(fullImageMap, matrix); + } + else + { + currentImageMap = fullImageMap.clone(); + } + + // ============== + // scale + var scaleX:Number = imageTransform.dw/currentImageMap.width; + var scaleY:Number = imageTransform.dh/currentImageMap.height; + var angle:Number = imageTransform.deg*Math.PI/180; + var maxScale:Number = Math.max(scaleX, scaleY); + + if (maxScale < 0.5) { + + trace ("multi-step "); + + var curWidth:Number = currentImageMap.width; + var curHeight:Number = currentImageMap.height; + var mapToScale:BitmapData; + // multi-step + while(maxScale < 0.5) + { + trace ("step ", maxScale); + // series if x2 scalings + + // temp bitmapdata + mapToScale = currentImageMap.clone(); + currentImageMap.dispose(); + + //we can downsample in a half + matrix = new Matrix(); + matrix.identity(); + matrix.scale( 0.5, 0.5 ); + curWidth = 0.5*curWidth; + curHeight = 0.5*curHeight; + + currentImageMap = new BitmapData( Math.ceil( curWidth ), Math.ceil( curHeight ) ); + + // TODO: set smoothing to false (nearest neighbour) - it should work the same + currentImageMap.draw(mapToScale, matrix, null, null, null, true ); + + // new scale + scaleX = imageTransform.dw/currentImageMap.width; + scaleY = imageTransform.dh/currentImageMap.height; + maxScale = Math.max(scaleX, scaleY); + } + } + + // ============== + // final scale and rotate + // single-step + + // final size including rotation + var tw:Number = Math.ceil( Math.abs( Math.sin(angle)*imageTransform.dh ) + Math.abs( Math.cos(angle)*imageTransform.dw ) ); + var th:Number = Math.ceil( Math.abs( Math.sin(angle)*imageTransform.dw ) + Math.abs( Math.cos(angle)*imageTransform.dh ) ); + + scaleX = imageTransform.dw/currentImageMap.width; + scaleY = imageTransform.dh/currentImageMap.height; + + matrix = new Matrix(); + matrix.identity(); + matrix.translate( -1*Math.ceil(0.5*currentImageMap.width), -1*Math.ceil(0.5*currentImageMap.height) ); + matrix.rotate( angle ); + matrix.scale( scaleX, scaleY ); + matrix.translate( Math.ceil(0.5*tw), Math.ceil(0.5*th) ); + + resizedImageMap = new BitmapData( tw , th ); + // resize with bilinear interpolation + resizedImageMap.draw( currentImageMap, matrix, null, null, null, true ); + + encodeImage(resizedImageMap); + } + catch( e:Error ){ + complete( false, null, new ErrorVO(e.toString()) ); + } + } + + /** + * Encode image using file type (JPG or PNG) + * @param imageMap + * + */ + private function encodeImage(imageMap:BitmapData):void + { + try { + var resizedImageData:ByteArray; + + // encode image + if (file.fileType.toLowerCase() == 'jpg' || file.fileType.toLowerCase() == 'jpeg') { + resizedImageData = JPEGEncoder.encode(imageMap, 90); + } + else { + // use png encoder by default + resizedImageData = PNG24Encoder.encode(imageMap); + } + + imageMap.dispose(); + + complete( true, resizedImageData ); + } + catch (e:Error) { + complete( false, null, new ErrorVO(e.toString()) ); + } + } + + private function complete( isSuccess:Boolean, fileBytes:ByteArray, error:ErrorVO = null ):void + { + dispatchEvent( new ImageTransformCompleteEvent( isSuccess, fileBytes, error ) ); + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/commands/UploadCommand.as b/flash/src/ru/mail/commands/UploadCommand.as new file mode 100644 index 00000000..87d2e7cb --- /dev/null +++ b/flash/src/ru/mail/commands/UploadCommand.as @@ -0,0 +1,212 @@ +package ru.mail.commands +{ + import flash.events.EventDispatcher; + import flash.events.ProgressEvent; + import flash.events.TextEvent; + + import ru.mail.data.IImageFactory; + import ru.mail.data.vo.ErrorVO; + import ru.mail.data.vo.FileVO; + import ru.mail.data.vo.IFileVO; + import ru.mail.data.vo.ImageTransformVO; + import ru.mail.events.ImageTransformCompleteEvent; + import ru.mail.events.UploadCompleteEvent; + import ru.mail.utils.LoggerJS; + + /** + * Upload command handles uploading process: + * 1) only original file - upload it using filereference + * 2) set of transformed images with or without original - + * upload them all at once using multipart uploader + * + * get transformed images if needed, + * watch progress + * @author v.demidov + * + */ + public class UploadCommand extends EventDispatcher + { + private static var INIT:String = "init"; + private static var ORIGINAL:String = "original"; + /** + * {"original":byteArray, "XL": "init", "S": byteArray, "M": errorVO} + * command - executing command + * "init" - preparing image for upload + * bytearray - ready to create upload command + * serverResponce - upload complete + * error - errorVO + */ + private var filesPool:Object = {}; + + private var url:String; + private var uploadPostData:Object; + private var headers:Object; + private var files:Object; + + private var currentUploadCommand:AbstractUploadFileCommand; + + public function UploadCommand(url:String + , uploadPostData:Object + , headers:Object + , files:Object) + { + super(); + + this.url = url; + this.uploadPostData = uploadPostData; + this.headers = headers; + this.files = files; + } + + public function execute():void + { + var useMultiple:Boolean = false; + var count:int = 0; + + // check transform + var s:String; + // init filesPool. We must init all its fields before calling checkPool function + for (s in files) + { + filesPool[s] = INIT; + count++; + if (!useMultiple && (files[s].matrix != null || count > 1)) { + // if there are several files or at least one with matrix, we cannot use fileReference + useMultiple = true; + } + } + + LoggerJS.log("UploadCommand: use fileReference upload = "+!useMultiple); + // then create transformed images + for (s in files) + { + if (useMultiple) { + // create transformed image, then upload it + createImage(s, files[s].file, files[s].matrix); + } + else { + uploadOriginal(s, files[s].file); + } + } + } + + public function cancel():void + { + if (currentUploadCommand) + currentUploadCommand.cancel(); + dispose(); + } + + public function dispose():void + { + filesPool = null; + headers = null; + files = null; + } + + //=================== prepare ======================== + + private function createImage(s:String, file:IFileVO, trans:Object):void + { + LoggerJS.log("get image data for "+s); + // add to queue + filesPool[s] = INIT; + var fileName:String = file.fileNameModified; + // get transformed image data + var imageFactory:IImageFactory = file.imageFactory; + (imageFactory as EventDispatcher).addEventListener(ImageTransformCompleteEvent.TYPE,function (event:ImageTransformCompleteEvent):void { + event.currentTarget.removeEventListener(event.type, arguments.callee); + trace("createImage imageTransform complete", event.isSuccess); + if (event.isSuccess) { + // upload transformed image + filesPool[s] = new Object() + filesPool[s][fileName] = event.data; + } + else { + complete(false, null, event.error); + } + checkFilesPool(); + }); + + imageFactory.createImage( trans? new ImageTransformVO(trans.sx, trans.sy, trans.sw, trans.sh, trans.dw, trans.dh, trans.deg) : null ); + } + + + private function checkFilesPool():void + { + trace ("checkFilesPool") + // check images ready + for (var s:String in filesPool) + { + trace ("filesPool["+s+"]="+filesPool[s].length); + if (filesPool[s] == INIT) { + // wait for loading this image + return; + } + } + + // since we are here, then all images are ready + uploadImages(); + } + + //=================== upload ========================= + + /** + * create and handle command for uploading transformed image + */ + private function uploadImages():void + { + LoggerJS.log("launch multipart upload command"); + // create multipart command + var command:AbstractUploadFileCommand = new UploadImageCommand( filesPool, url + , headers, uploadPostData ); + + upload(command); + } + + /** + * create and handle command for uploading fileReference + */ + private function uploadOriginal(name:String, file:IFileVO):void + { + LoggerJS.log("launch filereference upload command"); + // launch filereference upload command + var command:AbstractUploadFileCommand = new UploadFileCommand((file as FileVO).fileRef, url + , headers, uploadPostData + , name); + + upload(command); + } + + private function upload( command:AbstractUploadFileCommand ):void + { + command.addEventListener(UploadCompleteEvent.TYPE, function(event:UploadCompleteEvent):void { + event.currentTarget.removeEventListener(event.type, arguments.callee); + // complete + complete(event.isSuccess, event.result, event.error); + command.dispose(); + }); + + command.addEventListener(ProgressEvent.PROGRESS, function(progressEvent:ProgressEvent):void { + // notify progress + dispatchEvent( progressEvent.clone() ); + }); + + command.addEventListener("httpStatus", function(textEvent:TextEvent):void { + dispatchEvent( textEvent.clone() ); + }); + + // save command + currentUploadCommand = command; + + command.execute(); + } + + //=================== complete ======================= + + private function complete(isSuccess:Boolean, result:String, error:ErrorVO):void + { + dispatchEvent( new UploadCompleteEvent(isSuccess, result, error) ); + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/commands/UploadFileCommand.as b/flash/src/ru/mail/commands/UploadFileCommand.as new file mode 100644 index 00000000..4010684a --- /dev/null +++ b/flash/src/ru/mail/commands/UploadFileCommand.as @@ -0,0 +1,133 @@ +package ru.mail.commands +{ + import flash.events.DataEvent; + import flash.events.Event; + import flash.events.HTTPStatusEvent; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.events.TextEvent; + import flash.net.FileReference; + import flash.net.URLRequest; + import flash.net.URLRequestMethod; + + import ru.mail.data.vo.ErrorVO; + import ru.mail.utils.LoggerJS; + + /** + * Upload using fileReference.upload() + * @author v.demidov + * + */ + public class UploadFileCommand extends AbstractUploadFileCommand + { + private var fileRef:FileReference; + + public function UploadFileCommand(fileRef:FileReference, url:String, headers:Object, uploadPostData:Object, uploadDataFieldName:String) + { + super(url, headers, uploadPostData, uploadDataFieldName); + + this.fileRef = fileRef; + } + + override public function dispose():void + { + fileRef.removeEventListener(DataEvent.UPLOAD_COMPLETE_DATA, onUploadCompleteData); + fileRef.removeEventListener(HTTPStatusEvent.HTTP_STATUS, onHTTPStatus); + fileRef.removeEventListener(IOErrorEvent.IO_ERROR, onError); + fileRef.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onError); + fileRef.removeEventListener(ProgressEvent.PROGRESS, onProgress); + + fileRef = null; + } + + override public function execute():void + { + if ( _url == null ) { + complete(false, null, new ErrorVO("UploadFileCommand: upload url is null") ); + return; + } + + // add listeners + fileRef.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, onUploadCompleteData); + fileRef.addEventListener(HTTPStatusEvent.HTTP_STATUS, onHTTPStatus); + fileRef.addEventListener(IOErrorEvent.IO_ERROR, onError); + fileRef.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError); + fileRef.addEventListener(ProgressEvent.PROGRESS, onProgress); + + // create request + var request:URLRequest = new URLRequest(_url); + + // data + request.method = URLRequestMethod.POST; + request.data = _uploadPostData; + + LoggerJS.log("upload file with FileReference, url = " + request.url); + try + { + fileRef.upload(request, _uploadDataFieldName) ; + } + catch ( e:Error ){ + trace ("UploadFileCommand execute err", e); + complete( false, null, new ErrorVO( e.toString() ) ); + } + } + + /** + * Cancels upload and disposes the object + * + * Known issue: do not call cancel when the progress is 100% but uploadCompleteData hasn't received + * in this case the file fill not be deleted from server + */ + override public function cancel():void + { + try{ + trace ("UploadFileCommand:cancel"); + fileRef.cancel(); + + dispose(); + }catch(e:Error){ + trace ("UploadFileCommand cancel() error: "+e.message); + } + } + + /** + * Get the responce from server + */ + private function onUploadCompleteData(event:DataEvent):void + { + trace ("onUploadCompleteData", event); + complete(true, event.data); + } + + private function onHTTPStatus(event:HTTPStatusEvent):void + { + trace ("onHTTPStatus", event); + LoggerJS.log("fileReference.upload HTTPStatusEvent: " +event.status) + dispatchEvent(new TextEvent("httpStatus", false, false, event.status.toString() ) ); + } + + private function onError(event:Event):void + { + // choose error type + var errorType:String = null; + if (event is IOErrorEvent) { + errorType = "IOError"; + } else if (event is SecurityErrorEvent) { + errorType = "SecurityError"; + } + + LoggerJS.log("fileReference.upload onError: " +event.toString()); + + trace ("onError", event); + complete(false, null, new ErrorVO(event.toString(), errorType) ); + } + + private function onProgress(event:ProgressEvent):void + { + // because of bug (loaded:123123, total:0), use fileRef.size as total + dispatchEvent(new ProgressEvent( ProgressEvent.PROGRESS, false, false, event.bytesLoaded, fileRef.size) ); + } + + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/commands/UploadImageCommand.as b/flash/src/ru/mail/commands/UploadImageCommand.as new file mode 100644 index 00000000..9a2bf730 --- /dev/null +++ b/flash/src/ru/mail/commands/UploadImageCommand.as @@ -0,0 +1,160 @@ +package ru.mail.commands +{ + import flash.events.Event; + import flash.events.HTTPStatusEvent; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.events.TextEvent; + + import net.inspirit.MultipartURLLoader; + import net.inspirit.events.MultipartURLLoaderEvent; + + import ru.mail.data.vo.ErrorVO; + import ru.mail.utils.LoggerJS; + + /** + * Upload using MultipartURLLoader + * + * @author v.demidov + * + */ + public class UploadImageCommand extends AbstractUploadFileCommand + { + private var _files:Object; + private var _totalSize:int = 0; + private var _loader:MultipartURLLoader; + + public function UploadImageCommand(files:Object, url:String, headers:Object, uploadPostData:Object) + { + super(url, headers, uploadPostData); + + _files = files; + } + + override public function dispose():void + { + _loader.removeEventListener(Event.COMPLETE, onUploadComplete); + _loader.loader.removeEventListener(HTTPStatusEvent.HTTP_STATUS, onHTTPStatus); + _loader.loader.removeEventListener(IOErrorEvent.IO_ERROR, onError); + _loader.loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onError); + _loader.loader.removeEventListener(ProgressEvent.PROGRESS, onProgress); + + _loader.dispose(); + _loader = null; + _files = null; + } + + override public function execute():void + { + if ( _url == null ) { + complete(false, null, new ErrorVO("UploadImageCommand: upload url is null") ); + return; + } + + _loader = new MultipartURLLoader(); + // add listeners + _loader.addEventListener(Event.COMPLETE, onUploadComplete); + _loader.loader.addEventListener(ProgressEvent.PROGRESS, onProgress); + _loader.loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, onHTTPStatus); + _loader.loader.addEventListener(IOErrorEvent.IO_ERROR, onError); + _loader.loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError); + + // post data + for (var s:String in _uploadPostData) { + _loader.addVariable(s, _uploadPostData[s]); + } + // headers + if (_requestHeaders) { + _loader.requestHeaders = _requestHeaders; + } + + // files data + addFiles(); + + try + { + LoggerJS.log("Load file with multipartURLLoader. url = "+_url ); + _loader.addEventListener(MultipartURLLoaderEvent.DATA_PREPARE_COMPLETE, function(event:MultipartURLLoaderEvent):void { + event.currentTarget.removeEventListener(event.type, arguments.callee); + _loader.startLoad(); + }); + _loader.load(_url, true); + } + catch ( e:Error ){ + trace ("UploadImageCommand execute err", e); + complete( false, null, new ErrorVO( e.toString() ) ); + } + } + + /** + * add files + */ + private function addFiles():void + { + trace ("addFiles"); + for (var s:String in _files ) + { + for (var filename:String in _files[s]) { + trace ("s="+s+ ", filename = " + filename); + _totalSize += _files[s][filename].length; + _loader.addFile(_files[s][filename], filename, s); + } + } + } + + /** + * Cancels upload and disposes the object + */ + override public function cancel():void + { + try{ + trace ("UploadImageCommand:cancel"); + _loader.close(); + + dispose(); + }catch(e:Error){ + trace ("UploadImageCommand cancel() error: "+e.message); + } + } + + /** + * Get the responce from server + */ + private function onUploadComplete(event:Event):void + { + trace ("onUploadCompleteData", event); + complete(true, _loader.loader.data); + } + + private function onHTTPStatus(event:HTTPStatusEvent):void + { + trace ("onHTTPStatus", event); + LoggerJS.log("urlloader.upload onHTTPStatus: " +event.toString()); + dispatchEvent(new TextEvent("httpStatus", false, false, event.status.toString() ) ); + } + + private function onError(event:Event):void + { + // choose error type + var errorType:String = null; + if (event is IOErrorEvent) { + errorType = "IOError"; + } else if (event is SecurityErrorEvent) { + errorType = "SecurityError"; + } + + LoggerJS.log("fileReference.upload onError: " +event.toString()); + + trace ("onError", event); + complete(false, null, new ErrorVO(event.toString(), errorType) ); + } + + private function onProgress(event:ProgressEvent):void + { + // because of bug (loaded:123123, total:0), use _totalSize as total + dispatchEvent(new ProgressEvent( ProgressEvent.PROGRESS, false, false, event.bytesLoaded, _totalSize) ); + } + + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/commands/textloader/ITextLoader.as b/flash/src/ru/mail/commands/textloader/ITextLoader.as new file mode 100644 index 00000000..ce7e485c --- /dev/null +++ b/flash/src/ru/mail/commands/textloader/ITextLoader.as @@ -0,0 +1,13 @@ +package ru.mail.commands.textloader +{ + import flash.net.URLRequest; + + /** + * Simple util loader + */ + public interface ITextLoader + { + function loadText( request:URLRequest ):void; + } + +} \ No newline at end of file diff --git a/flash/src/ru/mail/commands/textloader/SimpleTextLoader.as b/flash/src/ru/mail/commands/textloader/SimpleTextLoader.as new file mode 100644 index 00000000..882c5728 --- /dev/null +++ b/flash/src/ru/mail/commands/textloader/SimpleTextLoader.as @@ -0,0 +1,66 @@ +package ru.mail.commands.textloader +{ + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IEventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.events.TimerEvent; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.utils.Timer; + + import ru.mail.commands.textloader.events.*; + import ru.mail.data.vo.ErrorVO; + + public class SimpleTextLoader extends EventDispatcher implements ITextLoader + { + private var _timeoutTimer:Timer = new Timer( _TIMEOUT, 1 ) ; // to keep off endless waiting + private var _loader:URLLoader= new URLLoader() ; + + private var _TIMEOUT:uint = 1 * 60 * 1000 ; // timeout in millisecond: minutes * secs per min * millisecs per sec + private const _SUCCESS:Boolean = true ; + + public function SimpleTextLoader( timeoutDelayInSecs:uint = 60 ) + { + _addURLLoaderListeners( _loader ) ; + + _timeoutTimer.delay = timeoutDelayInSecs * 1000; + _timeoutTimer.addEventListener( TimerEvent.TIMER + , function( e:TimerEvent ):void{ _complete( !_SUCCESS ); } ) ; + } + + /** + * method load data by request + */ + public function loadText( request:URLRequest ):void + { + _timeoutTimer.start() ; + try + { + _loader.load( request ) ; + } + catch( e:Error ) + { + _complete( !_SUCCESS, new ErrorVO( e.toString() ) ); + } + } + + private function _complete( isSuccess:Boolean, error:ErrorVO = null ):void + { + _timeoutTimer.stop() ; + dispatchEvent( new TextLoaderCompleteEvent( isSuccess, _loader.data, error ) ) ; + } + + private function _addURLLoaderListeners( dispatcher:IEventDispatcher ):void + { + dispatcher.addEventListener( Event.COMPLETE, function( e:Event ):void{ _complete( _SUCCESS ); } ); + dispatcher.addEventListener( SecurityErrorEvent.SECURITY_ERROR, function( e:Event ):void{ _complete( !_SUCCESS, new ErrorVO(e.toString()) ); } ); + dispatcher.addEventListener( IOErrorEvent.IO_ERROR, function( e:Event ):void{ _complete( !_SUCCESS, new ErrorVO(e.toString()) ); } ); + dispatcher.addEventListener( ProgressEvent.PROGRESS + , function( e:ProgressEvent ):void + { dispatchEvent( new LoaderProgressEvent( e.bytesLoaded, e.bytesTotal ) ) } ) ; + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/commands/textloader/events/LoaderProgressEvent.as b/flash/src/ru/mail/commands/textloader/events/LoaderProgressEvent.as new file mode 100644 index 00000000..ddc2f5ae --- /dev/null +++ b/flash/src/ru/mail/commands/textloader/events/LoaderProgressEvent.as @@ -0,0 +1,29 @@ +package ru.mail.commands.textloader.events +{ + import flash.events.Event; + + public class LoaderProgressEvent extends Event + { + public static const TYPE:String = "LoaderProgressEvent" ; + + public function LoaderProgressEvent( loaded:uint, total:uint ) + { + super( TYPE ) ; + _loaded = loaded ; + _total = total ; + } + + public function get total():uint + { + return _total ; + } + + public function get loaded():uint + { + return _loaded ; + } + + private var _loaded:uint ; + private var _total:uint ; + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/commands/textloader/events/TextLoaderCompleteEvent.as b/flash/src/ru/mail/commands/textloader/events/TextLoaderCompleteEvent.as new file mode 100644 index 00000000..5780e0e4 --- /dev/null +++ b/flash/src/ru/mail/commands/textloader/events/TextLoaderCompleteEvent.as @@ -0,0 +1,38 @@ +package ru.mail.commands.textloader.events +{ + import flash.events.Event; + + import ru.mail.data.vo.ErrorVO; + + public class TextLoaderCompleteEvent extends Event + { + public static const TYPE:String = "TextLoaderCompleteEvent" ; + + public function TextLoaderCompleteEvent( isSuccess:Boolean, content:String, error:ErrorVO = null ) + { + super( TYPE ) ; + _isSuccess = isSuccess ; + _content = content ; + _error = error; + } + + public function get isSuccess():Boolean + { + return _isSuccess ; + } + + public function get content():String + { + return _content ; + } + + public function get error():ErrorVO + { + return _error; + } + + private var _isSuccess:Boolean ; + private var _content:String ; + private var _error:ErrorVO; + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/communication/JSCallbackPresenter.as b/flash/src/ru/mail/communication/JSCallbackPresenter.as new file mode 100644 index 00000000..ea3bf0ac --- /dev/null +++ b/flash/src/ru/mail/communication/JSCallbackPresenter.as @@ -0,0 +1,114 @@ +package ru.mail.communication +{ + import flash.external.ExternalInterface; + import flash.system.Security; + + import ru.mail.controller.AppController; + import ru.mail.utils.LoggerJS; + + /** + * Configure js callback functions, redirect all of them to app controller. + * Controller will decide what to do with them + * @author v.demidov + * + */ + public class JSCallbackPresenter + { + private var appController:AppController; + + public function JSCallbackPresenter(appController:AppController) + { + this.appController = appController; + + try + { + Security.allowDomain("*"); + + ExternalInterface.addCallback("cmd", parseCmd); + } + catch (e:Error) { + trace ("{JSCallbackPresenter} - unable to set callback, error:", e.message); + } + } + + /** + * cmd('commandType', { ... }) + * + * @param command - string to determine what to do + * @param data - details - an object or a string, depending on command + * @return + * + */ + protected function parseCmd(command:String, data:Object):Boolean + { + switch (command) + { + case "accept": + appController.setTypeFilter(data.toString()); + break; + case "upload": + /* cmd("upload", {id:12312, url:, name:UploadDataFieldName + , data:URLVariables, headers:Object + , imageOriginal: Boolean, // send original or transformed + , imageTransform: Object, // transformObject + , callback:jsHandler}) */ + // headers: { 'Content-Type': 'application/x-mru-upload' , 'Content-Disposition': '...' , ...} + appController.uploadFile(data.url + , data.data, data.headers + , data.files + , data.callback); + break; + case "abort": + // cmd('abort', { id: '...' }) + appController.cancelFile(data.id); + break; + case "hitTest": + // cmd("hitTest") + // return whether mouse is over the flash + return appController.hitTest(); + break; + case "multiple": + appController.setMultipleSelect(data.toString() == "true"); + break; + case "clear": + // remove all files + appController.clear(); + break; + case "clearError": + // clear shared object error data + appController.clearError(); + break; + case "getFileInfo": + // cmd('getFileInfo', { id: '...', callback: '...' }); + appController.getFileInfo(data.id, data.callback) + break; + case "imageTransform": + // cmd('imageTransform', { + // id: '...', + // matrix: { + // sx: Number, // s* — original image region + // sy: Number, + // sw: Number, // if 0, then w - sx + // sh: Number, // if 0, then h - sy + // dw: Number, // if 0, then sw + // dh: Number, // if 0, then sh + // deg: Number + // resize: String, // min, max OR preview + // }, + // callback: '...' + //}); + appController.imageTransform(data.id, data.matrix, data.callback); + break; + default: + trace ("JSCallbackPresenter - cannot parse command: "+command); + LoggerJS.log("cannot parse command: "+command); + break; + } + + // by default function doesn't have to return anything + // If it have to, return it inside switch case + return false; + } + + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/communication/JSCaller.as b/flash/src/ru/mail/communication/JSCaller.as new file mode 100644 index 00000000..90029c91 --- /dev/null +++ b/flash/src/ru/mail/communication/JSCaller.as @@ -0,0 +1,208 @@ +package ru.mail.communication +{ + import flash.external.ExternalInterface; + + import ru.mail.data.vo.ErrorVO; + import ru.mail.data.vo.FileVO; + + /** + * Call JS methods via ExternalInterfaces + * + * @author v.demidov + * + */ + public class JSCaller + { + /** + * flashvar parameter with callback function name + */ + public static var callback:String = "callback"; + public static var flashId:String = "flashId"; + + private static var _instance:JSCaller = null; + + /** + * Reference to Singleton + * @return + * + */ + public static function get jsCaller():JSCaller + { + if (!_instance) { + _instance = new JSCaller(); + } + + return _instance; + } + + /** + * @private + * Constructor + */ + public function JSCaller() + { + } + + /** + * Call js + * @param _callback name of js function to call + * @param data object with all nesessary data + * + */ + public function callJS(_callback:String, data:Object, data2:Object = null):void + { + try { + // pass data to given callback + if (data2) { + ExternalInterface.call(_callback, data, data2); + } + else { + ExternalInterface.call(_callback, data); + } + } + catch (e:Error) { + trace ("callJS caused an exception", e); + } + } + + /** + * call js when the application is ready to work + * @param triesCount + * @return + * + */ + public function notifyJSAboutAppReady(triesCount:int):Boolean + { + var isReady:Boolean = false; + try { + var r:* = ExternalInterface.call(callback, {type:"ready", flashId:flashId}); + trace( "JSCaller.notifyJSAboutAppReady() ", triesCount ); + + isReady = ( r != null ); + } + catch ( e:Error ) { + trace ("notifyJSAboutAppReady error", e); + } + + return isReady; + } + + /** + * types: mouseenter, mouseleave, mouseDown, mouseUp + * @param eventType + * + */ + public function notifyJSMouseEvents(eventType:String):void + { + if (eventType == "rollOut") { + eventType = "mouseleave"; + } + else if (eventType == "rollOver") { + eventType = "mouseenter"; + } + + try { + ExternalInterface.call(callback, { type:eventType, flashId:flashId }); + } + catch (e:Error) { + trace ("notifyJSMouseEvents error", e); + } + } + + /** + * Notify js about file events - select, cancel, browse + * + * When sending select event, a files list is required. + * + * @param eventType + * @param filesVector + * + */ + public function notifyJSFilesEvents(eventType:String, filesVector:Vector. = null):void + { + trace ("{JSCaller} - notifyJSFilesEvents, eventType", eventType) + + var details:Object = new Object(); + details.type = eventType; + + if (eventType == "select") + { + if (!filesVector) { + throw new Error("{JSCaller} - notifyJSFilesEvents: filesVector is null"); + } + + // create fileInfo array: + var fileList:Array = new Array(); + var file:Object; + + for (var i:uint = 0; i < filesVector.length; i++) + { + // [{ uid: ... // unique id , name: "" // fileName , type: "" // fileType , size: "" // fileSize }] + file = new Object(); + file.id = filesVector[i].fileID; + file.flashId = flashId; + file.name = filesVector[i].fileName; + file.type = filesVector[i].fileType; + file.size = filesVector[i].fileSize; + + fileList.push(file); + } + + // add information about files + details.target = {files: fileList}; + details.flashId = flashId; + } + + try + { + ExternalInterface.call(callback, details); + } + catch (e:Error) { + trace ("notifyJSFilesEvents error",e); + } + } + + /** + * Send 'ping' event. + * @param status 'ok|error' + * @param savedStatus 'ok|error', value from sharedObject + * @param error - error's message + */ + public function notifyPing(statusOk:Boolean, savedStatusOk:Boolean, error:String = null):void + { + var details:Object = new Object(); + details.type = "ping"; + details.status = statusOk? 'ok' : 'error'; + details.savedStatus = savedStatusOk? 'ok' : 'error'; + details.flashId = flashId; + if (error) + details.error = error; + + try + { + ExternalInterface.call(callback, details); + } + catch (e:Error) { + trace ("notifyJSErrors error",e); + } + } + + /** + * Notify JS about errors not related to load and upload process + * + * Load and upload errors are handled in special callbacks, so use callJS() for them + * @return + * + */ + public function notifyJSErrors( errorVO:ErrorVO ):void + { + try + { + ExternalInterface.call(callback, {type:"error", message:errorVO.getError(), flashId:flashId}); + } + catch (e:Error) { + trace ("notifyJSErrors error",e); + } + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/controller/AppController.as b/flash/src/ru/mail/controller/AppController.as new file mode 100644 index 00000000..a24e3ec4 --- /dev/null +++ b/flash/src/ru/mail/controller/AppController.as @@ -0,0 +1,676 @@ +package ru.mail.controller +{ + import by.blooddy.crypto.Base64; + + import flash.display.Graphics; + import flash.display.Sprite; + import flash.display.Stage; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.ProgressEvent; + import flash.events.TextEvent; + import flash.events.TimerEvent; + import flash.net.FileFilter; + import flash.net.URLRequest; + import flash.system.Security; + import flash.utils.Timer; + + import ru.mail.commands.LoadFileCommand; + import ru.mail.commands.UploadCommand; + import ru.mail.commands.textloader.SimpleTextLoader; + import ru.mail.commands.textloader.events.TextLoaderCompleteEvent; + import ru.mail.communication.*; + import ru.mail.data.AttachmentsModel; + import ru.mail.data.IImageFactory; + import ru.mail.data.vo.BaseFileVO; + import ru.mail.data.vo.ErrorVO; + import ru.mail.data.vo.IFileVO; + import ru.mail.data.vo.ImageTransformVO; + import ru.mail.engines.chain.AbstractEngine; + import ru.mail.engines.chain.EnginesFactory; + import ru.mail.engines.chain.manage.SelectFilesEngine; + import ru.mail.engines.chain.presentation.MouseListenerEngine; + import ru.mail.engines.commands.AbstractEngineCommand; + import ru.mail.engines.commands.MouseListenerEngineCommand; + import ru.mail.engines.events.CommandCompleteEvent; + import ru.mail.events.ImageTransformCompleteEvent; + import ru.mail.events.UploadCompleteEvent; + import ru.mail.utils.LoggerJS; + + /** + * Application controller. + * + * @author v.demidov + * + */ + public class AppController extends EventDispatcher + { + private var _model:AttachmentsModel; + + private var _jsCaller:JSCaller; + private var _jsCallbackPresenter:JSCallbackPresenter; + + private var _view:Sprite; + + private var _options:Object; // flashvars {callback, ping} + + /** + * root of engines chain + */ + private var _chainRoot:AbstractEngine = null; + + /** + * call js ready notification until it responce + */ + private var readyTimer:Timer = new Timer(10); + private var readyTimerCount:int = 0; + + /** + * + * @param graphicContext + * @param options {callback, ping} + * + */ + public function AppController(graphicContext:Sprite, options:Object) + { + // init model + _model = AttachmentsModel.model; + // init js + _jsCaller = JSCaller.jsCaller; + + // configure js callbacks + _jsCallbackPresenter = new JSCallbackPresenter(this); + + // init view + _view = graphicContext; + initView(graphicContext); + + // parse flashvars + _options = options; + JSCaller.callback = getJsFunctionName(options, JSCaller.callback); + // error store prefix + _model.storeKey = options["storeKey"]; + LoggerJS.log("storeKey="+_model.storeKey); + _model.updateHasError(); + checkSOClear(options); + // get flashId from flashvars + if (options["flashId"]) { + JSCaller.flashId = options["flashId"]; + } + + setupChain(); + configureListeners(); + + // check ping and complete initialisation + completeInitialization(options); + } + + //=================================================== + // + // Initialization + // + //=================================================== + + /** + * We need transparent sprite to listen to mouseEvents + * JS places it over its button + * @param graphicContext + * + */ + private function initView(graphicContext:Sprite):void + { + // init with some default dimensionsm then listen to Stage resize event + resizeView(graphicContext, 300, 50); + + // use hand cursor true + setCursor("pointer"); + } + + /** + * Draw transparent rectangle with given dimenions + * @param graphicContext + * @param width + * @param height + * + */ + private function resizeView(graphicContext:Sprite, width:Number, height:Number):void + { + var g:Graphics = graphicContext.graphics; + g.clear(); + g.beginFill(0xCCCCCC, 0); + g.moveTo(0,0); + g.lineTo(width,0); + g.lineTo(width,height); + g.lineTo(0,height); + g.lineTo(0,0); + g.endFill(); + } + + /** + * construct chain of engines + */ + private function setupChain():void + { + // init factory + var factory:EnginesFactory = EnginesFactory.getEnginesFactory(); + var engine:AbstractEngine = null; + + engine = factory.getFactory(SelectFilesEngine.TYPE); + _chainRoot = engine; + engine.addEventListener(CommandCompleteEvent.TYPE, onFilesSelected); + + engine = factory.getFactory(MouseListenerEngine.TYPE); + _chainRoot.addEngine(engine); + (engine as MouseListenerEngine).view = _view; + } + + private function configureListeners():void + { + // listener for events from controller and from bubbled events + addEventListener(AbstractEngineCommand.COMMAND_EVENT_TYPE, onEngineCommand); + + // subscribe all engines to AbstractEngineCommand event + var engine:AbstractEngine = _chainRoot; + + while (engine) + { + engine.addEventListener(AbstractEngineCommand.COMMAND_EVENT_TYPE, onEngineCommand); + engine = engine.next; + } + } + + /** + * Get JS function name from options + * @param defaultFunctionName + * @return + * + */ + private function getJsFunctionName (options:Object, defaultFunctionName:String):String{ + var functionName:String = options[defaultFunctionName]; + if(functionName == null || functionName == ""){ + return defaultFunctionName; + } else { + return functionName; + } + } + + /** + * check if we have to clear shared object + * @param options + */ + private function checkSOClear(options:Object):void + { + if (options["clearError"] == "1") { + LoggerJS.log("clear saved error"); + _model.clearError(); + } + } + + /** + * Check that network is available, if needed, and notify js about ready + * Use ping urls from flashvars, + * Notify about flash ready only if ping successed + */ + private function completeInitialization(options:Object):void + { + var pingUrls:Array = getPing(options); + if (pingUrls && pingUrls.length > 0) { + var checksRemaining:int = pingUrls.length; + for(var i:uint = 0; i < pingUrls.length; i++) + { + var checkPingLoader:SimpleTextLoader = new SimpleTextLoader(); + if(pingUrls[i] == "") { + checksRemaining--; + if (checksRemaining == 0) { + if (_model.hasError) { + _jsCaller.notifyPing(true, false, "has saved error 2038"); + } + else { + _jsCaller.notifyPing(true, true); + } + notifyJSAboutAppReady(); // ready + } + continue; + } + + Security.loadPolicyFile( createPingRequest(pingUrls[i]) ); + + checkPingLoader.addEventListener(TextLoaderCompleteEvent.TYPE, function(event:TextLoaderCompleteEvent):void { + event.currentTarget.removeEventListener(event.type, arguments.callee); + if (event.isSuccess) { + checksRemaining --; + if (checksRemaining == 0) { + if (_model.hasError) { + // check shared object, if it contains information about error 2038, do not call ready + // instead, notify about this error + _jsCaller.notifyPing(true, false, "has saved error 2038"); + } + else { + _jsCaller.notifyPing(true, true); + } + notifyJSAboutAppReady(); // ready even if saved error + } + } + else { + _jsCaller.notifyPing(false, !_model.hasError, "failed to ping "+request.url); + if (!_model.hasError) { + _model.hasError = true; + } + } + }); + + var request:URLRequest = new URLRequest( createPingRequest(pingUrls[i]) ); + checkPingLoader.loadText( request ); + } + } + else { + // no ping required + // initialisation complete, notifyJS + notifyJSAboutAppReady(); + } + } + + /** + * parse ping urls from flashvars + */ + private function getPing(options:Object):Array { + var urls:Array; + var fvUrls:String = options["ping"]; + if(fvUrls != null && fvUrls != "") { + urls = fvUrls.split(","); + } + return urls; + } + + private function createPingRequest(url:String):String + { + return new String().concat(url, "/crossdomain.xml", "?", Math.random()); + } + + private function notifyJSAboutAppReady():void + { + // wait for boolean answer + readyTimer.addEventListener(TimerEvent.TIMER, onReadyTimer); + + readyTimer.start(); + } + + private function onReadyTimer(event:TimerEvent):void + { + if (_jsCaller.notifyJSAboutAppReady(++readyTimerCount)) + { + readyTimer.removeEventListener(TimerEvent.TIMER, onReadyTimer); + readyTimer.stop(); + } + } + + //=================================================== + // + // Events + // + //=================================================== + + /** + * choose engine and handle command + * @param command + */ + private function onEngineCommand(command:AbstractEngineCommand):void + { + var commandHandlers:Vector. = new Vector.(); + try { + commandHandlers = _chainRoot.isCommandHandable(command); + } + catch(error:Error) { + } + + var handlerToUse:AbstractEngine = null; + + if (commandHandlers.length == 1) { + handlerToUse = commandHandlers[0]; + } + else if (commandHandlers.length == 0) { + trace("No one engine could handle this command. Type is " + command.commandType); + } + else if (commandHandlers.length > 1) { + handlerToUse = commandHandlers[0]; + trace("there is several engine to handle this command! Using only first by default. Type is " + command.commandType); + } + + //execute current command + if (handlerToUse != null) { + try { + handlerToUse.handle(command); + } + catch(error:Error) { + trace ("onEngineCommand - calling handle on engine caused error:", error.message); + _jsCaller.notifyJSErrors( new ErrorVO( error.toString() ) ); + } + } + } + + public function onStageResize(event:Event):void + { + //resizeView (_view, (event.target as Stage).stageWidth, (event.target as Stage).stageHeight); + } + + private function onFilesSelected(event:CommandCompleteEvent):void + { + // enable rollout + dispatchEvent(new MouseListenerEngineCommand(true)); + } + + //=================================================== + // + // JS Callbacks + // + //=================================================== + + /** + * Set new file filter + * + * @param string 'jpg,gif,...' or '*' + * + * extension: it must be a semicolon-delimited list of file extensions, + * with a wildcard (*) preceding each extension, + * as shown in the following string: "*.jpg;*.gif;*.png" + * + */ + public function setTypeFilter(str:String):void + { + var filter:FileFilter; + var filtersArray:Array = []; + + try + { + if (str && str.length > 0) { + // convert to fileFilter format + str = str.replace(new RegExp(",", "g"), ";*."); + // add first wildcard + str = "*."+str; + + filter = new FileFilter("Files", str); + filtersArray.push(filter); + } + } + catch (e:Error) { + trace("setTypeFilter error") + } + + if (filtersArray.length > 0) + { + trace ("setTypeFilter sucsessful"); + _model.fileFilters = filtersArray; + } + } + + /** + * set cursor type + * in case type is "poiter" - use hand cursor + * in case "default" - use default arrow + * @param type + * + */ + public function setCursor(type:String):void + { + trace ("{AppController} - setCursor", type); + switch (type) + { + case "pointer": + _view.useHandCursor = true; + _view.buttonMode = true; + break; + default: + _view.useHandCursor = false; + _view.buttonMode = false; + break; + } + } + + /** + * If mouse is over the view, then return true + * @return + * + */ + public function hitTest():Boolean + { + var stage:Stage = _view.stage; + var result:Boolean = false; + + if ( stage.mouseX >= 0 && stage.mouseX <= stage.stageWidth + && stage.mouseY >= 0 && stage.mouseY <= stage.stageHeight) + { + result = true; + } + + LoggerJS.log("hitTest, mouse: "+ stage.mouseX+", "+ stage.mouseY+ ", result = "+ result); + + return result; + } + + /** + * Set files select type + * @param value - if true, select multiple files (use FileReferenceList), + * if false, select 1 file (use FileReference) + * + */ + public function setMultipleSelect(value:Boolean):void + { + _model.useMultipleSelect = value; +// LoggerJS.log("set multiple, value: "+ _model.useMultipleSelect); + } + + //===================== load, resize =================== + + /** + * Load file, return some its properties, + * currently for images only. width, heigth, exif rotation. + * @param fileID + * @param callback + * + */ + public function getFileInfo(fileID:String, callback:String):void + { + trace ("getFileInfo"); + var file:BaseFileVO = _model.filesBuilder.getFileByID(fileID); + if (!file) { + trace ("file with id "+ fileID +" doen't exist"); + return; + } + + var imageFactory:IImageFactory = file.imageFactory; + (imageFactory as EventDispatcher).addEventListener(ImageTransformCompleteEvent.TYPE,function (event:ImageTransformCompleteEvent):void { + event.currentTarget.removeEventListener(event.type, arguments.callee); + trace("getfileinfo complete", event.isSuccess); + if (event.isSuccess && (file as IFileVO).imageData) + { + // report file info + var info:Object = { "width":(file as IFileVO).imageData.width + , "height":(file as IFileVO).imageData.height + }; + var exif:Object = imageFactory.readExif(); + if ( exif ) { + info["exif"] = exif; + } + _jsCaller.callJS(callback, false, info); + } + else { + // report error + _jsCaller.callJS(callback, event.error.getError() ); + } + }); + + imageFactory.createImage(null); + } + + /** + * Transform image, return base64 string with transformed image. + * If needed, load file before transform (if it wasn't loaded yet) + * @param fileID + * @param trans + * @param callback + */ + public function imageTransform(fileID:String, trans:Object, callback:String):void + { + try { + // transform image and return base64 + var file:BaseFileVO = _model.filesBuilder.getFileByID(fileID); + if (!file) { + trace ("file with id "+ fileID +" doen't exist"); + return; + } + + LoggerJS.log("imageTransform, fileId = "+fileID); + + var imageFactory:IImageFactory = file.imageFactory; + (imageFactory as EventDispatcher).addEventListener(ImageTransformCompleteEvent.TYPE,function (event:ImageTransformCompleteEvent):void { + event.currentTarget.removeEventListener(event.type, arguments.callee); + trace("imageTransform complete", event.isSuccess); + if (event.isSuccess) { + _jsCaller.callJS( callback, false, Base64.encode(event.data) ); + } + else { + // report error + _jsCaller.callJS(callback, event.error.getError() ); + } + }); + + imageFactory.createImage( trans? new ImageTransformVO(trans.sx, trans.sy, trans.sw, trans.sh, trans.dw, trans.dh, trans.deg) : null ); + } + catch (e:Error){ + _jsCaller.notifyJSErrors(new ErrorVO(e.toString())); + } + } + + + //===================== upload ========================= + + /** + * + * @param url - where to upload + * @param uploadPostData - URLVariables + * @param headers - custom request headers for URLLoader + * @param files - object with files uids and matrix objects
+ * exapmle - files: {'filename[original]': { id, name, matrix:null }, 'filename[XL]': { id, name, matrix:{...} } } + * when there is only 1 file with null matrix, file will be uploaded using fileReference + * @param callback + * + */ + public function uploadFile( url:String + , uploadPostData:Object + , headers:Object + , files:Object + , callback:String ):void + { + trace ("upload file") + try { + LoggerJS.log("call upload"); + + // get files + var file:BaseFileVO; + for (var s:String in files) + { + file = _model.filesBuilder.getFileByID(files[s].id); + if (!file) { + trace ("file with id "+ files[s].id +" doen't exist"); + LoggerJS.log("upload: file with id "+ files[s].id + " doen't exist"); + return; + } + files[s].file = file; + } + + // launch command + var httpStatus:String; + var uploadCommand:UploadCommand = new UploadCommand(url + , uploadPostData, headers + , files ); + + uploadCommand.addEventListener(UploadCompleteEvent.TYPE, function(event:UploadCompleteEvent):void { + // complete + if (event.isSuccess) { + var result:String = event.result.toString().split("%").join("%25") + .split("\\").join("%5c") + .split("\"").join("%22") + .split("&").join("%26"); + _jsCaller.callJS( callback, {type:"complete", result:result, status:httpStatus} ); + } + else { + if (event.error.error.indexOf("#2038") > -1) { + _model.hasError = true; + } + _jsCaller.callJS( callback, {type:"error", message:event.error.getError() } ); + } + + uploadCommand.dispose(); + }); + + uploadCommand.addEventListener(ProgressEvent.PROGRESS, function(progressEvent:ProgressEvent):void { + _jsCaller.callJS( callback, {type:"progress", loaded: progressEvent.bytesLoaded, total: progressEvent.bytesTotal} ); + }); + + uploadCommand.addEventListener("httpStatus", function(textEvent:TextEvent):void { + httpStatus = textEvent.text; + //_jsCaller.callJS( callback, {type:"httpStatus", message:(textEvent as TextEvent).text} ); + }); + + // save command + file.uploadCommand = uploadCommand; + // run + uploadCommand.execute(); + } + catch (err:Error) { + _jsCaller.notifyJSErrors( new ErrorVO( err.toString() ) ); + } + } + + //===================== clear, cancel ========================= + /** + * If file is in uploading state, cancel the upload. + * Remove file from model. + * @param fileID + * + */ + public function cancelFile(fileID:String):void + { + trace ("{AppController} - cancelFile", fileID); + + var file:BaseFileVO = _model.filesBuilder.getFileByID(fileID); + if (!file) { + trace ("file with id "+ fileID +" doen't exist"); + return; + } + + if (file.uploadCommand) { + (file.uploadCommand as UploadCommand).cancel(); + } + if (file.loadCommand) { + (file.loadCommand as LoadFileCommand).cancel(); + } + _model.filesBuilder.removeFile(file); + file = null; + + } + + /** + * Clear all data and release memory + */ + public function clear():void + { + LoggerJS.log('clear'); + var files:Vector. = _model.filesBuilder.items; + + for each (var file:BaseFileVO in files) { + cancelFile(file.fileID); + } + _model.filesBuilder.removeAllFiles(); + } + + /** + * clear shared object's data + */ + public function clearError():void { + _model.clearError(); + completeInitialization(_options); + } + + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/data/AbstractImageFactory.as b/flash/src/ru/mail/data/AbstractImageFactory.as new file mode 100644 index 00000000..527eff0e --- /dev/null +++ b/flash/src/ru/mail/data/AbstractImageFactory.as @@ -0,0 +1,29 @@ +package ru.mail.data +{ + import ru.mail.data.vo.IFileVO; + + /** + * Produce factories for file + * + * @author v.demidov + * + */ + public class AbstractImageFactory + { + private var file:IFileVO; + + public function AbstractImageFactory(target:IFileVO) + { + if (!target) { + throw new Error("{ImageFactory} - init: target is null"); + } + + file = target; + } + + public function getImageFactory():IImageFactory + { + return new ImageFactory(file); + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/data/AttachmentsModel.as b/flash/src/ru/mail/data/AttachmentsModel.as new file mode 100644 index 00000000..44caf510 --- /dev/null +++ b/flash/src/ru/mail/data/AttachmentsModel.as @@ -0,0 +1,146 @@ +package ru.mail.data +{ + import flash.events.EventDispatcher; + import flash.net.FileFilter; + import flash.net.SharedObject; + + import ru.mail.data.builder.FilesDataBuilder; + + public class AttachmentsModel extends EventDispatcher + { + + //================================================= + // singleton + //================================================= + + protected static var _instance:AttachmentsModel = null; + + /** + * reference to Singleton + * @return + * + */ + public static function get model():AttachmentsModel + { + if (!_instance) + { + _instance = new AttachmentsModel(); + } + + return _instance + } + + //================================================= + // consts + //================================================= + + protected const DEFAULT_FILTER:FileFilter = new FileFilter( "All types", "*.*"); + + //================================================= + // vars + //================================================= + + // TODO: remove unused vars + + public var fileFilters:Array = [DEFAULT_FILTER]; + + protected var _filesBuilder:FilesDataBuilder = new FilesDataBuilder() + + public function get filesBuilder():FilesDataBuilder + { + return _filesBuilder; + } + + /** + * if true, user can select multiple files + */ + public var useMultipleSelect:Boolean = true; + + public var storeKey:String = ""; + protected var _hasError:Boolean = false; + /** + * read from sharedObject, whether application experienced error #2038 - problems with authorisation + * while uploading through proxy. + * @return + * + */ + public function get hasError():Boolean + { + return _hasError; + } + + public function set hasError(value:Boolean):void + { + _hasError = value; + writeError(value); + } + + public function clearError():void { + _clearError(); + _hasError = false; + } + + /** + * @private Constructor + */ + public function AttachmentsModel() + { + super(); + + // lock + if (_instance) + { + throw new Error("AttachmentsModel is singleton class, use get model method instead"); + } + + _hasError = readError(); + } + + public function updateHasError():void { + _hasError = readError(); + } + + /** + * whether error #2038 occured or not + * @return + */ + private function readError():Boolean { + try + { + var so:SharedObject = SharedObject.getLocal("flashfileapi", '/'); + var error:String = so.data[storeKey+"savedError"]; + + trace ("attachmetsModel read sharedobject, error = " + error); + + return (error == "1"); + } + catch (e:Error) { + trace ("read shared object error: "+e); + } + + return false; + } + + private function writeError(isError:Boolean):void { + try + { + var so:SharedObject = SharedObject.getLocal("flashfileapi", '/'); + so.data[storeKey+"savedError"] = isError? "1" : "0"; + so.flush(); + } + catch (e:Error) { + trace ("write shared object error: "+e); + } + } + + /** + * clear shared object data + */ + private function _clearError():void { + var so:SharedObject = SharedObject.getLocal("flashfileapi", '/'); + so.clear(); + } + + + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/data/IImageFactory.as b/flash/src/ru/mail/data/IImageFactory.as new file mode 100644 index 00000000..04fbfa38 --- /dev/null +++ b/flash/src/ru/mail/data/IImageFactory.as @@ -0,0 +1,28 @@ +package ru.mail.data +{ + import ru.mail.data.vo.ImageTransformVO; + + /** + * produces transformed images from target's source + * @author v.demidov + * + */ + public interface IImageFactory + { + /** + * Create transformed image using imageTransform params. + * if imageTransform is null, return original image. + * If original image has not been loaded, first load it. + * The result image is returned async via completeEvent + * @param imageTransform + * + */ + function createImage(imageTransform:ImageTransformVO):void; + /** + * try to read file's exif. return object with "Orientation" value + * @return + * + */ + function readExif():Object; + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/data/ImageFactory.as b/flash/src/ru/mail/data/ImageFactory.as new file mode 100644 index 00000000..f1ecbe3c --- /dev/null +++ b/flash/src/ru/mail/data/ImageFactory.as @@ -0,0 +1,162 @@ +package ru.mail.data +{ + import flash.display.BitmapData; + import flash.events.EventDispatcher; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.utils.ByteArray; + + import ru.mail.commands.DecodeBytesToBitmapCommand; + import ru.mail.commands.LoadFileCommand; + import ru.mail.commands.ResizeFileCommand; + import ru.mail.data.vo.ErrorVO; + import ru.mail.data.vo.FileVO; + import ru.mail.data.vo.IFileVO; + import ru.mail.data.vo.ImageTransformVO; + import ru.mail.events.DecodeBytesToBitmapCompleteEvent; + import ru.mail.events.ImageTransformCompleteEvent; + import ru.mail.utils.ExifReader2; + + /** + * Produces images from file's source + * + * If file is not loaded, load it + * and create transformed image + * + * @author v.demidov + * + */ + public class ImageFactory extends EventDispatcher implements IImageFactory + { + private var file:IFileVO; + + private var loadCommand:LoadFileCommand; + + public function ImageFactory(target:IFileVO) + { + if (!target) { + throw new Error("{ImageFactory} - init: target is null"); + } + + file = target; + } + + public function createImage(imageTransform:ImageTransformVO):void { + if (!file.fileData) { + // first load file + if( file is FileVO) { + var loadFileCommand:LoadFileCommand = (file as FileVO).loadCommand? (file as FileVO).loadCommand as LoadFileCommand : new LoadFileCommand(file as FileVO); + + loadFileCommand.addEventListener(ImageTransformCompleteEvent.TYPE, function(event:ImageTransformCompleteEvent):void{ + event.currentTarget.removeEventListener(event.type, arguments.callee); + trace ("loadCommand complete", event.isSuccess); + loadCommand = null; + if(event.isSuccess) { + checkImageData(imageTransform); + } + else { + complete( false, event.data, event.error ); + } + }); + if (!(file as FileVO).loadCommand) + loadFileCommand.execute(); + (file as FileVO).loadCommand = loadFileCommand; + } + } + else { + checkImageData(imageTransform); + } + } + + public function readExif():Object { + if (!file.fileData) { + return null; + } + + var exif:Object = {}; + try { + var exifReader:ExifReader2 = new ExifReader2(); + exifReader.processData(file.fileData); + if ( exifReader.hasKey("Orientation") ) { + // more info about orientation + // http://sylvana.net/jpegcrop/exif_orientation.html + var orientation:uint = uint(exifReader.getValue("Orientation")); + + exif["Orientation"] = orientation; + } + + } catch (e:Error) { + trace ("read exif error: "+ e); + } + return exif; + } + + /** + * 1/2 Check if imageData has been loaded + * @param imageTransform + * + */ + private function checkImageData(imageTransform:ImageTransformVO):void + { + if (!file.imageData) { + // init image + var decodeCommand:DecodeBytesToBitmapCommand = new DecodeBytesToBitmapCommand( file.fileData ); + decodeCommand.addEventListener(DecodeBytesToBitmapCompleteEvent.TYPE, function(event:DecodeBytesToBitmapCompleteEvent):void { + event.currentTarget.removeEventListener(event.type, arguments.callee); + trace ("bitmap created, isSuccess", event.isSuccess); + if (event.isSuccess) { + file.imageData = event.decodedBitmap.bitmapData; + file.imageData = new BitmapData( event.decodedBitmap.width, event.decodedBitmap.height ); + file.imageData.copyPixels( event.decodedBitmap.bitmapData + , new Rectangle( 0, 0, event.decodedBitmap.width, event.decodedBitmap.height ), new Point( 0, 0 )); + event.decodedBitmap.bitmapData.dispose(); + + createImageFromSource(imageTransform); + } + else { + complete( false, null, event.error ); + } + }); + decodeCommand.execute(); + } + else { + createImageFromSource(imageTransform); + } + } + + /** + * 2/2 transform original imagedata + * @param imageTransform + * + */ + private function createImageFromSource(imageTransform:ImageTransformVO):void + { + if (imageTransform == null) { + // dispatch event with source + complete(true, file.fileData); + } + else { + // transform then dispatch + var resizeCommand:ResizeFileCommand = new ResizeFileCommand(file, imageTransform); + + resizeCommand.addEventListener(ImageTransformCompleteEvent.TYPE, function(event:ImageTransformCompleteEvent):void { + event.currentTarget.removeEventListener(event.type, arguments.callee); + if (event.isSuccess) { + complete(true, event.data); + } + else { + complete(false, null, event.error); + } + }); + + resizeCommand.execute(); + } + } + + private function complete(isSuccess:Boolean, data:ByteArray, error:ErrorVO = null):void + { + trace ("imageFactory complete"); + dispatchEvent( new ImageTransformCompleteEvent( isSuccess, data, error ) ); + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/data/builder/AbstractDataBuilder.as b/flash/src/ru/mail/data/builder/AbstractDataBuilder.as new file mode 100644 index 00000000..f6c315d8 --- /dev/null +++ b/flash/src/ru/mail/data/builder/AbstractDataBuilder.as @@ -0,0 +1,194 @@ +package ru.mail.data.builder +{ + import flash.events.EventDispatcher; + + import ru.mail.data.vo.BaseFileVO; + + public class AbstractDataBuilder extends EventDispatcher + { + protected var _type:String = "AbstractDataBuilder" + + public function get type():String + { + return _type; + } + + protected var _items:Vector. = new Vector.(); + /** + * Return just a copy of items vector + * @return + * + */ + public function get items():Vector. + { + return _items.slice(); + } + + public function AbstractDataBuilder(type:String) + { + super(); + + _type = type; + } + + /** + * Check id and add file to the collection. + * @param file + * @return file that have been added + * + */ + public function addFile(file:BaseFileVO):BaseFileVO + { + _items.push(file); + validateID(file) + + return file; + } + + /** + * Search among all items and return file that matches given fileID + * @param fileID + * @return + * + */ + public function getFileByID(fileID:String):BaseFileVO + { + var result:BaseFileVO = null; + + for (var i:uint = 0; i < _items.length; i++) + { + if (_items[i].fileID == fileID) { + result = _items[i]; + break; + } + } + + return result; + } + + /** + * Return file at given index. + * @param index + * @return + * + */ + public function getFileAt(index:int):BaseFileVO + { + return _items[index]; + } + + /** + * Remove given file + * @param file + * @return removed file + * + */ + public function removeFile(file:BaseFileVO):BaseFileVO + { + var index:int = _items.indexOf(file); + + return removeFileAt(index); + } + + /** + * + * @param fileID - id of the file to be removed + * @return removed file + * + */ + public function removeFileByID(fileID:String):BaseFileVO + { + var file:BaseFileVO = getFileByID(fileID); + + if (file != null) + { + removeFile(file); + } + + return file; + } + + /** + * If index is valid, remove file from this index + * @param index + * @return removed file + * + */ + public function removeFileAt(index:int):BaseFileVO + { + var result:BaseFileVO = null; + + if (index > -1 && index < _items.length) + { + result = _items.splice(index, 1)[0]; + result.loadCommand = null; + result.uploadCommand = null; + } + + return result; + } + + /** + * + * + */ + public function removeAllFiles():void + { + _items.length = 0; + } + + /** + * Return files array length + * @return + * + */ + public function getFilesCount():int + { + return _items.length; + } + + /** + * returns random id string + * @return + * + */ + public function generateID():String + { + return new String().concat( new Date().valueOf(), Math.floor( Math.random() * 1000 ) ); + } + + /** + * Validate that file's id is not null and differs from other file ids. + * + * If id is not valid, generate new one and assign it to the file. + * @param file + * @return true if id had been changed + * + */ + protected function validateID(file:BaseFileVO):Boolean + { + var isIdChanged:Boolean = false; + + // check null + if (file.fileID == null || file.fileID == ""){ + isIdChanged = true; + file.fileID = generateID(); + } + + // compare with other files + for (var i:uint = 0; i < _items.length; i++) + { + if (_items[i] == file) + continue; + + if (_items[i].fileID == file.fileID) { + // generate new id + isIdChanged = true; + file.fileID = generateID(); + } + } + + return isIdChanged; + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/data/builder/FilesDataBuilder.as b/flash/src/ru/mail/data/builder/FilesDataBuilder.as new file mode 100644 index 00000000..158dcd2a --- /dev/null +++ b/flash/src/ru/mail/data/builder/FilesDataBuilder.as @@ -0,0 +1,64 @@ +package ru.mail.data.builder +{ + import flash.net.FileReference; + + import ru.mail.data.AbstractImageFactory; + import ru.mail.data.vo.FileVO; + + /** + * Create and store files + * + * @author v.demidov + * + */ + public class FilesDataBuilder extends AbstractDataBuilder + { + public static const TYPE:String = "FilesDataBuilder"; + + public function FilesDataBuilder() + { + super(TYPE); + } + + public function createFileVO(fileReference:FileReference, fileID:String = '', addToCollection:Boolean = true):FileVO + { + if (fileReference == null) { + throw new Error("FilesDataBuilder - createFileVO: fileReference is null"); + } + // create + var fileVO:FileVO = new FileVO(); + // set props + fileVO.fileRef = fileReference; + fileVO.fileID = fileID; + //fileVO.imageFactory = new ImageFactory(fileVO, true); + fileVO.abstractImageFactory = new AbstractImageFactory(fileVO); + // add + if(addToCollection) + { + addFile(fileVO); + } + + return fileVO; + } + + /*public function createRestoredFileVO(url:String, fileID:String = '', addToCollection:Boolean = true):RestoredFileVO + { + if (!url || url == "") { + throw new Error("FilesDataBuilder - createRestoredFileVO: empty url"); + } + // create + var fileVO:RestoredFileVO = new RestoredFileVO(); + // set props + fileVO.url = url; + fileVO.fileID = fileID; + // add + if(addToCollection) + { + addFile(fileVO); + } + + return fileVO; + }*/ + + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/data/vo/BaseFileVO.as b/flash/src/ru/mail/data/vo/BaseFileVO.as new file mode 100644 index 00000000..7af93fe5 --- /dev/null +++ b/flash/src/ru/mail/data/vo/BaseFileVO.as @@ -0,0 +1,62 @@ +package ru.mail.data.vo +{ + import ru.mail.data.AbstractImageFactory; + import ru.mail.data.IImageFactory; + + /** + * This class contains almost all information about the file. The difference is only the source of data - + * it can be fileReference or loaded from url. + * load from url isn't implemented now. + * + * @author v.demidov + */ + public class BaseFileVO + { + protected var _fileID:String = ""; + + public function get fileID():String + { + return _fileID; + } + + public function set fileID(value:String):void + { + _fileID = value; + } + + // TODO: refactor + private var _status:String = "selected"; + + public function get status():String { + return _status; + } + public function set status(value:String):void { + _status = value; + } + + public var loadCommand:Object; + public var uploadCommand:Object; + + private var _abstractImageFactory:AbstractImageFactory; + + public function set abstractImageFactory(value:AbstractImageFactory):void + { + _abstractImageFactory = value; + } + /** + * factory for producing images + * + * @return + * + */ + public function get imageFactory():IImageFactory + { + return _abstractImageFactory.getImageFactory(); + } + + public function BaseFileVO() + { + + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/data/vo/ErrorVO.as b/flash/src/ru/mail/data/vo/ErrorVO.as new file mode 100644 index 00000000..ee08713e --- /dev/null +++ b/flash/src/ru/mail/data/vo/ErrorVO.as @@ -0,0 +1,47 @@ +package ru.mail.data.vo +{ + /** + * + * Try to get error type, message and ID from given string. + * + * Input string can be Error.toString() or ErrorEvent.toString() or anything else + * + * @author v.demidov + * + */ + public class ErrorVO + { + public var error:String = ""; + public var errorType:String = "error"; + public var errorID:String = ""; + public var errorMessage:String = ""; + + public function ErrorVO(error:String, errorType:String = null) + { + super(); + parseError(error); + if (errorType) { + this.errorType = errorType; + } + } + + public function parseError(str:String):void { + if (!str) { + return; + } + error = str; + + var idIndex:int = str.indexOf("#"); + var msgIndex:int = str.indexOf(":", idIndex); + var msgEndIndex:int = str.lastIndexOf('"'); + // #1234: + errorID = str.substring(idIndex+1, msgIndex); + //: Error details + errorMessage = str.substring(msgIndex+1, msgEndIndex == -1? str.length : msgEndIndex); + } + + public function getError():String { + return errorType + " " + errorID + ": " + errorMessage; + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/data/vo/FileStatesEnum.as b/flash/src/ru/mail/data/vo/FileStatesEnum.as new file mode 100644 index 00000000..6cdfc7b4 --- /dev/null +++ b/flash/src/ru/mail/data/vo/FileStatesEnum.as @@ -0,0 +1,17 @@ +package ru.mail.data.vo +{ + public class FileStatesEnum + { + public static const SELECTED:String = "selected"; + public static const LOADING:String = "loading"; + public static const LOADED:String = "loaded"; + public static const RESIZING:String = "resizing"; + public static const UPLOADING:String = "uploading"; + public static const UPLOADED:String = "uploaded"; + public static const READY:String = "ready"; + + public function FileStatesEnum() + { + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/data/vo/FileVO.as b/flash/src/ru/mail/data/vo/FileVO.as new file mode 100644 index 00000000..95594440 --- /dev/null +++ b/flash/src/ru/mail/data/vo/FileVO.as @@ -0,0 +1,87 @@ +package ru.mail.data.vo +{ + import flash.display.BitmapData; + import flash.net.FileReference; + import flash.utils.ByteArray; + + /** + * Value object contains file reference, unique ID etc + * + * @author v.demidov + * + */ + public class FileVO extends BaseFileVO implements IFileVO + { + public var fileRef:FileReference; + + public function get fileData():ByteArray + { + return fileRef.data; + } + + public function get fileSize():Number { + var fileSize:Number = 0; + try + { + fileSize = fileRef.size; + } + catch ( e:Error ) { } + + return fileSize; + } + + public function get fileName():String + { + return fileRef.name; + } + + public function get fileType():String + { + var fileNameParts:Array = fileName.split( "." ); + if ( fileNameParts.length < 2 ) + return ""; + + return ( fileNameParts[ fileNameParts.length - 1 ] ).toLowerCase(); + } + + private var _imageData:BitmapData; + /** + * original image bitmapData; + * @return + * + */ + public function get imageData():BitmapData + { + return _imageData; + } + + public function set imageData(bd:BitmapData):void + { + _imageData = bd; + } + + /** + * for modified images we change filetype, because we encode bmp and gif with png encoder. + * for jpg - do not modify, because we use jpg encoder for them + * @return + */ + public function get fileNameModified():String + { + if (fileType == 'jpg' || fileType == 'jpeg') { + return fileName; + } + else { + var fileNameParts:Array = fileName.split( "." ); + if ( fileNameParts.length < 2 ) + return fileName; + + return fileNameParts[0] + 'jpg'; + } + } + + public function FileVO() + { + super(); + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/data/vo/IFileVO.as b/flash/src/ru/mail/data/vo/IFileVO.as new file mode 100644 index 00000000..17b56aa6 --- /dev/null +++ b/flash/src/ru/mail/data/vo/IFileVO.as @@ -0,0 +1,29 @@ +package ru.mail.data.vo +{ + import flash.display.BitmapData; + import flash.utils.ByteArray; + + import ru.mail.data.IImageFactory; + + public interface IFileVO + { + function get fileID():String; + function get fileData():ByteArray; + function get fileSize():Number; + function get fileName():String; + function get fileNameModified():String; + /** + * it gets fileRef name, cuts the dot and returns the lowerCase file extenation. + * it returns empty string if type is null + */ + function get fileType():String; + + function get status():String; + function set status(value:String):void; + + function get imageData():BitmapData; + function set imageData(bd:BitmapData):void; + + function get imageFactory():IImageFactory; + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/data/vo/ImageTransformVO.as b/flash/src/ru/mail/data/vo/ImageTransformVO.as new file mode 100644 index 00000000..39824c0d --- /dev/null +++ b/flash/src/ru/mail/data/vo/ImageTransformVO.as @@ -0,0 +1,39 @@ +package ru.mail.data.vo +{ + /** + * Value object with transformation matrix + * + * @author v.demidov + * + */ + public class ImageTransformVO + { + public var sx:Number = 0; + public var sy:Number = 0; + public var sw:Number = 0; + public var sh:Number = 0; + public var dw:Number = 0; + public var dh:Number = 0; + public var deg:Number = 0; + + public function ImageTransformVO(sx:Number = 0, sy:Number = 0, sw:Number = 0, sh:Number = 0, dw:Number = 0, dh:Number = 0, deg:Number = 0) + { + super(); + + if ( !isNaN(sx) ) + this.sx = sx; + if ( !isNaN(sy) ) + this.sy = sy; + if ( !isNaN(sw) ) + this.sw = sw; + if ( !isNaN(sh) ) + this.sh = sh; + if ( !isNaN(dw) ) + this.dw = dw; + if ( !isNaN(dh) ) + this.dh = dh; + if ( !isNaN(deg) ) + this.deg = deg; + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/data/vo/RestoredFileVO.as b/flash/src/ru/mail/data/vo/RestoredFileVO.as new file mode 100644 index 00000000..fbb72d59 --- /dev/null +++ b/flash/src/ru/mail/data/vo/RestoredFileVO.as @@ -0,0 +1,60 @@ +package ru.mail.data.vo +{ + import flash.display.BitmapData; + import flash.utils.ByteArray; + + /** + * Restored file (from url or any else bytearray). If it is image, it cannot be scaled or rotated. + * @author v.demidov + * + */ + public class RestoredFileVO extends BaseFileVO implements IFileVO + { + public var url:String; + + private var _fileData:ByteArray; + + public function get fileData():ByteArray + { + return _fileData; + } + + public function set fileData(value:ByteArray):void + { + _fileData = value; + } + + public function get fileSize():Number { + return _fileData? _fileData.length : 0; + } + + public function get fileName():String { + return ""; + } + + public function get fileType():String { + return ""; + } + + private var _imageData:BitmapData; + /** + * original image bitmapData; + * @return + * + */ + public function get imageData():BitmapData + { + return _imageData; + } + + public function set imageData(bd:BitmapData):void + { + _imageData = bd; + } + + public function RestoredFileVO() + { + super(); + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/engines/chain/AbstractModelJSEngine.as b/flash/src/ru/mail/engines/chain/AbstractModelJSEngine.as new file mode 100644 index 00000000..f55ad762 --- /dev/null +++ b/flash/src/ru/mail/engines/chain/AbstractModelJSEngine.as @@ -0,0 +1,47 @@ +package ru.mail.engines.chain +{ + import ru.mail.communication.JSCaller; + import ru.mail.data.AttachmentsModel; + + public class AbstractModelJSEngine extends AbstractEngine implements IJsCallerHolder, IModelHolder + { + protected var _jsCaller:JSCaller; + /** + * reference to JS communicator + * @return + * + */ + public function get jsCaller():JSCaller + { + return _jsCaller; + } + public function set jsCaller(value:JSCaller):void + { + _jsCaller = value; + } + + protected var _model:AttachmentsModel; + /** + * reference to the application model + * @return + * + */ + public function get model():AttachmentsModel + { + return _model; + } + public function set model(value:AttachmentsModel):void + { + _model = value; + } + + public function AbstractModelJSEngine(engineType:String) + { + super(engineType); + + // default + model = AttachmentsModel.model; + jsCaller = JSCaller.jsCaller; + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/engines/chain/EnginesFactory.as b/flash/src/ru/mail/engines/chain/EnginesFactory.as new file mode 100644 index 00000000..c30a3287 --- /dev/null +++ b/flash/src/ru/mail/engines/chain/EnginesFactory.as @@ -0,0 +1,42 @@ +package ru.mail.engines.chain +{ + import ru.mail.engines.chain.manage.SelectFilesEngine; + import ru.mail.engines.chain.presentation.MouseListenerEngine; + + /** + * concreate engines factory for attachments uploader + */ + public class EnginesFactory extends AbstractEnginesFactory + { + protected static var _instance:EnginesFactory = null; + + /** + * should be overrided in derived classes + * @return + * + */ + public static function getEnginesFactory():EnginesFactory { + if ( !_instance ) { + _instance = new EnginesFactory(); + } + + return _instance; + } + + public function EnginesFactory() { + super(); + + if ( _instance ) { + throw new Error("EnginesFactory is singleton class, use get engineFactory method instead"); + } + } + + override protected function registerEngines():void { + _registeredEngines.push(new SelectFilesEngine()); + _registeredEngines.push(new MouseListenerEngine()); +// _registeredEngines.push(new UploadEngine()); + + super.registerEngines(); + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/engines/chain/IJsCallerHolder.as b/flash/src/ru/mail/engines/chain/IJsCallerHolder.as new file mode 100644 index 00000000..088e2b9b --- /dev/null +++ b/flash/src/ru/mail/engines/chain/IJsCallerHolder.as @@ -0,0 +1,15 @@ +package ru.mail.engines.chain +{ + import ru.mail.communication.JSCaller; + + /** + * For Engines that have jsCaller property + * @author v.demidov + * + */ + public interface IJsCallerHolder + { + function get jsCaller():JSCaller; + function set jsCaller(value:JSCaller):void + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/engines/chain/IModelHolder.as b/flash/src/ru/mail/engines/chain/IModelHolder.as new file mode 100644 index 00000000..2b53fc87 --- /dev/null +++ b/flash/src/ru/mail/engines/chain/IModelHolder.as @@ -0,0 +1,15 @@ +package ru.mail.engines.chain +{ + import ru.mail.data.AttachmentsModel; + + /** + * For Engines that have link to the Attachments model + * @author v.demidov + * + */ + public interface IModelHolder + { + function get model():AttachmentsModel; + function set model(value:AttachmentsModel):void; + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/engines/chain/manage/SelectFilesEngine.as b/flash/src/ru/mail/engines/chain/manage/SelectFilesEngine.as new file mode 100644 index 00000000..0acf0a50 --- /dev/null +++ b/flash/src/ru/mail/engines/chain/manage/SelectFilesEngine.as @@ -0,0 +1,165 @@ +package ru.mail.engines.chain.manage +{ + import flash.errors.IllegalOperationError; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.net.FileReference; + import flash.net.FileReferenceList; + + import ru.mail.data.vo.ErrorVO; + import ru.mail.data.vo.FileVO; + import ru.mail.engines.chain.AbstractModelJSEngine; + import ru.mail.engines.commands.AbstractEngineCommand; + import ru.mail.engines.commands.SelectFilesCommand; + import ru.mail.engines.events.CommandCompleteEvent; + import ru.mail.engines.exceptions.WrongEngineCommandType; + + /** + * Select files, add files to model, send filesInfo + * + * @author v.demidov + * + */ + public class SelectFilesEngine extends AbstractModelJSEngine + { + public static const TYPE:String = "SelectFilesEngine"; + + /** + * It can be whether FileReferenceList or FileReference. It depends on model.useMultipleSelect value + */ + protected var _fileRefObj:EventDispatcher = null; + + protected var _isActive:Boolean = false; + + public function SelectFilesEngine() + { + super(TYPE); + } + + override public function handle(e:AbstractEngineCommand):void + { + var command:SelectFilesCommand = e as SelectFilesCommand; + + if (command == null){ + throw new WrongEngineCommandType("{SelectFilesEngine} - handle: command type is: " + command.type + " engine to handle is: " + command.engineToHandleCommandType); + } + + super.handle(command); + selectFiles(command.filesFilters); + } + + /** + * browse for files on disk + * @param filesFilter + * + */ + private function selectFiles(filesFilters:Array):void + { + // mail-8225 do not allow multiple sessions + if (_isActive) + return; + + var useMultiple:Boolean = _model.useMultipleSelect; + + if (useMultiple) + _fileRefObj = new FileReferenceList(); + else + _fileRefObj = new FileReference(); + + _fileRefObj.addEventListener( Event.SELECT, onFilesSelected) ; + _fileRefObj.addEventListener( Event.CANCEL, onFilesSelectionCanceled) ; + + try { + if (useMultiple) + (_fileRefObj as FileReferenceList).browse( filesFilters ) ; + else + (_fileRefObj as FileReference).browse( filesFilters ) ; + + jsCaller.notifyJSFilesEvents("browse"); + _isActive = true; + } + catch(error:Error) { + trace ("selectFiles error", error); + + // call js + jsCaller.notifyJSErrors( new ErrorVO(error.toString(), 'browseError') ); + + if ( error is IllegalOperationError) { + complete(false, _fileRefObj); + } + else if ( error is ArgumentError ) { + complete(false, _fileRefObj); + } + else if ( error is Error ) { + complete(false, _fileRefObj); + } + } + } + + private function onFilesSelected(event:Event):void { + complete( true, event.target as EventDispatcher); + } + + private function onFilesSelectionCanceled(event:Event):void { + complete( true, event.target as EventDispatcher, true); + } + + /** + * add files to model, notify js + * + * @param fileList + * + */ + private function processFiles(fileList:Array):void + { + var fileVO:FileVO; + var filesListForJS:Vector. = new Vector.(); + + for each (var fileRef:FileReference in fileList) + { + fileVO = _model.filesBuilder.createFileVO(fileRef); + + filesListForJS.push(fileVO); + } + + // notifyJS - collect all files in a vector and then send them all at once + jsCaller.notifyJSFilesEvents("select", filesListForJS); + } + + /** + * + * @param isSuccess + * @param fileRefrenceList + * @param isCanceled + * + */ + private function complete( isSuccess:Boolean, fileRefrenceObj:EventDispatcher, isCanceled:Boolean = false ):void { + //remove listeners + fileRefrenceObj.removeEventListener( Event.SELECT, onFilesSelected) ; + fileRefrenceObj.removeEventListener( Event.CANCEL, onFilesSelectionCanceled) ; + + _isActive = false; + + var fileList:Array; + + if (isCanceled) { + // notify about cancel + _jsCaller.notifyJSFilesEvents("cancel"); + } + else { + if (fileRefrenceObj is FileReferenceList) { + fileList = (fileRefrenceObj as FileReferenceList).fileList; + } + else { + fileList = [fileRefrenceObj]; + } + + processFiles(fileList); + } + + var event:CommandCompleteEvent = new CommandCompleteEvent(isSuccess); + dispatchEvent(event); + } + + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/engines/chain/presentation/MouseListenerEngine.as b/flash/src/ru/mail/engines/chain/presentation/MouseListenerEngine.as new file mode 100644 index 00000000..ec9c4301 --- /dev/null +++ b/flash/src/ru/mail/engines/chain/presentation/MouseListenerEngine.as @@ -0,0 +1,105 @@ +package ru.mail.engines.chain.presentation +{ + import flash.display.InteractiveObject; + import flash.events.MouseEvent; + + import ru.mail.communication.JSCaller; + import ru.mail.data.AttachmentsModel; + import ru.mail.engines.chain.AbstractEngine; + import ru.mail.engines.commands.AbstractEngineCommand; + import ru.mail.engines.commands.MouseListenerEngineCommand; + import ru.mail.engines.commands.SelectFilesCommand; + + public class MouseListenerEngine extends AbstractEngine + { + public static const TYPE:String = "MouseListenerEngine"; + + private var rollOutFlag:Boolean = false; + + protected var _view:InteractiveObject; + + /** + * we need link to the target object to subscribe to mouseEvents + * @param value + * + */ + public function get view():InteractiveObject + { + return _view; + } + + public function set view(value:InteractiveObject):void + { + if (_view != null) + { + // remove event listeners from old target + unsubscribeTarget(); + } + + // set new value + _view = value; + + try { + // add listeners to new target + subscribeTarget(); + } catch (e:Error) { + trace ("{MouseListenerEngine} - set target: cannot subscribe target", e.message); + } + } + + public function MouseListenerEngine() + { + super(TYPE); + } + + override public function handle(command:AbstractEngineCommand):void + { + super.handle(command); + + if (command is MouseListenerEngineCommand) + { + rollOutFlag = !(command as MouseListenerEngineCommand).isDispatchRollout; + } + } + + protected function subscribeTarget():void + { + _view.addEventListener(MouseEvent.ROLL_OVER, onMouseEvent); + _view.addEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent); + _view.addEventListener(MouseEvent.MOUSE_UP, onMouseEvent); + _view.addEventListener(MouseEvent.ROLL_OUT, onMouseEvent); + } + + protected function unsubscribeTarget():void + { + _view.removeEventListener(MouseEvent.ROLL_OVER, onMouseEvent); + _view.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent); + _view.removeEventListener(MouseEvent.MOUSE_UP, onMouseEvent); + _view.removeEventListener(MouseEvent.ROLL_OUT, onMouseEvent); + } + + protected function onMouseEvent(event:MouseEvent):void + { + // баг: открывается окно выбора файлов (browse() ) и от этого бросается эвент rollout, + // и от этого жс сворачивает флешку и больше ее не слушает + if (event.type == MouseEvent.ROLL_OUT && rollOutFlag) + { + // and + return; + // ha-ha! + } + // call js + JSCaller.jsCaller.notifyJSMouseEvents(event.type); + + if (event.type == MouseEvent.MOUSE_UP) + { + rollOutFlag = true; + + // get file filter from model, and dispatch select files event + var fileFilters:Array = AttachmentsModel.model.fileFilters; + dispatchEvent(new SelectFilesCommand(fileFilters)); + } + + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/engines/commands/MouseListenerEngineCommand.as b/flash/src/ru/mail/engines/commands/MouseListenerEngineCommand.as new file mode 100644 index 00000000..26ccb017 --- /dev/null +++ b/flash/src/ru/mail/engines/commands/MouseListenerEngineCommand.as @@ -0,0 +1,36 @@ +package ru.mail.engines.commands +{ + import flash.events.Event; + + import ru.mail.engines.chain.presentation.MouseListenerEngine; + + public class MouseListenerEngineCommand extends AbstractEngineCommand + { + public static const TYPE:String = "MouseListenerEngineCommand"; + + private var _dispatchRollout:Boolean; + /** + * true - dispatch js event mouseleave on rollout + * false - do not dispatch + * @return + * + */ + public function get isDispatchRollout():Boolean + { + return _dispatchRollout; + } + + + public function MouseListenerEngineCommand(dispatchRollout:Boolean) + { + super(TYPE, MouseListenerEngine.TYPE); + + _dispatchRollout = dispatchRollout; + } + + override public function clone():Event + { + return new MouseListenerEngineCommand(_dispatchRollout); + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/engines/commands/SelectFilesCommand.as b/flash/src/ru/mail/engines/commands/SelectFilesCommand.as new file mode 100644 index 00000000..8c494346 --- /dev/null +++ b/flash/src/ru/mail/engines/commands/SelectFilesCommand.as @@ -0,0 +1,42 @@ +package ru.mail.engines.commands +{ + import flash.events.Event; + + import ru.mail.engines.chain.manage.SelectFilesEngine; + + /** + * command to activate SelectFilesCommand engine + * + * store filesFilter to use in fileReference.browse + * + * @author s.osipov + * + */ + public class SelectFilesCommand extends AbstractEngineCommand + { + public static const TYPE:String = "SelectFilesCommand"; + + private var _filesFilters:Array = null; + /** + * file filter to use in fileReferenceList + * @return + * + */ + public function get filesFilters():Array + { + return _filesFilters; + } + + public function SelectFilesCommand(filters:Array) + { + super(TYPE, SelectFilesEngine.TYPE); + + _filesFilters = filters; + } + + override public function clone():Event + { + return new SelectFilesCommand(_filesFilters); + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/events/CompleteEvent.as b/flash/src/ru/mail/events/CompleteEvent.as new file mode 100644 index 00000000..5d644797 --- /dev/null +++ b/flash/src/ru/mail/events/CompleteEvent.as @@ -0,0 +1,44 @@ +package ru.mail.events +{ + import flash.events.Event; + + import ru.mail.data.vo.ErrorVO; + + /** + * the event that is triggered by Command on its executing completion. + * it provides access to execution result flag, that indicates whether command executed successfully + * @author ivanova + */ + public class CompleteEvent extends Event + { + public static const TYPE:String = "CompleteEvent" ; + + private var _isSuccess:Boolean; + public function get isSuccess():Boolean + { + return _isSuccess ; + } + + private var _error:ErrorVO + public function get error():ErrorVO + { + return _error; + } + + /** + * ctor + * @param isSuccess: indicates whether command executed successfully. + */ + public function CompleteEvent( isSuccess:Boolean, error:ErrorVO = null, type:String = null ) + { + super( type? type : TYPE ) ; + _isSuccess = isSuccess ; + _error = error; + } + + override public function clone():Event + { + return new CompleteEvent(_isSuccess, error); + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/events/DecodeBytesToBitmapCompleteEvent.as b/flash/src/ru/mail/events/DecodeBytesToBitmapCompleteEvent.as new file mode 100644 index 00000000..2e117e3b --- /dev/null +++ b/flash/src/ru/mail/events/DecodeBytesToBitmapCompleteEvent.as @@ -0,0 +1,35 @@ +package ru.mail.events +{ + import flash.display.Bitmap; + import flash.events.Event; + + import ru.mail.data.vo.ErrorVO; + + public class DecodeBytesToBitmapCompleteEvent extends CompleteEvent + { + public static const TYPE:String = "DecodeBytesToBitmapCompletedEvent"; + + private var _decodedBitmap:Bitmap; + /** + * transformed image data + * @return + */ + public function get decodedBitmap():Bitmap + { + return _decodedBitmap; + } + + + public function DecodeBytesToBitmapCompleteEvent(isSuccess:Boolean, decodedBitmap:Bitmap, error:ErrorVO = null) + { + super(isSuccess, error, TYPE); + + _decodedBitmap = decodedBitmap; + } + + override public function clone():Event + { + return new DecodeBytesToBitmapCompleteEvent(isSuccess, decodedBitmap, error); + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/events/ImageTransformCompleteEvent.as b/flash/src/ru/mail/events/ImageTransformCompleteEvent.as new file mode 100644 index 00000000..e5973348 --- /dev/null +++ b/flash/src/ru/mail/events/ImageTransformCompleteEvent.as @@ -0,0 +1,34 @@ +package ru.mail.events +{ + import flash.events.Event; + import flash.utils.ByteArray; + + import ru.mail.data.vo.ErrorVO; + + public class ImageTransformCompleteEvent extends CompleteEvent + { + public static const TYPE:String = "ImageTransformCompleteEvent"; + + private var _data:ByteArray; + /** + * transformed image data + * @return + */ + public function get data():ByteArray + { + return _data; + } + + public function ImageTransformCompleteEvent(isSuccess:Boolean, data:ByteArray, error:ErrorVO = null) + { + super(isSuccess, error, TYPE); + + _data = data; + } + + override public function clone():Event + { + return new ImageTransformCompleteEvent(isSuccess, data, error); + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/events/UploadCompleteEvent.as b/flash/src/ru/mail/events/UploadCompleteEvent.as new file mode 100644 index 00000000..e4b50e2d --- /dev/null +++ b/flash/src/ru/mail/events/UploadCompleteEvent.as @@ -0,0 +1,32 @@ +package ru.mail.events +{ + import flash.events.Event; + + import ru.mail.data.vo.ErrorVO; + + public class UploadCompleteEvent extends CompleteEvent + { + public static const TYPE:String = "UploadCompleteEvent"; + + private var _result:String; + /** + * Upload server responce + */ + public function get result():String + { + return _result; + } + + public function UploadCompleteEvent(isSuccess:Boolean, result:String, error:ErrorVO=null) + { + super(isSuccess, error, TYPE); + + _result = result; + } + + override public function clone():Event + { + return new UploadCompleteEvent(isSuccess, result, error); + } + } +} \ No newline at end of file diff --git a/flash/src/ru/mail/utils/BMPDecoder.as b/flash/src/ru/mail/utils/BMPDecoder.as new file mode 100644 index 00000000..208a816f --- /dev/null +++ b/flash/src/ru/mail/utils/BMPDecoder.as @@ -0,0 +1,564 @@ +package ru.mail.utils +{ + import flash.display.BitmapData; + import flash.errors.IOError; + import flash.utils.ByteArray; + import flash.utils.Endian; + public class BMPDecoder { + //___________________________________________________________ const + private const BITMAP_HEADER_TYPE:String = "BM"; + private const BITMAP_FILE_HEADER_SIZE:int = 14; + private const BITMAP_CORE_HEADER_SIZE:int = 12; + private const BITMAP_INFO_HEADER_SIZE:int = 40; + private const COMP_RGB :int = 0; + private const COMP_RLE8 :int = 1; + private const COMP_RLE4 :int = 2; + private const COMP_BITFIELDS:int = 3; + private const BIT1 :int = 1; + private const BIT4 :int = 4; + private const BIT8 :int = 8; + private const BIT16:int = 16; + private const BIT24:int = 24; + private const BIT32:int = 32; + //___________________________________________________________ vars + private var bytes:ByteArray; + private var palette:Array; + private var bd:BitmapData; + private var _decodeError:String; + private var nFileSize:uint; + private var nReserved1:uint; + private var nReserved2:uint; + private var nOffbits:uint; + private var nInfoSize:uint; + private var nWidth:int; + private var nHeight:int; + private var nPlains:uint; + private var nBitsPerPixel:uint; + private var nCompression:uint; + private var nSizeImage:uint; + private var nXPixPerMeter:int; + private var nYPixPerMeter:int; + private var nColorUsed:uint; + private var nColorImportant:uint; + private var nRMask:uint; + private var nGMask:uint; + private var nBMask:uint; + private var nRPos:uint; + private var nGPos:uint; + private var nBPos:uint; + private var nRMax:uint; + private var nGMax:uint; + private var nBMax:uint; + /** + * Constructor + */ + public function BMPDecoder() { + nRPos = 0; + nGPos = 0; + nBPos = 0; + } + /** + * decoder + * + * @param BMP file ByteArray + */ + public function decode( data:ByteArray ):BitmapData { + bytes = data; + bytes.endian = Endian.LITTLE_ENDIAN; + bytes.position = 0; + readFileHeader(); + nInfoSize = bytes.readUnsignedInt(); + switch ( nInfoSize ) { + case BITMAP_CORE_HEADER_SIZE: + readCoreHeader(); + break; + case BITMAP_INFO_HEADER_SIZE: + readInfoHeader(); + break; + default: + readExtendedInfoHeader(); + break; + } + try{ + bd = new BitmapData( nWidth, nHeight ); + }catch(e:Error){ + + } + switch ( nBitsPerPixel ){ + case BIT1: + readColorPalette(); + decode1BitBMP(); + break; + case BIT4: + readColorPalette(); + if ( nCompression == COMP_RLE4 ){ + decode4bitRLE(); + } else { + decode4BitBMP(); + } + break; + case BIT8: + readColorPalette(); + if ( nCompression == COMP_RLE8 ){ + decode8BitRLE(); + } else { + decode8BitBMP(); + } + break; + case BIT16: + readBitFields(); + checkColorMask(); + decode16BitBMP(); + break; + case BIT24: + decode24BitBMP(); + break; + case BIT32: + readBitFields(); + checkColorMask(); + decode32BitBMP(); + break; + default: + //throw new VerifyError("invalid bits per pixel : " + nBitsPerPixel ); + trace ("BMPDecoder invalid bits per pixel : " + nBitsPerPixel); + _decodeError = "BMPDecoder invalid bits per pixel : " + nBitsPerPixel; + break; + } + return bd; + } + /** + * read BITMAP FILE HEADER + */ + private function readFileHeader():void { + var fileHeader:ByteArray = new ByteArray(); + fileHeader.endian = Endian.LITTLE_ENDIAN; + try { + bytes.readBytes( fileHeader, 0, BITMAP_FILE_HEADER_SIZE ); + if ( fileHeader.readUTFBytes( 2 ) != BITMAP_HEADER_TYPE ){ + //throw new VerifyError("invalid bitmap header type"); + trace ("BMPDecoder invalid bitmap header type"); + } + nFileSize = fileHeader.readUnsignedInt(); + nReserved1 = fileHeader.readUnsignedShort(); + nReserved2 = fileHeader.readUnsignedShort(); + nOffbits = fileHeader.readUnsignedInt(); + } catch ( e:IOError ) { + //throw new VerifyError("invalid file header"); + trace ("BMPDecoder invalid file header"); + } + } + /** + * read BITMAP CORE HEADER + */ + private function readCoreHeader():void { + var coreHeader:ByteArray = new ByteArray(); + coreHeader.endian = Endian.LITTLE_ENDIAN; + try { + bytes.readBytes( coreHeader, 0, BITMAP_CORE_HEADER_SIZE - 4 ); + nWidth = coreHeader.readShort(); + nHeight = coreHeader.readShort(); + nPlains = coreHeader.readUnsignedShort(); + nBitsPerPixel = coreHeader.readUnsignedShort(); + } catch ( e:IOError ) { + //throw new VerifyError("invalid core header"); + trace ("BMPDecoder invalid core header"); + } + } + /** + * read BITMAP INFO HEADER + */ + private function readInfoHeader():void { + var infoHeader:ByteArray = new ByteArray(); + infoHeader.endian = Endian.LITTLE_ENDIAN; + try { + bytes.readBytes( infoHeader, 0, BITMAP_INFO_HEADER_SIZE - 4 ); + nWidth = infoHeader.readInt(); + nHeight = infoHeader.readInt(); + nPlains = infoHeader.readUnsignedShort(); + nBitsPerPixel = infoHeader.readUnsignedShort(); + nCompression = infoHeader.readUnsignedInt(); + nSizeImage = infoHeader.readUnsignedInt(); + nXPixPerMeter = infoHeader.readInt(); + nYPixPerMeter = infoHeader.readInt(); + nColorUsed = infoHeader.readUnsignedInt(); + nColorImportant = infoHeader.readUnsignedInt(); + } catch ( e:IOError ) { + //throw new VerifyError("invalid info header"); + trace ("BMPDecoder invalid info header"); + } + } + /** + * read the extend info of BITMAP INFO HEADER + */ + private function readExtendedInfoHeader():void { + var infoHeader:ByteArray = new ByteArray(); + infoHeader.endian = Endian.LITTLE_ENDIAN; + try { + bytes.readBytes( infoHeader, 0, nInfoSize - 4 ); + nWidth = infoHeader.readInt(); + nHeight = infoHeader.readInt(); + nPlains = infoHeader.readUnsignedShort(); + nBitsPerPixel = infoHeader.readUnsignedShort(); + nCompression = infoHeader.readUnsignedInt(); + nSizeImage = infoHeader.readUnsignedInt(); + nXPixPerMeter = infoHeader.readInt(); + nYPixPerMeter = infoHeader.readInt(); + nColorUsed = infoHeader.readUnsignedInt(); + nColorImportant = infoHeader.readUnsignedInt(); + if ( infoHeader.bytesAvailable >= 4 ) nRMask = infoHeader.readUnsignedInt(); + if ( infoHeader.bytesAvailable >= 4 ) nGMask = infoHeader.readUnsignedInt(); + if ( infoHeader.bytesAvailable >= 4 ) nBMask = infoHeader.readUnsignedInt(); + } catch ( e:IOError ) { + //throw new VerifyError("invalid info header"); + trace ("BMPDecoder invalid info header"); + } + } + /** + * read bitfields + */ + private function readBitFields():void { + if ( nCompression == COMP_RGB ){ + if ( nBitsPerPixel == BIT16 ){ + // RGB555 + nRMask = 0x00007c00; + nGMask = 0x000003e0; + nBMask = 0x0000001f; + } else { + //RGB888; + nRMask = 0x00ff0000; + nGMask = 0x0000ff00; + nBMask = 0x000000ff; + } + } else if ( ( nCompression == COMP_BITFIELDS ) && ( nInfoSize < 52 ) ){ + try { + nRMask = bytes.readUnsignedInt(); + nGMask = bytes.readUnsignedInt(); + nBMask = bytes.readUnsignedInt(); + } catch ( e:IOError ) { + //throw new VerifyError("invalid bit fields"); + trace ("BMPDecoder invalid bit fields"); + _decodeError = "BMPDecoder invalid bit fields"; + } + } + } + /** + * read color palette + */ + private function readColorPalette():void { + var i:int; + var len:int = ( nColorUsed > 0 ) ? nColorUsed : Math.pow( 2, nBitsPerPixel ); + palette = new Array( len ); + for ( i = 0; i < len; ++i ){ + palette[ i ] = bytes.readUnsignedInt(); + } + } + /** + * decode 1 bit BMP + */ + private function decode1BitBMP():void { + var x:int; + var y:int; + var i:int; + var col:int; + var buf:ByteArray = new ByteArray(); + var line:int = nWidth / 8; + if ( line % 4 > 0 ){ + line = ( ( line / 4 | 0 ) + 1 ) * 4; + } + try { + for ( y = nHeight - 1; y >= 0; --y ){ + buf.length = 0; + bytes.readBytes( buf, 0, line ); + for ( x = 0; x < nWidth; x += 8 ){ + col = buf.readUnsignedByte(); + for ( i = 0; i < 8; ++i ){ + bd.setPixel( x + i, y, palette[ col >> ( 7 - i ) & 0x01 ] ); + } + } + } + } catch ( e:IOError ) { + //throw new VerifyError("invalid image data"); + trace ("BMPDecoder invalid image data"); + _decodeError = "BMPDecoder invalid image data"; + } + } + /** + * decode 4bit RLE + */ + private function decode4bitRLE():void { + var x:int; + var y:int; + var i:int; + var n:int; + var col:int; + var data:uint; + var buf:ByteArray = new ByteArray(); + try { + for ( y = nHeight - 1; y >= 0; --y ){ + buf.length = 0; + while ( bytes.bytesAvailable > 0 ){ + n = bytes.readUnsignedByte(); + if ( n > 0 ){ + // encode data + data = bytes.readUnsignedByte(); + for ( i = 0; i < n/2; ++i ){ + buf.writeByte( data ); + } + } else { + n = bytes.readUnsignedByte(); + if ( n > 0 ){ + // abs mode + bytes.readBytes( buf, buf.length, n/2 ); + buf.position += n/2; + if ( n/2 + 1 >> 1 << 1 != n/2 ){ + bytes.readUnsignedByte(); + } + } else { + // EOL + break; + } + } + } + buf.position = 0; + for ( x = 0; x < nWidth; x += 2 ){ + col = buf.readUnsignedByte(); + bd.setPixel( x, y, palette[ col >> 4 ] ); + bd.setPixel( x + 1, y, palette[ col & 0x0f ] ); + } + } + } catch ( e:IOError ) { + //throw new VerifyError("invalid image data"); + trace ("BMPDecoder invalid image data"); + _decodeError = "BMPDecoder invalid image data"; + } + } + /** + * decode 4bit (no Compression) + */ + private function decode4BitBMP():void { + var x:int; + var y:int; + var i:int; + var col:int; + var buf:ByteArray = new ByteArray(); + var line:int = nWidth / 2; + if ( line % 4 > 0 ){ + line = ( ( line / 4 | 0 ) + 1 ) * 4; + } + try { + for ( y = nHeight - 1; y >= 0; --y ){ + buf.length = 0; + bytes.readBytes( buf, 0, line ); + for ( x = 0; x < nWidth; x += 2 ){ + col = buf.readUnsignedByte(); + bd.setPixel( x, y, palette[ col >> 4 ] ); + bd.setPixel( x + 1, y, palette[ col & 0x0f ] ); + } + } + } catch ( e:IOError ) { + //throw new VerifyError("invalid image data"); + trace ("BMPDecoder invalid image data"); + _decodeError = "BMPDecoder invalid image data"; + } + } + /** + * decode 8bit( RLE Compression ) + */ + private function decode8BitRLE():void { + var x:int; + var y:int; + var i:int; + var n:int; + var col:int; + var data:uint; + var buf:ByteArray = new ByteArray(); + try { + for ( y = nHeight - 1; y >= 0; --y ){ + buf.length = 0; + while ( bytes.bytesAvailable > 0 ){ + n = bytes.readUnsignedByte(); + if ( n > 0 ){ + // encode data + data = bytes.readUnsignedByte(); + for ( i = 0; i < n; ++i ){ + buf.writeByte( data ); + } + } else { + n = bytes.readUnsignedByte(); + if ( n > 0 ){ + // abs mode data + bytes.readBytes( buf, buf.length, n ); + buf.position += n; + if ( n + 1 >> 1 << 1 != n ){ + bytes.readUnsignedByte(); + } + } else { + // EOL + break; + } + } + } + buf.position = 0; + for ( x = 0; x < nWidth; ++x ){ + bd.setPixel( x, y, palette[ buf.readUnsignedByte() ] ); + } + } + } catch ( e:IOError ) { + //throw new VerifyError("invalid image data"); + trace ("BMPDecoder invalid image data"); + _decodeError = "BMPDecoder invalid image data"; + } + } + /** + * decode 8bit(no Compression) + */ + private function decode8BitBMP():void { + var x:int; + var y:int; + var i:int; + var col:int; + var buf:ByteArray = new ByteArray(); + var line:int = nWidth; + if ( line % 4 > 0 ){ + line = ( ( line / 4 | 0 ) + 1 ) * 4; + } + try { + for ( y = nHeight - 1; y >= 0; --y ){ + buf.length = 0; + bytes.readBytes( buf, 0, line ); + for ( x = 0; x < nWidth; ++x ){ + bd.setPixel( x, y, palette[ buf.readUnsignedByte() ] ); + } + } + } catch ( e:IOError ) { + //throw new VerifyError("invalid image data"); + trace ("BMPDecoder invalid image data"); + _decodeError = "BMPDecoder invalid image data"; + } + } + /** + * decode 16bit + */ + private function decode16BitBMP():void { + var x:int; + var y:int; + var col:int; + try { + for ( y = nHeight - 1; y >= 0; --y ){ + for ( x = 0; x < nWidth; ++x ){ + col = bytes.readUnsignedShort(); + bd.setPixel( x, y, ( ( ( col & nRMask ) >> nRPos )*0xff/nRMax << 16 ) + ( ( ( col & nGMask ) >> nGPos )*0xff/nGMax << 8 ) + ( ( ( col & nBMask ) >> nBPos )*0xff/nBMax << 0 ) ); + } + } + } catch ( e:IOError ) { + //throw new VerifyError("invalid image data"); + trace ("BMPDecoder invalid image data"); + _decodeError = "BMPDecoder invalid image data"; + } + } + /** + * decode 24bit BMP + */ + private function decode24BitBMP():void { + var x:int; + var y:int; + var col:int; + var buf:ByteArray = new ByteArray(); + var line:int = nWidth * 3; + if ( line % 4 > 0 ){ + line = ( ( line / 4 | 0 ) + 1 ) * 4; + } + try { + for ( y = nHeight - 1; y >= 0; --y ){ + buf.length = 0; + bytes.readBytes( buf, 0, line ); + for ( x = 0; x < nWidth; ++x ){ + bd.setPixel( x, y, buf.readUnsignedByte() + ( buf.readUnsignedByte() << 8 ) + ( buf.readUnsignedByte() << 16 ) ); + } + } + } catch ( e:IOError ) { + //throw new VerifyError("invalid image data"); + trace ("BMPDecoder invalid image data"); + _decodeError = "BMPDecoder invalid image data"; + } + } + /** + * decode 32bit BMP + */ + private function decode32BitBMP():void { + var x:int; + var y:int; + var col:int; + try { + for ( y = nHeight - 1; y >= 0; --y ){ + for ( x = 0; x < nWidth; ++x ){ + col = bytes.readUnsignedInt(); + bd.setPixel( x, y, ( ( ( col & nRMask ) >> nRPos )*0xff/nRMax << 16 ) + ( ( ( col & nGMask ) >> nGPos )*0xff/nGMax << 8 ) + ( ( ( col & nBMask ) >> nBPos )*0xff/nBMax << 0 ) ); + } + } + } catch ( e:IOError ) { + //throw new VerifyError("invalid image data"); + trace ("BMPDecoder invalid image data"); + _decodeError = "BMPDecoder invalid image data"; + } + } + /** + * check color mask + */ + private function checkColorMask():void { + if ( ( nRMask & nGMask ) | ( nGMask & nBMask ) | ( nBMask & nRMask ) ){ + //throw new VerifyError("invalid bit fields"); + trace ("BMPDecoder invalid bit fields"); + } + while ( ( ( nRMask >> nRPos ) & 0x00000001 ) == 0 ){ + nRPos++; + } + while ( ( ( nGMask >> nGPos ) & 0x00000001 ) == 0 ){ + nGPos++; + } + while ( ( ( nBMask >> nBPos ) & 0x00000001 ) == 0 ){ + nBPos++; + } + nRMax = nRMask >> nRPos; + nGMax = nGMask >> nGPos; + nBMax = nBMask >> nBPos; + } + /** + * print information + */ + public function traceInfo():void { + trace("---- FILE HEADER ----"); + trace("nFileSize: " + nFileSize ); + trace("nReserved1: " + nReserved1 ); + trace("nReserved2: " + nReserved2 ); + trace("nOffbits: " + nOffbits ); + trace("---- INFO HEADER ----"); + trace("nWidth: " + nWidth ); + trace("nHeight: " + nHeight ); + trace("nPlains: " + nPlains ); + trace("nBitsPerPixel: " + nBitsPerPixel ); + if ( nInfoSize >= 40 ){ + trace("nCompression: " + nCompression ); + trace("nSizeImage: " + nSizeImage ); + trace("nXPixPerMeter: " + nXPixPerMeter ); + trace("nYPixPerMeter: " + nYPixPerMeter ); + trace("nColorUsed: " + nColorUsed ); + trace("nColorUsed: " + nColorImportant ); + } + if ( nInfoSize >= 52 ){ + trace("nRMask: " + nRMask.toString( 2 ) ); + trace("nGMask: " + nGMask.toString( 2 ) ); + trace("nBMask: " + nBMask.toString( 2 ) ); + } + } + + /** + * report if there were some errors while decoding + * @return + * + */ + public function get decodeError():String { + return _decodeError; + } + } + } \ No newline at end of file diff --git a/flash/src/ru/mail/utils/ExifReader2.as b/flash/src/ru/mail/utils/ExifReader2.as new file mode 100644 index 00000000..a343b732 --- /dev/null +++ b/flash/src/ru/mail/utils/ExifReader2.as @@ -0,0 +1,158 @@ +package ru.mail.utils +{ + + import flash.utils.ByteArray; + + /** + * Get data from exif. + * Only orientation tag + * If need something else, add tags. + * + */ + public class ExifReader2 + { + private var m_data:ByteArray = new ByteArray(); + private var m_exif:Object = new Object; + private var m_exifKeys:Array = new Array(); + + private var m_intel:Boolean=true; + private var m_loc:uint=0; + + private var DATASIZES:Object = new Object; + private var TAGS:Object = new Object; + + public function getKeys():Array{ + return m_exifKeys; + } + public function hasKey(key:String):Boolean{ + return m_exif[key] != undefined; + } + public function getValue(key:String):Object{ + if(m_exif[key] == undefined) return null; + return m_exif[key]; + } + + public function ExifReader2(){ + DATASIZES[1] = 1; + DATASIZES[2] = 1; + DATASIZES[3] = 2; + DATASIZES[4] = 4; + DATASIZES[5] = 8; + DATASIZES[6] = 1; + DATASIZES[7] = 1; + DATASIZES[8] = 2; + DATASIZES[9] = 4; + DATASIZES[10] = 8; + DATASIZES[11] = 4; + DATASIZES[12] = 8; + + TAGS[0x0112] = 'Orientation'; + + //... add more if you like. + //See http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html + } + + + public function processData( data:ByteArray ):Boolean { + + m_data = data ; + m_data.position = 0 ; + + var iter:int=0; + + //confirm JPG type + if(!(m_data.readUnsignedByte()==0xff && m_data.readUnsignedByte()==0xd8)) + return false ; + + //Locate APP1 MARKER + var ff:uint=0; + var marker:uint=0; + for(iter=0;iter<5;++iter){ //cap iterations + ff = m_data.readUnsignedByte(); + marker = m_data.readUnsignedByte(); + var size:uint = (m_data.readUnsignedByte()<<8) + m_data.readUnsignedByte(); + if(marker == 0x00e1) break; + else{ + for(var x:int=0;x100) numEntries=100; //cap entries + + m_loc+=2; + for(iter=0; iterU@XsEEe#Y}sxA4KhARhSd1dMoAIbmRk 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 Q9}Wx+6 $images + , 'data' => print_r(array( + '_REQUEST' => $_REQUEST + , '_FILES' => FileAPI::getFiles() + ), true) + )); + + if( empty($jsonp) ){ + echo $json; + } + else { + echo '' + ; + } + exit; + } + + + + + + function fetchImages($files, &$images, $name = 'file'){ + if( isset($files['tmp_name']) ){ + $filename = $files['tmp_name']; + list($mime) = explode(';', @mime_content_type($filename)); + + if( strpos($mime, 'image') !== false ){ + $content = file_get_contents($filename); + $images[$name] = 'data:'. $mime .';base64,'. base64_encode($content); + } + } + else { + foreach( $files as $name => $file ){ + fetchImages($file, $images, $name); + } + } + } +?> + + + + + + FileAPI :: TEST + + + + + + + + + + + + + + + + + + +

+ +
+
Upload one file
+ +
+ + , + +
+
Multiple
+ +
+ + or + +
+
jpg, jpeg & gif
+ +
+ +
+ + + + +
+
+ +
+ + + + + diff --git a/lib/FileAPI.Flash.js b/lib/FileAPI.Flash.js new file mode 100644 index 00000000..b0da2ea6 --- /dev/null +++ b/lib/FileAPI.Flash.js @@ -0,0 +1,610 @@ +/** + * FileAPI fallback to Flash + * + * @flash-developer "Vladimer Demidov" + */ +(function (api, window, document){ + api.support.flash = (function (){ + var nav = window.navigator, mime = nav.mimeTypes, has = false; + + if( nav.plugins && typeof nav.plugins['Shockwave Flash'] == 'object' ){ + has = nav.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin); + } + else { + try { + has = !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash')); + } + catch(er){ /*__*/ } + } + + return has; + })(); + + + if( 1 && api.support.flash && (0 || !api.support.html5 || api.cors && !api.support.cors) ) (function (){ + var + _attr = api.uid() + , _retry = 0 + , _files = {} + , _rhttp = /^https?:/i + + , flash = { + _fn: {}, + + + /** + * Initialization & preload flash object + */ + init: function (){ + var child = document.body && document.body.firstChild; + + if( child ){ + do { + if( child.nodeType == 1 ){ + api.log('FlashAPI.Flash.inited'); + + var dummy = document.createElement('div'); + + _css(dummy, { + top: 1 + , right: 1 + , width: 5 + , height: 5 + , position: 'absolute' + }); + + child.parentNode.insertBefore(dummy, child); + flash.publish(dummy, _attr); + + return; + } + } + while( child = child.nextSibling ) + } + + if( _retry < 10 ){ + setTimeout(flash.init, ++_retry*50); + } + }, + + + /** + * Publish flash-object + * + * @param {HTMLElement} el + * @param {String} id + */ + publish: function (el, id){ + el.innerHTML = _makeFlashHTML({ + id: id + , src: _getUrl(api.staticPath + 'FileAPI.flash.swf?r=' + api.build) +// , src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1') + , wmode: 'transparent' + , flashvars: 'callback=FileAPI.Flash.event' + + '&flashId='+ id + + '&storeKey='+ navigator.userAgent.match(/\d/ig).join('') +'_'+ api.build + + (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : '')) + }); + }, + + + ready: function (){ + flash.ready = api.F; + flash.isReady = true; + flash.patch(); + + api.event.on(document, 'mouseover', flash.mouseover); + api.event.on(document, 'click', function (evt){ + if( flash.mouseover(evt) ){ + evt.preventDefault + ? evt.preventDefault() + : (evt.returnValue = true) + ; + } + }); + }, + + getWrapper: function (node){ + do { + if( /js-fileapi-wrapper/.test(node.className) ){ + return node; + } + } + while( (node = node.parentNode) && (node !== document.body) ); + }, + + mouseover: function (evt){ + var target = api.event.fix(evt).target; + + if( /input/i.test(target.nodeName) && target.type == 'file' ){ + var state = target.getAttribute(_attr); + + // check state + if( state == 'i' || state == 'r' ){ + // publish fail + return false; + } + else if( state != 'p' ){ + // set "init" state + target.setAttribute(_attr, 'i'); + + var + dummy = document.createElement('div') + , wrapper = flash.getWrapper(target) + ; + + if( !wrapper ){ + api.log('flash.mouseover.error: js-fileapi-wrapper not found'); + return + } + + _css(dummy, { + top: 0 + , left: 0 + , width: target.offsetWidth + 100 + , height: target.offsetHeight + 100 + , zIndex: 1e6+'' // set max zIndex + , position: 'absolute' + }); + + + wrapper.appendChild(dummy); + flash.publish(dummy, api.uid()); + + // set "publish" state + target.setAttribute(_attr, 'p'); + } + + return true; + } + }, + + event: function (evt){ + var type = evt.type; + + if( type == 'ready' ){ + try { + // set "ready" state + flash.getInput(evt.flashId).setAttribute(_attr, 'r'); + } catch (e){} + + flash.ready(); + setTimeout(function (){ flash.mouseenter(evt); }, 50); + return true; + } + else if( type === 'ping' ){ + api.log('(flash -> js).ping:', [evt.status, evt.savedStatus], evt.error); + } + else if( type === 'log' ){ + api.log('(flash -> js).log:', evt.target); + } + else if( type in flash ) setTimeout(function (){ + api.log('Flash.event.'+evt.type+':', evt); + flash[type](evt); + }, 1); + }, + + mouseenter: function (evt){ + var node = flash.getInput(evt.flashId); + if( node ){ + // Set multiple mode + flash.cmd(evt, 'multiple', node.getAttribute('multiple') !== null); + + // Set files filter + flash.cmd(evt, 'accept', (node.getAttribute('accept') || '*').replace(/\./g, '')); + } + }, + + get: function (id){ + return document[id] || window[id] || document.embeds[id]; + }, + + getInput: function (id){ + try { + var node = flash.getWrapper(flash.get(id)); + if( node ) return node.getElementsByTagName('input')[0]; + } catch (e){ } + }, + + select: function (evt){ + var + inp = flash.getInput(evt.flashId) + , uid = api.uid(inp) + , files = evt.target.files + , event + ; + + api.each(files, function (file){ + api.checkFileObj(file); + }); + + _files[uid] = files; + + if( document.createEvent ){ + event = document.createEvent('Event'); + event.initEvent ('change', true, false); + inp.dispatchEvent(event) + } + else if( document.createEventObject ){ + event = document.createEventObject(); + inp.fireEvent('onchange', event) + } + }, + + + cmd: function (id, name, data, last){ + try { + api.log('(js -> flash).'+name+':', data); + return flash.get(id.flashId || id).cmd(name, data); + } catch (e){ + api.log('(js -> flash).onError:', e); + if( !last ){ + // try again + setTimeout(function (){ flash.cmd(id, name, data, true); }, 50); + } + } + }, + + + patch: function (){ + api.flashEngine = + api.support.transform = true; + + // FileAPI + _inherit(api, { + getFiles: function (input, filter, callback){ + if( callback ){ + api.filterFiles(api.getFiles(input), filter, callback); + return null; + } + + var files = api.isArray(input) ? input : _files[api.uid(input.target || input.srcElement || input)]; + + + if( !files ){ + // Файлов нету, вызываем родительский метод + return this.parent.apply(this, arguments); + } + + + if( filter ){ + filter = api.getFilesFilter(filter); + files = api.filter(files, function (file){ return filter.test(file.name); }); + } + + return files; + }, + + + getInfo: function (file, fn){ + if( _isHtmlFile(file) ){ + this.parent.apply(this, arguments); + } + else { + if( !file.__info ){ + var defer = file.__info = FileAPI.defer(); + + flash.cmd(file, 'getFileInfo', { + id: file.id + , callback: _wrap(function _(err, info){ + _unwrap(_); + defer.resolve(err, file.info = info) + }) + }); + } + file.__info.then(fn); + } + } + }); + + + // FileAPI.Image + api.support.transform = true; + _inherit(FileAPI.Image.prototype, { + get: function (fn, scaleMode){ + this.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit + this.parent(fn); + }, + + _load: function (file, fn){ + api.log('FileAPI.Image._load:', file); + + if( _isHtmlFile(file) ){ + this.parent.apply(this, arguments); + } + else { + var _this = this; + api.getInfo(file, function (err, info){ + fn.call(_this, err, file); + }); + } + }, + + _apply: function (file, fn){ + api.log('FileAPI.Image._apply:', file); + + if( _isHtmlFile(file) ){ + this.parent.apply(this, arguments); + } + else { + var m = this.getMatrix(file.info); + + flash.cmd(file, 'imageTransform', { + id: file.id + , matrix: m + , callback: _wrap(function _(err, base64){ + api.log('FileAPI.Image._apply.callback:', err); + _unwrap(_); + + if( err ){ + fn(err); + } + else if( !api.support.dataURI || base64.length > 3e4 ){ + _makeFlashImage({ + width: !(m.deg % 180) ? m.dw : m.dh + , height: (m.deg % 180) ? m.dw : m.dh + , scale: m.scaleMode + }, base64, fn); + } + else { + var img = new Image; + api.event.one(img, 'error abort load', function (evt){ + fn(evt.type != 'load' && evt.type, img); + img = null; + }); + img.src = 'data:'+ file.type +';base64,'+ base64; + } + }) + }); + } + }, + + toData: function (fn){ + var file = this.file; + + if( _isHtmlFile(file) ){ + this.parent.apply(this, arguments); + } + else { + fn.call(this, !file.info, { + id: file.id + , flashId: file.flashId + , name: file.name + , type: file.type + , matrix: this.getMatrix(file.info) + }); + } + } + }); + + + // FileAPI.Form + _inherit(api.Form.prototype, { + toData: function (fn){ + var items = this.items, i = items.length; + + for( ; i--; ){ + if( items[i].file && _isHtmlFile(items[i].blob) ){ + return this.parent.apply(this, arguments); + } + } + + api.log('flash.Form.toData'); + fn(items); + } + }); + + + // FileAPI.XHR + _inherit(api.XHR.prototype, { + _send: function (options, formData){ + if( + formData.nodeName + || formData.append && api.support.html5 + || api.isArray(formData) && (typeof formData[0] === 'string') + ){ + // HTML5, Multipart or IFrame + return this.parent.apply(this, arguments); + } + + + var + data = {} + , files = {} + , _this = this + , flashId + , fileId + ; + + api.each(formData, function (item){ + if( item.file ){ + files[item.name] = item = item.blob; + fileId = item.id; + flashId = item.flashId; + } + else { + data[item.name] = item.blob; + } + }); + + api.log('flash.XHR._send:', flashId, fileId, files); + + _this.xhr = { + headers: {}, + abort: function (){ flash.cmd(flashId, 'abort', fileId); }, + getResponseHeader: function (name){ return this.headers[name]; }, + getAllResponseHeaders: function (){ return this.headers; } + }; + + + var queue = api.queue(function (){ + flash.cmd(flashId, 'upload', { + url: _getUrl(options.url) + , data: data + , files: files + , headers: options.headers + , callback: _wrap(function upload(evt){ + var type = evt.type, result = evt.result; + + api.log('flash.upload.'+type+':', evt); + + if( type == 'progress' ){ + evt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme + evt.lengthComputable = true; + options.progress(evt); + } + else if( type == 'complete' ){ + _unwrap(upload); + + if( typeof result == 'string' ){ + _this.responseText = result.replace(/%22/g, "\"").replace(/%5c/g, "\\").replace(/%26/g, "&").replace(/%25/g, "%"); + } + + _this.end(evt.status || 200); + } + else if( type == 'abort' || type == 'error' ){ + _this.end(0, evt.message); + _unwrap(upload); + } + }) + }); + }); + + + // #2174: FileReference.load() call while FileReference.upload() or vice versa + api.each(files, function (file){ + queue.inc(); + api.getInfo(file, queue.next); + }); + + queue.check(); + } + }); + } + } + ; + + + function _makeFlashHTML(opts){ + return ('' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '').replace(/#(\w+)#/ig, function (a, name){ return opts[name]; }) + ; + } + + + function _css(el, css){ + if( el && el.style ){ + var key, val; + for( key in css ){ + val = css[key]; + if( typeof val == 'number' ) val += 'px'; + try { el.style[key] = val; } catch (e) {} + } + } + } + + + function _inherit(obj, methods){ + api.each(methods, function (fn, name){ + var prev = obj[name]; + obj[name] = function (){ + this.parent = prev; + return fn.apply(this, arguments); + }; + }); + } + + function _isHtmlFile(file){ + return file && !file.flashId; + } + + function _wrap(fn){ + var id = fn.wid = api.uid(); + flash._fn[id] = fn; + return 'FileAPI.Flash._fn.'+id; + } + + + function _unwrap(fn){ + try { + flash._fn[fn.wid] = null; + delete flash._fn[fn.wid]; + } + catch(e){} + } + + + function _getUrl(url){ + if( !_rhttp.test(url) ){ + if( /^\.\//.test(url) || '/' != url.charAt(0) ){ + var path = location.pathname; + path = path.substr(0, path.lastIndexOf('/')); + url = path +'/'+ url.substr(RegExp.$1 == './' ? 2 : 0); + } + + if( '//' != url.substr(0, 2) ){ + url = location.hostname +'/'+ url; + } + + if( !_rhttp.test(url) ){ + url = location.protocol +'//'+ url; + } + } + + return url; + } + + + function _makeFlashImage(opts, base64, fn){ + var _id, flashId = api.uid(), el = document.createElement('div'); + + for( _id in opts ){ + el.setAttribute('data-img-' + _id, opts[_id]); + } + + _css(el, opts); + api.log('flash.image.publish'); + + el.innerHTML = _makeFlashHTML(ajs.extend({ + id: flashId + , src: api.staticPath +'FileAPI.flash.image.swf?r='+ api.uid() + , wmode: 'opaque' + , flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _setData(){ + _unwrap(_setData); + setTimeout(_setImage, 99); + return true; + }) + }, opts)); + + function _setImage(){ + try { + var img = flash.get(flashId); + img.setImage(base64); + } catch (e){} + } + + fn(false, el); + el = null; + } + + // @export + api.Flash = flash; + + // Check dataURI support + var dataURICheck = new Image; + api.event.one(dataURICheck, 'error load', function (){ + api.support.dataURI = !(dataURICheck.width != 1 || dataURICheck.height != 1); + dataURICheck = null; + flash.init(); + }); + dataURICheck.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='; + })(); +})(FileAPI, window, document); diff --git a/lib/FileAPI.Form.js b/lib/FileAPI.Form.js new file mode 100644 index 00000000..0b37d4b1 --- /dev/null +++ b/lib/FileAPI.Form.js @@ -0,0 +1,117 @@ +(function (api, window, document){ + var + encode = window.encodeURIComponent, + FormData = window.FormData, + Form = function (){ + this.items = []; + } + ; + + + Form.prototype = { + + append: function (name, blob, file, type){ + this.items.push({ + name: name + , blob: blob && blob.blob || blob + , file: file || blob.name + , type: type || blob.type + }); + }, + + each: function (fn){ + var i = 0, n = this.items.length; + for( ; i < n; i++ ){ + fn.call(this, this.items[i]); + } + }, + + toData: function (fn){ + if( !api.support.html5 ){ + api.log('tFileAPI.Form.toHtmlData'); + this.toHtmlData(fn); + } + else if( this.multipart ){ + api.log('FileAPI.Form.toMultipartData'); + this.toMultipartData(fn); + } + else { + api.log('FileAPI.Form.toFormData'); + this.toFormData(fn); + } + }, + + _to: function (data, complete, next, arg){ + var queue = api.queue(function (){ + complete(data); + }); + + this.each(function (file){ + next(file, data, queue, arg); + }); + + queue.check(); + }, + + + toHtmlData: function (fn){ + this._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){ + var blob = file.blob, hidden; + + if( file.file ){ + api.reset(blob); + // set new name + blob.name = file.name; + data.appendChild(blob); + } + else { + hidden = document.createElement('input'); + hidden.name = file.name; + hidden.type = 'hidden'; + hidden.value = blob; + data.appendChild(hidden); + } + }); + }, + + + toFormData: function (fn){ + this._to(new FormData, fn, function (file, data, queue){ + if( file.file ){ + data.append('_'+file.name, file.file); + } + + if( file.blob.toBlob ){ + queue.inc(); + file.blob.toBlob(function (blob){ + data.append(file.name, blob, file.file); + queue.next(); + }, 'image/png'); + } + else if( file.file ){ + data.append(file.name, file.blob, file.file); + } + else { + data.append(file.name, file.blob); + } + }); + }, + + + toMultipartData: function (fn){ + this._to([], fn, function (file, data, queue, boundary){ + data.push( + '--_' + boundary + ('\r\nContent-Disposition: form-data; name="'+ file.name +'"'+ (file.file ? '; filename="'+ encode(file.file) +'"' : '') + + (file.file ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '') + + '\r\n' + + '\r\n'+ (file.file ? file.blob : encode(file.blob)) + + '\r\n') + ); + }, api.expando); + } + }; + + + // @export + api.Form = Form; +})(FileAPI, window, document); diff --git a/lib/FileAPI.Image.js b/lib/FileAPI.Image.js new file mode 100644 index 00000000..25963e09 --- /dev/null +++ b/lib/FileAPI.Image.js @@ -0,0 +1,280 @@ +(function (api, document, undef){ + 'use strict'; + + var + min = Math.min, + round = Math.round, + getCanvas = function (){ return document.createElement('canvas'); }, + support = false, + exitOrientation = { + 8: 270 + , 3: 180 + , 6: 90 + } + ; + + try { + support = getCanvas().toDataURL('image/png').indexOf('data:image/png') > -1; + } + catch (e){} + + + function Image(file, low){ + if( !(this instanceof Image) ){ + return new Image(file); + } + + this.file = file; + this.better = !low; + this.matrix = { + sx: 0, + sy: 0, + sw: 0, + sh: 0, + dx: 0, + dy: 0, + dw: 0, + dh: 0, + resize: 0, // min, max OR preview + deg: 0 + }; + } + Image.prototype = { + constructor: Image, + + set: function (attrs){ + api.extend(this.matrix, attrs); + return this; + }, + + crop: function (x, y, w, h){ + if( w === undef ){ + w = x; + h = y; + x = y = 0; + } + return this.set({ sx: x, sy: y, sw: w, sh: h || w }); + }, + + resize: function (w, h, type){ + if( typeof h == 'string' ){ + type = h; + h = w; + } + + return this.set({ dw: w, dh: h, resize: type }); + }, + + preview: function (w, h){ + return this.set({ dw: w, dh: h || w, resize: 'preview' }); + }, + + rotate: function (deg){ + return this.set({ deg: deg }); + }, + + _load: function (image, fn){ + var self = this; + api.readAsImage(image, function (evt){ + fn.call(self, evt.type != 'load', evt.result); + }); + }, + + _apply: function (image, fn){ + var + canvas = getCanvas() + , m = this.getMatrix(image) + , ctx = canvas.getContext('2d') + , deg = m.deg + , dw = m.dw + , dh = m.dh + , w = image.width + , h = image.height + , copy, buffer = image + ; + + + if( this.better ){ + while( Math.min(w/dw, h/dh) > 2 ){ + w = ~~(w/2 + .5); + h = ~~(h/2 + .5); + + copy = getCanvas(); + copy.width = w; + copy.height = h; + + if( buffer !== image ){ + copy.getContext('2d').drawImage(buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h); + buffer = copy; + } + else { + buffer = copy; + buffer.getContext('2d').drawImage(image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h); + m.sx = m.sy = m.sw = m.sh = 0; + } + } + } + + canvas.width = !(deg % 180) ? dw : dh; + canvas.height = (deg % 180) ? dw : dh; + + ctx.rotate(deg * Math.PI / 180); + ctx.drawImage(buffer + , m.sx, m.sy + , m.sw || buffer.width + , m.sh || buffer.height + , (deg == 180 || deg == 270 ? -dw : 0) + , (deg == 90 || deg == 180 ? -dh : 0) + , dw, dh + ); + + fn.call(this, false, canvas); + }, + + getMatrix: function (image){ + var + m = api.extend({}, this.matrix) + , sw = m.sw = m.sw || image.width + , sh = m.sh = m.sh || image.height + , dw = m.dw = m.dw || m.sw + , dh = m.dh = m.dh || m.sh + , sf = sw/sh, df = dw/dh + , type = m.resize + ; + + if( type == 'preview' ){ + if( dw != sw || dh != sh ){ + // Make preview + var w, h; + + if( df >= sf ){ + w = sw; + h = w / df; + } else { + h = sh; + w = h * df; + } + + if( w != sw || h != sh ){ + m.sx = ~~((sw - w)/2); + m.sy = ~~((sh - h)/2); + sw = w; + sh = h; + } + } + } + else if( type ){ + if( type == 'min' ){ + dw = round(sf < df ? min(sw, dw) : dh*sf); + dh = round(sf < df ? dw/sf : min(sh, dh)); + } + else { + dw = round(sf >= df ? min(sw, dw) : dh*sf); + dh = round(sf >= df ? dw/sf : min(sh, dh)); + } + } + + m.sw = sw; + m.sh = sh; + m.dw = dw; + m.dh = dh; + + return m; + }, + + _trans: function (fn){ + this._load(this.file, function (err, image){ + if( err ){ + fn(err); + } + else { + this._apply(image, fn); + } + }); + }, + + get: function (fn){ + if( api.support.transform ){ + var _this = this; + + if( _this.matrix.deg == 'auto' ){ + api.getInfo(this.file, function (err, info){ + // rotate by exif orientation + _this.matrix.deg = exitOrientation[info && info.exif && info.exif.Orientation] || 0; + _this._trans(fn); + }); + } + else { + _this._trans(fn); + } + } + else { + fn('not_support'); + } + }, + + toData: function (fn){ + this.get(fn); + } + + }; + + + Image.transform = function (file, transform, autoOrientation, fn){ + api.getInfo(file, function (err, img){ + // img -- info object + + var + images = {} + , queue = api.queue(function (err){ + fn(err, images); + }) + ; + + if( !err ){ + api.each(transform, function (params, name){ + if( !queue.isFail() ){ + var ImgTrans = Image(img.nodeType ? img : file); + + if( typeof params == 'function' ){ + params(img, ImgTrans); + } + else if( params.width ){ + ImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.type); + } + else { + if( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){ + ImgTrans.resize(params.maxWidth, params.maxHeight, 'max'); + } + } + + if( params.rotate === undef && autoOrientation ){ + params.rotate = 'auto'; + } + + ImgTrans.rotate(params.rotate); + + queue.inc(); + ImgTrans.toData(function (err, image){ + if( err ){ + queue.fail(); + } + else { + images[name] = image; + queue.next(); + } + }); + } + }); + } + else { + queue.fail(); + } + }); + }; + + + // @export + api.support.canvas = api.support.transform = support; + api.Image = Image; +})(FileAPI, document); diff --git a/lib/FileAPI.XHR.js b/lib/FileAPI.XHR.js new file mode 100644 index 00000000..18f04271 --- /dev/null +++ b/lib/FileAPI.XHR.js @@ -0,0 +1,188 @@ +(function (window, api){ + var + noop = function (){} + + , XHR = function (options){ + this.uid = api.uid(); + this.xhr = { + abort: noop + , getResponseHeader: noop + , getAllResponseHeaders: noop + }; + this.options = options; + } + ; + + + XHR.prototype = { + status: 0, + statusText: '', + + getResponseHeader: function (name){ + return this.xhr.getResponseHeader(name); + }, + + getAllResponseHeaders: function (){ + return this.xhr.getAllResponseHeaders() || {}; + }, + + end: function (status, statusText){ + var _this = this, options = _this.options; + + _this.end = + _this.abort = noop; + _this.status = status; + + if( statusText ){ + _this.statusText = statusText; + } + + api.log('xhr.end:', status, statusText); + options.complete(status == 200 ? false : _this.statusText || 'unknown', _this); + + if( _this.xhr && _this.xhr.node ){ + setTimeout(function (){ + var node = _this.xhr.node; + try { node.parentNode.removeChild(node); } catch (e){} + try { delete window[_this.uid]; } catch (e){} + window[_this.uid] = _this.xhr.node = null; + }, 9); + } + }, + + abort: function (){ + this.end(0, 'abort'); + + if( this.xhr ){ + this.xhr.abort(); + } + }, + + send: function (FormData){ + var _this = this, options = this.options; + + FormData.toData(function (data){ + // Start uploading + options.upload(options, _this); + _this._send.call(_this, options, data); + }); + }, + + _send: function (options, data){ + var _this = this, xhr, uid = _this.uid, url = options.url; + + api.log('XHR._send:', data); + + // No cache + url += (~url.indexOf('?') ? '&' : '?') + api.uid(); + + if( data.nodeName ){ + options.upload(options, _this); + + xhr = document.createElement('div'); + xhr.innerHTML = '
' + + '' + + '' + + '
' + ; + + _this.xhr.abort = function (){ + var transport = xhr.getElementsByName('iframe')[0]; + if( transport ){ + try { + if( transport.stop ) transport.stop(); + else if( transport.contentWindow.stop ) transport.contentWindow.stop(); + else transport.contentWindow.document.execCommand('Stop'); + } + catch (er) {} + } + xhr = null; + }; + + // append form-data + var form = xhr.getElementsByTagName('form')[0]; + form.appendChild(data); + + api.log(form.parentNode.innerHTML); + + // append to DOM + document.body.appendChild(xhr); + + // keep a reference to node-transport + _this.xhr.node = xhr; + + // jsonp-callack + window[uid] = function (status, statusText, response){ + _this.readyState = 4; + _this.responseText = response; + _this.end(status, statusText); + xhr = null; + }; + + // send + _this.readyState = 2; // loaded + form.submit(); + form = null; + } + else { + xhr = _this.xhr = api.getXHR(); + + xhr.open('POST', url, true); + xhr.withCredential = "true"; + + if( !options.headers || !options.headers['X-Requested-With'] ){ + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + } + + api.each(options.headers, function (val, key){ + xhr.setRequestHeader(key, val); + }); + + if( xhr.upload ){ + // https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29 + xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){ + options.progress(evt, _this, options); + }, 100), false); + } + + xhr.onreadystatechange = function (){ + _this.status = xhr.status; + _this.statusText = xhr.statusText; + _this.readyState = xhr.readyState; + + if( xhr.readyState == 4 ){ + for( var k in { '': 1, XML: 1, Text: 1, Body: 1 } ){ + _this['response'+k] = xhr['response'+k]; + } + xhr.onreadystatechange = null; + _this.end(xhr.status); + xhr = null; + } + }; + + if( api.isArray(data) ){ + // multipart + xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando); + data = data.join('') +'--_'+ api.expando +'--'; + + /** @namespace xhr.sendAsBinary https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */ + if( xhr.sendAsBinary ){ + xhr.sendAsBinary(data); + } + else { + var bytes = Array.prototype.map.call(data, function(c){ return c.charCodeAt(0) & 0xff; }); + xhr.send(new Uint8Array(bytes).buffer); + + } + } + else { + xhr.send(data); + } + } + } + }; + + + // @export + api.XHR = XHR; +})(window, FileAPI); diff --git a/lib/FileAPI.core.js b/lib/FileAPI.core.js new file mode 100644 index 00000000..d51a9df2 --- /dev/null +++ b/lib/FileAPI.core.js @@ -0,0 +1,1382 @@ +/*global URL, webkitURL, dataURLtoBlob*/ + +(function (window, undef){ + 'use strict'; + + var + gid = 1, + noop = function (){}, + userAgent = navigator.userAgent, + + // https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48 + apiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL), + + File = window.File, + FileReader = window.FileReader, + FormData = window.FormData, + jQuery = window.jQuery, + + html5 = !!(File && (FileReader && window.Uint8Array || FormData)) + && !(/safari\//.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25 + + cors = html5 && ('withCredentials' in (new XMLHttpRequest)), + + document = window.document, + + // https://github.com/blueimp/JavaScript-Canvas-to-Blob + dataURLtoBlob = window.dataURLtoBlob, + + _mime = {}, + _rmime = {}, + + _rimg = /img/i, + _rcanvas = /canvas/i, + _rimgcanvas = /img|canvas/, + _rinput = /input/i, + _rdata = /^data:[^,]+,/, + + _KB = 1024, + _pow = Math.pow, + + _elEvents = {}, // element event listeners + _infoReader = [], // list of file info processors + + _readerEvents = 'abort progress error load loadend', + _xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '), + + currentTarget = 'currentTarget', + preventDefault = 'preventDefault', + + api = { + build: 4, + + cors: false, + debug: false, + + pingUrl: false, + staticPath: './', + + KB: _KB, + MB: _pow(_KB, 2), + GB: _pow(_KB, 3), + TB: _pow(_KB, 4), + + expando: 'fileapi' + (+new Date), + + uid: function (obj){ + return obj + ? (obj[api.expando] = obj[api.expando] || api.uid()) + : (api.expando + ++gid) + ; + }, + + log: function (){ + if( api.debug && window.console && console.log ){ + if( console.log.apply ){ + console.log.apply(console, arguments); + } + else { + console.log([].join.call(arguments, ' ')); + } + } + }, + + getXHR: function (){ + var xhr; + + if( window.XMLHttpRequest ){ + xhr = new XMLHttpRequest; + } + else if( window.ActiveXObject ){ + try { xhr = new ActiveXObject('MSXML2.XMLHttp.3.0'); } catch (e) { } + } + + return xhr; + }, + + isArray: isArray, + + support: { + dnd: cors && ('ondrop' in document.createElement('div')), + cors: cors, + html5: html5, + dataURI: true + }, + + event: { + on: _on + , off: _off + , one: _one + , fix: _fixEvent + }, + + + throttle: function(fn, delay) { + var id, args; + + return function _throttle(){ + args = arguments; + + if( !id ){ + fn.apply(window, args); + id = setTimeout(function (){ + id = 0; + fn.apply(window, args); + }, delay); + } + }; + }, + + + F: function (){}, + + + parseJSON: function (str){ + var json; + if( window.JSON && JSON.parse ){ + json = JSON.parse(str); + } + else { + json = (new Function('return ('+str.replace(/([\r\n])/g, '\\$1')+');'))(); + } + return json; + }, + + + trim: function (str){ + str = String(str); + return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); + }, + + /** + * Simple Defer + * @return {Object} + */ + defer: function (){ + var + list = [] + , result + , error + , defer = { + resolve: function (err, res){ + defer.resolve = noop; + error = err || false; + result = res; + + while( res = list.shift() ){ + res(error, result); + } + }, + + then: function (fn){ + if( error !== undef ){ + fn(error, result); + } else { + list.push(fn); + } + } + }; + + return defer; + }, + + queue: function (fn){ + var + _idx = 0 + , _length = 0 + , _fail = false + , _end = false + , queue = { + inc: function (){ + _length++; + }, + + next: function (){ + _idx++; + setTimeout(queue.check, 0); + }, + + check: function (){ + (_idx >= _length) && !_fail && queue.end(); + }, + + isFail: function (){ + return _fail; + }, + + fail: function (){ + !_fail && fn(_fail = true); + }, + + end: function (){ + if( !_end ){ + _end = true; + fn(); + } + } + } + ; + return queue; + }, + + + /** + * For each object + * + * @param {Object|Array} obj + * @param {Function} fn + * @param {*} [ctx] + */ + each: _each, + + + /** + * Async for + * + * @param {Array} array + */ + afor: function (array, callback){ + var i = 0, n = array.length; + + if( isArray(array) && n-- ){ + (function _next(){ + callback(n != i && _next, array[i], i++); + })(); + } + else { + callback(false); + } + }, + + + + /** + * Merge the contents of two or more objects together into the first object + * + * @param {Object} dst + * @param {Object} [src] + * @return {Object} + */ + extend: function (dst){ + _each(arguments, function (src){ + _each(src, function (val, key){ + dst[key] = val; + }); + }); + return dst; + }, + + + /** + * Is file instance + * + * @param {File} file + * @return {Boolean} + */ + isFile: function (file){ + return html5 && file && (file instanceof File); + }, + + + /** + * Is canvas element + * + * @param {HTMLElement} el + * @return {Boolean} + */ + isCanvas: function (el){ + return el && _rcanvas.test(el.nodeName); + }, + + + getFilesFilter: function (filter){ + filter = typeof filter == 'string' ? filter : (filter.getAttribute && filter.getAttribute('accept') || ''); + return filter ? new RegExp('('+ filter.replace(/\./g, '\\.').replace(/,/g, '|') +')$', 'i') : /./; + }, + + + + /** + * Read as DataURL + * + * @param {File|Element} file + * @param {Function} fn + */ + readAsDataURL: function (file, fn){ + if( api.isCanvas(file) ){ + _emit(file, fn, 'load', api.toDataURL(file)); + } + else { + _readAs(file, fn, 'DataURL'); + } + }, + + + /** + * Read as Binary string + * + * @param {File} file + * @param {Function} fn + */ + readAsBinaryString: function (file, fn){ + if( _hasSupportReadAs('BinaryString') ){ + _readAs(file, fn, 'BinaryString'); + } else { + // Hello IE10! + _readAs(file, function (evt){ + if( evt.type == 'load' ){ + try { + // dataURL -> binaryString + evt.result = api.toBinaryString(evt.result); + } catch (e){ + evt.type = 'error'; + evt.message = e.toString(); + } + } + fn(evt); + }, 'DataURL'); + } + }, + + + /** + * Read as ArrayBuffer + * + * @param {File} file + * @param {Function} fn + */ + readAsArrayBuffer: function(file, fn){ + _readAs(file, fn, 'ArrayBuffer'); + }, + + + /** + * Read as text + * + * @param {File} file + * @param {String} encoding + * @param {Function} fn + */ + readAsText: function(file, encoding, fn){ + if( !fn ){ + fn = encoding; + encoding = 'utf-8'; + } + + _readAs(file, fn, 'Text', encoding); + }, + + + /** + * Convert image or canvas to DataURL + * + * @param {Element} el Image or Canvas element + * @return {String} + */ + toDataURL: function (el){ + if( typeof el == 'string' ){ + return el; + } + else if( el.toDataURL ){ + return el.toDataURL('image/png'); + } + }, + + + /** + * Canvert string, image or canvas to binary string + * + * @param {String|Element} val + * @return {String} + */ + toBinaryString: function (val){ + return window.atob(api.toDataURL(val).replace(_rdata, '')); + }, + + + /** + * Read file or DataURL as ImageElement + * + * @param {File|String} file + * @param {Function} fn + * @param {Boolean} [progress] + */ + readAsImage: function (file, fn, progress){ + if( api.isFile(file) ){ + if( apiURL ){ + /** @namespace apiURL.createObjectURL */ + var data = apiURL.createObjectURL(file); + if( data === undef ){ + _emit(file, fn, 'error'); + } + else { + api.readAsImage(data, fn, progress); + } + } + else { + api.readAsDataURL(file, function (evt){ + if( evt.type == 'load' ){ + api.readAsImage(evt.result, fn, progress); + } + else if( progress || evt.type == 'error' ){ + _emit(file, fn, evt, null, { loaded: evt.loaded, total: evt.total }); + } + }); + } + } + else if( api.isCanvas(file) ){ + _emit(file, fn, 'load', file); + } + else if( _rimg.test(file.nodeName) ){ + if( file.complete ){ + _emit(file, fn, 'load', file); + } + else { + var events = 'error abort load'; + _one(file, events, function _fn(evt){ + if( evt.type == 'load' && apiURL ){ + /** @namespace apiURL.revokeObjectURL */ + apiURL.revokeObjectURL(file.src); + } + + _off(file, events, _fn); + _emit(file, fn, evt, file); + }); + } + } + else if( file.iframe ){ + _emit(file, fn, { type: 'error' }); + } + else { + // Created image + var img = new Image; + img.src = file.dataURL || file; + api.readAsImage(img, fn, progress); + } + }, + + + /** + * Make file by name + * + * @param {String} name + * @return {Array} + */ + checkFileObj: function (name){ + var file = {}; + + if( typeof name == 'object' ){ + file = name; + } + else { + file.name = (name+'').split(/(\\|\/)/g).pop(); + } + + if( file.type === undef ){ + file.type = file.name.split('.').pop(); + } + + _each(_rmime, function (mime, type){ + if( mime.test(file.type) ){ + file.type = type +'/'+ file.type; + } + }); + + return file; + }, + + + /** + * Get drop files + * + * @param {Event} evt + * @param {Function} callback + */ + getDropFiles: function (evt, callback){ + var + files = [] + , dataTransfer = _getDataTransfer(evt) + , entrySupport = isArray(dataTransfer.items) && dataTransfer.items[0] && _getAsEntry(dataTransfer.items[0]) + , queue = api.queue(function (){ callback(files); }) + ; + + _each((entrySupport ? dataTransfer.items : dataTransfer.files) || [], function (item){ + queue.inc(); + if( entrySupport ){ + _readEntryAsFiles(item, function (err, entryFiles){ + !err && files.push.apply(files, entryFiles); + queue.next(); + }); + } + else { + _isRegularFile(item, function (yes){ + yes && files.push(item); + queue.next(); + }); + } + }); + + queue.check(); + }, + + + /** + * Get file list + * + * @param {HTMLInput|Event} input + * @param {String|Function} [filter] + * @param {Function} [callback] + * @return {Array|Null} + */ + getFiles: function (input, filter, callback){ + var files = []; + + if( callback ){ + api.filterFiles(api.getFiles(input), filter, callback); + return null; + } + + if( input.jquery ){ + // jQuery object + input.each(function (){ + files = files.concat(api.getFiles(this)); + }); + input = files; + files = []; + } + + if( typeof filter == 'string' ){ + filter = api.getFilesFilter(filter); + } + + if( input.originalEvent ){ + // jQuery event + input = _fixEvent(input.originalEvent); + } + else if( input.srcElement ){ + // IE Event + input = _fixEvent(input); + } + + + if( input.dataTransfer ){ + // Drag'n'Drop + input = input.dataTransfer; + } + else if( input.target ){ + // Event + input = input.target; + } + + if( input.files ){ + // Input[type="file"] + files = input.files; + } + else if( !html5 && isInputFile(input) ){ + if( api.trim(input.value) ){ + files = [api.checkFileObj(input.value)]; + files[0].blob = input; + files[0].iframe = true; + } + } + else if( isArray(input) ){ + files = input; + } + + + if( !filter && isInputFile(input) ){ + filter = api.getFilesFilter(input); + } + + return api.filter(files, function (file){ return !filter || filter.test(file.name); }); + }, + + + /** + * Get image information + * + * @param {File} file + * @param {Function} fn + */ + getInfo: function (file, fn){ + var info = {}, readers = _infoReader.concat(); + + if( api.isFile(file) ){ + (function _next(){ + var reader = readers.shift(); + if( reader ){ + if( reader.test(file.type) ){ + reader(file, function (err, res){ + if( err ){ + fn(err); + } + else { + api.extend(info, res); + _next(); + } + }); + } + else { + _next(); + } + } + else { + fn(false, info); + } + })(); + } + else { + fn('not_support', info); + } + }, + + + /** + * Add information reader + * + * @param {RegExp} mime + * @param {Function} fn + */ + addInfoReader: function (mime, fn){ + fn.test = function (type){ return mime.test(type) }; + _infoReader.push(fn); + }, + + + addMime: function (type, extensions){ + _mime[type] = extensions; + _rmime[type] = new RegExp('('+ extensions.replace(/,/g, '|') +')$', 'i'); + }, + + + /** + * Filter of array + * + * @param {Array} input + * @param {Function} fn + * @return {Array} + */ + filter: function (input, fn){ + var result = [], i = 0, n = input.length, val; + for( ; i < n; i++ ) if( i in input ){ + val = input[i]; + if( fn.call(val, val, i, input) ){ + result.push(val); + } + } + return result; + }, + + + /** + * Filter files + * + * @param {Array} files + * @param {Function} eachFn + * @param {Function} resultFn + */ + filterFiles: function (files, eachFn, resultFn){ + if( files.length ){ + // HTML5 or Flash + var queue = files.concat(), file, result = [], deleted = []; + + (function _next(){ + if( queue.length ){ + file = queue.shift(); + api.getInfo(file, function (err, info){ + (eachFn(file, err ? false : info) ? result : deleted).push(file); + _next(); + }); + } + else { + resultFn(result, deleted); + } + })(); + } + else { + resultFn([], files); + } + }, + + + upload: function (options){ + options = api.extend({ + beforeupload: api.F + , upload: api.F + , fileupload: api.F + , fileprogress: api.F + , filecomplete: api.F + , progress: api.F + , complete: api.F + }, options); + + + if( options.imageAutoOrientation && !options.imageTransform ){ + options.imageTransform = { rotate: 'auto' }; + } + + + var + proxyXHR = new api.XHR(options) + , dataArray = this._getFilesDataArray(options.files) + , total = 0 + , loaded = 0 + , _loaded = 0 + , _part = 1 // part of total size + ; + + + // calc total size + _each(dataArray, function (data){ + total += data.size; + }); + + // Array of files + proxyXHR.files = [].concat(dataArray); + + // Set upload status props + proxyXHR.total = total; + proxyXHR.loaded = 0; + + + // emit "beforeupload" event + options.beforeupload(proxyXHR, options); + + + // Upload by file + (function _nextFile(){ + var data = dataArray.shift(), _this = this, _fileLoaded = false; + + // Set current upload file + proxyXHR.currentFile = data; + + if( proxyXHR.statusText != 'abort' && data ){ + this._getFormData(options, data, function (form){ + if( !loaded ){ + // emit "upload" event + options.upload(proxyXHR, options); + } + + var + _file = data.file, + xhr = new api.XHR(api.extend({}, options, { + + upload: function (){ + // emit "fileupload" event + options.fileupload(_file, xhr, options); + }, + + progress: function (evt){ + if( !_fileLoaded && evt.lengthComputable ){ + loaded += ~~(total * _part * (evt.loaded/evt.total) - _loaded + .5); + _loaded = loaded; + + data.total = evt.total; + data.loaded = evt.loaded; + + // emit "fileprogress" event + options.fileprogress(evt, _file, xhr, options); + + // emit "progress" event + options.progress({ + type: evt.type + , total: total + , loaded: proxyXHR.loaded = loaded + , lengthComputable: true + }, _file, xhr, options); + } + }, + + complete: function (err){ + _each(_xhrPropsExport, function (name){ + proxyXHR[name] = xhr[name]; + }); + + // fixed throttle event + _fileLoaded = true; + + data.loaded = data.total; + + // bytes loaded + proxyXHR.loaded = (loaded += (loaded - _loaded) + ~~(total*_part + .5)); + + // emit "filecomplete" event + options.filecomplete(err, xhr, _file, options); + + // upload next file + _nextFile.call(_this); + } + })) // xhr + ; + + + // share of file size from the total size + _part = data.size / total; + + // ... + proxyXHR.abort = function (){ xhr.abort(); }; + + // Start upload + xhr.send(form); + }); + } + else { + options.complete(proxyXHR.status == 200 ? false : (proxyXHR.statusText || 'error'), proxyXHR, options); + } + }).call(this); + + + return proxyXHR; + }, + + + _getFilesDataArray: function (data){ + var files = [], oFiles = {}; + + if( isInputFile(data) ){ + var tmp = api.getFiles(data); + oFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0]; + } + else if( isArray(data) && isInputFile(data[0]) ){ + _each(data, function (input){ + oFiles[input.name || 'file'] = api.getFiles(input); + }); + } + else { + oFiles = data; + } + + _each(oFiles, function add(file, name){ + if( isArray(file) ){ + _each(file, function (file, idx){ + add(file, name+'['+idx+']'); + }); + } + else if( file && file.name ){ + files.push({ + name: name + , file: file + , size: file.size + , total: file.size + , loaded: 0 + }); + } + }); + + return files; + }, + + + _getFormData: function (options, data, fn){ + var + file = data.file + , name = data.name + , filename = file.name + , filetype = file.type + , trans = api.support.transform && options.imageTransform + , Form = new api.Form + , queue = api.queue(function (){ fn(Form); }) + , isOrignTrans = trans && (parseInt(trans.maxWidth || trans.minWidth || trans.width, 10) > 0 || trans.rotate) + ; + + + if( trans && (/image/.test(file.type) || _rimgcanvas.test(file.nodeType)) ){ + queue.inc(); + + if( isOrignTrans ){ + // Convert to array for transform function + trans = [trans]; + } + + api.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){ + if( isOrignTrans && !err ){ + if( !dataURLtoBlob && !api.flashEngine ){ + images[0] = api.toBinaryString(images[0]); + Form.multipart = true; + } + + Form.append(name, images[0], filename, filetype); + } + else { + if( !err ){ + _each(images, function (image, idx){ + if( !dataURLtoBlob && !api.flashEngine ){ + image = api.toBinaryString(image); + Form.multipart = true; + } + + Form.append(name +'['+ idx +']', image, filename, filetype); + }); + + name += '[original]'; + } + + if( err || options.imageOriginal ){ + Form.append(name, file, filename, filetype); + } + } + + queue.next(); + }); + } + else { + Form.append(name, file, filename); + } + + // Append data + _each(options.data, function add(val, name){ + if( typeof val == 'object' ){ + _each(val, function (v, i){ + add(v, name+'['+i+']'); + }); + } + else { + Form.append(name, val); + } + }); + + queue.check(); + }, + + + reset: function (inp){ + var parent, clone; + + if( jQuery ){ + clone = jQuery(inp).clone(true).insertBefore(inp).val('')[0]; + jQuery(inp).remove(); + } else { + parent = inp.parentNode; + clone = parent.insertBefore(inp.cloneNode(true), inp); + clone.value = ''; + parent.removeChild(inp); + + _each(_elEvents[api.uid(inp)], function (fns, type){ + _each(fns, function (fn){ + _off(inp, type, fn); + _on(clone, type, fn); + }) + }); + } + + return clone; + }, + + + /** + * Load remote file + * + * @param {String} url + * @param {Function} fn + * @return {XMLHttpRequest} + */ + load: function (url, fn){ + var xhr = api.getXHR(); + if( xhr ){ + xhr.open('GET', url, true); + + if( xhr.overrideMimeType ){ + xhr.overrideMimeType('text/plain; charset=x-user-defined'); + } + + _on(xhr, 'progress', function (/**Event*/evt){ + /** @namespace evt.lengthComputable */ + if( evt.lengthComputable ){ + fn({ type: evt.type, loaded: evt.loaded, total: evt.total }, xhr); + } + }); + + xhr.onreadystatechange = function(){ + if( xhr.readyState == 4 ){ + xhr.onreadystatechange = null; + if( xhr.status == 200 ){ + url = url.split('/'); + /** @namespace xhr.responseBody */ + var file = { + name: url[url.length-1] + , size: xhr.getResponseHeader('Content-Length') + , type: xhr.getResponseHeader('Content-Type') + }; + file.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText); + fn({ type: 'load', result: file }); + } + else { + fn({ type: 'error' }); + } + } + }; + xhr.send(null); + } else { + fn({ type: 'error' }); + } + + return xhr; + }, + + encode64: function (str){ + var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', outStr = '', i = 0; + + if( typeof str !== 'string' ){ + str = String(str); + } + + while( i < str.length ){ + //all three "& 0xff" added below are there to fix a known bug + //with bytes returned by xhr.responseText + var + byte1 = str.charCodeAt(i++) & 0xff + , byte2 = str.charCodeAt(i++) & 0xff + , byte3 = str.charCodeAt(i++) & 0xff + , enc1 = byte1 >> 2 + , enc2 = ((byte1 & 3) << 4) | (byte2 >> 4) + , enc3, enc4 + ; + + if( isNaN(byte2) ){ + enc3 = enc4 = 64; + } else { + enc3 = ((byte2 & 15) << 2) | (byte3 >> 6); + enc4 = isNaN(byte3) ? 64 : byte3 & 63; + } + + outStr += b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4); + } + + return outStr; + } + + } // api + ; + + + function _each(obj, fn, ctx){ + if( obj ){ + if( isArray(obj) ){ + for( var i = 0, n = obj.length; i < n; i++ ){ if( i in obj ) + fn.call(ctx, obj[i], i, obj); + } + } + else { + for( var key in obj ) if( obj.hasOwnProperty(key) ){ + fn.call(ctx, obj[key], key, obj); + } + } + } + } + + + // @private methods + function _on(el, type, fn){ + if( el ){ + var uid = api.uid(el); + + if( !_elEvents[uid] ){ + _elEvents[uid] = {}; + } + + _each(type.split(/\s+/), function (type){ + if( jQuery ){ + jQuery.event.add(el, type, fn); + } + else { + if( !_elEvents[uid][type] ){ + _elEvents[uid][type] = [] + } + _elEvents[uid][type].push(fn); + + + if( el.addEventListener ) el.addEventListener(type, fn, false); + else if( el.attachEvent ) el.attachEvent('on'+type, fn); + else el['on'+type] = fn; + } + }); + } + } + + + function _off(el, type, fn){ + if( el ){ + var uid = api.uid(el), events = _elEvents[uid] || {}; + + _each(type.split(/\s+/), function (type){ + if( jQuery ){ + jQuery.event.remove(el, type, fn); + } + else { + var fns = events[type] || [], i = fns.length; + + while( i-- ){ + if( fns[i] === fn ){ + fns.splice(i, 1); + break; + } + } + + if( el.addEventListener ) el.removeEventListener(type, fn, false); + else if( el.detachEvent ) el.detachEvent('on'+type, fn); + else el['on'+type] = null; + } + }); + } + } + + + function _one(el, type, fn){ + _on(el, type, function _(evt){ + _off(el, type, _); + fn(evt); + }); + } + + + function _emit(target, fn, name, res, ext){ + var evt = { + type: name.type || name + , target: target + , result: res + }; + api.extend(evt, ext); + fn(evt); + } + + + function _hasSupportReadAs(as){ + return FileReader && !!FileReader.prototype['readAs'+as]; + } + + + function _readAs(file, fn, as, encoding){ + if( api.isFile(file) && _hasSupportReadAs(as) ){ + var Reader = new FileReader; + + // Add event listener + _on(Reader, _readerEvents, function _fn(evt){ + var type = evt.type; + if( type == 'progress' ){ + _emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total }) + } + else if( type == 'loadend' ){ + _off(Reader, _readerEvents, _fn); + Reader = null; + } + else { + _emit(file, fn, evt, evt.target.result); + } + }); + + + try { + // ReadAs ... + if( encoding ){ + Reader['readAs'+as](encoding, file); + } + else { + Reader['readAs'+as](file); + } + } + catch (err){ + _emit(file, fn, 'error', undef, { error: err.toString() }); + } + } + else { + _emit(file, fn, 'error', undef, { error: 'filreader_not_support_'+as }); + } + } + + + function _isRegularFile(file, callback){ + // http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects + if( !file.type && (file.size % 4096) == 0 && (file.size <= 102400) ){ + if( FileReader ){ + try { + var Reader = new FileReader(); + + _one(Reader, _readerEvents, function (evt){ + var isFile = evt.type != 'error'; + callback(isFile); + if( isFile ){ + Reader.abort(); + } + }); + + Reader.readAsDataURL(file); + } catch( err ){ + callback(false); + } + } + else { + callback(null) + } + } + else { + callback(true); + } + } + + + function _getAsEntry(item){ + var entry; + if( item.getAsEntry ) entry = item.getAsEntry(); + else if( item.webkitGetAsEntry ) entry = item.webkitGetAsEntry(); + return entry; + } + + + function _readEntryAsFiles(entry, callback){ + if( !entry ){ + // error + callback('empty_entry'); + } + else if( entry.isFile ){ + // Read as file + entry.file(function(file){ + // success + callback(false, [file]); + }, function (){ + // error + callback('entry_file'); + }); + } + else if( entry.isDirectory ){ + var reader = entry.createReader(), result = []; + + reader.readEntries(function(entries, i){ + // success + api.afor(entries, function (next, entry){ + _readEntryAsFiles(entry, function (err, files){ + if( !err ){ + result = result.concat(files); + } + + if( next ){ + next(); + } + else { + callback(false, result); + } + }); + }); + }, function (){ + // error + callback('directory_reader'); + }); + } + else { + _readEntryAsFiles(_getAsEntry(entry), callback); + } + } + + function isArray(ar) { + return (typeof ar == 'object') && ar && ('length' in ar); + } + + + function isInputFile(el){ + return _rinput.test(el && el.tagName); + } + + + function _getDataTransfer(evt){ + return (evt.originalEvent || evt || '').dataTransfer || {}; + } + + + function _fixEvent(evt){ + if( !evt.target ) evt.target = window.event && window.event.srcElement || document; + if( evt.target.nodeType === 3 ) evt.target = event.target.parentNode; + return evt; + } + + + // Add default mime + _each({ // default extensions by mime + 'image': 'png,jpg,jpeg,bmp,gif,ico,tif,tiff,tga,pcx,cbz,cbr' + , 'audio': 'm4a,flac,aac,rm,mpa,wav,wma,ogg,mp3,mp2,m3u,mod,amf,dmf,dsm,far,gdm,imf,it,m15,med,okt,s3m,stm,sfx,ult,uni,xm,sid,ac3,dts,cue,aif,aiff,wpl,ape,mac,mpc,mpp,shn,wv,nsf,spc,gym,adplug,adx,dsp,adp,ymf,ast,afc,hps,xsp' + , 'video': 'm4v,3gp,nsv,ts,ty,strm,rm,rmvb,m3u,ifo,mov,qt,divx,xvid,bivx,vob,nrg,img,iso,pva,wmv,asf,asx,ogm,m2v,avi,bin,dat,dvr-ms,mpg,mpeg,mp4,mkv,avc,vp3,svq3,nuv,viv,dv,fli,flv,wpl' + }, function (val, type){ + api.addMime(type, val); + }); + + + // Add default image info reader + api.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){ + if( !file.__dimensions ){ + var defer = file.__dimensions = api.defer(); + + api.readAsImage(file, function (evt){ + var img = evt.target; + defer.resolve(evt.type == 'load' ? false : 'error', { + width: img.width + , height: img.height + }); + img = null; + }); + } + + file.__dimensions.then(callback); + }); + + + // Special event + api.event.dnd = function (el, onHover, onDrop){ + var _id, _type; + + if( !onDrop ){ + onDrop = onHover; + onHover = api.F; + } + + if( FileReader ){ + _on(el, 'dragenter dragleave dragover', function (evt){ + var types = _getDataTransfer(evt).types, i = types && types.length; + + while( i-- ){ + if( ~types[i].indexOf('File') ){ + evt[preventDefault](); + + if( _type !== evt.type ){ + _type = evt.type; + + if( _type != 'dragleave' ){ + onHover.call(evt[currentTarget], true, evt); + } + + clearTimeout(_id); + _id = setTimeout(function (){ + onHover.call(evt[currentTarget], _type != 'dragleave', evt); + }, 50); + } + } + } + }); + + _on(el, 'drop', function (evt){ + evt[preventDefault](); + + _type = 0; + onHover.call(evt[currentTarget], false, evt); + + api.getDropFiles(evt, function (files){ + onDrop.call(evt[currentTarget], files, evt); + }); + }); + } + else { + api.log("Drag'n'Drop -- not supported"); + } + }; + + + // Support jQuery + if( jQuery && !jQuery.fn.dnd ){ + jQuery.fn.dnd = function (onHover, onDrop){ + return this.each(function (){ + api.event.dnd(this, onHover, onDrop); + }); + }; + } + + + // @export + window.FileAPI = api.extend(api, window.FileAPI); +})(window); diff --git a/lib/canvas-to-blob.js b/lib/canvas-to-blob.js new file mode 100644 index 00000000..72edfdbd --- /dev/null +++ b/lib/canvas-to-blob.js @@ -0,0 +1,91 @@ +/* + * JavaScript Canvas to Blob 2.0.3 + * https://github.com/blueimp/JavaScript-Canvas-to-Blob + * + * Copyright 2012, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + * + * Based on stackoverflow user Stoive's code snippet: + * http://stackoverflow.com/q/4998908 + */ + +/*jslint nomen: true, regexp: true */ +/*global window, atob, Blob, ArrayBuffer, Uint8Array, define */ + +(function (window) { + 'use strict'; + var CanvasPrototype = window.HTMLCanvasElement && + window.HTMLCanvasElement.prototype, + hasBlobConstructor = window.Blob && (function () { + try { + return Boolean(new Blob()); + } catch (e) { + return false; + } + }()), + hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array && + (function () { + try { + return new Blob([new Uint8Array(100)]).size === 100; + } catch (e) { + return false; + } + }()), + BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || + window.MozBlobBuilder || window.MSBlobBuilder, + dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob && + window.ArrayBuffer && window.Uint8Array && function (dataURI) { + var byteString, + arrayBuffer, + intArray, + i, + mimeString, + bb; + if (dataURI.split(',')[0].indexOf('base64') >= 0) { + // Convert base64 to raw binary data held in a string: + byteString = atob(dataURI.split(',')[1]); + } else { + // Convert base64/URLEncoded data component to raw binary data: + byteString = decodeURIComponent(dataURI.split(',')[1]); + } + // Write the bytes of the string to an ArrayBuffer: + arrayBuffer = new ArrayBuffer(byteString.length); + intArray = new Uint8Array(arrayBuffer); + for (i = 0; i < byteString.length; i += 1) { + intArray[i] = byteString.charCodeAt(i); + } + // Separate out the mime component: + mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; + // Write the ArrayBuffer (or ArrayBufferView) to a blob: + if (hasBlobConstructor) { + return new Blob( + [hasArrayBufferViewSupport ? intArray : arrayBuffer], + {type: mimeString} + ); + } + bb = new BlobBuilder(); + bb.append(arrayBuffer); + return bb.getBlob(mimeString); + }; + if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) { + if (CanvasPrototype.mozGetAsFile) { + CanvasPrototype.toBlob = function (callback, type) { + callback(this.mozGetAsFile('blob', type)); + }; + } else if (CanvasPrototype.toDataURL && dataURLtoBlob) { + CanvasPrototype.toBlob = function (callback, type) { + callback(dataURLtoBlob(this.toDataURL(type))); + }; + } + } + if (typeof define === 'function' && define.amd) { + define(function () { + return dataURLtoBlob; + }); + } else { + window.dataURLtoBlob = dataURLtoBlob; + } +}(this)); From 8c9b8a631549ca22e46095b7e613603c6405c74d Mon Sep 17 00:00:00 2001 From: Ilya Lebedev Date: Wed, 19 Dec 2012 15:56:30 +0400 Subject: [PATCH 02/12] FileAPI updated --- FileAPI.min.js | 113 ++++++++--------- README.md | 6 +- index.html | 293 +++++++++++++++++++++++++++++++++++++++++++ index.php | 6 +- lib/FileAPI.Flash.js | 14 +-- lib/FileAPI.core.js | 123 ++++++++++-------- 6 files changed, 440 insertions(+), 115 deletions(-) create mode 100644 index.html diff --git a/FileAPI.min.js b/FileAPI.min.js index 321223dd..f4174b47 100644 --- a/FileAPI.min.js +++ b/FileAPI.min.js @@ -4,60 +4,61 @@ * @author RubaXa * @build lib/canvas-to-blob lib/FileAPI.core lib/FileAPI.Image lib/FileAPI.Form lib/FileAPI.XHR lib/FileAPI.Flash */ -(function(a){var k=a.HTMLCanvasElement&&a.HTMLCanvasElement.prototype,g;if(g=a.Blob)try{g=Boolean(new Blob)}catch(n){g=!1}var m=g;if(g=m)if(g=a.Uint8Array)try{g=100===(new Blob([new Uint8Array(100)])).size}catch(f){g=!1}var c=g,e=a.BlobBuilder||a.WebKitBlobBuilder||a.MozBlobBuilder||a.MSBlobBuilder,q=(m||e)&&a.atob&&a.ArrayBuffer&&a.Uint8Array&&function(a){var h,g,v,f;h=0<=a.split(",")[0].indexOf("base64")?atob(a.split(",")[1]):decodeURIComponent(a.split(",")[1]);g=new ArrayBuffer(h.length);v=new Uint8Array(g); -for(f=0;f=a&&!c&&h.end()},isFail:function(){return c},fail:function(){!c&&b(c=!0)},end:function(){i||(i=!0,b())}};return h},each:g,afor:function(b,d){var a=0,c=b.length;h(b)&&c--?function B(){d(c!=a&&B,b[a],a++)}():d(!1)},extend:function(b){g(arguments,function(d){g(d,function(d,a){b[a]=d})});return b},isFile:function(b){return d&&b&&b instanceof w},isCanvas:function(b){return b&&M.test(b.nodeName)},getFilesFilter:function(b){return(b="string"== -typeof b?b:b.getAttribute&&b.getAttribute("accept")||"")?RegExp("("+b.replace(/\./g,"\\.").replace(/,/g,"|")+")$","i"):/./},readAsDataURL:function(b,d){j.isCanvas(b)?c(b,d,"load",j.toDataURL(b)):e(b,d,"DataURL")},readAsBinaryString:function(b,d){t&&t.prototype.readAsBinaryString?e(b,d,"BinaryString"):e(b,function(b){if("load"==b.type)try{b.result=j.toBinaryString(b.result)}catch(a){b.type="error",b.message=a.toString()}d(b)},"DataURL")},readAsArrayBuffer:function(b,d){e(b,d,"ArrayBuffer")},readAsText:function(b, -d,a){a||(a=d,d="utf-8");e(b,a,"Text",d)},toDataURL:function(b){if("string"==typeof b)return b;if(b.toDataURL)return b.toDataURL("image/png")},toBinaryString:function(b){return a.atob(j.toDataURL(b).replace(O,""))},readAsImage:function(b,d,a){if(j.isFile(b))if(y){var i=y.createObjectURL(b);i===k?c(b,d,"error"):j.readAsImage(i,d,a)}else j.readAsDataURL(b,function(i){"load"==i.type?j.readAsImage(i.result,d,a):(a||"error"==i.type)&&c(b,d,i,null,{loaded:i.loaded,total:i.total})});else j.isCanvas(b)?c(b, -d,"load",b):D.test(b.nodeName)?b.complete?c(b,d,"load",b):f(b,"error abort load",function B(a){"load"==a.type&&y&&y.revokeObjectURL(b.src);m(b,"error abort load",B);c(b,d,a,b)}):b.iframe?c(b,d,{type:"error"}):(i=new Image,i.src=b.dataURL||b,j.readAsImage(i,d,a))},checkFileObj:function(b){var d={};"object"==typeof b?d=b:d.name=(b+"").split(/(\\|\/)/g).pop();d.type===k&&(d.type=d.name.split(".").pop());g(I,function(b,a){b.test(d.type)&&(d.type=a+"/"+d.type)});return d},getDropFiles:function(b,d){var a= -[],c=(b.originalEvent||b||"").dataTransfer||{},i=h(c.items)&&c.items[0]&&q(c.items[0]),e=j.queue(function(){d(a)});g((i?c.items:c.files)||[],function(b){e.inc();if(i)u(b,function(b,d){!b&&a.push.apply(a,d);e.next()});else{var d=function(d){d&&a.push(b);e.next()};if(!b.type&&0==b.size%4096&&102400>=b.size)if(t)try{var c=new t;f(c,G,function(b){b="error"!=b.type;d(b);b&&c.abort()});c.readAsDataURL(b)}catch(r){d(!1)}else d(null);else d(!0)}});e.check()},getFiles:function(b,a,c){var i=[];if(c)return j.filterFiles(j.getFiles(b), -a,c),null;b.jquery&&(b.each(function(){i=i.concat(j.getFiles(this))}),b=i,i=[]);"string"==typeof a&&(a=j.getFilesFilter(a));b.originalEvent?b=A(b.originalEvent):b.srcElement&&(b=A(b));b.dataTransfer?b=b.dataTransfer:b.target&&(b=b.target);b.files?i=b.files:!d&&F.test(b&&b.tagName)?j.trim(b.value)&&(i=[j.checkFileObj(b.value)],i[0].blob=b,i[0].iframe=!0):h(b)&&(i=b);!a&&F.test(b&&b.tagName)&&(a=j.getFilesFilter(b));return j.filter(i,function(b){return!a||a.test(b.name)})},getInfo:function(b,d){var a= -{},c=J.concat();j.isFile(b)?function B(){var i=c.shift();i?i.test(b.type)?i(b,function(b,c){b?d(b):(j.extend(a,c),B())}):B():d(!1,a)}():d("not_support",a)},addInfoReader:function(b,d){d.test=function(d){return b.test(d)};J.push(d)},addMime:function(b,d){I[b]=RegExp("("+d.replace(/,/g,"|")+")$","i")},filter:function(b,d){for(var a=[],c=0,i=b.length,h;c>2,c=(c&3)<<4|i>>4;isNaN(i)?i=h=64:(i=(i&15)<<2|h>>6,h=isNaN(h)?64:h&63);d+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(u)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(c)+ -"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(i)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(h)}return d}};g({image:"png,jpg,jpeg,bmp,gif,ico,tif,tiff,tga,pcx,cbz,cbr",audio:"m4a,flac,aac,rm,mpa,wav,wma,ogg,mp3,mp2,m3u,mod,amf,dmf,dsm,far,gdm,imf,it,m15,med,okt,s3m,stm,sfx,ult,uni,xm,sid,ac3,dts,cue,aif,aiff,wpl,ape,mac,mpc,mpp,shn,wv,nsf,spc,gym,adplug,adx,dsp,adp,ymf,ast,afc,hps,xsp",video:"m4v,3gp,nsv,ts,ty,strm,rm,rmvb,m3u,ifo,mov,qt,divx,xvid,bivx,vob,nrg,img,iso,pva,wmv,asf,asx,ogm,m2v,avi,bin,dat,dvr-ms,mpg,mpeg,mp4,mkv,avc,vp3,svq3,nuv,viv,dv,fli,flv,wpl"}, -function(b,d){j.addMime(d,b)});j.addInfoReader(/^image/,function(b,d){if(!b.__dimensions){var a=b.__dimensions=j.defer();j.readAsImage(b,function(b){var d=b.target;a.resolve("load"==b.type?!1:"error",{width:d.width,height:d.height})})}b.__dimensions.then(d)});j.event.dnd=function(b,d,a){var c,i;a||(a=d,d=j.F);t?(n(b,"dragenter dragleave dragover",function(b){for(var a=((b.originalEvent||b||"").dataTransfer||{}).types,h=a&&a.length;h--;)~a[h].indexOf("File")&&(b.preventDefault(),i!==b.type&&(i=b.type, -"dragleave"!=i&&d.call(b.currentTarget,!0,b),clearTimeout(c),c=setTimeout(function(){d.call(b.currentTarget,"dragleave"!=i,b)},50)))}),n(b,"drop",function(b){b.preventDefault();i=0;d.call(b.currentTarget,!1,b);j.getDropFiles(b,function(d){a.call(b.currentTarget,d,b)})})):j.log("Drag'n'Drop -- not supported")};s&&!s.fn.dnd&&(s.fn.dnd=function(b,d){return this.each(function(){j.event.dnd(this,b,d)})});a.FileAPI=j.extend(j,a.FileAPI)})(window); -(function(a,k,g){function n(a,c){if(!(this instanceof n))return new n(a);this.file=a;this.better=!c;this.matrix={sx:0,sy:0,sw:0,sh:0,dx:0,dy:0,dw:0,dh:0,resize:0,deg:0}}var m=Math.min,f=Math.round,c=!1,e={8:270,3:180,6:90};try{c=-1=p?(p=e,n=p/k):(n=c,p=n*k),p!=e||n!=c)h.sx=~~((e-p)/2),h.sy=~~((c-n)/2),e=p,c=n}else n&&("min"==n?(g=f(p=k?m(e,g):q*p),q=f(p>=k?g/p:m(c,q))));h.sw=e;h.sh=c;h.dw=g;h.dh=q;return h},_trans:function(a){this._load(this.file,function(c,e){c?a(c):this._apply(e,a)})},get:function(c){if(a.support.transform){var h=this;"auto"==h.matrix.deg?a.getInfo(this.file,function(a,f){h.matrix.deg=e[f&& -f.exif&&f.exif.Orientation]||0;h._trans(c)}):h._trans(c)}else c("not_support")},toData:function(a){this.get(a)}};n.transform=function(c,e,f,q){a.getInfo(c,function(k,m){var y={},w=a.queue(function(a){q(a,y)});k?w.fail():a.each(e,function(a,e){if(!w.isFail()){var h=n(m.nodeType?m:c);if("function"==typeof a)a(m,h);else if(a.width)h[a.preview?"preview":"resize"](a.width,a.height,a.type);else a.maxWidth&&(m.width>a.maxWidth||m.height>a.maxHeight)&&h.resize(a.maxWidth,a.maxHeight,"max");a.rotate===g&& -f&&(a.rotate="auto");h.rotate(a.rotate);w.inc();h.toData(function(a,c){a?w.fail():(y[e]=c,w.next())})}})})};a.support.canvas=a.support.transform=c;a.Image=n})(FileAPI,document); -(function(a,k,g){var n=k.encodeURIComponent,m=k.FormData,k=function(){this.items=[]};k.prototype={append:function(a,c,e,g){this.items.push({name:a,blob:c&&c.blob||c,file:e||c.name,type:g||c.type})},each:function(a){for(var c=0,e=this.items.length;c',c.xhr.abort=function(){var a=e.getElementsByName("iframe")[0];if(a)try{a.stop?a.stop():a.contentWindow.stop?a.contentWindow.stop():a.contentWindow.document.execCommand("Stop")}catch(c){}e=null},n=e.getElementsByTagName("form")[0],n.appendChild(f), -k.log(n.parentNode.innerHTML),document.body.appendChild(e),c.xhr.node=e,a[q]=function(a,f,g){c.readyState=4;c.responseText=g;c.end(a,f);e=null},c.readyState=2,n.submit(),n=null):(e=c.xhr=k.getXHR(),e.open("POST",n,!0),e.withCredential="true",(!g.headers||!g.headers["X-Requested-With"])&&e.setRequestHeader("X-Requested-With","XMLHttpRequest"),k.each(g.headers,function(a,c){e.setRequestHeader(c,a)}),e.upload&&e.upload.addEventListener("progress",k.throttle(function(a){g.progress(a,c,g)},100),!1),e.onreadystatechange= -function(){c.status=e.status;c.statusText=e.statusText;c.readyState=e.readyState;if(4==e.readyState){for(var a in{"":1,XML:1,Text:1,Body:1})c["response"+a]=e["response"+a];e.onreadystatechange=null;c.end(e.status);e=null}},k.isArray(f)?(e.setRequestHeader("Content-Type","multipart/form-data; boundary=_"+k.expando),f=f.join("")+"--_"+k.expando+"--",e.sendAsBinary?e.sendAsBinary(f):(q=Array.prototype.map.call(f,function(a){return a.charCodeAt(0)&255}),e.send((new Uint8Array(q)).buffer))):e.send(f))}}; -k.XHR=n})(window,FileAPI); -(function(a,k,g){var n=a.support,m=k.navigator,f=m.mimeTypes,c=!1;if(m.plugins&&"object"==typeof m.plugins["Shockwave Flash"])c=m.plugins["Shockwave Flash"].description&&!(f&&f["application/x-shockwave-flash"]&&!f["application/x-shockwave-flash"].enabledPlugin);else try{c=!(!k.ActiveXObject||!new ActiveXObject("ShockwaveFlash.ShockwaveFlash"))}catch(e){}n.flash=c;if(a.support.flash&&(!a.support.html5||a.cors&&!a.support.cors)){var q=function(a){return('=a&&!c&&g.end()},isFail:function(){return c},fail:function(){!c&&b(c=!0)},end:function(){h||(h=!0,b())}};return g},each:f,afor:function(b,d){var a=0,c=b.length;g(b)&&c--?function C(){d(c!=a&&C,b[a],a++)}():d(!1)},extend:function(b){f(arguments,function(d){f(d,function(d,a){b[a]=d})});return b},isFile:function(b){return d&&b&&b instanceof v},isCanvas:function(b){return b&&N.test(b.nodeName)},getFilesFilter:function(b){return(b= +"string"==typeof b?b:b.getAttribute&&b.getAttribute("accept")||"")?RegExp("("+b.replace(/\./g,"\\.").replace(/,/g,"|")+")$","i"):/./},readAsDataURL:function(b,d){k.isCanvas(b)?c(b,d,"load",k.toDataURL(b)):e(b,d,"DataURL")},readAsBinaryString:function(b,d){u&&u.prototype.readAsBinaryString?e(b,d,"BinaryString"):e(b,function(b){if("load"==b.type)try{b.result=k.toBinaryString(b.result)}catch(a){b.type="error",b.message=a.toString()}d(b)},"DataURL")},readAsArrayBuffer:function(b,d){e(b,d,"ArrayBuffer")}, +readAsText:function(b,d,a){a||(a=d,d="utf-8");e(b,a,"Text",d)},toDataURL:function(b){if("string"==typeof b)return b;if(b.toDataURL)return b.toDataURL("image/png")},toBinaryString:function(b){return a.atob(k.toDataURL(b).replace(P,""))},readAsImage:function(b,d,a){if(k.isFile(b))if(A){var h=A.createObjectURL(b);h===j?c(b,d,"error"):k.readAsImage(h,d,a)}else k.readAsDataURL(b,function(h){"load"==h.type?k.readAsImage(h.result,d,a):(a||"error"==h.type)&&c(b,d,h,null,{loaded:h.loaded,total:h.total})}); +else k.isCanvas(b)?c(b,d,"load",b):E.test(b.nodeName)?b.complete?c(b,d,"load",b):l(b,"error abort load",function C(a){"load"==a.type&&A&&A.revokeObjectURL(b.src);p(b,"error abort load",C);c(b,d,a,b)}):b.iframe?c(b,d,{type:"error"}):(h=new Image,h.src=b.dataURL||b,k.readAsImage(h,d,a))},checkFileObj:function(b){var d={};"object"==typeof b?d=b:d.name=(b+"").split(/(\\|\/)/g).pop();d.type===j&&(d.type=d.name.split(".").pop());f(J,function(b,a){b.test(d.type)&&(d.type=a+"/"+d.type)});return d},getDropFiles:function(b, +d){var a=[],c=(b.originalEvent||b||"").dataTransfer||{},h=g(c.items)&&c.items[0]&&r(c.items[0]),e=k.queue(function(){d(a)});f((h?c.items:c.files)||[],function(b){e.inc();if(h)w(b,function(b,d){!b&&a.push.apply(a,d);e.next()});else{var d=function(d){d&&a.push(b);e.next()};if(!b.type&&0==b.size%4096&&102400>=b.size)if(u)try{var c=new u;l(c,H,function(b){b="error"!=b.type;d(b);b&&c.abort()});c.readAsDataURL(b)}catch(s){d(!1)}else d(null);else d(!0)}});e.check()},getFiles:function(b,a,c){var h=[];if(c)return k.filterFiles(k.getFiles(b), +a,c),null;b.jquery&&(b.each(function(){h=h.concat(k.getFiles(this))}),b=h,h=[]);"string"==typeof a&&(a=k.getFilesFilter(a));b.originalEvent?b=x(b.originalEvent):b.srcElement&&(b=x(b));b.dataTransfer?b=b.dataTransfer:b.target&&(b=b.target);b.files?h=b.files:!d&&G.test(b&&b.tagName)?k.trim(b.value)&&(h=[k.checkFileObj(b.value)],h[0].blob=b,h[0].iframe=!0):g(b)&&(h=b);!a&&G.test(b&&b.tagName)&&(a=k.getFilesFilter(b));return k.filter(h,function(b){return!a||a.test(b.name)})},getInfo:function(b,d){var a= +{},c=K.concat();k.isFile(b)?function C(){var h=c.shift();h?h.test(b.type)?h(b,function(b,c){b?d(b):(k.extend(a,c),C())}):C():d(!1,a)}():d("not_support",a)},addInfoReader:function(b,d){d.test=function(d){return b.test(d)};K.push(d)},addMime:function(b,d){J[b]=RegExp("("+d.replace(/,/g,"|")+")$","i")},filter:function(b,d){for(var a=[],c=0,h=b.length,g;c>2,c=(c&3)<<4|h>>4;isNaN(h)?h=g=64:(h=(h&15)<<2|g>>6,g=isNaN(g)?64:g&63);d+= +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(w)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(c)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(h)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(g)}return d}};f({image:"png,jpg,jpeg,bmp,gif,ico,tif,tiff,tga,pcx,cbz,cbr",audio:"m4a,flac,aac,rm,mpa,wav,wma,ogg,mp3,mp2,m3u,mod,amf,dmf,dsm,far,gdm,imf,it,m15,med,okt,s3m,stm,sfx,ult,uni,xm,sid,ac3,dts,cue,aif,aiff,wpl,ape,mac,mpc,mpp,shn,wv,nsf,spc,gym,adplug,adx,dsp,adp,ymf,ast,afc,hps,xsp", +video:"m4v,3gp,nsv,ts,ty,strm,rm,rmvb,m3u,ifo,mov,qt,divx,xvid,bivx,vob,nrg,img,iso,pva,wmv,asf,asx,ogm,m2v,avi,bin,dat,dvr-ms,mpg,mpeg,mp4,mkv,avc,vp3,svq3,nuv,viv,dv,fli,flv,wpl"},function(b,d){k.addMime(d,b)});k.addInfoReader(/^image/,function(b,d){if(!b.__dimensions){var a=b.__dimensions=k.defer();k.readAsImage(b,function(b){var d=b.target;a.resolve("load"==b.type?!1:"error",{width:d.width,height:d.height})})}b.__dimensions.then(d)});k.event.dnd=function(b,d,a){var c,h;a||(a=d,d=k.F);u?(n(b,"dragenter dragleave dragover", +function(b){for(var a=((b.originalEvent||b||"").dataTransfer||{}).types,g=a&&a.length;g--;)~a[g].indexOf("File")&&(b.preventDefault(),h!==b.type&&(h=b.type,"dragleave"!=h&&d.call(b.currentTarget,!0,b),clearTimeout(c),c=setTimeout(function(){d.call(b.currentTarget,"dragleave"!=h,b)},50)))}),n(b,"drop",function(b){b.preventDefault();h=0;d.call(b.currentTarget,!1,b);k.getDropFiles(b,function(d){a.call(b.currentTarget,d,b)})})):k.log("Drag'n'Drop -- not supported")};t&&!t.fn.dnd&&(t.fn.dnd=function(b, +d){return this.each(function(){k.event.dnd(this,b,d)})});a.FileAPI=k.extend(k,a.FileAPI)})(window); +(function(a,j,f){function n(a,c){if(!(this instanceof n))return new n(a);this.file=a;this.better=!c;this.matrix={sx:0,sy:0,sw:0,sh:0,dx:0,dy:0,dw:0,dh:0,resize:0,deg:0}}var p=Math.min,l=Math.round,c=!1,e={8:270,3:180,6:90};try{c=-1=j?(j=e,v=j/n):(v=c,j=v*n),j!=e||v!=c)g.sx=~~((e-j)/2),g.sy=~~((c-v)/2),e=j,c=v}else v&&("min"==v?(f=l(j=n?p(e,f):r*j),r=l(j>=n?f/j:p(c,r))));g.sw=e;g.sh=c;g.dw=f;g.dh=r;return g},_trans:function(a){this._load(this.file,function(c,e){c?a(c):this._apply(e,a)})},get:function(c){if(a.support.transform){var g=this;"auto"==g.matrix.deg?a.getInfo(this.file,function(a,f){g.matrix.deg= +e[f&&f.exif&&f.exif.Orientation]||0;g._trans(c)}):g._trans(c)}else c("not_support")},toData:function(a){this.get(a)}};n.transform=function(c,e,l,j){a.getInfo(c,function(r,p){var A={},v=a.queue(function(a){j(a,A)});r?v.fail():a.each(e,function(a,e){if(!v.isFail()){var g=n(p.nodeType?p:c);if("function"==typeof a)a(p,g);else if(a.width)g[a.preview?"preview":"resize"](a.width,a.height,a.type);else a.maxWidth&&(p.width>a.maxWidth||p.height>a.maxHeight)&&g.resize(a.maxWidth,a.maxHeight,"max");a.rotate=== +f&&l&&(a.rotate="auto");g.rotate(a.rotate);v.inc();g.toData(function(d,a){d?v.fail():(A[e]=a,v.next())})}})})};a.support.canvas=a.support.transform=c;a.Image=n})(FileAPI,document); +(function(a,j,f){var n=j.encodeURIComponent,p=j.FormData;j=function(){this.items=[]};j.prototype={append:function(a,c,e,f){this.items.push({name:a,blob:c&&c.blob||c,file:e||c.name,type:f||c.type})},each:function(a){for(var c=0,e=this.items.length;c',c.xhr.abort=function(){var a=e.getElementsByName("iframe")[0];if(a)try{a.stop?a.stop():a.contentWindow.stop?a.contentWindow.stop():a.contentWindow.document.execCommand("Stop")}catch(c){}e=null},n=e.getElementsByTagName("form")[0],n.appendChild(l), +j.log(n.parentNode.innerHTML),document.body.appendChild(e),c.xhr.node=e,a[r]=function(a,f,j){c.readyState=4;c.responseText=j;c.end(a,f);e=null},c.readyState=2,n.submit(),n=null):(e=c.xhr=j.getXHR(),e.open("POST",n,!0),e.withCredential="true",(!f.headers||!f.headers["X-Requested-With"])&&e.setRequestHeader("X-Requested-With","XMLHttpRequest"),j.each(f.headers,function(a,c){e.setRequestHeader(c,a)}),e.upload&&e.upload.addEventListener("progress",j.throttle(function(a){f.progress(a,c,f)},100),!1),e.onreadystatechange= +function(){c.status=e.status;c.statusText=e.statusText;c.readyState=e.readyState;if(4==e.readyState){for(var a in{"":1,XML:1,Text:1,Body:1})c["response"+a]=e["response"+a];e.onreadystatechange=null;c.end(e.status);e=null}},j.isArray(l)?(e.setRequestHeader("Content-Type","multipart/form-data; boundary=_"+j.expando),l=l.join("")+"--_"+j.expando+"--",e.sendAsBinary?e.sendAsBinary(l):(r=Array.prototype.map.call(l,function(a){return a.charCodeAt(0)&255}),e.send((new Uint8Array(r)).buffer))):e.send(l))}}; +j.XHR=n})(window,FileAPI); +(function(a,j,f){var n=a.support,p=j.navigator,l=p.mimeTypes,c=!1;if(p.plugins&&"object"==typeof p.plugins["Shockwave Flash"])c=p.plugins["Shockwave Flash"].description&&!(l&&l["application/x-shockwave-flash"]&&!l["application/x-shockwave-flash"].enabledPlugin);else try{c=!(!j.ActiveXObject||!new ActiveXObject("ShockwaveFlash.ShockwaveFlash"))}catch(e){}n.flash=c;if(a.support.flash&&(!a.support.html5||a.cors&&!a.support.cors)){var r=function(a){return('').replace(/#(\w+)#/ig,function(c,e){return a[e]})},u=function(a,c){if(a&&a.style){var e,f;for(e in c){f=c[e];"number"==typeof f&&(f+="px");try{a.style[e]=f}catch(g){}}}},h=function(d,c){a.each(c,function(a,c){var i=d[c];d[c]=function(){this.parent=i;return a.apply(this,arguments)}})},A=function(d){var c=d.wid=a.uid();l._fn[c]=d;return"FileAPI.Flash._fn."+c},v=function(a){try{l._fn[a.wid]=null,delete l._fn[a.wid]}catch(c){}}, -E=function(a){if(!t.test(a)){if(/^\.\//.test(a)||"/"!=a.charAt(0))var c=location.pathname,c=c.substr(0,c.lastIndexOf("/")),a=c+"/"+a.substr("./"==RegExp.$1?2:0);"//"!=a.substr(0,2)&&(a=location.hostname+"/"+a);t.test(a)||(a=location.protocol+"//"+a)}return a},p=a.uid(),y=0,w={},t=/^https?:/i,l={_fn:{},init:function(){var d=g.body&&g.body.firstChild;if(d){do if(1==d.nodeType){a.log("FlashAPI.Flash.inited");var c=g.createElement("div");u(c,{top:1,right:1,width:5,height:5,position:"absolute"});d.parentNode.insertBefore(c, -d);l.publish(c,p);return}while(d=d.nextSibling)}10>y&&setTimeout(l.init,50*++y)},publish:function(d,c){d.innerHTML=q({id:c,src:E(a.staticPath+"FileAPI.flash.swf?r="+a.build),wmode:"transparent",flashvars:"callback=FileAPI.Flash.event&flashId="+c+"&storeKey="+navigator.userAgent.match(/\d/ig).join("")+"_"+a.build+(l.isReady||(a.pingUrl?"&ping="+a.pingUrl:""))})},ready:function(){l.ready=a.F;l.isReady=!0;l.patch();a.event.on(g,"mouseover",l.mouseover);a.event.on(g,"click",function(a){l.mouseover(a)&& -(a.preventDefault?a.preventDefault():a.returnValue=!0)})},getWrapper:function(a){do if(/js-fileapi-wrapper/.test(a.className))return a;while((a=a.parentNode)&&a!==g.body)},mouseover:function(d){d=a.event.fix(d).target;if(/input/i.test(d.nodeName)&&"file"==d.type){var c=d.getAttribute(p);if("i"==c||"r"==c)return!1;if("p"!=c){d.setAttribute(p,"i");var c=g.createElement("div"),e=l.getWrapper(d);if(!e){a.log("flash.mouseover.error: js-fileapi-wrapper not found");return}u(c,{top:0,left:0,width:d.offsetWidth+ -100,height:d.offsetHeight+100,zIndex:"1000000",position:"absolute"});e.appendChild(c);l.publish(c,a.uid());d.setAttribute(p,"p")}return!0}},event:function(d){var c=d.type;if("ready"==c){try{l.getInput(d.flashId).setAttribute(p,"r")}catch(e){}l.ready();setTimeout(function(){l.mouseenter(d)},50);return!0}"ping"===c?a.log("(flash -> js).ping:",[d.status,d.savedStatus],d.error):"log"===c?a.log("(flash -> js).log:",d.target):c in l&&setTimeout(function(){a.log("Flash.event."+d.type+":",d);l[c](d)},1)}, -mouseenter:function(a){var c=l.getInput(a.flashId);c&&(l.cmd(a,"multiple",null!==c.getAttribute("multiple")),l.cmd(a,"accept",(c.getAttribute("accept")||"*").replace(/\./g,"")))},get:function(a){return g[a]||k[a]||g.embeds[a]},getInput:function(a){try{var c=l.getWrapper(l.get(a));if(c)return c.getElementsByTagName("input")[0]}catch(e){}},select:function(d){var c=l.getInput(d.flashId),e=a.uid(c),d=d.target.files;a.each(d,function(d){a.checkFileObj(d)});w[e]=d;g.createEvent?(e=g.createEvent("Event"), -e.initEvent("change",!0,!1),c.dispatchEvent(e)):g.createEventObject&&(e=g.createEventObject(),c.fireEvent("onchange",e))},cmd:function(d,c,e,f){try{return a.log("(js -> flash)."+c+":",e),l.get(d.flashId||d).cmd(c,e)}catch(g){a.log("(js -> flash).onError:",g),f||setTimeout(function(){l.cmd(d,c,e,!0)},50)}},patch:function(){a.flashEngine=a.support.transform=!0;h(a,{getFiles:function(d,c,e){if(e)return a.filterFiles(a.getFiles(d),c,e),null;var f=a.isArray(d)?d:w[a.uid(d.target||d.srcElement||d)];if(!f)return this.parent.apply(this, -arguments);c&&(c=a.getFilesFilter(c),f=a.filter(f,function(a){return c.test(a.name)}));return f},getInfo:function(a,c){if(a&&!a.flashId)this.parent.apply(this,arguments);else{if(!a.__info){var e=a.__info=FileAPI.defer();l.cmd(a,"getFileInfo",{id:a.id,callback:A(function D(c,i){v(D);e.resolve(c,a.info=i)})})}a.__info.then(c)}}});a.support.transform=!0;h(FileAPI.Image.prototype,{get:function(a,c){this.set({scaleMode:c||"noScale"});this.parent(a)},_load:function(d,c){a.log("FileAPI.Image._load:",d); -if(d&&!d.flashId)this.parent.apply(this,arguments);else{var e=this;a.getInfo(d,function(a){c.call(e,a,d)})}},_apply:function(d,c){a.log("FileAPI.Image._apply:",d);if(d&&!d.flashId)this.parent.apply(this,arguments);else{var e=this.getMatrix(d.info);l.cmd(d,"imageTransform",{id:d.id,matrix:e,callback:A(function D(f,h){a.log("FileAPI.Image._apply.callback:",f);v(D);if(f)c(f);else if(!a.support.dataURI||3E4').replace(/#(\w+)#/ig,function(c,e){return a[e]})},w=function(a,c){if(a&&a.style){var e,f;for(e in c){f=c[e];"number"==typeof f&&(f+="px");try{a.style[e]=f}catch(g){}}}},g=function(d,c){a.each(c,function(a,c){var h=d[c];d[c]=function(){this.parent=h;return a.apply(this,arguments)}})},x=function(d){var c=d.wid=a.uid();m._fn[c]=d;return"FileAPI.Flash._fn."+c},q=function(a){try{m._fn[a.wid]=null,delete m._fn[a.wid]}catch(c){}}, +F=function(a){if(!u.test(a)){if(/^\.\//.test(a)||"/"!=a.charAt(0)){var c=location.pathname,c=c.substr(0,c.lastIndexOf("/"));a=(c+"/"+a).replace("/./","/")}"//"!=a.substr(0,2)&&(a="//"+location.host+a);u.test(a)||(a=location.protocol+a)}return a},y=a.uid(),A=0,v={},u=/^https?:/i,m={_fn:{},init:function(){var d=f.body&&f.body.firstChild;if(d){do if(1==d.nodeType){a.log("FlashAPI.Flash.inited");var c=f.createElement("div");w(c,{top:1,right:1,width:5,height:5,position:"absolute"});d.parentNode.insertBefore(c, +d);m.publish(c,y);return}while(d=d.nextSibling)}10>A&&setTimeout(m.init,50*++A)},publish:function(d,c){d.innerHTML=r({id:c,src:F(a.staticPath+"FileAPI.flash.swf?r="+a.version),wmode:"transparent",flashvars:"callback=FileAPI.Flash.event&flashId="+c+"&storeKey="+navigator.userAgent.match(/\d/ig).join("")+"_"+a.version+(m.isReady||(a.pingUrl?"&ping="+a.pingUrl:""))})},ready:function(){m.ready=a.F;m.isReady=!0;m.patch();a.event.on(f,"mouseover",m.mouseover);a.event.on(f,"click",function(a){m.mouseover(a)&& +(a.preventDefault?a.preventDefault():a.returnValue=!0)})},getWrapper:function(a){do if(/js-fileapi-wrapper/.test(a.className))return a;while((a=a.parentNode)&&a!==f.body)},mouseover:function(d){d=a.event.fix(d).target;if(/input/i.test(d.nodeName)&&"file"==d.type){var c=d.getAttribute(y);if("i"==c||"r"==c)return!1;if("p"!=c){d.setAttribute(y,"i");var c=f.createElement("div"),e=m.getWrapper(d);if(!e){a.log("flash.mouseover.error: js-fileapi-wrapper not found");return}w(c,{top:0,left:0,width:d.offsetWidth+ +100,height:d.offsetHeight+100,zIndex:"1000000",position:"absolute"});e.appendChild(c);m.publish(c,a.uid());d.setAttribute(y,"p")}return!0}},event:function(d){var c=d.type;if("ready"==c){try{m.getInput(d.flashId).setAttribute(y,"r")}catch(e){}m.ready();setTimeout(function(){m.mouseenter(d)},50);return!0}"ping"===c?a.log("(flash -> js).ping:",[d.status,d.savedStatus],d.error):"log"===c?a.log("(flash -> js).log:",d.target):c in m&&setTimeout(function(){a.log("Flash.event."+d.type+":",d);m[c](d)},1)}, +mouseenter:function(a){var c=m.getInput(a.flashId);c&&(m.cmd(a,"multiple",null!==c.getAttribute("multiple")),m.cmd(a,"accept",(c.getAttribute("accept")||"*").replace(/\./g,"")))},get:function(a){return f[a]||j[a]||f.embeds[a]},getInput:function(a){try{var c=m.getWrapper(m.get(a));if(c)return c.getElementsByTagName("input")[0]}catch(e){}},select:function(d){var c=m.getInput(d.flashId),e=a.uid(c);d=d.target.files;a.each(d,function(d){a.checkFileObj(d)});v[e]=d;f.createEvent?(e=f.createEvent("Event"), +e.initEvent("change",!0,!1),c.dispatchEvent(e)):f.createEventObject&&(e=f.createEventObject(),c.fireEvent("onchange",e))},cmd:function(d,c,e,f){try{return a.log("(js -> flash)."+c+":",e),m.get(d.flashId||d).cmd(c,e)}catch(g){a.log("(js -> flash).onError:",g),f||setTimeout(function(){m.cmd(d,c,e,!0)},50)}},patch:function(){a.flashEngine=a.support.transform=!0;g(a,{getFiles:function(d,c,e){if(e)return a.filterFiles(a.getFiles(d),c,e),null;var f=a.isArray(d)?d:v[a.uid(d.target||d.srcElement||d)];if(!f)return this.parent.apply(this, +arguments);c&&(c=a.getFilesFilter(c),f=a.filter(f,function(a){return c.test(a.name)}));return f},getInfo:function(a,c){if(a&&!a.flashId)this.parent.apply(this,arguments);else{if(!a.__info){var e=a.__info=FileAPI.defer();m.cmd(a,"getFileInfo",{id:a.id,callback:x(function E(c,f){q(E);e.resolve(c,a.info=f)})})}a.__info.then(c)}}});a.support.transform=!0;g(FileAPI.Image.prototype,{get:function(a,c){this.set({scaleMode:c||"noScale"});this.parent(a)},_load:function(d,c){a.log("FileAPI.Image._load:",d); +if(d&&!d.flashId)this.parent.apply(this,arguments);else{var e=this;a.getInfo(d,function(a){c.call(e,a,d)})}},_apply:function(d,c){a.log("FileAPI.Image._apply:",d);if(d&&!d.flashId)this.parent.apply(this,arguments);else{var e=this.getMatrix(d.info);m.cmd(d,"imageTransform",{id:d.id,matrix:e,callback:x(function E(g,j){a.log("FileAPI.Image._apply.callback:",g);q(E);if(g)c(g);else if(!a.support.dataURI||3E4 ### Flash settings ```html - + ``` @@ -311,6 +311,10 @@ var xhr = FileAPI.upload({ maxHeight: 768 }, imageAutoOrientation: true, + prepare: function (file, options){ + // prepare options for current file + options.data.filename = file.name; + }, upload: function (xhr, options){ // start uploading }, diff --git a/index.html b/index.html new file mode 100644 index 00000000..c0fbf493 --- /dev/null +++ b/index.html @@ -0,0 +1,293 @@ + + + + + + FileAPI :: TEST + + + + + + + + + + + + + + + + + +
+ +
+
Upload one file
+ +
+ + , + +
+
Multiple
+ +
+ + or + +
+
jpg, jpeg & gif
+ +
+ +
+ + + + +
+
+ +
+ + + + + diff --git a/index.php b/index.php index 10d7f655..c85bed40 100644 --- a/index.php +++ b/index.php @@ -303,6 +303,10 @@ function onFiles(files){ } }, + prepare: function (file, options){ + options.data[FileAPI.uid()] = 1; + }, + beforeupload: function (){ FileAPI.log('beforeupload:', arguments); }, @@ -338,7 +342,7 @@ function onFiles(files){ ; }); - document.getElementById('Log').innerHTML += '
'+xhr.responseText+'
'; + document.getElementById('Log').innerHTML += '
'+xhr.responseText.substr(0, 200)+'
'; } }, diff --git a/lib/FileAPI.Flash.js b/lib/FileAPI.Flash.js index b0da2ea6..f8a867ea 100644 --- a/lib/FileAPI.Flash.js +++ b/lib/FileAPI.Flash.js @@ -77,12 +77,12 @@ publish: function (el, id){ el.innerHTML = _makeFlashHTML({ id: id - , src: _getUrl(api.staticPath + 'FileAPI.flash.swf?r=' + api.build) + , src: _getUrl(api.staticPath + 'FileAPI.flash.swf?r=' + api.version) // , src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1') , wmode: 'transparent' , flashvars: 'callback=FileAPI.Flash.event' + '&flashId='+ id - + '&storeKey='+ navigator.userAgent.match(/\d/ig).join('') +'_'+ api.build + + '&storeKey='+ navigator.userAgent.match(/\d/ig).join('') +'_'+ api.version + (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : '')) }); }, @@ -547,15 +547,15 @@ if( /^\.\//.test(url) || '/' != url.charAt(0) ){ var path = location.pathname; path = path.substr(0, path.lastIndexOf('/')); - url = path +'/'+ url.substr(RegExp.$1 == './' ? 2 : 0); + url = (path +'/'+ url).replace('/./', '/'); } if( '//' != url.substr(0, 2) ){ - url = location.hostname +'/'+ url; + url = '//' + location.host + url; } if( !_rhttp.test(url) ){ - url = location.protocol +'//'+ url; + url = location.protocol + url; } } @@ -571,9 +571,8 @@ } _css(el, opts); - api.log('flash.image.publish'); - el.innerHTML = _makeFlashHTML(ajs.extend({ + el.innerHTML = _makeFlashHTML(api.extend({ id: flashId , src: api.staticPath +'FileAPI.flash.image.swf?r='+ api.uid() , wmode: 'opaque' @@ -586,6 +585,7 @@ function _setImage(){ try { + // Get flash-object by id var img = flash.get(flashId); img.setImage(base64); } catch (e){} diff --git a/lib/FileAPI.core.js b/lib/FileAPI.core.js index d51a9df2..0eaf38dd 100644 --- a/lib/FileAPI.core.js +++ b/lib/FileAPI.core.js @@ -48,7 +48,7 @@ preventDefault = 'preventDefault', api = { - build: 4, + version: '1.0.0', cors: false, debug: false, @@ -61,12 +61,12 @@ GB: _pow(_KB, 3), TB: _pow(_KB, 4), - expando: 'fileapi' + (+new Date), + expando: 'fileapi' + (new Date).getTime(), uid: function (obj){ return obj ? (obj[api.expando] = obj[api.expando] || api.uid()) - : (api.expando + ++gid) + : (++gid, api.expando + gid) ; }, @@ -701,7 +701,8 @@ upload: function (options){ options = api.extend({ - beforeupload: api.F + prepare: api.F + , beforeupload: api.F , upload: api.F , fileupload: api.F , fileprogress: api.F @@ -732,7 +733,10 @@ }); // Array of files - proxyXHR.files = [].concat(dataArray); + proxyXHR.files = []; + _each(dataArray, function (data){ + proxyXHR.files.push(data.file); + }); // Set upload status props proxyXHR.total = total; @@ -745,69 +749,75 @@ // Upload by file (function _nextFile(){ - var data = dataArray.shift(), _this = this, _fileLoaded = false; - - // Set current upload file - proxyXHR.currentFile = data; + var + data = dataArray.shift() + , _this = this + , _file = data && data.file + , _fileLoaded = false + , _fileOptions = _simpleClone(options) + ; if( proxyXHR.statusText != 'abort' && data ){ - this._getFormData(options, data, function (form){ + // Set current upload file + proxyXHR.currentFile = _file; + + // Prepare file options + options.prepare(_file, _fileOptions); + + this._getFormData(_fileOptions, data, function (form){ if( !loaded ){ // emit "upload" event options.upload(proxyXHR, options); } - var - _file = data.file, - xhr = new api.XHR(api.extend({}, options, { + var xhr = new api.XHR(api.extend({}, _fileOptions, { - upload: function (){ - // emit "fileupload" event - options.fileupload(_file, xhr, options); - }, + upload: function (){ + // emit "fileupload" event + options.fileupload(_file, xhr, _fileOptions); + }, - progress: function (evt){ - if( !_fileLoaded && evt.lengthComputable ){ - loaded += ~~(total * _part * (evt.loaded/evt.total) - _loaded + .5); - _loaded = loaded; + progress: function (evt){ + if( !_fileLoaded && evt.lengthComputable ){ + loaded += ~~(total * _part * (evt.loaded/evt.total) - _loaded + .5); + _loaded = loaded; - data.total = evt.total; - data.loaded = evt.loaded; + data.total = evt.total; + data.loaded = evt.loaded; - // emit "fileprogress" event - options.fileprogress(evt, _file, xhr, options); + // emit "fileprogress" event + options.fileprogress(evt, _file, xhr, _fileOptions); - // emit "progress" event - options.progress({ - type: evt.type - , total: total - , loaded: proxyXHR.loaded = loaded - , lengthComputable: true - }, _file, xhr, options); - } - }, + // emit "progress" event + options.progress({ + type: evt.type + , total: total + , loaded: proxyXHR.loaded = loaded + , lengthComputable: true + }, _file, xhr, _fileOptions); + } + }, - complete: function (err){ - _each(_xhrPropsExport, function (name){ - proxyXHR[name] = xhr[name]; - }); + complete: function (err){ + _each(_xhrPropsExport, function (name){ + proxyXHR[name] = xhr[name]; + }); - // fixed throttle event - _fileLoaded = true; + // fixed throttle event + _fileLoaded = true; - data.loaded = data.total; + data.loaded = data.total; - // bytes loaded - proxyXHR.loaded = (loaded += (loaded - _loaded) + ~~(total*_part + .5)); + // bytes loaded + proxyXHR.loaded = (loaded += (loaded - _loaded) + ~~(total*_part + .5)); - // emit "filecomplete" event - options.filecomplete(err, xhr, _file, options); + // emit "filecomplete" event + options.filecomplete(err, xhr, _file, _fileOptions); - // upload next file - _nextFile.call(_this); - } - })) // xhr - ; + // upload next file + _nextFile.call(_this); + } + })); // xhr // share of file size from the total size @@ -1266,11 +1276,24 @@ } } + function isArray(ar) { return (typeof ar == 'object') && ar && ('length' in ar); } + function _simpleClone(obj){ + var copy = {}; + _each(obj, function (val, key){ + if( val && (typeof val === 'object') ){ + val = api.extend({}, val); + } + copy[key] = val; + }); + return copy; + } + + function isInputFile(el){ return _rinput.test(el && el.tagName); } From 2f87cac50e5d693f7ef6493c26ed477ed9b68e88 Mon Sep 17 00:00:00 2001 From: Ilya Lebedev Date: Thu, 20 Dec 2012 18:51:14 +0400 Subject: [PATCH 03/12] added resumable upload --- lib/FileAPI.Form.js | 26 ++++++++--- lib/FileAPI.XHR.js | 103 +++++++++++++++++++++++++++++--------------- lib/FileAPI.core.js | 6 ++- 3 files changed, 95 insertions(+), 40 deletions(-) diff --git a/lib/FileAPI.Form.js b/lib/FileAPI.Form.js index 0b37d4b1..bab6ec69 100644 --- a/lib/FileAPI.Form.js +++ b/lib/FileAPI.Form.js @@ -26,15 +26,19 @@ } }, - toData: function (fn){ + toData: function (fn, options){ if( !api.support.html5 ){ api.log('tFileAPI.Form.toHtmlData'); this.toHtmlData(fn); } - else if( this.multipart ){ - api.log('FileAPI.Form.toMultipartData'); - this.toMultipartData(fn); - } + else if( this.multipart ){ + api.log('FileAPI.Form.toMultipartData'); + this.toMultipartData(fn); + } + else if( options.resumable && api.support.resumable ){ + api.log('FileAPI.Form.toMultipartData'); + this.toPlainData(fn); + } else { api.log('FileAPI.Form.toFormData'); this.toFormData(fn); @@ -74,6 +78,18 @@ }); }, + toPlainData: function (fn){ + this._to({}, fn, function (file, data, queue){ + if( file.file ){ + data.type = file.file; + } + debugger + data.name = file.blob.name; + data.file = file.blob; + data.size = file.blob.size; + data.start = 0; + }); + }, toFormData: function (fn){ this._to(new FormData, fn, function (file, data, queue){ diff --git a/lib/FileAPI.XHR.js b/lib/FileAPI.XHR.js index 18f04271..ffbbfc7c 100644 --- a/lib/FileAPI.XHR.js +++ b/lib/FileAPI.XHR.js @@ -65,7 +65,7 @@ // Start uploading options.upload(options, _this); _this._send.call(_this, options, data); - }); + }, options); }, _send: function (options, data){ @@ -77,6 +77,7 @@ url += (~url.indexOf('?') ? '&' : '?') + api.uid(); if( data.nodeName ){ + // legacy options.upload(options, _this); xhr = document.createElement('div'); @@ -125,6 +126,7 @@ form = null; } else { + // html5 xhr = _this.xhr = api.getXHR(); xhr.open('POST', url, true); @@ -144,39 +146,72 @@ options.progress(evt, _this, options); }, 100), false); } - - xhr.onreadystatechange = function (){ - _this.status = xhr.status; - _this.statusText = xhr.statusText; - _this.readyState = xhr.readyState; - - if( xhr.readyState == 4 ){ - for( var k in { '': 1, XML: 1, Text: 1, Body: 1 } ){ - _this['response'+k] = xhr['response'+k]; - } - xhr.onreadystatechange = null; - _this.end(xhr.status); - xhr = null; - } - }; - - if( api.isArray(data) ){ - // multipart - xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando); - data = data.join('') +'--_'+ api.expando +'--'; - - /** @namespace xhr.sendAsBinary https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */ - if( xhr.sendAsBinary ){ - xhr.sendAsBinary(data); - } - else { - var bytes = Array.prototype.map.call(data, function(c){ return c.charCodeAt(0) & 0xff; }); - xhr.send(new Uint8Array(bytes).buffer); - - } - } - else { - xhr.send(data); + + if (options.resumable && api.support.resumable) { + + xhr.onreadystatechange = function (){ + _this.status = xhr.status; + _this.statusText = xhr.statusText; + _this.readyState = xhr.readyState; + + if( xhr.readyState == 4 ){ + for( var k in { '': 1, XML: 1, Text: 1, Body: 1 } ){ + _this['response'+k] = xhr['response'+k]; + } + xhr.onreadystatechange = null; + + if (xhr.status != 201) { + + } + _this.end(xhr.status); + xhr = null; + } + }; + + xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.start + 500 * api.KB + "/" + data.size); + xhr.setRequestHeader("Content-Disposition", 'attachment; file-name=' + data.name); + + + var slice; + (slice = 'slice') in data.file || (slice = 'mozSlice') in data.file || (slice = 'webkitSlice') in data.file; + + slice = data.file[slice](data.start, data.start + 500 * api.KB ); + + xhr.send(slice); + } else { + xhr.onreadystatechange = function (){ + _this.status = xhr.status; + _this.statusText = xhr.statusText; + _this.readyState = xhr.readyState; + + if( xhr.readyState == 4 ){ + for( var k in { '': 1, XML: 1, Text: 1, Body: 1 } ){ + _this['response'+k] = xhr['response'+k]; + } + xhr.onreadystatechange = null; + _this.end(xhr.status); + xhr = null; + } + }; + + if( api.isArray(data) ){ + // multipart + xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando); + data = data.join('') +'--_'+ api.expando +'--'; + + /** @namespace xhr.sendAsBinary https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */ + if( xhr.sendAsBinary ){ + xhr.sendAsBinary(data); + } + else { + var bytes = Array.prototype.map.call(data, function(c){ return c.charCodeAt(0) & 0xff; }); + xhr.send(new Uint8Array(bytes).buffer); + + } + } else { + // FormData + xhr.send(data); + } } } } diff --git a/lib/FileAPI.core.js b/lib/FileAPI.core.js index 0eaf38dd..ae734df8 100644 --- a/lib/FileAPI.core.js +++ b/lib/FileAPI.core.js @@ -1,5 +1,6 @@ /*global URL, webkitURL, dataURLtoBlob*/ + (function (window, undef){ 'use strict'; @@ -20,6 +21,8 @@ && !(/safari\//.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25 cors = html5 && ('withCredentials' in (new XMLHttpRequest)), + + resumable = html5 && !!Blob && !!(Blob.prototype.webkitSlice||Blob.prototype.mozSlice||Blob.prototype.slice), document = window.document, @@ -99,7 +102,8 @@ support: { dnd: cors && ('ondrop' in document.createElement('div')), cors: cors, - html5: html5, + html5: html5, + resumable: resumable, dataURI: true }, From 4f5de7ed73b0c4c22be3dc5b4b89bcf80296c590 Mon Sep 17 00:00:00 2001 From: Ilya Lebedev Date: Thu, 20 Dec 2012 18:54:56 +0400 Subject: [PATCH 04/12] added resumable upload --- lib/FileAPI.Form.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/FileAPI.Form.js b/lib/FileAPI.Form.js index bab6ec69..c87ff281 100644 --- a/lib/FileAPI.Form.js +++ b/lib/FileAPI.Form.js @@ -83,7 +83,6 @@ if( file.file ){ data.type = file.file; } - debugger data.name = file.blob.name; data.file = file.blob; data.size = file.blob.size; From 8c589750affba123a842da9707f5d637fba82997 Mon Sep 17 00:00:00 2001 From: Ilya Lebedev Date: Fri, 21 Dec 2012 15:11:53 +0400 Subject: [PATCH 05/12] fixed chunked upload --- lib/FileAPI.Form.js | 2 ++ lib/FileAPI.XHR.js | 60 ++++++++++++++++++++++++++++++++++----------- lib/FileAPI.core.js | 3 +++ 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/lib/FileAPI.Form.js b/lib/FileAPI.Form.js index c87ff281..b54e3a7f 100644 --- a/lib/FileAPI.Form.js +++ b/lib/FileAPI.Form.js @@ -87,6 +87,8 @@ data.file = file.blob; data.size = file.blob.size; data.start = 0; + data.end = 0; + data.retry = 0; }); }, diff --git a/lib/FileAPI.XHR.js b/lib/FileAPI.XHR.js index ffbbfc7c..bf8c074c 100644 --- a/lib/FileAPI.XHR.js +++ b/lib/FileAPI.XHR.js @@ -140,15 +140,20 @@ xhr.setRequestHeader(key, val); }); - if( xhr.upload ){ - // https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29 - xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){ - options.progress(evt, _this, options); - }, 100), false); - } if (options.resumable && api.support.resumable) { - + // resumable upload + if( xhr.upload ){ + // https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29 + xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){ + var e = jQuery.extend({}, evt, + { loaded : data.start + evt.loaded, + totalSize : data.size, + total : data.size}); + options.progress(e, _this, options); + }, 100), false); + } + xhr.onreadystatechange = function (){ _this.status = xhr.status; _this.statusText = xhr.statusText; @@ -160,25 +165,52 @@ } xhr.onreadystatechange = null; - if (xhr.status != 201) { - + if (xhr.status - 201 > 0) { + // some kind of error + if (++data.retry > api.resumableRetryCount) { + // no mo retries + _this.end(xhr.status); + } else { + // let's try again the same chunk + data.end = data.start + _this._send(options, data); + } + } else { + // success + if (data.end == data.size - 1) { + // finished + _this.end(xhr.status); + } else { + // next chunk + _this._send(options, data); + } } - _this.end(xhr.status); xhr = null; } }; - xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.start + 500 * api.KB + "/" + data.size); - xhr.setRequestHeader("Content-Disposition", 'attachment; file-name=' + data.name); - + data.start = data.end; + data.end = Math.min(data.end + api.resumableChunk, data.size -1 ); var slice; (slice = 'slice') in data.file || (slice = 'mozSlice') in data.file || (slice = 'webkitSlice') in data.file; - slice = data.file[slice](data.start, data.start + 500 * api.KB ); + xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size); + xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + data.name); + + slice = data.file[slice](data.start, data.end); xhr.send(slice); + slice = null; } else { + // single piece upload + if( xhr.upload ){ + // https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29 + xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){ + options.progress(evt, _this, options); + }, 100), false); + } + xhr.onreadystatechange = function (){ _this.status = xhr.status; _this.statusText = xhr.statusText; diff --git a/lib/FileAPI.core.js b/lib/FileAPI.core.js index ae734df8..72bc7152 100644 --- a/lib/FileAPI.core.js +++ b/lib/FileAPI.core.js @@ -58,6 +58,9 @@ pingUrl: false, staticPath: './', + + resumableChunk : 500 * _KB, + resumableRetryCount : 3, KB: _KB, MB: _pow(_KB, 2), From 5a0d440f7f37d09e7dda3ff8e201794dc2db823c Mon Sep 17 00:00:00 2001 From: Ilya Lebedev Date: Fri, 21 Dec 2012 16:37:52 +0400 Subject: [PATCH 06/12] fixed chunked upload --- lib/FileAPI.XHR.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/FileAPI.XHR.js b/lib/FileAPI.XHR.js index bf8c074c..c97cfd1c 100644 --- a/lib/FileAPI.XHR.js +++ b/lib/FileAPI.XHR.js @@ -190,7 +190,7 @@ }; data.start = data.end; - data.end = Math.min(data.end + api.resumableChunk, data.size -1 ); + data.end = Math.min(data.end + api.resumableChunk, data.size ) - 1; var slice; (slice = 'slice') in data.file || (slice = 'mozSlice') in data.file || (slice = 'webkitSlice') in data.file; @@ -198,7 +198,7 @@ xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size); xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + data.name); - slice = data.file[slice](data.start, data.end); + slice = data.file[slice](data.start, data.end + 1); xhr.send(slice); slice = null; From c39b8de9d25bd17bf323280062eb2f53880312b4 Mon Sep 17 00:00:00 2001 From: Ilya Lebedev Date: Fri, 21 Dec 2012 17:07:49 +0400 Subject: [PATCH 07/12] added support for 500 and 416 resumable error codes --- lib/FileAPI.XHR.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/FileAPI.XHR.js b/lib/FileAPI.XHR.js index c97cfd1c..f4c7dff2 100644 --- a/lib/FileAPI.XHR.js +++ b/lib/FileAPI.XHR.js @@ -167,13 +167,14 @@ if (xhr.status - 201 > 0) { // some kind of error - if (++data.retry > api.resumableRetryCount) { - // no mo retries - _this.end(xhr.status); - } else { + if (++data.retry <= api.resumableRetryCount && (500 == xhr.status || 416 == xhr.status)) { // let's try again the same chunk + // only applicable for recoverable error codes 500 && 416 data.end = data.start _this._send(options, data); + } else { + // no mo retries + _this.end(xhr.status); } } else { // success From 8224096818f2d36b435b8ab8d07a23bace7f8256 Mon Sep 17 00:00:00 2001 From: Ilya Lebedev Date: Mon, 24 Dec 2012 18:47:57 +0400 Subject: [PATCH 08/12] fixed retry counter reset on a success chunk --- lib/FileAPI.XHR.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/FileAPI.XHR.js b/lib/FileAPI.XHR.js index f4c7dff2..25c93505 100644 --- a/lib/FileAPI.XHR.js +++ b/lib/FileAPI.XHR.js @@ -178,6 +178,8 @@ } } else { // success + data.retry = 0; + if (data.end == data.size - 1) { // finished _this.end(xhr.status); From a7ef31067462b4b4e6e87aaa45fce059750db8aa Mon Sep 17 00:00:00 2001 From: Ilya Lebedev Date: Thu, 31 Jan 2013 17:10:17 +0400 Subject: [PATCH 09/12] changed call from jquery to api method --- lib/FileAPI.XHR.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FileAPI.XHR.js b/lib/FileAPI.XHR.js index 9e407862..7f2c12dc 100644 --- a/lib/FileAPI.XHR.js +++ b/lib/FileAPI.XHR.js @@ -146,7 +146,7 @@ if( xhr.upload ){ // https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29 xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){ - var e = jQuery.extend({}, evt, { + var e = api.extend({}, evt, { loaded : data.start + evt.loaded, totalSize : data.size, total : data.size}); From 92f4053a0f0f6c7e38ca06b116433d14f6d59e74 Mon Sep 17 00:00:00 2001 From: Ilya Lebedev Date: Tue, 5 Feb 2013 16:05:21 +0400 Subject: [PATCH 10/12] added more config options for resumable upload --- lib/FileAPI.XHR.js | 4 ++-- lib/FileAPI.core.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/FileAPI.XHR.js b/lib/FileAPI.XHR.js index 7f2c12dc..c1fec341 100644 --- a/lib/FileAPI.XHR.js +++ b/lib/FileAPI.XHR.js @@ -167,7 +167,7 @@ if (xhr.status - 201 > 0) { // some kind of error - if (++data.retry <= api.resumableRetryCount && (500 == xhr.status || 416 == xhr.status)) { + if (++data.retry <= options.retryCount && (500 == xhr.status || 416 == xhr.status)) { // let's try again the same chunk // only applicable for recoverable error codes 500 && 416 data.end = data.start @@ -193,7 +193,7 @@ }; data.start = data.end; - data.end = Math.min(data.end + api.resumableChunk, data.size ) - 1; + data.end = Math.min(data.end + options.chunkSize, data.size ) - 1; var slice; (slice = 'slice') in data.file || (slice = 'mozSlice') in data.file || (slice = 'webkitSlice') in data.file; diff --git a/lib/FileAPI.core.js b/lib/FileAPI.core.js index 0a7d4b1e..5f5ed4b0 100644 --- a/lib/FileAPI.core.js +++ b/lib/FileAPI.core.js @@ -718,9 +718,11 @@ , filecomplete: api.F , progress: api.F , complete: api.F + , resumable: false + , chunkSize: api.resumableChunkSize + , retryCount: api.resumableRetryCount }, options); - if( options.imageAutoOrientation && !options.imageTransform ){ options.imageTransform = { rotate: 'auto' }; } From de93a9d6c2fc55b760332eae761191af91b8a0c1 Mon Sep 17 00:00:00 2001 From: Ilya Lebedev Date: Thu, 7 Feb 2013 16:34:46 +0400 Subject: [PATCH 11/12] resumable changed to chunked, added some api shugar --- lib/FileAPI.Form.js | 4 ++-- lib/FileAPI.XHR.js | 4 ++-- lib/FileAPI.core.js | 42 ++++++++++++++++++++++++++++++------------ 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/lib/FileAPI.Form.js b/lib/FileAPI.Form.js index 222922f6..050ce3b7 100644 --- a/lib/FileAPI.Form.js +++ b/lib/FileAPI.Form.js @@ -26,7 +26,7 @@ } }, - toData: function (fn){ + toData: function (fn, options){ if( !api.support.html5 ){ api.log('FileAPI.Form.toHtmlData'); this.toHtmlData(fn); @@ -35,7 +35,7 @@ api.log('FileAPI.Form.toMultipartData'); this.toMultipartData(fn); } - else if( options.resumable && api.support.resumable ){ + else if( api.support.chunked && options.chunkSize > 0 ){ api.log('FileAPI.Form.toMultipartData'); this.toPlainData(fn); } diff --git a/lib/FileAPI.XHR.js b/lib/FileAPI.XHR.js index c1fec341..1dd17224 100644 --- a/lib/FileAPI.XHR.js +++ b/lib/FileAPI.XHR.js @@ -141,7 +141,7 @@ }); - if (options.resumable && api.support.resumable) { + if (api.support.chunked && options.chunkSize > 0) { // resumable upload if( xhr.upload ){ // https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29 @@ -167,7 +167,7 @@ if (xhr.status - 201 > 0) { // some kind of error - if (++data.retry <= options.retryCount && (500 == xhr.status || 416 == xhr.status)) { + if (++data.retry <= options.chunkUploadRetry && (500 == xhr.status || 416 == xhr.status)) { // let's try again the same chunk // only applicable for recoverable error codes 500 && 416 data.end = data.start diff --git a/lib/FileAPI.core.js b/lib/FileAPI.core.js index 5f5ed4b0..3d3a0f94 100644 --- a/lib/FileAPI.core.js +++ b/lib/FileAPI.core.js @@ -21,7 +21,7 @@ cors = html5 && ('withCredentials' in (new XMLHttpRequest)), - resumable = html5 && !!Blob && !!(Blob.prototype.webkitSlice||Blob.prototype.mozSlice||Blob.prototype.slice), + chunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice||Blob.prototype.mozSlice||Blob.prototype.slice), document = window.document, @@ -37,8 +37,14 @@ _rinput = /input/i, _rdata = /^data:[^,]+,/, - _KB = 1024, _pow = Math.pow, + _from = function (sz) { + return Math.round(sz * this); + }, + _KB = new Number(1024), + _MB = new Number(_pow(_KB, 2)), + _GB = new Number(_pow(_KB, 3)), + _TB = new Number(_pow(_KB, 4)), _elEvents = {}, // element event listeners _infoReader = [], // list of file info processors @@ -61,13 +67,26 @@ flashUrl: 0, // @default: './FileAPI.flash.swf' flashImageUrl: 0, // @default: './FileAPI.flash.image.swf' - resumableChunk : 500 * _KB, - resumableRetryCount : 3, + chunkSize : 0, + chunkUploadRetry : 0, - KB: _KB, - MB: _pow(_KB, 2), - GB: _pow(_KB, 3), - TB: _pow(_KB, 4), + KB: (_KB.from = _from, _KB), + MB: (_MB.from = _from, _MB), + GB: (_GB.from = _from, _GB), + TB: (_TB.from = _from, _TB), + + toKB : function (sz) { + return Math.round(sz * this.KB); + }, + toMB : function (sz) { + return Math.round(sz * this.MB); + }, + toGB : function (sz) { + return Math.round(sz * this.GB); + }, + toTB : function (sz) { + return Math.round(sz * this.TB); + }, expando: 'fileapi' + (new Date).getTime(), @@ -108,7 +127,7 @@ dnd: cors && ('ondrop' in document.createElement('div')), cors: cors, html5: html5, - resumable: resumable, + chunked: chunked, dataURI: true }, @@ -718,9 +737,8 @@ , filecomplete: api.F , progress: api.F , complete: api.F - , resumable: false - , chunkSize: api.resumableChunkSize - , retryCount: api.resumableRetryCount + , chunkSize: api.chunkSize + , chunkUpoloadRetry: api.chunkUploadRetry }, options); if( options.imageAutoOrientation && !options.imageTransform ){ From 167ae7c1f0718ac84b10c0b5e5d0508e84c5b038 Mon Sep 17 00:00:00 2001 From: Ilya Lebedev Date: Thu, 7 Feb 2013 18:13:24 +0400 Subject: [PATCH 12/12] code cleanup --- lib/FileAPI.core.js | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/lib/FileAPI.core.js b/lib/FileAPI.core.js index 3d3a0f94..7278d290 100644 --- a/lib/FileAPI.core.js +++ b/lib/FileAPI.core.js @@ -38,13 +38,15 @@ _rdata = /^data:[^,]+,/, _pow = Math.pow, + _round = Math.round, + _num = Number, _from = function (sz) { - return Math.round(sz * this); + return _round(sz * this); }, - _KB = new Number(1024), - _MB = new Number(_pow(_KB, 2)), - _GB = new Number(_pow(_KB, 3)), - _TB = new Number(_pow(_KB, 4)), + _KB = new _num(1024), + _MB = new _num(_pow(_KB, 2)), + _GB = new _num(_pow(_KB, 3)), + _TB = new _num(_pow(_KB, 4)), _elEvents = {}, // element event listeners _infoReader = [], // list of file info processors @@ -75,19 +77,6 @@ GB: (_GB.from = _from, _GB), TB: (_TB.from = _from, _TB), - toKB : function (sz) { - return Math.round(sz * this.KB); - }, - toMB : function (sz) { - return Math.round(sz * this.MB); - }, - toGB : function (sz) { - return Math.round(sz * this.GB); - }, - toTB : function (sz) { - return Math.round(sz * this.TB); - }, - expando: 'fileapi' + (new Date).getTime(), uid: function (obj){