home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World 2008 February
/
PCWorld_2008-02_cd.bin
/
temacd
/
songbird
/
Songbird_0.4_windows-i686.exe
/
components
/
sbBookmarksService.js
< prev
next >
Wrap
Text File
|
2007-12-21
|
22KB
|
685 lines
/** vim: ts=2 sw=2 expandtab
//
// BEGIN SONGBIRD GPL
//
// This file is part of the Songbird web player.
//
// Copyright(c) 2005-2008 POTI, Inc.
// http://songbirdnest.com
//
// This file may be licensed under the terms of of the
// GNU General Public License Version 2 (the "GPL").
//
// Software distributed under the License is distributed
// on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
// express or implied. See the GPL for the specific language
// governing rights and limitations.
//
// You should have received a copy of the GPL along with this
// program. If not, go to http://www.gnu.org/licenses/gpl.html
// or write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// END SONGBIRD GPL
//
*/
/**
* \file sbBookmarks.js
* \brief the service pane service manages the tree behind the service pane.
* It provides an interface for creating bookmark nodes in the service pane tree.
*
* TODO:
* - move downloading to nsIChannel and friends for better chrome-proofing
* - send version and locale info when requesting new bookmarks
* - handle errors more elegantly in bookmarks downloading
* - handle the adding of bookmarks before the defaults are downloaded
* - allow the server bookmarks list to remove stale entries [bug 2352]
* PERHAPS:
* - move default bookmarks downloading to after first-run so we can have
* per-locale bookmarks?
* - download and cache images
* - add a confirmation dialog for for bookmark deletion
*
*/
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
const CONTRACTID = "@songbirdnest.com/servicepane/bookmarks;1"
const ROOTNODE = "SB:Bookmarks"
const BOOKMARK_DRAG_TYPE = 'text/x-sb-bookmark';
const MOZ_URL_DRAG_TYPE = 'text/x-moz-url';
const BSP = 'http://songbirdnest.com/rdf/bookmarks#';
const SP='http://songbirdnest.com/rdf/servicepane#';
function SB_NewDataRemote(a,b) {
return (new Components.Constructor("@songbirdnest.com/Songbird/DataRemote;1",
"sbIDataRemote", "init"))(a,b);
}
function sbBookmarks() {
this._servicePane = null;
this._stringBundle = null;
// use the default stringbundle to translate tree nodes
this.stringbundle = null;
this._importAttempts = 5;
this._importTimer = null;
}
sbBookmarks.prototype.QueryInterface =
function sbBookmarks_QueryInterface(iid) {
if (!iid.equals(Ci.nsISupports) &&
!iid.equals(Ci.sbIBookmarks) &&
!iid.equals(Ci.sbIServicePaneModule)) {
throw Cr.NS_ERROR_NO_INTERFACE;
}
return this;
}
sbBookmarks.prototype.servicePaneInit =
function sbBookmarks_servicePaneInit(sps) {
this._servicePane = sps;
// if we don't have a bookmarks node, lets create one
this._bookmarkNode = this._servicePane.getNode(ROOTNODE);
if (!this._bookmarkNode) {
// create bookmarks folder
this._bookmarkNode = this.addFolderAt('SB:Bookmarks',
'&servicesource.bookmarks', null, sps.root, null);
}
// set the weight of the bookmarks node
this._bookmarkNode.setAttributeNS(SP, 'Weight', 4);
// if the bookmark node doesn't have the Imported attribute set, lets do an import
if (this._bookmarkNode.getAttributeNS(BSP, 'Imported') != 'true') {
// run the importer
this.importBookmarks();
}
var sbSvc = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
this._stringBundle = sbSvc.createBundle("chrome://songbird/locale/songbird.properties");
}
sbBookmarks.prototype.scheduleImportBookmarks =
function sbBookmarks_scheduleImportBookmarks() {
this._importTimer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer);
this._importTimer.init(this, 5000, Ci.nsITimer.TYPE_ONE_SHOT);
}
sbBookmarks.prototype.observe =
function sbBookmarks_observe(subject, topic, data) {
if (topic == 'timer-callback' && subject == this._importTimer) {
// the bookmarks import timer
this._importTimer = null;
if (!this._servicePane) {
// hmm, we're shutting down, no need to import now
return;
}
this.importBookmarks();
}
}
sbBookmarks.prototype.importBookmarks =
function sbBookmarks_importBookmarks() {
var prefsService =
Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch);
var bookmarksURL = prefsService.getCharPref("songbird.url.bookmarks");
// fetch the default set of bookmarks through a series of tubes
// FIXME: don't use XHR - use nsIChannel and friends
// FIXME: send parameters and/or headers to indicate product version or something
var xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIDOMEventTarget);
var sps = this._servicePane;
var service = this;
function importError() {
service._importAttempts--;
if (service._importAttempts < 0) {
// we tried, but it's time to give up till the next time the player starts
// but first, let's create some default bookmarks to get us through
var default_bookmarks = [
{name:'Add-ons', url:'http://addons.songbirdnest.com/'},
{name:'Directory', url:'http://birdhouse.songbirdnest.com/directory'}];
for (var i=0; i<default_bookmarks.length; i++) {
var bm = default_bookmarks[i];
var bnode = sps.getNode(bm.url);
if (!bnode) {
service.addBookmarkAt(bm.url, bm.name,
'chrome://songbird-branding/skin/logo_16.png',
service._bookmarkNode, null);
}
}
return;
}
service.scheduleImportBookmarks();
}
xhr.addEventListener('load', function(evt) {
var root = null;
try {
// a non-XML page will cause an exception here.
root = xhr.responseXML.documentElement;
} catch (e) {
// catch it and try again
importError();
return;
}
var folders = root.getElementsByTagName('folder');
for (var f=0; folders && f<folders.length; f++) {
var folder = folders[f];
if (!folder.hasAttribute('id') ||
!folder.hasAttribute('name')) {
// the folder is missing required attributes, we must ignore it
continue;
}
var fnode = sps.getNode(folder.getAttribute('id'));
if (!fnode) {
// if the folder doesn't exist, create it
fnode = service.addFolderAt(folder.getAttribute('id'),
folder.getAttribute('name'), folder.getAttribute('image'),
sps.root, null);
}
fnode.isOpen = (folder.getAttribute('open') == 'true');
if (fnode && fnode.getAttributeNS(BSP, 'Imported')) {
// don't reimport a folder that's already been imported
continue;
}
if (fnode.id == ROOTNODE) {
// we just created the default bookmarks root
// we'll need that later if we want to let the user create
// their own bookmarks
service._bookmarkNode = fnode;
}
// now, let's create what goes in
var bookmarks = folder.getElementsByTagName('bookmark');
for (var b=0; bookmarks && b<bookmarks.length; b++) {
var bookmark = bookmarks[b];
if (!bookmark.hasAttribute('url') ||
!bookmark.hasAttribute('name')) {
// missing required attributes
continue;
}
// If the bookmark already exists, then it's somewhere else
// in the tree and we should leave it there untouched.
// Except that it should be marked as imported now...
var bnode = sps.getNode(bookmark.getAttribute('url'));
if (!bnode) {
// create the bookmark
bnode = service.addBookmarkAt(bookmark.getAttribute('url'),
bookmark.getAttribute('name'), bookmark.getAttribute('image'),
fnode, null);
}
// remember we imported it.
bnode.setAttributeNS(BSP, 'Imported', 'true');
}
fnode.setAttributeNS(BSP, 'Imported', 'true');
}
// try to import json bookmarks from 0.2.5
try {
service.migrateLegacyBookmarks();
} catch (e) {
}
}, false);
xhr.addEventListener('error', function(evt) {
importError();
}, false);
xhr.QueryInterface(Ci.nsIXMLHttpRequest);
xhr.open('GET', bookmarksURL, true);
xhr.send(null);
}
sbBookmarks.prototype.shutdown =
function sbBookmarks_shutdown() {
this._bookmarkNode = null;
this._servicePane = null;
this._stringBundle = null;
this._importAttempts = -1; // do not make any more attempts to import
if (this._importTimer) {
this._importTimer.cancel();
this._importTimer = null;
}
}
sbBookmarks.prototype.getString =
function sbBookmarks_getString(aStringId, aDefault) {
try {
return this._stringBundle.GetStringFromName(aStringId);
} catch (e) {
return aDefault;
}
}
sbBookmarks.prototype.migrateLegacyBookmarks =
function sbBookmarks_migrateLegacyBookmarks() {
try {
var prefsService =
Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch);
var LEGACY_BOOKMARKS_PREF = 'songbird.bookmarks.serializedTree';
if (prefsService.prefHasUserValue(LEGACY_BOOKMARKS_PREF)) {
var json = '(' + prefsService.getCharPref(LEGACY_BOOKMARKS_PREF) + ')';
var bms = eval(json);
for (var i in bms.children) {
var folder = bms.children[i];
if (folder.label == '&servicesource.bookmarks') {
for (var j in folder.children) {
var bm = folder.children[j];
if (bm.properties != 'bookmark') {
continue;
}
var node = this._servicePane.getNode(bm.url);
if (node) {
// this bookmark already existed.
// we want to set the title to the old
node.name = bm.label;
} else {
// the bookmark does not exist. We need to create it
var icon = null;
// only import the icon if its from the web
if (bm.icon.match(/^http/)) {
icon = bm.icon;
}
this.addBookmark(bm.url, bm.label, icon);
}
}
break;
}
}
// let's clear that pref
prefsService.clearUserPref(LEGACY_BOOKMARKS_PREF);
}
} catch (e) {
}
}
sbBookmarks.prototype.addBookmark =
function sbBookmarks_addBookmark(aURL, aTitle, aIconURL) {
dump('sbBookmarks.addBookmark('+aURL.toSource()+','+aTitle.toSource()+','+aIconURL.toSource()+')\n');
if (!this._bookmarkNode) {
// if we try to add a bookmark the defaults are loaded, lets create the default folder anyway
this._bookmarkNode = this._servicePane.addNode(ROOTNODE,
this._servicePane.root, true);
}
return this.addBookmarkAt(aURL, aTitle, aIconURL, this._bookmarkNode, null);
}
sbBookmarks.prototype.addBookmarkAt =
function sbBookmarks_addBookmarkAt(aURL, aTitle, aIconURL, aParent, aBefore) {
var bnode = this._servicePane.addNode(aURL, aParent, false);
if (!bnode) {
return bnode;
}
bnode.url = aURL;
bnode.name = aTitle;
if (aBefore) {
aBefore.parentNode.insertBefore(bnode, aBefore);
}
bnode.properties = "bookmark " + aTitle;
bnode.hidden = false;
bnode.contractid = CONTRACTID;
bnode.dndDragTypes = BOOKMARK_DRAG_TYPE;
bnode.dndAcceptNear = BOOKMARK_DRAG_TYPE;
bnode.editable = true;
if (aIconURL && aIconURL.match(/^https?:/)) {
// check that the supplied image url works, otherwise use the default
var checker = Cc["@mozilla.org/network/urichecker;1"]
.createInstance(Ci.nsIURIChecker);
var uri = Cc["@mozilla.org/network/standard-url;1"]
.createInstance(Ci.nsIURI);
uri.spec = aIconURL;
checker.init(uri);
checker.asyncCheck(new ImageUriCheckerObserver(bnode, aIconURL), null);
}
return bnode;
}
function ImageUriCheckerObserver(bnode, icon) {
this._bnode = bnode;
this._icon = icon;
}
ImageUriCheckerObserver.prototype.onStartRequest =
function ImageUriCheckerObserver_onStartRequest(aRequest, aContext)
{
}
ImageUriCheckerObserver.prototype.onStopRequest =
function ImageUriCheckerObserver_onStopRequest(aRequest, aContext, aStatusCode)
{
if (aStatusCode == 0) {
if(aRequest &&
aRequest.baseChannel &&
aRequest.baseChannel instanceof Ci.nsIHttpChannel) {
var channel = aRequest.baseChannel.QueryInterface(Ci.nsIHttpChannel);
if (channel) {
try {
var contentType = channel.getResponseHeader("content-type");
if (contentType.substr(0,6) != "image/") {
Cu.reportError("Favicon URL is not an image - content-type = " +
contentType +
"faviconURL = " +
this._icon);
return;
}
}
catch(e) {
if (Components.lastResult != Cr.NS_ERROR_NOT_AVAILABLE) {
Cu.reportError(e);
}
}
}
}
// If the requested image exists, set it as the icon.
this._bnode.image = this._icon;
}
// Otherwise, we don't set the image property and we get the default from the skin.
return;
}
sbBookmarks.prototype.addFolder =
function sbBookmarks_addFolder(aTitle) {
return this.addFolderAt(null, aTitle, null, this._servicePane.root, null);
}
sbBookmarks.prototype.addFolderAt =
function sbBookmarks_addFolderAt(aId, aTitle, aIconURL, aParent, aBefore) {
var fnode = this._servicePane.addNode(aId, aParent, true);
fnode.name = aTitle;
if (aIconURL != null) {
fnode.image = aIconURL;
}
if (aBefore) {
aBefore.parentNode.insertBefore(fnode, aBefore);
}
fnode.properties = "folder " + aTitle;
fnode.hidden = false;
fnode.contractid = CONTRACTID;
fnode.dndAcceptIn = BOOKMARK_DRAG_TYPE;
fnode.editable = false; // folder names are not editable
return fnode;
}
sbBookmarks.prototype.bookmarkExists =
function sbBookmarks_bookmarkExists(aURL) {
var node = this._servicePane.getNode(aURL);
return (node != null);
}
sbBookmarks.prototype.fillContextMenu =
function sbBookmarks_fillContextMenu(aNode, aContextMenu, aParentWindow) {
dump ('called fillContextMenu with node: '+aNode+'\n');
if (!aNode.editable) {
// not editable - don't add any items
return;
}
if (aNode.contractid != CONTRACTID) {
dump('or not...('+aNode.contractid+')\n');
return;
}
var document = aContextMenu.ownerDocument;
var item = document.createElement('menuitem');
item.setAttribute('label',
this.getString('bookmarks.menu.rename', 'Rename'));
var service = this; // so we can get to it from the callback
item.addEventListener('command',
function bookmark_edit_oncommand (event) {
var servicepane = aParentWindow.document.getElementById('servicepane');
servicepane.startEditingNode(aNode);
}, false);
aContextMenu.appendChild(item);
item = document.createElement('menuitem');
item.setAttribute('label',
this.getString('bookmarks.menu.edit', 'Edit'));
var service = this; // so we can get to it from the callback
item.addEventListener('command',
function bookmark_edit_oncommand (event) {
dump ('edit properties of: '+aNode.name+'\n');
aParentWindow.QueryInterface(Ci.nsIDOMWindowInternal);
/* begin code stolen from windowUtils.js:SBOpenModalDialog */
/* FIXME: what's that BackscanPause stuff? */
var chromeFeatures = "chrome,centerscreen,modal=yes,resizable=no";
var accessibility = SB_NewDataRemote('accessibility.enabled', null).boolValue;
chromeFeatures += (',titlebar='+accessibility?'yes':'no');
aParentWindow.openDialog('chrome://songbird/content/xul/editBookmark.xul',
'edit_bookmark', chromeFeatures, aNode);
}, false);
aContextMenu.appendChild(item);
item = document.createElement('menuitem');
item.setAttribute('label',
this.getString('bookmarks.menu.remove', 'Remove'));
item.addEventListener('command',
function bookmark_delete_oncommand (event) {
dump ('delete: '+aNode.name+'\n');
// FIXME: confirmation dialog, eh??
service._servicePane.removeNode(aNode);
}, false);
aContextMenu.appendChild(item);
}
sbBookmarks.prototype.fillNewItemMenu =
function sbBookmarks_fillNewItemMenu(aNode, aContextMenu, aParentWindow) {
// XXX lone: disabled; see bug 4387
/*
var stringBundle = this._stringBundle;
function add(id, label, accesskey, oncommand) {
var menuitem = aContextMenu.ownerDocument.createElement('menuitem');
menuitem.setAttribute('id', id);
menuitem.setAttribute('class', 'menuitem-iconic');
menuitem.setAttribute('label', stringBundle.GetStringFromName(label));
menuitem.setAttribute('accesskey', stringBundle.GetStringFromName(accesskey));
menuitem.setAttribute('oncommand', oncommand);
aContextMenu.appendChild(menuitem);
}
add('file.folder', 'menu.file.folder', 'menu.file.folder.accesskey', 'doMenu("file.folder")');
*/
}
sbBookmarks.prototype.onSelectionChanged=
function sbBookmarks_onSelectionChanged(aNode, aContainer, aParentWindow) {
}
sbBookmarks.prototype.canDrop =
function sbBookmarks_canDrop(aNode, aDragSession, aOrientation) {
if (aNode.isContainer) {
if (aOrientation != 0) {
// for folders you can only drop on the folder, not before or after
return false;
}
}
if (aDragSession.isDataFlavorSupported(MOZ_URL_DRAG_TYPE)) {
return true;
}
return false;
}
sbBookmarks.prototype._getDragData =
function sbBookmarks__getDragData(aDragSession, aDataType) {
// create an nsITransferable
var transferable = Cc["@mozilla.org/widget/transferable;1"].
createInstance(Ci.nsITransferable);
// specify what kind of data we want it to contain
transferable.addDataFlavor(aDataType);
// ask the drag session to fill the transferable with that data
aDragSession.getData(transferable, 0);
// get the data from the transferable
var data = {};
var dataLength = {};
transferable.getTransferData(aDataType, data, dataLength);
// it's always a string. always.
data = data.value.QueryInterface(Ci.nsISupportsString);
return data.toString();
}
sbBookmarks.prototype.onDrop =
function sbBookmarks_onDrop(aNode, aDragSession, aOrientation) {
if (aDragSession.isDataFlavorSupported(MOZ_URL_DRAG_TYPE)) {
var data = this._getDragData(aDragSession, MOZ_URL_DRAG_TYPE).split('\n');
var url = data[0];
var text = data[1];
var parent, before=null;
if (aNode.isContainer) {
parent = aNode;
} else {
parent = aNode.parentNode;
before = aNode;
if (aOrientation == 1) {
// after
before = aNode.nextSibling;
}
}
this.addBookmarkAt(url, text, null, parent, before);
}
}
sbBookmarks.prototype._addDragData =
function sbBookmarks__addDragData (aTransferable, aData, aDataType) {
aTransferable.addDataFlavor(aDataType);
var text = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
text.data = aData;
// double the length - it's unicode - this is stupid
aTransferable.setTransferData(aDataType, text, text.data.length*2);
}
sbBookmarks.prototype.onDragGesture =
function sbBookmarks_onDragGesture(aNode, aTransferable) {
if (aNode.isContainer) {
// you can't drag folders - that should be handled at the
// service pane service layer
return false;
}
// attach a text/x-moz-url
// this is for dragging to other places
/*
this._addDragData(aTransferable, aNode.url+'\n'+aNode.name, MOZ_URL_DRAG_TYPE);
*/
// and say yet - lets do this drag
return true;
}
/**
* Called when the user has attempted to rename a bookmark node
*/
sbBookmarks.prototype.onRename =
function sbBookmarks_onRename(aNode, aNewName) {
if (aNode && aNewName) {
aNode.name = aNewName;
}
}
/**
* /brief XPCOM initialization code
*/
function makeGetModule(CONSTRUCTOR, CID, CLASSNAME, CONTRACTID, CATEGORIES) {
return function (comMgr, fileSpec) {
return {
registerSelf : function (compMgr, fileSpec, location, type) {
compMgr.QueryInterface(Ci.nsIComponentRegistrar);
compMgr.registerFactoryLocation(CID,
CLASSNAME,
CONTRACTID,
fileSpec,
location,
type);
if (CATEGORIES && CATEGORIES.length) {
var catman = Cc["@mozilla.org/categorymanager;1"]
.getService(Ci.nsICategoryManager);
for (var i=0; i<CATEGORIES.length; i++) {
var e = CATEGORIES[i];
catman.addCategoryEntry(e.category, e.entry, e.value,
true, true);
}
}
},
getClassObject : function (compMgr, cid, iid) {
if (!cid.equals(CID)) {
throw Cr.NS_ERROR_NO_INTERFACE;
}
if (!iid.equals(Ci.nsIFactory)) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
}
return this._factory;
},
_factory : {
createInstance : function (outer, iid) {
if (outer != null) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return (new CONSTRUCTOR()).QueryInterface(iid);
}
},
unregisterSelf : function (compMgr, location, type) {
compMgr.QueryInterface(Ci.nsIComponentRegistrar);
compMgr.unregisterFactoryLocation(CID, location);
if (CATEGORIES && CATEGORIES.length) {
var catman = Cc["@mozilla.org/categorymanager;1"]
.getService(Ci.nsICategoryManager);
for (var i=0; i<CATEGORIES.length; i++) {
var e = CATEGORIES[i];
catman.deleteCategoryEntry(e.category, e.entry, true);
}
}
},
canUnload : function (compMgr) {
return true;
},
QueryInterface : function (iid) {
if ( !iid.equals(Ci.nsIModule) ||
!iid.equals(Ci.nsISupports) )
throw Cr.NS_ERROR_NO_INTERFACE;
return this;
}
};
}
}
var NSGetModule = makeGetModule (
sbBookmarks,
Components.ID("{21e3e540-1191-46b5-a041-d0ab95d4c067}"),
"Songbird Bookmarks Service",
CONTRACTID,
[{
category: 'service-pane',
entry: 'bookmarks',
value: '@songbirdnest.com/servicepane/bookmarks;1'
}]);