sbCoverHelper.jsm
Go to the documentation of this file.
1 /*
2  *=BEGIN SONGBIRD GPL
3  *
4  * This file is part of the Songbird web player.
5  *
6  * Copyright(c) 2005-2010 POTI, Inc.
7  * http://www.songbirdnest.com
8  *
9  * This file may be licensed under the terms of of the
10  * GNU General Public License Version 2 (the ``GPL'').
11  *
12  * Software distributed under the License is distributed
13  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
14  * express or implied. See the GPL for the specific language
15  * governing rights and limitations.
16  *
17  * You should have received a copy of the GPL along with this
18  * program. If not, go to http://www.gnu.org/licenses/gpl.html
19  * or write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  *
22  *=END SONGBIRD GPL
23  */
24 
25 EXPORTED_SYMBOLS = [ "sbCoverHelper" ];
26 
27 const Cc = Components.classes;
28 const Ci = Components.interfaces;
29 const Cr = Components.results;
30 const Ce = Components.Exception;
31 const Cu = Components.utils;
32 
33 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
34 Cu.import("resource://app/jsmodules/StringUtils.jsm");
35 Cu.import("resource://app/jsmodules/sbProperties.jsm");
36 Cu.import("resource://app/jsmodules/ArrayConverter.jsm");
37 Cu.import("resource://app/jsmodules/sbLibraryUtils.jsm");
38 Cu.import("resource://app/jsmodules/SBJobUtils.jsm");
39 Cu.import("resource://app/jsmodules/SBUtils.jsm");
40 
41 // File operation constants for init (-1 is default mode)
42 const FLAGS_DEFAULT = -1;
43 
44 // Default maximum size of image files we allow.
45 // This is defined as the maximum size of a FRAME in the id3v2 spec.
46 // We are defaulting to this since we only read/write id3v2 tags for album art.
47 const MAX_FILE_SIZE_BYTES = 16777216;
48 
49 var sbCoverHelper = {
50 
61  isImageSizeValid: function (aImageURL, aImageSize) {
62  var Application = Cc["@mozilla.org/fuel/application;1"]
63  .getService(Ci.fuelIApplication);
64  var maxFileSize = Application.prefs.getValue("songbird.albumart.maxsize",
66 
67  if ( (aImageURL == 'undefined') &&
68  (aImageSize == 'undefined') ) {
69  // We need something to compare so fail
70  return false;
71  }
72 
73  // Default to aImageSize unless aImageURL is defined.
74  var checkFileSize = aImageSize;
75  if (aImageURL) {
76  // Otherwise open the file and check the size
77  var ioService = Cc["@mozilla.org/network/io-service;1"]
78  .getService(Ci.nsIIOService);
79  var uri = null;
80  try {
81  uri = ioService.newURI(aImageURL, null, null);
82  } catch (err) {
83  Cu.reportError("sbCoverHelper: Unable to convert to URI: [" +
84  aImageURL + "] " + err);
85  return false;
86  }
87 
88  if (uri instanceof Ci.nsIFileURL) {
89  var imageFile = uri.file;
90  checkFileSize = imageFile.fileSize;
91  }
92  }
93 
94  if (checkFileSize > maxFileSize) {
95  // Inform the user that this file is too big.
96  var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"]
97  .getService(Ci.nsIPromptService);
98 
99  storageConverter =
100  Cc["@songbirdnest.com/Songbird/Properties/UnitConverter/Storage;1"]
101  .createInstance(Ci.sbIPropertyUnitConverter);
102  var strTitle = SBString("albumart.maxsize.title", null);
103  var strMsg = SBBrandedFormattedString
104  ("albumart.maxsize.message",
105  [ storageConverter.autoFormat(maxFileSize, -1, 1),
106  storageConverter.autoFormat(checkFileSize, -1, 1) ]);
107 
108  promptService.alert(null, strTitle, strMsg);
109  return false;
110  }
111  return true;
112  },
113 
119  readImageData: function (aInputFile) {
120  if(!aInputFile.exists()) {
121  return null;
122  }
123 
124  try {
125  // Try and read the mime type from the file
126  var newMimeType = Cc["@mozilla.org/mime;1"]
127  .getService(Ci.nsIMIMEService)
128  .getTypeFromFile(aInputFile);
129 
130  // Read in the data
131  var inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
132  .createInstance(Ci.nsIFileInputStream);
133  inputStream.init(aInputFile, FLAGS_DEFAULT, FLAGS_DEFAULT, 0);
134 
135  // Read from a binaryStream so we can get primitive data (bytes)
136  var binaryStream = Cc["@mozilla.org/binaryinputstream;1"]
137  .createInstance(Ci.nsIBinaryInputStream);
138  binaryStream.setInputStream(inputStream);
139  var size = binaryStream.available();
140  var newImageData = binaryStream.readByteArray(size);
141  binaryStream.close();
142 
143  // Make sure we read as many bytes as we expected to.
144  if (newImageData.length != size) {
145  return null;
146  }
147 
148  return [newImageData, newMimeType];
149  } catch (err) {
150  Cu.reportError("sbCoverHelper: Unable to read file image data: " + err);
151  }
152 
153  return null;
154  },
155 
163  saveFileToArtworkFolder: function (aFromFile) {
164  if ( !(aFromFile instanceof Ci.nsIFile)) {
165  return null;
166  }
167 
168  // Make sure this is a valid image file
169  if (!this.isFileImage(aFromFile)) {
170  return null;
171  }
172 
173  // First check that we do not exceed the maximum file size.
174  if (!this.isImageSizeValid(null, aFromFile.fileSize)) {
175  return null;
176  }
177 
178  try {
179  var imageData;
180  var mimeType;
181  [imageData, mimeType] = this.readImageData(aFromFile);
182 
183  // Save the image data out to the new file in the artwork folder
184  var artService = Cc["@songbirdnest.com/Songbird/album-art-service;1"]
185  .getService(Ci.sbIAlbumArtService);
186  var newUri = artService.cacheImage(mimeType, imageData, imageData.length);
187  if (newUri) {
188  return newUri.spec;
189  }
190  } catch (err) {
191  Cu.reportError("sbCoverHelper: Unable to save file image data: " + err);
192  }
193 
194  return null;
195  },
196 
203  isFileImage: function(aFile) {
204  var isSafe = false;
205 
206  if (aFile instanceof Ci.nsIFile) {
207  var prefs = Cc["@mozilla.org/preferences-service;1"]
208  .getService(Ci.nsIPrefBranch);
209  var prefStr = prefs.getCharPref("songbird.albumart.file.extensions");
210  var supportedFileExtensions = prefStr.split(",");
211 
212  var length = supportedFileExtensions.length;
213  var filename = aFile.QueryInterface(Ci.nsIFile).leafName;
214  var result = filename.match(/.*\.(\w*)$/);
215  var extension = (result ? result[1] : null);
216  if (extension) {
217  isSafe = (supportedFileExtensions.indexOf(extension) != -1);
218  }
219  }
220 
221  return isSafe;
222  },
223 
230  downloadFile: function (aWebURL, aCallback) {
231  // The tempFile we are saving to.
232  var tempFile = Cc["@mozilla.org/file/directory_service;1"]
233  .getService(Ci.nsIProperties)
234  .get("TmpD", Ci.nsIFile);
235 
236  var self = this;
237  var webProgressListener = {
238  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener]),
239 
240  /* nsIWebProgressListener methods */
241  // No need to implement anything in these functions
242  onLocationChange : function (a, b, c) { },
243  onProgressChange : function (a, b, c, d, e, f) { },
244  onSecurityChange : function (a, b, c) { },
245  onStatusChange : function (a, b, c, d) { },
246  onStateChange : function (aWebProgress, aRequest, aStateFlags, aStatus) {
247  // when the transfer is complete...
248  if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
249  if (aStatus == 0) {
250  var fileName = self.saveFileToArtworkFolder(tempFile);
251  aCallback(fileName);
252  } else { }
253  }
254  }
255  };
256 
257  // Create the temp file to download to
258  var extension = null;
259  try {
260  // try to extract the extension from download URL
261  // If this fails then the saveFileToArtworkFolder call will get it from
262  // the contents.
263  extension = aWebURL.match(/\.[a-zA-Z0-9]+$/)[0];
264  } catch(e) { }
265 
266  var uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
267  .getService(Ci.nsIUUIDGenerator);
268  var uuid = uuidGenerator.generateUUID();
269  var uuidFileName = uuid.toString();
270 
271  uuidFileName = uuidFileName + extension;
272  tempFile.append(uuidFileName);
273  tempFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0644);
274 
275  // Make sure it is deleted when we shutdown
276  var registerFileForDelete = Cc["@mozilla.org/uriloader/external-helper-app-service;1"]
277  .getService(Ci.nsPIExternalAppLauncher);
278  registerFileForDelete.deleteTemporaryFileOnExit(tempFile);
279 
280  // Download the file
281  var ioService = Cc["@mozilla.org/network/io-service;1"]
282  .getService(Ci.nsIIOService);
283  var webDownloader = Cc['@mozilla.org/embedding/browser/nsWebBrowserPersist;1']
284  .createInstance(Ci.nsIWebBrowserPersist);
285  webDownloader.persistFlags = Ci.nsIWebBrowserPersist.PERSIST_FLAGS_NONE;
286  webDownloader.progressListener = webProgressListener;
287  webDownloader.saveURI(ioService.newURI(aWebURL, null, null), // URL
288  null,
289  null,
290  null,
291  null,
292  tempFile); // File to save to
293  },
294 
298  getFlavours: function(flavours) {
299  flavours.appendFlavour("application/x-moz-file", "nsIFile");
300  flavours.appendFlavour("text/x-moz-url");
301  flavours.appendFlavour("text/uri-list");
302  flavours.appendFlavour("text/html");
303  flavours.appendFlavour("text/unicode");
304  flavours.appendFlavour("text/plain");
305  return flavours;
306  },
307 
315  handleDrop: function(aCallback, aDropData) {
316  switch(aDropData.flavour.contentType) {
317  case "text/html":
318  case "text/x-moz-url":
319  case "text/uri-list":
320  case "text/unicode":
321  case "text/plain":
322  // Get the url string
323  var url = "";
324  if (aDropData.data instanceof Ci.nsISupportsString) {
325  url = aDropData.data.toString();
326  } else {
327  url = aDropData.data;
328  }
329 
330  if (aDropData.flavour.contentType == "text/html") {
331  // Check the html code for images
332  // Find an image tag and then parse out the src attribute
333  var imgRegExpr = /\<img.+src=\"(.+?)\"/i;
334  var matches = url.match(imgRegExpr);
335  if (matches && matches.length > 1) {
336  url = matches[1];
337  }
338  } else {
339  // Only take the first url if there are more than one.
340  url = url.split("\n")[0];
341  }
342 
343  // Now convert it into an URI so we can determine what to do with it
344  var ioService = Cc["@mozilla.org/network/io-service;1"]
345  .getService(Ci.nsIIOService);
346  var uri = null;
347  try {
348  uri = ioService.newURI(url, null, null);
349  } catch (err) {
350  Cu.reportError("sbCoverHelper: Unable to convert to URI: [" + url +
351  "] " + err);
352  return;
353  }
354 
355  switch (uri.scheme) {
356  case 'file':
357  if (uri instanceof Ci.nsIFileURL) {
358  var fileName = this.saveFileToArtworkFolder(uri.file);
359  aCallback(fileName);
360  }
361  break;
362 
363  case 'http':
364  case 'https':
365  case 'ftp':
366  this.downloadFile(url, aCallback);
367  break;
368 
369  default:
370  Cu.reportError("sbCoverHelper: Unable to handle: " + uri.scheme);
371  break;
372  }
373  break;
374 
375  case "application/x-moz-file":
376  // Files are super easy :) just save it to our artwork folder
377  if (aDropData.data instanceof Ci.nsILocalFile) {
378  var fileName = this.saveFileToArtworkFolder(aDropData.data);
379  aCallback(fileName);
380  } else {
381  Cu.reportError("sbCoverHelper: Not a local file.");
382  }
383  break;
384  }
385  },
386 
394  setupDragTransferData: function(aTransferData, aImageURL) {
395  var ioService = Cc["@mozilla.org/network/io-service;1"]
396  .getService(Ci.nsIIOService);
397  var imageURI = null;
398 
399  // First try to convert the URL spec to an URI
400  try {
401  imageURI = ioService.newURI(aImageURL, null, null);
402  // We need to convert resource:// urls to file://
403  if (imageURI.schemeIs("resource")) {
404  var protoHandler = ioService.getProtocolHandler("resource")
405  .QueryInterface(Ci.nsIResProtocolHandler);
406  var newImageURL = protoHandler.resolveURI(imageURI);
407  if (newImageURL) {
408  aImageURL = newImageURL;
409  imageURI = ioService.newURI(aImageURL, null, null);
410  } else {
411  Cu.reportError(aImageURL + " did not properly convert from resource" +
412  " to a file url.");
413  return;
414  }
415  }
416  } catch (err) {
417  Cu.reportError("sbCoverHelper: Unable to convert to URI: [" + aImageURL +
418  "] " + err);
419  return;
420  }
421 
422  // If we have a local file then put it as a proper image mime type
423  // and as a x-moz-file
424  if (imageURI instanceof Ci.nsIFileURL) {
425  try {
426  // Read the mime type for the flavour
427  var mimetype = Cc["@mozilla.org/mime;1"]
428  .getService(Ci.nsIMIMEService)
429  .getTypeFromFile(imageURI.file);
430 
431  // Create an input stream for mime type flavour if we can
432  var inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
433  .createInstance(Ci.nsIFileInputStream);
434  inputStream.init(imageURI.file, FLAGS_DEFAULT, FLAGS_DEFAULT, 0);
435 
436  aTransferData.data.addDataForFlavour(mimetype,
437  inputStream,
438  0,
439  Ci.nsIFileInputStream);
440  } catch (err) {
441  Cu.reportError("sbCoverHelper: Unable to add image from file: [" +
442  aImageURL + "] " + err);
443  }
444 
445  // Add a file flavour
446  aTransferData.data.addDataForFlavour("application/x-moz-file",
447  imageURI.file,
448  0,
449  Ci.nsILocalFile);
450  }
451 
452  // Add a url flavour
453  aTransferData.data.addDataForFlavour("text/x-moz-url",
454  aImageURL + "\n" + imageURI.file.path);
455 
456  // Add a uri-list flavour
457  aTransferData.data.addDataForFlavour("text/uri-list", aImageURL);
458 
459  // Add simple Unicode flavour
460  aTransferData.data.addDataForFlavour("text/unicode", aImageURL);
461 
462  // Add HTML flavour
463  aTransferData.data.addDataForFlavour("text/html",
464  "<img src=\"" +
465  encodeURIComponent(aImageURL) +
466  "\"/>");
467 
468  // Finally a plain text flavour
469  aTransferData.data.addDataForFlavour("text/plain", aImageURL);
470  },
471 
481  getArtworkForItems: function(aItemList,
482  aWindow,
483  aLibrary,
484  aSuppressProgressDialog) {
485  var library = aLibrary;
486  if (!library)
487  library = LibraryUtils.mainLibrary;
488 
489  var mediaItems = aItemList;
490  if (aItemList instanceof Ci.nsIArray) {
491  mediaItems = aItemList.enumerate();
492  } else if (!(aItemList instanceof Ci.nsISimpleEnumerator)) {
493  Cu.reportError("getArtworkForItems: Item list is not a valid" +
494  " nsIArray or nsISimpleEnumerator.");
495  return;
496  }
497 
498  if (!mediaItems.hasMoreElements()) {
499  Cu.reportError("getArtworkForItems: No items to get artwork for.");
500  return;
501  }
502 
503  // Create a hidden playlist temporarily
504  var listProperties =
505  Cc["@songbirdnest.com/Songbird/Properties/MutablePropertyArray;1"]
506  .createInstance(Ci.sbIPropertyArray);
507  listProperties.appendProperty(SBProperties.hidden, "1");
508  listProperties.appendProperty(SBProperties.mediaListName, "Get Artwork");
509  var getArtworkMediaList = library.createMediaList("simple",
510  listProperties);
511  var isAudioItem = function(aItem) {
512  return aItem.getProperty(SBProperties.contentType) == "audio";
513  }
514  var audioItems = new SBFilteredEnumerator(mediaItems, isAudioItem);
515  // Add all the items to our new hidden temporary playlist
516  getArtworkMediaList.addSome(audioItems);
517 
518  // Set up the scanner
519  var artworkScanner = Cc["@songbirdnest.com/Songbird/album-art/scanner;1"]
520  .createInstance(Ci.sbIAlbumArtScanner);
521 
522  // Listener so that we can remove our list when done.
523  var jobProgressListener = {
524  onJobProgress: function(aJobProgress) {
525  if (aJobProgress.status != Ci.sbIJobProgress.STATUS_RUNNING) {
526  library.remove(getArtworkMediaList);
527  // Remove ourselves so that we do not get called multiple times.
528  artworkScanner.removeJobProgressListener(jobProgressListener);
529  }
530  },
531  QueryInterface: XPCOMUtils.generateQI([Ci.sbIJobProgressListener])
532  };
533 
534  // Now start scanning
535  artworkScanner.addJobProgressListener(jobProgressListener);
536  artworkScanner.scanListForArtwork(getArtworkMediaList);
537  if (!aSuppressProgressDialog)
538  SBJobUtils.showProgressDialog(artworkScanner, aWindow);
539  }
540 }
const Cu
const Cc
nsString encodeURIComponent(const nsString &c)
function SBFilteredEnumerator(aEnumerator, aFilterFunc)
Definition: SBUtils.jsm:163
id service()
var Application
Definition: sbAboutDRM.js:37
const MAX_FILE_SIZE_BYTES
function SBBrandedFormattedString(aKey, aParams, aDefault, aStringBundle)
sbOSDControlService prototype QueryInterface
var ioService
var uuid
ui plugin add("draggable","cursor",{start:function(e, ui){var t=$('body');if(t.css("cursor")) ui.options._cursor=t.css("cursor");t.css("cursor", ui.options.cursor);}, stop:function(e, ui){if(ui.options._cursor)$('body').css("cursor", ui.options._cursor);}})
function handle(request, response)
Element Properties html
function SBString(aKey, aDefault, aStringBundle)
Definition: StringUtils.jsm:93
function d(s)
EXPORTED_SYMBOLS
function downloadFile(url)
Download an XPI file from a URL. Downloads an XPI file from a URL to a temporary file on disk...
const FLAGS_DEFAULT
return null
Definition: FeedWriter.js:1143
function url(spec)
var uri
Definition: FeedWriter.js:1135
var prefs
Definition: FeedWriter.js:1169
const Ce
const Cr
let promptService
const Ci
Javascript wrappers for common library tasks.
ContinuingWebProgressListener prototype onStateChange
var file