gecko/services/sync/modules/stores.js

557 lines
18 KiB
JavaScript

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* 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.
*
* The Original Code is Bookmarks Sync.
*
* The Initial Developer of the Original Code is Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Dan Mills <thunder@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
const EXPORTED_SYMBOLS = ['Store', 'SnapshotStore', 'BookmarksStore'];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
addModuleAlias("weave", "{340c2bbc-ce74-4362-90b5-7c26312808ef}");
Cu.import("resource://weave/log4moz.js");
Cu.import("resource://weave/constants.js");
Cu.import("resource://weave/util.js");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
/*
* Data Stores
* These can wrap, serialize items and apply commands
*/
function Store() {
this._init();
}
Store.prototype = {
_logName: "Store",
_init: function Store__init() {
this._log = Log4Moz.Service.getLogger("Service." + this._logName);
},
wrap: function Store_wrap() {
},
applyCommands: function Store_applyCommands(commandList) {
}
};
function SnapshotStore() {
this._init();
}
SnapshotStore.prototype = {
_logName: "SStore",
__dirSvc: null,
get _dirSvc() {
if (!this.__dirSvc)
this.__dirSvc = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties);
return this.__dirSvc;
},
// Last synced tree, version, and GUID (to detect if the store has
// been completely replaced and invalidate the snapshot)
_data: {},
get data() { return this._data; },
set data(value) { this._data = value; },
_version: 0,
get version() { return this._version; },
set version(value) { this._version = value; },
_GUID: null,
get GUID() {
if (!this._GUID) {
let uuidgen = Cc["@mozilla.org/uuid-generator;1"].
getService(Ci.nsIUUIDGenerator);
this._GUID = uuidgen.generateUUID().toString().replace(/[{}]/g, '');
}
return this._GUID;
},
set GUID(GUID) {
this._GUID = GUID;
},
save: function SStore_save() {
this._log.info("Saving snapshot to disk");
let file = this._dirSvc.get("ProfD", Ci.nsIFile);
file.append("bm-sync-snapshot.json");
file.QueryInterface(Ci.nsILocalFile);
if (!file.exists())
file.create(file.NORMAL_FILE_TYPE, PERMS_FILE);
let fos = Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
let flags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE;
fos.init(file, flags, PERMS_FILE, 0);
let out = {version: this.version,
GUID: this.GUID,
snapshot: this.data};
out = uneval(out);
fos.write(out, out.length);
fos.close();
},
load: function SStore_load() {
let file = this._dirSvc.get("ProfD", Ci.nsIFile);
file.append("bm-sync-snapshot.json");
if (!file.exists())
return;
let fis = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
fis.init(file, MODE_RDONLY, PERMS_FILE, 0);
fis.QueryInterface(Ci.nsILineInputStream);
let json = "";
while (fis.available()) {
let ret = {};
fis.readLine(ret);
json += ret.value;
}
fis.close();
json = eval(json);
if (json && 'snapshot' in json && 'version' in json && 'GUID' in json) {
this._log.info("Read saved snapshot from disk");
this.data = json.snapshot;
this.version = json.version;
this.GUID = json.GUID;
}
},
serialize: function SStore_serialize() {
let json = uneval(this.data);
json = json.replace(/:{type/g, ":\n\t{type");
json = json.replace(/}, /g, "},\n ");
json = json.replace(/, parentGUID/g, ",\n\t parentGUID");
json = json.replace(/, index/g, ",\n\t index");
json = json.replace(/, title/g, ",\n\t title");
json = json.replace(/, URI/g, ",\n\t URI");
json = json.replace(/, tags/g, ",\n\t tags");
json = json.replace(/, keyword/g, ",\n\t keyword");
return json;
},
wrap: function SStore_wrap() {
},
applyCommands: function SStore_applyCommands(commands) {
for (let i = 0; i < commands.length; i++) {
// this._log.debug("Applying cmd to obj: " + uneval(commands[i]));
switch (commands[i].action) {
case "create":
this._data[commands[i].GUID] = eval(uneval(commands[i].data));
break;
case "edit":
if ("GUID" in commands[i].data) {
// special-case guid changes
let newGUID = commands[i].data.GUID,
oldGUID = commands[i].GUID;
this._data[newGUID] = this._data[oldGUID];
delete this._data[oldGUID]
for (let GUID in this._data) {
if (this._data[GUID].parentGUID == oldGUID)
this._data[GUID].parentGUID = newGUID;
}
}
for (let prop in commands[i].data) {
if (prop == "GUID")
continue;
this._data[commands[i].GUID][prop] = commands[i].data[prop];
}
break;
case "remove":
delete this._data[commands[i].GUID];
break;
}
}
return this._data;
}
};
SnapshotStore.prototype.__proto__ = new Store();
function BookmarksStore() {
this._init();
}
BookmarksStore.prototype = {
_logName: "BStore",
__bms: null,
get _bms() {
if (!this.__bms)
this.__bms = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
getService(Ci.nsINavBookmarksService);
return this.__bms;
},
__hsvc: null,
get _hsvc() {
if (!this.__hsvc)
this.__hsvc = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
return this.__hsvc;
},
__ls: null,
get _ls() {
if (!this.__ls)
this.__ls = Cc["@mozilla.org/browser/livemark-service;2"].
getService(Ci.nsILivemarkService);
return this.__ls;
},
__ms: null,
get _ms() {
if (!this.__ms)
this.__ms = Cc["@mozilla.org/microsummary/service;1"].
getService(Ci.nsIMicrosummaryService);
return this.__ms;
},
__ts: null,
get _ts() {
if (!this.__ts)
this.__ts = Cc["@mozilla.org/browser/tagging-service;1"].
getService(Ci.nsITaggingService);
return this.__ts;
},
__ans: null,
get _ans() {
if (!this.__ans)
this.__ans = Cc["@mozilla.org/browser/annotation-service;1"].
getService(Ci.nsIAnnotationService);
return this.__ans;
},
_getFolderNodes: function BSS__getFolderNodes(folder) {
let query = this._hsvc.getNewQuery();
query.setFolders([folder], 1);
return this._hsvc.executeQuery(query, this._hsvc.getNewQueryOptions()).root;
},
_wrapNode: function BSS__wrapNode(node) {
var items = {};
this._wrapNodeInternal(node, items, null, null);
return items;
},
_wrapNodeInternal: function BSS__wrapNodeInternal(node, items, parentGUID, index) {
let GUID = this._bms.getItemGUID(node.itemId);
let item = {parentGUID: parentGUID,
index: index};
if (node.type == node.RESULT_TYPE_FOLDER) {
if (this._ls.isLivemark(node.itemId)) {
item.type = "livemark";
let siteURI = this._ls.getSiteURI(node.itemId);
let feedURI = this._ls.getFeedURI(node.itemId);
item.siteURI = siteURI? siteURI.spec : "";
item.feedURI = feedURI? feedURI.spec : "";
} else {
item.type = "folder";
node.QueryInterface(Ci.nsINavHistoryQueryResultNode);
node.containerOpen = true;
for (var i = 0; i < node.childCount; i++) {
this._wrapNodeInternal(node.getChild(i), items, GUID, i);
}
}
item.title = node.title;
} else if (node.type == node.RESULT_TYPE_URI ||
node.type == node.RESULT_TYPE_QUERY) {
if (this._ms.hasMicrosummary(node.itemId)) {
item.type = "microsummary";
let micsum = this._ms.getMicrosummary(node.itemId);
item.generatorURI = micsum.generator.uri.spec; // breaks local generators
} else if (node.type == node.RESULT_TYPE_QUERY) {
item.type = "query";
item.title = node.title;
} else {
item.type = "bookmark";
item.title = node.title;
}
item.URI = node.uri;
item.tags = this._ts.getTagsForURI(makeURI(node.uri));
item.keyword = this._bms.getKeywordForBookmark(node.itemId);
} else if (node.type == node.RESULT_TYPE_SEPARATOR) {
item.type = "separator";
} else {
this._log.warn("Warning: unknown item type, cannot serialize: " + node.type);
return;
}
items[GUID] = item;
},
_getWrappedBookmarks: function BSS__getWrappedBookmarks(folder) {
return this._wrapNode(this._getFolderNodes(folder));
},
_resetGUIDsInt: function BSS__resetGUIDsInt(node) {
if (this._ans.itemHasAnnotation(node.itemId, "placesInternal/GUID"))
this._ans.removeItemAnnotation(node.itemId, "placesInternal/GUID");
if (node.type == node.RESULT_TYPE_FOLDER &&
!this._ls.isLivemark(node.itemId)) {
node.QueryInterface(Ci.nsINavHistoryQueryResultNode);
node.containerOpen = true;
for (var i = 0; i < node.childCount; i++) {
this._resetGUIDsInt(node.getChild(i));
}
}
},
_createCommand: function BStore__createCommand(command) {
let newId;
let parentId = this._bms.getItemIdForGUID(command.data.parentGUID);
if (parentId < 0) {
this._log.warn("Creating node with unknown parent -> reparenting to root");
parentId = this._bms.bookmarksMenuFolder;
}
switch (command.data.type) {
case "query":
case "bookmark":
case "microsummary":
this._log.info(" -> creating bookmark \"" + command.data.title + "\"");
let URI = makeURI(command.data.URI);
newId = this._bms.insertBookmark(parentId,
URI,
command.data.index,
command.data.title);
this._ts.untagURI(URI, null);
this._ts.tagURI(URI, command.data.tags);
this._bms.setKeywordForBookmark(newId, command.data.keyword);
if (command.data.type == "microsummary") {
this._log.info(" \-> is a microsummary");
let genURI = makeURI(command.data.generatorURI);
try {
let micsum = this._ms.createMicrosummary(URI, genURI);
this._ms.setMicrosummary(newId, micsum);
}
catch(ex) { /* ignore "missing local generator" exceptions */ }
}
break;
case "folder":
this._log.info(" -> creating folder \"" + command.data.title + "\"");
newId = this._bms.createFolder(parentId,
command.data.title,
command.data.index);
break;
case "livemark":
this._log.info(" -> creating livemark \"" + command.data.title + "\"");
newId = this._ls.createLivemark(parentId,
command.data.title,
makeURI(command.data.siteURI),
makeURI(command.data.feedURI),
command.data.index);
break;
case "separator":
this._log.info(" -> creating separator");
newId = this._bms.insertSeparator(parentId, command.data.index);
break;
default:
this._log.error("_createCommand: Unknown item type: " + command.data.type);
break;
}
if (newId)
this._bms.setItemGUID(newId, command.GUID);
},
_removeCommand: function BStore__removeCommand(command) {
var itemId = this._bms.getItemIdForGUID(command.GUID);
if (itemId < 0) {
this._log.warn("Attempted to remove item " + command.GUID +
", but it does not exist. Skipping.");
return;
}
var type = this._bms.getItemType(itemId);
switch (type) {
case this._bms.TYPE_BOOKMARK:
this._log.info(" -> removing bookmark " + command.GUID);
this._bms.removeItem(itemId);
break;
case this._bms.TYPE_FOLDER:
this._log.info(" -> removing folder " + command.GUID);
this._bms.removeFolder(itemId);
break;
case this._bms.TYPE_SEPARATOR:
this._log.info(" -> removing separator " + command.GUID);
this._bms.removeItem(itemId);
break;
default:
this._log.error("removeCommand: Unknown item type: " + type);
break;
}
},
_editCommand: function BStore__editCommand(command) {
var itemId = this._bms.getItemIdForGUID(command.GUID);
if (itemId < 0) {
this._log.warn("Item for GUID " + command.GUID + " not found. Skipping.");
return;
}
for (let key in command.data) {
switch (key) {
case "GUID":
var existing = this._bms.getItemIdForGUID(command.data.GUID);
if (existing < 0)
this._bms.setItemGUID(itemId, command.data.GUID);
else
this._log.warn("Can't change GUID " + command.GUID +
" to " + command.data.GUID + ": GUID already exists.");
break;
case "title":
this._bms.setItemTitle(itemId, command.data.title);
break;
case "URI":
this._bms.changeBookmarkURI(itemId, makeURI(command.data.URI));
break;
case "index":
this._bms.moveItem(itemId, this._bms.getFolderIdForItem(itemId),
command.data.index);
break;
case "parentGUID":
let index = -1;
if (command.data.index && command.data.index >= 0)
index = command.data.index;
this._bms.moveItem(
itemId, this._bms.getItemIdForGUID(command.data.parentGUID), index);
break;
case "tags":
let tagsURI = this._bms.getBookmarkURI(itemId);
this._ts.untagURI(URI, null);
this._ts.tagURI(tagsURI, command.data.tags);
break;
case "keyword":
this._bms.setKeywordForBookmark(itemId, command.data.keyword);
break;
case "generatorURI":
let micsumURI = makeURI(this._bms.getBookmarkURI(itemId));
let genURI = makeURI(command.data.generatorURI);
let micsum = this._ms.createMicrosummary(micsumURI, genURI);
this._ms.setMicrosummary(itemId, micsum);
break;
case "siteURI":
this._ls.setSiteURI(itemId, makeURI(command.data.siteURI));
break;
case "feedURI":
this._ls.setFeedURI(itemId, makeURI(command.data.feedURI));
break;
default:
this._log.warn("Can't change item property: " + key);
break;
}
}
},
wrap: function BStore_wrap() {
let filed = this._getWrappedBookmarks(this._bms.bookmarksMenuFolder);
let toolbar = this._getWrappedBookmarks(this._bms.toolbarFolder);
let unfiled = this._getWrappedBookmarks(this._bms.unfiledBookmarksFolder);
for (let guid in unfiled) {
if (!(guid in filed))
filed[guid] = unfiled[guid];
}
for (let guid in toolbar) {
if (!(guid in filed))
filed[guid] = toolbar[guid];
}
return filed; // (combined)
},
resetGUIDs: function BStore_resetGUIDs() {
this._resetGUIDsInt(this._getFolderNodes(this._bms.bookmarksMenuFolder));
this._resetGUIDsInt(this._getFolderNodes(this._bms.toolbarFolder));
this._resetGUIDsInt(this._getFolderNodes(this._bms.unfiledBookmarksFolder));
},
applyCommands: function BStore_applyCommands(commandList) {
for (var i = 0; i < commandList.length; i++) {
var command = commandList[i];
this._log.debug("Processing command: " + uneval(command));
switch (command["action"]) {
case "create":
this._createCommand(command);
break;
case "remove":
this._removeCommand(command);
break;
case "edit":
this._editCommand(command);
break;
default:
this._log.error("unknown action in command: " + command["action"]);
break;
}
}
}
};
BookmarksStore.prototype.__proto__ = new Store();
function addModuleAlias(alias, extensionId) {
let ioSvc = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
let resProt = ioSvc.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
if (!resProt.hasSubstitution(alias)) {
let extMgr = Cc["@mozilla.org/extensions/manager;1"]
.getService(Ci.nsIExtensionManager);
let loc = extMgr.getInstallLocation(extensionId);
let extD = loc.getItemLocation(extensionId);
extD.append("modules");
resProt.setSubstitution(alias, ioSvc.newFileURI(extD));
}
}