sbAddToDevice.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 Components.utils.import("resource://app/jsmodules/sbProperties.jsm");
26 Components.utils.import("resource://app/jsmodules/DropHelper.jsm");
27 Components.utils.import("resource://app/jsmodules/sbLibraryUtils.jsm");
28 Components.utils.import("resource://app/jsmodules/SBUtils.jsm");
29 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
30 
31 
32 const Ci = Components.interfaces;
33 const Cc = Components.classes;
34 const Cr = Components.results;
35 
37  "@songbirdnest.com/Songbird/Library/medialistduplicatefilter;1";
38 
39 const ADDTODEVICE_MENU_TYPE = "submenu";
40 const ADDTODEVICE_MENU_ID = "library_cmd_addtodevice";
41 const ADDTODEVICE_MENU_NAME = "&command.addtodevice";
42 const ADDTODEVICE_MENU_TOOLTIP = "&command.tooltip.addtodevice";
43 const ADDTODEVICE_MENU_KEY = "&command.shortcut.key.addtodevice";
44 const ADDTODEVICE_MENU_KEYCODE = "&command.shortcut.keycode.addtodevice";
45 const ADDTODEVICE_MENU_MODIFIERS = "&command.shortcut.modifiers.addtodevice";
46 
47 
48 const ADDTODEVICE_COMMAND_ID = "library_cmd_addtodevice:";
49 
50 EXPORTED_SYMBOLS = [ "addToDeviceHelper",
51  "SBPlaylistCommand_AddToDevice" ];
52 
60 function createUnwrapper(aSelection) {
61  var unwrapper = Cc["@songbirdnest.com/Songbird/Library/EnumeratorWrapper;1"]
62  .createInstance(Ci.sbIMediaListEnumeratorWrapper);
63  unwrapper.initialize(aSelection);
64 
65  return unwrapper;
66 }
67 
68 // ----------------------------------------------------------------------------
69 // The "Add to device" dynamic command object
70 // ----------------------------------------------------------------------------
72 {
73  m_Context: {
74  m_Playlist: null,
75  m_Window: null
76  },
77 
78  m_addToDevice: null,
79 
81  {
82  m_Types: new Array
83  (
85  ),
86 
87  m_Ids: new Array
88  (
90  ),
91 
92  m_Names: new Array
93  (
95  ),
96 
97  m_Tooltips: new Array
98  (
100  ),
101 
102  m_Keys: new Array
103  (
105  ),
106 
107  m_Keycodes: new Array
108  (
110  ),
111 
112  m_Enableds: new Array
113  (
114  true
115  ),
116 
117  m_Modifiers: new Array
118  (
120  ),
121 
122  m_PlaylistCommands: new Array
123  (
124  null
125  ),
126 
127  m_DeviceIds: new Array
128  (
129  null
130  )
131  },
132 
133  _getMenu: function(aSubMenu)
134  {
135  var cmds;
136 
137  cmds = this.m_addToDevice.handleGetMenu(aSubMenu);
138  if (cmds) return cmds;
139 
140  switch (aSubMenu) {
141  default:
142  cmds = this.m_root_commands;
143  break;
144  }
145  return cmds;
146  },
147 
148  getVisible: function( aHost )
149  {
150  return this.m_addToDevice.hasDevices();
151  },
152 
153  getNumCommands: function( aSubMenu, aHost )
154  {
155  var cmds = this._getMenu(aSubMenu);
156  return cmds.m_Ids.length;
157  },
158 
159  getCommandId: function( aSubMenu, aIndex, aHost )
160  {
161  var cmds = this._getMenu(aSubMenu);
162  if ( aIndex >= cmds.m_Ids.length ) return "";
163  return cmds.m_Ids[ aIndex ];
164  },
165 
166  getCommandType: function( aSubMenu, aIndex, aHost )
167  {
168  var cmds = this._getMenu(aSubMenu);
169  if ( aIndex >= cmds.m_Ids.length ) return "";
170  return cmds.m_Types[ aIndex ];
171  },
172 
173  getCommandText: function( aSubMenu, aIndex, aHost )
174  {
175  var cmds = this._getMenu(aSubMenu);
176  if ( aIndex >= cmds.m_Names.length ) return "";
177  return cmds.m_Names[ aIndex ];
178  },
179 
180  getCommandFlex: function( aSubMenu, aIndex, aHost )
181  {
182  var cmds = this._getMenu(aSubMenu);
183  if ( cmds.m_Types[ aIndex ] == "separator" ) return 1;
184  return 0;
185  },
186 
187  getCommandToolTipText: function( aSubMenu, aIndex, aHost )
188  {
189  var cmds = this._getMenu(aSubMenu);
190  if ( aIndex >= cmds.m_Tooltips.length ) return "";
191  return cmds.m_Tooltips[ aIndex ];
192  },
193 
194  getCommandValue: function( aSubMenu, aIndex, aHost )
195  {
196  },
197 
198  instantiateCustomCommand: function( aDocument, aId, aHost )
199  {
200  return null;
201  },
202 
203  refreshCustomCommand: function( aElement, aId, aHost )
204  {
205  },
206 
207  getCommandVisible: function( aSubMenu, aIndex, aHost )
208  {
209  return true;
210  },
211 
212  getCommandFlag: function( aSubmenu, aIndex, aHost )
213  {
214  return false;
215  },
216 
217  getCommandChoiceItem: function( aChoiceMenu, aHost )
218  {
219  return "";
220  },
221 
222  getCommandEnabled: function( aSubMenu, aIndex, aHost )
223  {
224  if (this.m_Context.m_Playlist.tree.currentIndex == -1) return false;
225 
226  var cmds = this._getMenu(aSubMenu);
227  if (aSubMenu == ADDTODEVICE_MENU_ID) {
228  var deviceRegistrar =
229  Components.classes["@songbirdnest.com/Songbird/DeviceManager;2"]
230  .getService(Components.interfaces.sbIDeviceRegistrar);
231 
232  var device = deviceRegistrar.getDevice(cmds.m_DeviceIds[aIndex]);
233  if (device.state == Components.interfaces.sbIDevice.STATE_MOUNTING) {
234  // Don't enable the device command if the device is
235  // currently mounting.
236  return false;
237  }
238  }
239 
240  return cmds.m_Enableds[ aIndex ];
241  },
242 
243  getCommandShortcutModifiers: function ( aSubMenu, aIndex, aHost )
244  {
245  var cmds = this._getMenu(aSubMenu);
246  if ( aIndex >= cmds.m_Modifiers.length ) return "";
247  return cmds.m_Modifiers[ aIndex ];
248  },
249 
250  getCommandShortcutKey: function ( aSubMenu, aIndex, aHost )
251  {
252  var cmds = this._getMenu(aSubMenu);
253  if ( aIndex >= cmds.m_Keys.length ) return "";
254  return cmds.m_Keys[ aIndex ];
255  },
256 
257  getCommandShortcutKeycode: function ( aSubMenu, aIndex, aHost )
258  {
259  var cmds = this._getMenu(aSubMenu);
260  if ( aIndex >= cmds.m_Keycodes.length ) return "";
261  return cmds.m_Keycodes[ aIndex ];
262  },
263 
264  getCommandShortcutLocal: function ( aSubMenu, aIndex, aHost )
265  {
266  return true;
267  },
268 
269  getCommandSubObject: function ( aSubMenu, aIndex, aHost )
270  {
271  var cmds = this._getMenu(aSubMenu);
272  if ( aIndex >= cmds.m_PlaylistCommands.length ) return null;
273  return cmds.m_PlaylistCommands[ aIndex ];
274  },
275 
276  onCommand: function( aSubMenu, aIndex, aHost, id, value )
277  {
278  if ( id )
279  {
280  // ADDTODEVICE
281  if (this.m_addToDevice.handleCommand(id)) return;
282 
283  // ...
284  }
285  },
286 
287  // The object registered with the sbIPlaylistCommandsManager interface acts
288  // as a template for instances bound to specific playlist elements
289 
290  dupObject: function (obj) {
291  var r = {};
292  for ( var i in obj )
293  {
294  r[ i ] = obj[ i ];
295  }
296  return r;
297  },
298 
299  duplicate: function()
300  {
301  var obj = this.dupObject(this);
302  obj.m_Context = this.dupObject(this.m_Context);
303  return obj;
304  },
305 
306  initCommands: function(aHost) {
307  if (!this.m_addToDevice) {
308  this.m_addToDevice = new addToDeviceHelper();
309  this.m_addToDevice.init(this);
310  }
311  },
312 
313  shutdownCommands: function() {
314  if (!this.m_addToDevice) {
315  dump("this.m_addToDevice is null in SBPlaylistCommand_AddToDevice ?!!\n");
316  return;
317  }
318  this.m_addToDevice.shutdown();
319  this.m_addToDevice = null;
320  this.m_Context = null;
321  },
322 
323  setContext: function( context )
324  {
325  var playlist = context.playlist;
326  var window = context.window;
327 
328  // Ah. Sometimes, things are being secure.
329 
330  if ( playlist && playlist.wrappedJSObject )
331  playlist = playlist.wrappedJSObject;
332 
333  if ( window && window.wrappedJSObject )
334  window = window.wrappedJSObject;
335 
336  this.m_Context.m_Playlist = playlist;
337  this.m_Context.m_Window = window;
338  },
339 
340  QueryInterface : function(aIID)
341  {
342  if (!aIID.equals(Components.interfaces.sbIPlaylistCommands) &&
343  !aIID.equals(Components.interfaces.nsISupportsWeakReference) &&
344  !aIID.equals(Components.interfaces.nsISupports))
345  {
346  throw Components.results.NS_ERROR_NO_INTERFACE;
347  }
348 
349  return this;
350  }
351 }; // SBPlaylistCommand_AddToDevice declaration
352 
353 function addToDeviceHelper() {
354 }
355 
356 addToDeviceHelper.prototype = {
357  m_listofdevices: null,
358  m_commands: null,
359  m_deviceManager: null,
360  m_libraryManager: null,
361 
362  LOG: function(str) {
363  var consoleService = Components.classes['@mozilla.org/consoleservice;1']
364  .getService(Components.interfaces.nsIConsoleService);
365  consoleService.logStringMessage(str);
366  },
367 
368  init: function addToDeviceHelper_init(aCommands) {
369  this.m_libraryManager =
370  Components.classes["@songbirdnest.com/Songbird/library/Manager;1"]
371  .getService(Components.interfaces.sbILibraryManager);
372  this.m_deviceManager =
373  Components.classes["@songbirdnest.com/Songbird/DeviceManager;2"]
374  .getService(Components.interfaces.sbIDeviceManager2);
375  var eventTarget =
376  this.m_deviceManager.QueryInterface(
377  Components.interfaces.sbIDeviceEventTarget
378  );
379  eventTarget.addEventListener(this);
380  this.m_commands = aCommands;
381  this.makeListOfDevices();
382  },
383 
384  shutdown: function addToDeviceHelper_shutdown() {
385  var eventTarget =
386  this.m_deviceManager.QueryInterface(
387  Components.interfaces.sbIDeviceEventTarget
388  );
389  eventTarget.removeEventListener(this);
390  this.m_deviceManager = null;
391  },
392 
393  // returns true if we have at least one device in the list,
394  // the commands object hides the "add to device" submenu when
395  // no device is present
396  hasDevices: function addToDeviceHelper_hasDevices() {
397  return (this.m_listofdevices &&
398  this.m_listofdevices.m_Types &&
399  this.m_listofdevices.m_Types.length > 0);
400  },
401 
402  // builds the list of devices, called on startup and when a
403  // device is added or removed
404  makeListOfDevices: function addToDeviceHelper_makeListOfDevices() {
405  this._makingList = true;
406 
407  this.m_listofdevices = {};
408  this.m_listofdevices.m_Types = new Array();
409  this.m_listofdevices.m_Ids = new Array();
410  this.m_listofdevices.m_Names = new Array();
411  this.m_listofdevices.m_Tooltips = new Array();
412  this.m_listofdevices.m_Enableds = new Array();
413  this.m_listofdevices.m_Modifiers = new Array();
414  this.m_listofdevices.m_Keys = new Array();
415  this.m_listofdevices.m_Keycodes = new Array();
416  this.m_listofdevices.m_PlaylistCommands = new Array();
417  this.m_listofdevices.m_DeviceIds = new Array();
418 
419  // get all devices
420  var registrar =
421  this.m_deviceManager.QueryInterface(
422  Components.interfaces.sbIDeviceRegistrar
423  );
424  var devices = Array();
425  // turn into a js array
426  for (var i=0;i<registrar.devices.length;i++) {
427  var device = registrar.devices.queryElementAt
428  (i, Components.interfaces.sbIDevice);
429  if (device && device.connected)
430  devices.push(device);
431  }
432  // order of devices returned by the registrar is undefined,
433  // so sort by device name
434  function deviceSorter(x, y) {
435  var nameX = x.name;
436  var nameY = y.name;
437  if (x < y) return -1;
438  if (y < x) return 1;
439  return 0;
440  }
441  devices.sort(deviceSorter);
442  // extract the device names and associated library guids
443  // and fill the arrays used by the command object
444  for (var d in devices) {
445  var libraryguid;
446  var device = devices[d];
447  var devicename = device.name;
448 
449  // don't add devices that are read-only
450  var deviceProperties = device.properties.properties;
451  var accessCompatibility;
452  try {
453  accessCompatibility = deviceProperties.getPropertyAsAString(
454  "http://songbirdnest.com/device/1.0#accessCompatibility");
455  } catch (ex) {}
456  if (accessCompatibility == "ro") {
457  continue;
458  }
459 
460  var isEnabled = false;
461  if (!devicename)
462  devicename = "Unnamed Device";
463  var library = device.defaultLibrary;
464  if (library) {
465  isEnabled = library.userEditable;
466  libraryguid = library.guid;
467  } else {
468  continue;
469  }
470  this.m_listofdevices.m_Types.push("action");
471  this.m_listofdevices.m_Ids.push(ADDTODEVICE_COMMAND_ID +
472  libraryguid + ";" +
473  devicename);
474  this.m_listofdevices.m_Names.push(devicename);
475  this.m_listofdevices.m_Tooltips.push(devicename);
476  this.m_listofdevices.m_Enableds.push(isEnabled);
477  this.m_listofdevices.m_Modifiers.push("");
478  this.m_listofdevices.m_Keys.push("");
479  this.m_listofdevices.m_Keycodes.push("");
480  this.m_listofdevices.m_PlaylistCommands.push(null);
481  this.m_listofdevices.m_DeviceIds.push(device.id);
482  }
483 
484  this._makingList = false;
485  },
486 
487  handleGetMenu: function addToDeviceHelper_handleGetMenu(aSubMenu) {
488  if (this.m_listofdevices == null) {
489  // handleGetMenu called before makeListOfPlaylists, this would
490  // cause infinite recursion : the command object would not find
491  // the menu either, would return null to getMenu which corresponds
492  // to the root menu, and it'd recurse infinitly.
493  throw Components.results.NS_ERROR_FAILURE;
494  }
495  if (aSubMenu == ADDTODEVICE_MENU_ID) return this.m_listofdevices;
496  return null;
497  },
498 
499  // handle click on a device command item
500  handleCommand: function addToDeviceHelper_handleCommand(id) {
501  try {
502  var context = this.m_commands.m_Context;
503  var addtodevicestr = ADDTODEVICE_COMMAND_ID;
504  if ( id.slice(0, addtodevicestr.length) == addtodevicestr) {
505  var r = id.slice(addtodevicestr.length);
506  var parts = r.split(';');
507  if (parts.length >= 2) {
508  var libraryguid = parts[0];
509  var devicename = parts[1];
510  this.addToDevice(libraryguid, context.m_Playlist, devicename);
511  return true;
512  }
513  }
514  } catch (e) {
515  Components.utils.reportError(e);
516  }
517  return false;
518  },
519 
520  _getDeviceLibraryForLibrary: function(aDevice, aLibrary) {
521  var libs = aDevice.content.libraries;
522  for (var i = 0; i < libs.length; i++) {
523  var devLib = libs.queryElementAt(i, Ci.sbIDeviceLibrary);
524  if (devLib.equals(aLibrary))
525  return devLib;
526  }
527 
528  return null;
529  },
530 
531  _itemsFromEnumerator: function(aItemEnum) {
532  var items = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
533 
534  while (aItemEnum.hasMoreElements())
535  items.appendElement(aItemEnum.getNext(), false);
536 
537  return items;
538  },
539 
540  // perform the transfer of the selected items to the device library
541  addToDevice: function addToDeviceHelper_addToDevice(
542  devicelibraryguid,
543  sourceplaylist,
544  devicename) {
545  var library = this.m_libraryManager.getLibrary(devicelibraryguid);
546  if (library) {
547  var selection =
548  sourceplaylist.mediaListView.selection.selectedMediaItems;
549  var items = this._itemsFromEnumerator(selection);
550 
551  var deviceManager = Cc["@songbirdnest.com/Songbird/DeviceManager;2"]
552  .getService(Ci.sbIDeviceManager2);
553  var device = deviceManager.getDeviceForItem(library);
554  var deviceLibrary = this._getDeviceLibraryForLibrary(device, library);
555 
556  var differ =
557  Cc["@songbirdnest.com/Songbird/Device/DeviceLibrarySyncDiff;1"]
558  .createInstance(Ci.sbIDeviceLibrarySyncDiff);
559  var changeset = {};
560  var destItems = {};
561 
562  differ.generateDropLists(sourceplaylist.library,
563  library,
564  null,
565  items,
566  destItems,
567  changeset);
568 
569  device.exportToDevice(deviceLibrary, changeset.value);
570 
571  DNDUtils.reportAddedTracks(changeset.value.changes.length,
572  0, /* no duplicate reporting */
573  0, /* no unsupported reporting */
574  devicename,
575  true);
576  }
577  },
578 
579  //-----------------------------------------------------------------------------
580  // methods for refreshing the list when needed, detection is performed via
581  // an sbIDeviceEventListener on the device manager
582  //-----------------------------------------------------------------------------
583 
584  _makingList : false,
585 
586  refreshCommands: function addToDeviceHelper_refreshCommands() {
587 
588  // Bug fixers beware! This code gets called very frequently and can have
589  // difficult-to-debug side effects. Proceed with caution.
590 
591  var self = this;
592  function ensureRefreshExists() {
593  // Explicitly return true or false. Otherwise, JS will complain to the
594  // error console if any of the checks finds an undefined property.
595  return (self.m_commands &&
596  self.m_commands.m_Context &&
597  self.m_commands.m_Context.m_Playlist &&
598  self.m_commands.m_Context.m_Playlist.refreshCommands) ?
599  true : false;
600  }
601 
602  /* We need to ensure that the context and playlist have been initialized
603  * and are ready to display commands before calling refreshCommands.
604  *
605  * It is possible for an event handler to fire before the binding
606  * is created or after it is destroyed, so it is possible for this to
607  * be triggered before m_Playlist is fully instantiated or after pieces
608  * of it have gone away. Thus, we need to also check that refreshCommands
609  * is present to ensure the playlist binding is in a good state. */
610 
611  if (ensureRefreshExists()) {
612  this.makeListOfDevices();
613  // Check again, as the playlist binding can be destroyed during the
614  // previous function call.
615  if (ensureRefreshExists()) {
616  this.m_commands.m_Context.m_Playlist.refreshCommands();
617  }
618  }
619  },
620 
621  onUpdateEvent: function addToDeviceHelper_onUpdateEvent() {
622  if (this._makingList) return;
623  this.refreshCommands();
624  },
625 
626  QueryInterface: function QueryInterface(iid) {
627  if (!iid.equals(Components.interfaces.sbIDeviceEventListener) &&
628  !iid.equals(Components.interfaces.nsISupports))
629  throw Components.results.NS_ERROR_NO_INTERFACE;
630  return this;
631  },
632 
633  // trap event for device library added/removed, and refresh the commands
634  // we trap the library add/remove (instead of the plain device add/remove)
635  // since we check the library count in makeListOfDevices(), so we need to
636  // make sure we don't run before the libraries have been added to the device
637  onDeviceEvent: function addToDeviceHelper_onDeviceEvent(aEvent) {
638  if (aEvent.type == Components.interfaces.sbIDeviceEvent.EVENT_DEVICE_LIBRARY_ADDED ||
639  aEvent.type == Components.interfaces.sbIDeviceEvent.EVENT_DEVICE_LIBRARY_REMOVED ||
640  aEvent.type == Components.interfaces.sbIDeviceEvent.EVENT_DEVICE_MOUNTING_END) {
641  this.onUpdateEvent();
642  }
643  }
644 
645 };
const Cc
const ADDTODEVICE_MENU_MODIFIERS
var registrar
#define LOG(args)
const Ci
menuItem id
Definition: FeedWriter.js:971
const ADDTODEVICE_MENU_KEY
const ADDTODEVICE_MENU_KEYCODE
function addToDeviceHelper()
_updateCookies aHost
var DNDUtils
Definition: DropHelper.jsm:80
sbOSDControlService prototype QueryInterface
const ADDTODEVICE_MENU_NAME
sbDownloadDeviceServicePaneModule prototype shutdown
EXPORTED_SYMBOLS
let window
function d(s)
_window init
Definition: FeedWriter.js:1144
_collectFormDataForFrame aDocument
function createUnwrapper(aSelection)
Creates a new unwrapper helper object which ensures downloadStatusTarget is always set when adding it...
const Cr
return null
Definition: FeedWriter.js:1143
const ADDTODEVICE_MENU_TYPE
const SB_MEDIALISTDUPLICATEFILTER_CONTRACTID
const ADDTODEVICE_COMMAND_ID
countRef value
Definition: FeedWriter.js:1423
var SBPlaylistCommand_AddToDevice
SBPlaylistCommand_DownloadToPlaylist m_root_commands
const ADDTODEVICE_MENU_TOOLTIP
_getSelectedPageStyle s i
const ADDTODEVICE_MENU_ID