sbDownloadDeviceHelper.js
Go to the documentation of this file.
1 /*
2 //
3 // BEGIN SONGBIRD GPL
4 //
5 // This file is part of the Songbird web player.
6 //
7 // Copyright(c) 2005-2008 POTI, Inc.
8 // http://songbirdnest.com
9 //
10 // This file may be licensed under the terms of of the
11 // GNU General Public License Version 2 (the "GPL").
12 //
13 // Software distributed under the License is distributed
14 // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
15 // express or implied. See the GPL for the specific language
16 // governing rights and limitations.
17 //
18 // You should have received a copy of the GPL along with this
19 // program. If not, go to http://www.gnu.org/licenses/gpl.html
20 // or write to the Free Software Foundation, Inc.,
21 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 //
23 // END SONGBIRD GPL
24 //
25 */
26 
27 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
28 Components.utils.import("resource://app/jsmodules/sbProperties.jsm");
29 Components.utils.import("resource://app/jsmodules/ArrayConverter.jsm");
30 Components.utils.import("resource://app/jsmodules/FolderUtils.jsm");
31 
32 const Ci = Components.interfaces;
33 const Cc = Components.classes;
34 const Cr = Components.results;
35 const Cu = Components.utils;
36 const CE = Components.Exception;
37 
38 const PREF_DOWNLOAD_FOLDER = {audio: "songbird.download.music.folder",
39  video: "songbird.download.video.folder"};
40 const PREF_DOWNLOAD_ALWAYSPROMPT = {audio: "songbird.download.music.alwaysPrompt",
41  video: "songbird.download.video.alwaysPrompt"};
42 const _MFM_GET_MANAGED_PATH_OPTIONS = Ci.sbIMediaFileManager.MANAGE_MOVE |
43  Ci.sbIMediaFileManager.MANAGE_COPY |
44  Ci.sbIMediaFileManager.MANAGE_RENAME;
45 
54 function getPlatformString()
55 {
56  try {
57  var sysInfo =
58  Components.classes["@mozilla.org/system-info;1"]
59  .getService(Components.interfaces.nsIPropertyBag2);
60  return sysInfo.getProperty("name");
61  }
62  catch (e) {
63  dump("System-info not available, trying the user agent string.\n");
64  var user_agent = navigator.userAgent;
65  if (user_agent.indexOf("Windows") != -1)
66  return "Windows_NT";
67  else if (user_agent.indexOf("Mac OS X") != -1)
68  return "Darwin";
69  else if (user_agent.indexOf("Linux") != -1)
70  return "Linux";
71  else if (user_agent.indexOf("SunOS") != -1)
72  return "SunOS";
73  return "";
74  }
75 }
76 
80 function folderIsValid(folder) {
81  try {
82  return folder && folder.isDirectory();
83  }
84  catch (e) {
85  }
86  return false;
87 }
88 
92 function makeFile(path) {
93  var file = Cc["@mozilla.org/file/local;1"].
94  createInstance(Ci.nsILocalFile);
95 
96  // Ensure all paths are lowercase for Windows.
97  // See bug #7178.
98  var actualPath = path;
99  if(getPlatformString() == "Windows_NT") {
100  actualPath = actualPath.toLowerCase();
101  }
102 
103  try {
104  file.initWithPath(actualPath);
105  }
106  catch (e) {
107  return null;
108  }
109  return file;
110 }
111 
115 function makeFileURL(path)
116 {
117  // Can't use the IOService because we have callers off of the main thread...
118  // Super lame hack instead.
119 
120  // Ensure all paths are lowercase for Windows.
121  // See bug #7178.
122  var actualPath = path;
123  if(getPlatformString() == "Windows_NT") {
124  actualPath = actualPath.toLowerCase();
125  }
126 
127  return "file://" + actualPath;
128 }
129 
131 {
132  this._mainLibrary = Cc["@songbirdnest.com/Songbird/library/Manager;1"]
133  .getService(Ci.sbILibraryManager).mainLibrary;
134 }
135 
136 sbDownloadDeviceHelper.prototype =
137 {
138  classDescription: "Songbird Download Device Helper",
139  classID: Components.ID("{576b6833-15d8-483a-84d6-2fbd329c82e1}"),
140  contractID: "@songbirdnest.com/Songbird/DownloadDeviceHelper;1",
141  QueryInterface: XPCOMUtils.generateQI([Ci.sbIDownloadDeviceHelper])
142 }
143 
144 sbDownloadDeviceHelper.prototype.downloadItem =
145 function sbDownloadDeviceHelper_downloadItem(aMediaItem)
146 {
147  var downloadFolder = this._getDownloadFolder_nothrow(aMediaItem.contentType);
148  if (!downloadFolder) {
149  return;
150  }
151 
152  let uriArray = Cc["@songbirdnest.com/moz/xpcom/threadsafe-array;1"]
153  .createInstance(Ci.nsIMutableArray);
154  let propertyArrayArray = Cc["@songbirdnest.com/moz/xpcom/threadsafe-array;1"]
155  .createInstance(Ci.nsIMutableArray);
156 
157  let item;
158  if (aMediaItem.library.equals(this._mainLibrary)) {
159  item = aMediaItem;
160  }
161  else {
162  this._addItemToArrays(aMediaItem, uriArray, propertyArrayArray);
163  let items = this._mainLibrary.batchCreateMediaItems(uriArray,
164  propertyArrayArray,
165  true);
166  item = items.queryElementAt(0, Ci.sbIMediaItem);
167  }
168 
169  // Pass the downloadFolder as the 'media-folder' option for the
170  // Media File Manager. The MFM will then put the file in the
171  // user's preferred folder (or possibly a subfolder thereof)
172  this._setDownloadDestinationIfNotSet([item],
173  {'media-folder': downloadFolder});
174  this._checkAndRemoveExistingItem(item);
175  this.getDownloadMediaList().add(item);
176 }
177 
178 sbDownloadDeviceHelper.prototype.downloadSome =
179 function sbDownloadDeviceHelper_downloadSome(aMediaItems)
180 {
181  let uriArray = Cc["@songbirdnest.com/moz/xpcom/threadsafe-array;1"]
182  .createInstance(Ci.nsIMutableArray);
183  let propertyArrayArray = Cc["@songbirdnest.com/moz/xpcom/threadsafe-array;1"]
184  .createInstance(Ci.nsIMutableArray);
185 
186  var items = [];
187  var foundTypes = {};
188  while (aMediaItems.hasMoreElements()) {
189  let item = aMediaItems.getNext();
190  if (item.library.equals(this._mainLibrary)) {
191  items.push(item);
192  }
193  else {
194  this._checkAndRemoveExistingItem(item);
195  this._addItemToArrays(item, uriArray, propertyArrayArray);
196  }
197  // Keep track of which media types are present. This
198  // will be used below to get the download folders for
199  // these media types:
200  foundTypes[item.contentType] = true;
201  }
202 
203  // Get the user's preferred download folder for each media type
204  // in the item list and set up Media File Manager options with
205  // those folders. The MFM will then sort the files into the
206  // respective folders (or possibly subfolders thereof). Do this
207  // before creating the media items in case the user is prompted
208  // to choose a folder and clicks cancel:
209  mfmFolders = {};
210  try {
211  for (let contentType in foundTypes) {
212  let folder = this.getDownloadFolder(contentType);
213  if (folder) {
214  mfmFolders["media-folder:" + contentType] = folder;
215  }
216  }
217  }
218  catch (e if e.result == Cr.NS_ERROR_ABORT) {
219  return;
220  }
221 
222  if (uriArray.length > 0) {
223  let addedItems = this._mainLibrary.batchCreateMediaItems(uriArray,
224  propertyArrayArray,
225  true);
226  for (let i = 0; i < addedItems.length; i++) {
227  items.push(addedItems.queryElementAt(i, Ci.sbIMediaItem));
228  }
229  }
230 
231  this._setDownloadDestinationIfNotSet(items, mfmFolders);
232  this.getDownloadMediaList().addSome(ArrayConverter.enumerator(items));
233 }
234 
235 sbDownloadDeviceHelper.prototype.downloadAll =
236 function sbDownloadDeviceHelper_downloadAll(aMediaList)
237 {
238  let uriArray = Cc["@songbirdnest.com/moz/xpcom/threadsafe-array;1"]
239  .createInstance(Ci.nsIMutableArray);
240  let propertyArrayArray = Cc["@songbirdnest.com/moz/xpcom/threadsafe-array;1"]
241  .createInstance(Ci.nsIMutableArray);
242 
243  var items = [];
244  var foundTypes = {};
245  var isForeign = aMediaList.library.equals(this._mainLibrary);
246  for (let i = 0; i < aMediaList.length; i++) {
247  var item = aMediaList.getItemByIndex(i);
248  if (isForeign) {
249  this._checkAndRemoveExistingItem(item);
250  this._addItemToArrays(item, uriArray, propertyArrayArray);
251  }
252  else {
253  items.push(item);
254  }
255  // Keep track of which media types are present. This
256  // will be used below to get the download folders for
257  // these media types:
258  foundTypes[item.contentType] = true;
259  }
260 
261  // Get the user's preferred download folder for each media type
262  // in the item list and set up Media File Manager options with
263  // those folders. The MFM will then sort the files into the
264  // respective folders (or possibly subfolders thereof). Do this
265  // before creating the media items in case the user is prompted
266  // to choose a folder and clicks cancel.
267  mfmFolders = {};
268  try {
269  for (let contentType in foundTypes) {
270  let folder = this.getDownloadFolder(contentType);
271  if (folder) {
272  mfmFolders["media-folder:" + contentType] = folder;
273  }
274  }
275  }
276  catch (e if e.result == Cr.NS_ERROR_ABORT) {
277  return;
278  }
279 
280  if (uriArray.length > 0) {
281  let addedItems = this._mainLibrary.batchCreateMediaItems(uriArray,
282  propertyArrayArray,
283  true);
284  for (let i = 0; i < addedItems.length; i++) {
285  items.push(addedItems.queryElementAt(i, Ci.sbIMediaItem));
286  }
287  }
288 
289  this._setDownloadDestinationIfNotSet(items, mfmFolders);
290  this.getDownloadMediaList().addSome(ArrayConverter.enumerator(items));
291 }
292 
293 sbDownloadDeviceHelper.prototype.getDownloadMediaList =
294 function sbDownloadDeviceHelper_getDownloadMediaList()
295 {
296  if (!this._downloadDevice) {
297  var devMgr = Cc["@songbirdnest.com/Songbird/DeviceManager;1"]
298  .getService(Ci.sbIDeviceManager);
299  if (devMgr) {
300  var downloadCat = "Songbird Download Device";
301  if (devMgr.hasDeviceForCategory(downloadCat)) {
302  this._downloadDevice = devMgr.getDeviceByCategory(downloadCat)
303  .QueryInterface(Ci.sbIDownloadDevice);
304  }
305  }
306  }
307  return this._downloadDevice ? this._downloadDevice.downloadMediaList : null;
308 }
309 
310 sbDownloadDeviceHelper.prototype.getDefaultDownloadFolder =
311 function sbDownloadDeviceHelper_getDefaultDownloadFolder(aContentType)
312 {
313  var mediaDir = FolderUtils.getDownloadFolder(aContentType);
314  // We should never get something bad here, but just in case...
315  if (!folderIsValid(mediaDir)) {
316  Cu.reportError("Desktop directory is not a directory!");
317  throw Cr.NS_ERROR_FILE_NOT_DIRECTORY;
318  }
319 
320  return mediaDir;
321 }
322 
323 sbDownloadDeviceHelper.prototype.getDownloadFolder =
324 function sbDownloadDeviceHelper_getDownloadFolder(aContentType)
325 {
326  const Application = Cc["@mozilla.org/fuel/application;1"].
327  getService(Ci.fuelIApplication);
328  const prefs = Application.prefs;
329 
330  var downloadPath;
331  if (prefs.has(PREF_DOWNLOAD_FOLDER[aContentType])) {
332  downloadPath = prefs.get(PREF_DOWNLOAD_FOLDER[aContentType]).value;
333  }
334 
335  var downloadFolder;
336  if (downloadPath) {
337  downloadFolder = makeFile(downloadPath);
338  }
339  else {
340  // The pref was empty. Use (and write) the default.
341  downloadFolder = this.getDefaultDownloadFolder(aContentType);
342  if (downloadFolder) {
343  prefs.setValue(PREF_DOWNLOAD_FOLDER[aContentType], downloadFolder.path);
344  }
345  }
346 
347  const alwaysPrompt = prefs.getValue(PREF_DOWNLOAD_ALWAYSPROMPT[aContentType], false);
348  if (folderIsValid(downloadFolder) && !alwaysPrompt) {
349  return downloadFolder;
350  }
351 
352  const sbs = Cc["@mozilla.org/intl/stringbundle;1"].
353  getService(Ci.nsIStringBundleService);
354  const strings =
355  sbs.createBundle("chrome://songbird/locale/songbird.properties");
356  const title = strings.GetStringFromName(
357  "prefs.main.downloads." + aContentType + ".chooseTitle");
358 
359  const wm = Cc["@mozilla.org/appshell/window-mediator;1"].
360  getService(Ci.nsIWindowMediator);
361  const mainWin = wm.getMostRecentWindow("Songbird:Main");
362 
363  // Need to prompt, launch the filepicker.
364  const folderPicker = Cc["@mozilla.org/filepicker;1"].
365  createInstance(Ci.nsIFilePicker);
366  folderPicker.init(mainWin, title, Ci.nsIFilePicker.modeGetFolder);
367  folderPicker.displayDirectory = downloadFolder;
368 
369  if (folderPicker.show() != Ci.nsIFilePicker.returnOK) {
370  // This is our signal that the user cancelled the dialog. Some folks won't
371  // care, but most will.
372  throw new CE("User canceled the download dialog.", Cr.NS_ERROR_ABORT);
373  }
374 
375  downloadFolder = folderPicker.file;
376 
377  prefs.setValue(PREF_DOWNLOAD_FOLDER[aContentType], downloadFolder.path);
378  return downloadFolder;
379 }
380 
381 sbDownloadDeviceHelper.prototype._checkAndRemoveExistingItem =
382 function sbDownloadDeviceHelper__checkAndRemoveExistingItem(aMediaItem)
383 {
384  var downloadMediaList = this.getDownloadMediaList();
385  if (downloadMediaList) {
386  var itemOriginUrl = aMediaItem.getProperty(SBProperties.originURL);
387 
388  var listEnumerationListener = {
389  onEnumerationBegin: function() {
390  },
391  onEnumeratedItem: function(list, item) {
392  downloadMediaList.remove(item);
393  },
394  onEnumerationEnd: function() {
395  },
396  };
397 
398  if ( (itemOriginUrl != "") &&
399  (itemOriginUrl != null) ) {
400  downloadMediaList.enumerateItemsByProperty(SBProperties.originURL,
401  itemOriginUrl,
402  listEnumerationListener);
403  }
404  }
405 }
406 
407 sbDownloadDeviceHelper.prototype._getDownloadFolder_nothrow =
408 function sbDownloadDeviceHelper__getDownloadFolder_nothrow(aContentType)
409 {
410  // This function returns null if the user cancels the dialog.
411  try {
412  return this.getDownloadFolder(aContentType);
413  }
414  catch (e if e.result == Cr.NS_ERROR_ABORT) {
415  }
416  return null;
417 }
418 
419 sbDownloadDeviceHelper.prototype._addItemToArrays =
420 function sbDownloadDeviceHelper__addItemToArrays(aMediaItem,
421  aURIArray,
422  aPropertyArrayArray)
423 {
424  aURIArray.appendElement(aMediaItem.contentSrc, false);
425 
426  var dest = SBProperties.createArray();
427  var source = aMediaItem.getProperties();
428  for (let i = 0; i < source.length; i++) {
429  var prop = source.getPropertyAt(i);
430  // Filter our enableAutoDownload, as the Auto Downloader
431  // should ignore these items. Their download is already
432  // imminent:
433  if (prop.id != SBProperties.contentSrc &&
434  prop.id != SBProperties.enableAutoDownload)
435  {
436  dest.appendElement(prop, false);
437  }
438  }
439 
440  var target = aMediaItem.library.guid + "," + aMediaItem.guid;
441  dest.appendProperty(SBProperties.downloadStatusTarget, target);
442 
443  aPropertyArrayArray.appendElement(dest, false);
444 }
445 
446 sbDownloadDeviceHelper.prototype._setDownloadDestinationIfNotSet =
447 function sbDownloadDeviceHelper__setDownloadDestinationIfNotSet(
448  aItems,
449  aMediaFileManagerOptions)
450 {
451  // Copy the Media File Manager options to an nsIPropertyBag
452  var bag = Cc["@mozilla.org/hash-property-bag;1"]
453  .createInstance(Ci.nsIWritablePropertyBag);
454  for (prop in aMediaFileManagerOptions) {
455  bag.setProperty(prop, aMediaFileManagerOptions[prop]);
456  }
457 
458  // Use the Media File Manager to organize files into subfolders.
459  // The caller will designate the root folder for each media type
460  // in the MFM options bag (based on user preferences):
461  var mediaFileMgr = Cc["@songbirdnest.com/Songbird/media-manager/file;1"]
462  .createInstance(Ci.sbIMediaFileManager);
463  mediaFileMgr.init(bag);
464 
465  for each (var item in aItems) {
466  try {
467  var curDestination = item.getProperty(SBProperties.destination);
468  if (!curDestination) {
469  let destFile = mediaFileMgr.getManagedPath(
470  item,
472  let path = destFile.path;
473 
477  // sbDownloadSession::CompleteTransfer() will construct a
478  // file name from the source URL:
479  if (!item.getProperty(SBProperties.trackName)) {
480  // Leave a trailing delimiter so the path parses as a directory:
481  path = path.replace(/[^/\\]*$/, "");
482  }
483 
484  // Ensure all paths are lowercase for Windows.
485  // See bug #7178.
486  let actualDestination = makeFileURL(path);
487  if(getPlatformString() == "Windows_NT") {
488  actualDestination = actualDestination.toLowerCase();
489  }
490 
491  item.setProperty(SBProperties.destination, actualDestination);
492  }
493  }
494  catch (e) {
495  // We're not allowed to set the download destination on remote media items
496  // so that call may fail. The remoteAPI should unwrap all items and lists
497  // *before* handing them to us, so throw the error with a slightly more
498  // helpful message.
499  throw new CE("Download destination could not be set on this media item:\n"
500  + " " + item + "\nIs it actually a remote media item?",
501  e.result);
502  }
503  }
504 }
505 
506 function NSGetModule(compMgr, fileSpec) {
507  return XPCOMUtils.generateModule([sbDownloadDeviceHelper]);
508 }
var Application
Definition: sbAboutDRM.js:37
function getPlatformString()
Get the name of the platform we are running on.
const Cu
sbDeviceFirmwareAutoCheckForUpdate prototype contractID
sidebarFactory createInstance
Definition: nsSidebar.js:351
sbOSDControlService prototype QueryInterface
sbDeviceFirmwareAutoCheckForUpdate prototype classDescription
getService(Ci.sbIFaceplateManager)
const _MFM_GET_MANAGED_PATH_OPTIONS
var strings
Definition: Info.js:46
const CE
function sbDownloadDeviceHelper()
const Cc
const Ci
return null
Definition: FeedWriter.js:1143
function makeFile(path)
var prefs
Definition: FeedWriter.js:1169
function folderIsValid(folder)
sbDeviceFirmwareAutoCheckForUpdate prototype classID
const PREF_DOWNLOAD_FOLDER
const Cr
_getSelectedPageStyle s i
function makeFileURL(path)
const PREF_DOWNLOAD_ALWAYSPROMPT
var file