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

Revision 641, 20.3 kB (checked in by jdecq, 5 months ago)

new temporary build, additional command line handler modification to retrieve frob, GM version matching site...

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