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

Revision 456, 16.4 kB (checked in by jdecq, 1 year ago)

case 7620: support for another time format string.
hopefully there aren't too many out there

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 var threads = {
12
13     initialized: false,
14         // Hooks to threads
15         worker: null,
16         uploadr: null,
17         main: null,
18
19         // GraphicsMagick XPCOM object
20         gm: null,
21        
22         // Create thread hooks and instantiate GraphicsMagick
23         init: function() {
24                 try {
25                         // Threads themselves
26                         var t = Cc['@mozilla.org/thread-manager;1'].getService();
27                         threads.worker = t.newThread(0);
28                         threads.uploadr = t.newThread(0);
29                         threads.main = t.mainThread;
30
31                         // GraphicsMagick, for use on the worker thread
32                         threads.gm = Cc['@flickr.com/gm;1'].createInstance(Ci.flIGM);
33                         threads.gm.init(Cc['@mozilla.org/file/directory_service;1']
34                                 .getService(Ci.nsIProperties)
35                                 .get('resource:app', Ci.nsIFile).path);
36             threads.initialized = true;
37                 } catch (err) {
38                         Components.utils.reportError(err);
39                 }
40         }
41
42 };
43
44 // Thumbnail thread wrapper
45 var Thumb = function(id, thumb_size, path, auto_select) {
46         this.id = id;
47         this.thumb_size = thumb_size;
48         this.path = path;
49         this.auto_select = null == auto_select ? false : auto_select;
50 };
51 Thumb.prototype = {
52         run: function() {
53             if(photos.thumb_cancel === true)
54                 return;
55                 var result = '';
56
57                 try {
58                         // Thumbnail your photos
59                         if (photos.is_photo(this.path)) {
60                                 result = threads.gm.thumb(this.thumb_size, this.path);
61                         }
62
63                         // But get a keyframe for videos
64                         else if (photos.is_video(this.path)) {
65                                 result = threads.gm.keyframe(this.thumb_size, this.path);
66                         }
67
68                 }
69
70                 // The nerdy error message
71                 catch (err) {
72                         Components.utils.reportError(err);
73                 }
74
75                 // Phone home to the UI
76                 threads.main.dispatch(new ThumbCallback(this.id, result,
77                         this.auto_select), threads.main.DISPATCH_NORMAL);
78
79         },
80         QueryInterface: function(iid) {
81                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
82                         return this;
83                 }
84                 throw Components.results.NS_ERROR_NO_INTERFACE;
85         }
86 };
87 var ThumbCallback = function(id, result, auto_select) {
88         this.id = id;
89         this.result = result;
90         this.auto_select = auto_select;
91 };
92 ThumbCallback.prototype = {
93         run: function() {
94         if (photos.thumb_cancel === true)
95             return;
96                 try {
97                         unblock_normalize();
98                         if (conf.console.thumb) {
99                                 Cc['@mozilla.org/consoleservice;1']
100                                         .getService(Ci.nsIConsoleService)
101                                         .logStringMessage('GM THUMB: ' + this.result);
102                         }
103
104                         // Parse the returned string
105                         //   <orient>###<width>###<height>###<date_taken>###<thumb_width>###<thumb_height>###<thumb_path>###<title>###<description>###<tags>
106                         var thumb = this.result.split('###');
107
108                         // Get this photo from the DOM and remove its loading class
109                        
110                        
111                         var li = document.getElementById('photo' + this.id);
112                         var oldImg = li.getElementsByTagName('img')[0];
113                         var img = document.createElementNS(NS_HTML, 'img');
114                         li.replaceChild(img, oldImg);
115
116                         // If successful, replace with the thumb and update the
117                         // Photo object
118                         if (7 <= thumb.length) {
119
120                                 // Undo escaping done in XPCOM
121                                 var ii = thumb.length;
122                                 for (var i = 0; i < ii; ++i) {
123                                         thumb[i] = thumb[i];
124                                 }
125
126                                 // Orientation (for photos) or duration (for videos)
127                                 //   Orientation is currently unused
128                                 if (photos.is_video(photos.list[this.id].path)) {
129                                         photos.list[this.id].duration = parseInt(thumb[0]);
130                                 }
131
132                                 // Width and height
133                                 photos.list[this.id].width = parseInt(thumb[1]);
134                                 photos.list[this.id].height = parseInt(thumb[2]);
135
136                                 // Date taken
137                                 if (/\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2}/.test(thumb[3])) {
138                                         photos.list[this.id].date_taken = thumb[3];
139                                 } else if(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(thumb[3])) { //yes you can get "2008-03-15T11:39:09.00-07:00" as well!
140                                     photos.list[this.id].date_taken = thumb[3].replace(/-/g, ':').replace(/T/, ' ');
141                                 } else {
142                                         var f = Cc['@mozilla.org/file/local;1'].createInstance(
143                                                 Ci.nsILocalFile);
144                                         f.initWithPath(photos.list[this.id].path);
145                                         var mod = new Date(f.lastModifiedTime);
146                                         var month = mod.getMonth();
147                                         if (10 > month) month = '0' + month;
148                                         var day = mod.getDate();
149                                         if (10 > day) day = '0' + day;
150                                         var hours = mod.getHours();
151                                         if (10 > hours) hours = '0' + hours;
152                                         var minutes = mod.getMinutes();
153                                         if (10 > minutes)  minutes = '0' + minutes;
154                                         var seconds = mod.getSeconds();
155                                         if (10 > seconds) seconds = '0' + seconds;
156                                         photos.list[this.id].date_taken = mod.getFullYear() +
157                                                 ':' + month + ':' + day + ' ' + hours + ':' +
158                                                 minutes + ':' + seconds;
159                                 }
160
161                                 // Thumbnail
162                                 photos.list[this.id].thumb_width = parseInt(thumb[4]);
163                                 photos.list[this.id].thumb_height = parseInt(thumb[5]);
164                                 img.setAttribute('width', thumb[4]);
165                                 img.setAttribute('height', thumb[5]);
166                                 var thumbPath = thumb[6].replace(/^\s+|\s+$/g, '')
167                                         .replace(/\{---THREE---POUND---DELIM---\}/g, '###');
168                                 img.src = 'file:///' + escape(thumbPath);
169                                 photos.list[this.id].thumb = thumbPath;
170
171                                 // Make video icons for videos
172                                 //   This will look funny for a portrait-oriented video
173                                 if (photos.is_video(photos.list[this.id].path)) {
174                                         var icon = document.createElementNS(NS_HTML, 'img');
175                                         icon.setAttribute('width', 11);
176                                         icon.setAttribute('height', 11);
177                                         icon.src = 'chrome://uploadr/skin/icon_video.png';
178                                         icon.style.position = 'absolute';
179                                         icon.style.margin = '-20px 0 0 7px';
180                                         img.parentNode.appendChild(icon);
181                                 }
182
183                                 // Title/tags/description
184                                 if ('' == photos.list[this.id].title) {
185                                         var title = thumb[7] ?
186                                                 thumb[7].replace(/^\s+|\s+$/, '')
187                                                 .replace(/\{---THREE---POUND---DELIM---\}/g, '###')
188                                                 .replace(/^lang="[^"]+"\s*/g, '') : '';
189                                         if ('' == title) {
190                                                 title = photos.list[this.id].filename.split(
191                                                         /(.+)\.[a-z0-9]{3,4}/i);
192                                                 photos.list[this.id].title = title[1];
193                                         } else {
194                                                 photos.list[this.id].title = title;
195                                         }
196                                 }
197                                 if ('' == photos.list[this.id].description) {
198                                         var desc = thumb[8] ?
199                                                 thumb[8].replace(/^\s+|\s+$/g, '')
200                                                 .replace(/\{---THREE---POUND---DELIM---\}/g, '###')
201                                                 .replace(/^lang="[^"]+"\s*/g, '') : '';
202
203                                         // Copy the site's rules for bad descriptions
204                                         if ('DCF 1.0' == desc ||
205                                                 'Samsung' == desc ||
206                                                 'Nucam Tulip Project' == desc ||
207                                                 'IslBG' == desc ||
208                                                 'SONY DSC' == desc ||
209                                                 'Pentax Image' == desc ||
210                                                 /^(?:OLYMPUS|(?:KONICA )?MINOLTA|SANYO) DIGITAL CAMERA$/
211                                                 .test(desc) ||
212                                                 /^\d{6}_\d{4}(?:\~\d+)?$/.test(desc) ||
213                                                 /^Copyright \(c\) \d+ Hewlett-Packard Company$/
214                                                 .test(desc) ||
215                                                 /^Autosave-File.*AgfaPhoto.*$/.test(desc)) {
216                                                 photos.list[this.id].description = '';
217                                         }
218
219                                         // Passed the test
220                                         else {
221                                                 photos.list[this.id].description = desc;
222                                         }
223
224                                 }
225                                 if ('' == photos.list[this.id].tags) {
226                                         photos.list[this.id].tags = thumb[9] ? thumb[9]
227                                                 .replace(/^\s+|\s+$/g, '')
228                                                 .replace(/\{---THREE---POUND---DELIM---\}/g, '###')
229                                                 : '';
230                                 }
231
232                                 // Select newly added images if the user hasn't clicked
233                                 if (meta.auto_select || this.auto_select) {
234                                         mouse.click({
235                                                 target: img,
236                                                 ctrlKey: true,
237                                                 metaKey: true,
238                                                 shiftKey: false
239                                         });
240                                 }
241
242                                 // If only one photo is selected, refresh the other
243                                 // thumbnail, too
244                                 if (1 == photos.selected.length
245                                         && this.id == photos.selected[0] && !meta.first) {
246                                         document.getElementById('meta_div')
247                                                 .getElementsByTagName('img')[0].src = img.src;
248                                 }
249
250                                 // Calculate file size
251                                 photos.list[this.id].size = file.size(
252                                         photos.list[this.id].path);
253                                 photos.batch_size += photos.list[this.id].size;
254                                 ui.bandwidth_updated();
255
256                         }
257
258                         // If unsuccessful, replace with the error image
259                         else {
260                                 img.setAttribute('src',
261                                         'chrome://uploadr/skin/icon_alert.png');
262                                 img.setAttribute('width', 16);
263                                 img.setAttribute('height', 16);
264                                 img.className = 'error';
265                                 img.parentNode.appendChild(document.createTextNode(
266                                         photos.list[this.id].filename));
267                                 --photos.count;
268                                 ++photos.errors;
269                                 img.onclick = function() {
270                                         this.parentNode.parentNode.removeChild(this.parentNode);
271                                         photos.normalize();
272                                         --photos.errors;
273                                         if (0 == photos.count + photos.errors) {
274                                                 document.getElementById('t_clear').className = 'disabled_button';
275                                                 document.getElementById('photos_init')
276                                                         .style.display = '-moz-box';
277                                         }
278                                 };
279                                 Components.utils.reportError(this.result);
280                         }
281
282                         // After updating, make it visible again
283                         img.style.visibility = 'visible';
284
285                 } catch (err) {
286                         Components.utils.reportError(err);
287                 }
288
289                 // Tell extensions that we got a new thumbnail
290                 extension.after_thumb.exec(this.id);
291
292                 unblock_sort();
293                 unblock_remove();
294         },
295         QueryInterface: function(iid) {
296                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
297                         return this;
298                 }
299                 throw Components.results.NS_ERROR_NO_INTERFACE;
300         }
301 };
302
303 // Rotate thread wrapper
304 var Rotate = function(id, degrees, thumb_size, path) {
305         this.id = id;
306         this.degrees = degrees;
307         this.thumb_size = thumb_size;
308         this.path = path;
309 };
310 Rotate.prototype = {
311         run: function() {
312                 try {
313
314                         // Rotate and if successful re-thumb the image
315                         var result = threads.gm.rotate(this.degrees, this.path);
316
317                         // Parse the returned string
318                         //   ok<path>
319                         var rotate = result.match(/^ok(.*)$/);
320
321                         if (null == rotate) {
322                                 Components.utils.reportError(result);
323                         } else {
324                                 threads.main.dispatch(new RotateCallback(this.id, rotate[1]),
325                                         threads.main.DISPATCH_NORMAL);
326                                 result = threads.gm.thumb(this.thumb_size,
327                                         rotate[1]);
328                                 threads.main.dispatch(new ThumbCallback(this.id, result,
329                                         conf.auto_select_after_rotate),
330                                         threads.main.DISPATCH_NORMAL);
331                         }
332
333                 } catch (err) {
334                         Components.utils.reportError(err);
335                 }
336         },
337         QueryInterface: function(iid) {
338                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
339                         return this;
340                 }
341                 throw Components.results.NS_ERROR_NO_INTERFACE;
342         }
343 };
344 var RotateCallback = function(id, path) {
345         this.id = id;
346         this.path = path;
347 };
348 RotateCallback.prototype = {
349         run: function() {
350                 unblock_normalize();
351                 photos.list[this.id].path = this.path;
352
353                 // Tell extensions that this photo was edited (rotated)
354                 extension.after_edit.exec([this.id]);
355
356         },
357         QueryInterface: function(iid) {
358                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
359                         return this;
360                 }
361                 throw Components.results.NS_ERROR_NO_INTERFACE;
362         }
363 };
364
365 // Sorting thread wrapper
366 //   The sorting all happens in the UI thread but this empty background job ensures it
367 //   happens after all the added photos have been processed
368 var Sort = function() {
369 };
370 Sort.prototype = {
371         run: function() {
372                 try {
373
374                         // The background job is just to ensure the sort only happens after the last
375                         // photo has been processed - go back to the UI thread
376                         threads.main.dispatch(new SortCallback(), threads.main.DISPATCH_NORMAL);
377
378                 } catch (err) {
379                         Components.utils.reportError(err);
380                 }
381         },
382         QueryInterface: function(iid) {
383                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
384                         return this;
385                 }
386                 throw Components.results.NS_ERROR_NO_INTERFACE;
387         }
388 };
389 var SortCallback = function() {
390 };
391 SortCallback.prototype = {
392         run: function() {
393
394                 // Allow blocking sorts during loading
395                 if (0 != _block_sort) { return; }
396
397                 // Perform the sort
398                 if (1 >= photos.list.length) {
399                         if (1 == photos.list.length) { buttons.upload.enable(); }
400                         unblock_normalize();
401                         return;
402                 }
403                 var p = [];
404                 for each (var photo in photos.list) {
405                         if (null != photo) {
406                                 p.push({
407                                         id: photo.id,
408                                         date_taken: photo.date_taken
409                                 });
410                         }
411                 }
412                 p.sort(function(a, b) {
413                         return a.date_taken > b.date_taken;
414                 });
415
416                 // Lazily do the UI refresh by appendChild'ing everything in the
417                 // right order
418                 //   This is far from being a bottleneck, so leave it alone
419                 //   until it is
420                 var list = document.getElementById('photos_list');
421                 for (var i = p.length - 1; i >= 0; --i) {
422                         if (null != p[i]) {
423                                 list.appendChild(document.getElementById('photo' + p[i].id));
424                         }
425                 }
426                 unblock_normalize();
427                 photos.normalize();
428
429                 // And finally allow them to upload
430                 buttons.upload.enable();
431                 meta.first = false;
432
433                 // Tell extensions that photos were sorted
434                 extension.after_reorder.exec(false);
435
436         },
437         QueryInterface: function(iid) {
438                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
439                         return this;
440                 }
441                 throw Components.results.NS_ERROR_NO_INTERFACE;
442         }
443 };
444
445 var Resize = function(id, square, path) {
446         this.id = id;
447         this.square = square;
448         this.path = path;
449 };
450 Resize.prototype = {
451         run: function() {
452                 try {
453
454                         // Resize the image and callback to the UI thread
455                         var result = threads.gm.resize(this.square, this.path);
456                         threads.main.dispatch(new ResizeCallback(this.id, result),
457                                 threads.main.DISPATCH_NORMAL);
458
459                 } catch (err) {
460                         Components.utils.reportError(err);
461                 }
462         },
463         QueryInterface: function(iid) {
464                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
465                         return this;
466                 }
467                 throw Components.results.NS_ERROR_NO_INTERFACE;
468         }
469 };
470 var ResizeCallback = function(id, result) {
471         this.id = id;
472         this.result = result;
473 };
474 ResizeCallback.prototype = {
475         run: function() {
476                 try {
477
478                         // Parse the returned string
479                         //   <width>x<height><path>
480                         var resize = this.result.match(/^([0-9]+)x([0-9]+)(.+)$/);
481
482                         if (null == resize) {
483                                 Components.utils.reportError(this.result);
484                         } else {
485                                 list = photos.ready[photos.ready.length - 1];
486
487                                 // Update photo properties
488                                 list[this.id].width = resize[1];
489                                 list[this.id].height = resize[2];
490                                 list[this.id].path = resize[3];
491
492                                 // Update bandwidth
493                                 var size = file.size(resize[3]);
494                                 photos.ready_size[photos.ready.length - 1] += size;
495                                 list[this.id].size = size;
496
497                         }
498                 } catch (err) {
499                         Components.utils.reportError(err);
500                 }
501         },
502         QueryInterface: function(iid) {
503                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
504                         return this;
505                 }
506                 throw Components.results.NS_ERROR_NO_INTERFACE;
507         }
508 };
509
510 // Job to enable uploads that can follow a bunch of jobs in the queue
511 var EnableUpload = function() {
512 };
513 EnableUpload.prototype = {
514         run: function() {
515                 threads.main.dispatch(new EnableUploadCallback(), threads.main.DISPATCH_NORMAL);
516         },
517         QueryInterface: function(iid) {
518                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
519                         return this;
520                 }
521                 throw Components.results.NS_ERROR_NO_INTERFACE;
522         }
523 };
524 var EnableUploadCallback = function() {
525 };
526 EnableUploadCallback.prototype = {
527         run: function() {
528                 unblock_normalize();
529                 buttons.upload.enable();
530                 meta.first = false;
531         },
532         QueryInterface: function(iid) {
533                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
534                         return this;
535                 }
536                 throw Components.results.NS_ERROR_NO_INTERFACE;
537         }
538 };
539
540
541 // Retry an upload batch after we finish resizing
542 var RetryUpload = function(from_ready) {
543         this.from_ready = from_ready;
544 };
545 RetryUpload.prototype = {
546         run: function() {
547
548                 // As with Sort, the background job here is just for ordering
549                 threads.main.dispatch(new RetryUploadCallback(this.from_ready),
550                         threads.main.DISPATCH_NORMAL);
551
552         },
553         QueryInterface: function(iid) {
554                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
555                         return this;
556                 }
557                 throw Components.results.NS_ERROR_NO_INTERFACE;
558         }
559 };
560 var RetryUploadCallback = function(from_ready) {
561         this.from_ready = from_ready;
562 };
563 RetryUploadCallback.prototype = {
564         run: function() {
565
566                 // Take a batch from the ready queue
567                 if (this.from_ready && 0 != photos.ready.length) {
568                         photos.upload(photos.ready.shift(),
569                                 photos.ready_size.shift());
570                 }
571
572                 // Take the batch in the UI
573                 else { photos.upload(); }
574
575         },
576         QueryInterface: function(iid) {
577                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
578                         return this;
579                 }
580                 throw Components.results.NS_ERROR_NO_INTERFACE;
581         }
582 };
583
584 // Job to force ordering of photo._add calls for dock.xul
585 var PhotoAdd = function(path) {
586         this.path = path;
587 };
588 PhotoAdd.prototype = {
589         run: function() {
590                 threads.main.dispatch(new PhotoAddCallback(this.path),
591                         threads.main.DISPATCH_NORMAL);
592         },
593         QueryInterface: function(iid) {
594                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
595                         return this;
596                 }
597                 throw Components.results.NS_ERROR_NO_INTERFACE;
598         }
599 };
600 var PhotoAddCallback = function(path) {
601         this.path = path;
602 };
603 PhotoAddCallback.prototype = {
604         run: function() {
605                 photos.add([this.path]);
606         },
607         QueryInterface: function(iid) {
608                 if (iid.equals(Ci.nsIRunnable) || iid.equals(Ci.nsISupports)) {
609                         return this;
610                 }
611                 throw Components.results.NS_ERROR_NO_INTERFACE;
612         }
613 };
Note: See TracBrowser for help on using the browser.