Merge lp:~urbanape/bindwood/dbus-and-local-uuids into lp:~urbanape/bindwood/trunk

Proposed by Zachery Bir
Status: Merged
Approved by: Zachery Bir
Approved revision: 13
Merged at revision: not available
Proposed branch: lp:~urbanape/bindwood/dbus-and-local-uuids
Merge into: lp:~urbanape/bindwood/trunk
Diff against target: None lines
To merge this branch: bzr merge lp:~urbanape/bindwood/dbus-and-local-uuids
Reviewer Review Type Date Requested Status
Stuart Langridge (community) Needs Fixing
Zachery Bir Abstain
Elliot Murphy Pending
Review via email: mp+8359@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Zachery Bir (urbanape) wrote :

This has lp:~sil/bindwood/design-view and lp:~sil/bindwood/find-desktop-couch-with-dbus merged in, as well as the work I did supporting internally-generated uuids for bookmarks. There are still a few rough spots to be worked out:

=== TODO ===

This code has been fixed to use the suggested style (creating a new nsIURI, rather than just poking the .spec struct member):

... sync.js +341
                    if (metadata.spec != bm.uri) {
                        try {
                            metadata = Bindwood.ioService.newURI(bm.uri, null, null);
                        } catch(e) {
                            Bindwood.writeError("Problem creating a new URI for bookmark", e);
                        }
                        Bindwood.bookmarksService.changeBookmarkURI(itemId, metadata);
                    }
...

This fails when dealing with a bookmark whose URI looks like this:
  `place:folder=BOOKMARKS_MENU&folder=UNFILED_BOOKMARKS&folder=TOOLBAR&queryType=1&sort=12&excludeItemIfParentHasAnnotation=livemark%2FfeedURI&maxResults=10&excludeQueries=1`

The problem is that while bookmarksService.getBookmarkURI(thatBookmark) will produce the `place:folder=...` URI spec, the IIOService.newURI() function fails when given that spec to create a URI for the updated bookmark. So, we'll likely need to special case these and either ignore them or find the proper way to spell BookmarkURIs for livemarks, &c.

Revision history for this message
Zachery Bir (urbanape) wrote :

Also, missed in my checkin messages, I've removed the menuitem from the Bookmarks menu. We no longer have any need for manually triggering the pull.

I've also pushed the interval back to 30sec, though this isn't set in stone, naturally. I just got tired of seeing my console zoom by.

review: Abstain
Revision history for this message
Stuart Langridge (sil) wrote :

I'm getting an error:
Error: guid is not defined
Source File: chrome://bindwood/content/sync.js
Line: 385

                doc.application_annotations.Firefox.uuid = uuid;
                couch.save(doc);
                doc.application_annotations.Firefox.guid = guid;

Where is "guid" coming from?

review: Needs Fixing
Revision history for this message
Zachery Bir (urbanape) wrote :

On Jul 9, 2009, at 8:03 AM, Stuart Langridge wrote:

> Review: Needs Fixing
> I'm getting an error:
> Error: guid is not defined
> Source File: chrome://bindwood/content/sync.js
> Line: 385
>
> doc.application_annotations.Firefox.uuid = uuid;
> couch.save(doc);
> doc.application_annotations.Firefox.guid = guid;
>
> Where is "guid" coming from?

Must have been from the merge. One moment.

Zac

13. By Zachery Bir <email address hidden>

