PageRenderTime 33ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/plupload/src/flash/plupload/src/com/plupload/File.as

http://buddypress-media.googlecode.com/
ActionScript | 421 lines | 267 code | 73 blank | 81 comment | 35 complexity | 052a750e0b4d3065ed4baef2a437f23b MD5 | raw file
Possible License(s): AGPL-1.0, Apache-2.0, GPL-2.0, LGPL-2.1
  1. /**
  2. * File.as
  3. *
  4. * Copyright 2009, Moxiecode Systems AB
  5. * Released under GPL License.
  6. *
  7. * License: http://www.plupload.com/license
  8. * Contributing: http://www.plupload.com/contributing
  9. */
  10. package com.plupload {
  11. import com.formatlos.BitmapDataUnlimited;
  12. import com.formatlos.events.BitmapDataUnlimitedEvent;
  13. import flash.display.Bitmap;
  14. import flash.display.BitmapData;
  15. import flash.display.IBitmapDrawable;
  16. import flash.events.EventDispatcher;
  17. import flash.geom.Matrix;
  18. import flash.net.FileReference;
  19. import flash.events.Event;
  20. import flash.events.IOErrorEvent;
  21. import flash.events.HTTPStatusEvent;
  22. import flash.events.ProgressEvent;
  23. import flash.events.SecurityErrorEvent;
  24. import flash.events.DataEvent;
  25. import flash.net.FileReferenceList;
  26. import flash.net.URLLoader;
  27. import flash.net.URLRequest;
  28. import flash.net.URLRequestHeader;
  29. import flash.net.URLRequestMethod;
  30. import flash.net.URLStream;
  31. import flash.net.URLVariables;
  32. import flash.utils.ByteArray;
  33. import flash.external.ExternalInterface;
  34. import com.mxi.image.Image;
  35. import com.mxi.image.events.ImageEvent;
  36. /**
  37. * Container class for file references, this handles upload logic for individual files.
  38. */
  39. public class File extends EventDispatcher {
  40. // Private fields
  41. private var _fileRef:FileReference, _cancelled:Boolean;
  42. private var _uploadUrl:String, _uploadPath:String, _mimeType:String;
  43. private var _id:String, _fileName:String, _size:Number, _imageData:ByteArray;
  44. private var _multipart:Boolean, _fileDataName:String, _chunking:Boolean, _chunk:int, _chunks:int, _chunkSize:int, _postvars:Object;
  45. private var _headers:Object, _settings:Object;
  46. /**
  47. * Id property of file.
  48. */
  49. public function get id():String {
  50. return this._id;
  51. }
  52. /**
  53. * File name for the file.
  54. */
  55. public function get fileName():String {
  56. return this._fileName;
  57. }
  58. /**
  59. * File name for the file.
  60. */
  61. public function set fileName(value:String):void {
  62. this._fileName = value;
  63. }
  64. /**
  65. * File size property.
  66. */
  67. public function get size():Number {
  68. return this._size;
  69. }
  70. /**
  71. * Constructs a new file object.
  72. *
  73. * @param id Unique indentifier for the file.
  74. * @param file_ref File reference for the selected file.
  75. */
  76. public function File(id:String, file_ref:FileReference) {
  77. this._id = id;
  78. this._fileRef = file_ref;
  79. this._size = file_ref.size;
  80. this._fileName = file_ref.name;
  81. }
  82. /**
  83. * Uploads a the file to the specified url. This method will upload it as a normal
  84. * multipart file upload if the file size is smaller than the chunk size. But if the file is to
  85. * large it will be chunked into multiple requests.
  86. *
  87. * @param url Url to upload the file to.
  88. * @param settings Settings object.
  89. */
  90. public function upload(url:String, settings:Object):void {
  91. this._settings = settings;
  92. if (this.canUseSimpleUpload(settings)) {
  93. this.simpleUpload(url, settings);
  94. } else {
  95. this.advancedUpload(url, settings);
  96. }
  97. }
  98. // Private methods
  99. public function canUseSimpleUpload(settings:Object):Boolean {
  100. var multipart:Boolean = new Boolean(settings["multipart"]);
  101. var resize:Boolean = (settings["width"] || settings["height"]);
  102. var chunking:Boolean = (settings["chunk_size"] > 0);
  103. // Check if it's not an image, chunking is disabled, multipart enabled and the ref_upload setting isn't forced
  104. return (!(/\.(jpeg|jpg|png)$/i.test(this._fileName)) || !resize) && multipart && !chunking && !settings.urlstream_upload;
  105. }
  106. public function simpleUpload(url:String, settings:Object):void {
  107. var file:File = this, request:URLRequest, postData:URLVariables, fileDataName:String;
  108. file._postvars = settings["multipart_params"];
  109. file._chunk = 0;
  110. file._chunks = 1;
  111. postData = new URLVariables();
  112. file._postvars["name"] = settings["name"];
  113. for (var key:String in file._postvars) {
  114. if (key != 'Filename') { // Flash will add it by itself, so we need to omit potential duplicate
  115. postData[key] = file._postvars[key];
  116. }
  117. }
  118. request = new URLRequest();
  119. request.method = URLRequestMethod.POST;
  120. request.url = url;
  121. request.data = postData;
  122. fileDataName = new String(settings["file_data_name"]);
  123. file._fileRef.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, function(e:DataEvent):void {
  124. var pe:ProgressEvent = new ProgressEvent(ProgressEvent.PROGRESS, false, false, file._size, file._size);
  125. dispatchEvent(pe);
  126. // Fake UPLOAD_COMPLETE_DATA event
  127. var uploadChunkEvt:UploadChunkEvent = new UploadChunkEvent(
  128. UploadChunkEvent.UPLOAD_CHUNK_COMPLETE_DATA,
  129. false,
  130. false,
  131. e.data,
  132. file._chunk,
  133. file._chunks
  134. );
  135. file._chunk++;
  136. dispatchEvent(uploadChunkEvt);
  137. dispatchEvent(e);
  138. });
  139. // Delegate upload IO errors
  140. file._fileRef.addEventListener(IOErrorEvent.IO_ERROR, function(e:IOErrorEvent):void {
  141. dispatchEvent(e);
  142. });
  143. // Delegate secuirty errors
  144. file._fileRef.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function(e:SecurityErrorEvent):void {
  145. dispatchEvent(e);
  146. });
  147. // Delegate progress
  148. file._fileRef.addEventListener(ProgressEvent.PROGRESS, function(e:ProgressEvent):void {
  149. dispatchEvent(e);
  150. });
  151. file._fileRef.upload(request, fileDataName, false);
  152. }
  153. public function advancedUpload(url:String, settings:Object):void {
  154. var file:File = this, width:int, height:int, quality:int, multipart:Boolean, chunking:Boolean, fileDataName:String;
  155. var chunk:int, chunks:int, chunkSize:int, postvars:Object;
  156. // Setup internal vars
  157. this._uploadUrl = url;
  158. this._cancelled = false;
  159. this._headers = settings.headers;
  160. this._mimeType = settings.mime;
  161. // Handle image resizing settings
  162. if (settings["width"] || settings["height"]) {
  163. width = settings["width"];
  164. height = settings["height"];
  165. quality = settings["quality"];
  166. }
  167. multipart = new Boolean(settings["multipart"]);
  168. fileDataName = new String(settings["file_data_name"]);
  169. chunkSize = settings["chunk_size"];
  170. chunking = chunkSize > 0;
  171. postvars = settings["multipart_params"];
  172. chunk = 0;
  173. // When file is loaded start uploading
  174. this._fileRef.addEventListener(Event.COMPLETE, function(e:Event):void {
  175. var startUpload:Function = function() : void
  176. {
  177. if (chunking) {
  178. chunks = Math.ceil(file._size / chunkSize);
  179. // Force at least 4 chunks to fake progress. We need to fake this since the URLLoader
  180. // doesn't have a upload progress event and we can't use FileReference.upload since it
  181. // doesn't support cookies, breaks on HTTPS and doesn't support custom data so client
  182. // side image resizing will not be possible.
  183. if (chunks < 4 && file._size > 1024 * 32) {
  184. chunkSize = Math.ceil(file._size / 4);
  185. chunks = 4;
  186. }
  187. } else {
  188. // If chunking is disabled then upload file in one huge chunk
  189. chunkSize = file._size;
  190. chunks = 1;
  191. }
  192. // Start uploading the scaled down image
  193. file._multipart = multipart;
  194. file._fileDataName = fileDataName;
  195. file._chunking = chunking;
  196. file._chunk = chunk;
  197. file._chunks = chunks;
  198. file._chunkSize = chunkSize;
  199. file._postvars = postvars;
  200. file.uploadNextChunk();
  201. }
  202. if (/\.(jpeg|jpg|png)$/i.test(file._fileName) && (width || height)) {
  203. var image:Image = new Image(file._fileRef.data);
  204. image.addEventListener(ImageEvent.COMPLETE, function(e:ImageEvent) : void
  205. {
  206. if (image.imageData) {
  207. file._imageData = image.imageData;
  208. file._imageData.position = 0;
  209. file._size = image.imageData.length;
  210. }
  211. startUpload();
  212. });
  213. image.addEventListener(ImageEvent.ERROR, function(e:ImageEvent) : void
  214. {
  215. file.dispatchEvent(e);
  216. });
  217. image.scale(width, height, quality);
  218. } else {
  219. startUpload();
  220. }
  221. });
  222. // File load IO error
  223. this._fileRef.addEventListener(IOErrorEvent.IO_ERROR, function(e:Event):void {
  224. this.dispatchEvent(e);
  225. });
  226. // Start loading local file
  227. this._fileRef.load();
  228. }
  229. /**
  230. * Uploads the next chunk or terminates the upload loop if all chunks are done.
  231. */
  232. public function uploadNextChunk():Boolean {
  233. var req:URLRequest, fileData:ByteArray, chunkData:ByteArray;
  234. var urlStream:URLStream, url:String, file:File = this;
  235. // All chunks uploaded?
  236. if (this._chunk >= this._chunks) {
  237. // Clean up memory
  238. if(this._fileRef.data) {
  239. this._fileRef.data.clear();
  240. }
  241. this._fileRef = null;
  242. this._imageData = null;
  243. return false;
  244. }
  245. // Slice out a chunk
  246. chunkData = new ByteArray();
  247. // Use image data if it exists, will exist if the image was resized
  248. if (this._imageData != null)
  249. fileData = this._imageData;
  250. else
  251. fileData = this._fileRef.data;
  252. fileData.readBytes(chunkData, 0, fileData.position + this._chunkSize > fileData.length ? fileData.length - fileData.position : this._chunkSize);
  253. // Setup URL stream
  254. urlStream = new URLStream();
  255. // Wait for response and dispatch it
  256. urlStream.addEventListener(Event.COMPLETE, function(e:Event):void {
  257. var response:String;
  258. response = urlStream.readUTFBytes(urlStream.bytesAvailable);
  259. // Fake UPLOAD_COMPLETE_DATA event
  260. var uploadChunkEvt:UploadChunkEvent = new UploadChunkEvent(
  261. UploadChunkEvent.UPLOAD_CHUNK_COMPLETE_DATA,
  262. false,
  263. false,
  264. response,
  265. file._chunk,
  266. file._chunks
  267. );
  268. file._chunk++;
  269. dispatchEvent(uploadChunkEvt);
  270. // Fake progress event since Flash doesn't have a progress event for streaming data up to the server
  271. var pe:ProgressEvent = new ProgressEvent(ProgressEvent.PROGRESS, false, false, fileData.position, file._size);
  272. dispatchEvent(pe);
  273. // Clean up memory
  274. urlStream.close();
  275. chunkData.clear();
  276. });
  277. // Delegate upload IO errors
  278. urlStream.addEventListener(IOErrorEvent.IO_ERROR, function(e:IOErrorEvent):void {
  279. file._chunk = file._chunks; // Cancel upload of all remaining chunks
  280. dispatchEvent(e);
  281. });
  282. // Delegate secuirty errors
  283. urlStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function(e:SecurityErrorEvent):void {
  284. file._chunk = file._chunks; // Cancel upload of all remaining chunks
  285. dispatchEvent(e);
  286. });
  287. // Setup URL
  288. url = this._uploadUrl;
  289. // Add name and chunk/chunks to URL if we use direct streaming method
  290. if (!this._multipart) {
  291. if (url.indexOf('?') == -1)
  292. url += '?';
  293. else
  294. url += '&';
  295. url += "name=" + encodeURIComponent(this._settings["name"]);
  296. if (this._chunking) {
  297. url += "&chunk=" + this._chunk + "&chunks=" + this._chunks;
  298. }
  299. }
  300. // Setup request
  301. req = new URLRequest(url);
  302. req.method = URLRequestMethod.POST;
  303. // Add custom headers
  304. if (this._headers) {
  305. for (var headerName:String in this._headers) {
  306. req.requestHeaders.push(new URLRequestHeader(headerName, this._headers[headerName]));
  307. }
  308. }
  309. // Build multipart request
  310. if (this._multipart) {
  311. var boundary:String = '----pluploadboundary' + new Date().getTime(),
  312. dashdash:String = '--', crlf:String = '\r\n', multipartBlob: ByteArray = new ByteArray();
  313. req.requestHeaders.push(new URLRequestHeader("Content-Type", 'multipart/form-data; boundary=' + boundary));
  314. this._postvars["name"] = this._settings["name"];
  315. // Add chunking parameters if needed
  316. if (this._chunking) {
  317. this._postvars["chunk"] = this._chunk;
  318. this._postvars["chunks"] = this._chunks;
  319. }
  320. // Append mutlipart parameters
  321. for (var name:String in this._postvars) {
  322. multipartBlob.writeUTFBytes(
  323. dashdash + boundary + crlf +
  324. 'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf +
  325. this._postvars[name] + crlf
  326. );
  327. }
  328. // Add file header
  329. multipartBlob.writeUTFBytes(
  330. dashdash + boundary + crlf +
  331. 'Content-Disposition: form-data; name="' + this._fileDataName + '"; filename="' + this._fileName + '"' + crlf +
  332. 'Content-Type: ' + this._mimeType + crlf + crlf
  333. );
  334. // Add file data
  335. multipartBlob.writeBytes(chunkData, 0, chunkData.length);
  336. // Add file footer
  337. multipartBlob.writeUTFBytes(crlf + dashdash + boundary + dashdash + crlf);
  338. req.data = multipartBlob;
  339. } else {
  340. req.requestHeaders.push(new URLRequestHeader("Content-Type", "application/octet-stream"));
  341. req.data = chunkData;
  342. }
  343. // Make request
  344. urlStream.load(req);
  345. return true;
  346. }
  347. }
  348. }