home *** CD-ROM | disk | FTP | other *** search
/ Freelog 112 / FreelogNo112-NovembreDecembre2012.iso / Multimedia / Songbird / Songbird_2.0.0-2311_windows-i686-msvc8.exe / jsmodules / sbCoverHelper.jsm < prev    next >
Text File  |  2012-06-08  |  20KB  |  541 lines

  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.   
  51.   /**
  52.    * \brief Checks if the size of the image either provided by aImageSize or
  53.    *        loaded from aImageURL is under the MAX_FILE_SIZE in bytes for
  54.    *        storing into the meta data of a media file. The aImageURL should
  55.    *        be a valid local file. MAX_FILE_SIZE is defined as a preference
  56.    *        songbird.albumart.maxsize or defaults to 16Mb.
  57.    * \param aImageURL - URL of the image to check
  58.    * \param aImageSize - Size of the image in bytes
  59.    * \returns True if the image size is under the MAX_FILE_SIZE or false if not.
  60.    */
  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",
  65.                                                  MAX_FILE_SIZE_BYTES);
  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.   
  114.   /**
  115.    * \brief Reads the image data from a local file.
  116.    * \param aInputFile the nsIFile to read the image data from.
  117.    * \return array of imageData and the mimeType or null on failure.
  118.    */
  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.  
  156.   /**
  157.    * \brief Reads a file and saves that to our ProfLD/artwork folder. This
  158.    *        checks the size of the image and will not save if it is too big.
  159.    * \param aFromFile - File to read image data from.
  160.    * \return String version of the filename to the new image saved to the
  161.    *         artwork folder, or null if an error occurs.
  162.    */
  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.  
  197.   /**
  198.    * \brief Make sure the given file has at least the appropriate file name
  199.    *        extension. In the future, it would be nice to do some real MIME
  200.    *        sniffing here.
  201.    * \param aFile file to look for a matching file extension with.
  202.    */
  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.  
  224.   /**
  225.    * \brief Downloads a file from the web and then saves it to the artwork
  226.    *        folder using saveFileToArtworkFolder.
  227.    * \param aWebURL string of a web url to download file from.
  228.    * \param aCallback function to call back with the new image file name.
  229.    */
  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.  
  295.   /**
  296.    * Drag and Drop helper functions
  297.    */
  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.   
  308.   /**
  309.    * \brief Called from the onDrop function this will retrieve the image and
  310.    *        save it to the Local Profile folder under artwork.
  311.    * \param aCallback Function to call with the string URL of the newly saved
  312.    *        image.
  313.    * \param aDropData Is the DnD data that has been dropped on us.
  314.    */
  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.   
  387.   /**
  388.    * \brief Called from the onDragStart function this will setup all the
  389.    *        flavours needed for Drag and Drop the image to another place.
  390.    * \param aTransferData Is the DnD transfer data passed into the onDragStart
  391.    *        function call.
  392.    * \param aImageURL Is any image url, file, http, data etc.
  393.    */
  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.  
  472.   /**
  473.    * \brief Sets up a media list to get artwork for each of the items passed in.
  474.    * \param aItemList A nsIArray or nsISimpleEnumerator of sbIMediaItem items.
  475.    * \param aWindow Window to bind to, this can be null.
  476.    * \param aLibrary The library that these items are from. Optional, default
  477.    *                 to the main library.
  478.    * \param aSuppressProgressDialog
  479.    *                            If true, don't open job progress dialog.
  480.    */
  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. }
  541.