1
/* Lots and lots of debugging information */
3
bookmarksService: Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"]
4
.getService(Components.interfaces.nsINavBookmarksService),
5
uuidService: Components.classes["@mozilla.org/uuid-generator;1"]
6
.getService(Components.interfaces.nsIUUIDGenerator),
7
annotationService: Components.classes["@mozilla.org/browser/annotation-service;1"]
8
.getService(Components.interfaces.nsIAnnotationService),
9
consoleService: Components.classes["@mozilla.org/consoleservice;1"]
10
.getService(Components.interfaces.nsIConsoleService),
11
historyService: Components.classes["@mozilla.org/browser/nav-history-service;1"]
12
.getService(Components.interfaces.nsINavHistoryService),
13
ioService: Components.classes["@mozilla.org/network/io-service;1"]
14
.getService(Components.interfaces.nsIIOService),
16
annotationKey: "bindwood/uuid",
19
generateUUIDString: function() {
20
return Bindwood.uuidService.generateUUID().toString();
23
annotateItemWithUUID: function(itemId) {
24
var uuid = Bindwood.generateUUIDString();
25
Bindwood.annotationService.setItemAnnotation(itemId, Bindwood.annotationKey, uuid, 0, Bindwood.annotationService.EXPIRE_NEVER);
26
// Whenever we create a new UUID, stash it and the itemId in
28
Bindwood.uuidItemIdMap[uuid] = itemId;
32
itemIdForUUID: function(uuid) {
33
// First, try to look it up in our local cache, barring that
34
// (which shouldn't happen), look it up slowly.
35
var itemId = Bindwood.uuidItemIdMap[uuid];
38
var items = Bindwood.annotationService.getItemsWithAnnotation(Bindwood.annotationKey, {});
39
for (var i = 0; i < items.length; i++) {
40
if (Bindwood.annotationService.getItemAnnotation(items[i], Bindwood.annotationKey) == uuid) {
41
Bindwood.uuidItemIdMap[uuid] = itemId = items[i];
49
uuidForItemId: function(itemId) {
50
// Try to look up the uuid, and failing that, assign a new one
55
uuid = Bindwood.annotationService.getItemAnnotation(itemId, Bindwood.annotationKey);
58
uuid = Bindwood.annotateItemWithUUID(itemId);
64
writeMessage: function(aMessage) {
65
// convenience method for logging. Way better than alert()s.
66
Bindwood.consoleService.logStringMessage("Bindwood: " + aMessage);
69
writeError: function(aMessage, e) {
70
Bindwood.writeMessage(aMessage + "message: '" + e.message + "', reason: '" + e.reason + "', description: '" + e.description + "', error: '" + e.error + "'");
76
// Start the process and de-register ourself
77
// http://forums.mozillazine.org/viewtopic.php?f=19&t=657911&start=0
78
// It ensures that we're only running that code on the first window.
80
if(Components.classes["@mozilla.org/appshell/window-mediator;1"]
81
.getService(Components.interfaces.nsIWindowMediator)
82
.getEnumerator("").getNext() == window) {
83
Bindwood.getCouchPortNumber(Bindwood.startProcess);
87
getCouchPortNumber: function(continueFunction) {
88
// find the desktop Couch port number by making a D-Bus call
89
// we call D-Bus by shelling out to a bash script which calls
90
// it for us, and writes the port number into a temp file
92
// find OS temp dir to put the tempfile in
93
// https://developer.mozilla.org/index.php?title=File_I%2F%2FO#Getting_special_files
94
var tmpdir = Components.classes["@mozilla.org/file/directory_service;1"]
95
.getService(Components.interfaces.nsIProperties)
96
.get("TmpD", Components.interfaces.nsIFile);
97
// create a randomly named tempfile in the tempdir
98
var tmpfile = Components.classes["@mozilla.org/file/local;1"]
99
.createInstance(Components.interfaces.nsILocalFile);
100
tmpfile.initWithPath(tmpdir.path + "/desktopcouch." + Math.random());
101
tmpfile.createUnique(tmpfile.NORMAL_FILE_TYPE, 0600);
102
Bindwood.writeMessage("Tempfile for Couch port number: " + tmpfile.path);
104
// find the D-Bus bash script, which is in our extension folder
105
var MY_ID = "bindwood@ubuntu.com";
106
var em = Components.classes["@mozilla.org/extensions/manager;1"].
107
getService(Components.interfaces.nsIExtensionManager);
108
var dbus_script = em.getInstallLocation(MY_ID).getItemFile(MY_ID,
109
"chrome/content/dbus.sh");
110
// create an nsILocalFile for the executable
111
var nsifile = Components.classes["@mozilla.org/file/local;1"]
112
.createInstance(Components.interfaces.nsILocalFile);
113
nsifile.initWithPath(dbus_script.path);
115
// create an nsIProcess2 to execute this bash script
116
var process = Components.classes["@mozilla.org/process/util;1"]
117
.createInstance(Components.interfaces.nsIProcess2);
118
process.init(nsifile);
120
// Run the process, passing the tmpfile path
121
var args = [tmpfile.path];
122
process.runAsync(args, args.length, {
123
observe: function(process, finishState, data) {
125
if (finishState == "process-finished") {
126
// read temp file to find port number
127
// https://developer.mozilla.org/en/Code_snippets/File_I%2f%2fO#Reading_from_a_file
129
var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].
130
createInstance(Components.interfaces.nsIFileInputStream);
131
var cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
132
createInstance(Components.interfaces.nsIConverterInputStream);
133
fstream.init(tmpfile, -1, 0, 0);
134
cstream.init(fstream, "UTF-8", 0, 0);
136
cstream.readString(-1, str); // read the whole file and put it in str.value
139
cstream.close(); // this closes fstream
140
data = data.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
141
if (/^[0-9]+$/.test(data)) {
144
Bindwood.writeMessage("D-Bus port data is not a number (" + data + ")");
147
// fall back to system CouchDB
148
Bindwood.writeMessage("D-Bus port find failed");
150
tmpfile.remove(false);
151
continueFunction(port);
156
startProcess: function(couchPortNumber) {
157
Bindwood.writeMessage("Starting process with Couch on port " + couchPortNumber);
158
CouchDB.PORT_NUMBER = couchPortNumber;
160
Bindwood.pushBookmarks();
162
Bindwood.writeError("Error when calling pushBookmarks: ", e);
164
Bindwood.createView();
165
Bindwood.pullBookmarks();
168
createView: function() {
169
var DDID = "_design/views";
170
var couch = new CouchDB('bookmarks');
173
current_doc = couch.open(DDID);
174
if (current_doc !== null) {
175
Bindwood.writeMessage("View already exists; leaving it alone");
181
map: "function(doc) { " +
182
"var scheme = doc.uri.split(':',1)[0]; " +
184
"if (scheme == 'http' || scheme == 'https') {" +
185
"uri = doc.uri.split('/')[2];" +
186
"if (uri.length < 30) {" +
188
"doc.uri.split('/',4)[3].substr(0,30-uri.length) + '...';" +
191
"uri = scheme + ' URL';" +
193
"emit(doc.title, uri);" +
201
Bindwood.writeError("Problem saving view: ", e);
205
// some kind of error fetching the existing design doc
206
Bindwood.writeError("Problem checking for view: ", e);
210
pushBookmarks: function() {
211
// Prime the pump, so to speak, by uploading all our local
212
// bookmarks to CouchDB (if they're not there already).
213
var couch = new CouchDB('bookmarks');
214
// Create the DB if it doesn't already exist
218
Bindwood.writeError("Error when creating database in pushBookmarks (file_exists is OK here): ", e);
221
Bindwood.pushBookmarksFromList(Bindwood.bookmarksService.toolbarFolder,
222
"toolbarFolder", couch);
223
Bindwood.pushBookmarksFromList(Bindwood.bookmarksService.bookmarksMenuFolder,
224
"bookmarksMenuFolder", couch);
227
getBookmarksFromList: function(bookmarksList) {
229
var options = Bindwood.historyService.getNewQueryOptions();
230
var query = Bindwood.historyService.getNewQuery();
231
query.setFolders([bookmarksList], 1);
232
var result = Bindwood.historyService.executeQuery(query, options);
233
var rootNode = result.root;
234
rootNode.containerOpen = true;
235
for (var i=0; i<rootNode.childCount; i++) {
236
var node = rootNode.getChild(i);
237
if (Bindwood.bookmarksService.getItemType(node.itemId) !=
238
Bindwood.bookmarksService.TYPE_BOOKMARK) {
242
var title = Bindwood.bookmarksService.getItemTitle(node.itemId);
244
var metadata = Bindwood.bookmarksService.getBookmarkURI(node.itemId);
246
Bindwood.writeError("problem fetching metadata for bookmark '" + title + "': ", e);
249
var uuid = Bindwood.uuidForItemId(node.itemId);
256
rootNode.containerOpen = false;
260
pushBookmarksFromList: function(bookmarksList, bookmarksListName, db) {
261
var bookmarkData = Bindwood.getBookmarksFromList(bookmarksList);
262
for (var i = 0; i < bookmarkData.length; i++) {
263
// find this bookmark in CouchDB
264
var uuid = Bindwood.uuidForItemId(bookmarkData[i].id);
265
var uri = bookmarkData[i].metadata.spec;
266
var title = bookmarkData[i].title;
268
var results = db.query(function(doc) {
269
if (doc.application_annotations &&
270
doc.application_annotations.Firefox &&
271
doc.application_annotations.Firefox.uuid) {
272
emit(doc.application_annotations.Firefox.uuid, doc);
275
startkey: uuid, endkey: uuid
278
if (results.rows.length === 0) {
279
// this bookmark is not in CouchDB, so write it
281
record_type: "http://example.com/bookmark",
284
application_annotations: {
287
list: bookmarksListName
294
Bindwood.writeError("Problem saving bookmark to CouchDB; bookmark is " + JSON.stringify(record) + ": ", e);
297
// bookmark is already in CouchDB, so do nothing
302
pullBookmarks: function() {
303
var couch = new CouchDB('bookmarks');
304
// Fetch all bookmark documents from the database
305
// the query function is evaluated by Couch, which doesn't know
306
// what Bindwood.RECORD_TYPE is, so we string-encode it first to
307
// include the literal value
309
var rows = couch.query(function (doc) {
310
if (doc.record_type == "http://example.com/bookmark") {
315
Bindwood.writeError("Problem fetching all bookmarks from Couch:", e);
317
for (var i = 0; i < rows.rows.length; i++) {
318
var recordid = rows.rows[i].id;
319
var bm = rows.rows[i].value;
320
if (bm.application_annotations &&
321
bm.application_annotations.Firefox &&
322
bm.application_annotations.Firefox.uuid) {
323
// this bookmark has a uuid, so check its values haven't changed
324
// find the bookmark with this uuid
325
var itemId = Bindwood.itemIdForUUID(
326
bm.application_annotations.Firefox.uuid);
328
// This bookmark has a uuid, but it's not one of ours.
329
// We need to work out whether (a) it's the same as one
330
// of ours but with a different uuid (so we need to
331
// make the uuids the same), or (b) it's a new one
332
// that happens to have been created on a different
334
// For the moment, punt on this.
336
var title = Bindwood.bookmarksService.getItemTitle(itemId);
337
var metadata = Bindwood.bookmarksService.getBookmarkURI(itemId);
338
if (title != bm.title) {
339
Bindwood.bookmarksService.setItemTitle(itemId, bm.title);
341
if (metadata.spec != bm.uri) {
343
metadata = Bindwood.ioService.newURI(bm.uri, null, null);
345
Bindwood.writeError("Problem creating a new URI for bookmark", e);
347
Bindwood.bookmarksService.changeBookmarkURI(itemId, metadata);
351
// this bookmark has no uuid, so create it from fresh in Firefox
353
if (bm.application_annotations &&
354
bm.application_annotations.Firefox &&
355
bm.application_annotations.Firefox.list) {
356
switch (bm.application_annotations.Firefox.list) {
357
case "toolbarFolder":
358
list = Bindwood.bookmarksService.toolbarFolder;
360
case "bookmarksMenuFolder":
361
list = Bindwood.bookmarksService.bookmarksMenuFolder;
364
list = Bindwood.bookmarksService.toolbarFolder;
368
list = Bindwood.bookmarksService.toolbarFolder;
370
var metadata = Bindwood.ioService.newURI(bm.uri, null, null);
372
var itemId = Bindwood.bookmarksService.insertBookmark(list,
373
metadata, -1, bm.title);
374
// and then write the new uuid back to the record
375
var uuid = Bindwood.uuidForItemId(itemId);
376
var doc = couch.open(recordid);
377
if (!doc.application_annotations) {
378
doc.application_annotations = {};
380
if (!doc.application_annotations.Firefox) {
381
doc.application_annotations.Firefox = {};
383
doc.application_annotations.Firefox.uuid = uuid;
387
Bindwood.writeError("Problem writing record for new bookmark: ",e);
391
// reschedule ourself
392
setTimeout(Bindwood.pullBookmarks, 30000);
396
// An nsINavBookmarkObserver
397
onItemAdded: function(aItemId, aFolder, aIndex) {
398
// A bookmark has been added, so we create a blank entry
399
// in Couch with our local itemId attached.
400
netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
402
var couch = new CouchDB('bookmarks');
404
var uuid = Bindwood.uuidForItemId(aItemId);
408
case Bindwood.bookmarksService.toolbarFolder:
409
list = "toolbarFolder";
411
case Bindwood.bookmarksService.bookmarksMenuFolder:
412
list = "bookmarksMenuFolder";
415
list = "toolbarFolder";
420
record_type: "http://example.com/bookmark",
421
application_annotations: {
430
var result = couch.save(doc);
432
Bindwood.writeError("Problem saving new bookmark to Couch", e);
435
onBeforeItemRemoved: function(aItemId) {
436
// A bookmark has been removed. This is called before it's
437
// been removed locally, though we're passed the itemId,
438
// which we use to delete from Couch.
439
netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
441
var couch = new CouchDB('bookmarks');
443
var uuid = Bindwood.uuidForItemId(aItemId);
445
var results = couch.query(function(doc) {
446
if (doc.application_annotations &&
447
doc.application_annotations.Firefox &&
448
doc.application_annotations.Firefox.uuid) {
449
emit(doc.application_annotations.Firefox.uuid, doc);
452
startkey: uuid, endkey: uuid
455
if (results.rows.length === 0) {
456
Bindwood.writeMessage("A bookmark was deleted, but this bookmark isn't in CouchDB. This isn't supposed to happen.");
460
var doc = couch.open(results.rows[0].id);
463
var result = couch.deleteDoc(doc);
464
// Also remove from our local cache and remove
465
// annotation from service.
466
delete Bindwood.uuidItemIdMap[uuid];
468
Bindwood.writeError("Problem deleting record from Couch", e);
471
onItemRemoved: function(aItemId, aFolder, aIndex) {
472
Bindwood.annotationService.removeItemAnnotation(aItemId, Bindwood.annotationKey);
474
onItemChanged: function(aBookmarkId, aProperty, aIsAnnotationProperty, aValue) {
475
// A property of a bookmark has changed. On multiple
476
// property updates, this will be called multiple times,
477
// once per property (i.e., for title and URI)
478
netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
480
var couch = new CouchDB('bookmarks');
482
var uuid = Bindwood.uuidForItemId(aBookmarkId);
484
var results = couch.query(function(doc) {
485
if (doc.application_annotations &&
486
doc.application_annotations.Firefox &&
487
doc.application_annotations.Firefox.uuid) {
488
emit(doc.application_annotations.Firefox.uuid, doc);
491
startkey: uuid, endkey: uuid
494
if (results.rows.length === 0) {
495
Bindwood.writeMessage("a bookmark has changed, but this bookmark isn't in CouchDB. this isn't supposed to happen.");
499
var doc = couch.open(results.rows[0].id);
500
doc[aProperty.toString()] = aValue.toString();
503
var result = couch.save(doc);
505
Bindwood.writeError("Problem saving updated bookmark to Couch", e);
509
onBeginUpdateBatch: function() {},
510
onEndUpdateBatch: function() {},
511
onItemVisited: function(aBookmarkId, aVisitID, time) {},
512
onItemMoved: function(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex) {},
513
QueryInterface: function(iid) {
514
if (iid.equals(Components.interfaces.nsINavBookmarkObserver) ||
515
iid.equals(Components.interfaces.nsINavBookmarkObserver_MOZILLA_1_9_1_ADDITIONS) ||
516
iid.equals(Components.interfaces.nsISupports)) {
519
throw Cr.NS_ERROR_NO_INTERFACE;