deviceSupport.js
Go to the documentation of this file.
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 :miv */
3 /*
4  *=BEGIN SONGBIRD GPL
5  *
6  * This file is part of the Songbird web player.
7  *
8  * Copyright(c) 2005-2010 POTI, Inc.
9  * http://www.songbirdnest.com
10  *
11  * This file may be licensed under the terms of of the
12  * GNU General Public License Version 2 (the ``GPL'').
13  *
14  * Software distributed under the License is distributed
15  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
16  * express or implied. See the GPL for the specific language
17  * governing rights and limitations.
18  *
19  * You should have received a copy of the GPL along with this
20  * program. If not, go to http://www.gnu.org/licenses/gpl.html
21  * or write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23  *
24  *=END SONGBIRD GPL
25  */
26 
32 //------------------------------------------------------------------------------
33 //------------------------------------------------------------------------------
34 //
35 // Device support services.
36 //
37 // These services provide device support for the main Songbird window.
38 //
39 //------------------------------------------------------------------------------
40 //------------------------------------------------------------------------------
41 
42 //------------------------------------------------------------------------------
43 //
44 // Device support imported services.
45 //
46 //------------------------------------------------------------------------------
47 
48 // Component manager defs.
49 var Cc = Components.classes;
50 var Ci = Components.interfaces;
51 var Cr = Components.results;
52 var Cu = Components.utils;
53 
54 // Songbird imports.
55 Cu.import("resource://app/jsmodules/ArrayConverter.jsm");
56 Cu.import("resource://app/jsmodules/DOMUtils.jsm");
57 Cu.import("resource://app/jsmodules/StringUtils.jsm");
58 
59 
60 //------------------------------------------------------------------------------
61 //------------------------------------------------------------------------------
62 //
63 // Device volume support services.
64 //
65 //------------------------------------------------------------------------------
66 //------------------------------------------------------------------------------
67 
69  //
70  // Device volume support services configuration.
71  //
72  // volumeListStabilizationTime Amount of time in milliseconds to wait for
73  // a device volume list to stabilize.
74  //
75 
76  volumeListStabilizationTime: 1000,
77 
78 
79  //
80  // Device volume support services fields.
81  //
82  // _deviceInfoTable Table of device info.
83  // _domEventListenerSet Set of DOM event listeners.
84  // _deviceManager Device manager.
85  //
86 
87  _deviceInfoTable: null,
88  _domEventListenerSet: null,
90 
91 
92  //----------------------------------------------------------------------------
93  //
94  // Device volume support services.
95  //
96  //----------------------------------------------------------------------------
97 
102  initialize: function sbDeviceVolumeSupport_initialize() {
103  var _this = this;
104  var func;
105 
106  // Create a DOM event listener set.
107  this._domEventListenerSet = new DOMEventListenerSet();
108 
109  // Finalize when the window unloads.
110  func = function onUnload(aEvent) { _this.finalize(); };
111  this._domEventListenerSet.add(window, "unload", func, false, true);
112 
113  // Continue initializing after the main window loads.
114  func = function onLoad(aEvent) { _this._initialize(); };
115  this._domEventListenerSet.add(window, "load", func, false, true);
116  },
117 
118  _initialize: function sbDeviceVolumeSupport__initialize() {
119  // Initialize the device info table.
120  this._deviceInfoTable = {};
121 
122  // Get the device manager.
123  this._deviceManager = Cc["@songbirdnest.com/Songbird/DeviceManager;2"]
124  .getService(Ci.sbIDeviceEventTarget)
125  .QueryInterface(Ci.sbIDeviceRegistrar);
126 
127  // Listen to all device events.
128  this._deviceManager.addEventListener(this);
129 
130  // Add each device.
131  var deviceRegistrar = Cc["@songbirdnest.com/Songbird/DeviceManager;2"]
132  .getService(Ci.sbIDeviceRegistrar);
133  for each (device in ArrayConverter.JSEnum(deviceRegistrar.devices)) {
134  this._addDevice(device.QueryInterface(Ci.sbIDevice));
135  }
136  },
137 
138 
143  finalize: function sbDeviceVolumeSupport_finalize() {
144  // Stop listening to device events.
145  if (this._deviceManager)
146  this._deviceManager.removeEventListener(this);
147 
148  // Remove DOM event listeners.
149  if (this._domEventListenerSet) {
150  this._domEventListenerSet.removeAll();
151  }
152 
153  // Remove all devices.
154  this._removeAllDevices();
155 
156  // Remove all references.
157  this._deviceInfoTable = null;
158  this._domEventListenerSet = null;
159  this._deviceManager = null;
160  },
161 
162 
163  //----------------------------------------------------------------------------
164  //
165  // Device volume support sbIDeviceEventListener services.
166  //
167  //----------------------------------------------------------------------------
168 
175  onDeviceEvent: function deviceVolumeSupport_onDeviceEvent(aEvent) {
176  // Dispatch processing of event.
177  switch (aEvent.type)
178  {
179  case Ci.sbIDeviceEvent.EVENT_DEVICE_ADDED :
180  this._addDevice(aEvent.data.QueryInterface(Ci.sbIDevice));
181  break;
182 
183  case Ci.sbIDeviceEvent.EVENT_DEVICE_REMOVED :
184  this._removeDevice(aEvent.data.QueryInterface(Ci.sbIDevice));
185  break;
186 
187  case Ci.sbIDeviceEvent.EVENT_DEVICE_DEFAULT_LIBRARY_CHANGED :
188  case Ci.sbIDeviceEvent.EVENT_DEVICE_LIBRARY_ADDED :
189  case Ci.sbIDeviceEvent.EVENT_DEVICE_LIBRARY_REMOVED :
190  this._monitorDeviceVolumes(aEvent.origin.QueryInterface(Ci.sbIDevice));
191  break;
192 
193  default :
194  break;
195  }
196  },
197 
198 
199  //----------------------------------------------------------------------------
200  //
201  // Internal device volume support services.
202  //
203  //----------------------------------------------------------------------------
204 
212  _addDevice: function sbDeviceVolumeSupport__addDevice(aDevice) {
213  // Do nothing if device already added.
214  if (aDevice.id in this._deviceInfoTable)
215  return;
216 
217  // Create a device info data record.
218  var deviceInfo = { device: aDevice };
219 
220  // Add the device info to the device info table.
221  this._deviceInfoTable[aDevice.id] = deviceInfo;
222 
223  // Monitor device volumes.
224  this._monitorDeviceVolumes(deviceInfo);
225  },
226 
227 
236  _removeDevice: function sbDeviceVolumeSupport__removeDevice(aDeviceSpec) {
237  // Get the device info data record from the device spec. Return if no
238  // device info data record is available.
239  var deviceInfo;
240  if (aDeviceSpec instanceof Ci.sbIDevice) {
241  deviceInfo = this._deviceInfoTable[aDeviceSpec.id];
242  }
243  else {
244  deviceInfo = aDeviceSpec
245  }
246  if (!deviceInfo)
247  return;
248 
249  // Remove the device info from the device info table.
250  delete this._deviceInfoTable[deviceInfo.device.id];
251 
252  // Clear any volume monitor timeouts.
253  if (deviceInfo.monitorVolumesTimeout) {
254  clearTimeout(deviceInfo.monitorVolumesTimeout);
255  deviceInfo.monitorVolumesTimeout = null;
256  }
257 
258  // Remove any device notifications.
259  if (deviceInfo.notification) {
260  deviceInfo.notification.close();
261  deviceInfo.notification = null;
262  }
263  },
264 
265 
271  _removeAllDevices: function sbDeviceVolumeSupport__removeAllDevices() {
272  // Remove all devices.
273  var deviceInfoList = [];
274  for each (deviceInfo in this._deviceInfoTable) {
275  deviceInfoList.push(deviceInfo);
276  }
277  for each (deviceInfo in deviceInfoList) {
278  this._removeDevice(deviceInfo);
279  }
280  },
281 
282 
291  _monitorDeviceVolumes:
292  function sbDeviceVolumeSupport__monitorDeviceVolumes(aDeviceSpec) {
293  // Get the device info data record from the device spec. Return if no
294  // device info data record is available.
295  var deviceInfo;
296  if (aDeviceSpec instanceof Ci.sbIDevice) {
297  deviceInfo = this._deviceInfoTable[aDeviceSpec.id];
298  }
299  else {
300  deviceInfo = aDeviceSpec
301  }
302  if (!deviceInfo)
303  return;
304 
305  // Check if the device volume list is stable.
306  var stable = this._isDeviceVolumeListStable(deviceInfo);
307 
308  // If the device volume list is not stable, start a timer to check again
309  // after a delay and return.
310  if (!stable) {
311  // Clear any currently running timer.
312  if (deviceInfo.monitorVolumesTimeout)
313  clearTimeout(deviceInfo.monitorVolumesTimeout);
314 
315  // Start a timer.
316  var _this = this;
317  var func = function _monitorDeviceVolumesWrapper() {
318  deviceInfo.monitorVolumesTimeout = null;
319  _this._monitorDeviceVolumes(deviceInfo);
320  };
321  deviceInfo.monitorVolumesTimeout =
322  setTimeout(func, this.volumeListStabilizationTime);
323 
324  return;
325  }
326 
327  // Update device notification.
328  this._updateNotification(deviceInfo);
329 
330  // Update service pane.
331  this._updateServicePane(deviceInfo);
332 
333  // Check for new volumes.
334  this._checkForNewVolumes(deviceInfo);
335  },
336 
337 
348  _isDeviceVolumeListStable:
349  function sbDeviceVolumeSupport__isDeviceVolumeListStable(aDeviceInfo) {
350  // Get the current device volume list and sort it.
351  var volumeList = [];
352  var libraries = aDeviceInfo.device.content.libraries;
353  for (var i = 0; i < libraries.length; i++) {
354  var library = libraries.queryElementAt(i, Ci.sbIDeviceLibrary);
355  volumeList.push(library.guid);
356  }
357  volumeList.sort();
358 
359  // Determine if the device volume list has changed.
360  var volumeListChanged = false;
361  if (!aDeviceInfo.volumeList ||
362  (aDeviceInfo.volumeList.length != volumeList.length)) {
363  volumeListChanged = true;
364  }
365  else {
366  for (var i = 0; i < volumeList.length; i++) {
367  if (aDeviceInfo.volumeList[i] != volumeList[i]) {
368  volumeListChanged = true;
369  break;
370  }
371  }
372  }
373 
374  // If the device volume list has changed, update it and return that the
375  // volume list is not stable.
376  if (volumeListChanged) {
377  aDeviceInfo.volumeList = volumeList;
378  aDeviceInfo.volumeListTimeStamp = Date.now();
379  return false;
380  }
381 
382  // If the device volume list has not been stable for long enough, return
383  // that the volume list is not stable.
384  var currentTime = Date.now();
385  if ((currentTime - aDeviceInfo.volumeListTimeStamp) <
386  this.volumeListStabilizationTime) {
387  return false;
388  }
389 
390  // Device volume list is stable.
391  return true;
392  },
393 
394 
403  _checkForNewVolumes:
404  function sbDeviceVolumeSupport__checkForNewVolumes(aDeviceInfo) {
405  // Get the device info.
406  var device = aDeviceInfo.device;
407 
408  // Get the current device volume library list.
409  var volumeLibraryList = ArrayConverter.JSArray(device.content.libraries);
410 
411  // Just return if there are currently no volumes. This could be because
412  // the device has not yet made its volumes available.
413  if (volumeLibraryList.length == 0)
414  return;
415 
416  // Get the last device volume GUID list.
417  var lastVolumeGUIDList = device.getPreference("last_volume_list", null);
418  if (lastVolumeGUIDList) {
419  lastVolumeGUIDList = lastVolumeGUIDList.split(",");
420  }
421  else {
422  lastVolumeGUIDList = [];
423  }
424 
425  // Produce the list of new volume libraries.
426  var newVolumeLibraryList = [];
427  for each (var library in volumeLibraryList) {
428  if (lastVolumeGUIDList.indexOf(library.guid) < 0)
429  newVolumeLibraryList.push(library);
430  }
431 
432  // Notify user of new volume if necessary. Notify if there's a new volume
433  // and at least two volumes to manage. Only display one notification per
434  // device at a time.
435  if (!aDeviceInfo.notification &&
436  (newVolumeLibraryList.length > 0) &&
437  (volumeLibraryList.length > 1)) {
438  // Use the first new volume library.
439  var volumeLibrary = newVolumeLibraryList[0];
440 
441  // Get the volume capacity.
442  var capacity = volumeLibrary.getProperty
443  ("http://songbirdnest.com/device/1.0#capacity");
444 
445  // Produce the notification label.
446  storageConverter =
447  Cc["@songbirdnest.com/Songbird/Properties/UnitConverter/Storage;1"]
448  .createInstance(Ci.sbIPropertyUnitConverter);
450  ("device.new_volume_notification.label",
451  [ storageConverter.autoFormat(capacity, -1, 1),
452  device.name ]);
453 
454  // Produce the list of notification buttons. Only add a manage volumes
455  // button if the service pane is available.
456  var buttonList = [];
457  var _this = this;
458  if (gServicePane) {
459  buttonList.push({
460  label: SBString("device.new_volume_notification.manage_button.label"),
461  accessKey:
462  SBString("device.new_volume_notification.manage_button.accesskey"),
463  callback: function buttonCallback(aNotification, aButton) {
464  _this._doManageVolumes(device);
465  }
466  });
467  }
468 
469  // Notify the user of the new volume.
470  var notificationBox = SBGetApplicationNotificationBox();
471  if (notificationBox) {
472  aDeviceInfo.notification = notificationBox.appendNotification
473  (label,
474  "new_volume",
475  null,
476  notificationBox.PRIORITY_INFO_LOW,
477  buttonList);
478  }
479  }
480 
481  // Update the device last volume list.
482  var volumeGUIDList = [];
483  for each (var library in volumeLibraryList)
484  volumeGUIDList.push(library.guid);
485  device.setPreference("last_volume_list", volumeGUIDList.join(","));
486  },
487 
488 
494  _doManageVolumes: function sbDeviceVolumeSupport__doManageVolumes(aDevice) {
495  // Get the device service pane node.
496  var dsps = Cc["@songbirdnest.com/servicepane/device;1"]
497  .getService(Ci.sbIDeviceServicePaneService);
498  var deviceNode = dsps.getNodeForDevice(aDevice);
499 
500  // Load the device node.
501  if (deviceNode && gServicePane) {
502  gServicePane.activateAndLoadNode(deviceNode,
503  null,
504  { manageVolumes: "true" });
505  }
506  },
507 
508 
516  _updateNotification:
517  function sbDeviceVolumeSupport__updateNotification(aDeviceInfo) {
518  // If a notification has previously been presented for a device and has
519  // since been dismissed, remove notification object from device info.
520  // Unfortunately, there's no callback available for when a notification has
521  // been dismissed.
522  if (aDeviceInfo.notification) {
523  // Check if the notification is still present in the notification box.
524  let notificationBox = SBGetApplicationNotificationBox();
525  let allNotifications = notificationBox.allNotifications;
526  let notificationPresent = false;
527  for (let i = 0; i < allNotifications.length; i++) {
528  if (aDeviceInfo.notification == allNotifications[i]) {
529  notificationPresent = true;
530  break;
531  }
532  }
533 
534  // Clear the notification from the device info if it's no longer present.
535  if (!notificationPresent)
536  aDeviceInfo.notification = null;
537  }
538 
539  // If a volume management notification is still present, but the number of
540  // volumes is less than two, remove the notification since volume management
541  // is no longer relevant.
542  if (aDeviceInfo.notification &&
543  (aDeviceInfo.device.content.libraries.length < 2)) {
544  aDeviceInfo.notification.close();
545  aDeviceInfo.notification = null;
546  }
547  },
548 
557  _updateServicePane:
558  function sbDeviceVolumeSupport__updateServicePane(aDeviceInfo)
559  {
560  var selected = gServicePane.activeNode;
561  let invisible = true;
562  if (selected) {
563  invisible = StringSet.contains(selected.className,
564  "non-default-library-node");
565  }
566  if (invisible) {
567  // Get the device service pane node.
568  var dsps = Cc["@songbirdnest.com/servicepane/device;1"]
569  .getService(Ci.sbIDeviceServicePaneService);
570  var deviceNode = dsps.getNodeForDevice(aDeviceInfo.device);
571  gServicePane.activateAndLoadNode(deviceNode, null, null);
572  }
573  }
574 };
575 
576 // Initialize the device volume support services.
577 sbDeviceVolumeSupport.initialize();
578 
579 
580 //------------------------------------------------------------------------------
581 //------------------------------------------------------------------------------
582 //
583 // Device transcode support services.
584 //
585 // These services provide device transcode support for the main Songbird
586 // window.
587 //
588 //------------------------------------------------------------------------------
589 //------------------------------------------------------------------------------
590 
592  Cu.import("resource://app/jsmodules/StringUtils.jsm");
593  Cu.import("resource://app/jsmodules/WindowUtils.jsm");
594 
595  // A set of devices with events. The key is the device id; only the
596  // presence of the key matters.
597  var devicesWithEvents = {};
598 
599  // A set of notifications. The key is the device id.
600  var notificationTable = {};
601 
602  function deviceManagerListener(event) {
603 
604  var device = event.origin;
605  if (!(device instanceof Ci.sbIDevice)) {
606  return;
607  }
608 
609  var msg = SBString("transcode.error.notification.label");
610 
611  switch (event.type) {
612  case Ci.sbIDeviceEvent.EVENT_DEVICE_TRANSCODE_ERROR: {
613  let bag = event.data;
614  if (!bag || !(bag instanceof Ci.nsIPropertyBag2)) {
615  // no event data, nothing we can do
616  break;
617  }
618  if (bag.hasKey("transcode-error")) {
619  let error = bag.get("transcode-error");
620  if ((error instanceof Ci.nsIScriptError) &&
621  (error instanceof Ci.sbITranscodeError))
622  {
623  devicesWithEvents[device.id] = true;
624  }
625  }
626  break;
627  }
628  case Ci.sbIDeviceEvent.EVENT_DEVICE_NOT_ENOUGH_FREESPACE: {
629  devicesWithEvents[device.id] = true;
630  break;
631  }
632  }
633 
634  if (device.isBusy) {
635  // The device is still busy; wait until it becomes idle before reporting
636  return;
637  }
638  if (!(device.id in devicesWithEvents)) {
639  // this device has no events we want to report
640  return;
641  }
642 
643  // If we get here, there is some unreported but interesting event,
644  // but the device is now idle
645  delete devicesWithEvents[device.id];
646  var notificationBox = SBGetApplicationNotificationBox();
647  // Only one notification per device for transcoding error.
648  if (!notificationBox || notificationTable[device.id])
649  return;
650  var buttons = [
651  {
652  label: SBString("transcode.error.notification.detail.label"),
653  accessKey: SBString("transcode.error.notification.detail.accesskey"),
654  callback: function(notification, button) {
655  let deviceErrorMonitor =
656  Cc["@songbirdnest.com/device/error-monitor-service;1"]
657  .getService(Ci.sbIDeviceErrorMonitor);
658  let errorItems = deviceErrorMonitor.getDeviceErrors(device);
659  WindowUtils.openDialog
660  (window,
661  "chrome://songbird/content/xul/device/deviceErrorDialog.xul",
662  "device_error_dialog",
663  "chrome,centerscreen",
664  false,
665  [ "", device, errorItems, "syncing" ],
666  null);
667  deviceErrorMonitor.clearErrorsForDevice(device);
668  delete notificationTable[device.id];
669  },
670  popup: null
671  }
672  ];
673  var notification = notificationBox.appendNotification(
674  msg,
675  event,
676  "chrome://songbird/skin/device/error.png",
677  notificationBox.PRIORITY_CRITICAL_MEDIUM,
678  buttons);
679  notificationTable[device.id] = notification;
680  var onNotificationCommand = function(event) {
681  let classes = event.originalTarget.className.split(/\s+/);
682  if (classes.indexOf("messageCloseButton") > -1) {
683  // the user clicked on the dismiss button; clear the device errors
684  let deviceErrorMonitor =
685  Cc["@songbirdnest.com/device/error-monitor-service;1"]
686  .getService(Ci.sbIDeviceErrorMonitor);
687  deviceErrorMonitor.clearErrorsForDevice(device);
688  delete notificationTable[device.id];
689  }
690  };
691  notification.addEventListener("command", onNotificationCommand, false);
692  }
693 
694  // attach our device event listener
695  var deviceManager = Cc["@songbirdnest.com/Songbird/DeviceManager;2"]
696  .getService(Ci.sbIDeviceEventTarget);
697  deviceManager.addEventListener(deviceManagerListener);
698 
699  // attach our cleanup routine
700  addEventListener("unload", function(event) {
701  removeEventListener(event.type, arguments.callee, false);
702  deviceManager.removeEventListener(deviceManagerListener);
703  delete deviceManager;
704  }, false);
705 })();
706 
var StringSet
function SBFormattedString(aKey, aParams, aDefault, aStringBundle)
var Cr
handlersMenuPopup addEventListener("command", this, false)
var event
sbDeviceFirmwareAutoCheckForUpdate prototype _deviceManager
function DOMEventListenerSet()
Definition: DOMUtils.jsm:766
var Cu
function SBString(aKey, aDefault, aStringBundle)
Definition: StringUtils.jsm:93
let window
var Ci
this _contentSandbox label
Definition: FeedWriter.js:814
sbDeviceVolumeSupport initialize()
function deviceErrorMonitor()
Lastfm onLoad
Definition: mini.js:36
var _this
aWindow setTimeout(function(){_this.restoreHistory(aWindow, aTabs, aTabData, aIdMap);}, 0)
grep callback
return null
Definition: FeedWriter.js:1143
function DeviceTranscodeSupport()
return!aWindow arguments!aWindow arguments[0]
var sbDeviceVolumeSupport
var Cc
var gServicePane
Definition: mainWinInit.js:39
function msg
this removeEventListener("load", this.__SS_restore, true)
function onUnload()
onUnload - called when the cover preview window unloads.
Definition: coverPreview.js:36
_getSelectedPageStyle s i