Merge lp:~sil/bindwood/design-view into lp:~urbanape/bindwood/trunk
- design-view
- Merge into 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Zachery Bir | Approve | ||
Review via email: mp+8267@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Stuart Langridge (sil) wrote : | # |
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> |
Add a design document, providing a more convenient view on your bookmarks