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

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