// BEGIN FLOCK GPL // // Copyright Flock Inc. 2005-2007 // http://flock.com // // This file may be used under the terms of of the // GNU General Public License Version 2 or later (the "GPL"), // http://www.gnu.org/licenses/gpl.html // // Software distributed under the License is distributed on an "AS IS" basis, // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License // for the specific language governing rights and limitations under the // License. // // END FLOCK GPL var EXPORTED_SYMBOLS = ["onlineFavoritesBackend"]; const CC = Components.classes; const CI = Components.interfaces; const CR = Components.results; const CU = Components.utils; CU.import("resource:///modules/FlockScheduler.jsm"); /************************************************************************** * Module: Online Favorites Back End **************************************************************************/ /* Note: This module depends on access to the following PRIVATE properties of the services being supported: - aService._coop - aService._logger - aService._c_svc */ // Bookmark stream refresh interval in seconds. const DEFAULT_REFRESH_INTERVAL = 5 * 60; const PREF_ONLINEFAVE_REFRESH_INTERVAL = "flock.favorites.online.refreshInterval"; CC["@mozilla.org/moz/jssubscript-loader;1"] .getService(CI.mozIJSSubScriptLoader) .loadSubScript("chrome://flock/content/common/flocksafe.js"); /************************************************************************** * Private Data and Functions **************************************************************************/ // Helper function to assist in comparing strings. function stringify(aObj) { if (!aObj) { return ""; } else { return aObj; } } // Helper function to assist in comparing dates. function dateify(aObj) { if (!aObj) { return 0; } else { return aObj.getTime(); } } // Helper function to assist in comparing lists of tags. function sanitizeTags(aTagList) { return aTagList ? aTagList.split(/[\s,]/).sort().join(",") : ""; } // Helper function to assist in comparing complete bookmarks. function bookmarks_match(bm1, bm2) { return (bm1.private === bm2.private && /* booleans - nice and easy */ stringify(bm1.name) === stringify (bm2.name) && stringify(bm1.description) === stringify(bm2.description) && sanitizeTags(bm1.tags) === sanitizeTags(bm2.tags) && dateify(bm1.datevalue) === dateify(bm2.datevalue)); } /************************************************************************** * Online Favorites Back End Public Interface **************************************************************************/ var onlineFavoritesBackend = {}; onlineFavoritesBackend.createAccount = function OFBE_createAccount(aService, aAccountID, aIsTransient) { var account_urn = aService.urn + ":" + aAccountID; var account; if (!aService._coop.Account.exists(account_urn)) { account = new aService._coop.Account(account_urn, { name: aAccountID, serviceId: aService.contractId, service: aService._c_svc, accountId: aAccountID, favicon: aService.icon, URL: aService.getUserUrl(aAccountID), isTransient: aIsTransient, showInSidebar: false }); aService._coop.accounts_root.children.addOnce(account); } else { account = aService._coop.get(account_urn); } // Create a "stream" for the online bookmarks var bookmarks = {}; var bookmarks_urn = account_urn + ":bookmarks"; if (!aService._coop.OnlineBookmarksStream.exists(bookmarks_urn)) { prefService = CC["@mozilla.org/preferences-service;1"] .getService(CI.nsIPrefBranch); var prefRefreshInterval = DEFAULT_REFRESH_INTERVAL; if (prefService.getPrefType(PREF_ONLINEFAVE_REFRESH_INTERVAL)) { prefRefreshInterval = prefService.getIntPref(PREF_ONLINEFAVE_REFRESH_INTERVAL); } bookmarks = new aService._coop.OnlineBookmarksStream(bookmarks_urn, { name: aAccountID + " on " + aService.title, userid: aAccountID, serviceId: aService.contractId, favicon: aService.icon, isTransient: aIsTransient, refreshInterval: prefRefreshInterval }); aService._coop.onlinebookmarks_root.children.addOnce(bookmarks); } // Create the "no tag" folder var noTagFolder = new aService._coop.Folder(bookmarks.id() + ":notag", { name: flockGetString("favorites/favorites", "flock.favs.online.noTag") }); bookmarks.children.add(noTagFolder); var acct = aService.getAccount(account_urn); return acct; }; onlineFavoritesBackend.removeAccount = function OFBE_removeAccount(aService, aAccountUrn) { var acctCoopObj = aService._coop.get(aAccountUrn); var parents = {}; var i; // Next delete the bookmarks that are solely associated with this account var bookmarks_urn = aAccountUrn + ":bookmarks"; var bmStream = aService._coop.get(bookmarks_urn); if (bmStream) { aService._logger.debug("found bookmark stream " + bookmarks_urn); // Remove the Delicious Bookmarks node from its parents parents = bmStream.getParents(); for (i = 0; i < parents.length; i++) { parents[i].children.remove(bmStream); } } else { aService._logger.debug("DID NOT find bookmark stream " + bookmarks_urn); } // Next remove the account from any parents it might have parents = acctCoopObj.getParents(); for (i = 0; i < parents.length; i++) { aService._logger.debug("removing from parent " + i); parents[i].children.remove(acctCoopObj); } // kill the account node aService._logger.debug("destroying account " + aAccountUrn); acctCoopObj.destroy(); // Finally, slowly delete the bookmarks var synchronize = function OFBE_removeAccount_sync(should_yield) { var i = 0; var bmChildren = bmStream.children.enumerate(); while (bmChildren.hasMoreElements()) { var tag = bmChildren.getNext(); var enum_ = tag.children.enumerate(); while (enum_.hasMoreElements()) { var bm = enum_.getNext(); aService._logger.debug("destroying bookmark " + bm.URL); var parents = bm.getParents(); for (var j = 0; j < parents.length; j++) { parents[j].children.remove(bm); } bm.destroy(); if (should_yield()) { yield; } } } bmStream.destroy(); }; if (bmStream) { FlockScheduler.schedule(null, 0.1, 10, synchronize); } }; onlineFavoritesBackend.updateBookmark = function OFBE_updateBookmark(aService, aAccountUrn, aServerBookmark, aPrivate) { var bookmark_urn = aAccountUrn + ":" + aServerBookmark.URL; var localBookmark = null; var tagList; var i; // Create/update the bookmark itself if (aService._coop.Bookmark.exists(bookmark_urn)) { // Need to check if any modification is needed before we assert anything localBookmark = aService._coop.get(bookmark_urn); if (bookmarks_match(aServerBookmark, localBookmark)) { aService._logger.debug("Unchanged on server: Ignoring " + aServerBookmark.URL); return; } aService._logger.debug("Modified on server : Updating " + aServerBookmark.URL); if (stringify(aServerBookmark.name) !== stringify(localBookmark.name)) { localBookmark.name = aServerBookmark.name; } if (stringify(aServerBookmark.description) !== stringify(localBookmark.description)) { localBookmark.description = aServerBookmark.description; } if (sanitizeTags(aServerBookmark.tags) !== sanitizeTags(localBookmark.tags)) { localBookmark.tags = sanitizeTags(aServerBookmark.tags); var tagenum = localBookmark.tag.enumerate(); while (tagenum.hasMoreElements()) { var tag = tagenum.getNext(); localBookmark.tag.remove(tag); } } var date = new Date(); localBookmark.LastModifiedDate = date; localBookmark.private = aPrivate; } else { // New bookmark aService._logger.debug("New on server : Creating " + aServerBookmark.URL); localBookmark = new aService._coop.Bookmark(bookmark_urn, { URL: aServerBookmark.URL, name: aServerBookmark.name, description: aServerBookmark.description, tags: aServerBookmark.tags, datevalue: aServerBookmark.datevalue, hash: aServerBookmark.hash, flockType: "bookmark", private: aPrivate, isPollable: false }); // bookmarks.children.add(localBookmark); } tagList = aServerBookmark.tags.split(/[\s,]/); for (i = 0; i < tagList.length; i++) { localBookmark.tag.addOnce(tagList[i]); } var bookmarks = aService._coop.get(aAccountUrn + ":bookmarks"); this.classifyBookmark(aService, bookmarks, localBookmark, tagList); }; onlineFavoritesBackend.classifyBookmark = function OFBE_classifyBookmark(aService, aBookmarks, aLocalBookmark, aTagList) { var i; // Delete the bookmark from the tag folders, and delete empty tags var parents = aLocalBookmark.getParents(); while (parents.length > 0) { var parent_ = parents.pop(); parent_.children.remove(aLocalBookmark); if (!parent_.children.count()) { aBookmarks.children.remove(parent_); parent_.destroy(); } } var tagFolder; // Add the bookmark to the tag folders it should be if ((aTagList.length === 1 && !aTagList[0].length) || (!aTagList.length) || (aTagList === [""])) { var noTagUrn = aBookmarks.id() + ":notag"; tagFolder = aService._coop.get(noTagUrn); tagFolder.children.add(aLocalBookmark); } else { for (i = 0; i < aTagList.length; i++) { var tagUrn = aBookmarks.id() + ":tag:" + aTagList[i]; if (aService._coop.Folder.exists(tagUrn)) { tagFolder = aService._coop.get(tagUrn); } else { tagFolder = this.createTag(aService, aBookmarks, aTagList[i], tagUrn); } tagFolder.children.add(aLocalBookmark); } } }; onlineFavoritesBackend.destroyBookmark = function OFBE_destroyBookmark(aService, aAccountId, aUrl) { var bookmark_urn = aService.urn + ":" + aAccountId + ":" + aUrl; if (aService._coop.Bookmark.exists(bookmark_urn)) { var bookmarks = aService._coop.get(aService.urn + ":" + aAccountId + ":bookmarks"); var bookmark = aService._coop.get(bookmark_urn); bookmarks.children.remove(bookmark); bookmark.destroy(); } }; onlineFavoritesBackend.updateLocal = function OFBE_updateLocal(aService, aPostsList, aLastUpdate, aAccountUrn) { var i; var bmEnum; var bmEnum2; // Update the last update time for the bookmark stream var localBookmarks = aService._coop.get(aAccountUrn + ":bookmarks"); localBookmarks.last_update_time = aLastUpdate; var be = this; var synchronize = function OFBE_updateLocal_sync(should_yield) { var urlHash = {}; // Useful to optimize deletion // Create new bookmarks, or update existing bookmarks for (i = 0; i < aPostsList.length; i++) { if (aPostsList[i].tags === "system:unfiled") { aPostsList[i].tags = ""; } // To optimize deletion: put the URLs in a hash urlHash[aPostsList[i].URL] = true; be.updateBookmark(aService, aAccountUrn, aPostsList[i]); if (should_yield()) { yield; } } // Remove locally bookmarks deleted on the server var localLength = 0; i = 0; bmEnum = localBookmarks.children.enumerate(); while (bmEnum.hasMoreElements()) { var localTag = bmEnum.getNext(); bmEnum2 = localTag.children.enumerate(); while (bmEnum2.hasMoreElements()) { var localBookmark = bmEnum2.getNext(); if (!urlHash[localBookmark.URL]) { aService._logger.debug("Deleted on server : Deleting " + localBookmark.URL); var parents = localBookmark.getParents(); for (var j = 0; j < parents.length; j++) { parents[j].children.remove(localBookmark); } localBookmark.destroy(); } } if (should_yield()) { yield; } } // Remove empty tags bmEnum = localBookmarks.children.enumerate(); while (bmEnum.hasMoreElements()) { var tag = bmEnum.getNext(); bmEnum2 = tag.children.enumerate(); if (!bmEnum2.hasMoreElements() && (tag.id() !== localBookmarks.id() + ":notag")) { // The tag is empty, let's delete it. // Note: Don't delete "No Tag". localBookmarks.children.remove(tag); tag.destroy(); } } }; FlockScheduler.schedule(null, 0.1, 10, synchronize); }; onlineFavoritesBackend.createTag = function OFBE_createTag(aService, aBookmarks, aTag, aTagUrn) { var tagFolder = new aService._coop.Folder(aTagUrn, { name: aTag } ); var enum_ = aBookmarks.children.enumerate(); var found = false; var i = 1; enum_.getNext(); // Ignore "notag" while (enum_.hasMoreElements() && !found) { var currentTag = enum_.getNext(); if (aTag < currentTag.name) { found = true; aBookmarks.children.insertAt(tagFolder, i + 1); } i++; } if (!found) { aBookmarks.children.add(tagFolder); } return tagFolder; }; onlineFavoritesBackend.updateTags = function OFBE_updateTags(aService, aAccountId, aTags) { // Copy aTags, so we can modify it. var tags = []; var tag; var i; for (i = 0; i < aTags.length; i++) { tags.push(aTags[i]); } // Remove tags no longer present. var localBookmarks = aService._coop.get(aService.urn + ":" + aAccountId + ":bookmarks"); var tagenum = localBookmarks.tag.enumerate(); while (tagenum.hasMoreElements()) { tag = tagenum.getNext(); if (tags.indexOf(tag) < 0) { // Not in new array, so remove it. localBookmarks.tag.remove(tag); } else { // Already there, remove from input so we don't try adding it again. tags.splice(tags.indexOf(tag), 1); } } // Put in the net new tags. for (i = 0; i < tags.length; i++) { localBookmarks.tag.addOnce(tags[i]); } }; onlineFavoritesBackend.showNotification = function OFBE_showNotification(aMessage) { var wm = CC["@mozilla.org/appshell/window-mediator;1"] .getService(CI.nsIWindowMediator); var topNavWindow = wm.getMostRecentWindow("navigator:browser"); var nBox = topNavWindow.gBrowser.getNotificationBox(); var notification = nBox.getNotificationWithValue("favorite-error"); if (notification) { notification.label = aMessage; } else { nBox.appendNotification(aMessage, "favorite-error", "chrome://browser/skin/Info.png", nBox.FLOCK_PRIORITY_HIGH, null); } };