Fix merge cruft

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'chrome.manifest'
2--- chrome.manifest 2009-06-30 13:47:50 +0000
3+++ chrome.manifest 2009-07-02 13:41:07 +0000
4@@ -1,2 +1,3 @@
5-content ubuntuone-firefox-bookmark-sync chrome/content/
6-overlay chrome://browser/content/browser.xul chrome://ubuntuone-firefox-bookmark-sync/content/browserOverlay.xul
7+content bindwood chrome/content/
8+locale bindwood en chrome/locale/en/
9+overlay chrome://browser/content/browser.xul chrome://bindwood/content/browserOverlay.xul
10
11=== modified file 'chrome/content/browserOverlay.xul'
12--- chrome/content/browserOverlay.xul 2009-06-30 13:47:50 +0000
13+++ chrome/content/browserOverlay.xul 2009-07-07 21:45:47 +0000
14@@ -1,15 +1,20 @@
15 <?xml version="1.0"?>
16
17-<!DOCTYPE overlay SYSTEM "chrome://ubuntuone-firefox-bookmark-sync/locale/ubuntuone-firefox-bookmark-sync.dtd">
18+<!DOCTYPE overlay SYSTEM "chrome://bindwood/locale/en/bindwood.dtd">
19
20-<overlay id="ubuntuone-firefox-bookmark-sync"
21+<overlay id="bindwood"
22 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
23
24- <menupopup id="menu_ToolsPopup">
25- <menuitem id="UbuntuOneFirefoxBookmarkSync"
26- label="Sync Bookmarks (well, alert)"
27- insertbefore="javascriptConsole"
28- oncommand="alert('syncing...');" />
29- </menupopup>
30+ <script type="application/x-javascript" src="chrome://bindwood/content/couch.js" />
31+ <script type="application/x-javascript" src="chrome://bindwood/content/sync.js" />
32+
33+ <window id="main-window">
34+ <script type="application/x-javascript">
35+ window.addEventListener("load", Bindwood.init, true);
36+
37+ var bmsvc = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"].getService(Components.interfaces.nsINavBookmarksService);
38+ window.addEventListener("load", function() { bmsvc.addObserver(Bindwood.Observer, false); }, false);
39+ </script>
40+ </window>
41
42 </overlay>
43
44=== added file 'chrome/content/couch.js'
45--- chrome/content/couch.js 1970-01-01 00:00:00 +0000
46+++ chrome/content/couch.js 2009-07-08 01:24:33 +0000
47@@ -0,0 +1,397 @@
48+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
49+// use this file except in compliance with the License. You may obtain a copy
50+// of the License at
51+//
52+// http://www.apache.org/licenses/LICENSE-2.0
53+//
54+// Unless required by applicable law or agreed to in writing, software
55+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
56+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
57+// License for the specific language governing permissions and limitations under
58+// the License.
59+
60+// A simple class to represent a database. Uses XMLHttpRequest to interface with
61+// the CouchDB server.
62+
63+function CouchDB(name, httpHeaders) {
64+ this.name = name;
65+ this.uri = "/" + encodeURIComponent(name) + "/";
66+
67+ // The XMLHttpRequest object from the most recent request. Callers can
68+ // use this to check result http status and headers.
69+ this.last_req = null;
70+
71+ this.request = function(method, uri, requestOptions) {
72+ requestOptions = requestOptions || {}
73+ requestOptions.headers = combine(requestOptions.headers, httpHeaders)
74+ return CouchDB.request(method, uri, requestOptions);
75+ }
76+
77+ // Creates the database on the server
78+ this.createDb = function() {
79+ this.last_req = this.request("PUT", this.uri);
80+ CouchDB.maybeThrowError(this.last_req);
81+ return JSON.parse(this.last_req.responseText);
82+ }
83+
84+ // Deletes the database on the server
85+ this.deleteDb = function() {
86+ this.last_req = this.request("DELETE", this.uri);
87+ if (this.last_req.status == 404)
88+ return false;
89+ CouchDB.maybeThrowError(this.last_req);
90+ return JSON.parse(this.last_req.responseText);
91+ }
92+
93+ // Save a document to the database
94+ this.save = function(doc, options) {
95+ if (doc._id == undefined)
96+ doc._id = CouchDB.newUuids(1)[0];
97+
98+ this.last_req = this.request("PUT", this.uri +
99+ encodeURIComponent(doc._id) + encodeOptions(options),
100+ {body: JSON.stringify(doc)});
101+ CouchDB.maybeThrowError(this.last_req);
102+ var result = JSON.parse(this.last_req.responseText);
103+ doc._rev = result.rev;
104+ return result;
105+ }
106+
107+ // Open a document from the database
108+ this.open = function(docId, options) {
109+ this.last_req = this.request("GET", this.uri + encodeURIComponent(docId) + encodeOptions(options));
110+ if (this.last_req.status == 404)
111+ return null;
112+ CouchDB.maybeThrowError(this.last_req);
113+ return JSON.parse(this.last_req.responseText);
114+ }
115+
116+ // Deletes a document from the database
117+ this.deleteDoc = function(doc) {
118+ this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id) + "?rev=" + doc._rev);
119+ CouchDB.maybeThrowError(this.last_req);
120+ var result = JSON.parse(this.last_req.responseText);
121+ doc._rev = result.rev; //record rev in input document
122+ doc._deleted = true;
123+ return result;
124+ }
125+
126+ // Deletes an attachment from a document
127+ this.deleteDocAttachment = function(doc, attachment_name) {
128+ this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id) + "/" + attachment_name + "?rev=" + doc._rev);
129+ CouchDB.maybeThrowError(this.last_req);
130+ var result = JSON.parse(this.last_req.responseText);
131+ doc._rev = result.rev; //record rev in input document
132+ return result;
133+ }
134+
135+ this.bulkSave = function(docs, options) {
136+ // first prepoulate the UUIDs for new documents
137+ var newCount = 0
138+ for (var i=0; i<docs.length; i++) {
139+ if (docs[i]._id == undefined)
140+ newCount++;
141+ }
142+ var newUuids = CouchDB.newUuids(docs.length);
143+ var newCount = 0
144+ for (var i=0; i<docs.length; i++) {
145+ if (docs[i]._id == undefined)
146+ docs[i]._id = newUuids.pop();
147+ }
148+ var json = {"docs": docs};
149+ // put any options in the json
150+ for (var option in options) {
151+ json[option] = options[option];
152+ }
153+ this.last_req = this.request("POST", this.uri + "_bulk_docs", {
154+ body: JSON.stringify(json)
155+ });
156+ if (this.last_req.status == 417) {
157+ return {errors: JSON.parse(this.last_req.responseText)};
158+ }
159+ else {
160+ CouchDB.maybeThrowError(this.last_req);
161+ var results = JSON.parse(this.last_req.responseText);
162+ for (var i = 0; i < docs.length; i++) {
163+ if(results[i].rev)
164+ docs[i]._rev = results[i].rev;
165+ }
166+ return results;
167+ }
168+ }
169+
170+ this.ensureFullCommit = function() {
171+ this.last_req = this.request("POST", this.uri + "_ensure_full_commit");
172+ CouchDB.maybeThrowError(this.last_req);
173+ return JSON.parse(this.last_req.responseText);
174+ }
175+
176+ // Applies the map function to the contents of database and returns the results.
177+ this.query = function(mapFun, reduceFun, options, keys) {
178+ var body = {language: "javascript"};
179+ if(keys) {
180+ body.keys = keys ;
181+ }
182+ if (typeof(mapFun) != "string")
183+ mapFun = mapFun.toSource ? mapFun.toSource() : "(" + mapFun.toString() + ")";
184+ body.map = mapFun;
185+ if (reduceFun != null) {
186+ if (typeof(reduceFun) != "string")
187+ reduceFun = reduceFun.toSource ? reduceFun.toSource() : "(" + reduceFun.toString() + ")";
188+ body.reduce = reduceFun;
189+ }
190+ if (options && options.options != undefined) {
191+ body.options = options.options;
192+ delete options.options;
193+ }
194+ this.last_req = this.request("POST", this.uri + "_temp_view" + encodeOptions(options), {
195+ headers: {"Content-Type": "application/json"},
196+ body: JSON.stringify(body)
197+ });
198+ CouchDB.maybeThrowError(this.last_req);
199+ return JSON.parse(this.last_req.responseText);
200+ }
201+
202+ this.view = function(viewname, options, keys) {
203+ var viewParts = viewname.split('/');
204+ var viewPath = this.uri + "_design/" + viewParts[0] + "/_view/"
205+ + viewParts[1] + encodeOptions(options);
206+ if(!keys) {
207+ this.last_req = this.request("GET", viewPath);
208+ } else {
209+ this.last_req = this.request("POST", viewPath, {
210+ headers: {"Content-Type": "application/json"},
211+ body: JSON.stringify({keys:keys})
212+ });
213+ }
214+ if (this.last_req.status == 404)
215+ return null;
216+ CouchDB.maybeThrowError(this.last_req);
217+ return JSON.parse(this.last_req.responseText);
218+ }
219+
220+ // gets information about the database
221+ this.info = function() {
222+ this.last_req = this.request("GET", this.uri);
223+ CouchDB.maybeThrowError(this.last_req);
224+ return JSON.parse(this.last_req.responseText);
225+ }
226+
227+ this.allDocs = function(options,keys) {
228+ if(!keys) {
229+ this.last_req = this.request("GET", this.uri + "_all_docs" + encodeOptions(options));
230+ } else {
231+ this.last_req = this.request("POST", this.uri + "_all_docs" + encodeOptions(options), {
232+ headers: {"Content-Type": "application/json"},
233+ body: JSON.stringify({keys:keys})
234+ });
235+ }
236+ CouchDB.maybeThrowError(this.last_req);
237+ return JSON.parse(this.last_req.responseText);
238+ }
239+
240+ this.designDocs = function() {
241+ return this.allDocs({startkey:"_design", endkey:"_design0"});
242+ };
243+
244+ this.allDocsBySeq = function(options,keys) {
245+ var req = null;
246+ if(!keys) {
247+ req = this.request("GET", this.uri + "_all_docs_by_seq" + encodeOptions(options));
248+ } else {
249+ req = this.request("POST", this.uri + "_all_docs_by_seq" + encodeOptions(options), {
250+ headers: {"Content-Type": "application/json"},
251+ body: JSON.stringify({keys:keys})
252+ });
253+ }
254+ CouchDB.maybeThrowError(req);
255+ return JSON.parse(req.responseText);
256+ }
257+
258+ this.compact = function() {
259+ this.last_req = this.request("POST", this.uri + "_compact");
260+ CouchDB.maybeThrowError(this.last_req);
261+ return JSON.parse(this.last_req.responseText);
262+ }
263+
264+ this.setDbProperty = function(propId, propValue) {
265+ this.last_req = this.request("PUT", this.uri + propId,{
266+ body:JSON.stringify(propValue)
267+ });
268+ CouchDB.maybeThrowError(this.last_req);
269+ return JSON.parse(this.last_req.responseText);
270+ }
271+
272+ this.getDbProperty = function(propId) {
273+ this.last_req = this.request("GET", this.uri + propId);
274+ CouchDB.maybeThrowError(this.last_req);
275+ return JSON.parse(this.last_req.responseText);
276+ }
277+
278+ this.setAdmins = function(adminsArray) {
279+ this.last_req = this.request("PUT", this.uri + "_admins",{
280+ body:JSON.stringify(adminsArray)
281+ });
282+ CouchDB.maybeThrowError(this.last_req);
283+ return JSON.parse(this.last_req.responseText);
284+ }
285+
286+ this.getAdmins = function() {
287+ this.last_req = this.request("GET", this.uri + "_admins");
288+ CouchDB.maybeThrowError(this.last_req);
289+ return JSON.parse(this.last_req.responseText);
290+ }
291+
292+ // Convert a options object to an url query string.
293+ // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"'
294+ function encodeOptions(options) {
295+ var buf = []
296+ if (typeof(options) == "object" && options !== null) {
297+ for (var name in options) {
298+ if (!options.hasOwnProperty(name)) continue;
299+ var value = options[name];
300+ if (name == "key" || name == "startkey" || name == "endkey") {
301+ value = toJSON(value);
302+ }
303+ buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
304+ }
305+ }
306+ if (!buf.length) {
307+ return "";
308+ }
309+ return "?" + buf.join("&");
310+ }
311+
312+ function toJSON(obj) {
313+ return obj !== null ? JSON.stringify(obj) : null;
314+ }
315+
316+ function combine(object1, object2) {
317+ if (!object2)
318+ return object1;
319+ if (!object1)
320+ return object2;
321+
322+ for (var name in object2)
323+ object1[name] = object2[name];
324+
325+ return object1;
326+ }
327+
328+
329+}
330+
331+CouchDB.PORT_NUMBER = 5984; // default
332+
333+// this is the XMLHttpRequest object from last request made by the following
334+// CouchDB.* functions (except for calls to request itself).
335+// Use this from callers to check HTTP status or header values of requests.
336+CouchDB.last_req = null;
337+
338+
339+CouchDB.allDbs = function() {
340+ CouchDB.last_req = CouchDB.request("GET", "/_all_dbs");
341+ CouchDB.maybeThrowError(CouchDB.last_req);
342+ return JSON.parse(CouchDB.last_req.responseText);
343+}
344+
345+CouchDB.allDesignDocs = function() {
346+ var ddocs = {}, dbs = CouchDB.allDbs();
347+ for (var i=0; i < dbs.length; i++) {
348+ var db = new CouchDB(dbs[i]);
349+ ddocs[dbs[i]] = db.designDocs();
350+ };
351+ return ddocs;
352+};
353+
354+CouchDB.getVersion = function() {
355+ CouchDB.last_req = CouchDB.request("GET", "/");
356+ CouchDB.maybeThrowError(CouchDB.last_req);
357+ return JSON.parse(CouchDB.last_req.responseText).version;
358+}
359+
360+CouchDB.replicate = function(source, target, rep_options) {
361+ rep_options = rep_options || {};
362+ var headers = rep_options.headers || {};
363+ CouchDB.last_req = CouchDB.request("POST", "/_replicate", {
364+ headers: headers,
365+ body: JSON.stringify({source: source, target: target})
366+ });
367+ CouchDB.maybeThrowError(CouchDB.last_req);
368+ return JSON.parse(CouchDB.last_req.responseText);
369+}
370+
371+CouchDB.request = function(method, uri, options) {
372+ options = options || {};
373+ var req = null;
374+ if (typeof(XMLHttpRequest) != "undefined") {
375+ req = new XMLHttpRequest();
376+ } else if (typeof(ActiveXObject) != "undefined") {
377+ req = new ActiveXObject("Microsoft.XMLHTTP");
378+ } else {
379+ throw new Error("No XMLHTTPRequest support detected");
380+ }
381+ req.open(method, "http://localhost:" + CouchDB.PORT_NUMBER + uri, false);
382+ if (options.headers) {
383+ var headers = options.headers;
384+ for (var headerName in headers) {
385+ if (!headers.hasOwnProperty(headerName)) continue;
386+ req.setRequestHeader(headerName, headers[headerName]);
387+ }
388+ }
389+ req.send(options.body || "");
390+ return req;
391+}
392+
393+CouchDB.requestStats = function(module, key, test) {
394+ var query_arg = "";
395+ if(test !== null) {
396+ query_arg = "?flush=true";
397+ }
398+
399+ var stat = CouchDB.request("GET", "/_stats/" + module + "/" + key + query_arg).responseText;
400+ return JSON.parse(stat)[module][key];
401+}
402+
403+CouchDB.uuids_cache = [];
404+
405+CouchDB.newUuids = function(n) {
406+ if (CouchDB.uuids_cache.length >= n) {
407+ var uuids = CouchDB.uuids_cache.slice(CouchDB.uuids_cache.length - n);
408+ if(CouchDB.uuids_cache.length - n == 0) {
409+ CouchDB.uuids_cache = [];
410+ } else {
411+ CouchDB.uuids_cache =
412+ CouchDB.uuids_cache.slice(0, CouchDB.uuids_cache.length - n);
413+ }
414+ return uuids;
415+ } else {
416+ CouchDB.last_req = CouchDB.request("GET", "/_uuids?count=" + (100 + n));
417+ CouchDB.maybeThrowError(CouchDB.last_req);
418+ var result = JSON.parse(CouchDB.last_req.responseText);
419+ CouchDB.uuids_cache =
420+ CouchDB.uuids_cache.concat(result.uuids.slice(0, 100));
421+ return result.uuids.slice(100);
422+ }
423+}
424+
425+CouchDB.maybeThrowError = function(req) {
426+ if (req.status >= 400) {
427+ try {
428+ var result = JSON.parse(req.responseText);
429+ } catch (ParseError) {
430+ var result = {error:"unknown", reason:req.responseText};
431+ }
432+ throw result;
433+ }
434+}
435+
436+CouchDB.params = function(options) {
437+ options = options || {};
438+ var returnArray = [];
439+ for(var key in options) {
440+ var value = options[key];
441+ returnArray.push(key + "=" + value);
442+ }
443+ return returnArray.join("&");
444+}
445
446=== added file 'chrome/content/dbus.sh'
447--- chrome/content/dbus.sh 1970-01-01 00:00:00 +0000
448+++ chrome/content/dbus.sh 2009-07-07 10:47:14 +0000
449@@ -0,0 +1,11 @@
450+#!/bin/bash
451+OUT=$1
452+PORT=$(dbus-send --session --dest=org.desktopcouch.CouchDB --print-reply --type=method_call / org.desktopcouch.CouchDB.getPort 2>/dev/null | grep int32 | awk '{print $2}' )
453+if [ -z "$PORT" ]; then
454+ # D-Bus call failed for some reason, so use default port
455+ echo 5984 > $OUT
456+else
457+ echo $PORT > $OUT
458+fi
459+
460+
461
462=== added file 'chrome/content/sync.js'
463--- chrome/content/sync.js 1970-01-01 00:00:00 +0000
464+++ chrome/content/sync.js 2009-07-08 01:24:33 +0000
465@@ -0,0 +1,524 @@
466+/* Lots and lots of debugging information */
467+var Bindwood = {
468+ bookmarksService: Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"]
469+ .getService(Components.interfaces.nsINavBookmarksService),
470+ uuidService: Components.classes["@mozilla.org/uuid-generator;1"]
471+ .getService(Components.interfaces.nsIUUIDGenerator),
472+ annotationService: Components.classes["@mozilla.org/browser/annotation-service;1"]
473+ .getService(Components.interfaces.nsIAnnotationService),
474+ consoleService: Components.classes["@mozilla.org/consoleservice;1"]
475+ .getService(Components.interfaces.nsIConsoleService),
476+ historyService: Components.classes["@mozilla.org/browser/nav-history-service;1"]
477+ .getService(Components.interfaces.nsINavHistoryService),
478+ ioService: Components.classes["@mozilla.org/network/io-service;1"]
479+ .getService(Components.interfaces.nsIIOService),
480+
481+ annotationKey: "bindwood/uuid",
482+ uuidItemIdMap: {},
483+
484+ generateUUIDString: function() {
485+ return Bindwood.uuidService.generateUUID().toString();
486+ },
487+
488+ annotateItemWithUUID: function(itemId) {
489+ var uuid = Bindwood.generateUUIDString();
490+ Bindwood.annotationService.setItemAnnotation(itemId, Bindwood.annotationKey, uuid, 0, Bindwood.annotationService.EXPIRE_NEVER);
491+ // Whenever we create a new UUID, stash it and the itemId in
492+ // our local cache.
493+ Bindwood.uuidItemIdMap[uuid] = itemId;
494+ return uuid;
495+ },
496+
497+ itemIdForUUID: function(uuid) {
498+ // First, try to look it up in our local cache, barring that
499+ // (which shouldn't happen), look it up slowly.
500+ var itemId = Bindwood.uuidItemIdMap[uuid];
501+
502+ if (!itemId) {
503+ var items = Bindwood.annotationService.getItemsWithAnnotation(Bindwood.annotationKey, {});
504+ for (var i = 0; i < items.length; i++) {
505+ if (Bindwood.annotationService.getItemAnnotation(items[i], Bindwood.annotationKey) == uuid) {
506+ Bindwood.uuidItemIdMap[uuid] = itemId = items[i];
507+ break;
508+ }
509+ }
510+ }
511+ return itemId;
512+ },
513+
514+ uuidForItemId: function(itemId) {
515+ // Try to look up the uuid, and failing that, assign a new one
516+ // and return it.
517+ var uuid;
518+ var found = false;
519+ try {
520+ uuid = Bindwood.annotationService.getItemAnnotation(itemId, Bindwood.annotationKey);
521+ found = true;
522+ } catch(e) {
523+ uuid = Bindwood.annotateItemWithUUID(itemId);
524+ }
525+
526+ return uuid;
527+ },
528+
529+ writeMessage: function(aMessage) {
530+ // convenience method for logging. Way better than alert()s.
531+ Bindwood.consoleService.logStringMessage("Bindwood: " + aMessage);
532+ },
533+
534+ writeError: function(aMessage, e) {
535+ Bindwood.writeMessage(aMessage + "message: '" + e.message + "', reason: '" + e.reason + "', description: '" + e.description + "', error: '" + e.error + "'");
536+ },
537+
538+
539+
540+ init: function() {
541+ // Start the process and de-register ourself
542+ // http://forums.mozillazine.org/viewtopic.php?f=19&t=657911&start=0
543+ // It ensures that we're only running that code on the first window.
544+
545+ if(Components.classes["@mozilla.org/appshell/window-mediator;1"]
546+ .getService(Components.interfaces.nsIWindowMediator)
547+ .getEnumerator("").getNext() == window) {
548+ Bindwood.getCouchPortNumber(Bindwood.startProcess);
549+ }
550+ },
551+
552+ getCouchPortNumber: function(continueFunction) {
553+ // find the desktop Couch port number by making a D-Bus call
554+ // we call D-Bus by shelling out to a bash script which calls
555+ // it for us, and writes the port number into a temp file
556+
557+ // find OS temp dir to put the tempfile in
558+ // https://developer.mozilla.org/index.php?title=File_I%2F%2FO#Getting_special_files
559+ var tmpdir = Components.classes["@mozilla.org/file/directory_service;1"]
560+ .getService(Components.interfaces.nsIProperties)
561+ .get("TmpD", Components.interfaces.nsIFile);
562+ // create a randomly named tempfile in the tempdir
563+ var tmpfile = Components.classes["@mozilla.org/file/local;1"]
564+ .createInstance(Components.interfaces.nsILocalFile);
565+ tmpfile.initWithPath(tmpdir.path + "/desktopcouch." + Math.random());
566+ tmpfile.createUnique(tmpfile.NORMAL_FILE_TYPE, 0600);
567+ Bindwood.writeMessage("Tempfile for Couch port number: " + tmpfile.path);
568+
569+ // find the D-Bus bash script, which is in our extension folder
570+ var MY_ID = "bindwood@ubuntu.com";
571+ var em = Components.classes["@mozilla.org/extensions/manager;1"].
572+ getService(Components.interfaces.nsIExtensionManager);
573+ var dbus_script = em.getInstallLocation(MY_ID).getItemFile(MY_ID,
574+ "chrome/content/dbus.sh");
575+ // create an nsILocalFile for the executable
576+ var nsifile = Components.classes["@mozilla.org/file/local;1"]
577+ .createInstance(Components.interfaces.nsILocalFile);
578+ nsifile.initWithPath(dbus_script.path);
579+
580+ // create an nsIProcess2 to execute this bash script
581+ var process = Components.classes["@mozilla.org/process/util;1"]
582+ .createInstance(Components.interfaces.nsIProcess2);
583+ process.init(nsifile);
584+
585+ // Run the process, passing the tmpfile path
586+ var args = [tmpfile.path];
587+ process.runAsync(args, args.length, {
588+ observe: function(process, finishState, data) {
589+ var port = 5984;
590+ if (finishState == "process-finished") {
591+ // read temp file to find port number
592+ // https://developer.mozilla.org/en/Code_snippets/File_I%2f%2fO#Reading_from_a_file
593+ var data = "";
594+ var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].
595+ createInstance(Components.interfaces.nsIFileInputStream);
596+ var cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
597+ createInstance(Components.interfaces.nsIConverterInputStream);
598+ fstream.init(tmpfile, -1, 0, 0);
599+ cstream.init(fstream, "UTF-8", 0, 0);
600+ let (str = {}) {
601+ cstream.readString(-1, str); // read the whole file and put it in str.value
602+ data = str.value;
603+ }
604+ cstream.close(); // this closes fstream
605+ data = data.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
606+ if (/^[0-9]+$/.test(data)) {
607+ port = data;
608+ } else {
609+ Bindwood.writeMessage("D-Bus port data is not a number (" + data + ")");
610+ }
611+ } else {
612+ // fall back to system CouchDB
613+ Bindwood.writeMessage("D-Bus port find failed");
614+ }
615+ tmpfile.remove(false);
616+ continueFunction(port);
617+ }
618+ });
619+ },
620+
621+ startProcess: function(couchPortNumber) {
622+ Bindwood.writeMessage("Starting process with Couch on port " + couchPortNumber);
623+ CouchDB.PORT_NUMBER = couchPortNumber;
624+ try {
625+ Bindwood.pushBookmarks();
626+ } catch(e) {
627+ Bindwood.writeError("Error when calling pushBookmarks: ", e);
628+ }
629+ Bindwood.createView();
630+ Bindwood.pullBookmarks();
631+ },
632+
633+ createView: function() {
634+ var DDID = "_design/views";
635+ var couch = new CouchDB('bookmarks');
636+ var current_doc;
637+ try {
638+ current_doc = couch.open(DDID);
639+ if (current_doc !== null) {
640+ Bindwood.writeMessage("View already exists; leaving it alone");
641+ } else {
642+ new_doc = {
643+ _id: DDID,
644+ views: {
645+ display: {
646+ map: "function(doc) { " +
647+ "var scheme = doc.uri.split(':',1)[0]; " +
648+ "var uri; " +
649+ "if (scheme == 'http' || scheme == 'https') {" +
650+ "uri = doc.uri.split('/')[2];" +
651+ "if (uri.length < 30) {" +
652+ " uri += '/' + " +
653+ "doc.uri.split('/',4)[3].substr(0,30-uri.length) + '...';" +
654+ "}" +
655+ "} else {" +
656+ "uri = scheme + ' URL';" +
657+ "}" +
658+ "emit(doc.title, uri);" +
659+ "}"
660+ }
661+ }
662+ };
663+ try {
664+ couch.save(new_doc);
665+ } catch(e) {
666+ Bindwood.writeError("Problem saving view: ", e);
667+ }
668+ }
669+ } catch(e) {
670+ // some kind of error fetching the existing design doc
671+ Bindwood.writeError("Problem checking for view: ", e);
672+ }
673+ },
674+
675+ pushBookmarks: function() {
676+ // Prime the pump, so to speak, by uploading all our local
677+ // bookmarks to CouchDB (if they're not there already).
678+ var couch = new CouchDB('bookmarks');
679+ // Create the DB if it doesn't already exist
680+ try {
681+ couch.createDb();
682+ } catch (e) {
683+ Bindwood.writeError("Error when creating database in pushBookmarks (file_exists is OK here): ", e);
684+ }
685+
686+ Bindwood.pushBookmarksFromList(Bindwood.bookmarksService.toolbarFolder,
687+ "toolbarFolder", couch);
688+ Bindwood.pushBookmarksFromList(Bindwood.bookmarksService.bookmarksMenuFolder,
689+ "bookmarksMenuFolder", couch);
690+ },
691+
692+ getBookmarksFromList: function(bookmarksList) {
693+ var retval = [];
694+ var options = Bindwood.historyService.getNewQueryOptions();
695+ var query = Bindwood.historyService.getNewQuery();
696+ query.setFolders([bookmarksList], 1);
697+ var result = Bindwood.historyService.executeQuery(query, options);
698+ var rootNode = result.root;
699+ rootNode.containerOpen = true;
700+ for (var i=0; i<rootNode.childCount; i++) {
701+ var node = rootNode.getChild(i);
702+ if (Bindwood.bookmarksService.getItemType(node.itemId) !=
703+ Bindwood.bookmarksService.TYPE_BOOKMARK) {
704+ continue;
705+ }
706+
707+ var title = Bindwood.bookmarksService.getItemTitle(node.itemId);
708+ try {
709+ var metadata = Bindwood.bookmarksService.getBookmarkURI(node.itemId);
710+ } catch(e) {
711+ Bindwood.writeError("problem fetching metadata for bookmark '" + title + "': ", e);
712+ continue;
713+ }
714+ var uuid = Bindwood.uuidForItemId(node.itemId);
715+ retval.push({
716+ title: title,
717+ metadata: metadata,
718+ uuid: uuid,
719+ id: node.itemId});
720+ }
721+ rootNode.containerOpen = false;
722+ return retval;
723+ },
724+
725+ pushBookmarksFromList: function(bookmarksList, bookmarksListName, db) {
726+ var bookmarkData = Bindwood.getBookmarksFromList(bookmarksList);
727+ for (var i = 0; i < bookmarkData.length; i++) {
728+ // find this bookmark in CouchDB
729+ var uuid = Bindwood.uuidForItemId(bookmarkData[i].id);
730+ var uri = bookmarkData[i].metadata.spec;
731+ var title = bookmarkData[i].title;
732+
733+ var results = db.query(function(doc) {
734+ if (doc.application_annotations &&
735+ doc.application_annotations.Firefox &&
736+ doc.application_annotations.Firefox.uuid) {
737+ emit(doc.application_annotations.Firefox.uuid, doc);
738+ }
739+ }, null, {
740+ startkey: uuid, endkey: uuid
741+ });
742+
743+ if (results.rows.length === 0) {
744+ // this bookmark is not in CouchDB, so write it
745+ var record = {
746+ record_type: "http://example.com/bookmark",
747+ uri: uri,
748+ title: title,
749+ application_annotations: {
750+ Firefox: {
751+ uuid: uuid,
752+ list: bookmarksListName
753+ }
754+ }
755+ };
756+ try {
757+ db.save(record);
758+ } catch(e) {
759+ Bindwood.writeError("Problem saving bookmark to CouchDB; bookmark is " + JSON.stringify(record) + ": ", e);
760+ }
761+ } else {
762+ // bookmark is already in CouchDB, so do nothing
763+ }
764+ }
765+ },
766+
767+ pullBookmarks: function() {
768+ var couch = new CouchDB('bookmarks');
769+ // Fetch all bookmark documents from the database
770+ // the query function is evaluated by Couch, which doesn't know
771+ // what Bindwood.RECORD_TYPE is, so we string-encode it first to
772+ // include the literal value
773+ try {
774+ var rows = couch.query(function (doc) {
775+ if (doc.record_type == "http://example.com/bookmark") {
776+ emit(doc._id,doc);
777+ }
778+ });
779+ } catch(e) {
780+ Bindwood.writeError("Problem fetching all bookmarks from Couch:", e);
781+ }
782+ for (var i = 0; i < rows.rows.length; i++) {
783+ var recordid = rows.rows[i].id;
784+ var bm = rows.rows[i].value;
785+ if (bm.application_annotations &&
786+ bm.application_annotations.Firefox &&
787+ bm.application_annotations.Firefox.uuid) {
788+ // this bookmark has a uuid, so check its values haven't changed
789+ // find the bookmark with this uuid
790+ var itemId = Bindwood.itemIdForUUID(
791+ bm.application_annotations.Firefox.uuid);
792+ if (!itemId) {
793+ // This bookmark has a uuid, but it's not one of ours.
794+ // We need to work out whether (a) it's the same as one
795+ // of ours but with a different uuid (so we need to
796+ // make the uuids the same), or (b) it's a new one
797+ // that happens to have been created on a different
798+ // machine.
799+ // For the moment, punt on this.
800+ } else {
801+ var title = Bindwood.bookmarksService.getItemTitle(itemId);
802+ var metadata = Bindwood.bookmarksService.getBookmarkURI(itemId);
803+ if (title != bm.title) {
804+ Bindwood.bookmarksService.setItemTitle(itemId, bm.title);
805+ }
806+ if (metadata.spec != bm.uri) {
807+ try {
808+ metadata = Bindwood.ioService.newURI(bm.uri, null, null);
809+ } catch(e) {
810+ Bindwood.writeError("Problem creating a new URI for bookmark", e);
811+ }
812+ Bindwood.bookmarksService.changeBookmarkURI(itemId, metadata);
813+ }
814+ }
815+ } else {
816+ // this bookmark has no uuid, so create it from fresh in Firefox
817+ var list;
818+ if (bm.application_annotations &&
819+ bm.application_annotations.Firefox &&
820+ bm.application_annotations.Firefox.list) {
821+ switch (bm.application_annotations.Firefox.list) {
822+ case "toolbarFolder":
823+ list = Bindwood.bookmarksService.toolbarFolder;
824+ break;
825+ case "bookmarksMenuFolder":
826+ list = Bindwood.bookmarksService.bookmarksMenuFolder;
827+ break;
828+ default:
829+ list = Bindwood.bookmarksService.toolbarFolder;
830+ break;
831+ }
832+ } else {
833+ list = Bindwood.bookmarksService.toolbarFolder;
834+ }
835+ var metadata = Bindwood.ioService.newURI(bm.uri, null, null);
836+
837+ var itemId = Bindwood.bookmarksService.insertBookmark(list,
838+ metadata, -1, bm.title);
839+ // and then write the new uuid back to the record
840+ var uuid = Bindwood.uuidForItemId(itemId);
841+ var doc = couch.open(recordid);
842+ if (!doc.application_annotations) {
843+ doc.application_annotations = {};
844+ }
845+ if (!doc.application_annotations.Firefox) {
846+ doc.application_annotations.Firefox = {};
847+ }
848+ doc.application_annotations.Firefox.uuid = uuid;
849+ couch.save(doc);
850+ doc.application_annotations.Firefox.guid = guid;
851+ try {
852+ couch.save(doc);
853+ } catch(e) {
854+ Bindwood.writeError("Problem writing record for new bookmark: ",e);
855+ }
856+ }
857+ }
858+ // reschedule ourself
859+ setTimeout(Bindwood.pullBookmarks, 30000);
860+ },
861+
862+ Observer: {
863+ // An nsINavBookmarkObserver
864+ onItemAdded: function(aItemId, aFolder, aIndex) {
865+ // A bookmark has been added, so we create a blank entry
866+ // in Couch with our local itemId attached.
867+ netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
868+
869+ var couch = new CouchDB('bookmarks');
870+
871+ var uuid = Bindwood.uuidForItemId(aItemId);
872+
873+ var list;
874+ switch(aFolder) {
875+ case Bindwood.bookmarksService.toolbarFolder:
876+ list = "toolbarFolder";
877+ break;
878+ case Bindwood.bookmarksService.bookmarksMenuFolder:
879+ list = "bookmarksMenuFolder";
880+ break;
881+ default:
882+ list = "toolbarFolder";
883+ break;
884+ }
885+
886+ var doc = {
887+ record_type: "http://example.com/bookmark",
888+ application_annotations: {
889+ Firefox: {
890+ uuid: uuid,
891+ list: list
892+ }
893+ }
894+ };
895+
896+ try {
897+ var result = couch.save(doc);
898+ } catch(e) {
899+ Bindwood.writeError("Problem saving new bookmark to Couch", e);
900+ }
901+ },
902+ onBeforeItemRemoved: function(aItemId) {
903+ // A bookmark has been removed. This is called before it's
904+ // been removed locally, though we're passed the itemId,
905+ // which we use to delete from Couch.
906+ netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
907+
908+ var couch = new CouchDB('bookmarks');
909+
910+ var uuid = Bindwood.uuidForItemId(aItemId);
911+
912+ var results = couch.query(function(doc) {
913+ if (doc.application_annotations &&
914+ doc.application_annotations.Firefox &&
915+ doc.application_annotations.Firefox.uuid) {
916+ emit(doc.application_annotations.Firefox.uuid, doc);
917+ }
918+ }, null, {
919+ startkey: uuid, endkey: uuid
920+ });
921+
922+ if (results.rows.length === 0) {
923+ Bindwood.writeMessage("A bookmark was deleted, but this bookmark isn't in CouchDB. This isn't supposed to happen.");
924+ return;
925+ }
926+
927+ var doc = couch.open(results.rows[0].id);
928+
929+ try {
930+ var result = couch.deleteDoc(doc);
931+ // Also remove from our local cache and remove
932+ // annotation from service.
933+ delete Bindwood.uuidItemIdMap[uuid];
934+ } catch(e) {
935+ Bindwood.writeError("Problem deleting record from Couch", e);
936+ }
937+ },
938+ onItemRemoved: function(aItemId, aFolder, aIndex) {
939+ Bindwood.annotationService.removeItemAnnotation(aItemId, Bindwood.annotationKey);
940+ },
941+ onItemChanged: function(aBookmarkId, aProperty, aIsAnnotationProperty, aValue) {
942+ // A property of a bookmark has changed. On multiple
943+ // property updates, this will be called multiple times,
944+ // once per property (i.e., for title and URI)
945+ netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
946+
947+ var couch = new CouchDB('bookmarks');
948+
949+ var uuid = Bindwood.uuidForItemId(aBookmarkId);
950+
951+ var results = couch.query(function(doc) {
952+ if (doc.application_annotations &&
953+ doc.application_annotations.Firefox &&
954+ doc.application_annotations.Firefox.uuid) {
955+ emit(doc.application_annotations.Firefox.uuid, doc);
956+ }
957+ }, null, {
958+ startkey: uuid, endkey: uuid
959+ });
960+
961+ if (results.rows.length === 0) {
962+ Bindwood.writeMessage("a bookmark has changed, but this bookmark isn't in CouchDB. this isn't supposed to happen.");
963+ return;
964+ }
965+
966+ var doc = couch.open(results.rows[0].id);
967+ doc[aProperty.toString()] = aValue.toString();
968+
969+ try {
970+ var result = couch.save(doc);
971+ } catch(e) {
972+ Bindwood.writeError("Problem saving updated bookmark to Couch", e);
973+ }
974+ },
975+
976+ onBeginUpdateBatch: function() {},
977+ onEndUpdateBatch: function() {},
978+ onItemVisited: function(aBookmarkId, aVisitID, time) {},
979+ onItemMoved: function(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex) {},
980+ QueryInterface: function(iid) {
981+ if (iid.equals(Components.interfaces.nsINavBookmarkObserver) ||
982+ iid.equals(Components.interfaces.nsINavBookmarkObserver_MOZILLA_1_9_1_ADDITIONS) ||
983+ iid.equals(Components.interfaces.nsISupports)) {
984+ return this;
985+ }
986+ throw Cr.NS_ERROR_NO_INTERFACE;
987+ }
988+ }
989+};
990
991=== added file 'chrome/content/tutorial.html'
992--- chrome/content/tutorial.html 1970-01-01 00:00:00 +0000
993+++ chrome/content/tutorial.html 2009-07-02 13:41:07 +0000
994@@ -0,0 +1,8 @@
995+<html>
996+ <head>
997+ <title>BindWood Tutorial</title>
998+ </head>
999+ <body>
1000+ This is BindWood.
1001+ </body>
1002+</html>
1003
1004=== added directory 'chrome/locale'
1005=== added directory 'chrome/locale/en'
1006=== added file 'chrome/locale/en/bindwood.dtd'
1007=== modified file 'install.rdf'
1008--- install.rdf 2009-06-30 13:47:50 +0000
1009+++ install.rdf 2009-07-06 20:00:16 +0000
1010@@ -3,7 +3,7 @@
1011 xmlns:em="http://www.mozilla.org/2004/em-rdf#">
1012
1013 <Description about="urn:mozilla:install-manifest">
1014- <em:id>ubuntuone-firefox-bookmark-sync@ubuntu.com</em:id>
1015+ <em:id>bindwood@ubuntu.com</em:id>
1016 <em:version>1.0</em:version>
1017 <em:type>2</em:type>
1018
1019@@ -18,9 +18,9 @@
1020 </em:targetApplication>
1021
1022 <!-- Front End MetaData -->
1023- <em:name>UbuntuOne Bookmarks Sync</em:name>
1024- <em:description>An extension to synchronize your bookmarks to UbuntuOne.</em:description>
1025+ <em:name>Bindwood</em:name>
1026+ <em:description>An extension to synchronize your bookmarks to a local CouchDB.</em:description>
1027 <em:creator>Zachery Bir</em:creator>
1028- <em:homepageURL>http://launchpad.net/ubuntuone-firefox-bookmark-sync</em:homepageURL>
1029+ <em:homepageURL>http://launchpad.net/bindwood</em:homepageURL>
1030 </Description>
1031 </RDF>

Subscribers

People subscribed via source and target branches

to all changes: