Merge lp:~sil/bindwood/design-view into lp:~urbanape/bindwood/trunk

Proposed by Stuart Langridge
Status: Merged
Merge reported by: Zachery Bir
Merged at revision: not available
Proposed branch: lp:~sil/bindwood/design-view
Merge into: lp:~urbanape/bindwood/trunk
Diff against target: None lines
To merge this branch: bzr merge lp:~sil/bindwood/design-view
Reviewer Review Type Date Requested Status
Zachery Bir Approve
Review via email: mp+8267@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Stuart Langridge (sil) wrote :

Add a design document, providing a more convenient view on your bookmarks

lp:~sil/bindwood/design-view updated
13. By Stuart Langridge

Add more debug messages

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

Looks good and has been merged into lp:~urbanape/bindwood/dbus-and-local-uuids

review: Approve

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-03 02:22:26 +0000
14@@ -1,15 +1,27 @@
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+ <script type="application/x-javascript" src="chrome://bindwood/content/couch.js" />
27+ <script type="application/x-javascript" src="chrome://bindwood/content/sync.js" />
28+
29+ <window id="main-window">
30+ <script type="application/x-javascript">
31+ window.addEventListener("load", Bindwood.init, true);
32+
33+ var bmsvc = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"].getService(Components.interfaces.nsINavBookmarksService);
34+ window.addEventListener("load", function() { bmsvc.addObserver(Bindwood.Observer, false); }, false);
35+ </script>
36+ </window>
37+
38+ <menupopup id="bookmarksMenuPopup">
39+ <menuitem id="BindwoodSync"
40 label="Sync Bookmarks (well, alert)"
41- insertbefore="javascriptConsole"
42- oncommand="alert('syncing...');" />
43+ oncommand="pullBookmarks(false);"
44+ insertafter="bookmarksShowAll" />
45 </menupopup>
46
47 </overlay>
48
49=== added file 'chrome/content/couch.js'
50--- chrome/content/couch.js 1970-01-01 00:00:00 +0000
51+++ chrome/content/couch.js 2009-07-06 10:49:17 +0000
52@@ -0,0 +1,396 @@
53+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
54+// use this file except in compliance with the License. You may obtain a copy
55+// of the License at
56+//
57+// http://www.apache.org/licenses/LICENSE-2.0
58+//
59+// Unless required by applicable law or agreed to in writing, software
60+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
61+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
62+// License for the specific language governing permissions and limitations under
63+// the License.
64+
65+// A simple class to represent a database. Uses XMLHttpRequest to interface with
66+// the CouchDB server.
67+
68+function CouchDB(name, httpHeaders) {
69+ this.name = name;
70+ this.uri = "/" + encodeURIComponent(name) + "/";
71+
72+ // The XMLHttpRequest object from the most recent request. Callers can
73+ // use this to check result http status and headers.
74+ this.last_req = null;
75+
76+ this.request = function(method, uri, requestOptions) {
77+ requestOptions = requestOptions || {}
78+ requestOptions.headers = combine(requestOptions.headers, httpHeaders)
79+ return CouchDB.request(method, uri, requestOptions);
80+ }
81+
82+ // Creates the database on the server
83+ this.createDb = function() {
84+ this.last_req = this.request("PUT", this.uri);
85+ CouchDB.maybeThrowError(this.last_req);
86+ return JSON.parse(this.last_req.responseText);
87+ }
88+
89+ // Deletes the database on the server
90+ this.deleteDb = function() {
91+ this.last_req = this.request("DELETE", this.uri);
92+ if (this.last_req.status == 404)
93+ return false;
94+ CouchDB.maybeThrowError(this.last_req);
95+ return JSON.parse(this.last_req.responseText);
96+ }
97+
98+ // Save a document to the database
99+ this.save = function(doc, options) {
100+ if (doc._id == undefined)
101+ doc._id = CouchDB.newUuids(1)[0];
102+
103+ this.last_req = this.request("PUT", this.uri +
104+ encodeURIComponent(doc._id) + encodeOptions(options),
105+ {body: JSON.stringify(doc)});
106+ CouchDB.maybeThrowError(this.last_req);
107+ var result = JSON.parse(this.last_req.responseText);
108+ doc._rev = result.rev;
109+ return result;
110+ }
111+
112+ // Open a document from the database
113+ this.open = function(docId, options) {
114+ this.last_req = this.request("GET", this.uri + encodeURIComponent(docId) + encodeOptions(options));
115+ if (this.last_req.status == 404)
116+ return null;
117+ CouchDB.maybeThrowError(this.last_req);
118+ return JSON.parse(this.last_req.responseText);
119+ }
120+
121+ // Deletes a document from the database
122+ this.deleteDoc = function(doc) {
123+ this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id) + "?rev=" + doc._rev);
124+ CouchDB.maybeThrowError(this.last_req);
125+ var result = JSON.parse(this.last_req.responseText);
126+ doc._rev = result.rev; //record rev in input document
127+ doc._deleted = true;
128+ return result;
129+ }
130+
131+ // Deletes an attachment from a document
132+ this.deleteDocAttachment = function(doc, attachment_name) {
133+ this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id) + "/" + attachment_name + "?rev=" + doc._rev);
134+ CouchDB.maybeThrowError(this.last_req);
135+ var result = JSON.parse(this.last_req.responseText);
136+ doc._rev = result.rev; //record rev in input document
137+ return result;
138+ }
139+
140+ this.bulkSave = function(docs, options) {
141+ // first prepoulate the UUIDs for new documents
142+ var newCount = 0
143+ for (var i=0; i<docs.length; i++) {
144+ if (docs[i]._id == undefined)
145+ newCount++;
146+ }
147+ var newUuids = CouchDB.newUuids(docs.length);
148+ var newCount = 0
149+ for (var i=0; i<docs.length; i++) {
150+ if (docs[i]._id == undefined)
151+ docs[i]._id = newUuids.pop();
152+ }
153+ var json = {"docs": docs};
154+ // put any options in the json
155+ for (var option in options) {
156+ json[option] = options[option];
157+ }
158+ this.last_req = this.request("POST", this.uri + "_bulk_docs", {
159+ body: JSON.stringify(json)
160+ });
161+ if (this.last_req.status == 417) {
162+ return {errors: JSON.parse(this.last_req.responseText)};
163+ }
164+ else {
165+ CouchDB.maybeThrowError(this.last_req);
166+ var results = JSON.parse(this.last_req.responseText);
167+ for (var i = 0; i < docs.length; i++) {
168+ if(results[i].rev)
169+ docs[i]._rev = results[i].rev;
170+ }
171+ return results;
172+ }
173+ }
174+
175+ this.ensureFullCommit = function() {
176+ this.last_req = this.request("POST", this.uri + "_ensure_full_commit");
177+ CouchDB.maybeThrowError(this.last_req);
178+ return JSON.parse(this.last_req.responseText);
179+ }
180+
181+ // Applies the map function to the contents of database and returns the results.
182+ this.query = function(mapFun, reduceFun, options, keys) {
183+ var body = {language: "javascript"};
184+ if(keys) {
185+ body.keys = keys ;
186+ }
187+ if (typeof(mapFun) != "string")
188+ mapFun = mapFun.toSource ? mapFun.toSource() : "(" + mapFun.toString() + ")";
189+ body.map = mapFun;
190+ if (reduceFun != null) {
191+ if (typeof(reduceFun) != "string")
192+ reduceFun = reduceFun.toSource ? reduceFun.toSource() : "(" + reduceFun.toString() + ")";
193+ body.reduce = reduceFun;
194+ }
195+ if (options && options.options != undefined) {
196+ body.options = options.options;
197+ delete options.options;
198+ }
199+ this.last_req = this.request("POST", this.uri + "_temp_view" + encodeOptions(options), {
200+ headers: {"Content-Type": "application/json"},
201+ body: JSON.stringify(body)
202+ });
203+ CouchDB.maybeThrowError(this.last_req);
204+ return JSON.parse(this.last_req.responseText);
205+ }
206+
207+ this.view = function(viewname, options, keys) {
208+ var viewParts = viewname.split('/');
209+ var viewPath = this.uri + "_design/" + viewParts[0] + "/_view/"
210+ + viewParts[1] + encodeOptions(options);
211+ if(!keys) {
212+ this.last_req = this.request("GET", viewPath);
213+ } else {
214+ this.last_req = this.request("POST", viewPath, {
215+ headers: {"Content-Type": "application/json"},
216+ body: JSON.stringify({keys:keys})
217+ });
218+ }
219+ if (this.last_req.status == 404)
220+ return null;
221+ CouchDB.maybeThrowError(this.last_req);
222+ return JSON.parse(this.last_req.responseText);
223+ }
224+
225+ // gets information about the database
226+ this.info = function() {
227+ this.last_req = this.request("GET", this.uri);
228+ CouchDB.maybeThrowError(this.last_req);
229+ return JSON.parse(this.last_req.responseText);
230+ }
231+
232+ this.allDocs = function(options,keys) {
233+ if(!keys) {
234+ this.last_req = this.request("GET", this.uri + "_all_docs" + encodeOptions(options));
235+ } else {
236+ this.last_req = this.request("POST", this.uri + "_all_docs" + encodeOptions(options), {
237+ headers: {"Content-Type": "application/json"},
238+ body: JSON.stringify({keys:keys})
239+ });
240+ }
241+ CouchDB.maybeThrowError(this.last_req);
242+ return JSON.parse(this.last_req.responseText);
243+ }
244+
245+ this.designDocs = function() {
246+ return this.allDocs({startkey:"_design", endkey:"_design0"});
247+ };
248+
249+ this.allDocsBySeq = function(options,keys) {
250+ var req = null;
251+ if(!keys) {
252+ req = this.request("GET", this.uri + "_all_docs_by_seq" + encodeOptions(options));
253+ } else {
254+ req = this.request("POST", this.uri + "_all_docs_by_seq" + encodeOptions(options), {
255+ headers: {"Content-Type": "application/json"},
256+ body: JSON.stringify({keys:keys})
257+ });
258+ }
259+ CouchDB.maybeThrowError(req);
260+ return JSON.parse(req.responseText);
261+ }
262+
263+ this.compact = function() {
264+ this.last_req = this.request("POST", this.uri + "_compact");
265+ CouchDB.maybeThrowError(this.last_req);
266+ return JSON.parse(this.last_req.responseText);
267+ }
268+
269+ this.setDbProperty = function(propId, propValue) {
270+ this.last_req = this.request("PUT", this.uri + propId,{
271+ body:JSON.stringify(propValue)
272+ });
273+ CouchDB.maybeThrowError(this.last_req);
274+ return JSON.parse(this.last_req.responseText);
275+ }
276+
277+ this.getDbProperty = function(propId) {
278+ this.last_req = this.request("GET", this.uri + propId);
279+ CouchDB.maybeThrowError(this.last_req);
280+ return JSON.parse(this.last_req.responseText);
281+ }
282+
283+ this.setAdmins = function(adminsArray) {
284+ this.last_req = this.request("PUT", this.uri + "_admins",{
285+ body:JSON.stringify(adminsArray)
286+ });
287+ CouchDB.maybeThrowError(this.last_req);
288+ return JSON.parse(this.last_req.responseText);
289+ }
290+
291+ this.getAdmins = function() {
292+ this.last_req = this.request("GET", this.uri + "_admins");
293+ CouchDB.maybeThrowError(this.last_req);
294+ return JSON.parse(this.last_req.responseText);
295+ }
296+
297+ // Convert a options object to an url query string.
298+ // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"'
299+ function encodeOptions(options) {
300+ var buf = []
301+ if (typeof(options) == "object" && options !== null) {
302+ for (var name in options) {
303+ if (!options.hasOwnProperty(name)) continue;
304+ var value = options[name];
305+ if (name == "key" || name == "startkey" || name == "endkey") {
306+ value = toJSON(value);
307+ }
308+ buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
309+ }
310+ }
311+ if (!buf.length) {
312+ return "";
313+ }
314+ return "?" + buf.join("&");
315+ }
316+
317+ function toJSON(obj) {
318+ return obj !== null ? JSON.stringify(obj) : null;
319+ }
320+
321+ function combine(object1, object2) {
322+ if (!object2)
323+ return object1;
324+ if (!object1)
325+ return object2;
326+
327+ for (var name in object2)
328+ object1[name] = object2[name];
329+
330+ return object1;
331+ }
332+
333+
334+}
335+
336+// this is the XMLHttpRequest object from last request made by the following
337+// CouchDB.* functions (except for calls to request itself).
338+// Use this from callers to check HTTP status or header values of requests.
339+CouchDB.last_req = null;
340+
341+
342+CouchDB.allDbs = function() {
343+ CouchDB.last_req = CouchDB.request("GET", "/_all_dbs");
344+ CouchDB.maybeThrowError(CouchDB.last_req);
345+ return JSON.parse(CouchDB.last_req.responseText);
346+}
347+
348+CouchDB.allDesignDocs = function() {
349+ var ddocs = {}, dbs = CouchDB.allDbs();
350+ for (var i=0; i < dbs.length; i++) {
351+ var db = new CouchDB(dbs[i]);
352+ ddocs[dbs[i]] = db.designDocs();
353+ };
354+ return ddocs;
355+};
356+
357+CouchDB.getVersion = function() {
358+ CouchDB.last_req = CouchDB.request("GET", "/");
359+ CouchDB.maybeThrowError(CouchDB.last_req);
360+ return JSON.parse(CouchDB.last_req.responseText).version;
361+}
362+
363+CouchDB.replicate = function(source, target, rep_options) {
364+ rep_options = rep_options || {};
365+ var headers = rep_options.headers || {};
366+ CouchDB.last_req = CouchDB.request("POST", "/_replicate", {
367+ headers: headers,
368+ body: JSON.stringify({source: source, target: target})
369+ });
370+ CouchDB.maybeThrowError(CouchDB.last_req);
371+ return JSON.parse(CouchDB.last_req.responseText);
372+}
373+
374+CouchDB.request = function(method, uri, options) {
375+ Bindwood.writeMessage("CouchDB.request connecting to " + uri);
376+ options = options || {};
377+ var req = null;
378+ if (typeof(XMLHttpRequest) != "undefined") {
379+ req = new XMLHttpRequest();
380+ } else if (typeof(ActiveXObject) != "undefined") {
381+ req = new ActiveXObject("Microsoft.XMLHTTP");
382+ } else {
383+ throw new Error("No XMLHTTPRequest support detected");
384+ }
385+ req.open(method, "http://localhost:5984" + uri, false);
386+ if (options.headers) {
387+ var headers = options.headers;
388+ for (var headerName in headers) {
389+ if (!headers.hasOwnProperty(headerName)) continue;
390+ req.setRequestHeader(headerName, headers[headerName]);
391+ }
392+ }
393+ req.send(options.body || "");
394+ return req;
395+}
396+
397+CouchDB.requestStats = function(module, key, test) {
398+ var query_arg = "";
399+ if(test !== null) {
400+ query_arg = "?flush=true";
401+ }
402+
403+ var stat = CouchDB.request("GET", "/_stats/" + module + "/" + key + query_arg).responseText;
404+ return JSON.parse(stat)[module][key];
405+}
406+
407+CouchDB.uuids_cache = [];
408+
409+CouchDB.newUuids = function(n) {
410+ if (CouchDB.uuids_cache.length >= n) {
411+ var uuids = CouchDB.uuids_cache.slice(CouchDB.uuids_cache.length - n);
412+ if(CouchDB.uuids_cache.length - n == 0) {
413+ CouchDB.uuids_cache = [];
414+ } else {
415+ CouchDB.uuids_cache =
416+ CouchDB.uuids_cache.slice(0, CouchDB.uuids_cache.length - n);
417+ }
418+ return uuids;
419+ } else {
420+ CouchDB.last_req = CouchDB.request("GET", "/_uuids?count=" + (100 + n));
421+ CouchDB.maybeThrowError(CouchDB.last_req);
422+ var result = JSON.parse(CouchDB.last_req.responseText);
423+ CouchDB.uuids_cache =
424+ CouchDB.uuids_cache.concat(result.uuids.slice(0, 100));
425+ return result.uuids.slice(100);
426+ }
427+}
428+
429+CouchDB.maybeThrowError = function(req) {
430+ if (req.status >= 400) {
431+ try {
432+ var result = JSON.parse(req.responseText);
433+ } catch (ParseError) {
434+ var result = {error:"unknown", reason:req.responseText};
435+ }
436+ throw result;
437+ }
438+}
439+
440+CouchDB.params = function(options) {
441+ options = options || {};
442+ var returnArray = [];
443+ for(var key in options) {
444+ var value = options[key];
445+ returnArray.push(key + "=" + value);
446+ }
447+ return returnArray.join("&");
448+}
449
450=== added file 'chrome/content/sync.js'
451--- chrome/content/sync.js 1970-01-01 00:00:00 +0000
452+++ chrome/content/sync.js 2009-07-06 16:00:58 +0000
453@@ -0,0 +1,358 @@
454+/* Lots and lots of debugging information */
455+var Bindwood = {
456+ bookmarksService: Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"]
457+ .getService(Components.interfaces.nsINavBookmarksService),
458+
459+ writeMessage: function(aMessage) {
460+ // convenience method for logging. Way better than alert()s.
461+ var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
462+ .getService(Components.interfaces.nsIConsoleService);
463+ consoleService.logStringMessage("Bindwood: " + aMessage);
464+ },
465+
466+ writeError: function(aMessage, e) {
467+ Bindwood.writeMessage(aMessage + "message: '" + e.message + "', reason: '" + e.reason + "', description: '" + e.description + "', error: '" + e.error + "'");
468+ },
469+
470+ init: function() {
471+ // Start the process and de-register ourself
472+ // http://forums.mozillazine.org/viewtopic.php?f=19&t=657911&start=0
473+ // It ensures that we're only running that code on the first window.
474+ if(Components.classes["@mozilla.org/appshell/window-mediator;1"]
475+ .getService(Components.interfaces.nsIWindowMediator)
476+ .getEnumerator("").getNext() == window) {
477+ try {
478+ Bindwood.writeMessage("About to push bookmarks (startup)");
479+ Bindwood.pushBookmarks();
480+ } catch(e) {
481+ Bindwood.writeError("Error when calling pushBookmarks: ", e);
482+ }
483+ Bindwood.writeMessage("Creating view");
484+ Bindwood.createView();
485+ Bindwood.writeMessage("About to pull bookmarks (startup)");
486+ Bindwood.pullBookmarks();
487+ }
488+ },
489+
490+ createView: function() {
491+ var DDID = "_design/views";
492+ var couch = new CouchDB('bookmarks');
493+ var current_doc;
494+ try {
495+ current_doc = couch.open(DDID);
496+ if (current_doc !== null) {
497+ Bindwood.writeMessage("View already exists; leaving it alone");
498+ } else {
499+ new_doc = {
500+ _id: DDID,
501+ views: {
502+ display: {
503+ map: "function(doc) { " +
504+ "var scheme = doc.uri.split(':',1)[0]; " +
505+ "var uri; " +
506+ "if (scheme == 'http' || scheme == 'https') {" +
507+ "uri = doc.uri.split('/')[2];" +
508+ "if (uri.length < 30) {" +
509+ " uri += '/' + " +
510+ "doc.uri.split('/',4)[3].substr(0,30-uri.length) + '...';" +
511+ "}" +
512+ "} else {" +
513+ "uri = scheme + ' URL';" +
514+ "}" +
515+ "emit(doc.title, uri);" +
516+ "}"
517+ }
518+ }
519+ };
520+ try {
521+ couch.save(new_doc);
522+ Bindwood.writeMessage("Added view");
523+ } catch(e) {
524+ Bindwood.writeError("Problem saving view: ", e);
525+ }
526+ }
527+ } catch(e) {
528+ // some kind of error fetching the existing design doc
529+ Bindwood.writeError("Problem checking for view: ", e);
530+ }
531+ },
532+
533+ pushBookmarks: function() {
534+ // Prime the pump, so to speak, by uploading all our local
535+ // bookmarks to CouchDB (if they're not there already).
536+ Bindwood.writeMessage("in pushBookmarks");
537+ var couch = new CouchDB('bookmarks');
538+ // Create the DB if it doesn't already exist
539+ try {
540+ Bindwood.writeMessage("creating database");
541+ couch.createDb();
542+ } catch (e) {
543+ Bindwood.writeError("Error when creating database in pushBookmarks (file_exists is OK here): ", e);
544+ }
545+
546+ Bindwood.writeMessage("about to push toolbar bookmarks");
547+ Bindwood.pushBookmarksFromList(Bindwood.bookmarksService.toolbarFolder,
548+ "toolbarFolder", couch);
549+ Bindwood.writeMessage("about to push menu bookmarks");
550+ Bindwood.pushBookmarksFromList(Bindwood.bookmarksService.bookmarksMenuFolder,
551+ "bookmarksMenuFolder", couch);
552+ },
553+
554+ getBookmarksFromList: function(bookmarksList) {
555+ Bindwood.writeMessage("in getBookmarksFromList with list " + bookmarksList);
556+ var retval = [];
557+ var historyService = Components.classes["@mozilla.org/browser/nav-history-service;1"]
558+ .getService(Components.interfaces.nsINavHistoryService);
559+ var options = historyService.getNewQueryOptions();
560+ var query = historyService.getNewQuery();
561+ query.setFolders([bookmarksList], 1);
562+ var result = historyService.executeQuery(query, options);
563+ var rootNode = result.root;
564+ rootNode.containerOpen = true;
565+ for (var i=0; i<rootNode.childCount; i++) {
566+ var node = rootNode.getChild(i);
567+ if (Bindwood.bookmarksService.getItemType(node.itemId) !=
568+ Components.interfaces.nsINavBookmarksService.TYPE_BOOKMARK) {
569+ continue;
570+ }
571+
572+ var title = Bindwood.bookmarksService.getItemTitle(node.itemId);
573+ Bindwood.writeMessage("got bookmark '" + title + "'");
574+ try {
575+ var metadata = Bindwood.bookmarksService.getBookmarkURI(node.itemId);
576+ } catch(e) {
577+ Bindwood.writeError("problem fetching metadata for bookmark '" + title + "': ", e);
578+ continue;
579+ }
580+ var guid = Bindwood.bookmarksService.getItemGUID(node.itemId);
581+ Bindwood.writeMessage("got GUID");
582+ retval.push({title: title, metadata:metadata,guid:guid, id:node.itemId});
583+ }
584+ rootNode.containerOpen = false;
585+ return retval;
586+ },
587+
588+ pushBookmarksFromList: function(bookmarksList, bookmarksListName, db) {
589+ Bindwood.writeMessage("in pushBookmarksFromList");
590+ var bookmarkData = Bindwood.getBookmarksFromList(bookmarksList);
591+ for (i=0; i<bookmarkData.length; i++) {
592+ // find this bookmark in CouchDB
593+ Bindwood.writeMessage("searching for bookmark '" + bookmarkData[i].title + "' in Couch");
594+ var results = db.query(function(doc) {
595+ if (doc.application_annotations &&
596+ doc.application_annotations.Firefox &&
597+ doc.application_annotations.Firefox.guid) {
598+ emit(doc.application_annotations.Firefox.guid, doc);
599+ }
600+ }, null, {
601+ startkey: bookmarkData[i].guid, endkey: bookmarkData[i].guid
602+ });
603+
604+ if (results.rows.length === 0) {
605+ // this bookmark is not in CouchDB, so write it
606+ Bindwood.writeMessage("Didn't find it, so creating it");
607+ var record = {
608+ record_type: "http://example.com/bookmark",
609+ uri: bookmarkData[i].metadata.spec,
610+ title: bookmarkData[i].title,
611+ application_annotations: {
612+ Firefox: {
613+ guid: bookmarkData[i].guid,
614+ list: bookmarksListName
615+ }
616+ }
617+ };
618+ try {
619+ db.save(record);
620+ } catch(e) {
621+ Bindwood.writeError("Problem saving bookmark to CouchDB; bookmark is " + JSON.stringify(record) + ": ", e);
622+ }
623+ } else {
624+ // bookmark is already in CouchDB, so do nothing
625+ Bindwood.writeMessage("Did find it, so don't need to do anything");
626+ }
627+ }
628+ },
629+
630+ pullBookmarks: function() {
631+ Bindwood.writeMessage("in pullBookmarks");
632+ var couch = new CouchDB('bookmarks');
633+ // Fetch all bookmark documents from the database
634+ // the query function is evaluated by Couch, which doesn't know
635+ // what Bindwood.RECORD_TYPE is, so we string-encode it first to
636+ // include the literal value
637+ try {
638+ var rows = couch.query(function (doc) {
639+ if (doc.record_type == "http://example.com/bookmark") {
640+ emit(doc._id,doc);
641+ }
642+ });
643+ } catch(e) {
644+ Bindwood.writeError("Problem fetching all bookmarks from Couch:", e);
645+ }
646+ for (var i=0; i<rows.rows.length; i++) {
647+ var recordid = rows.rows[i].id;
648+ var bm = rows.rows[i].value;
649+ if (bm.application_annotations && bm.application_annotations.Firefox &&
650+ bm.application_annotations.Firefox.guid) {
651+ // this bookmark has a guid, so check its values haven't changed
652+ // find the bookmark with this guid
653+ var itemId = Bindwood.bookmarksService.getItemIdForGUID(
654+ bm.application_annotations.Firefox.guid);
655+ if (itemId == -1) {
656+ // This bookmark has a guid, but it's not one of ours.
657+ // We need to work out whether (a) it's the same as one
658+ // of ours but with a different guid (so we need to
659+ // make the guids the same), or (b) it's a new one
660+ // that happens to have been created on a different
661+ // machine.
662+ // For the moment, punt on this.
663+ } else {
664+ var title = Bindwood.bookmarksService.getItemTitle(itemId);
665+ var metadata = Bindwood.bookmarksService.getBookmarkURI(itemId);
666+ if (title != bm.title) {
667+ Bindwood.bookmarksService.setItemTitle(itemId, bm.title);
668+ }
669+ if (metadata.spec != bm.uri) {
670+ metadata.spec = bm.uri;
671+ Bindwood.bookmarksService.changeBookmarkURI(itemId, metadata);
672+ }
673+ }
674+ } else {
675+ // this bookmark has no guid, so create it from fresh in Firefox
676+ var list;
677+ if (bm.application_annotations && bm.application_annotations.Firefox &&
678+ bm.application_annotations.Firefox.list) {
679+ switch (bm.application_annotations.Firefox.list) {
680+ case "toolbarFolder":
681+ list = Bindwood.bookmarksService.toolbarFolder;
682+ break;
683+ case "bookmarksMenuFolder":
684+ list = Bindwood.bookmarksService.bookmarksMenuFolder;
685+ break;
686+ default:
687+ list = Bindwood.bookmarksService.toolbarFolder;
688+ break;
689+ }
690+ } else {
691+ list = Bindwood.bookmarksService.toolbarFolder;
692+ }
693+ var metadata = Components.classes["@mozilla.org/network/io-service;1"]
694+ .getService(Components.interfaces.nsIIOService)
695+ .newURI(bm.uri, null, null);
696+
697+ var itemId = Bindwood.bookmarksService.insertBookmark(list,
698+ metadata, -1, bm.title);
699+ // and then write the new guid back to the record
700+ var guid = Bindwood.bookmarksService.getItemGUID(itemId);
701+ var doc = couch.open(recordid);
702+ if (!doc.application_annotations) {
703+ doc.application_annotations = {};
704+ }
705+ if (!doc.application_annotations.Firefox) {
706+ doc.application_annotations.Firefox = {};
707+ }
708+ doc.application_annotations.Firefox.guid = guid;
709+ couch.save(doc);
710+ }
711+ }
712+ // reschedule ourself
713+ setTimeout(Bindwood.pullBookmarks, 5000);
714+ },
715+
716+ Observer: {
717+ // An nsINavBookmarkObserver
718+ onBeginUpdateBatch: function() {},
719+ onEndUpdateBatch: function() {},
720+ onItemAdded: function(aItemId, aFolder, aIndex) {
721+ // A bookmark has been added, so we create a blank entry
722+ // in Couch with our local itemId attached.
723+ Bindwood.writeMessage("onItemAdded!!!!" + Bindwood.bookmarksService.getItemGUID(aItemId));
724+ var list;
725+ switch(aFolder) {
726+ case Bindwood.bookmarksService.toolbarFolder:
727+ list = "toolbarFolder";
728+ break;
729+ case Bindwood.bookmarksService.bookmarksMenuFolder:
730+ list = "bookmarksMenuFolder";
731+ break;
732+ default:
733+ list = "toolbarFolder";
734+ break;
735+ }
736+
737+ netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
738+ var couch = new CouchDB('bookmarks');
739+
740+ var doc = {
741+ record_type: "http://example.com/bookmark",
742+ application_annotations: {
743+ Firefox: {
744+ guid: Bindwood.bookmarksService.getItemGUID(aItemId),
745+ list: list
746+ }
747+ }
748+ };
749+ try {
750+ var result = couch.save(doc);
751+ Bindwood.writeMessage("Saved record: " + result.rev);
752+ } catch(e) {
753+ Bindwood.writeMessage("Error '" + e.error + "': " + e.reason);
754+ }
755+ return true;
756+ },
757+ onItemRemoved: function(aItemId, aFolder, aIndex) {
758+ // A bookmark has been removed. This is called after it's
759+ // been removed locally, though we're passed the itemId,
760+ // which we use to delete from Couch.
761+ var guid = Bindwood.bookmarksService.getItemGUID(aItemId);
762+ var results = db.query(function(doc) {
763+ if (doc.application_annotations &&
764+ doc.application_annotations.Firefox &&
765+ doc.application_annotations.Firefox.guid) {
766+ emit(doc.application_annotations.Firefox.guid, doc);
767+ }
768+ }, null, {
769+ startkey: guid, endkey: guid
770+ });
771+ Bindwood.writeMessage("deleted item " + JSON.stringify(results));
772+ },
773+ onItemChanged: function(aBookmarkId, aProperty, aIsAnnotationProperty, aValue) {
774+ // A property of a bookmark has changed. On multiple
775+ // property updates, this will be called multiple times,
776+ // once per property (i.e., for title and URI)
777+ bindwood.writeMessage("in onItemChanged: " + aProperty + " changed to " + aValue);
778+ netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
779+ var couch = new CouchDB('bookmarks');
780+ var guid = Bindwood.bookmarksService.getItemGUID(aBookmarkId);
781+ var results = db.query(function(doc) {
782+ if (doc.application_annotations &&
783+ doc.application_annotations.Firefox &&
784+ doc.application_annotations.Firefox.guid) {
785+ emit(doc.application_annotations.Firefox.guid, doc);
786+ }
787+ }, null, {
788+ startkey: guid, endkey: guid
789+ });
790+ if (results.rows.length === 0) {
791+ Bindwood.writeMessage("a bookmark has changed, but this bookmark isn't in CouchDB. this isn't supposed to happen.");
792+ return;
793+ }
794+ Bindwood.writeMessage("We don't actually update Couch here.");
795+ return;
796+
797+ var doc = couch.open(results.rows[0].id);
798+ doc[aProperty.toString()] = aValue.toString();
799+ try {
800+ var result = couch.save(doc);
801+ Bindwood.writeMessage("Updated record: " + result.rev + ", setting (" + aProperty.toString() + ") to (" + aValue.toString() + ")");
802+ } catch(e) {
803+ Bindwood.writeMessage("Error '" + e.error + "': " + e.reason);
804+ }
805+ return true;
806+ },
807+ onItemVisited: function(aBookmarkId, aVisitID, time) {},
808+ onItemMoved: function(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex) {},
809+ //QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsINavBookmarkObserver])
810+ }
811+};
812
813=== added file 'chrome/content/tutorial.html'
814--- chrome/content/tutorial.html 1970-01-01 00:00:00 +0000
815+++ chrome/content/tutorial.html 2009-07-02 13:41:07 +0000
816@@ -0,0 +1,8 @@
817+<html>
818+ <head>
819+ <title>BindWood Tutorial</title>
820+ </head>
821+ <body>
822+ This is BindWood.
823+ </body>
824+</html>
825
826=== added directory 'chrome/locale'
827=== added directory 'chrome/locale/en'
828=== added file 'chrome/locale/en/bindwood.dtd'
829=== modified file 'install.rdf'
830--- install.rdf 2009-06-30 13:47:50 +0000
831+++ install.rdf 2009-07-03 02:22:26 +0000
832@@ -3,7 +3,7 @@
833 xmlns:em="http://www.mozilla.org/2004/em-rdf#">
834
835 <Description about="urn:mozilla:install-manifest">
836- <em:id>ubuntuone-firefox-bookmark-sync@ubuntu.com</em:id>
837+ <em:id>bindwood@ubuntu.com</em:id>
838 <em:version>1.0</em:version>
839 <em:type>2</em:type>
840
841@@ -18,8 +18,8 @@
842 </em:targetApplication>
843
844 <!-- Front End MetaData -->
845- <em:name>UbuntuOne Bookmarks Sync</em:name>
846- <em:description>An extension to synchronize your bookmarks to UbuntuOne.</em:description>
847+ <em:name>BindWood</em:name>
848+ <em:description>An extension to synchronize your bookmarks to a local CouchDB.</em:description>
849 <em:creator>Zachery Bir</em:creator>
850 <em:homepageURL>http://launchpad.net/ubuntuone-firefox-bookmark-sync</em:homepageURL>
851 </Description>

Subscribers

People subscribed via source and target branches