File.as 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. /**
  2. * Swiff.Uploader Class File
  3. *
  4. * @licence MIT Licence
  5. *
  6. * @author Harald Kirschner <http://digitarald.de>
  7. * @copyright Authors
  8. */
  9. package
  10. {
  11. import flash.events.*;
  12. import flash.net.FileReference;
  13. import flash.net.FileFilter;
  14. import flash.utils.Timer;
  15. import flash.net.URLRequest;
  16. import flash.net.URLRequestMethod;
  17. import flash.net.URLVariables;
  18. /**
  19. * @author Harald Kirschner <mail [at] digitarald.de>
  20. */
  21. public class File
  22. {
  23. static var idStack:uint = 1;
  24. static const STATUS_QUEUED:uint = 0;
  25. static const STATUS_RUNNING:uint = 1;
  26. static const STATUS_ERROR:uint = 2;
  27. static const STATUS_COMPLETE:uint = 3;
  28. static const STATUS_STOPPED:uint = 4;
  29. public var id:uint = 0;
  30. public var status:uint = 0;
  31. private var options:Object = {
  32. url: null,
  33. method: null,
  34. data: null,
  35. mergeData: null,
  36. fieldName: null
  37. }
  38. public var reference:FileReference = null;
  39. private var parent:Main = null;
  40. public var responseText:String = null;
  41. public var responseCode:uint = 0;
  42. public var responseError:String = null;
  43. public var validationError:String = null;
  44. public var addDate:Date = null;
  45. public var startDate:Date = null;
  46. eublic var progressDate:Date = null;
  47. public var completeDate:Date = null;
  48. public var fileExtension:String = null;
  49. public var bytesLoaded:uint = 0;
  50. public var timeElapsed:uint = 0;
  51. public var timeRemaining:uint = 0;
  52. public var progressGraph:Array = new Array();
  53. public var rate:uint = 0;
  54. public var rateAvg:uint = 0;
  55. private var timeout:Timer = null;
  56. private var lockProgress:uint = 0;
  57. public function File(parent_init:Main, reference_init:FileReference)
  58. {
  59. id = File.idStack++;
  60. reference = reference_init;
  61. parent = parent_init;
  62. reference.addEventListener(Event.OPEN, handleOpen);
  63. reference.addEventListener(ProgressEvent.PROGRESS, handleProgress);
  64. reference.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, handleComplete);
  65. reference.addEventListener(HTTPStatusEvent.HTTP_STATUS, handleHttpStatus);
  66. reference.addEventListener(IOErrorEvent.IO_ERROR, handleIoError);
  67. reference.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleSecurityError);
  68. fileExtension = reference.name.replace(/.+\.([^.]+)$/, '$1').toLowerCase();
  69. addDate = new Date();
  70. if (parent.options.timeLimit) {
  71. timeout = new Timer(parent.options.timeLimit * 1000, 1);
  72. timeout.addEventListener('timer', handleTimeout);
  73. timeout.start();
  74. }
  75. }
  76. public function fireEvent(functionName:String, queue:Boolean = false):void
  77. {
  78. var args:Array = [export()];
  79. if (queue) args.push(parent.queueUpdate());
  80. parent.fireEvent('file' + functionName.charAt(0).toUpperCase() + functionName.substr(1), args);
  81. }
  82. private function handleOpen(event:Event):void
  83. {
  84. progressDate = startDate = new Date();
  85. if (timeout) timeout.start();
  86. fireEvent('open');
  87. }
  88. private function handleProgress(event:ProgressEvent):void
  89. {
  90. if (timeout) {
  91. timeout.reset();
  92. timeout.start();
  93. }
  94. var now:uint = new Date().getTime();
  95. var allow:Boolean = (!lockProgress || lockProgress < now - 100 || event.bytesLoaded == reference.size)
  96. if (allow) {
  97. updateProgress((event.bytesLoaded >= 0) ? event.bytesLoaded : 0);
  98. lockProgress = now;
  99. fireEvent('progress', true);
  100. } else {
  101. // parent.verboseLog('Process Skipped', [event.bytesLoaded, now - lockProgress]);
  102. }
  103. }
  104. private function resetProgress():void
  105. {
  106. responseText = responseError = null;
  107. responseCode = 0;
  108. progressGraph.length = 0;
  109. startDate = progressDate = completeDate = null;
  110. timeElapsed = timeRemaining = rate = rateAvg = bytesLoaded = 0;
  111. }
  112. private function updateProgress(bytes_loaded:uint, complete:Boolean = false)
  113. {
  114. var now:Date = new Date();
  115. var i:uint = 0, length:uint = 0;
  116. rate = rateAvg = 0;
  117. if (bytes_loaded && !complete) {
  118. var bytes_diff:Number = (bytes_loaded - bytesLoaded) / (now.getTime() - progressDate.getTime());
  119. length = progressGraph.unshift(Math.round(bytes_diff));
  120. bytesLoaded = bytes_loaded;
  121. if (length > 1) {
  122. if (length > parent.options.progressGraphSize) length = parent.options.progressGraphSize;
  123. var mean:Number = 0;
  124. var variance:Number = 0;
  125. for (i = 0; i < length; i++) {
  126. mean += progressGraph[i];
  127. variance += Math.pow(progressGraph[i] - mean, 2);
  128. }
  129. mean /= length;
  130. rate = rateAvg = Math.round(mean * 1000);
  131. if (length > 6) {
  132. var standard_dev:Number = Math.sqrt(variance / length);
  133. var deviation_range:Number = 2.0;
  134. var filtered_sum:Number = 0;
  135. var filtered_count:uint = 0;
  136. for (i = 0; i < length; i++) {
  137. var value:Number = (progressGraph[i] - mean) / standard_dev;
  138. if (value <= deviation_range && value >= -deviation_range) {
  139. filtered_sum += progressGraph[i];
  140. filtered_count++;
  141. }
  142. }
  143. rateAvg = Math.round(filtered_sum / filtered_count * 1000);
  144. timeRemaining = (reference.size - bytes_loaded) / rateAvg;
  145. }
  146. }
  147. }
  148. timeElapsed = (now.getTime() - startDate.getTime()) / 1000;
  149. progressDate = now;
  150. parent.rate = parent.bytesLoaded = 0;
  151. for (var i = 0; i < parent.fileList.length; i++) {
  152. var file:File = parent.fileList[i];
  153. switch (file.status) {
  154. case File.STATUS_RUNNING:
  155. parent.rate += file.rateAvg;
  156. case File.STATUS_COMPLETE:
  157. parent.bytesLoaded += file.bytesLoaded;
  158. }
  159. }
  160. }
  161. private function handleTimeout(event:TimerEvent):void
  162. {
  163. if (status != File.STATUS_RUNNING) return;
  164. reference.cancel();
  165. complete('timeLimit (' + options.timeLimit + 's) exceeded', 'timeout', null);
  166. }
  167. private function handleComplete(event:DataEvent):void
  168. {
  169. complete(event.data);
  170. }
  171. private function handleIoError(event:IOErrorEvent):void
  172. {
  173. complete(event.text, event.type, null);
  174. }
  175. private function handleSecurityError(event:SecurityErrorEvent):void
  176. {
  177. complete(event.text, event.type, null);
  178. }
  179. private function handleHttpStatus(event:HTTPStatusEvent):void
  180. {
  181. if (parent.options.passStatus is Array) {
  182. var list:Array = parent.options.passStatus;
  183. if (list.length && list.indexOf(event.status) != -1) {
  184. complete(null, null, event.status);
  185. return;
  186. }
  187. }
  188. complete(null, event.type, event.status);
  189. }
  190. private function complete(text:String = null, error:String = null, code:int = 0)
  191. {
  192. if (status != File.STATUS_RUNNING) {
  193. parent.verboseLog('File[' + id + ']::complete wasted!', [].concat(arguments));
  194. return;
  195. }
  196. if (timeout) timeout.reset();
  197. completeDate = new Date();
  198. responseText = text;
  199. responseCode = code;
  200. if (error) {
  201. responseError = error;
  202. status = File.STATUS_ERROR;
  203. } else {
  204. status = File.STATUS_COMPLETE;
  205. }
  206. parent.uploading--;
  207. updateProgress(reference.size, true);
  208. fireEvent('complete', true);
  209. parent.checkQueue(true);
  210. }
  211. public function setOptions(options_init:Object = null):void
  212. {
  213. if (options_init != null) {
  214. for (var prop:String in options) {
  215. if (options_init.hasOwnProperty(prop)) options[prop] = options_init[prop];
  216. }
  217. }
  218. }
  219. public function start():Boolean
  220. {
  221. if (status == File.STATUS_RUNNING) return false;
  222. var options_parent:Object = parent.options;
  223. var merged:Object = new Object();
  224. for (var prop:String in options) {
  225. merged[prop] = (options[prop] != null) ? options[prop] : options_parent[prop];
  226. }
  227. try {
  228. var url_request:URLRequest = new URLRequest(merged.url || '');
  229. if (merged.data != null && merged.data != false) {
  230. if (merged.mergeData && options.data && options_parent.data) {
  231. if (options.data is String && options_parent.data is String) {
  232. merged.data = options_parent.data + '&' + options.data;
  233. } else {
  234. merged.data = new Object();
  235. for (var prop:String in options_parent.data) {
  236. merged.data[prop] = options_parent.data[prop];
  237. }
  238. for (var prop:String in options.data) {
  239. merged.data[prop] = options.data[prop];
  240. }
  241. }
  242. }
  243. var data:URLVariables = new URLVariables();
  244. if (merged.data is String) data.decode(merged.data);
  245. else for (var key:Object in merged.data) data[key] = merged.data[key];
  246. url_request.data = data;
  247. }
  248. url_request.method = URLRequestMethod[(merged.method) ? merged.method.toUpperCase() : 'POST'];
  249. } catch (e:Error) {
  250. parent.verboseLog('File[' + id + ']::start - Exception (URLRequest)', String(e));
  251. return false;
  252. }
  253. parent.verboseLog('File[' + id + ']::start', merged);
  254. resetProgress();
  255. status = File.STATUS_RUNNING;
  256. parent.uploading++;
  257. fireEvent('start', true);
  258. try {
  259. reference.upload(url_request, merged.fieldName || 'Filedata');
  260. } catch (e:Error) {
  261. parent.verboseLog('File[' + id + ']::start - Exception (upload)', String(e));
  262. stop();
  263. return false;
  264. }
  265. return true;
  266. }
  267. public function stop(eventful:Boolean = true):Boolean
  268. {
  269. if (status != File.STATUS_RUNNING) return false;
  270. if (timeout) timeout.reset();
  271. reference.cancel();
  272. status = File.STATUS_STOPPED;
  273. parent.uploading--;
  274. parent.bytesLoaded -= bytesLoaded;
  275. parent.rate -= rateAvg;
  276. resetProgress();
  277. if (eventful) {
  278. fireEvent('stop', true);
  279. parent.checkQueue(true);
  280. }
  281. return true;
  282. }
  283. public function requeue():Boolean
  284. {
  285. var running:Boolean = stop(false);
  286. status = File.STATUS_QUEUED;
  287. fireEvent('requeue');
  288. if (running) parent.checkQueue(true);
  289. return true;
  290. }
  291. public function remove():Boolean
  292. {
  293. var running:Boolean = stop(false);
  294. var idx = parent.fileList.indexOf(this);
  295. parent.fileList.splice(idx, 1);
  296. parent.size -= reference.size;
  297. fireEvent('remove', true);
  298. if (running) parent.checkQueue(true);
  299. reference = null;
  300. return true;
  301. }
  302. public function validate():Boolean
  303. {
  304. if (!parent.options.allowDuplicates && parent.hasFile(this)) {
  305. validationError = 'duplicate';
  306. return false;
  307. }
  308. if (parent.options.fileSizeMin > 0 && reference.size < parent.options.fileSizeMin) {
  309. validationError = 'sizeLimitMin';
  310. return false;
  311. }
  312. if (parent.options.fileSizeMax > 0 && reference.size > parent.options.fileSizeMax) {
  313. validationError = 'sizeLimitMax';
  314. return false;
  315. }
  316. return true;
  317. }
  318. public function export():Object
  319. {
  320. var export:Object = {
  321. id: id,
  322. name: reference.name,
  323. size: reference.size,
  324. modificationDate: reference.modificationDate,
  325. creationDate: reference.creationDate,
  326. extension: fileExtension,
  327. status: status,
  328. validationError: validationError,
  329. addDate: addDate
  330. };
  331. if (startDate) {
  332. export.startDate = startDate;
  333. export.progressDate = progressDate;
  334. export.progress = {
  335. graph: progressGraph,
  336. bytesLoaded: bytesLoaded,
  337. percentLoaded: Math.ceil(bytesLoaded / reference.size * 100),
  338. rate: rate,
  339. rateAvg: rateAvg,
  340. timeElapsed: timeElapsed,
  341. timeRemaining: timeRemaining
  342. };
  343. };
  344. if (completeDate) {
  345. export.completeDate = completeDate;
  346. export.response = {
  347. text: responseText,
  348. code: responseCode,
  349. error: responseError
  350. }
  351. }
  352. return export;
  353. }
  354. // Static methods
  355. static public function exportMany(files:Array):Array
  356. {
  357. if (!files.length) return null;
  358. return files.map(function(current:File, i:uint, self:Array) {
  359. return current.export();
  360. });
  361. }
  362. }
  363. }