root/trunk/uploadr/MacUploadr.app/Contents/Resources/chrome/content/uploadr/upload.js

Revision 493, 26.7 kB (checked in by jdecq, 11 months ago)

add timestamps in log message

Line 
1 /*
2  * Flickr Uploadr
3  *
4  * Copyright (c) 2007-2008 Yahoo! Inc.  All rights reserved.  This library is
5  * free software; you can redistribute it and/or modify it under the terms of
6  * the GNU General Public License (GPL), version 2 only.  This library is
7  * distributed WITHOUT ANY WARRANTY, whether express or implied. See the GNU
8  * GPL for more details (http://www.gnu.org/licenses/gpl.html)
9  */
10
11 // Notes about the upload API:
12 //   Upload batches should be kicked off using photos.upload() in photos.js.
13 //   The setting conf.mode in conf.js can select either synchronous or
14 //   asynchronous uploads.  However, synchronous uploads won't work right
15 //   with the new post-upload page.
16
17 // The upload API
18 var upload = {
19
20         // Count how many times a we've retried
21         retry_count: 0,
22         tickets_retry_count: 0,
23
24         // Flag set if a batch is cancelled
25         cancel: false,
26
27         // Progress metering
28         progress_bar: null,
29         progress_handle: null,
30         progress_id: -1,
31         progress_last: 0,
32         progress_total: -1,
33         progress_zero: 0,
34
35         // Ticket tracking
36         tickets: {},
37         tickets_count: 0,
38         tickets_delta: 1000, // Milliseconds
39         tickets_handle: null,
40
41         // Timestamps for defining this batch on the site
42         timestamps: {
43                 earliest: 0,
44                 latest: 0
45         },
46
47         // Stats tracking
48         stats: {
49                 photos: 0,
50                 bytes: 0,
51                 errors: 0
52         },
53
54         // Track when we're "processing" for special multi-batch handling
55         processing: false,
56
57         // Holding pen for photos we may try again
58         try_again: [],
59
60     startTime : 0,
61    
62         // Upload a photo
63         start: function(id) {
64            
65             //reset upload progress
66             upload.progress_id = -1;
67
68                 // Update the UI
69                 if (null == upload.progress_bar) {
70                         document.getElementById('footer').style.display = '-moz-box';
71                         upload.progress_bar = new ProgressBar('progress_bar');
72                         var progress_text = document.getElementById('progress_text');
73                         progress_text.className = 'spinning';
74                         progress_text.value = '';
75                 }
76
77                 var photo = photos.uploading[id];
78
79                 // Let extensions have their say
80                 extension.before_one_upload.exec(photo);
81
82                 // EXPERIMENTAL: Pass the photo to the socket uploadr
83                 if (conf.socket_uploadr) {
84
85                         // Dispatch for health and non-blocking profit!
86                         threads.uploadr.dispatch(new Upload({
87                                 'async': 'async' == conf.mode ? 1 : 0,
88                                 'auth_token': users.list[photo.nsid].token,
89                                 'title': photo.title,
90                                 'description': photo.description,
91                                 'tags': photo.tags,
92                                 'is_public': photo.is_public,
93                                 'is_friend': photo.is_friend,
94                                 'is_family': photo.is_family,
95                                 'content_type': photo.content_type,
96                                 'hidden': photo.hidden,
97                                 'safety_level': photo.safety_level,
98                                 'photo': {
99                                         'filename': photo.filename,
100                                         'path': photo.path
101                                 }
102                         }, id), threads.uploadr.DISPATCH_NORMAL);
103                 }
104
105                 // Pass the photo to the regular API
106                 //   Possibly/probably broken
107                 else {
108                         api.start({
109                                 'async': 'async' == conf.mode ? 1 : 0,
110                                 'auth_token': users.list[photo.nsid].token,
111                                 'title': photo.title,
112                                 'description': photo.description,
113                                 'tags': photo.tags,
114                                 'is_public': photo.is_public,
115                                 'is_friend': photo.is_friend,
116                                 'is_family': photo.is_family,
117                                 'content_type': photo.content_type,
118                                 'hidden': photo.hidden,
119                                 'safety_level': photo.safety_level,
120                                 'photo': {
121                                         'filename': photo.filename,
122                                         'path': photo.path
123                                 }
124                         }, null, 'http://' + UPLOAD_HOST + '/services/upload/',
125                                 false, true, id);
126                 }
127
128         },
129         _start: function(rsp, id) {
130                 upload['_' + conf.mode](rsp, id);
131         },
132
133         // Finish an asynchronous upload
134         _async: function(rsp, id) {
135
136                 // Stop checking progress
137                 if (null != upload.progress_handle) {
138                         window.clearInterval(upload.progress_handle);
139                         upload.progress_handle = null;
140                 }
141                 upload.progress_zero = 0;
142
143         // If no ticket came back, fail this photo
144                 if ('object' != typeof rsp || 'ok' != rsp.getAttribute('stat')) {
145                         if (conf.console.error) {
146                                 Components.utils.reportError('UPLOAD ERROR: ' +
147                                         'object' == typeof rsp ? rsp.toSource() : rsp);
148                         }
149
150                         // Make sure this isn't a bandwidth error, as those are
151                         // unrecoverable
152                         if (upload.bandwidth(rsp)) { return; }
153
154                         // If the result indicates that videos are disabled
155                         if ('object' == typeof rsp && rsp.getElementsByTagName('err')[0] &&
156                                 7 == parseInt(rsp.getElementsByTagName('err')[0]
157                                 .getAttribute('code'))) {
158                                 alert(locale.getString('video.disabled.text'),
159                                         locale.getString('video.disabled.title'),
160                                         locale.getString('video.disabled.ok'));
161                                 upload.cancel = true;
162                         }
163
164                         // Still have available retries
165                         if (!upload.cancel && conf.auto_retry_count >
166                                 upload.retry_count) {
167                                 ++upload.stats.errors;
168                                 ++upload.retry_count;
169                                 photos.kb.sent -= photos.uploading[id].size;
170                                 upload.start(id);
171                                 if (conf.console.retry) {
172                                         logStringMessage('UPLOAD RETRY: id = ' + id +
173                                                 ', retry = ' + upload.retry_count);
174                                 }
175                         }
176
177                         // Out of retry attempts, this time we really die
178                         else {
179                                 upload.retry_count = 0;
180                                 if (null != photos.uploading[id]) {
181                                         photos.uploading[id].progress_bar.done(false);
182                                         ++photos.fail;
183                                         photos.failed.push(photos.uploading[id]);
184                                 }
185                                 photos.uploading[id] = null;
186                                 upload.cancel = true;
187                                 if (0 == upload.tickets_count) {
188                                         upload.done();
189                                 }
190                         }
191
192                         return;
193                 }
194
195                 // Otherwise, spin for a ticket
196                 if (null != photos.uploading[id]) {
197                         photos.uploading[id].progress_bar.done(true);
198                         upload.tickets[rsp.getElementsByTagName('ticketid')[0]
199                                 .firstChild.nodeValue] = {
200                                         'id': id,
201                                         'token': users.list[photos.uploading[id].nsid].token
202                                 };
203                         ++upload.tickets_count;
204                         if (null != upload.tickets_handle) {
205                                 window.clearTimeout(upload.tickets_handle);
206                                 upload.tickets_handle = null;
207                                 upload.tickets_delta = 1000;
208                         }
209                         upload.tickets_retry_count = 0;
210                         upload.check_tickets();
211                 }
212
213         // Start the next one or quit if we're cancelling
214         if (!upload.cancel) {
215             ++id;
216             if (id < photos.uploading.length) {
217                 upload.retry_count = 0;
218                         upload.start(id);
219             }
220         }
221
222                
223
224         },
225
226         // Finish a synchronous upload
227         _sync: function(rsp, id) {
228
229                 // Stop checking progress if we're in synchronous mode
230                 if ('sync' == conf.mode) {
231                         if (null != upload.progress_handle) {
232                                 window.clearInterval(upload.progress_handle);
233                                 upload.progress_handle = null;
234                         }
235                         upload.progress_zero = 0;
236                 }
237
238                 // How did the upload go?
239                 var photo_id;
240                 var stat;
241                 if ('object' == typeof rsp) {
242                         stat = rsp.getAttribute('stat');
243                 } else if ('number' == typeof rsp) {
244                         photo_id = rsp;
245                         stat = 'ok';
246                 } else {
247                         stat = 'fail';
248                 }
249                 if ('ok' == stat) {
250                         if (null != photos.uploading[id]) {
251                                 photos.uploading[id].progress_bar.done(true);
252                                 ++photos.ok;
253                         }
254                         if ('object' == typeof rsp) {
255                                 photo_id = parseInt(rsp.getElementsByTagName('photoid')[0]
256                                         .firstChild.nodeValue);
257
258                                 // If we were ever to use sync upload, we would need imported
259                                 // timestamps here
260
261                         }
262                         photos.uploading[id].photo_id = photo_id;
263                         photos.uploaded.push(photos.uploading[id]);
264
265                         // Add to sets
266                         for each (var i in photos.uploading[id].sets) {
267                                 var token = users.list[photos.uploading[id].nsid].token;
268                                 var set = photos.sets[photos.uploading[id].nsid][i];
269                                 if (null == set.id) {
270
271                                         // Queue this photo if a create call hasn't returned
272                                         if (set.busy) { set.add.push(photo_id); }
273
274                                         // Otherwise create
275                                         else {
276                                                 block_exit();
277                                                 set.busy = true;
278                                                 var _nsid = photos.uploading[id].nsid;
279                                                 ++photos.sets_out;
280                                                 flickr.photosets.create(function(rsp) {
281                                                         var nsid = _nsid;
282
283                                                         // Failure just fails and moves on
284                                                         if ('object' != typeof rsp
285                                                                 || 'ok' != rsp.getAttribute('stat')) {
286                                                                 photos.sets_fail = true;
287                                                                 // TODO: Retry or at least deal with set.add
288                                                         }
289
290                                                         // Success needs to update sets list and other
291                                                         // photos
292                                                         else {
293                                                                 set.id = rsp.getElementsByTagName(
294                                                                         'photoset')[0].getAttribute('id');
295                                                                 for each (var p in set.add) {
296                                                                         wrap.photosets.addPhoto(token, set.id, p);
297                                                                 }
298                                                                 set.busy = false;
299
300                                                                 // If we're still the same user as is
301                                                                 // uploading, update the main sets list
302                                                                 if (users.nsid == nsid) {
303                                                                         meta.sets[i].id = set.id;
304                                                                 }
305
306                                                         }
307
308                                                         --photos.sets_out;
309                                                         unblock_exit();
310                                                         upload.finalize();
311                                                 }, token, set.title, set.description, photo_id);
312                                         }
313                                 }
314
315                                 // Add photo to set
316                                 else {
317                                         wrap.photosets.addPhoto(token, set.id, photo_id);
318                                 }
319
320                         }
321
322                 } else if ('fail' == stat) {
323                         photos.uploading[id].progress_bar.done(false);
324                         ++photos.fail;
325                         photos.failed.push(photos.uploading[id]);
326                         if (upload.bandwidth(rsp)) { return; }
327                 }
328
329                 // Let extensions have their say
330                 extension.after_one_upload.exec(photos.uploading[id], 'ok' == stat);
331
332                 photos.uploading[id] = null;
333
334                 // For the last upload, we have some cleanup to do
335                 if ((upload.cancel || photos.ok + photos.fail == photos.uploading.length) &&
336                         0 == upload.tickets_count) {
337                         upload.done();
338                 }
339
340                 // But if this isn't last and we're doing synchronous, kick off
341                 // the next upload
342                 else if ('sync' == conf.mode) {
343                         var ii = photos.uploading.length;
344                         for (var i = id; i < ii; ++i) {
345                                 if (null != photos.uploading[i]) {
346                                         upload.retry_count = 0;
347                                         upload.start(i);
348                                         break;
349                                 }
350                         }
351                 }
352
353         },
354
355         // Track progress of an upload POST
356         progress: function(stream, id) {
357                 upload.progress2(stream.available(), id);
358         },
359         progress2: function(available, id) {
360
361                 // Get this bit of progress
362                 if (id != upload.progress_id) {
363                         upload.progress_id = id;
364                         upload.progress_last = upload.progress_total;
365                 }
366                 var a = available >> 10;
367                 var kb = upload.progress_last - a;
368
369                 // Tell extensions how many kilobytes went by
370                 extension.on_upload_progress.exec(kb);
371
372                 // Have we made any progress?
373                 if (0 == kb) {
374                         ++upload.progress_zero;
375                 }
376                 if (conf.timeout < conf.check * upload.progress_zero) {
377                         upload.timeout(id);
378                 }
379                 if (0 != upload.progress_last) {
380                         photos.kb.sent += kb;
381                 }
382
383                 upload.progress_last = a;
384
385                 // Update the UI
386                 if (null != photos.uploading[id]) {
387                     var currentTime = new Date().getTime();
388                     status.set(locale.getString('status.uploading') + ' ' +
389                         (1000 * photos.kb.sent / (currentTime-upload.startTime)).toFixed(1) + ' KB/s');
390                 photos.uploading[id].progress_bar.update(1 -
391                                 a / upload.progress_total);
392                 }
393                 var percent = Math.max(0, Math.min(1,
394                         photos.kb.sent / photos.kb.total));
395                 if (null != upload.progress_bar) {
396                         upload.progress_bar.update(percent);
397                 }
398                 if (100 == Math.round(100 * percent)) {
399                         document.getElementById('progress_text').value =
400                                 locale.getString('upload.waiting.status');
401                         upload.processing = true;
402                 } else {
403                         document.getElementById('progress_text').value =
404                                 locale.getFormattedString('upload.progress.status', [
405                                         id + 1,
406                                         photos.uploading.length,
407                                         Math.round(100 * percent)
408                                 ]);
409                 }
410
411         },
412
413         // Timeout an upload after too much inactivity
414         timeout: function(id) {
415                 if (conf.console.timeout) {
416                         Components.utils.reportError('UPLOAD TIMEOUT: ' + id);
417                 }
418                 window.clearInterval(upload.progress_handle);
419                 upload.progress_handle = null;
420                 upload.cancel = true;
421                 upload._start(false, id);
422         },
423
424         // Check tickets exponentially
425         //   Couldn't two tokens collide here?  Wouldn't it continue to work
426         //   just fine if they did?
427         check_tickets: function() {
428                 var tickets = {};
429                 for (var t in upload.tickets) {
430                         if ('undefined' == typeof tickets[upload.tickets[t].token]) {
431                                 tickets[upload.tickets[t].token] = [t];
432                         } else {
433                                 tickets[upload.tickets[t].token].push(t);
434                         }
435                 }
436                 for (var token in tickets) {
437                         wrap.photos.upload.checkTickets(token, tickets[token]);
438                 }
439         },
440         _check_tickets: function() {
441                 if (60000 > upload.tickets_delta) {
442                         upload.tickets_delta *= 2;
443                 }
444                 if (0 < upload.tickets_count) {
445                         upload.tickets_handle = window.setTimeout(function() {
446                                 upload.check_tickets();
447                         }, upload.tickets_delta);
448                 }
449         },
450
451         // Check a response for out-of-bandwidth error
452         bandwidth: function(rsp) {
453                 if ('object' == typeof rsp && rsp.getElementsByTagName('err')[0] &&
454                         6 == parseInt(rsp.getElementsByTagName('err')[0]
455                         .getAttribute('code'))) {
456                         document.getElementById('progress').style.display = 'none';
457                         var f = photos.failed;
458                         for each (var p in photos.uploading) {
459                                 if (null != p && -1 == f.indexOf(p.path)) { f.push(p); }
460                         }
461                         var ii = f.length;
462                         if (0 != ii) {
463                                 document.getElementById('photos_init')
464                                         .style.display = 'none';
465                                 document.getElementById('photos_new')
466                                         .style.display = 'none';
467                                 if (photos.sort) {
468                                         document.getElementById('photos_sort_default')
469                                                 .style.display = 'block';
470                                         document.getElementById('photos_sort_revert')
471                                                 .style.display = 'none';
472                                 } else {
473                                         document.getElementById('photos_sort_default')
474                                                 .style.display = 'none';
475                                         document.getElementById('photos_sort_revert')
476                                                 .style.display = 'block';
477                                 }
478                         }
479                         for (var i  = 0; i < ii; ++i) {
480                                 photos._add(f[i].path);
481                                 photos.list[photos.list.length - 1] = f[i];
482                         }
483
484                         // Add back any queued batches
485                         while (photos.ready.length) {
486                                 var r = photos.ready.shift();
487                                 ii = r.length;
488                                 for (var i  = 0; i < ii; ++i) {
489                                         photos._add(r[i].path);
490                                         photos.list[photos.list.length - 1] = r[i];
491                                 }
492                         }
493
494                         photos.uploading = [];
495                         photos.uploaded = [];
496                         photos.failed = [];
497                         photos.ok = 0;
498                         photos.fail = 0;
499                         upload.processing = false;
500                         unblock_exit();
501                         if (confirm(locale.getString('dialog.bandwidth.text'),
502                                 locale.getString('dialog.bandwidth.title'),
503                                 locale.getString('dialog.bandwidth.ok'),
504                                 locale.getString('dialog.bandwidth.cancel'))) {
505                                 launch_browser('http://' + SITE_HOST + '/upgrade/');
506                         } else {
507                                 buttons.login.click();
508                         }
509                         mouse.show_photos();
510                         return true;
511                 } else {
512                         return false;
513                 }
514         },
515
516         // Start to clean up after an upload finishes
517         done: function() {
518                 if (null != upload.progress_handle) {
519                         window.clearInterval(upload.progress_handle);
520                         upload.progress_handle = null;
521                 }
522                 upload.progress_zero = 0;
523
524                 // Update the UI
525                 if (null != upload.progress_bar) {
526                     upload.progress_bar.update(1);
527                 }
528                 var text = document.getElementById('progress_text');
529                 if (0 == photos.fail) {
530                         text.className = 'done';
531                         text.value = locale.getString('upload.success.status');
532                 } else {
533                         text.className = 'error';
534                         text.value = locale.getString('upload.error.status');
535                 }
536                 mouse.show_photos();
537                 var queue = document.getElementById('queue_list');
538                 while (queue.hasChildNodes()) {
539                         queue.removeChild(queue.firstChild);
540                 }
541                 status.clear();
542
543                 // Hold failed photos for trying again
544                 var f = photos.failed;
545                 var ii = f.length;
546                 if (0 != ii) {
547                         for (var i  = 0; i < ii; ++i) {
548                                 upload.try_again.push(f[i]);
549                         }
550                 }
551
552                 // Re-add photos we didn't get to
553                 if (upload.cancel) {
554                         for each (var p in photos.uploading) {
555                                 if (null != p && !p.photo_id) { upload.try_again.push(p); }
556                         }
557
558                         // Add back any queued batches
559                         while (photos.ready.length) {
560                                 var r = photos.ready.shift();
561                                 ii = r.length;
562                                 for (var i  = 0; i < ii; ++i) {
563                                         upload.try_again.push(r[i]);
564                                 }
565                         }
566
567                 }
568
569                 upload.finalize();
570         },
571
572         // Finally give the user feedback on their upload
573         finalize: function() {
574                 status.clear();
575
576                 // An upload must be done before it can be finalized
577                 //   Uploads that are done might still have set calls outstanding
578                 if (!upload.cancel && photos.ok + photos.fail != photos.uploading.length
579                         || upload.tickets_count || photos.sets_out) {
580                         return;
581                 }
582
583                 // An upload has completed so do extension stuff
584                 extension.after_upload.exec(photos.uploaded, photos.failed);
585
586                 // If there is a batch queued up, start that batch, preserving the
587                 // timestamps so that this looks like one big batch
588                 upload.processing = false;
589                 if (photos.ready.length) {
590                         buttons.upload.enable();
591                         photos.uploading = [];
592                         photos.failed = [];
593                         photos.uploaded = [];
594                         upload.stats.photos += photos.ok + photos.fail;
595                         upload.stats.errors += photos.fail;
596                         photos.ok = 0;
597                         photos.fail = 0;
598                         photos.sets_fail = false;
599                         photos.kb.sent = 0;
600                         upload.stats.bytes += 1024 * photos.kb.total;
601                         photos.kb.total = 0;
602                         upload.progress_bar = null;
603                         upload.cancel = false;
604                         upload.tickets = {};
605                         upload.tickets_count = 0;
606                         upload.tickets_delta = 1000;
607                         upload.tickets_handle = null;
608                         unblock_exit();
609                         photos.upload(photos.ready.shift(), photos.ready_size.shift());
610                         return;
611                 }
612
613                 // Ask the site for an update
614                 wrap.photosets.getList(users.token, users.nsid);
615                 wrap.people.getUploadStatus(users.token);
616
617                 // Send stats to the site
618                 wrap.utils.logUploadStats(users.token,
619                         0 /* Source, known by API key */,
620                         upload.stats.photos + photos.ok + photos.fail,
621                         1000 * (upload.timestamps.latest - upload.timestamps.earliest),
622                         upload.stats.bytes + 1024 * photos.kb.total,
623                         upload.stats.errors + photos.fail);
624
625                 // Decide which message to show
626                 var go_to_flickr = false;
627                 var try_again = false;
628                 if(!ui.cancel) {
629                     if (0 == photos.fail && 0 < photos.ok && !photos.sets_fail) {
630                             go_to_flickr = confirm(locale.getString('upload.success.text'),
631                                     locale.getString('upload.success.title'),
632                                     locale.getString('upload.success.ok'),
633                                     locale.getString('upload.success.cancel'));
634                     } else if (0 < photos.fail && 0 < photos.ok) {
635                             var c = confirm(locale.getFormattedString(
636                                     'upload.error.some.text', [
637                                             photos.uploading.length - photos.ok,
638                                             photos.uploading.length
639                                     ]),
640                                     locale.getString('upload.error.some.title'),
641                                     locale.getString('upload.error.some.ok'),
642                                     locale.getString('upload.error.some.cancel'));
643                             if (c) { try_again = true; }
644                             else { go_to_flickr = true; }
645                     } else if (0 == photos.fail && 0 < photos.ok && photos.sets_fail) {
646                             go_to_flickr = confirm([
647                                             locale.getString('upload.error.sets.text'),
648                                             locale.getString('upload.error.sets.text.more')
649                                     ],
650                                     locale.getString('upload.error.sets.title'),
651                                     locale.getString('upload.error.sets.ok'),
652                                     locale.getString('upload.error.sets.cancel'));
653                     } else {
654                             try_again = confirm([locale.getString('upload.error.all.text'),
655                                     locale.getString('upload.error.all.more')],
656                                     locale.getString('upload.error.all.title'),
657                                     locale.getString('upload.error.all.ok'),
658                                     locale.getString('upload.error.all.cancel'));
659                     }
660         }
661        
662                 // Hide the progress bar now that the user has realized we're done
663                 document.getElementById('progress_bar').style.width = '0';
664                 document.getElementById('footer').style.display = 'none';
665
666                 // If requested, open the site
667                 if (go_to_flickr) {
668                         launch_browser('http://' + SITE_HOST + '/photos/upload/done/?b=' +
669                                 upload.timestamps.earliest + '-' + upload.timestamps.latest +
670                                 '-' + users.nsid);
671                 }
672
673                 // Really finally actually done, so reset
674                 buttons.upload.enable();
675                 photos.uploading = [];
676                 photos.failed = [];
677                 photos.uploaded = [];
678                 photos.ok = 0;
679                 photos.fail = 0;
680                 photos.sets_fail = false;
681                 photos.kb.sent = 0;
682                 photos.kb.total = 0;
683                 try {
684                     threads.uploadr.shutdown();
685                     }
686                     catch(ex){}
687                 upload.progress_bar = null;
688                 threads.uploadr = Cc['@mozilla.org/thread-manager;1'].getService().newThread(0);
689                 upload.cancel = false;
690                 upload.tickets = {};
691                 upload.tickets_count = 0;
692                 upload.tickets_delta = 1000;
693                 upload.tickets_handle = null;
694                 upload.timestamps.earliest = 0;
695                 upload.timestamps.latest = 0;
696                 upload.stats.photos = 0;
697                 upload.stats.bytes = 0;
698                 upload.stats.errors = 0;
699                 unblock_exit();
700
701                 // Try again without deleting the list of <photoid>s
702                 if (try_again) {
703
704                         // Queue 'em up
705                         var ii = upload.try_again.length;
706                         photos.ready = [[]];
707                         photos.ready_size = [0];
708                         for (var i = 0; i < ii; ++i) {
709                                 photos.ready[0].push(upload.try_again[i]);
710                                 photos.ready_size[0] += upload.try_again[i].size;
711                         }
712
713                         threads.worker.dispatch(new RetryUpload(true),
714                                 threads.worker.DISPATCH_NORMAL);
715                 }
716
717                 // Otherwise drop the recovered photos into the current batch
718                 else { photos.add(upload.try_again); }
719                 upload.try_again = [];
720
721         }
722
723 };
724
725 var Upload = function(params, id) {
726         this.params = params;
727         this.id = id;
728 }
729 Upload.prototype = {
730         run: function() {
731                 if (conf.console.upload) {
732                         logStringMessage('UPLOAD: ' + this.params.toSource());
733                 }
734                 var esc_params = api.escape_and_sign(this.params, true);
735         try {
736                     // Stream containing the entire HTTP POST payload
737                     var boundary = '------deadbeef---deadbeef---' + Math.random();
738                     var mstream = Cc['@mozilla.org/io/multiplex-input-stream;1']
739                             .createInstance(Ci.nsIMultiplexInputStream);
740                     var sstream;
741                     for (var p in esc_params) {
742                             sstream = Cc['@mozilla.org/io/string-input-stream;1']
743                                     .createInstance(Ci.nsIStringInputStream);
744                             sstream.setData('--' + boundary +
745                                     '\r\nContent-Disposition: form-data; name="' + p + '"',
746                                     -1);
747                             mstream.appendStream(sstream);
748                             if ('object' == typeof esc_params[p] &&
749                                     null != esc_params[p]) {
750                                     sstream = Cc['@mozilla.org/io/string-input-stream;1']
751                                             .createInstance(Ci.nsIStringInputStream);
752                                     sstream.setData('; filename="' + esc_params[p].filename +
753                                             '"\r\nContent-Type: application/octet-stream\r\n\r\n',
754                                             -1);
755                                     mstream.appendStream(sstream);
756                                     var file = Cc['@mozilla.org/file/local;1']
757                                             .createInstance(Ci.nsILocalFile);
758                                     file.initWithPath(esc_params[p].path);
759                                     var fstream =
760                                             Cc['@mozilla.org/network/file-input-stream;1']
761                                             .createInstance(Ci.nsIFileInputStream);
762                                     fstream.init(file, 1, 1,
763                                             Ci.nsIFileInputStream.CLOSE_ON_EOF);
764                                     var bstream =
765                                             Cc['@mozilla.org/network/buffered-input-stream;1']
766                                             .createInstance(Ci.nsIBufferedInputStream);
767                                     bstream.init(fstream, 4096);
768                                     mstream.appendStream(bstream);
769                                     sstream = Cc['@mozilla.org/io/string-input-stream;1']
770                                             .createInstance(Ci.nsIStringInputStream);
771                                     sstream.setData('\r\n', -1);
772                                     mstream.appendStream(sstream);
773                             } else {
774                                     sstream = Cc['@mozilla.org/io/string-input-stream;1']
775                                             .createInstance(Ci.nsIStringInputStream);
776                                     sstream.setData('\r\n\r\n' + esc_params[p] + '\r\n', -1);
777                                     mstream.appendStream(sstream);
778                             }
779                     }
780                     sstream = Cc['@mozilla.org/io/string-input-stream;1']
781                             .createInstance(Ci.nsIStringInputStream);
782                     sstream.setData('--' + boundary + '--\r\n', -1);
783                     mstream.appendStream(sstream);
784                     upload.progress_total = mstream.available() >> 10;
785
786                     // Headers!
787                     sstream = Cc['@mozilla.org/io/string-input-stream;1']
788                             .createInstance(Ci.nsIStringInputStream);
789                     sstream.setData('POST /services/upload/ HTTP/1.1\r\n' +
790                             'Host: ' + UPLOAD_HOST + '\r\n' +
791                             'User-Agent: Flickr Uploadr ' + conf.version + '\r\n' +
792                             'Content-Length: ' + mstream.available() + '\r\n' +
793                             'Content-Type: multipart/form-data; boundary=' + boundary +
794                             '\r\n\r\n', -1);
795                     mstream.insertStream(sstream, 0);
796
797                     // POST over a raw socket connection
798                     //   http://www.xulplanet.com/tutorials/mozsdk/sockets.php
799                
800                         var service =
801                                 Cc['@mozilla.org/network/socket-transport-service;1']
802                                 .getService(Ci.nsISocketTransportService);
803                         var transport = service.createTransport(null, 0,
804                                 UPLOAD_HOST, 80, null);
805                         var ostream = transport.openOutputStream(
806                                 Ci.nsITransport.OPEN_BLOCKING, 0, 0);
807                         var a = 0;
808                         while (!upload.cancel && (a = mstream.available())) {
809                                 ostream.writeFrom(mstream, Math.min(a, 8192));
810                                 threads.main.dispatch(new UploadProgressCallback(a, this.id),
811                                     threads.main.DISPATCH_NORMAL);
812                         }
813                         if(upload.cancel) {
814                             ostream.close();
815                             threads.main.dispatch(new UploadDoneCallback(
816                                false, this.id), threads.main.DISPATCH_NORMAL);
817                                return; // we are done
818                          }
819                         var _istream = transport.openInputStream(0,0,0);
820                         var istream = Cc['@mozilla.org/scriptableinputstream;1']
821                                 .createInstance(Ci.nsIScriptableInputStream);
822                         istream.init(_istream);
823                         var pump = Cc['@mozilla.org/network/input-stream-pump;1']
824                                 .createInstance(Ci.nsIInputStreamPump);
825                         pump.init(_istream, -1, -1, 0, 0, false);
826                         pump.asyncRead({
827                                 id: this.id,
828                                 content_length: null,
829                                 raw: '',
830                                 onStartRequest: function(request, context) {},
831                                 onStopRequest: function(request, context, status) {
832                                         istream.close();
833                                         ostream.close();
834                                 },
835
836                                 // Docs are slim so I'm not sure if this gets called only
837                                 // once per request or perhaps multiple times
838                                 //   The code can handle whatever
839                                 onDataAvailable: function(request, context,
840                                         stream, offset, count) {
841                                         this.raw += istream.read(count);
842
843                                         // If we've received all of the headers, grab the
844                                         // content length and drop the headers
845                                         if (!this.content_length
846                                                 && this.raw.match(/\r?\n\r?\n/)) {
847                                                 var match = this.raw.match(
848                                                         /^Content-Length:\s*([0-9]+)$/mi);
849                                                 if (match) {
850                                                         this.content_length = match[1];
851                                                         this.raw = this.raw.split(/\r?\n\r?\n/)[1];
852                                                 }
853                                         }
854
855                                         // Nothing left to do if we don't know the length
856                                         if (!this.content_length) { return; }
857
858                                         // Also nothing more to do if there's still data coming
859                                         if (this.raw.length != this.content_length) { return; }
860
861                                         // Dispatch to the UI as soon as we have the entire
862                                         // payload
863                                         threads.main.dispatch(new UploadDoneCallback(
864                                                 this.raw, this.id), threads.main.DISPATCH_NORMAL);
865
866                                 },
867                         }, null);
868                 } catch (err) {
869                         Components.utils.reportError(err);
870                         threads.main.dispatch(new UploadDoneCallback(
871                             false, this.id), threads.main.DISPATCH_NORMAL);
872                 }
873
874         },
875         QueryInterface: function(iid) {
876                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
877                         return this;
878                 }
879                 throw Components.results.NS_ERROR_NO_INTERFACE;
880         }
881 };
882
883 var UploadProgressCallback = function(available, id) {
884         this.available = available;
885         this.id = id;
886 }
887 UploadProgressCallback.prototype = {
888         run: function() {
889             if(!threads.main.hasPendingEvents()) { // if the thread is already doing something no need to overload it for progress
890                     upload.progress2(this.available, this.id);
891                 }
892         },
893         QueryInterface: function(iid) {
894                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
895                         return this;
896                 }
897                 throw Components.results.NS_ERROR_NO_INTERFACE;
898         }
899 };
900
901 var UploadDoneCallback = function(raw, id) {
902         this.raw = raw;
903         this.id = id;
904 }
905 UploadDoneCallback.prototype = {
906         run: function() {
907                 if (conf.console.upload) {
908                         logStringMessage('UPLOAD DONE: ' + this.raw
909                                 + ' upload cancelled : ' + upload.cancel + ' ui cancel :  ' + ui.cancel);
910                 }
911                 // Try to parse the response but fail gracefully
912                 var rsp = false;
913                 if(this.raw) {
914                     try {
915                             var parser = Cc['@mozilla.org/xmlextras/domparser;1']
916                                     .createInstance(Ci.nsIDOMParser);
917                             rsp = parser.parseFromString(this.raw,
918                                     'text/xml').documentElement;
919                     } catch (err) {
920                             Components.utils.reportError(err);
921                     }
922                 }
923
924                 upload._start(rsp, this.id);
925         },
926         QueryInterface: function(iid) {
927                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
928                         return this;
929                 }
930                 throw Components.results.NS_ERROR_NO_INTERFACE;
931         }
932 };
Note: See TracBrowser for help on using the browser.