home *** CD-ROM | disk | FTP | other *** search
/ Freelog 112 / FreelogNo112-NovembreDecembre2012.iso / Multimedia / Songbird / Songbird_2.0.0-2311_windows-i686-msvc8.exe / jsmodules / DropHelper.jsm < prev    next >
Text File  |  2012-06-08  |  49KB  |  1,331 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. /*
  26.  
  27.   DropHelper Module
  28.  
  29.     This module contains three different helpers:
  30.  
  31.     - ExternalDropHandler
  32.  
  33.       Used to handle external drops (ie, standard file drag and drop from the
  34.       operating system).
  35.  
  36.     - InternalDropHandler
  37.  
  38.       Used to handle internal drops (ie, mediaitems, medialists)
  39.  
  40.     - DNDUtils
  41.  
  42.       Contains methods that are used by both of the helpers, and that are useful for
  43.       drag and drop operations in general.
  44.  
  45. */
  46.  
  47. EXPORTED_SYMBOLS = [ "DNDUtils",
  48.                      "ExternalDropHandler",
  49.                      "InternalDropHandler" ];
  50.  
  51. const Cc = Components.classes;
  52. const Ci = Components.interfaces;
  53. const Cr = Components.results;
  54. const Ce = Components.Exception;
  55. const Cu = Components.utils;
  56.  
  57. const URI_GENERIC_ICON_XPINSTALL =
  58.   "chrome://songbird/skin/base-elements/icon-generic-addon.png";
  59.  
  60. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  61.  
  62. Cu.import("resource://app/jsmodules/ArrayConverter.jsm");
  63. Cu.import("resource://app/jsmodules/sbProperties.jsm");
  64. Cu.import("resource://app/jsmodules/SBJobUtils.jsm");
  65. Cu.import("resource://app/jsmodules/sbLibraryUtils.jsm");
  66. Cu.import("resource://app/jsmodules/SBUtils.jsm");
  67. Cu.import("resource://app/jsmodules/StringUtils.jsm");
  68.  
  69. const SB_MEDIALISTDUPLICATEFILTER_CONTRACTID =
  70.   "@songbirdnest.com/Songbird/Library/medialistduplicatefilter;1";
  71. /*
  72.  
  73.   DNDUtils
  74.  
  75.   This helper contains a number of methods that may be used when implementing
  76.   a drag and drop handler.
  77.  
  78. */
  79.  
  80. var DNDUtils = {
  81.  
  82.   // returns true if the drag session contains supported flavors
  83.   isSupported: function(aDragSession, aFlavourArray) {
  84.     for (var i=0;i<aFlavourArray.length;i++) {
  85.       if (aDragSession.isDataFlavorSupported(aFlavourArray[i])) {
  86.         return true;
  87.       }
  88.     }
  89.     return false;
  90.   },
  91.  
  92.   // adds the flavors in the array to the given flavourset
  93.   addFlavours: function(aFlavourSet, aFlavourArray) {
  94.     for (var i=0; i<aFlavourArray.length; i++) {
  95.       aFlavourSet.appendFlavour(aFlavourArray[i]);
  96.     }
  97.   },
  98.  
  99.   // fills an array with the data for all items of a given flavor
  100.   getTransferDataForFlavour: function(aFlavour, aSession, aArray) {
  101.     if (!aSession) {
  102.       var dragService = Cc["@mozilla.org/widget/dragservice;1"]
  103.                             .getService(Ci.nsIDragService);
  104.       aSession = dragService.getCurrentSession();
  105.     }
  106.  
  107.     var nitems = aSession.numDropItems;
  108.     var r = null;
  109.  
  110.     if (aSession.isDataFlavorSupported(aFlavour)) {
  111.       var transfer = Cc["@mozilla.org/widget/transferable;1"]
  112.                        .createInstance(Ci.nsITransferable);
  113.       transfer.addDataFlavor(aFlavour);
  114.  
  115.       for (var i=0;i<nitems;i++) {
  116.         aSession.getData(transfer, i);
  117.         var data = {};
  118.         var length = {};
  119.         transfer.getTransferData(aFlavour, data, length);
  120.         if (!r) r = data.value;
  121.         if (aArray) aArray.push([data.value, length.value, aFlavour]);
  122.       }
  123.     }
  124.  
  125.     return r;
  126.   },
  127.  
  128.   // similar to getTransferDataForFlavour but adds extraction of the internal
  129.   // drag data from the dndSourceTracker, and queries an interface from the
  130.   // result
  131.   getInternalTransferDataForFlavour: function(aSession, aFlavour, aInterface) {
  132.     var data = this.getTransferDataForFlavour(aFlavour, aSession);
  133.     if (data)
  134.       return this.getInternalTransferData(data, aInterface);
  135.     return null;
  136.   },
  137.  
  138.   // similar to getTransferData but adds extraction of the internal drag data
  139.   // from the dndSourceTracker, and queries an interface from the result
  140.   getInternalTransferData: function(aData, aInterface) {
  141.     // get the object from the dnd source tracker
  142.     var dnd = Components.classes["@songbirdnest.com/Songbird/DndSourceTracker;1"]
  143.         .getService(Ci.sbIDndSourceTracker);
  144.     var source = dnd.getSourceSupports(aData);
  145.     // and request the specified interface
  146.     return source.QueryInterface(aInterface);
  147.   },
  148.  
  149.   // returns an array with the data for any flavour listed in the given array
  150.   getTransferData: function(aSession, aFlavourArray) {
  151.     // I know this is confusing but because numDropItems is a Number it causes
  152.     // the Array constructor to pre-allocate N slots which should speed up
  153.     // pushing elements into it.
  154.     var data = new Array(aSession.numDropItems);
  155.     for (var i=0;i<aFlavourArray.length;i++) {
  156.       if (this.getTransferDataForFlavour(aFlavourArray[i], aSession, data))
  157.         break;
  158.     }
  159.     return data;
  160.   },
  161.  
  162.   // reports a custom temporary message to the status bar
  163.   customReport: function(aMessage) {
  164.     var SB_NewDataRemote =
  165.       Components.Constructor("@songbirdnest.com/Songbird/DataRemote;1",
  166.                              "sbIDataRemote",
  167.                              "init");
  168.     var statusOverrideText =
  169.       SB_NewDataRemote( "faceplate.status.override.text", null );
  170.     var statusOverrideType =
  171.       SB_NewDataRemote( "faceplate.status.override.type", null );
  172.  
  173.     statusOverrideText.stringValue = "";
  174.     statusOverrideText.stringValue = aMessage;
  175.     statusOverrideType.stringValue = "report";
  176.   },
  177.  
  178.   // temporarily writes "X tracks added to <name>, Y tracks already present"
  179.   // in the status bar. if 0 is specified for aDups, the second part of the
  180.   // message is skipped.
  181.   reportAddedTracks: function(aAdded, aDups, aUnsupported, aDestName, aIsDevice) {
  182.     var msg = "";
  183.  
  184.     /* We only report D&D status for non-device-related transfers
  185.      * Please see bug 23763 if status bar reporting for D&D onto a device is
  186.      * being implemented for notes from sneumann.
  187.      */
  188.     if (!aIsDevice) {
  189.       if (aDups && aUnsupported) {
  190.         msg = SBFormattedString("library.tracksadded.with.dups.and.unsupported",
  191.           [aAdded, aDestName, aDups, aUnsupported]);
  192.       }
  193.       else if (aDups) {
  194.         msg = SBFormattedString("library.tracksadded.with.dups",
  195.           [aAdded, aDestName, aDups]);
  196.       }
  197.       else if (aUnsupported) {
  198.         msg = SBFormattedString("library.tracksadded.with.unsupported",
  199.           [aAdded, aDestName, aUnsupported]);
  200.       }
  201.       else {
  202.         msg = SBFormattedString("library.tracksadded",
  203.           [aAdded, aDestName]);
  204.       }
  205.     }
  206.     this.customReport(msg);
  207.   },
  208.  
  209.   // reports stats on the statusbar using standard rules for what to show and
  210.   // in which circumstances
  211.   standardReport: function(aTargetList,
  212.                            aImportedInLibrary,
  213.                            aDuplicates,
  214.                            aUnsupported,
  215.                            aInsertedInMediaList,
  216.                            aOtherDropsHandled,
  217.                            aDevice) {
  218.     // do not report anything if all we did was drop an XPI
  219.     if ((aImportedInLibrary == 0) &&
  220.         (aInsertedInMediaList == 0) &&
  221.         (aDuplicates == 0) &&
  222.         (aUnsupported == 0) &&
  223.         (aOtherDropsHandled != 0))
  224.       return;
  225.  
  226.     // report different things depending on whether we dropped
  227.     // on a library, or just a list
  228.     if (aTargetList != aTargetList.library) {
  229.       DNDUtils.reportAddedTracks(aInsertedInMediaList,
  230.                                  0,
  231.                                  aUnsupported,
  232.                                  aTargetList.name,
  233.                                  aDevice);
  234.     } else {
  235.       DNDUtils.reportAddedTracks(aImportedInLibrary,
  236.                                  aDuplicates,
  237.                                  aUnsupported,
  238.                                  aTargetList.name,
  239.                                  aDevice);
  240.     }
  241.   }
  242. }
  243.  
  244. /* MediaListViewSelectionTransferContext
  245.  *
  246.  * Create a drag and drop context containing the selected items in the view
  247.  * which is passed in.
  248.  * Implements: sbIMediaItemsTransferContext
  249.  */
  250. DNDUtils.MediaListViewSelectionTransferContext = function (mediaListView) {
  251.     this.items          = null; // filled in during reset()
  252.     this.indexedItems   = null;
  253.     this.source         = mediaListView.mediaList;
  254.     this.count          = mediaListView.selection.count;
  255.     this._mediaListView = mediaListView;
  256.     this.reset();
  257. };
  258. DNDUtils.MediaListViewSelectionTransferContext.prototype = {
  259.     reset: function() {
  260.       // Create an enumerator that unwraps the sbIIndexedMediaItem enumerator
  261.       // which selection provides.
  262.       var enumerator = Cc["@songbirdnest.com/Songbird/Library/EnumeratorWrapper;1"]
  263.                          .createInstance(Ci.sbIMediaListEnumeratorWrapper);
  264.       enumerator.initialize(this._mediaListView
  265.                                 .selection
  266.                                 .selectedIndexedMediaItems);
  267.       this.items = enumerator;
  268.  
  269.       // and here's the wrapped form for those cases where you want it
  270.       this.indexedItems = this._mediaListView.selection.selectedIndexedMediaItems;
  271.     },
  272.     QueryInterface : function(iid) {
  273.       if (iid.equals(Components.interfaces.sbIMediaItemsTransferContext) ||
  274.           iid.equals(Components.interfaces.nsISupports))
  275.         return this;
  276.       throw Components.results.NS_NOINTERFACE;
  277.     }
  278.   };
  279.  
  280. /* EntireMediaListViewTransferContext
  281.  *
  282.  * Create a drag and drop context containing all the items in the view
  283.  * which is passed in.
  284.  * Implements: sbIMediaItemsTransferContext
  285.  */
  286. DNDUtils.EntireMediaListViewTransferContext = function(view) {
  287.     this.items          = null;
  288.     this.indexedItems   = null;
  289.     this.source         = view.mediaList;
  290.     this.count          = view.length;
  291.     this._mediaListView = view;
  292.     this.reset();
  293.   }
  294. DNDUtils.EntireMediaListViewTransferContext.prototype = {
  295.   reset: function() {
  296.     // Create an ugly pseudoenumerator
  297.     var that = this;
  298.     this.items = {
  299.       i: 0,
  300.       hasMoreElements : function() {
  301.         return this.i < that._mediaListView.length;
  302.       },
  303.       getNext : function() {
  304.         var item = that._mediaListView.getItemByIndex(this.i++);
  305.         item.setProperty(SBProperties.downloadStatusTarget,
  306.                          item.library.guid + "," + item.guid);
  307.         return item;
  308.       },
  309.       QueryInterface : function(iid) {
  310.         if (iid.equals(Components.interfaces.nsISimpleEnumerator) ||
  311.             iid.equals(Components.interfaces.nsISupports))
  312.           return this;
  313.         throw Components.results.NS_NOINTERFACE;
  314.       }
  315.     };
  316.   },
  317.   QueryInterface : function(iid) {
  318.     if (iid.equals(Components.interfaces.sbIMediaItemsTransferContext) ||
  319.         iid.equals(Components.interfaces.nsISupports))
  320.       return this;
  321.     throw Components.results.NS_NOINTERFACE;
  322.   }
  323. };
  324.  
  325. /* MediaListTransferContext
  326.  *
  327.  * A transfer context suitable for moving a single media list around the system.
  328.  * As of this writing, the only place to create a drag/drop session of a single
  329.  * media list is the service pane, though it is also possible that extension
  330.  * developers will create them in the playlist widget.
  331.  */
  332. DNDUtils.MediaListTransferContext = function (item, mediaList) {
  333.     this.item   = item;
  334.     this.list   = item;
  335.     this.source = mediaList;
  336.     this.count  = 1;
  337.   }
  338. DNDUtils.MediaListTransferContext.prototype = {
  339.     QueryInterface : function(iid) {
  340.       if (iid.equals(Components.interfaces.sbIMediaListTransferContext) ||
  341.           iid.equals(Components.interfaces.nsISupports))
  342.         return this;
  343.       throw Components.results.NS_NOINTERFACE;
  344.     }
  345.   }
  346.  
  347. /*
  348.  
  349.   InternalDropHandler
  350.  
  351.  
  352. This helper is used to let you handle internal drops (mediaitems and medialists)
  353. and inject the items into a medialist, potentially at a specific position.
  354.  
  355. There are two ways of triggering a drop handling, the question of which one you
  356. should be using depends on how it is you would like to handle the drop:
  357.  
  358. To handle a drop in a generic manner, and have all dropped items automatically
  359. directed to the default library, all you need to do is add the following code in
  360. your onDrop/ondragdrop handler:
  361.  
  362.   InternalDropHandler.drop(window, dragSession, dropHandlerListener);
  363.  
  364. The last parameter is optional, it allows you to receive notifications. Here is
  365. a minimal implementation:
  366.  
  367.   var dropHandlerListener = {
  368.     // called when the drop handling has completed
  369.     onDropComplete: function(aTargetList,
  370.                              aImportedInLibrary,
  371.                              aDuplicates,
  372.                              aUnsupported,
  373.                              aInsertedInMediaList,
  374.                              aOtherDropsHandled) {
  375.       // returning true causes the standard drop report to be printed
  376.       // on the status bar, it is equivalent to calling standardReport
  377.       // using the parameters received on this callback
  378.       return true;
  379.     },
  380.     // called when the first item is handled (eg, to implement playback)
  381.     onFirstMediaItem: function(aTargetList, aFirstMediaItem) { }
  382.     // called when a medialist has been copied from a different source library
  383.     onCopyMediaList: function(aSourceList, aNewList) { }
  384.   };
  385.  
  386. To handle a drop with a specific mediaList target and drop insertion point, use
  387. the following code:
  388.  
  389.   InternalDropHandler.dropOnList(window,
  390.                                  dragSession,
  391.                                  targetMediaList,
  392.                                  targetMediaListPosition,
  393.                                  dropHandlerListener);
  394.  
  395. In order to target the drop at the end of the targeted mediaList, you
  396. should give a value of -1 for targetMediaListPosition.
  397.  
  398. The other public methods in this helper can be used to simplify the rest of your
  399. drag and drop handler as well. For instance, an nsDragAndDrop observer's
  400. getSupportedFlavours() method may be implemented simply as:
  401.  
  402.     var flavours = new FlavourSet();
  403.     InternalDropHandler.addFlavours(flavours);
  404.     return flavours;
  405.  
  406. Also, getTransferData, getInternalTransferDataForFlavour, and
  407. getTransferDataForFlavour may be used to inspect the content of the dragSession
  408. before deciding what to do with it.
  409.  
  410. */
  411.  
  412. const TYPE_X_SB_TRANSFER_MEDIA_ITEM = "application/x-sb-transfer-media-item";
  413. const TYPE_X_SB_TRANSFER_MEDIA_LIST = "application/x-sb-transfer-media-list";
  414. const TYPE_X_SB_TRANSFER_MEDIA_ITEMS = "application/x-sb-transfer-media-items";
  415.  
  416. var InternalDropHandler = {
  417.  
  418.   supportedFlavours: [ TYPE_X_SB_TRANSFER_MEDIA_ITEM,
  419.                        TYPE_X_SB_TRANSFER_MEDIA_LIST,
  420.                        TYPE_X_SB_TRANSFER_MEDIA_ITEMS ],
  421.  
  422.   // returns true if the drag session contains supported internal flavors
  423.   isSupported: function(aDragSession) {
  424.     return DNDUtils.isSupported(aDragSession, this.supportedFlavours);
  425.   },
  426.  
  427.   // performs a default drop of the drag session. media items go to the
  428.   // main library.
  429.   drop: function(aWindow, aDragSession, aListener) {
  430.     var mainLibrary = Cc["@songbirdnest.com/Songbird/library/Manager;1"]
  431.                           .getService(Ci.sbILibraryManager)
  432.                           .mainLibrary;
  433.     this.dropOnList(aWindow, aDragSession, mainLibrary, -1, aListener);
  434.   },
  435.  
  436.   // perform a drop onto a medialist. media items are inserted at the specified
  437.   // position in the list if that list is orderable. otherwise, or if the
  438.   // position is invalid, the items are added to the target list.
  439.   dropOnList: function(aWindow,
  440.                        aDragSession,
  441.                        aTargetList,
  442.                        aDropPosition,
  443.                        aListener) {
  444.     if (!aTargetList) {
  445.       throw new Error("No target medialist specified for dropOnList");
  446.     }
  447.     this._dropItems(aDragSession,
  448.                     aTargetList,
  449.                     aDropPosition,
  450.                     aListener);
  451.   },
  452.  
  453.   // call this to automatically add the supported internal flavors
  454.   // to a flavourSet object
  455.   addFlavours: function(aFlavourSet) {
  456.     DNDUtils.addFlavours(aFlavourSet, this.supportedFlavours);
  457.   },
  458.  
  459.   // returns an array with the data for any supported internal flavor
  460.   getTransferData: function(aSession) {
  461.     return DNDUtils.getTransferData(aSession, this.supportedFlavours);
  462.   },
  463.  
  464.   // simply forward the call. here in this object for completeness
  465.   // see DNDUtils.getTransferDataForFlavour for more info
  466.   getTransferDataForFlavour: function(aFlavour, aSession, aArray) {
  467.     return DNDUtils.getTransferDataForFlavour(aFlavour, aSession, aArray);
  468.   },
  469.  
  470.   // --------------------------------------------------------------------------
  471.   // methods below this point are pretend-private
  472.   // --------------------------------------------------------------------------
  473.  
  474.   /**
  475.    * Performs the actual drop of items into a media list
  476.    * \param aDragSession The nsIDragSession associated with the drop
  477.    * \param aTargetList The list to drop the items into
  478.    * \param aDropPosition The position to drop the items into in the list; if
  479.    *                      the list is unordered or if the position is invalid,
  480.    *                      the items will be inserted at the end of the list
  481.    *                      instead.
  482.    * \param aListener The listener for drop progress feedback; see the
  483.    *                  documentation for InternalDropHandler for details.
  484.    */
  485.   _dropItems: function(aDragSession, aTargetList, aDropPosition, aListener) {
  486.     // are we dropping a media list ?
  487.     if (aDragSession.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_LIST)) {
  488.       this._dropItemsList(aDragSession, aTargetList, aDropPosition, aListener);
  489.     } else if (aDragSession.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_ITEMS)) {
  490.       this._dropItemsItems(aDragSession, aTargetList, aDropPosition, aListener);
  491.     }
  492.   },
  493.  
  494.   /**
  495.    * Return true if the given device supports playlist
  496.    */
  497.   _doesDeviceSupportPlaylist: function(aDevice) {
  498.     // Check the device capabilities to see if it supports playlists.
  499.     // Device implementations may respond to CONTENT_PLAYLIST for either
  500.     // FUNCTION_DEVICE or FUNCTION_AUDIO_PLAYBACK.
  501.     var capabilities = aDevice.capabilities;
  502.     var sbIDC = Ci.sbIDeviceCapabilities;
  503.     try {
  504.       if (capabilities.supportsContent(sbIDC.FUNCTION_DEVICE,
  505.                                        sbIDC.CONTENT_PLAYLIST) ||
  506.           capabilities.supportsContent(sbIDC.FUNCTION_AUDIO_PLAYBACK,
  507.                                        sbIDC.CONTENT_PLAYLIST)) {
  508.         return true;
  509.       }
  510.     } catch (e) {}
  511.  
  512.     // couldn't find PLAYLIST support in either the DEVICE
  513.     // or AUDIO_PLAYBACK category
  514.     return false;
  515.   },
  516.  
  517.   _getTransferForDeviceChanges: function(aDevice, aSourceItems, aSourceList,
  518.                                          aDestLibrary) {
  519.     var differ = Cc["@songbirdnest.com/Songbird/Device/DeviceLibrarySyncDiff;1"]
  520.                    .createInstance(Ci.sbIDeviceLibrarySyncDiff);
  521.  
  522.     var sourceLibrary;
  523.     if (aSourceItems)
  524.       sourceLibrary = aSourceItems.queryElementAt(0, Ci.sbIMediaItem).library;
  525.     else
  526.       sourceLibrary = aSourceList.library;
  527.  
  528.     var changeset = {};
  529.     var destItems = {};
  530.     differ.generateDropLists(sourceLibrary,
  531.                              aDestLibrary,
  532.                              aSourceList,
  533.                              aSourceItems,
  534.                              destItems,
  535.                              changeset);
  536.  
  537.     return {changeset: changeset.value, items: destItems.value};
  538.   },
  539.  
  540.   _notifyListeners: function(aListener, aTargetList, aNewList, aSourceItems,
  541.                              aSourceList, aIsDevice) {
  542.     var sourceLength = 0;
  543.  
  544.     if (aSourceList)
  545.       sourceLength = aSourceList.length;
  546.     else if (aSourceItems)
  547.       sourceLength = aSourceItems.length;
  548.  
  549.     // Let our listeners know.
  550.     if (aListener) {
  551.       if (aNewList)
  552.         aListener.onCopyMediaList(aTargetList, aNewList);
  553.  
  554.       if (sourceLength) {
  555.         if (aSourceList)
  556.           aListener.onFirstMediaItem(aSourceList.getItemByIndex(0));
  557.         else if (aSourceItems)
  558.           aListener.onFirstMediaItem(
  559.                   aSourceItems.queryElementAt(0, Ci.sbIMediaItem));
  560.       }
  561.     }
  562.  
  563.     // These values are bogus, at least if the target is a library (and hence
  564.     // already-existing items won't be copied). Better than nothing?
  565.     this._dropComplete(aListener,
  566.                        aTargetList,
  567.                        sourceLength,
  568.                        0,
  569.                        0,
  570.                        sourceLength,
  571.                        0,
  572.                        aIsDevice);
  573.   },
  574.  
  575.   // aItem.library doesn't return the device library. Find the matching one
  576.   // from the device.
  577.   _getDeviceLibraryForItem: function(aDevice, aItem) {
  578.     var lib = aItem.library;
  579.  
  580.     var libs = aDevice.content.libraries;
  581.     for (var i = 0; i < libs.length; i++) {
  582.       var deviceLib = libs.queryElementAt(i, Ci.sbIDeviceLibrary);
  583.       if (lib.equals(deviceLib))
  584.         return deviceLib;
  585.     }
  586.  
  587.     return null;
  588.   },
  589.  
  590.   // Transfer a dropped list where the source is a device, and the destination
  591.   // is not.
  592.   _dropListFromDevice: function(aDevice, aSourceList, aTargetList,
  593.                                 aDropPosition, aListener)
  594.   {
  595.     // Find out what changes need making.
  596.     var changes = this._getTransferForDeviceChanges(aDevice,
  597.             null, aSourceList, aTargetList.library);
  598.     var changeset = changes.changeset;
  599.  
  600.     var targetLibrary = aTargetList.library;
  601.  
  602.     aDevice.importFromDevice(targetLibrary, changeset);
  603.  
  604.     // Get the list that was created, if any.
  605.     var newlist = null;
  606.     var changes = changeset.changes;
  607.     for (var i = 0; i < changes.length; i++) {
  608.       var change = changes.queryElementAt(i, Ci.sbILibraryChange);
  609.       if (change.itemIsList) {
  610.         if (change.operation == Ci.sbIChangeOperation.ADD) {
  611.           var originGUID = 
  612.             change.sourceItem.getProperty(SBProperties.originItemGuid);
  613.           newlist = targetLibrary.getItemByGuid(originGUID);
  614.           break;
  615.         }
  616.       }
  617.     }
  618.  
  619.     this._notifyListeners(aListener, aTargetList, newlist,
  620.                           null, aSourceList, true);
  621.   },
  622.  
  623.   _dropItemsFromDevice: function(aDevice, aSourceItems, aTargetList,
  624.                                  aDropPosition, aListener)
  625.   {
  626.     // Find out what changes need making.
  627.     var changes = this._getTransferForDeviceChanges(aDevice,
  628.             aSourceItems, null, aTargetList.library);
  629.     var changeset = changes.changeset;
  630.  
  631.     var targetLibrary = aTargetList.library;
  632.  
  633.     aDevice.importFromDevice(targetLibrary, changeset);
  634.  
  635.     if (aTargetList.library != aTargetList) {
  636.       // This was a drop on an existing playlist. Now the items need
  637.       // adding to the playlist - changes.items is an nsIArray containing these
  638.       if (aTargetList instanceof Ci.sbIOrderableMediaList &&
  639.           aDropPosition != -1) {
  640.         aTargetList.insertSomeBefore(aDropPosition,
  641.                                      ArrayConverter.enumerator(changes.items));
  642.       }
  643.       else {
  644.         aTargetList.addSome(ArrayConverter.enumerator(changes.items));
  645.       }
  646.     }
  647.  
  648.     this._notifyListeners(aListener, aTargetList, null,
  649.                           aSourceItems, null, true);
  650.   },
  651.  
  652.  
  653.   // Transfer a dropped list to a device.
  654.   //
  655.   // aTargetList may be a device library or a device playlist.
  656.   _dropListOnDevice: function(aDevice, aSourceList, aTargetList,
  657.                               aDropPosition, aListener) {
  658.     // Find out what changes need making.
  659.     var changes = this._getTransferForDeviceChanges(aDevice,
  660.             null, aSourceList, aTargetList.library);
  661.     var changeset = changes.changeset;
  662.  
  663.     var deviceLibrary = this._getDeviceLibraryForItem(aDevice, aTargetList);
  664.  
  665.     // Apply the changes to get the actual media items onto the device, even
  666.     // if the device doesn't support playlists. This will add the items to the
  667.     // device library and schedule all the necessary file copies/etc. This
  668.     // also creates the device-side list if appropriate.
  669.     aDevice.exportToDevice(deviceLibrary, changeset);
  670.  
  671.     // Get the list that was created, if any.
  672.     var newlist = null;
  673.     var changes = changeset.changes;
  674.     for (var i = 0; i < changes.length; i++) {
  675.       var change = changes.queryElementAt(i, Ci.sbILibraryChange);
  676.       if (change.itemIsList) {
  677.         if (change.operation == Ci.sbIChangeOperation.ADD) {
  678.           var foundLists = 
  679.             this.deviceLibrary.getItemsByProperty(SBProperties.originItemGuid,
  680.                                                   change.sourceItem.guid);
  681.           if (foundLists.length > 0) {
  682.             newlist = foundLists[0];
  683.           } 
  684.           break;
  685.         }
  686.       }
  687.     }
  688.  
  689.     this._notifyListeners(aListener, aTargetList, newlist,
  690.                           null, aSourceList, true);
  691.   },
  692.  
  693.   // Transfer dropped items to a device.
  694.   //
  695.   // aTargetList may be a device library or a device playlist.
  696.   _dropItemsOnDevice: function(aDevice, aSourceItems, aTargetList,
  697.                                aDropPosition, aListener) {
  698.     // Find out what changes need making.
  699.     var changes = this._getTransferForDeviceChanges(aDevice,
  700.             aSourceItems, null, aTargetList.library);
  701.     var changeset = changes.changeset;
  702.  
  703.     var deviceLibrary = this._getDeviceLibraryForItem(aDevice, aTargetList);
  704.  
  705.     // Apply the changes to get the media items onto the device
  706.     // if the device doesn't support playlists. This will add the items to the
  707.     // device library and schedule all the necessary file copies/etc.
  708.     aDevice.exportToDevice(deviceLibrary, changeset);
  709.  
  710.     if (aTargetList.library != aTargetList) {
  711.       // This was a drop on an existing device playlist. Now the items need
  712.       // adding to the playlist - changes.items is an nsIArray containing these
  713.       if (aTargetList instanceof Ci.sbIOrderableMediaList &&
  714.           aDropPosition != -1) {
  715.         aTargetList.insertSomeBefore(aDropPosition,
  716.                                      ArrayConverter.enumerator(changes.items));
  717.       }
  718.       else {
  719.         aTargetList.addSome(ArrayConverter.enumerator(changes.items));
  720.       }
  721.     }
  722.  
  723.     this._notifyListeners(aListener, aTargetList, null,
  724.                           aSourceItems, null, true);
  725.   },
  726.  
  727.   // Transfer dropped items, where neither source or destination is on a device
  728.   _dropItemsSimple: function(aItems, aTargetList, aDropPosition, aListener) {
  729.     if (aTargetList instanceof Ci.sbIOrderableMediaList &&
  730.         aDropPosition != -1) {
  731.       aTargetList.insertSomeBefore(aDropPosition,
  732.                                    ArrayConverter.enumerator(aItems));
  733.     }
  734.     else {
  735.       aTargetList.addSome(ArrayConverter.enumerator(aItems));
  736.     }
  737.  
  738.     this._notifyListeners(aListener, aTargetList, null, aItems, null, false);
  739.   },
  740.  
  741.   // Drop from a list to another list, where neither list is on a device.
  742.   _dropListSimple: function(aSourceList, aTargetList,
  743.                             aDropPosition, aListener) {
  744.     var newlist = null;
  745.  
  746.     var targetIsLibrary = (aTargetList instanceof Ci.sbILibrary);
  747.     if (targetIsLibrary) {
  748.       newlist = aTargetList.copyMediaList('simple', aSourceList, false);
  749.     }
  750.     else {
  751.       if (aTargetList instanceof Ci.sbIOrderableMediaList &&
  752.           aDropPosition != -1)
  753.       {
  754.         aTargetList.insertAllBefore(aDropPosition, aSourceList);
  755.       }
  756.       else {
  757.         aTargetList.addAll(aSourceList);
  758.       }
  759.     }
  760.  
  761.     this._notifyListeners(aListener, aTargetList, newlist, null, aSourceList,
  762.                           false);
  763.   },
  764.  
  765.   /**
  766.    * Performs the actual drop of a media list into another media list
  767.    * This is the media list variant of _dropItems(); please see that method for
  768.    * documentation.
  769.    */
  770.   _dropItemsList: function(aDragSession, aTargetList,
  771.                            aDropPosition, aListener) {
  772.     var context = DNDUtils.
  773.       getInternalTransferDataForFlavour(aDragSession,
  774.                                         TYPE_X_SB_TRANSFER_MEDIA_LIST,
  775.                                         Ci.sbIMediaListTransferContext);
  776.     var sourceList = context.list;
  777.     if (sourceList == aTargetList) {
  778.       // uh oh - you can't drop a list onto itself
  779.       this._dropComplete(aListener, aTargetList, 0, context.count, 0, 0, 0,
  780.                          false);
  781.       return;
  782.     }
  783.  
  784.     // Check if our destination is on a device; some behaviour differs if it is.
  785.     var deviceManager = Cc["@songbirdnest.com/Songbird/DeviceManager;2"]
  786.                           .getService(Ci.sbIDeviceManager2);
  787.     var destDevice = deviceManager.getDeviceForItem(aTargetList);
  788.     var sourceDevice = deviceManager.getDeviceForItem(sourceList);
  789.  
  790.     if (destDevice) {
  791.       // We use heavily customised behaviour if the target is a device.
  792.       if (aTargetList.library == aTargetList) {
  793.         // Drop onto a library
  794.         this._dropListOnDevice(destDevice, sourceList, aTargetList,
  795.                                aDropPosition, aListener);
  796.       }
  797.       else {
  798.         // Drop onto a playlist in a library. This should actually be
  799.         // treated as a drop of the ITEMS in the source list.
  800.         items = this._itemsFromList(sourceList);
  801.         this._dropItemsOnDevice(destDevice, items, aTargetList,
  802.                                 aDropPosition, aListener);
  803.       }
  804.     }
  805.     else if (sourceDevice) {
  806.       // We use special D&D behaviour for dragging a list _from_ a device too.
  807.       if (aTargetList.library == aTargetList) {
  808.         // Drop onto a library
  809.         this._dropListFromDevice(sourceDevice, sourceList, aTargetList,
  810.                                  aDropPosition, aListener);
  811.       }
  812.       else {
  813.         // Drop onto a playlist in a library; treat as a drop of the items.
  814.         items = this._itemsFromList(sourceList);
  815.         this._dropItemsFromDevice(destDevice, items, aTargetList,
  816.                                   aDropPosition, aListener);
  817.       }
  818.     }
  819.     else {
  820.       // No devices are involved, we just need the simple behaviour.
  821.       this._dropListSimple(sourceList, aTargetList,
  822.                            aDropPosition, aListener);
  823.     }
  824.   },
  825.  
  826.   // Get an nsIArray of sbIMediaItems from a media list
  827.   _itemsFromList: function(list)
  828.   {
  829.     var items = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
  830.     var listener = {
  831.       onEnumerationBegin : function(aMediaList) {
  832.         return Ci.sbIMediaListEnumerationListener.CONTINUE;
  833.       },
  834.       onEnumeratedItem : function(aMediaList, aMediaItem) {
  835.         items.appendElement(aMediaItem, false);
  836.         return Ci.sbIMediaListEnumerationListener.CONTINUE;
  837.       },
  838.       onEnumerationEnd : function(aMediaList, aStatusCode) {
  839.       }
  840.     };
  841.     list.enumerateAllItems(listener, Ci.sbIMediaList.ENUMERATIONTYPE_SNAPSHOT);
  842.  
  843.     return items;
  844.   },
  845.  
  846.   // Get an nsIArray of sbIMediaItems from an nsISimpleEnumerator of same.
  847.   _itemsFromEnumerator: function(itemEnum) {
  848.     var items = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
  849.  
  850.     while (itemEnum.hasMoreElements())
  851.       items.appendElement(itemEnum.getNext(), false);
  852.  
  853.     return items;
  854.   },
  855.  
  856.   /**
  857.    * Performs the actual drop of media items into a media list
  858.    * This is the media items variant of _dropItems(); please see that method for
  859.    * documentation.
  860.    */
  861.   _dropItemsItems: function(aDragSession, aTargetList,
  862.                             aDropPosition, aListener) {
  863.     var context = DNDUtils.
  864.       getInternalTransferDataForFlavour(aDragSession,
  865.                                         TYPE_X_SB_TRANSFER_MEDIA_ITEMS,
  866.                                         Ci.sbIMediaItemsTransferContext);
  867.  
  868.     var itemEnumerator = context.items;
  869.  
  870.     var items = this._itemsFromEnumerator(itemEnumerator);
  871.  
  872.     // are we dropping on a library? Assume all source items are from the same
  873.     // library.
  874.     if (aTargetList instanceof Ci.sbILibrary) {
  875.       if (items.length > 0 &&
  876.               items.queryElementAt(0, Ci.sbIMediaItem).library == aTargetList)
  877.       {
  878.         // can't add items to a library to which they already belong
  879.         this._dropComplete(aListener, aTargetList, 0, context.count, 0, 0, 0,
  880.                            false);
  881.         return;
  882.       }
  883.     }
  884.  
  885.     var deviceManager = Cc["@songbirdnest.com/Songbird/DeviceManager;2"]
  886.                           .getService(Ci.sbIDeviceManager2);
  887.     var destDevice = deviceManager.getDeviceForItem(aTargetList);
  888.     var sourceDevice = null;
  889.     if (items.length > 0)
  890.       sourceDevice = deviceManager.getDeviceForItem(
  891.               items.queryElementAt(0, Ci.sbIMediaItem));
  892.  
  893.     if (destDevice) {
  894.       // We use heavily customised behaviour if the target is a device.
  895.       this._dropItemsOnDevice(destDevice, items, aTargetList,
  896.                               aDropPosition, aListener);
  897.     }
  898.     else if (sourceDevice) {
  899.       this._dropItemsFromDevice(sourceDevice, items, aTargetList,
  900.                                 aDropPosition, aListener);
  901.     }
  902.     else {
  903.       // If the source is a device, or no devices are involved, we just need
  904.       // the simple behaviour.
  905.       this._dropItemsSimple(items, aTargetList,
  906.                             aDropPosition, aListener);
  907.     }
  908.   },
  909.  
  910.   // called when the whole drop handling operation has completed, used
  911.   // to notify the original caller and free up any resources we can
  912.   _dropComplete: function(listener,
  913.                           targetList,
  914.                           totalImported,
  915.                           totalDups,
  916.                           totalUnsupported,
  917.                           totalInserted,
  918.                           otherDrops,
  919.                           isDevice) {
  920.     // notify the listener that we're done
  921.     if (listener) {
  922.       if (listener.onDropComplete(targetList,
  923.                                   totalImported,
  924.                                   totalDups,
  925.                                   totalUnsupported,
  926.                                   totalInserted,
  927.                                   otherDrops)) {
  928.         DNDUtils.standardReport(targetList,
  929.                                 totalImported,
  930.                                 totalDups,
  931.                                 totalUnsupported,
  932.                                 totalInserted,
  933.                                 otherDrops,
  934.                                 isDevice);
  935.       }
  936.     } else {
  937.       DNDUtils.standardReport(targetList,
  938.                               totalImported,
  939.                               totalDups,
  940.                               totalUnsupported,
  941.                               totalInserted,
  942.                               otherDrops,
  943.                               isDevice);
  944.     }
  945.   },
  946. }
  947.  
  948. /*
  949.  
  950.  
  951.   ExternalDropHandler JSM Module
  952.  
  953.  
  954.  
  955. This helper is used to let you handle external file drops and automatically
  956. handle scanning directories as needed, injecting items at a specific spot in
  957. a media list, schedule a metadata scanner job for the newly imported items,
  958. and so on.
  959.  
  960. There are two ways of triggering a drop handling, the question of which one you
  961. should be using depends on how it is you would like to handle the drop:
  962.  
  963. To handle a drop in a generic manner, and have all dropped items automatically
  964. directed to the default library, all you need to do is add the following code in
  965. your onDrop/ondragdrop handler:
  966.  
  967.   ExternalDropHandler.drop(window, dragSession, dropHandlerListener);
  968.  
  969. The last parameter is optional, it allows you to receive notifications. Here is
  970. a minimal implementation:
  971.  
  972.   var dropHandlerListener = {
  973.     // called when the drop handling has completed
  974.     onDropComplete: function(aTargetList,
  975.                              aImportedInLibrary,
  976.                              aDuplicates,
  977.                              aInsertedInMediaList,
  978.                              aOtherDropsHandled) {
  979.       // returning true causes the standard drop report to be printed
  980.       // on the status bar, it is equivalent to calling standardReport
  981.       // using the parameters received on this callback
  982.       return true;
  983.     },
  984.     // called when the first item is handled (eg, to implement playback)
  985.     onFirstMediaItem: function(aTargetList, aFirstMediaItem) { }
  986.   };
  987.  
  988. To handle a drop with a specific mediaList target and drop insertion point, use
  989. the following code:
  990.  
  991.   ExternalDropHandler.dropOnList(window,
  992.                                  dragSession,
  993.                                  targetMediaList,
  994.                                  targetMediaListPosition,
  995.                                  dropHandlerListener);
  996.  
  997. In order to target the drop at the end of the targeted mediaList, you
  998. should give a value of -1 for targetMediaListPosition.
  999.  
  1000. Two similar methods (dropUrls and dropUrlsOnList) exist that let you simulate a
  1001. drop by giving a list of URLs, and triggering the same handling as the one that
  1002. would happen had these URLs been part of a dragsession drop.
  1003.  
  1004. The other public methods in this helper can be used to simplify the rest of your
  1005. drag and drop handler as well. For instance, an nsDragAndDrop observer's
  1006. getSupportedFlavours() method may be implemented simply as:
  1007.  
  1008.     var flavours = new FlavourSet();
  1009.     ExternalDropHandler.addFlavours(flavours);
  1010.     return flavours;
  1011.  
  1012. Also, getTransferData and DNDUtils.getTransferDataForFlavour may be used to
  1013. inspect the content of the dragSession before deciding what to do with it.
  1014.  
  1015. Finally, the standardReport and reportAddedTracks methods are used to send a
  1016. temporary message on the status bar, to report the result of a drag and drop
  1017. session. standardReport will format the text using the specific rules for what
  1018. to show and in which circumstances, and reportAddedTracks will report exactly
  1019. what you tell it to.
  1020.  
  1021. Important note:
  1022. ---------------
  1023.  
  1024. The window being passed as a parameter to both the drop and dropOnList methods
  1025. must implement the following two functions :
  1026.  
  1027. SBOpenModalDialog(aChromeUrl, aTargetId, aWindowFeatures, aWindowArguments);
  1028. installXPI(aXpiUrl);
  1029.  
  1030. These two methods are respectively implemented in windowUtils.js and
  1031. playerOpen.js, importing these scripts in your window ensures that the
  1032. requirements are met.
  1033.  
  1034. */
  1035.  
  1036. var ExternalDropHandler = {
  1037.  
  1038.   supportedFlavours: [ "application/x-moz-file",
  1039.                        "text/x-moz-url",
  1040.                        "text/unicode"],
  1041.  
  1042.   // returns true if the drag session contains supported external flavors
  1043.   isSupported: function(aDragSession) {
  1044.     return DNDUtils.isSupported(aDragSession, this.supportedFlavours);
  1045.   },
  1046.  
  1047.   // performs a default drop of the drag session. media items go to the
  1048.   // main library.
  1049.   drop: function(aWindow, aDragSession, aListener) {
  1050.     var mainLibrary = Cc["@songbirdnest.com/Songbird/library/Manager;1"]
  1051.                           .getService(Ci.sbILibraryManager)
  1052.                           .mainLibrary;
  1053.     this.dropOnList(aWindow, aDragSession, mainLibrary, -1, aListener);
  1054.   },
  1055.  
  1056.   // performs a default drop of a list of urls. media items go to the
  1057.   // main library.
  1058.   dropUrls: function(aWindow, aUrlArray, aListener) {
  1059.     var mainLibrary = Cc["@songbirdnest.com/Songbird/library/Manager;1"]
  1060.                           .getService(Ci.sbILibraryManager)
  1061.                           .mainLibrary;
  1062.     this.dropUrlsOnList(aWindow, aUrlArray, mainLibrary, -1, aListener);
  1063.   },
  1064.  
  1065.   // perform a drop onto a medialist. media items are inserted at the specified
  1066.   // position in the list if that list is orderable. otherwise, or if the
  1067.   // position is invalid, the items are added to the target list.
  1068.   dropOnList: function(aWindow,
  1069.                        aDragSession,
  1070.                        aTargetList,
  1071.                        aDropPosition,
  1072.                        aListener) {
  1073.     if (!aTargetList) {
  1074.       throw new Error("No target medialist specified for dropOnList");
  1075.     }
  1076.     this._dropFiles(aWindow,
  1077.                     aDragSession,
  1078.                     null,
  1079.                     aTargetList,
  1080.                     aDropPosition,
  1081.                     aListener);
  1082.   },
  1083.  
  1084.   // perform a drop of a list of urls onto a medialist. media items are inserted at
  1085.   // the specified position in the list if that list is orderable. otherwise, or if the
  1086.   // position is invalid, the items are added to the target list.
  1087.   dropUrlsOnList: function(aWindow,
  1088.                            aUrlArray,
  1089.                            aTargetList,
  1090.                            aDropPosition,
  1091.                            aListener) {
  1092.     if (!aTargetList) {
  1093.       throw new Error("No target medialist specified for dropOnList");
  1094.     }
  1095.     this._dropFiles(aWindow,
  1096.                     null,
  1097.                     aUrlArray,
  1098.                     aTargetList,
  1099.                     aDropPosition,
  1100.                     aListener);
  1101.   },
  1102.  
  1103.   // call this to automatically add the supported external flavors
  1104.   // to a flavourSet object
  1105.   addFlavours: function(aFlavourSet) {
  1106.     DNDUtils.addFlavours(aFlavourSet, this.supportedFlavours);
  1107.   },
  1108.  
  1109.   // returns an array with the data for any supported external flavor
  1110.   getTransferData: function(aSession) {
  1111.     return DNDUtils.getTransferData(aSession, this.supportedFlavours);
  1112.   },
  1113.  
  1114.   // simply forward the call. here in this object for completeness
  1115.   // see DNDUtils.getTransferDataForFlavour for more info
  1116.   getTransferDataForFlavour: function(aFlavour, aSession, aArray) {
  1117.     return DNDUtils.getTransferDataForFlavour(aFlavour, aSession, aArray);
  1118.   },
  1119.  
  1120.   // --------------------------------------------------------------------------
  1121.   // methods below this point are pretend-private
  1122.   // --------------------------------------------------------------------------
  1123.  
  1124.   _listener            : null,  // listener object, for notifications
  1125.  
  1126.   // initiate the handling of all dropped files: this handling is sliced up
  1127.   // into a number of 'frames', each frame importing one item, or queuing up
  1128.   // one directory for later import. at the end of each frame, the
  1129.   // _nextImportDropFrame method is called to schedule the next frame using a
  1130.   // short timer, so as to give the UI time to catch up, and we keep doing that
  1131.   // until everything in the file queue has been processed. when that's done,
  1132.   // we then look for queued directory scans, which we give to the directory
  1133.   // import service. after the directories have been processed, we notify the
  1134.   // listener that processing has ended. Note that the function can take either
  1135.   // a drag session of an array of URLs. If both are provided, only the session
  1136.   // will be handled (ie, the method is not meant to be called with both a session
  1137.   // and a urlarray).
  1138.   _dropFiles: function(window, session, urlarray, targetlist, position, listener) {
  1139.  
  1140.     // check that we are indeed processing an external drop
  1141.     if (session && !this.isSupported(session)) {
  1142.       return;
  1143.     }
  1144.  
  1145.     // if we are on win32, we will need to make local filenames lowercase
  1146.     var lcase = (this._getPlatformString() == "Windows_NT");
  1147.  
  1148.     // get drop data in any of the supported formats
  1149.     var dropdata = session ? this.getTransferData(session) : urlarray;
  1150.  
  1151.     // reset first media item, so we know to record it again
  1152.     this._firstMediaItem = null;
  1153.  
  1154.     // remember listener
  1155.     this._listener = listener;
  1156.  
  1157.     // Install all the dropped XPI files at the same time
  1158.     var xpiArray = {};
  1159.     var xpiCount = 0;
  1160.  
  1161.     var uriList = Cc["@songbirdnest.com/moz/xpcom/threadsafe-array;1"]
  1162.                       .createInstance(Ci.nsIMutableArray);
  1163.  
  1164.     var ioService = Cc["@mozilla.org/network/io-service;1"]
  1165.                         .getService(Ci.nsIIOService);
  1166.  
  1167.     // process all entries in the drop
  1168.     for (var dropentry in dropdata) {
  1169.       var dropitem = dropdata[dropentry];
  1170.  
  1171.       var item, flavour;
  1172.       if (session) {
  1173.         item = dropitem[0];
  1174.         flavour = dropitem[2];
  1175.       } else {
  1176.         item = dropitem;
  1177.         flavour = "text/x-moz-url";
  1178.       }
  1179.       var islocal = true;
  1180.       var rawData;
  1181.  
  1182.       var prettyName;
  1183.  
  1184.       if (flavour == "application/x-moz-file") {
  1185.         var ioService = Cc["@mozilla.org/network/io-service;1"]
  1186.                             .getService(Ci.nsIIOService);
  1187.         var fileHandler = ioService.getProtocolHandler("file")
  1188.                           .QueryInterface(Ci.nsIFileProtocolHandler);
  1189.         rawData = fileHandler.getURLSpecFromFile(item);
  1190.  
  1191.         // Check to see that this is a xpi/jar - if so handle that event
  1192.         if ( /\.(xpi|jar)$/i.test(rawData) && (item instanceof Ci.nsIFile) ) {
  1193.           xpiArray[item.leafName] = {
  1194.             URL: rawData,
  1195.             IconURL: URI_GENERIC_ICON_XPINSTALL,
  1196.             toString: function() { return this.URL; }
  1197.           };
  1198.           ++xpiCount;
  1199.         }
  1200.       } else {
  1201.         if (item instanceof Ci.nsISupportsString) {
  1202.           rawData = item.toString();
  1203.         } else {
  1204.           rawData = ""+item;
  1205.         }
  1206.         if (rawData.toLowerCase().indexOf("http://") >= 0) {
  1207.           // remember that this is not a local file
  1208.           islocal = false;
  1209.         } else if (rawData.toLowerCase().indexOf("file://") >= 0) {
  1210.           islocal = true;
  1211.         } else {
  1212.           // not a url, ignore
  1213.           continue;
  1214.         }
  1215.       }
  1216.  
  1217.       // rawData contains a file or http URL to the dropped media.
  1218.  
  1219.       // check if there is a pretty name we can grab
  1220.       var separator = rawData.indexOf("\n");
  1221.       if (separator != -1) {
  1222.         prettyName = rawData.substr(separator+1);
  1223.         rawData = rawData.substr(0,separator);
  1224.       }
  1225.  
  1226.       // make filename lowercase if necessary (win32)
  1227.       if (lcase && islocal) {
  1228.         rawData = rawData.toLowerCase();
  1229.       }
  1230.  
  1231.       // record this file for later processing
  1232.       uriList.appendElement(ioService.newURI(rawData, null, null), false);
  1233.     }
  1234.  
  1235.     // Timeout the XPI install
  1236.     if (xpiCount > 0) {
  1237.       window.setTimeout(window.installXPIArray, 10, xpiArray);
  1238.     }
  1239.  
  1240.     var uriImportService = Cc["@songbirdnest.com/uri-import-service;1"]
  1241.                                .getService(Ci.sbIURIImportService);
  1242.     uriImportService.importURIArray(uriList,
  1243.                                     window,
  1244.                                     targetlist,
  1245.                                     position,
  1246.                                     this);
  1247.   },
  1248.  
  1249.   // sbIURIImportListener
  1250.   onImportComplete: function(aTargetMediaList,
  1251.                              aTotalImportCount,
  1252.                              aTotalDupeCount,
  1253.                              aTotalUnsupported,
  1254.                              aTotalInserted,
  1255.                              aOtherDrops)
  1256.   {
  1257.     var device,
  1258.         isDevice;
  1259.  
  1260.     // Get the device reference from the target library.
  1261.     try {
  1262.       device = aTargetMediaList.library.device;
  1263.     } catch (e) {
  1264.       device = null;
  1265.     }
  1266.  
  1267.     isDevice = (device !== null);
  1268.  
  1269.     if (this._listener) {
  1270.       if (this._listener.onDropComplete(aTargetMediaList,
  1271.                                         aTotalImportCount,
  1272.                                         aTotalDupeCount,
  1273.                                         aTotalInserted,
  1274.                                         aTotalUnsupported,
  1275.                                         aOtherDrops)) {
  1276.         DNDUtils.standardReport(aTargetMediaList,
  1277.                                 aTotalImportCount,
  1278.                                 aTotalDupeCount,
  1279.                                 aTotalUnsupported,
  1280.                                 aTotalInserted,
  1281.                                 aOtherDrops,
  1282.                                 isDevice);
  1283.       }
  1284.     }
  1285.     else {
  1286.       DNDUtils.standardReport(aTargetMediaList,
  1287.                               aTotalImportCount,
  1288.                               aTotalInserted, // usually dupes
  1289.                               aTotalUnsupported,
  1290.                               aTotalInserted,
  1291.                               aOtherDrops,
  1292.                               isDevice);
  1293.     }
  1294.   },
  1295.  
  1296.  
  1297.   onFirstMediaItem: function(aTargetMediaList, aTargetMediaItem)
  1298.   {
  1299.     if (this._listener) {
  1300.       this._listener.onFirstMediaItem(aTargetMediaList, aTargetMediaItem);
  1301.     }
  1302.   },
  1303.  
  1304.   // returns the platform string
  1305.   _getPlatformString: function() {
  1306.     try {
  1307.       var sysInfo =
  1308.         Components.classes["@mozilla.org/system-info;1"]
  1309.                   .getService(Components.interfaces.nsIPropertyBag2);
  1310.       return sysInfo.getProperty("name");
  1311.     }
  1312.     catch (e) {
  1313.       var user_agent = navigator.userAgent;
  1314.       if (user_agent.indexOf("Windows") != -1)
  1315.         return "Windows_NT";
  1316.       else if (user_agent.indexOf("Mac OS X") != -1)
  1317.         return "Darwin";
  1318.       else if (user_agent.indexOf("Linux") != -1)
  1319.         return "Linux";
  1320.       else if (user_agent.indexOf("SunOS") != -1)
  1321.         return "SunOS";
  1322.       return "";
  1323.     }
  1324.   }
  1325.  
  1326. }
  1327.  
  1328.  
  1329.  
  1330.  
  1331.