4
* Copyright 2009, Moxiecode Systems AB
5
* Released under GPL License.
7
* License: http://www.plupload.com/license
8
* Contributing: http://www.plupload.com/contributing
11
package com.plupload {
12
import com.formatlos.BitmapDataUnlimited;
13
import com.formatlos.events.BitmapDataUnlimitedEvent;
14
import flash.display.Bitmap;
15
import flash.display.BitmapData;
16
import flash.display.IBitmapDrawable;
17
import flash.events.EventDispatcher;
18
import flash.geom.Matrix;
19
import flash.net.FileReference;
20
import flash.events.Event;
21
import flash.events.IOErrorEvent;
22
import flash.events.HTTPStatusEvent;
23
import flash.events.ProgressEvent;
24
import flash.events.SecurityErrorEvent;
25
import flash.events.DataEvent;
26
import flash.net.FileReferenceList;
27
import flash.net.URLLoader;
28
import flash.net.URLRequest;
29
import flash.net.URLRequestHeader;
30
import flash.net.URLRequestMethod;
31
import flash.net.URLStream;
32
import flash.net.URLVariables;
33
import flash.utils.ByteArray;
34
import flash.utils.setTimeout;
35
import flash.external.ExternalInterface;
36
import com.mxi.image.events.ExifParserEvent;
38
import com.mxi.image.Image;
39
import com.mxi.image.events.ImageEvent;
43
* Container class for file references, this handles upload logic for individual files.
45
public class File extends EventDispatcher {
47
private var _fileRef:FileReference, _urlStream:URLStream, _cancelled:Boolean;
48
private var _uploadUrl:String, _uploadPath:String, _mimeType:String;
49
private var _id:String, _fileName:String, _size:Number, _imageData:ByteArray;
50
private var _multipart:Boolean, _fileDataName:String, _chunking:Boolean, _chunk:int, _chunks:int, _chunkSize:int, _postvars:Object;
51
private var _headers:Object, _settings:Object;
52
private var _removeAllListeners:Function;
53
private var _removeAllURLStreamListeners:Function;
56
* Id property of file.
58
public function get id():String {
63
* File name for the file.
65
public function get fileName():String {
66
return this._fileName;
70
* File name for the file.
72
public function set fileName(value:String):void {
73
this._fileName = value;
79
public function get size():Number {
84
* Constructs a new file object.
86
* @param id Unique indentifier for the file.
87
* @param file_ref File reference for the selected file.
89
public function File(id:String, file_ref:FileReference) {
91
this._fileRef = file_ref;
92
this._size = file_ref.size;
93
this._fileName = file_ref.name;
97
* Cancel current upload.
99
public function cancelUpload(): void
101
if (this.canUseSimpleUpload(this._settings)) {
102
this._fileRef.cancel();
103
} else if (!this._urlStream) {
104
// In case of a large file and before _fileRef.load#COMPLETE.
105
// Need to cancel() twice, not sure why.
106
// If single cancel(), #2037 will occurred at _fileRef.load().
107
this._fileRef.cancel();
108
this._fileRef.cancel();
109
this._removeAllListeners();
110
} else if (this._urlStream.connected) {
112
this._removeAllURLStreamListeners();
114
this._urlStream.close();
116
// In case of a large file and after the first uploadNextChunk().
117
// #2174 will occur at _fileRef.load() and
118
// #2029 will occur at _urlStream.readUTFBytes as well
119
// after loading file is stopped before _fileRef.load#COMPLETE.
121
// Uploaded file will be broken as well if the following line does not exist.
122
this._urlStream = null;
127
* Uploads a the file to the specified url. This method will upload it as a normal
128
* multipart file upload if the file size is smaller than the chunk size. But if the file is to
129
* large it will be chunked into multiple requests.
131
* @param url Url to upload the file to.
132
* @param settings Settings object.
134
public function upload(url:String, settings:Object):void {
135
this._settings = settings;
137
if (this.canUseSimpleUpload(settings)) {
138
this.simpleUpload(url, settings);
140
this.advancedUpload(url, settings);
146
public function canUseSimpleUpload(settings:Object):Boolean {
147
var multipart:Boolean = new Boolean(settings["multipart"]);
148
var resize:Boolean = (settings["width"] || settings["height"] || settings["quality"]);
149
var chunking:Boolean = (settings["chunk_size"] > 0);
151
// Check if it's not an image, chunking is disabled, multipart enabled and the ref_upload setting isn't forced
152
return (!(/\.(jpeg|jpg|png)$/i.test(this._fileName)) || !resize) && multipart && !chunking && !settings.urlstream_upload && !settings.headers;
155
public function simpleUpload(url:String, settings:Object):void {
156
var file:File = this, request:URLRequest, postData:URLVariables, fileDataName:String;
157
var onProgress:Function, onUploadComplete:Function, onIOError:Function, onSecurityErrorEvent:Function;
158
var removeAllListeners:Function = function () : void {
159
file._fileRef.removeEventListener(ProgressEvent.PROGRESS, onProgress);
160
file._fileRef.removeEventListener(DataEvent.UPLOAD_COMPLETE_DATA, onUploadComplete);
161
file._fileRef.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);
162
file._fileRef.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityErrorEvent);
165
this._removeAllListeners = removeAllListeners;
167
file._postvars = settings["multipart_params"];
171
postData = new URLVariables();
173
file._postvars["name"] = settings["name"];
175
for (var key:String in file._postvars) {
176
if (key != 'Filename') { // Flash will add it by itself, so we need to omit potential duplicate
177
postData[key] = file._postvars[key];
181
request = new URLRequest();
182
request.method = URLRequestMethod.POST;
184
request.data = postData;
186
fileDataName = new String(settings["file_data_name"]);
188
onUploadComplete = function(e:DataEvent):void {
189
removeAllListeners();
191
var pe:ProgressEvent = new ProgressEvent(ProgressEvent.PROGRESS, false, false, file._size, file._size);
194
// Fake UPLOAD_COMPLETE_DATA event
195
var uploadChunkEvt:UploadChunkEvent = new UploadChunkEvent(
196
UploadChunkEvent.UPLOAD_CHUNK_COMPLETE_DATA,
206
dispatchEvent(uploadChunkEvt);
210
file._fileRef.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, onUploadComplete);
212
// Delegate upload IO errors
213
onIOError = function(e:IOErrorEvent):void {
214
removeAllListeners();
217
file._fileRef.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
219
// Delegate secuirty errors
220
onSecurityErrorEvent = function(e:SecurityErrorEvent):void {
221
removeAllListeners();
224
file._fileRef.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityErrorEvent);
227
onProgress = function(e:ProgressEvent):void {
230
file._fileRef.addEventListener(ProgressEvent.PROGRESS, onProgress);
232
file._fileRef.upload(request, fileDataName, false);
235
public function advancedUpload(url:String, settings:Object):void {
236
var file:File = this, width:int, height:int, quality:int, multipart:Boolean, chunking:Boolean, fileDataName:String;
237
var chunk:int, chunks:int, chunkSize:int, postvars:Object;
238
var onComplete:Function, onIOError:Function;
239
var removeAllListeners:Function = function() : void {
240
file._fileRef.removeEventListener(Event.COMPLETE, onComplete);
241
file._fileRef.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);
244
// Setup internal vars
245
this._uploadUrl = url;
246
this._cancelled = false;
247
this._headers = settings.headers;
248
this._mimeType = settings.mime;
250
// make it available for whole class (cancelUpload requires it for example)
251
this._removeAllListeners = removeAllListeners;
253
multipart = new Boolean(settings["multipart"]);
254
fileDataName = new String(settings["file_data_name"]);
255
chunkSize = settings["chunk_size"];
256
chunking = chunkSize > 0;
257
postvars = settings["multipart_params"];
260
// When file is loaded start uploading
261
onComplete = function(e:Event):void {
262
removeAllListeners();
264
var startUpload:Function = function() : void
267
chunks = Math.ceil(file._size / chunkSize);
269
// Force at least 4 chunks to fake progress. We need to fake this since the URLLoader
270
// doesn't have a upload progress event and we can't use FileReference.upload since it
271
// doesn't support cookies, breaks on HTTPS and doesn't support custom data so client
272
// side image resizing will not be possible.
273
if (chunks < 4 && file._size > 1024 * 32) {
274
chunkSize = Math.ceil(file._size / 4);
278
// If chunking is disabled then upload file in one huge chunk
279
chunkSize = file._size;
283
// Start uploading the scaled down image
284
file._multipart = multipart;
285
file._fileDataName = fileDataName;
286
file._chunking = chunking;
288
file._chunks = chunks;
289
file._chunkSize = chunkSize;
290
file._postvars = postvars;
292
file.uploadNextChunk();
295
if (/\.(jpeg|jpg|png)$/i.test(file._fileName) && (settings["width"] || settings["height"] || settings["quality"])) {
296
var image:Image = new Image(file._fileRef.data);
297
image.addEventListener(ImageEvent.COMPLETE, function(e:ImageEvent) : void
299
image.removeAllEventListeners();
300
if (image.imageData) {
301
file._imageData = image.imageData;
302
file._imageData.position = 0;
303
file._size = image.imageData.length;
307
image.addEventListener(ImageEvent.ERROR, function(e:ImageEvent) : void
309
image.removeAllEventListeners();
310
file.dispatchEvent(e);
312
image.addEventListener(ExifParserEvent.EXIF_DATA, function(e:ExifParserEvent) : void
314
file.dispatchEvent(e);
316
image.addEventListener(ExifParserEvent.GPS_DATA, function(e:ExifParserEvent) : void
318
file.dispatchEvent(e);
320
image.scale(settings["width"], settings["height"], settings["quality"]);
325
this._fileRef.addEventListener(Event.COMPLETE, onComplete);
327
// File load IO error
328
onIOError = function(e:Event):void {
329
removeAllListeners();
332
this._fileRef.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
334
// Start loading local file
335
this._fileRef.load();
339
* Uploads the next chunk or terminates the upload loop if all chunks are done.
341
public function uploadNextChunk():Boolean {
342
var file:File = this, fileData:ByteArray, chunkData:ByteArray, req:URLRequest, url:String;
343
var onComplete:Function, onIOError:Function, onSecurityError:Function;
344
var removeAllEventListeners:Function = function() : void {
345
file._urlStream.removeEventListener(Event.COMPLETE, onComplete);
346
file._urlStream.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);
347
file._urlStream.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
350
this._removeAllURLStreamListeners = removeAllEventListeners;
352
// All chunks uploaded?
353
if (this._chunk >= this._chunks) {
355
if(this._fileRef.data) {
356
this._fileRef.data.clear();
358
this._imageData = null;
364
chunkData = new ByteArray();
366
// Use image data if it exists, will exist if the image was resized
367
if (this._imageData != null)
368
fileData = this._imageData;
370
fileData = this._fileRef.data;
372
fileData.readBytes(chunkData, 0, fileData.position + this._chunkSize > fileData.length ? fileData.length - fileData.position : this._chunkSize);
375
file._urlStream = null;
376
file._urlStream = new URLStream();
378
// Wait for response and dispatch it
379
onComplete = function(e:Event):void {
382
var response:String = file._urlStream.readUTFBytes(file._urlStream.bytesAvailable);
384
// Fake UPLOAD_COMPLETE_DATA event
385
var uploadChunkEvt:UploadChunkEvent = new UploadChunkEvent(
386
UploadChunkEvent.UPLOAD_CHUNK_COMPLETE_DATA,
395
dispatchEvent(uploadChunkEvt);
397
// Fake progress event since Flash doesn't have a progress event for streaming data up to the server
398
var pe:ProgressEvent = new ProgressEvent(ProgressEvent.PROGRESS, false, false, fileData.position, file._size);
402
file._urlStream.close();
403
removeAllEventListeners();
406
file._urlStream.addEventListener(Event.COMPLETE, onComplete);
408
// Delegate upload IO errors
409
onIOError = function(e:IOErrorEvent):void {
410
removeAllEventListeners();
413
file._urlStream.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
415
// Delegate secuirty errors
416
onSecurityError = function(e:SecurityErrorEvent):void {
417
removeAllEventListeners();
420
file._urlStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
423
url = this._uploadUrl;
425
// Add name and chunk/chunks to URL if we use direct streaming method
426
if (!this._multipart) {
427
if (url.indexOf('?') == -1)
432
url += "name=" + encodeURIComponent(this._settings["name"]);
434
if (this._chunking) {
435
url += "&chunk=" + this._chunk + "&chunks=" + this._chunks;
440
req = new URLRequest(url);
441
req.method = URLRequestMethod.POST;
443
// Add custom headers
445
for (var headerName:String in this._headers) {
446
req.requestHeaders.push(new URLRequestHeader(headerName, this._headers[headerName]));
450
// Build multipart request
451
if (this._multipart) {
452
var boundary:String = '----pluploadboundary' + new Date().getTime(),
453
dashdash:String = '--', crlf:String = '\r\n', multipartBlob: ByteArray = new ByteArray();
455
req.requestHeaders.push(new URLRequestHeader("Content-Type", 'multipart/form-data; boundary=' + boundary));
457
this._postvars["name"] = this._settings["name"];
459
// Add chunking parameters if needed
460
if (this._chunking) {
461
this._postvars["chunk"] = this._chunk;
462
this._postvars["chunks"] = this._chunks;
465
// Append mutlipart parameters
466
for (var name:String in this._postvars) {
467
multipartBlob.writeUTFBytes(
468
dashdash + boundary + crlf +
469
'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf +
470
this._postvars[name] + crlf
475
multipartBlob.writeUTFBytes(
476
dashdash + boundary + crlf +
477
'Content-Disposition: form-data; name="' + this._fileDataName + '"; filename="' + this._fileName + '"' + crlf +
478
'Content-Type: ' + this._mimeType + crlf + crlf
482
multipartBlob.writeBytes(chunkData, 0, chunkData.length);
485
multipartBlob.writeUTFBytes(crlf + dashdash + boundary + dashdash + crlf);
486
req.data = multipartBlob;
488
req.requestHeaders.push(new URLRequestHeader("Content-Type", "application/octet-stream"));
489
req.data = chunkData;
493
setTimeout(function() : void { // otherwise URLStream eventually hangs for Chrome+Flash (as of FP11.2 r202)
494
file._urlStream.load(req);