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

Revision 453, 26.9 kB (checked in by jdecq, 1 year ago)

add "sign in" button in place of upload button for signed out users.

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