home *** CD-ROM | disk | FTP | other *** search
/ PC World 2007 December / PCWorld_2007-12_cd.bin / audio-video / songbird / Songbird_0.3_windows-i686.exe / components / sbBookmarksService.js < prev    next >
Text File  |  2007-10-27  |  22KB  |  654 lines

  1. /** vim: ts=2 sw=2 expandtab
  2. //
  3. // BEGIN SONGBIRD GPL
  4. //
  5. // This file is part of the Songbird web player.
  6. //
  7. // Copyright(c) 2005-2007 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. /**
  28.  * \file sbBookmarks.js
  29.  * \brief the service pane service manages the tree behind the service pane.
  30.  * It provides an interface for creating bookmark nodes in the service pane tree.
  31.  *
  32.  * TODO:
  33.  *  - move downloading to nsIChannel and friends for better chrome-proofing
  34.  *  - send version and locale info when requesting new bookmarks
  35.  *  - handle errors more elegantly in bookmarks downloading
  36.  *  - handle the adding of bookmarks before the defaults are downloaded
  37.  *  - allow the server bookmarks list to remove stale entries [bug 2352]
  38.  * PERHAPS:
  39.  *  - move default bookmarks downloading to after first-run so we can have
  40.  *  per-locale bookmarks?
  41.  *  - download and cache images
  42.  *  - add a confirmation dialog for for bookmark deletion
  43.  *  
  44.  */
  45.  
  46. const Cc = Components.classes;
  47. const Ci = Components.interfaces;
  48. const Cr = Components.results;
  49.  
  50. const CONTRACTID = "@songbirdnest.com/servicepane/bookmarks;1"
  51. const ROOTNODE = "SB:Bookmarks"
  52. const BOOKMARK_DRAG_TYPE = 'text/x-sb-bookmark';
  53. const MOZ_URL_DRAG_TYPE = 'text/x-moz-url';
  54. const BSP = 'http://songbirdnest.com/rdf/bookmarks#';
  55. const SP='http://songbirdnest.com/rdf/servicepane#';
  56.  
  57. function SB_NewDataRemote(a,b) {
  58.   return (new Components.Constructor("@songbirdnest.com/Songbird/DataRemote;1",
  59.                     "sbIDataRemote", "init"))(a,b);
  60. }
  61.  
  62.  
  63.  
  64. function sbBookmarks() {
  65.   this._servicePane = null;
  66.   this._stringBundle = null;
  67.   
  68.   // use the default stringbundle to translate tree nodes
  69.   this.stringbundle = null;
  70.  
  71.   this._importAttempts = 5;
  72.   this._importTimer = null;
  73. }
  74. sbBookmarks.prototype.QueryInterface = 
  75. function sbBookmarks_QueryInterface(iid) {
  76.   if (!iid.equals(Ci.nsISupports) &&
  77.     !iid.equals(Ci.sbIBookmarks) &&
  78.     !iid.equals(Ci.sbIServicePaneModule)) {
  79.     throw Components.results.NS_ERROR_NO_INTERFACE;
  80.   }
  81.   return this;
  82. }
  83. sbBookmarks.prototype.servicePaneInit = 
  84. function sbBookmarks_servicePaneInit(sps) {
  85.   this._servicePane = sps;
  86.   
  87.   // if we don't have a bookmarks node, lets create one
  88.   this._bookmarkNode = this._servicePane.getNode(ROOTNODE);
  89.   if (!this._bookmarkNode) {
  90.     // create bookmarks folder
  91.     this._bookmarkNode = this.addFolderAt('SB:Bookmarks',
  92.         '&servicesource.bookmarks', null, sps.root, null);
  93.   }
  94.  
  95.   // set the weight of the bookmarks node
  96.   this._bookmarkNode.setAttributeNS(SP, 'Weight', 4);
  97.     
  98.   // if the bookmark node doesn't have the Imported attribute set, lets do an import
  99.   if (this._bookmarkNode.getAttributeNS(BSP, 'Imported') != 'true') {
  100.     // run the importer
  101.     this.importBookmarks();
  102.   }
  103.  
  104.   var sbSvc = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
  105.   this._stringBundle = sbSvc.createBundle("chrome://songbird/locale/songbird.properties");
  106. }
  107.  
  108. sbBookmarks.prototype.scheduleImportBookmarks =
  109. function sbBookmarks_scheduleImportBookmarks() {
  110.   this._importTimer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer);
  111.   this._importTimer.init(this, 5000, Ci.nsITimer.TYPE_ONE_SHOT);
  112. }
  113.  
  114. sbBookmarks.prototype.observe = 
  115. function sbBookmarks_observe(subject, topic, data) {
  116.   if (topic == 'timer-callback' && subject == this._importTimer) {
  117.     // the bookmarks import timer
  118.     this._importTimer = null;
  119.     if (!this._servicePane) {
  120.       // hmm, we're shutting down, no need to import now
  121.       return;
  122.     }
  123.     this.importBookmarks();
  124.   }
  125. }
  126.  
  127. sbBookmarks.prototype.importBookmarks =
  128. function sbBookmarks_importBookmarks() {
  129.   var prefsService =
  130.       Components.classes["@mozilla.org/preferences-service;1"].
  131.       getService(Components.interfaces.nsIPrefBranch);
  132.   var bookmarksURL = prefsService.getCharPref("songbird.url.bookmarks");
  133.   
  134.   // fetch the default set of bookmarks through a series of tubes
  135.   // FIXME: don't use XHR - use nsIChannel and friends
  136.   // FIXME: send parameters and/or headers to indicate product version or something
  137.   var xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
  138.       .createInstance(Ci.nsIDOMEventTarget);
  139.   var sps = this._servicePane;
  140.   var service = this;
  141.   function importError() {
  142.     service._importAttempts--;
  143.     if (service._importAttempts < 0) {
  144.       // we tried, but it's time to give up till the next time the player starts
  145.       // but first, let's create some default bookmarks to get us through
  146.       var default_bookmarks = [
  147.         {name:'Add-ons', url:'http://addons.songbirdnest.com/'},
  148.         {name:'Directory', url:'http://birdhouse.songbirdnest.com/directory'}];
  149.       for (var i=0; i<default_bookmarks.length; i++) {
  150.         var bm = default_bookmarks[i];
  151.         var bnode = sps.getNode(bm.url);
  152.         if (!bnode) {
  153.           service.addBookmarkAt(bm.url, bm.name, 
  154.             'chrome://songbird-branding/skin/logo_16.png', 
  155.             service._bookmarkNode, null);
  156.         }
  157.       }
  158.       
  159.       return;
  160.     }
  161.     service.scheduleImportBookmarks();
  162.   }
  163.   xhr.addEventListener('load', function(evt) {
  164.     var root = null;
  165.     try {
  166.       // a non-XML page will cause an exception here.
  167.       root = xhr.responseXML.documentElement;
  168.     } catch (e) {
  169.       // catch it and try again
  170.       importError();
  171.       return;
  172.     }
  173.     var folders = root.getElementsByTagName('folder');
  174.     for (var f=0; folders && f<folders.length; f++) {
  175.       var folder = folders[f];
  176.       if (!folder.hasAttribute('id') ||
  177.           !folder.hasAttribute('name')) {
  178.         // the folder is missing required attributes, we must ignore it
  179.         continue;
  180.       }
  181.  
  182.       var fnode = sps.getNode(folder.getAttribute('id'));
  183.       
  184.       if (!fnode) {
  185.         // if the folder doesn't exist, create it
  186.         fnode = service.addFolderAt(folder.getAttribute('id'),
  187.             folder.getAttribute('name'), folder.getAttribute('image'),
  188.             sps.root, null);
  189.       }
  190.       
  191.       fnode.isOpen = (folder.getAttribute('open') == 'true');
  192.       
  193.       if (fnode && fnode.getAttributeNS(BSP, 'Imported')) {
  194.         // don't reimport a folder that's already been imported
  195.         continue;
  196.       }
  197.       
  198.       if (fnode.id == ROOTNODE) {
  199.         // we just created the default bookmarks root
  200.         // we'll need that later if we want to let the user create
  201.         // their own bookmarks
  202.         service._bookmarkNode = fnode;
  203.       }
  204.       
  205.       // now, let's create what goes in
  206.       var bookmarks = folder.getElementsByTagName('bookmark');
  207.       for (var b=0; bookmarks && b<bookmarks.length; b++) {
  208.         var bookmark = bookmarks[b];
  209.         if (!bookmark.hasAttribute('url') ||
  210.             !bookmark.hasAttribute('name')) {
  211.           // missing required attributes
  212.           continue;
  213.         }
  214.         // If the bookmark already exists, then it's somewhere else
  215.         // in the tree and we should leave it there untouched.
  216.         // Except that it should be marked as imported now...
  217.         var bnode = sps.getNode(bookmark.getAttribute('url'));
  218.         if (!bnode) {
  219.           // create the bookmark
  220.           bnode = service.addBookmarkAt(bookmark.getAttribute('url'),
  221.               bookmark.getAttribute('name'), bookmark.getAttribute('image'),
  222.               fnode, null);
  223.         }
  224.         // remember we imported it.
  225.         bnode.setAttributeNS(BSP, 'Imported', 'true');
  226.       }
  227.       
  228.       fnode.setAttributeNS(BSP, 'Imported', 'true');
  229.     }
  230.     
  231.     // try to import json bookmarks from 0.2.5
  232.     try {
  233.       service.migrateLegacyBookmarks();
  234.     } catch (e) {
  235.     }
  236.  
  237.   }, false);
  238.   xhr.addEventListener('error', function(evt) {
  239.     importError();
  240.   }, false);
  241.   xhr.QueryInterface(Ci.nsIXMLHttpRequest);
  242.   xhr.open('GET', bookmarksURL, true);
  243.   xhr.send(null);
  244. }
  245.  
  246. sbBookmarks.prototype.shutdown = 
  247. function sbBookmarks_shutdown() {
  248.   this._bookmarkNode = null;
  249.   this._servicePane = null;
  250.   this._stringBundle = null;
  251.   if (this._importTimer) {
  252.     this._importTimer.cancel();
  253.     this._importTimer = null;
  254.   }
  255. }
  256.  
  257. sbBookmarks.prototype.getString =
  258. function sbBookmarks_getString(aStringId, aDefault) {
  259.   try {
  260.     return this._stringBundle.GetStringFromName(aStringId);
  261.   } catch (e) {
  262.     return aDefault;
  263.   }
  264. }
  265. sbBookmarks.prototype.migrateLegacyBookmarks =
  266. function sbBookmarks_migrateLegacyBookmarks() {
  267.   try {
  268.     var prefsService =
  269.         Components.classes["@mozilla.org/preferences-service;1"].
  270.         getService(Components.interfaces.nsIPrefBranch);
  271.     var LEGACY_BOOKMARKS_PREF = 'songbird.bookmarks.serializedTree';
  272.     if (prefsService.prefHasUserValue(LEGACY_BOOKMARKS_PREF)) {
  273.       var json = '(' + prefsService.getCharPref(LEGACY_BOOKMARKS_PREF) + ')';
  274.       var bms = eval(json);
  275.       for (var i in bms.children) {
  276.         var folder = bms.children[i];
  277.         if (folder.label == '&servicesource.bookmarks') {
  278.           for (var j in folder.children) {
  279.             var bm = folder.children[j];
  280.             if (bm.properties != 'bookmark') {
  281.               continue;
  282.             }
  283.             var node = this._servicePane.getNode(bm.url);
  284.             if (node) {
  285.               // this bookmark already existed.
  286.               // we want to set the title to the old
  287.               node.name = bm.label;
  288.             } else {
  289.               // the bookmark does not exist. We need to create it
  290.               var icon = null;
  291.               // only import the icon if its from the web
  292.               if (bm.icon.match(/^http/)) {
  293.                 icon = bm.icon;
  294.               }
  295.               this.addBookmark(bm.url, bm.label, icon);
  296.             }
  297.           }
  298.           break;
  299.         }
  300.       }
  301.       // let's clear that pref
  302.       prefsService.clearUserPref(LEGACY_BOOKMARKS_PREF);
  303.     }
  304.   } catch (e) {
  305.   }
  306. }
  307.  
  308. sbBookmarks.prototype.addBookmark =
  309. function sbBookmarks_addBookmark(aURL, aTitle, aIconURL) {
  310.   dump('sbBookmarks.addBookmark('+aURL.toSource()+','+aTitle.toSource()+','+aIconURL.toSource()+')\n');
  311.   if (!this._bookmarkNode) {
  312.     // if we try to add a bookmark the defaults are loaded, lets create the default folder anyway
  313.     this._bookmarkNode = this._servicePane.addNode(ROOTNODE,
  314.         this._servicePane.root, true);
  315.   }
  316.   return this.addBookmarkAt(aURL, aTitle, aIconURL, this._bookmarkNode, null);
  317. }
  318.  
  319. sbBookmarks.prototype.addBookmarkAt =
  320. function sbBookmarks_addBookmarkAt(aURL, aTitle, aIconURL, aParent, aBefore) {
  321.   var bnode = this._servicePane.addNode(aURL, aParent, false);
  322.   if (!bnode) {
  323.     return bnode;
  324.   }
  325.   
  326.   bnode.url = aURL;
  327.   bnode.name = aTitle;
  328.   if (aBefore) {
  329.     aBefore.parentNode.insertBefore(bnode, aBefore);
  330.   }
  331.   bnode.properties = "bookmark " + aTitle;
  332.   bnode.hidden = false;
  333.   bnode.contractid = CONTRACTID;
  334.   bnode.dndDragTypes = BOOKMARK_DRAG_TYPE;
  335.   bnode.dndAcceptNear = BOOKMARK_DRAG_TYPE;
  336.   bnode.editable = true;
  337.   
  338.   if (aIconURL && aIconURL.match(/^https?:/)) {
  339.     // check that the supplied image url works, otherwise use the default
  340.     var checker = Components.classes["@mozilla.org/network/urichecker;1"]
  341.       .createInstance(Components.interfaces.nsIURIChecker);
  342.     var uri = Components.classes["@mozilla.org/network/standard-url;1"]
  343.       .createInstance(Components.interfaces.nsIURI);
  344.     uri.spec = aIconURL;
  345.     checker.init(uri);
  346.     checker.asyncCheck(new ImageUriCheckerObserver(bnode, aIconURL), null);
  347.   }
  348.  
  349.   return bnode;
  350. }
  351.  
  352. function ImageUriCheckerObserver(bnode, icon) {
  353.   this._bnode = bnode;
  354.   this._icon = icon;
  355. }
  356. ImageUriCheckerObserver.prototype.onStartRequest =
  357. function ImageUriCheckerObserver_onStartRequest(aRequest, aContext)
  358. {
  359. }
  360. ImageUriCheckerObserver.prototype.onStopRequest =
  361. function ImageUriCheckerObserver_onStopRequest(aRequest, aContext, aStatusCode)
  362. {
  363.   // If the requested image exists, set it as the icon.
  364.   if (aStatusCode == 0) {
  365.     this._bnode.image = this._icon;
  366.   }
  367.   // Otherwise, we don't set the image property and we get the default from the skin.
  368. }
  369.  
  370. sbBookmarks.prototype.addFolder =
  371. function sbBookmarks_addFolder(aTitle) {
  372.   return this.addFolderAt(null, aTitle, null, this._servicePane.root, null);
  373. }
  374.  
  375. sbBookmarks.prototype.addFolderAt =
  376. function sbBookmarks_addFolderAt(aId, aTitle, aIconURL, aParent, aBefore) {
  377.   var  fnode = this._servicePane.addNode(aId, aParent, true);  
  378.   fnode.name = aTitle;
  379.   if (aIconURL != null) {
  380.     fnode.image = aIconURL;
  381.   }
  382.   if (aBefore) {
  383.     aBefore.parentNode.insertBefore(fnode, aBefore);
  384.   }
  385.   fnode.properties = "folder " + aTitle;
  386.   fnode.hidden = false;
  387.   fnode.contractid = CONTRACTID;
  388.   fnode.dndAcceptIn = BOOKMARK_DRAG_TYPE;
  389.   fnode.editable = false; // folder names are not editable
  390.   
  391.   return fnode;
  392. }
  393.  
  394. sbBookmarks.prototype.bookmarkExists =
  395. function sbBookmarks_bookmarkExists(aURL) {
  396.   var node = this._servicePane.getNode(aURL);
  397.   return (node != null);
  398. }
  399. sbBookmarks.prototype.fillContextMenu =
  400. function sbBookmarks_fillContextMenu(aNode, aContextMenu, aParentWindow) {
  401.   dump ('called fillContextMenu with node: '+aNode+'\n');
  402.  
  403.   if (!aNode.editable) {
  404.     // not editable - don't add any items
  405.     return;
  406.   }
  407.   
  408.   if (aNode.contractid != CONTRACTID) {
  409.     dump('or not...('+aNode.contractid+')\n');
  410.     return;
  411.   }
  412.     
  413.   var document = aContextMenu.ownerDocument;
  414.     
  415.   var item = document.createElement('menuitem');
  416.   item.setAttribute('label',
  417.             this.getString('bookmarks.menu.rename', 'Rename'));
  418.   var service = this; // so we can get to it from the callback
  419.   item.addEventListener('command',
  420.   function bookmark_edit_oncommand (event) {
  421.     var servicepane = aParentWindow.document.getElementById('servicepane');
  422.     servicepane.startEditingNode(aNode);
  423.   }, false);
  424.   aContextMenu.appendChild(item);
  425.  
  426.   item = document.createElement('menuitem');
  427.   item.setAttribute('label',
  428.             this.getString('bookmarks.menu.edit', 'Edit'));
  429.   var service = this; // so we can get to it from the callback
  430.   item.addEventListener('command',
  431.   function bookmark_edit_oncommand (event) {
  432.     dump ('edit properties of: '+aNode.name+'\n');
  433.     aParentWindow.QueryInterface(Ci.nsIDOMWindowInternal);
  434.     /* begin code stolen from windowUtils.js:SBOpenModalDialog */
  435.     /* FIXME: what's that BackscanPause stuff? */
  436.     var chromeFeatures = "chrome,centerscreen,modal=yes,resizable=no";
  437.     var accessibility = SB_NewDataRemote('accessibility.enabled', null).boolValue;
  438.     chromeFeatures += (',titlebar='+accessibility?'yes':'no');
  439.     aParentWindow.openDialog('chrome://songbird/content/xul/editBookmark.xul',
  440.                  'edit_bookmark', chromeFeatures, aNode);
  441.   }, false);
  442.   aContextMenu.appendChild(item);
  443.   
  444.   item = document.createElement('menuitem');
  445.   item.setAttribute('label',
  446.             this.getString('bookmarks.menu.remove', 'Remove'));
  447.   item.addEventListener('command',
  448.   function bookmark_delete_oncommand (event) {
  449.     dump ('delete: '+aNode.name+'\n');
  450.     // FIXME: confirmation dialog, eh??
  451.     service._servicePane.removeNode(aNode);
  452.   }, false);
  453.   aContextMenu.appendChild(item);
  454. }
  455.  
  456. sbBookmarks.prototype.fillNewItemMenu =
  457. function sbBookmarks_fillNewItemMenu(aNode, aContextMenu, aParentWindow) {
  458.   // XXX lone: disabled; see bug 4387
  459.   /*
  460.   var stringBundle = this._stringBundle;
  461.   function add(id, label, accesskey, oncommand) {
  462.     var menuitem = aContextMenu.ownerDocument.createElement('menuitem');
  463.     menuitem.setAttribute('id', id);
  464.     menuitem.setAttribute('class', 'menuitem-iconic');
  465.     menuitem.setAttribute('label', stringBundle.GetStringFromName(label));
  466.     menuitem.setAttribute('accesskey', stringBundle.GetStringFromName(accesskey));
  467.     menuitem.setAttribute('oncommand', oncommand);
  468.     aContextMenu.appendChild(menuitem);
  469.   }
  470.  
  471.   add('file.folder', 'menu.file.folder', 'menu.file.folder.accesskey', 'doMenu("file.folder")');
  472.   */
  473. }
  474.  
  475. sbBookmarks.prototype.onSelectionChanged=
  476. function sbBookmarks_onSelectionChanged(aNode, aContainer, aParentWindow) {
  477. }
  478.  
  479. sbBookmarks.prototype.canDrop =
  480. function sbBookmarks_canDrop(aNode, aDragSession, aOrientation) {
  481.   if (aNode.isContainer) {
  482.     if (aOrientation != 0) {
  483.       // for folders you can only drop on the folder, not before or after
  484.       return false;
  485.     }
  486.   }
  487.   if (aDragSession.isDataFlavorSupported(MOZ_URL_DRAG_TYPE)) {
  488.     return true;
  489.  
  490.   }
  491.   return false;
  492. }
  493. sbBookmarks.prototype._getDragData =
  494. function sbBookmarks__getDragData(aDragSession, aDataType) {
  495.   // create an nsITransferable
  496.   var transferable = Components.classes["@mozilla.org/widget/transferable;1"].
  497.       createInstance(Components.interfaces.nsITransferable);
  498.   // specify what kind of data we want it to contain
  499.   transferable.addDataFlavor(aDataType);
  500.   // ask the drag session to fill the transferable with that data
  501.   aDragSession.getData(transferable, 0);
  502.   // get the data from the transferable
  503.   var data = {};
  504.   var dataLength = {};
  505.   transferable.getTransferData(aDataType, data, dataLength);
  506.   // it's always a string. always.
  507.   data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
  508.   return data.toString();
  509. }
  510. sbBookmarks.prototype.onDrop =
  511. function sbBookmarks_onDrop(aNode, aDragSession, aOrientation) {
  512.   if (aDragSession.isDataFlavorSupported(MOZ_URL_DRAG_TYPE)) {
  513.     var data = this._getDragData(aDragSession, MOZ_URL_DRAG_TYPE).split('\n');
  514.     var url = data[0];
  515.     var text = data[1];
  516.     var parent, before=null;
  517.     if (aNode.isContainer) {
  518.       parent = aNode;
  519.     } else {
  520.       parent = aNode.parentNode;
  521.       before = aNode;
  522.       if (aOrientation == 1) {
  523.         // after
  524.         before = aNode.nextSibling;
  525.       }
  526.     }
  527.     this.addBookmarkAt(url, text, null, parent, before);
  528.   }
  529. }
  530. sbBookmarks.prototype._addDragData =
  531. function sbBookmarks__addDragData (aTransferable, aData, aDataType) {
  532.   aTransferable.addDataFlavor(aDataType);
  533.   var text = Components.classes["@mozilla.org/supports-string;1"].
  534.      createInstance(Components.interfaces.nsISupportsString);
  535.   text.data = aData;
  536.   // double the length - it's unicode - this is stupid
  537.   aTransferable.setTransferData(aDataType, text, text.data.length*2);
  538. }
  539. sbBookmarks.prototype.onDragGesture =
  540. function sbBookmarks_onDragGesture(aNode, aTransferable) {
  541.   if (aNode.isContainer) {
  542.     // you can't drag folders - that should be handled at the
  543.     // service pane service layer
  544.     return false;
  545.   }
  546.   
  547.   // attach a text/x-moz-url
  548.   // this is for dragging to other places
  549.   /*
  550.   this._addDragData(aTransferable, aNode.url+'\n'+aNode.name, MOZ_URL_DRAG_TYPE);
  551.   */
  552.   
  553.   // and say yet - lets do this drag
  554.   return true;
  555. }
  556.  
  557. /**
  558.  * Called when the user has attempted to rename a bookmark node
  559.  */
  560. sbBookmarks.prototype.onRename =
  561. function sbBookmarks_onRename(aNode, aNewName) {
  562.   if (aNode && aNewName) {
  563.     aNode.name = aNewName;
  564.   }
  565. }
  566.  
  567.  
  568.  
  569. /**
  570.  * /brief XPCOM initialization code
  571.  */
  572. function makeGetModule(CONSTRUCTOR, CID, CLASSNAME, CONTRACTID, CATEGORIES) {
  573.   return function (comMgr, fileSpec) {
  574.     return {
  575.       registerSelf : function (compMgr, fileSpec, location, type) {
  576.         compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  577.         compMgr.registerFactoryLocation(CID,
  578.                         CLASSNAME,
  579.                         CONTRACTID,
  580.                         fileSpec,
  581.                         location,
  582.                         type);
  583.         if (CATEGORIES && CATEGORIES.length) {
  584.           var catman =  Cc["@mozilla.org/categorymanager;1"]
  585.               .getService(Ci.nsICategoryManager);
  586.           for (var i=0; i<CATEGORIES.length; i++) {
  587.             var e = CATEGORIES[i];
  588.             catman.addCategoryEntry(e.category, e.entry, e.value, 
  589.               true, true);
  590.           }
  591.         }
  592.       },
  593.  
  594.       getClassObject : function (compMgr, cid, iid) {
  595.         if (!cid.equals(CID)) {
  596.           throw Cr.NS_ERROR_NO_INTERFACE;
  597.         }
  598.  
  599.         if (!iid.equals(Ci.nsIFactory)) {
  600.           throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  601.         }
  602.  
  603.         return this._factory;
  604.       },
  605.  
  606.       _factory : {
  607.         createInstance : function (outer, iid) {
  608.           if (outer != null) {
  609.             throw Cr.NS_ERROR_NO_AGGREGATION;
  610.           }
  611.           return (new CONSTRUCTOR()).QueryInterface(iid);
  612.         }
  613.       },
  614.  
  615.       unregisterSelf : function (compMgr, location, type) {
  616.         compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  617.         compMgr.unregisterFactoryLocation(CID, location);
  618.         if (CATEGORIES && CATEGORIES.length) {
  619.           var catman =  Cc["@mozilla.org/categorymanager;1"]
  620.               .getService(Ci.nsICategoryManager);
  621.           for (var i=0; i<CATEGORIES.length; i++) {
  622.             var e = CATEGORIES[i];
  623.             catman.deleteCategoryEntry(e.category, e.entry, true);
  624.           }
  625.         }
  626.       },
  627.  
  628.       canUnload : function (compMgr) {
  629.         return true;
  630.       },
  631.  
  632.       QueryInterface : function (iid) {
  633.         if ( !iid.equals(Ci.nsIModule) ||
  634.              !iid.equals(Ci.nsISupports) )
  635.           throw Cr.NS_ERROR_NO_INTERFACE;
  636.         return this;
  637.       }
  638.  
  639.     };
  640.   }
  641. }
  642.  
  643. var NSGetModule = makeGetModule (
  644.   sbBookmarks,
  645.   Components.ID("{21e3e540-1191-46b5-a041-d0ab95d4c067}"),
  646.   "Songbird Bookmarks Service",
  647.   CONTRACTID,
  648.   [{
  649.     category: 'service-pane',
  650.     entry: 'bookmarks',
  651.     value: '@songbirdnest.com/servicepane/bookmarks;1'
  652.   }]);
  653.  
  654.