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
=== modified file 'chrome.manifest'
--- chrome.manifest 2009-06-30 13:47:50 +0000
+++ chrome.manifest 2009-07-02 13:41:07 +0000
@@ -1,2 +1,3 @@
1content ubuntuone-firefox-bookmark-sync chrome/content/1content bindwood chrome/content/
2overlay chrome://browser/content/browser.xul chrome://ubuntuone-firefox-bookmark-sync/content/browserOverlay.xul2locale bindwood en chrome/locale/en/
3overlay chrome://browser/content/browser.xul chrome://bindwood/content/browserOverlay.xul
34
=== modified file 'chrome/content/browserOverlay.xul'
--- chrome/content/browserOverlay.xul 2009-06-30 13:47:50 +0000
+++ chrome/content/browserOverlay.xul 2009-07-03 02:22:26 +0000
@@ -1,15 +1,27 @@
1<?xml version="1.0"?>1<?xml version="1.0"?>
22
3<!DOCTYPE overlay SYSTEM "chrome://ubuntuone-firefox-bookmark-sync/locale/ubuntuone-firefox-bookmark-sync.dtd">3<!DOCTYPE overlay SYSTEM "chrome://bindwood/locale/en/bindwood.dtd">
44
5<overlay id="ubuntuone-firefox-bookmark-sync"5<overlay id="bindwood"
6 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">6 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
77
8 <menupopup id="menu_ToolsPopup">8 <script type="application/x-javascript" src="chrome://bindwood/content/couch.js" />
9 <menuitem id="UbuntuOneFirefoxBookmarkSync"9 <script type="application/x-javascript" src="chrome://bindwood/content/sync.js" />
10
11 <window id="main-window">
12 <script type="application/x-javascript">
13 window.addEventListener("load", Bindwood.init, true);
14
15 var bmsvc = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"].getService(Components.interfaces.nsINavBookmarksService);
16 window.addEventListener("load", function() { bmsvc.addObserver(Bindwood.Observer, false); }, false);
17 </script>
18 </window>
19
20 <menupopup id="bookmarksMenuPopup">
21 <menuitem id="BindwoodSync"
10 label="Sync Bookmarks (well, alert)"22 label="Sync Bookmarks (well, alert)"
11 insertbefore="javascriptConsole"23 oncommand="pullBookmarks(false);"
12 oncommand="alert('syncing...');" />24 insertafter="bookmarksShowAll" />
13 </menupopup>25 </menupopup>
1426
15</overlay>27</overlay>
1628
=== added file 'chrome/content/couch.js'
--- chrome/content/couch.js 1970-01-01 00:00:00 +0000
+++ chrome/content/couch.js 2009-07-06 10:49:17 +0000
@@ -0,0 +1,396 @@
1// Licensed under the Apache License, Version 2.0 (the "License"); you may not
2// use this file except in compliance with the License. You may obtain a copy
3// of the License at
4//
5// http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10// License for the specific language governing permissions and limitations under
11// the License.
12
13// A simple class to represent a database. Uses XMLHttpRequest to interface with
14// the CouchDB server.
15
16function CouchDB(name, httpHeaders) {
17 this.name = name;
18 this.uri = "/" + encodeURIComponent(name) + "/";
19
20 // The XMLHttpRequest object from the most recent request. Callers can
21 // use this to check result http status and headers.
22 this.last_req = null;
23
24 this.request = function(method, uri, requestOptions) {
25 requestOptions = requestOptions || {}
26 requestOptions.headers = combine(requestOptions.headers, httpHeaders)
27 return CouchDB.request(method, uri, requestOptions);
28 }
29
30 // Creates the database on the server
31 this.createDb = function() {
32 this.last_req = this.request("PUT", this.uri);
33 CouchDB.maybeThrowError(this.last_req);
34 return JSON.parse(this.last_req.responseText);
35 }
36
37 // Deletes the database on the server
38 this.deleteDb = function() {
39 this.last_req = this.request("DELETE", this.uri);
40 if (this.last_req.status == 404)
41 return false;
42 CouchDB.maybeThrowError(this.last_req);
43 return JSON.parse(this.last_req.responseText);
44 }
45
46 // Save a document to the database
47 this.save = function(doc, options) {
48 if (doc._id == undefined)
49 doc._id = CouchDB.newUuids(1)[0];
50
51 this.last_req = this.request("PUT", this.uri +
52 encodeURIComponent(doc._id) + encodeOptions(options),
53 {body: JSON.stringify(doc)});
54 CouchDB.maybeThrowError(this.last_req);
55 var result = JSON.parse(this.last_req.responseText);
56 doc._rev = result.rev;
57 return result;
58 }
59
60 // Open a document from the database
61 this.open = function(docId, options) {
62 this.last_req = this.request("GET", this.uri + encodeURIComponent(docId) + encodeOptions(options));
63 if (this.last_req.status == 404)
64 return null;
65 CouchDB.maybeThrowError(this.last_req);
66 return JSON.parse(this.last_req.responseText);
67 }
68
69 // Deletes a document from the database
70 this.deleteDoc = function(doc) {
71 this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id) + "?rev=" + doc._rev);
72 CouchDB.maybeThrowError(this.last_req);
73 var result = JSON.parse(this.last_req.responseText);
74 doc._rev = result.rev; //record rev in input document
75 doc._deleted = true;
76 return result;
77 }
78
79 // Deletes an attachment from a document
80 this.deleteDocAttachment = function(doc, attachment_name) {
81 this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id) + "/" + attachment_name + "?rev=" + doc._rev);
82 CouchDB.maybeThrowError(this.last_req);
83 var result = JSON.parse(this.last_req.responseText);
84 doc._rev = result.rev; //record rev in input document
85 return result;
86 }
87
88 this.bulkSave = function(docs, options) {
89 // first prepoulate the UUIDs for new documents
90 var newCount = 0
91 for (var i=0; i<docs.length; i++) {
92 if (docs[i]._id == undefined)
93 newCount++;
94 }
95 var newUuids = CouchDB.newUuids(docs.length);
96 var newCount = 0
97 for (var i=0; i<docs.length; i++) {
98 if (docs[i]._id == undefined)
99 docs[i]._id = newUuids.pop();
100 }
101 var json = {"docs": docs};
102 // put any options in the json
103 for (var option in options) {
104 json[option] = options[option];
105 }
106 this.last_req = this.request("POST", this.uri + "_bulk_docs", {
107 body: JSON.stringify(json)
108 });
109 if (this.last_req.status == 417) {
110 return {errors: JSON.parse(this.last_req.responseText)};
111 }
112 else {
113 CouchDB.maybeThrowError(this.last_req);
114 var results = JSON.parse(this.last_req.responseText);
115 for (var i = 0; i < docs.length; i++) {
116 if(results[i].rev)
117 docs[i]._rev = results[i].rev;
118 }
119 return results;
120 }
121 }
122
123 this.ensureFullCommit = function() {
124 this.last_req = this.request("POST", this.uri + "_ensure_full_commit");
125 CouchDB.maybeThrowError(this.last_req);
126 return JSON.parse(this.last_req.responseText);
127 }
128
129 // Applies the map function to the contents of database and returns the results.
130 this.query = function(mapFun, reduceFun, options, keys) {
131 var body = {language: "javascript"};
132 if(keys) {
133 body.keys = keys ;
134 }
135 if (typeof(mapFun) != "string")
136 mapFun = mapFun.toSource ? mapFun.toSource() : "(" + mapFun.toString() + ")";
137 body.map = mapFun;
138 if (reduceFun != null) {
139 if (typeof(reduceFun) != "string")
140 reduceFun = reduceFun.toSource ? reduceFun.toSource() : "(" + reduceFun.toString() + ")";
141 body.reduce = reduceFun;
142 }
143 if (options && options.options != undefined) {
144 body.options = options.options;
145 delete options.options;
146 }
147 this.last_req = this.request("POST", this.uri + "_temp_view" + encodeOptions(options), {
148 headers: {"Content-Type": "application/json"},
149 body: JSON.stringify(body)
150 });
151 CouchDB.maybeThrowError(this.last_req);
152 return JSON.parse(this.last_req.responseText);
153 }
154
155 this.view = function(viewname, options, keys) {
156 var viewParts = viewname.split('/');
157 var viewPath = this.uri + "_design/" + viewParts[0] + "/_view/"
158 + viewParts[1] + encodeOptions(options);
159 if(!keys) {
160 this.last_req = this.request("GET", viewPath);
161 } else {
162 this.last_req = this.request("POST", viewPath, {
163 headers: {"Content-Type": "application/json"},
164 body: JSON.stringify({keys:keys})
165 });
166 }
167 if (this.last_req.status == 404)
168 return null;
169 CouchDB.maybeThrowError(this.last_req);
170 return JSON.parse(this.last_req.responseText);
171 }
172
173 // gets information about the database
174 this.info = function() {
175 this.last_req = this.request("GET", this.uri);
176 CouchDB.maybeThrowError(this.last_req);
177 return JSON.parse(this.last_req.responseText);
178 }
179
180 this.allDocs = function(options,keys) {
181 if(!keys) {
182 this.last_req = this.request("GET", this.uri + "_all_docs" + encodeOptions(options));
183 } else {
184 this.last_req = this.request("POST", this.uri + "_all_docs" + encodeOptions(options), {
185 headers: {"Content-Type": "application/json"},
186 body: JSON.stringify({keys:keys})
187 });
188 }
189 CouchDB.maybeThrowError(this.last_req);
190 return JSON.parse(this.last_req.responseText);
191 }
192
193 this.designDocs = function() {
194 return this.allDocs({startkey:"_design", endkey:"_design0"});
195 };
196
197 this.allDocsBySeq = function(options,keys) {
198 var req = null;
199 if(!keys) {
200 req = this.request("GET", this.uri + "_all_docs_by_seq" + encodeOptions(options));
201 } else {
202 req = this.request("POST", this.uri + "_all_docs_by_seq" + encodeOptions(options), {
203 headers: {"Content-Type": "application/json"},
204 body: JSON.stringify({keys:keys})
205 });
206 }
207 CouchDB.maybeThrowError(req);
208 return JSON.parse(req.responseText);
209 }
210
211 this.compact = function() {
212 this.last_req = this.request("POST", this.uri + "_compact");
213 CouchDB.maybeThrowError(this.last_req);
214 return JSON.parse(this.last_req.responseText);
215 }
216
217 this.setDbProperty = function(propId, propValue) {
218 this.last_req = this.request("PUT", this.uri + propId,{
219 body:JSON.stringify(propValue)
220 });
221 CouchDB.maybeThrowError(this.last_req);
222 return JSON.parse(this.last_req.responseText);
223 }
224
225 this.getDbProperty = function(propId) {
226 this.last_req = this.request("GET", this.uri + propId);
227 CouchDB.maybeThrowError(this.last_req);
228 return JSON.parse(this.last_req.responseText);
229 }
230
231 this.setAdmins = function(adminsArray) {
232 this.last_req = this.request("PUT", this.uri + "_admins",{
233 body:JSON.stringify(adminsArray)
234 });
235 CouchDB.maybeThrowError(this.last_req);
236 return JSON.parse(this.last_req.responseText);
237 }
238
239 this.getAdmins = function() {
240 this.last_req = this.request("GET", this.uri + "_admins");
241 CouchDB.maybeThrowError(this.last_req);
242 return JSON.parse(this.last_req.responseText);
243 }
244
245 // Convert a options object to an url query string.
246 // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"'
247 function encodeOptions(options) {
248 var buf = []
249 if (typeof(options) == "object" && options !== null) {
250 for (var name in options) {
251 if (!options.hasOwnProperty(name)) continue;
252 var value = options[name];
253 if (name == "key" || name == "startkey" || name == "endkey") {
254 value = toJSON(value);
255 }
256 buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
257 }
258 }
259 if (!buf.length) {
260 return "";
261 }
262 return "?" + buf.join("&");
263 }
264
265 function toJSON(obj) {
266 return obj !== null ? JSON.stringify(obj) : null;
267 }
268
269 function combine(object1, object2) {
270 if (!object2)
271 return object1;
272 if (!object1)
273 return object2;
274
275 for (var name in object2)
276 object1[name] = object2[name];
277
278 return object1;
279 }
280
281
282}
283
284// this is the XMLHttpRequest object from last request made by the following
285// CouchDB.* functions (except for calls to request itself).
286// Use this from callers to check HTTP status or header values of requests.
287CouchDB.last_req = null;
288
289
290CouchDB.allDbs = function() {
291 CouchDB.last_req = CouchDB.request("GET", "/_all_dbs");
292 CouchDB.maybeThrowError(CouchDB.last_req);
293 return JSON.parse(CouchDB.last_req.responseText);
294}
295
296CouchDB.allDesignDocs = function() {
297 var ddocs = {}, dbs = CouchDB.allDbs();
298 for (var i=0; i < dbs.length; i++) {
299 var db = new CouchDB(dbs[i]);
300 ddocs[dbs[i]] = db.designDocs();
301 };
302 return ddocs;
303};
304
305CouchDB.getVersion = function() {
306 CouchDB.last_req = CouchDB.request("GET", "/");
307 CouchDB.maybeThrowError(CouchDB.last_req);
308 return JSON.parse(CouchDB.last_req.responseText).version;
309}
310
311CouchDB.replicate = function(source, target, rep_options) {
312 rep_options = rep_options || {};
313 var headers = rep_options.headers || {};
314 CouchDB.last_req = CouchDB.request("POST", "/_replicate", {
315 headers: headers,
316 body: JSON.stringify({source: source, target: target})
317 });
318 CouchDB.maybeThrowError(CouchDB.last_req);
319 return JSON.parse(CouchDB.last_req.responseText);
320}
321
322CouchDB.request = function(method, uri, options) {
323 Bindwood.writeMessage("CouchDB.request connecting to " + uri);
324 options = options || {};
325 var req = null;
326 if (typeof(XMLHttpRequest) != "undefined") {
327 req = new XMLHttpRequest();
328 } else if (typeof(ActiveXObject) != "undefined") {
329 req = new ActiveXObject("Microsoft.XMLHTTP");
330 } else {
331 throw new Error("No XMLHTTPRequest support detected");
332 }
333 req.open(method, "http://localhost:5984" + uri, false);
334 if (options.headers) {
335 var headers = options.headers;
336 for (var headerName in headers) {
337 if (!headers.hasOwnProperty(headerName)) continue;
338 req.setRequestHeader(headerName, headers[headerName]);
339 }
340 }
341 req.send(options.body || "");
342 return req;
343}
344
345CouchDB.requestStats = function(module, key, test) {
346 var query_arg = "";
347 if(test !== null) {
348 query_arg = "?flush=true";
349 }
350
351 var stat = CouchDB.request("GET", "/_stats/" + module + "/" + key + query_arg).responseText;
352 return JSON.parse(stat)[module][key];
353}
354
355CouchDB.uuids_cache = [];
356
357CouchDB.newUuids = function(n) {
358 if (CouchDB.uuids_cache.length >= n) {
359 var uuids = CouchDB.uuids_cache.slice(CouchDB.uuids_cache.length - n);
360 if(CouchDB.uuids_cache.length - n == 0) {
361 CouchDB.uuids_cache = [];
362 } else {
363 CouchDB.uuids_cache =
364 CouchDB.uuids_cache.slice(0, CouchDB.uuids_cache.length - n);
365 }
366 return uuids;
367 } else {
368 CouchDB.last_req = CouchDB.request("GET", "/_uuids?count=" + (100 + n));
369 CouchDB.maybeThrowError(CouchDB.last_req);
370 var result = JSON.parse(CouchDB.last_req.responseText);
371 CouchDB.uuids_cache =
372 CouchDB.uuids_cache.concat(result.uuids.slice(0, 100));
373 return result.uuids.slice(100);
374 }
375}
376
377CouchDB.maybeThrowError = function(req) {
378 if (req.status >= 400) {
379 try {
380 var result = JSON.parse(req.responseText);
381 } catch (ParseError) {
382 var result = {error:"unknown", reason:req.responseText};
383 }
384 throw result;
385 }
386}
387
388CouchDB.params = function(options) {
389 options = options || {};
390 var returnArray = [];
391 for(var key in options) {
392 var value = options[key];
393 returnArray.push(key + "=" + value);
394 }
395 return returnArray.join("&");
396}
0397
=== added file 'chrome/content/sync.js'
--- chrome/content/sync.js 1970-01-01 00:00:00 +0000
+++ chrome/content/sync.js 2009-07-06 16:00:58 +0000
@@ -0,0 +1,358 @@
1/* Lots and lots of debugging information */
2var Bindwood = {
3 bookmarksService: Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"]
4 .getService(Components.interfaces.nsINavBookmarksService),
5
6 writeMessage: function(aMessage) {
7 // convenience method for logging. Way better than alert()s.
8 var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
9 .getService(Components.interfaces.nsIConsoleService);
10 consoleService.logStringMessage("Bindwood: " + aMessage);
11 },
12
13 writeError: function(aMessage, e) {
14 Bindwood.writeMessage(aMessage + "message: '" + e.message + "', reason: '" + e.reason + "', description: '" + e.description + "', error: '" + e.error + "'");
15 },
16
17 init: function() {
18 // Start the process and de-register ourself
19 // http://forums.mozillazine.org/viewtopic.php?f=19&t=657911&start=0
20 // It ensures that we're only running that code on the first window.
21 if(Components.classes["@mozilla.org/appshell/window-mediator;1"]
22 .getService(Components.interfaces.nsIWindowMediator)
23 .getEnumerator("").getNext() == window) {
24 try {
25 Bindwood.writeMessage("About to push bookmarks (startup)");
26 Bindwood.pushBookmarks();
27 } catch(e) {
28 Bindwood.writeError("Error when calling pushBookmarks: ", e);
29 }
30 Bindwood.writeMessage("Creating view");
31 Bindwood.createView();
32 Bindwood.writeMessage("About to pull bookmarks (startup)");
33 Bindwood.pullBookmarks();
34 }
35 },
36
37 createView: function() {
38 var DDID = "_design/views";
39 var couch = new CouchDB('bookmarks');
40 var current_doc;
41 try {
42 current_doc = couch.open(DDID);
43 if (current_doc !== null) {
44 Bindwood.writeMessage("View already exists; leaving it alone");
45 } else {
46 new_doc = {
47 _id: DDID,
48 views: {
49 display: {
50 map: "function(doc) { " +
51 "var scheme = doc.uri.split(':',1)[0]; " +
52 "var uri; " +
53 "if (scheme == 'http' || scheme == 'https') {" +
54 "uri = doc.uri.split('/')[2];" +
55 "if (uri.length < 30) {" +
56 " uri += '/' + " +
57 "doc.uri.split('/',4)[3].substr(0,30-uri.length) + '...';" +
58 "}" +
59 "} else {" +
60 "uri = scheme + ' URL';" +
61 "}" +
62 "emit(doc.title, uri);" +
63 "}"
64 }
65 }
66 };
67 try {
68 couch.save(new_doc);
69 Bindwood.writeMessage("Added view");
70 } catch(e) {
71 Bindwood.writeError("Problem saving view: ", e);
72 }
73 }
74 } catch(e) {
75 // some kind of error fetching the existing design doc
76 Bindwood.writeError("Problem checking for view: ", e);
77 }
78 },
79
80 pushBookmarks: function() {
81 // Prime the pump, so to speak, by uploading all our local
82 // bookmarks to CouchDB (if they're not there already).
83 Bindwood.writeMessage("in pushBookmarks");
84 var couch = new CouchDB('bookmarks');
85 // Create the DB if it doesn't already exist
86 try {
87 Bindwood.writeMessage("creating database");
88 couch.createDb();
89 } catch (e) {
90 Bindwood.writeError("Error when creating database in pushBookmarks (file_exists is OK here): ", e);
91 }
92
93 Bindwood.writeMessage("about to push toolbar bookmarks");
94 Bindwood.pushBookmarksFromList(Bindwood.bookmarksService.toolbarFolder,
95 "toolbarFolder", couch);
96 Bindwood.writeMessage("about to push menu bookmarks");
97 Bindwood.pushBookmarksFromList(Bindwood.bookmarksService.bookmarksMenuFolder,
98 "bookmarksMenuFolder", couch);
99 },
100
101 getBookmarksFromList: function(bookmarksList) {
102 Bindwood.writeMessage("in getBookmarksFromList with list " + bookmarksList);
103 var retval = [];
104 var historyService = Components.classes["@mozilla.org/browser/nav-history-service;1"]
105 .getService(Components.interfaces.nsINavHistoryService);
106 var options = historyService.getNewQueryOptions();
107 var query = historyService.getNewQuery();
108 query.setFolders([bookmarksList], 1);
109 var result = historyService.executeQuery(query, options);
110 var rootNode = result.root;
111 rootNode.containerOpen = true;
112 for (var i=0; i<rootNode.childCount; i++) {
113 var node = rootNode.getChild(i);
114 if (Bindwood.bookmarksService.getItemType(node.itemId) !=
115 Components.interfaces.nsINavBookmarksService.TYPE_BOOKMARK) {
116 continue;
117 }
118
119 var title = Bindwood.bookmarksService.getItemTitle(node.itemId);
120 Bindwood.writeMessage("got bookmark '" + title + "'");
121 try {
122 var metadata = Bindwood.bookmarksService.getBookmarkURI(node.itemId);
123 } catch(e) {
124 Bindwood.writeError("problem fetching metadata for bookmark '" + title + "': ", e);
125 continue;
126 }
127 var guid = Bindwood.bookmarksService.getItemGUID(node.itemId);
128 Bindwood.writeMessage("got GUID");
129 retval.push({title: title, metadata:metadata,guid:guid, id:node.itemId});
130 }
131 rootNode.containerOpen = false;
132 return retval;
133 },
134
135 pushBookmarksFromList: function(bookmarksList, bookmarksListName, db) {
136 Bindwood.writeMessage("in pushBookmarksFromList");
137 var bookmarkData = Bindwood.getBookmarksFromList(bookmarksList);
138 for (i=0; i<bookmarkData.length; i++) {
139 // find this bookmark in CouchDB
140 Bindwood.writeMessage("searching for bookmark '" + bookmarkData[i].title + "' in Couch");
141 var results = db.query(function(doc) {
142 if (doc.application_annotations &&
143 doc.application_annotations.Firefox &&
144 doc.application_annotations.Firefox.guid) {
145 emit(doc.application_annotations.Firefox.guid, doc);
146 }
147 }, null, {
148 startkey: bookmarkData[i].guid, endkey: bookmarkData[i].guid
149 });
150
151 if (results.rows.length === 0) {
152 // this bookmark is not in CouchDB, so write it
153 Bindwood.writeMessage("Didn't find it, so creating it");
154 var record = {
155 record_type: "http://example.com/bookmark",
156 uri: bookmarkData[i].metadata.spec,
157 title: bookmarkData[i].title,
158 application_annotations: {
159 Firefox: {
160 guid: bookmarkData[i].guid,
161 list: bookmarksListName
162 }
163 }
164 };
165 try {
166 db.save(record);
167 } catch(e) {
168 Bindwood.writeError("Problem saving bookmark to CouchDB; bookmark is " + JSON.stringify(record) + ": ", e);
169 }
170 } else {
171 // bookmark is already in CouchDB, so do nothing
172 Bindwood.writeMessage("Did find it, so don't need to do anything");
173 }
174 }
175 },
176
177 pullBookmarks: function() {
178 Bindwood.writeMessage("in pullBookmarks");
179 var couch = new CouchDB('bookmarks');
180 // Fetch all bookmark documents from the database
181 // the query function is evaluated by Couch, which doesn't know
182 // what Bindwood.RECORD_TYPE is, so we string-encode it first to
183 // include the literal value
184 try {
185 var rows = couch.query(function (doc) {
186 if (doc.record_type == "http://example.com/bookmark") {
187 emit(doc._id,doc);
188 }
189 });
190 } catch(e) {
191 Bindwood.writeError("Problem fetching all bookmarks from Couch:", e);
192 }
193 for (var i=0; i<rows.rows.length; i++) {
194 var recordid = rows.rows[i].id;
195 var bm = rows.rows[i].value;
196 if (bm.application_annotations && bm.application_annotations.Firefox &&
197 bm.application_annotations.Firefox.guid) {
198 // this bookmark has a guid, so check its values haven't changed
199 // find the bookmark with this guid
200 var itemId = Bindwood.bookmarksService.getItemIdForGUID(
201 bm.application_annotations.Firefox.guid);
202 if (itemId == -1) {
203 // This bookmark has a guid, but it's not one of ours.
204 // We need to work out whether (a) it's the same as one
205 // of ours but with a different guid (so we need to
206 // make the guids the same), or (b) it's a new one
207 // that happens to have been created on a different
208 // machine.
209 // For the moment, punt on this.
210 } else {
211 var title = Bindwood.bookmarksService.getItemTitle(itemId);
212 var metadata = Bindwood.bookmarksService.getBookmarkURI(itemId);
213 if (title != bm.title) {
214 Bindwood.bookmarksService.setItemTitle(itemId, bm.title);
215 }
216 if (metadata.spec != bm.uri) {
217 metadata.spec = bm.uri;
218 Bindwood.bookmarksService.changeBookmarkURI(itemId, metadata);
219 }
220 }
221 } else {
222 // this bookmark has no guid, so create it from fresh in Firefox
223 var list;
224 if (bm.application_annotations && bm.application_annotations.Firefox &&
225 bm.application_annotations.Firefox.list) {
226 switch (bm.application_annotations.Firefox.list) {
227 case "toolbarFolder":
228 list = Bindwood.bookmarksService.toolbarFolder;
229 break;
230 case "bookmarksMenuFolder":
231 list = Bindwood.bookmarksService.bookmarksMenuFolder;
232 break;
233 default:
234 list = Bindwood.bookmarksService.toolbarFolder;
235 break;
236 }
237 } else {
238 list = Bindwood.bookmarksService.toolbarFolder;
239 }
240 var metadata = Components.classes["@mozilla.org/network/io-service;1"]
241 .getService(Components.interfaces.nsIIOService)
242 .newURI(bm.uri, null, null);
243
244 var itemId = Bindwood.bookmarksService.insertBookmark(list,
245 metadata, -1, bm.title);
246 // and then write the new guid back to the record
247 var guid = Bindwood.bookmarksService.getItemGUID(itemId);
248 var doc = couch.open(recordid);
249 if (!doc.application_annotations) {
250 doc.application_annotations = {};
251 }
252 if (!doc.application_annotations.Firefox) {
253 doc.application_annotations.Firefox = {};
254 }
255 doc.application_annotations.Firefox.guid = guid;
256 couch.save(doc);
257 }
258 }
259 // reschedule ourself
260 setTimeout(Bindwood.pullBookmarks, 5000);
261 },
262
263 Observer: {
264 // An nsINavBookmarkObserver
265 onBeginUpdateBatch: function() {},
266 onEndUpdateBatch: function() {},
267 onItemAdded: function(aItemId, aFolder, aIndex) {
268 // A bookmark has been added, so we create a blank entry
269 // in Couch with our local itemId attached.
270 Bindwood.writeMessage("onItemAdded!!!!" + Bindwood.bookmarksService.getItemGUID(aItemId));
271 var list;
272 switch(aFolder) {
273 case Bindwood.bookmarksService.toolbarFolder:
274 list = "toolbarFolder";
275 break;
276 case Bindwood.bookmarksService.bookmarksMenuFolder:
277 list = "bookmarksMenuFolder";
278 break;
279 default:
280 list = "toolbarFolder";
281 break;
282 }
283
284 netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
285 var couch = new CouchDB('bookmarks');
286
287 var doc = {
288 record_type: "http://example.com/bookmark",
289 application_annotations: {
290 Firefox: {
291 guid: Bindwood.bookmarksService.getItemGUID(aItemId),
292 list: list
293 }
294 }
295 };
296 try {
297 var result = couch.save(doc);
298 Bindwood.writeMessage("Saved record: " + result.rev);
299 } catch(e) {
300 Bindwood.writeMessage("Error '" + e.error + "': " + e.reason);
301 }
302 return true;
303 },
304 onItemRemoved: function(aItemId, aFolder, aIndex) {
305 // A bookmark has been removed. This is called after it's
306 // been removed locally, though we're passed the itemId,
307 // which we use to delete from Couch.
308 var guid = Bindwood.bookmarksService.getItemGUID(aItemId);
309 var results = db.query(function(doc) {
310 if (doc.application_annotations &&
311 doc.application_annotations.Firefox &&
312 doc.application_annotations.Firefox.guid) {
313 emit(doc.application_annotations.Firefox.guid, doc);
314 }
315 }, null, {
316 startkey: guid, endkey: guid
317 });
318 Bindwood.writeMessage("deleted item " + JSON.stringify(results));
319 },
320 onItemChanged: function(aBookmarkId, aProperty, aIsAnnotationProperty, aValue) {
321 // A property of a bookmark has changed. On multiple
322 // property updates, this will be called multiple times,
323 // once per property (i.e., for title and URI)
324 bindwood.writeMessage("in onItemChanged: " + aProperty + " changed to " + aValue);
325 netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
326 var couch = new CouchDB('bookmarks');
327 var guid = Bindwood.bookmarksService.getItemGUID(aBookmarkId);
328 var results = db.query(function(doc) {
329 if (doc.application_annotations &&
330 doc.application_annotations.Firefox &&
331 doc.application_annotations.Firefox.guid) {
332 emit(doc.application_annotations.Firefox.guid, doc);
333 }
334 }, null, {
335 startkey: guid, endkey: guid
336 });
337 if (results.rows.length === 0) {
338 Bindwood.writeMessage("a bookmark has changed, but this bookmark isn't in CouchDB. this isn't supposed to happen.");
339 return;
340 }
341 Bindwood.writeMessage("We don't actually update Couch here.");
342 return;
343
344 var doc = couch.open(results.rows[0].id);
345 doc[aProperty.toString()] = aValue.toString();
346 try {
347 var result = couch.save(doc);
348 Bindwood.writeMessage("Updated record: " + result.rev + ", setting (" + aProperty.toString() + ") to (" + aValue.toString() + ")");
349 } catch(e) {
350 Bindwood.writeMessage("Error '" + e.error + "': " + e.reason);
351 }
352 return true;
353 },
354 onItemVisited: function(aBookmarkId, aVisitID, time) {},
355 onItemMoved: function(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex) {},
356 //QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsINavBookmarkObserver])
357 }
358};
0359
=== added file 'chrome/content/tutorial.html'
--- chrome/content/tutorial.html 1970-01-01 00:00:00 +0000
+++ chrome/content/tutorial.html 2009-07-02 13:41:07 +0000
@@ -0,0 +1,8 @@
1<html>
2 <head>
3 <title>BindWood Tutorial</title>
4 </head>
5 <body>
6 This is BindWood.
7 </body>
8</html>
09
=== added directory 'chrome/locale'
=== added directory 'chrome/locale/en'
=== added file 'chrome/locale/en/bindwood.dtd'
=== modified file 'install.rdf'
--- install.rdf 2009-06-30 13:47:50 +0000
+++ install.rdf 2009-07-03 02:22:26 +0000
@@ -3,7 +3,7 @@
3 xmlns:em="http://www.mozilla.org/2004/em-rdf#">3 xmlns:em="http://www.mozilla.org/2004/em-rdf#">
44
5 <Description about="urn:mozilla:install-manifest">5 <Description about="urn:mozilla:install-manifest">
6 <em:id>ubuntuone-firefox-bookmark-sync@ubuntu.com</em:id>6 <em:id>bindwood@ubuntu.com</em:id>
7 <em:version>1.0</em:version>7 <em:version>1.0</em:version>
8 <em:type>2</em:type>8 <em:type>2</em:type>
99
@@ -18,8 +18,8 @@
18 </em:targetApplication>18 </em:targetApplication>
1919
20 <!-- Front End MetaData -->20 <!-- Front End MetaData -->
21 <em:name>UbuntuOne Bookmarks Sync</em:name>21 <em:name>BindWood</em:name>
22 <em:description>An extension to synchronize your bookmarks to UbuntuOne.</em:description>22 <em:description>An extension to synchronize your bookmarks to a local CouchDB.</em:description>
23 <em:creator>Zachery Bir</em:creator>23 <em:creator>Zachery Bir</em:creator>
24 <em:homepageURL>http://launchpad.net/ubuntuone-firefox-bookmark-sync</em:homepageURL>24 <em:homepageURL>http://launchpad.net/ubuntuone-firefox-bookmark-sync</em:homepageURL>
25 </Description>25 </Description>

Subscribers

People subscribed via source and target branches