Merge lp:~ken-vandine/firefox-extensions/bindwood.ubuntu into lp:~ubuntu-dev/firefox-extensions/bindwood.ubuntu

Proposed by Ken VanDine on 2009-09-25
Status: Merged
Merged at revision: not available
Proposed branch: lp:~ken-vandine/firefox-extensions/bindwood.ubuntu
Merge into: lp:~ubuntu-dev/firefox-extensions/bindwood.ubuntu
Diff against target: 2764 lines
15 files modified
.bzr-builddeb/default.conf (+0/-4)
COPYING.BSD (+26/-0)
config_build.sh (+1/-1)
content/browserOverlay.xul (+2/-0)
content/couch.js (+130/-47)
content/oauth.js (+515/-0)
content/sha1.js (+202/-0)
content/sync.js (+672/-371)
couchdb_env.sh (+8/-4)
debian/changelog (+0/-6)
debian/compat (+0/-1)
debian/control (+0/-22)
debian/copyright (+0/-43)
debian/rules (+0/-8)
install.rdf (+1/-1)
To merge this branch: bzr merge lp:~ken-vandine/firefox-extensions/bindwood.ubuntu
Reviewer Review Type Date Requested Status
Alexander Sack (community) 2009-09-25 Approve on 2009-09-28
Review via email: mp+12428@code.launchpad.net
To post a comment you must log in.
Ken VanDine (ken-vandine) wrote :

Bug fix release for (LP: #436705)

Alexander Sack (asac) :
review: Approve
Alexander Sack (asac) wrote :

> Bug fix release for (LP: #436705)

please use branches with proper topic name in future. i refer to this branch now in bzr commit log, so please keep it (not reuse the same branch name)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory '.bzr-builddeb'
2=== removed directory '.bzr-builddeb'
3=== added file '.bzr-builddeb/default.conf'
4--- .bzr-builddeb/default.conf 1970-01-01 00:00:00 +0000
5+++ .bzr-builddeb/default.conf 2009-09-25 16:00:27 +0000
6@@ -0,0 +1,4 @@
7+[BUILDDEB]
8+merge = True
9+export-upstream-revision = tag:bindwood-0_3
10+export-upstream = .
11
12=== removed file '.bzr-builddeb/default.conf'
13--- .bzr-builddeb/default.conf 2009-08-18 19:21:27 +0000
14+++ .bzr-builddeb/default.conf 1970-01-01 00:00:00 +0000
15@@ -1,4 +0,0 @@
16-[BUILDDEB]
17-merge = True
18-export-upstream-revision = revid:elliot@elliotmurphy.com-20090818180924-l23evvtklqqb8tji
19-export-upstream = .
20
21=== added file 'COPYING.BSD'
22--- COPYING.BSD 1970-01-01 00:00:00 +0000
23+++ COPYING.BSD 2009-09-25 16:00:27 +0000
24@@ -0,0 +1,26 @@
25+Copyright (c) The Regents of the University of California.
26+All rights reserved.
27+
28+Redistribution and use in source and binary forms, with or without
29+modification, are permitted provided that the following conditions
30+are met:
31+1. Redistributions of source code must retain the above copyright
32+ notice, this list of conditions and the following disclaimer.
33+2. Redistributions in binary form must reproduce the above copyright
34+ notice, this list of conditions and the following disclaimer in the
35+ documentation and/or other materials provided with the distribution.
36+3. Neither the name of the University nor the names of its contributors
37+ may be used to endorse or promote products derived from this software
38+ without specific prior written permission.
39+
40+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
41+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
42+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
43+ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
44+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
45+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
46+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
47+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
48+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
49+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
50+SUCH DAMAGE.
51
52=== modified file 'config_build.sh'
53--- config_build.sh 2009-07-14 19:27:48 +0000
54+++ config_build.sh 2009-09-25 16:00:27 +0000
55@@ -3,7 +3,7 @@
56 APP_NAME=bindwood
57 CHROME_PROVIDERS="content locale"
58 CLEAN_UP=1
59-ROOT_FILES="dbus.sh"
60+ROOT_FILES="couchdb_env.sh"
61 ROOT_DIRS="defaults"
62 BEFORE_BUILD=
63 AFTER_BUILD=
64
65=== modified file 'content/browserOverlay.xul'
66--- content/browserOverlay.xul 2009-08-18 14:17:30 +0000
67+++ content/browserOverlay.xul 2009-09-25 16:00:27 +0000
68@@ -19,6 +19,8 @@
69 <overlay id="bindwood"
70 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
71
72+ <script type="application/x-javascript" src="chrome://bindwood/content/sha1.js" />
73+ <script type="application/x-javascript" src="chrome://bindwood/content/oauth.js" />
74 <script type="application/x-javascript" src="chrome://bindwood/content/couch.js" />
75 <script type="application/x-javascript" src="chrome://bindwood/content/sync.js" />
76
77
78=== modified file 'content/couch.js'
79--- content/couch.js 2009-07-08 01:24:33 +0000
80+++ content/couch.js 2009-09-25 16:00:27 +0000
81@@ -1,12 +1,12 @@
82 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
83-// use this file except in compliance with the License. You may obtain a copy
84-// of the License at
85+// use this file except in compliance with the License. You may obtain a copy of
86+// the License at
87 //
88 // http://www.apache.org/licenses/LICENSE-2.0
89 //
90 // Unless required by applicable law or agreed to in writing, software
91 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
92-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
93+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
94 // License for the specific language governing permissions and limitations under
95 // the License.
96
97@@ -22,17 +22,22 @@
98 this.last_req = null;
99
100 this.request = function(method, uri, requestOptions) {
101- requestOptions = requestOptions || {}
102- requestOptions.headers = combine(requestOptions.headers, httpHeaders)
103+ CouchDB.message.action = uri;
104+ CouchDB.message.method = method;
105+ OAuth.completeRequest(CouchDB.message, CouchDB.accessor);
106+ var parameters = CouchDB.message.parameters;
107+ requestOptions = requestOptions || {};
108+ requestOptions.headers = combine(requestOptions.headers, httpHeaders);
109+ requestOptions.headers = combine(requestOptions.headers, {Authorization: OAuth.getAuthorizationHeader('', parameters)});
110 return CouchDB.request(method, uri, requestOptions);
111- }
112+ };
113
114 // Creates the database on the server
115 this.createDb = function() {
116 this.last_req = this.request("PUT", this.uri);
117 CouchDB.maybeThrowError(this.last_req);
118 return JSON.parse(this.last_req.responseText);
119- }
120+ };
121
122 // Deletes the database on the server
123 this.deleteDb = function() {
124@@ -41,7 +46,7 @@
125 return false;
126 CouchDB.maybeThrowError(this.last_req);
127 return JSON.parse(this.last_req.responseText);
128- }
129+ };
130
131 // Save a document to the database
132 this.save = function(doc, options) {
133@@ -55,7 +60,7 @@
134 var result = JSON.parse(this.last_req.responseText);
135 doc._rev = result.rev;
136 return result;
137- }
138+ };
139
140 // Open a document from the database
141 this.open = function(docId, options) {
142@@ -64,7 +69,7 @@
143 return null;
144 CouchDB.maybeThrowError(this.last_req);
145 return JSON.parse(this.last_req.responseText);
146- }
147+ };
148
149 // Deletes a document from the database
150 this.deleteDoc = function(doc) {
151@@ -74,7 +79,7 @@
152 doc._rev = result.rev; //record rev in input document
153 doc._deleted = true;
154 return result;
155- }
156+ };
157
158 // Deletes an attachment from a document
159 this.deleteDocAttachment = function(doc, attachment_name) {
160@@ -83,17 +88,17 @@
161 var result = JSON.parse(this.last_req.responseText);
162 doc._rev = result.rev; //record rev in input document
163 return result;
164- }
165+ };
166
167 this.bulkSave = function(docs, options) {
168 // first prepoulate the UUIDs for new documents
169- var newCount = 0
170+ var newCount = 0;
171 for (var i=0; i<docs.length; i++) {
172 if (docs[i]._id == undefined)
173 newCount++;
174 }
175 var newUuids = CouchDB.newUuids(docs.length);
176- var newCount = 0
177+ var newCount = 0;
178 for (var i=0; i<docs.length; i++) {
179 if (docs[i]._id == undefined)
180 docs[i]._id = newUuids.pop();
181@@ -118,17 +123,17 @@
182 }
183 return results;
184 }
185- }
186+ };
187
188 this.ensureFullCommit = function() {
189 this.last_req = this.request("POST", this.uri + "_ensure_full_commit");
190 CouchDB.maybeThrowError(this.last_req);
191 return JSON.parse(this.last_req.responseText);
192- }
193+ };
194
195 // Applies the map function to the contents of database and returns the results.
196- this.query = function(mapFun, reduceFun, options, keys) {
197- var body = {language: "javascript"};
198+ this.query = function(mapFun, reduceFun, options, keys, language) {
199+ var body = {language: language || "javascript"};
200 if(keys) {
201 body.keys = keys ;
202 }
203@@ -144,13 +149,14 @@
204 body.options = options.options;
205 delete options.options;
206 }
207- this.last_req = this.request("POST", this.uri + "_temp_view" + encodeOptions(options), {
208+ var uri = this.uri + "_temp_view" + encodeOptions(options);
209+ this.last_req = this.request("POST", uri , {
210 headers: {"Content-Type": "application/json"},
211 body: JSON.stringify(body)
212 });
213 CouchDB.maybeThrowError(this.last_req);
214 return JSON.parse(this.last_req.responseText);
215- }
216+ };
217
218 this.view = function(viewname, options, keys) {
219 var viewParts = viewname.split('/');
220@@ -168,14 +174,27 @@
221 return null;
222 CouchDB.maybeThrowError(this.last_req);
223 return JSON.parse(this.last_req.responseText);
224- }
225+ };
226
227 // gets information about the database
228 this.info = function() {
229 this.last_req = this.request("GET", this.uri);
230 CouchDB.maybeThrowError(this.last_req);
231 return JSON.parse(this.last_req.responseText);
232- }
233+ };
234+
235+ // gets information about a design doc
236+ this.designInfo = function(docid) {
237+ this.last_req = this.request("GET", this.uri + docid + "/_info");
238+ CouchDB.maybeThrowError(this.last_req);
239+ return JSON.parse(this.last_req.responseText);
240+ };
241+
242+ this.viewCleanup = function() {
243+ this.last_req = this.request("POST", this.uri + "_view_cleanup");
244+ CouchDB.maybeThrowError(this.last_req);
245+ return JSON.parse(this.last_req.responseText);
246+ };
247
248 this.allDocs = function(options,keys) {
249 if(!keys) {
250@@ -188,7 +207,7 @@
251 }
252 CouchDB.maybeThrowError(this.last_req);
253 return JSON.parse(this.last_req.responseText);
254- }
255+ };
256
257 this.designDocs = function() {
258 return this.allDocs({startkey:"_design", endkey:"_design0"});
259@@ -206,13 +225,13 @@
260 }
261 CouchDB.maybeThrowError(req);
262 return JSON.parse(req.responseText);
263- }
264+ };
265
266 this.compact = function() {
267 this.last_req = this.request("POST", this.uri + "_compact");
268 CouchDB.maybeThrowError(this.last_req);
269 return JSON.parse(this.last_req.responseText);
270- }
271+ };
272
273 this.setDbProperty = function(propId, propValue) {
274 this.last_req = this.request("PUT", this.uri + propId,{
275@@ -220,13 +239,13 @@
276 });
277 CouchDB.maybeThrowError(this.last_req);
278 return JSON.parse(this.last_req.responseText);
279- }
280+ };
281
282 this.getDbProperty = function(propId) {
283 this.last_req = this.request("GET", this.uri + propId);
284 CouchDB.maybeThrowError(this.last_req);
285 return JSON.parse(this.last_req.responseText);
286- }
287+ };
288
289 this.setAdmins = function(adminsArray) {
290 this.last_req = this.request("PUT", this.uri + "_admins",{
291@@ -234,18 +253,18 @@
292 });
293 CouchDB.maybeThrowError(this.last_req);
294 return JSON.parse(this.last_req.responseText);
295- }
296+ };
297
298 this.getAdmins = function() {
299 this.last_req = this.request("GET", this.uri + "_admins");
300 CouchDB.maybeThrowError(this.last_req);
301 return JSON.parse(this.last_req.responseText);
302- }
303+ };
304
305 // Convert a options object to an url query string.
306 // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"'
307 function encodeOptions(options) {
308- var buf = []
309+ var buf = [];
310 if (typeof(options) == "object" && options !== null) {
311 for (var name in options) {
312 if (!options.hasOwnProperty(name)) continue;
313@@ -278,22 +297,82 @@
314 return object1;
315 }
316
317-
318 }
319
320-CouchDB.PORT_NUMBER = 5984; // default
321-
322 // this is the XMLHttpRequest object from last request made by the following
323 // CouchDB.* functions (except for calls to request itself).
324 // Use this from callers to check HTTP status or header values of requests.
325 CouchDB.last_req = null;
326
327+CouchDB.login = function(username, password) {
328+ CouchDB.last_req = CouchDB.request("POST", "/_session", {
329+ headers: {"Content-Type": "application/x-www-form-urlencoded",
330+ "X-CouchDB-WWW-Authenticate": "Cookie"},
331+ body: "username=" + encodeURIComponent(username) + "&password=" + encodeURIComponent(password)
332+ });
333+ return JSON.parse(CouchDB.last_req.responseText);
334+};
335+
336+CouchDB.logout = function() {
337+ CouchDB.last_req = CouchDB.request("DELETE", "/_session", {
338+ headers: {"Content-Type": "application/x-www-form-urlencoded",
339+ "X-CouchDB-WWW-Authenticate": "Cookie"}
340+ });
341+ return JSON.parse(CouchDB.last_req.responseText);
342+};
343+
344+CouchDB.createUser = function(username, password, email, roles, basicAuth) {
345+ var roles_str = "";
346+ if (roles) {
347+ for (var i=0; i< roles.length; i++) {
348+ roles_str += "&roles=" + encodeURIComponent(roles[i]);
349+ }
350+ }
351+ var headers = {"Content-Type": "application/x-www-form-urlencoded"};
352+ if (basicAuth) {
353+ headers['Authorization'] = basicAuth;
354+ } else {
355+ headers['X-CouchDB-WWW-Authenticate'] = 'Cookie';
356+ }
357+
358+ CouchDB.last_req = CouchDB.request("POST", "/_user/", {
359+ headers: headers,
360+ body: "username=" + encodeURIComponent(username) + "&password=" + encodeURIComponent(password)
361+ + "&email="+ encodeURIComponent(email)+ roles_str
362+
363+ });
364+ return JSON.parse(CouchDB.last_req.responseText);
365+};
366+
367+CouchDB.updateUser = function(username, email, roles, password, old_password) {
368+ var roles_str = "";
369+ if (roles) {
370+ for (var i=0; i< roles.length; i++) {
371+ roles_str += "&roles=" + encodeURIComponent(roles[i]);
372+ }
373+ }
374+
375+ var body = "email="+ encodeURIComponent(email)+ roles_str;
376+
377+ if (typeof(password) != "undefined" && password)
378+ body += "&password=" + password;
379+
380+ if (typeof(old_password) != "undefined" && old_password)
381+ body += "&old_password=" + old_password;
382+
383+ CouchDB.last_req = CouchDB.request("PUT", "/_user/"+encodeURIComponent(username), {
384+ headers: {"Content-Type": "application/x-www-form-urlencoded",
385+ "X-CouchDB-WWW-Authenticate": "Cookie"},
386+ body: body
387+ });
388+ return JSON.parse(CouchDB.last_req.responseText);
389+};
390
391 CouchDB.allDbs = function() {
392 CouchDB.last_req = CouchDB.request("GET", "/_all_dbs");
393 CouchDB.maybeThrowError(CouchDB.last_req);
394 return JSON.parse(CouchDB.last_req.responseText);
395-}
396+};
397
398 CouchDB.allDesignDocs = function() {
399 var ddocs = {}, dbs = CouchDB.allDbs();
400@@ -308,7 +387,7 @@
401 CouchDB.last_req = CouchDB.request("GET", "/");
402 CouchDB.maybeThrowError(CouchDB.last_req);
403 return JSON.parse(CouchDB.last_req.responseText).version;
404-}
405+};
406
407 CouchDB.replicate = function(source, target, rep_options) {
408 rep_options = rep_options || {};
409@@ -319,19 +398,23 @@
410 });
411 CouchDB.maybeThrowError(CouchDB.last_req);
412 return JSON.parse(CouchDB.last_req.responseText);
413-}
414+};
415
416-CouchDB.request = function(method, uri, options) {
417- options = options || {};
418- var req = null;
419+CouchDB.newXhr = function() {
420 if (typeof(XMLHttpRequest) != "undefined") {
421- req = new XMLHttpRequest();
422+ return new XMLHttpRequest();
423 } else if (typeof(ActiveXObject) != "undefined") {
424- req = new ActiveXObject("Microsoft.XMLHTTP");
425+ return new ActiveXObject("Microsoft.XMLHTTP");
426 } else {
427 throw new Error("No XMLHTTPRequest support detected");
428 }
429- req.open(method, "http://localhost:" + CouchDB.PORT_NUMBER + uri, false);
430+};
431+
432+CouchDB.request = function(method, uri, options) {
433+ options = options || {};
434+ var req = CouchDB.newXhr();
435+ var computed_uri = "http://localhost:" + CouchDB.port + uri;
436+ req.open(method, computed_uri, false);
437 if (options.headers) {
438 var headers = options.headers;
439 for (var headerName in headers) {
440@@ -341,7 +424,7 @@
441 }
442 req.send(options.body || "");
443 return req;
444-}
445+};
446
447 CouchDB.requestStats = function(module, key, test) {
448 var query_arg = "";
449@@ -351,7 +434,7 @@
450
451 var stat = CouchDB.request("GET", "/_stats/" + module + "/" + key + query_arg).responseText;
452 return JSON.parse(stat)[module][key];
453-}
454+};
455
456 CouchDB.uuids_cache = [];
457
458@@ -373,7 +456,7 @@
459 CouchDB.uuids_cache.concat(result.uuids.slice(0, 100));
460 return result.uuids.slice(100);
461 }
462-}
463+};
464
465 CouchDB.maybeThrowError = function(req) {
466 if (req.status >= 400) {
467@@ -384,7 +467,7 @@
468 }
469 throw result;
470 }
471-}
472+};
473
474 CouchDB.params = function(options) {
475 options = options || {};
476@@ -394,4 +477,4 @@
477 returnArray.push(key + "=" + value);
478 }
479 return returnArray.join("&");
480-}
481+};
482
483=== added file 'content/oauth.js'
484--- content/oauth.js 1970-01-01 00:00:00 +0000
485+++ content/oauth.js 2009-09-25 16:00:27 +0000
486@@ -0,0 +1,515 @@
487+/*
488+ * Copyright 2008 Netflix, Inc.
489+ *
490+ * Licensed under the Apache License, Version 2.0 (the "License");
491+ * you may not use this file except in compliance with the License.
492+ * You may obtain a copy of the License at
493+ *
494+ * http://www.apache.org/licenses/LICENSE-2.0
495+ *
496+ * Unless required by applicable law or agreed to in writing, software
497+ * distributed under the License is distributed on an "AS IS" BASIS,
498+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
499+ * See the License for the specific language governing permissions and
500+ * limitations under the License.
501+ */
502+
503+/* Here's some JavaScript software for implementing OAuth.
504+
505+ This isn't as useful as you might hope. OAuth is based around
506+ allowing tools and websites to talk to each other. However,
507+ JavaScript running in web browsers is hampered by security
508+ restrictions that prevent code running on one website from
509+ accessing data stored or served on another.
510+
511+ Before you start hacking, make sure you understand the limitations
512+ posed by cross-domain XMLHttpRequest.
513+
514+ On the bright side, some platforms use JavaScript as their
515+ language, but enable the programmer to access other web sites.
516+ Examples include Google Gadgets, and Microsoft Vista Sidebar.
517+ For those platforms, this library should come in handy.
518+*/
519+
520+// The HMAC-SHA1 signature method calls b64_hmac_sha1, defined by
521+// http://pajhome.org.uk/crypt/md5/sha1.js
522+
523+/* An OAuth message is represented as an object like this:
524+ {method: "GET", action: "http://server.com/path", parameters: ...}
525+
526+ The parameters may be either a map {name: value, name2: value2}
527+ or an Array of name-value pairs [[name, value], [name2, value2]].
528+ The latter representation is more powerful: it supports parameters
529+ in a specific sequence, or several parameters with the same name;
530+ for example [["a", 1], ["b", 2], ["a", 3]].
531+
532+ Parameter names and values are NOT percent-encoded in an object.
533+ They must be encoded before transmission and decoded after reception.
534+ For example, this message object:
535+ {method: "GET", action: "http://server/path", parameters: {p: "x y"}}
536+ ... can be transmitted as an HTTP request that begins:
537+ GET /path?p=x%20y HTTP/1.0
538+ (This isn't a valid OAuth request, since it lacks a signature etc.)
539+ Note that the object "x y" is transmitted as x%20y. To encode
540+ parameters, you can call OAuth.addToURL, OAuth.formEncode or
541+ OAuth.getAuthorization.
542+
543+ This message object model harmonizes with the browser object model for
544+ input elements of an form, whose value property isn't percent encoded.
545+ The browser encodes each value before transmitting it. For example,
546+ see consumer.setInputs in example/consumer.js.
547+ */
548+var OAuth; if (OAuth == null) OAuth = {};
549+
550+OAuth.setProperties = function setProperties(into, from) {
551+ if (into != null && from != null) {
552+ for (var key in from) {
553+ into[key] = from[key];
554+ }
555+ }
556+ return into;
557+}
558+
559+OAuth.setProperties(OAuth, // utility functions
560+{
561+ percentEncode: function percentEncode(s) {
562+ if (s == null) {
563+ return "";
564+ }
565+ if (s instanceof Array) {
566+ var e = "";
567+ for (var i = 0; i < s.length; ++s) {
568+ if (e != "") e += '&';
569+ e += percentEncode(s[i]);
570+ }
571+ return e;
572+ }
573+ s = encodeURIComponent(s);
574+ // Now replace the values which encodeURIComponent doesn't do
575+ // encodeURIComponent ignores: - _ . ! ~ * ' ( )
576+ // OAuth dictates the only ones you can ignore are: - _ . ~
577+ // Source: http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Functions:encodeURIComponent
578+ s = s.replace(/\!/g, "%21");
579+ s = s.replace(/\*/g, "%2A");
580+ s = s.replace(/\'/g, "%27");
581+ s = s.replace(/\(/g, "%28");
582+ s = s.replace(/\)/g, "%29");
583+ return s;
584+ }
585+,
586+ decodePercent: function decodePercent(s) {
587+ if (s != null) {
588+ // Handle application/x-www-form-urlencoded, which is defined by
589+ // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
590+ s = s.replace(/\+/g, " ");
591+ }
592+ return decodeURIComponent(s);
593+ }
594+,
595+ /** Convert the given parameters to an Array of name-value pairs. */
596+ getParameterList: function getParameterList(parameters) {
597+ if (parameters == null) {
598+ return [];
599+ }
600+ if (typeof parameters != "object") {
601+ return decodeForm(parameters + "");
602+ }
603+ if (parameters instanceof Array) {
604+ return parameters;
605+ }
606+ var list = [];
607+ for (var p in parameters) {
608+ list.push([p, parameters[p]]);
609+ }
610+ return list;
611+ }
612+,
613+ /** Convert the given parameters to a map from name to value. */
614+ getParameterMap: function getParameterMap(parameters) {
615+ if (parameters == null) {
616+ return {};
617+ }
618+ if (typeof parameters != "object") {
619+ return getParameterMap(decodeForm(parameters + ""));
620+ }
621+ if (parameters instanceof Array) {
622+ var map = {};
623+ for (var p = 0; p < parameters.length; ++p) {
624+ var key = parameters[p][0];
625+ if (map[key] === undefined) { // first value wins
626+ map[key] = parameters[p][1];
627+ }
628+ }
629+ return map;
630+ }
631+ return parameters;
632+ }
633+,
634+ getParameter: function getParameter(parameters, name) {
635+ if (parameters instanceof Array) {
636+ for (var p = 0; p < parameters.length; ++p) {
637+ if (parameters[p][0] == name) {
638+ return parameters[p][1]; // first value wins
639+ }
640+ }
641+ } else {
642+ return OAuth.getParameterMap(parameters)[name];
643+ }
644+ return null;
645+ }
646+,
647+ formEncode: function formEncode(parameters) {
648+ var form = "";
649+ var list = OAuth.getParameterList(parameters);
650+ for (var p = 0; p < list.length; ++p) {
651+ var value = list[p][1];
652+ if (value == null) value = "";
653+ if (form != "") form += '&';
654+ form += OAuth.percentEncode(list[p][0])
655+ +'='+ OAuth.percentEncode(value);
656+ }
657+ return form;
658+ }
659+,
660+ decodeForm: function decodeForm(form) {
661+ var list = [];
662+ var nvps = form.split('&');
663+ for (var n = 0; n < nvps.length; ++n) {
664+ var nvp = nvps[n];
665+ if (nvp == "") {
666+ continue;
667+ }
668+ var equals = nvp.indexOf('=');
669+ var name;
670+ var value;
671+ if (equals < 0) {
672+ name = OAuth.decodePercent(nvp);
673+ value = null;
674+ } else {
675+ name = OAuth.decodePercent(nvp.substring(0, equals));
676+ value = OAuth.decodePercent(nvp.substring(equals + 1));
677+ }
678+ list.push([name, value]);
679+ }
680+ return list;
681+ }
682+,
683+ setParameter: function setParameter(message, name, value) {
684+ var parameters = message.parameters;
685+ if (parameters instanceof Array) {
686+ for (var p = 0; p < parameters.length; ++p) {
687+ if (parameters[p][0] == name) {
688+ if (value === undefined) {
689+ parameters.splice(p, 1);
690+ } else {
691+ parameters[p][1] = value;
692+ value = undefined;
693+ }
694+ }
695+ }
696+ if (value !== undefined) {
697+ parameters.push([name, value]);
698+ }
699+ } else {
700+ parameters = OAuth.getParameterMap(parameters);
701+ parameters[name] = value;
702+ message.parameters = parameters;
703+ }
704+ }
705+,
706+ setParameters: function setParameters(message, parameters) {
707+ var list = OAuth.getParameterList(parameters);
708+ for (var i = 0; i < list.length; ++i) {
709+ OAuth.setParameter(message, list[i][0], list[i][1]);
710+ }
711+ }
712+,
713+ /** Fill in parameters to help construct a request message.
714+ This function doesn't fill in every parameter.
715+ The accessor object should be like:
716+ {consumerKey:'foo', consumerSecret:'bar', accessorSecret:'nurn', token:'krelm', tokenSecret:'blah'}
717+ The accessorSecret property is optional.
718+ */
719+ completeRequest: function completeRequest(message, accessor) {
720+ if (message.method == null) {
721+ message.method = "GET";
722+ }
723+ var map = OAuth.getParameterMap(message.parameters);
724+ if (map.oauth_consumer_key == null) {
725+ OAuth.setParameter(message, "oauth_consumer_key", accessor.consumerKey || "");
726+ }
727+ if (map.oauth_token == null && accessor.token != null) {
728+ OAuth.setParameter(message, "oauth_token", accessor.token);
729+ }
730+ if (map.oauth_version == null) {
731+ OAuth.setParameter(message, "oauth_version", "1.0");
732+ }
733+ if (map.oauth_timestamp == null) {
734+ OAuth.setParameter(message, "oauth_timestamp", OAuth.timestamp());
735+ }
736+ if (map.oauth_nonce == null) {
737+ OAuth.setParameter(message, "oauth_nonce", OAuth.nonce(6));
738+ }
739+ OAuth.SignatureMethod.sign(message, accessor);
740+ }
741+,
742+ setTimestampAndNonce: function setTimestampAndNonce(message) {
743+ OAuth.setParameter(message, "oauth_timestamp", OAuth.timestamp());
744+ OAuth.setParameter(message, "oauth_nonce", OAuth.nonce(6));
745+ }
746+,
747+ addToURL: function addToURL(url, parameters) {
748+ newURL = url;
749+ if (parameters != null) {
750+ var toAdd = OAuth.formEncode(parameters);
751+ if (toAdd.length > 0) {
752+ var q = url.indexOf('?');
753+ if (q < 0) newURL += '?';
754+ else newURL += '&';
755+ newURL += toAdd;
756+ }
757+ }
758+ return newURL;
759+ }
760+,
761+ /** Construct the value of the Authorization header for an HTTP request. */
762+ getAuthorizationHeader: function getAuthorizationHeader(realm, parameters) {
763+ var header = 'OAuth realm="' + OAuth.percentEncode(realm) + '"';
764+ var list = OAuth.getParameterList(parameters);
765+ for (var p = 0; p < list.length; ++p) {
766+ var parameter = list[p];
767+ var name = parameter[0];
768+ if (name.indexOf("oauth_") == 0) {
769+ header += ',' + OAuth.percentEncode(name) + '="' + OAuth.percentEncode(parameter[1]) + '"';
770+ }
771+ }
772+ return header;
773+ }
774+,
775+ timestamp: function timestamp() {
776+ var d = new Date();
777+ return Math.floor(d.getTime()/1000);
778+ }
779+,
780+ nonce: function nonce(length) {
781+ var chars = OAuth.nonce.CHARS;
782+ var result = "";
783+ for (var i = 0; i < length; ++i) {
784+ var rnum = Math.floor(Math.random() * chars.length);
785+ result += chars.substring(rnum, rnum+1);
786+ }
787+ return result;
788+ }
789+});
790+
791+OAuth.nonce.CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
792+
793+/** Define a constructor function,
794+ without causing trouble to anyone who was using it as a namespace.
795+ That is, if parent[name] already existed and had properties,
796+ copy those properties into the new constructor.
797+ */
798+OAuth.declareClass = function declareClass(parent, name, newConstructor) {
799+ var previous = parent[name];
800+ parent[name] = newConstructor;
801+ if (newConstructor != null && previous != null) {
802+ for (var key in previous) {
803+ if (key != "prototype") {
804+ newConstructor[key] = previous[key];
805+ }
806+ }
807+ }
808+ return newConstructor;
809+}
810+
811+/** An abstract algorithm for signing messages. */
812+OAuth.declareClass(OAuth, "SignatureMethod", function OAuthSignatureMethod(){});
813+
814+OAuth.setProperties(OAuth.SignatureMethod.prototype, // instance members
815+{
816+ /** Add a signature to the message. */
817+ sign: function sign(message) {
818+ var baseString = OAuth.SignatureMethod.getBaseString(message);
819+ var signature = this.getSignature(baseString);
820+ OAuth.setParameter(message, "oauth_signature", signature);
821+ return signature; // just in case someone's interested
822+ }
823+,
824+ /** Set the key string for signing. */
825+ initialize: function initialize(name, accessor) {
826+ var consumerSecret;
827+ if (accessor.accessorSecret != null
828+ && name.length > 9
829+ && name.substring(name.length-9) == "-Accessor")
830+ {
831+ consumerSecret = accessor.accessorSecret;
832+ } else {
833+ consumerSecret = accessor.consumerSecret;
834+ }
835+ this.key = OAuth.percentEncode(consumerSecret)
836+ +"&"+ OAuth.percentEncode(accessor.tokenSecret);
837+ }
838+});
839+
840+/* SignatureMethod expects an accessor object to be like this:
841+ {tokenSecret: "lakjsdflkj...", consumerSecret: "QOUEWRI..", accessorSecret: "xcmvzc..."}
842+ The accessorSecret property is optional.
843+ */
844+// Class members:
845+OAuth.setProperties(OAuth.SignatureMethod, // class members
846+{
847+ sign: function sign(message, accessor) {
848+ var name = OAuth.getParameterMap(message.parameters).oauth_signature_method;
849+ if (name == null || name == "") {
850+ name = "HMAC-SHA1";
851+ OAuth.setParameter(message, "oauth_signature_method", name);
852+ }
853+ OAuth.SignatureMethod.newMethod(name, accessor).sign(message);
854+ }
855+,
856+ /** Instantiate a SignatureMethod for the given method name. */
857+ newMethod: function newMethod(name, accessor) {
858+ var impl = OAuth.SignatureMethod.REGISTERED[name];
859+ if (impl != null) {
860+ var method = new impl();
861+ method.initialize(name, accessor);
862+ return method;
863+ }
864+ var err = new Error("signature_method_rejected");
865+ var acceptable = "";
866+ for (var r in OAuth.SignatureMethod.REGISTERED) {
867+ if (acceptable != "") acceptable += '&';
868+ acceptable += OAuth.percentEncode(r);
869+ }
870+ err.oauth_acceptable_signature_methods = acceptable;
871+ throw err;
872+ }
873+,
874+ /** A map from signature method name to constructor. */
875+ REGISTERED : {}
876+,
877+ /** Subsequently, the given constructor will be used for the named methods.
878+ The constructor will be called with no parameters.
879+ The resulting object should usually implement getSignature(baseString).
880+ You can easily define such a constructor by calling makeSubclass, below.
881+ */
882+ registerMethodClass: function registerMethodClass(names, classConstructor) {
883+ for (var n = 0; n < names.length; ++n) {
884+ OAuth.SignatureMethod.REGISTERED[names[n]] = classConstructor;
885+ }
886+ }
887+,
888+ /** Create a subclass of OAuth.SignatureMethod, with the given getSignature function. */
889+ makeSubclass: function makeSubclass(getSignatureFunction) {
890+ var superClass = OAuth.SignatureMethod;
891+ var subClass = function() {
892+ superClass.call(this);
893+ };
894+ subClass.prototype = new superClass();
895+ // Delete instance variables from prototype:
896+ // delete subclass.prototype... There aren't any.
897+ subClass.prototype.getSignature = getSignatureFunction;
898+ subClass.prototype.constructor = subClass;
899+ return subClass;
900+ }
901+,
902+ getBaseString: function getBaseString(message) {
903+ var URL = message.action;
904+ var q = URL.indexOf('?');
905+ var parameters;
906+ if (q < 0) {
907+ parameters = message.parameters;
908+ } else {
909+ // Combine the URL query string with the other parameters:
910+ parameters = OAuth.decodeForm(URL.substring(q + 1));
911+ var toAdd = OAuth.getParameterList(message.parameters);
912+ for (var a = 0; a < toAdd.length; ++a) {
913+ parameters.push(toAdd[a]);
914+ }
915+ }
916+ return OAuth.percentEncode(message.method.toUpperCase())
917+ +'&'+ OAuth.percentEncode(OAuth.SignatureMethod.normalizeUrl(URL))
918+ +'&'+ OAuth.percentEncode(OAuth.SignatureMethod.normalizeParameters(parameters));
919+ }
920+,
921+ normalizeUrl: function normalizeUrl(url) {
922+ var uri = OAuth.SignatureMethod.parseUri(url);
923+ var scheme = uri.protocol.toLowerCase();
924+ var authority = uri.authority.toLowerCase();
925+ var dropPort = (scheme == "http" && uri.port == 80)
926+ || (scheme == "https" && uri.port == 443);
927+ if (dropPort) {
928+ // find the last : in the authority
929+ var index = authority.lastIndexOf(":");
930+ if (index >= 0) {
931+ authority = authority.substring(0, index);
932+ }
933+ }
934+ var path = uri.path;
935+ /* This actually produces different signatures from the python lib
936+ if (!path) {
937+ path = "/"; // conforms to RFC 2616 section 3.2.2
938+ }
939+ */
940+ // we know that there is no query and no fragment here.
941+ return scheme + "://" + authority + path;
942+ }
943+,
944+ parseUri: function parseUri (str) {
945+ /* This function was adapted from parseUri 1.2.1
946+ http://stevenlevithan.com/demo/parseuri/js/assets/parseuri.js
947+ */
948+ var o = {key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
949+ parser: {strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/ }};
950+ var m = o.parser.strict.exec(str);
951+ var uri = {};
952+ var i = 14;
953+ while (i--) uri[o.key[i]] = m[i] || "";
954+ return uri;
955+ }
956+,
957+ normalizeParameters: function normalizeParameters(parameters) {
958+ if (parameters == null) {
959+ return "";
960+ }
961+ var list = OAuth.getParameterList(parameters);
962+ var sortable = [];
963+ for (var p = 0; p < list.length; ++p) {
964+ var nvp = list[p];
965+ if (nvp[0] == "oauth_signature" || nvp[0] == "realm") {
966+ continue;
967+ } else {
968+ sortable.push([ OAuth.percentEncode(nvp[0])
969+ + " " // because it comes before any character that can appear in a percentEncoded string.
970+ + OAuth.percentEncode(nvp[1])
971+ , nvp]);
972+ }
973+ }
974+ sortable.sort(function(a,b) {
975+ if (a[0] < b[0]) return -1;
976+ if (a[0] > b[0]) return 1;
977+ return 0;
978+ });
979+ var sorted = [];
980+ for (var s = 0; s < sortable.length; ++s) {
981+ sorted.push(sortable[s][1]);
982+ }
983+ return OAuth.formEncode(sorted);
984+ }
985+});
986+
987+OAuth.SignatureMethod.registerMethodClass(["PLAINTEXT", "PLAINTEXT-Accessor"],
988+ OAuth.SignatureMethod.makeSubclass(
989+ function getSignature(baseString) {
990+ return this.key;
991+ }
992+ ));
993+
994+OAuth.SignatureMethod.registerMethodClass(["HMAC-SHA1", "HMAC-SHA1-Accessor"],
995+ OAuth.SignatureMethod.makeSubclass(
996+ function getSignature(baseString) {
997+ b64pad = '=';
998+ var signature = b64_hmac_sha1(this.key, baseString);
999+ return signature;
1000+ }
1001+ ));
1002
1003=== added file 'content/sha1.js'
1004--- content/sha1.js 1970-01-01 00:00:00 +0000
1005+++ content/sha1.js 2009-09-25 16:00:27 +0000
1006@@ -0,0 +1,202 @@
1007+/*
1008+ * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
1009+ * in FIPS PUB 180-1
1010+ * Version 2.1a Copyright Paul Johnston 2000 - 2002.
1011+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
1012+ * Distributed under the BSD License
1013+ * See http://pajhome.org.uk/crypt/md5 for details.
1014+ */
1015+
1016+/*
1017+ * Configurable variables. You may need to tweak these to be compatible with
1018+ * the server-side, but the defaults work in most cases.
1019+ */
1020+var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
1021+var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
1022+var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
1023+
1024+/*
1025+ * These are the functions you'll usually want to call
1026+ * They take string arguments and return either hex or base-64 encoded strings
1027+ */
1028+function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
1029+function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
1030+function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
1031+function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
1032+function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
1033+function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
1034+
1035+/*
1036+ * Perform a simple self-test to see if the VM is working
1037+ */
1038+function sha1_vm_test()
1039+{
1040+ return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
1041+}
1042+
1043+/*
1044+ * Calculate the SHA-1 of an array of big-endian words, and a bit length
1045+ */
1046+function core_sha1(x, len)
1047+{
1048+ /* append padding */
1049+ x[len >> 5] |= 0x80 << (24 - len % 32);
1050+ x[((len + 64 >> 9) << 4) + 15] = len;
1051+
1052+ var w = Array(80);
1053+ var a = 1732584193;
1054+ var b = -271733879;
1055+ var c = -1732584194;
1056+ var d = 271733878;
1057+ var e = -1009589776;
1058+
1059+ for(var i = 0; i < x.length; i += 16)
1060+ {
1061+ var olda = a;
1062+ var oldb = b;
1063+ var oldc = c;
1064+ var oldd = d;
1065+ var olde = e;
1066+
1067+ for(var j = 0; j < 80; j++)
1068+ {
1069+ if(j < 16) w[j] = x[i + j];
1070+ else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
1071+ var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
1072+ safe_add(safe_add(e, w[j]), sha1_kt(j)));
1073+ e = d;
1074+ d = c;
1075+ c = rol(b, 30);
1076+ b = a;
1077+ a = t;
1078+ }
1079+
1080+ a = safe_add(a, olda);
1081+ b = safe_add(b, oldb);
1082+ c = safe_add(c, oldc);
1083+ d = safe_add(d, oldd);
1084+ e = safe_add(e, olde);
1085+ }
1086+ return Array(a, b, c, d, e);
1087+
1088+}
1089+
1090+/*
1091+ * Perform the appropriate triplet combination function for the current
1092+ * iteration
1093+ */
1094+function sha1_ft(t, b, c, d)
1095+{
1096+ if(t < 20) return (b & c) | ((~b) & d);
1097+ if(t < 40) return b ^ c ^ d;
1098+ if(t < 60) return (b & c) | (b & d) | (c & d);
1099+ return b ^ c ^ d;
1100+}
1101+
1102+/*
1103+ * Determine the appropriate additive constant for the current iteration
1104+ */
1105+function sha1_kt(t)
1106+{
1107+ return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
1108+ (t < 60) ? -1894007588 : -899497514;
1109+}
1110+
1111+/*
1112+ * Calculate the HMAC-SHA1 of a key and some data
1113+ */
1114+function core_hmac_sha1(key, data)
1115+{
1116+ var bkey = str2binb(key);
1117+ if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);
1118+
1119+ var ipad = Array(16), opad = Array(16);
1120+ for(var i = 0; i < 16; i++)
1121+ {
1122+ ipad[i] = bkey[i] ^ 0x36363636;
1123+ opad[i] = bkey[i] ^ 0x5C5C5C5C;
1124+ }
1125+
1126+ var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
1127+ return core_sha1(opad.concat(hash), 512 + 160);
1128+}
1129+
1130+/*
1131+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
1132+ * to work around bugs in some JS interpreters.
1133+ */
1134+function safe_add(x, y)
1135+{
1136+ var lsw = (x & 0xFFFF) + (y & 0xFFFF);
1137+ var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
1138+ return (msw << 16) | (lsw & 0xFFFF);
1139+}
1140+
1141+/*
1142+ * Bitwise rotate a 32-bit number to the left.
1143+ */
1144+function rol(num, cnt)
1145+{
1146+ return (num << cnt) | (num >>> (32 - cnt));
1147+}
1148+
1149+/*
1150+ * Convert an 8-bit or 16-bit string to an array of big-endian words
1151+ * In 8-bit function, characters >255 have their hi-byte silently ignored.
1152+ */
1153+function str2binb(str)
1154+{
1155+ var bin = Array();
1156+ var mask = (1 << chrsz) - 1;
1157+ for(var i = 0; i < str.length * chrsz; i += chrsz)
1158+ bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
1159+ return bin;
1160+}
1161+
1162+/*
1163+ * Convert an array of big-endian words to a string
1164+ */
1165+function binb2str(bin)
1166+{
1167+ var str = "";
1168+ var mask = (1 << chrsz) - 1;
1169+ for(var i = 0; i < bin.length * 32; i += chrsz)
1170+ str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
1171+ return str;
1172+}
1173+
1174+/*
1175+ * Convert an array of big-endian words to a hex string.
1176+ */
1177+function binb2hex(binarray)
1178+{
1179+ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
1180+ var str = "";
1181+ for(var i = 0; i < binarray.length * 4; i++)
1182+ {
1183+ str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
1184+ hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
1185+ }
1186+ return str;
1187+}
1188+
1189+/*
1190+ * Convert an array of big-endian words to a base-64 string
1191+ */
1192+function binb2b64(binarray)
1193+{
1194+ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1195+ var str = "";
1196+ for(var i = 0; i < binarray.length * 4; i += 3)
1197+ {
1198+ var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16)
1199+ | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
1200+ | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
1201+ for(var j = 0; j < 4; j++)
1202+ {
1203+ if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
1204+ else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
1205+ }
1206+ }
1207+ return str;
1208+}
1209
1210=== modified file 'content/sync.js'
1211--- content/sync.js 2009-08-18 14:17:30 +0000
1212+++ content/sync.js 2009-09-25 16:00:27 +0000
1213@@ -4,443 +4,714 @@
1214 * This program is free software: you can redistribute it and/or modify it
1215 * under the terms of the GNU General Public License version 3, as published
1216 * by the Free Software Foundation.
1217- *
1218+ *
1219 * This program is distributed in the hope that it will be useful, but
1220 * WITHOUT ANY WARRANTY; without even the implied warranties of
1221 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1222 * PURPOSE. See the GNU General Public License for more details.
1223- *
1224+ *
1225 * You should have received a copy of the GNU General Public License along
1226 * with this program. If not, see <http://www.gnu.org/licenses/>.
1227 */
1228 /* Lots and lots of debugging information */
1229+
1230 var Bindwood = {
1231- bookmarksService: Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"]
1232- .getService(Components.interfaces.nsINavBookmarksService),
1233- uuidService: Components.classes["@mozilla.org/uuid-generator;1"]
1234- .getService(Components.interfaces.nsIUUIDGenerator),
1235- annotationService: Components.classes["@mozilla.org/browser/annotation-service;1"]
1236- .getService(Components.interfaces.nsIAnnotationService),
1237- consoleService: Components.classes["@mozilla.org/consoleservice;1"]
1238- .getService(Components.interfaces.nsIConsoleService),
1239- historyService: Components.classes["@mozilla.org/browser/nav-history-service;1"]
1240- .getService(Components.interfaces.nsINavHistoryService),
1241- ioService: Components.classes["@mozilla.org/network/io-service;1"]
1242- .getService(Components.interfaces.nsIIOService),
1243+ bookmarksService: Cc["@mozilla.org/browser/nav-bookmarks-service;1"]
1244+ .getService(Ci.nsINavBookmarksService),
1245+ uuidService: Cc["@mozilla.org/uuid-generator;1"]
1246+ .getService(Ci.nsIUUIDGenerator),
1247+ annotationService: Cc["@mozilla.org/browser/annotation-service;1"]
1248+ .getService(Ci.nsIAnnotationService),
1249+ consoleService: Cc["@mozilla.org/consoleservice;1"]
1250+ .getService(Ci.nsIConsoleService),
1251+ historyService: Cc["@mozilla.org/browser/nav-history-service;1"]
1252+ .getService(Ci.nsINavHistoryService),
1253+ ioService: Cc["@mozilla.org/network/io-service;1"]
1254+ .getService(Ci.nsIIOService),
1255+ envService: Cc["@mozilla.org/process/environment;1"]
1256+ .getService(Ci.nsIEnvironment),
1257+ directoryService: Cc["@mozilla.org/file/directory_service;1"]
1258+ .getService(Ci.nsIProperties),
1259+ windowService: Cc["@mozilla.org/appshell/window-mediator;1"]
1260+ .getService(Ci.nsIWindowMediator),
1261
1262+ push: 'ENABLED', // Start off enabled
1263 annotationKey: "bindwood/uuid",
1264 uuidItemIdMap: {},
1265
1266- generateUUIDString: function() {
1267- return Bindwood.uuidService.generateUUID().toString();
1268- },
1269-
1270- annotateItemWithUUID: function(itemId, uuid) {
1271- var uuid = uuid ? uuid : Bindwood.generateUUIDString();
1272- Bindwood.annotationService.setItemAnnotation(itemId, Bindwood.annotationKey, uuid, 0, Bindwood.annotationService.EXPIRE_NEVER);
1273- // Whenever we create a new UUID, stash it and the itemId in
1274- // our local cache.
1275- Bindwood.uuidItemIdMap[uuid] = itemId;
1276- return uuid;
1277- },
1278-
1279- itemIdForUUID: function(uuid) {
1280- // First, try to look it up in our local cache, barring that
1281- // (which shouldn't happen), look it up slowly.
1282- var itemId = Bindwood.uuidItemIdMap[uuid];
1283-
1284- if (!itemId) {
1285- var items = Bindwood.annotationService.getItemsWithAnnotation(Bindwood.annotationKey, {});
1286- for (var i = 0; i < items.length; i++) {
1287- if (Bindwood.annotationService.getItemAnnotation(items[i], Bindwood.annotationKey) == uuid) {
1288- Bindwood.uuidItemIdMap[uuid] = itemId = items[i];
1289- break;
1290- }
1291- }
1292- }
1293- return itemId;
1294- },
1295-
1296- uuidForItemId: function(itemId) {
1297- // Try to look up the uuid, and failing that, assign a new one
1298- // and return it.
1299- var uuid;
1300- var found = false;
1301- try {
1302- uuid = Bindwood.annotationService.getItemAnnotation(itemId, Bindwood.annotationKey);
1303- found = true;
1304- } catch(e) {
1305- uuid = Bindwood.annotateItemWithUUID(itemId, null);
1306- }
1307-
1308- return uuid;
1309- },
1310-
1311+ // Debugging/Console Behavior
1312 writeMessage: function(aMessage) {
1313 // convenience method for logging. Way better than alert()s.
1314- Bindwood.consoleService.logStringMessage("Bindwood: " + aMessage);
1315+ if (Bindwood.envService.exists("BINDWOOD_DEBUG")) {
1316+ Bindwood.consoleService.logStringMessage("Bindwood: " + aMessage);
1317+ }
1318 },
1319
1320 writeError: function(aMessage, e) {
1321- Bindwood.writeMessage(aMessage + "message: '" + e.message + "', reason: '" + e.reason + "', description: '" + e.description + "', error: '" + e.error + "'");
1322- },
1323-
1324-
1325-
1326+ // This should fire whether we're in DEBUG or not
1327+ Bindwood.consoleService.logStringMessage("Bindwood: " + aMessage + " message: '" + e.message + "', reason: '" + e.reason + "', description: '" + e.description + "', error: '" + e.error + "'");
1328+ },
1329+
1330+ extractProfileName: function(path) {
1331+ // We want the last part of the profile path
1332+ // ('default' for '/home/zbir/.mozilla/firefox/ixlw0nvl.default')
1333+ // For profiles that have not been created via the Profile Manager,
1334+ // we just take the last path segment as the profile name.
1335+ //
1336+ // Actually, there's a degenerate case, where the last segment
1337+ // doesn't have the Firefox-provided random string:
1338+ // '/home/zbir/canonical.com' => 'com'
1339+ // But as I said, this is maybe degenerate.
1340+
1341+ var segments = path.split('/');
1342+ var possible_profile = segments[segments.length - 1];
1343+ var first_period = possible_profile.indexOf('.');
1344+ if (first_period == -1) {
1345+ return possible_profile; // no periods in the last segment, return as is
1346+ } else {
1347+ return possible_profile.substr(first_period + 1);
1348+ }
1349+ },
1350+
1351+ // Setup the environment and go
1352 init: function() {
1353 // Start the process and de-register ourself
1354 // http://forums.mozillazine.org/viewtopic.php?f=19&t=657911&start=0
1355 // It ensures that we're only running that code on the first window.
1356
1357- if(Components.classes["@mozilla.org/appshell/window-mediator;1"]
1358- .getService(Components.interfaces.nsIWindowMediator)
1359- .getEnumerator("").getNext() == window) {
1360- Bindwood.writeMessage("Getting a Couch Port");
1361- Bindwood.getCouchPortNumber(Bindwood.startProcess);
1362+ Bindwood.currentProfile = Bindwood.extractProfileName(Bindwood.directoryService.get('ProfD', Ci.nsIFile).path);
1363+
1364+ if(Bindwood.windowService.getEnumerator("").getNext() == window) {
1365+ Bindwood.getCouchEnvironment(Bindwood.startProcess);
1366 }
1367 },
1368
1369- getCouchPortNumber: function(continueFunction) {
1370+ getCouchEnvironment: function(continueFunction) {
1371 // find the desktop Couch port number by making a D-Bus call
1372 // we call D-Bus by shelling out to a bash script which calls
1373 // it for us, and writes the port number into a temp file
1374
1375 // find OS temp dir to put the tempfile in
1376 // https://developer.mozilla.org/index.php?title=File_I%2F%2FO#Getting_special_files
1377- var tmpdir = Components.classes["@mozilla.org/file/directory_service;1"]
1378- .getService(Components.interfaces.nsIProperties)
1379- .get("TmpD", Components.interfaces.nsIFile);
1380+ var tmpdir = Cc["@mozilla.org/file/directory_service;1"]
1381+ .getService(Ci.nsIProperties)
1382+ .get("TmpD", Ci.nsIFile);
1383 // create a randomly named tempfile in the tempdir
1384- var tmpfile = Components.classes["@mozilla.org/file/local;1"]
1385- .createInstance(Components.interfaces.nsILocalFile);
1386+ var tmpfile = Cc["@mozilla.org/file/local;1"]
1387+ .createInstance(Ci.nsILocalFile);
1388 tmpfile.initWithPath(tmpdir.path + "/desktopcouch." + Math.random());
1389 tmpfile.createUnique(tmpfile.NORMAL_FILE_TYPE, 0600);
1390- Bindwood.writeMessage("Tempfile for Couch port number: " + tmpfile.path);
1391
1392 // find the D-Bus bash script, which is in our extension folder
1393 var MY_ID = "bindwood@ubuntu.com";
1394- var em = Components.classes["@mozilla.org/extensions/manager;1"].
1395- getService(Components.interfaces.nsIExtensionManager);
1396- var dbus_script = em.getInstallLocation(MY_ID).getItemFile(MY_ID, "dbus.sh");
1397- Bindwood.writeMessage("Found path to dbus_script: " + dbus_script.path);
1398+ var em = Cc["@mozilla.org/extensions/manager;1"].
1399+ getService(Ci.nsIExtensionManager);
1400+ var couchdb_env_script = em.getInstallLocation(MY_ID).getItemFile(MY_ID, "couchdb_env.sh");
1401 // create an nsILocalFile for the executable
1402- var nsifile = Components.classes["@mozilla.org/file/local;1"]
1403- .createInstance(Components.interfaces.nsILocalFile);
1404- nsifile.initWithPath(dbus_script.path);
1405+ var nsifile = Cc["@mozilla.org/file/local;1"]
1406+ .createInstance(Ci.nsILocalFile);
1407+ nsifile.initWithPath(couchdb_env_script.path);
1408
1409 // create an nsIProcess2 to execute this bash script
1410- var process = Components.classes["@mozilla.org/process/util;1"]
1411- .createInstance(Components.interfaces.nsIProcess2);
1412+ var process = Cc["@mozilla.org/process/util;1"]
1413+ .createInstance(Ci.nsIProcess2);
1414 process.init(nsifile);
1415
1416 // Run the process, passing the tmpfile path
1417 var args = [tmpfile.path];
1418 process.runAsync(args, args.length, {
1419- observe: function(process, finishState, data) {
1420- var port = 5984;
1421+ observe: function(process, finishState, unused_data) {
1422+ // If the script exists cleanly, we should have a file
1423+ // containing the port couch is running on as well as
1424+ // the various OAuth tokens necessary to talk to it.
1425 if (finishState == "process-finished") {
1426- // read temp file to find port number
1427+ // read temp file to find couch environment
1428 // https://developer.mozilla.org/en/Code_snippets/File_I%2f%2fO#Reading_from_a_file
1429- var data = "";
1430- var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].
1431- createInstance(Components.interfaces.nsIFileInputStream);
1432- var cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
1433- createInstance(Components.interfaces.nsIConverterInputStream);
1434+ var environment;
1435+ var fstream = Cc["@mozilla.org/network/file-input-stream;1"].
1436+ createInstance(Ci.nsIFileInputStream);
1437+ var cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].
1438+ createInstance(Ci.nsIConverterInputStream);
1439 fstream.init(tmpfile, -1, 0, 0);
1440 cstream.init(fstream, "UTF-8", 0, 0);
1441 let (str = {}) {
1442 cstream.readString(-1, str); // read the whole file and put it in str.value
1443- data = str.value;
1444- }
1445+ environment = str.value;
1446+ };
1447 cstream.close(); // this closes fstream
1448- data = data.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
1449- if (/^[0-9]+$/.test(data)) {
1450- port = data;
1451- } else {
1452- Bindwood.writeMessage("D-Bus port data is not a number (" + data + ")");
1453- }
1454+ environment = environment.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
1455 } else {
1456- // fall back to system CouchDB
1457+ // If we fail, we should just return
1458 Bindwood.writeMessage("D-Bus port find failed");
1459 }
1460 tmpfile.remove(false);
1461- continueFunction(port);
1462- }
1463+
1464+ if (environment == 'ENOCOUCH') {
1465+ // No Couch environment found. Just spit out a
1466+ // message and return, stopping Bindwood from
1467+ // doing anything further.
1468+ Bindwood.writeError("No suitable Couch environment found. Not proceeding.", e);
1469+ } else if (environment) {
1470+ // If we don't have a Couch environment, don't bother
1471+ // trying to fall back on the system CouchDB.
1472+ continueFunction(environment);
1473+ } else { // No environment found at all
1474+ return;
1475+ }
1476+ }
1477 });
1478 },
1479
1480- startProcess: function(couchPortNumber) {
1481- Bindwood.writeMessage("Starting process with Couch on port " + couchPortNumber);
1482- CouchDB.PORT_NUMBER = couchPortNumber;
1483+ startProcess: function(couchEnvironment) {
1484+ Bindwood.writeMessage("Starting process with Couch environment: " + couchEnvironment);
1485+
1486+ var env_array = couchEnvironment.split(':');
1487+ var port = env_array[0];
1488+ var consumer_key = env_array[1];
1489+ var consumer_secret = env_array[2];
1490+ var token = env_array[3];
1491+ var token_secret = env_array[4];
1492+
1493+ CouchDB.port = port;
1494+ CouchDB.accessor = {
1495+ consumerSecret: consumer_secret,
1496+ tokenSecret: token_secret
1497+ };
1498+ CouchDB.message = {
1499+ parameters: {
1500+ oauth_callback: "None",
1501+ oauth_consumer_key: consumer_key,
1502+ oauth_signature_method: "HMAC-SHA1",
1503+ oauth_token: token,
1504+ oauth_verifier: "None",
1505+ oauth_version: "1.0"
1506+ }
1507+ };
1508+
1509+ Bindwood.couch = new CouchDB('bookmarks');
1510+
1511 try {
1512 Bindwood.pushBookmarks();
1513 } catch(e) {
1514 Bindwood.writeError("Error when calling pushBookmarks: ", e);
1515 }
1516- Bindwood.createView();
1517+ Bindwood.createViews();
1518 Bindwood.pullBookmarks();
1519 },
1520
1521- createView: function() {
1522- var DDID = "_design/views";
1523- var couch = new CouchDB('bookmarks');
1524- var current_doc;
1525- try {
1526- current_doc = couch.open(DDID);
1527- if (current_doc !== null) {
1528- Bindwood.writeMessage("View already exists; leaving it alone");
1529- } else {
1530- new_doc = {
1531- _id: DDID,
1532- views: {
1533- display: {
1534- map: "function(doc) { " +
1535+ createViews: function() {
1536+ var views = [{ id: "_design/all_bookmarks",
1537+ map: "function(doc) { " +
1538 "var scheme = doc.uri.split(':',1)[0]; " +
1539 "var uri; " +
1540 "if (scheme == 'http' || scheme == 'https') {" +
1541- "uri = doc.uri.split('/')[2];" +
1542- "if (uri.length < 30) {" +
1543- " uri += '/' + " +
1544- "doc.uri.split('/',4)[3].substr(0,30-uri.length) + '...';" +
1545- "}" +
1546+ "uri = doc.uri.split('/')[2];" +
1547+ "if (uri.length < 30) {" +
1548+ " uri += '/' + " +
1549+ "doc.uri.split('/',4)[3].substr(0,30-uri.length) + '...';" +
1550+ "}" +
1551 "} else {" +
1552- "uri = scheme + ' URL';" +
1553- "}" +
1554- "emit(doc.title, uri);" +
1555- "}"
1556- }
1557- }
1558- };
1559- try {
1560- couch.save(new_doc);
1561- } catch(e) {
1562- Bindwood.writeError("Problem saving view: ", e);
1563- }
1564- }
1565+ "uri = scheme + ' URL';" +
1566+ "}" +
1567+ "if (!doc.deleted) {" +
1568+ "emit(doc.title, uri);" +
1569+ "}" +
1570+ "}" },
1571+ { id: "_design/deleted_bookmarks",
1572+ map: "function(doc) { if (doc.deleted) { emit (doc.title, doc.uri); } }" },
1573+ { id: "_design/" + Bindwood.currentProfile,
1574+ map: "function(doc) { var scheme = doc.uri.split(':',1)[0]; var uri; if (scheme == 'http' || scheme == 'https') {uri = doc.uri.split('/')[2];if (uri.length < 30) { uri += '/' + doc.uri.split('/',4)[3].substr(0,30-uri.length) + '...';}} else {uri = scheme + ' URL';}if ((!doc.deleted) && (doc.application_annotations.Firefox.profile == '" + Bindwood.currentProfile + "')) {emit(doc.title, uri);}}" }];
1575+ for (var i = 0; i < views.length; i++) {
1576+ var view_info = views[i];
1577+ var dirty = false;
1578+ try {
1579+ var new_doc;
1580+ var current_doc = Bindwood.couch.open(view_info.id);
1581+ if (current_doc !== null) {
1582+ var old_map = current_doc.views.display.map;
1583+ if (old_map != view_info.map) { // Ours is definitive
1584+ new_doc = current_doc;
1585+ new_doc.views.display.map = view_info.map;
1586+ dirty = true;
1587+ }
1588+ } else {
1589+ new_doc = {
1590+ _id: view_info.id,
1591+ views: {
1592+ display: {
1593+ map: view_info.map
1594+ }
1595+ }
1596+ };
1597+ dirty = true;
1598+ }
1599+ if (dirty) {
1600+ try {
1601+ Bindwood.couch.save(new_doc);
1602+ } catch(e) {
1603+ Bindwood.writeError("Problem saving view: ", e);
1604+ }
1605+ }
1606+ } catch(e) {
1607+ // some kind of error fetching the existing design doc
1608+ Bindwood.writeError("Problem checking for view: ", e);
1609+ }
1610+ }
1611+ },
1612+
1613+ // Looking up records locally
1614+ annotateItemWithUUID: function(itemId, seed_uuid) {
1615+ Bindwood.writeMessage("Annotating an item with a UUID");
1616+ Bindwood.writeMessage("Seeded? " + (seed_uuid ? seed_uuid : "No"));
1617+ var uuid = seed_uuid ? seed_uuid : Bindwood.uuidService.generateUUID().toString();
1618+ Bindwood.writeMessage("UUID We came up with: " + uuid);
1619+ Bindwood.writeMessage("Annotating the item now.");
1620+ Bindwood.annotationService.setItemAnnotation(itemId, Bindwood.annotationKey, uuid, 0, Bindwood.annotationService.EXPIRE_NEVER);
1621+ // Whenever we create a new UUID, stash it and the itemId in
1622+ // our local cache.
1623+ Bindwood.uuidItemIdMap[uuid] = itemId;
1624+ Bindwood.writeMessage("Stashed the new uuid and itemId in our map.");
1625+ return uuid;
1626+ },
1627+
1628+ itemIdForUUID: function(uuid) {
1629+ // First, try to look it up in our local cache, barring that
1630+ // (which shouldn't happen), look it up slowly.
1631+ Bindwood.writeMessage("Looking up itemId for uuid: " + uuid);
1632+ var itemId = Bindwood.uuidItemIdMap[uuid];
1633+
1634+ if (!itemId) {
1635+ Bindwood.writeMessage("Didn't find itemId, looking up annotations");
1636+ var items = Bindwood.annotationService.getItemsWithAnnotation(Bindwood.annotationKey, {});
1637+ var num_items = items.length;
1638+ Bindwood.writeMessage("Looking through " + num_items + " item" + (num_items != 1 ? "s" : ""));
1639+ for (var i = 0; i < items.length; i++) {
1640+ if (Bindwood.annotationService.getItemAnnotation(items[i], Bindwood.annotationKey) == uuid) {
1641+ Bindwood.uuidItemIdMap[uuid] = itemId = items[i];
1642+ Bindwood.writeMessage("Found the matching itemId: " + itemId);
1643+ break;
1644+ }
1645+ }
1646+ if (!itemId) {
1647+ Bindwood.writeMessage("XXX: Still haven't found the right itemId!");
1648+ }
1649+ }
1650+ return itemId;
1651+ },
1652+
1653+ uuidForItemId: function(itemId) {
1654+ // Try to look up the uuid, and failing that, assign a new one
1655+ // and return it.
1656+ Bindwood.writeMessage("Looking up a UUID for itemId: " + itemId);
1657+ var uuid;
1658+ try {
1659+ uuid = Bindwood.annotationService.getItemAnnotation(itemId, Bindwood.annotationKey);
1660+ Bindwood.writeMessage("Found uuid (" + uuid + ") on annotation");
1661 } catch(e) {
1662- // some kind of error fetching the existing design doc
1663- Bindwood.writeError("Problem checking for view: ", e);
1664- }
1665- },
1666-
1667+ Bindwood.writeError("Couldn't find a UUID for itemId: " + itemId, e);
1668+ uuid = Bindwood.makeLocalChangeOnly(
1669+ function() { return Bindwood.annotateItemWithUUID(itemId, null); } );
1670+ Bindwood.writeMessage("Didn't find it, so made it: " + uuid);
1671+ }
1672+
1673+ Bindwood.writeMessage("Returning uuid");
1674+ return uuid;
1675+ },
1676+
1677+ resolveLocalBookmark: function(bm) {
1678+ // This function is only used to resolve bookmarks from Couch with uuids
1679+ var couch_uuid = bm.application_annotations.Firefox.uuid;
1680+ Bindwood.writeMessage("Bookmark from Couch has a uuid (" + couch_uuid + ") so we're looking it up locally");
1681+ var itemId = Bindwood.itemIdForUUID(couch_uuid);
1682+ if (itemId) {
1683+ return itemId;
1684+ } else {
1685+ // This bookmark has a uuid, but it's not one of ours.
1686+ // We need to work out whether (a) it's the same as one
1687+ // of ours but with a different uuid (so we need to
1688+ // make the uuids the same), or (b) it's a new one
1689+ // that happens to have been created on a different
1690+ // machine.
1691+ Bindwood.writeMessage("We didn't find that uuid (" + couch_uuid + ") in the ItemId map, so we'll need to search for it by URI");
1692+ try {
1693+ var uri = Bindwood.ioService.newURI(bm.uri, null, null);
1694+ } catch(e) {
1695+ Bindwood.writeError("Problem creating URI (" + bm.uri + ") for bookmark, skipping: ", e);
1696+ throw e;
1697+ }
1698+ var ids = Bindwood.bookmarksService.getBookmarkIdsForURI(uri, {});
1699+ if (ids.length == 0) {
1700+ Bindwood.writeMessage("Couldn't find a bookmark with that URI, either. Returning.");
1701+ return;
1702+ }
1703+ Bindwood.writeMessage("Returning the first bookmark for that URI");
1704+ return ids[0]; // XXX: Just return the first one. Right now, we don't worry too much about duplicates.
1705+ }
1706+ },
1707+
1708+ couchReadyObjectForNodeInBookmarksFolder: function(node, bookmarksFolderName) {
1709+ var itemId = node.itemId;
1710+
1711+ var title = Bindwood.bookmarksService.getItemTitle(itemId);
1712+ var uuid = Bindwood.uuidForItemId(itemId);
1713+ var folder = bookmarksFolderName;
1714+ var profile = Bindwood.currentProfile;
1715+
1716+ var bookmark = {
1717+ title: title,
1718+ application_annotations: {
1719+ Firefox: {
1720+ uuid: uuid,
1721+ folder: folder,
1722+ profile: profile
1723+ }
1724+ }
1725+ };
1726+
1727+ switch(Bindwood.bookmarksService.getItemType(itemId)) {
1728+ case Bindwood.bookmarksService.TYPE_BOOKMARK:
1729+ bookmark.record_type = "http://www.freedesktop.org/wiki/Specifications/desktopcouch/bookmark";
1730+ bookmark.uri = Bindwood.bookmarksService.getBookmarkURI(itemId).spec;
1731+ break;
1732+ case Bindwood.bookmarksService.TYPE_FOLDER:
1733+ bookmark.record_type = "http://www.freedesktop.org/wiki/Specifications/desktopcouch/folder";
1734+ break;
1735+ case Bindwood.bookmarksService.TYPE_SEPARATOR:
1736+ bookmark.record_type = "http://www.freedesktop.org/wiki/Specifications/desktopcouch/separator";
1737+ break;
1738+ default:
1739+ break;
1740+ }
1741+
1742+ Bindwood.writeMessage("Prepared bookmark (" + uuid + ") for Couch: " + JSON.stringify(bookmark));
1743+ return bookmark;
1744+ },
1745+
1746+ makeLocalChangeOnly: function(func) {
1747+ Bindwood.writeMessage("Temporarily disabling pushing changes to Couch");
1748+ Bindwood.push = 'DISABLED';
1749+ Bindwood.writeMessage("Running locally only: " + func.toString());
1750+ var results = func();
1751+ Bindwood.writeMessage("Reenabling pushing changes to Couch");
1752+ Bindwood.push = 'ENABLED';
1753+ return results;
1754+ },
1755+
1756+ // Back and forth
1757 pushBookmarks: function() {
1758 // Prime the pump, so to speak, by uploading all our local
1759 // bookmarks to CouchDB (if they're not there already).
1760- var couch = new CouchDB('bookmarks');
1761 // Create the DB if it doesn't already exist
1762 try {
1763- couch.createDb();
1764+ Bindwood.couch.createDb();
1765 } catch (e) {
1766- Bindwood.writeError("Error when creating database in pushBookmarks (file_exists is OK here): ", e);
1767- }
1768-
1769- Bindwood.pushBookmarksFromList(Bindwood.bookmarksService.toolbarFolder,
1770- "toolbarFolder", couch);
1771- Bindwood.pushBookmarksFromList(Bindwood.bookmarksService.bookmarksMenuFolder,
1772- "bookmarksMenuFolder", couch);
1773+ if (e.error == 'file_exists') {
1774+ Bindwood.writeMessage("Database already exists. We're okay.");
1775+ } else {
1776+ Bindwood.writeError("Error when creating database in pushBookmarks: ", e);
1777+ }
1778+ }
1779+
1780+ try {
1781+ Bindwood.pushBookmarksFromFolder("toolbarFolder", Bindwood.bookmarksService.toolbarFolder);
1782+ } catch(e) {
1783+ Bindwood.writeError("Error pushing toolbarFolder bookmarks: ", e);
1784+ }
1785+
1786+ try {
1787+ Bindwood.pushBookmarksFromFolder("bookmarksMenuFolder", Bindwood.bookmarksService.bookmarksMenuFolder);
1788+ } catch(e) {
1789+ Bindwood.writeError("Error pushing bookmarksMenuFolder bookmarks: ", e);
1790+ }
1791+
1792+ try {
1793+ Bindwood.pushBookmarksFromFolder("unfiledBookmarksFolder", Bindwood.bookmarksService.unfiledBookmarksFolder);
1794+ } catch(e) {
1795+ Bindwood.writeError("Error pushing unfiledBookmarksFolder bookmarks: ", e);
1796+ }
1797 },
1798
1799- getBookmarksFromList: function(bookmarksList) {
1800- var retval = [];
1801+ getBookmarksFromFolder: function(bookmarksFolderName, bookmarksFolder, accum) {
1802+ Bindwood.writeMessage("Fetching bookmarks from: " + bookmarksFolderName);
1803 var options = Bindwood.historyService.getNewQueryOptions();
1804 var query = Bindwood.historyService.getNewQuery();
1805- query.setFolders([bookmarksList], 1);
1806+ query.setFolders([bookmarksFolder], 1);
1807 var result = Bindwood.historyService.executeQuery(query, options);
1808 var rootNode = result.root;
1809 rootNode.containerOpen = true;
1810 for (var i=0; i<rootNode.childCount; i++) {
1811 var node = rootNode.getChild(i);
1812- if (Bindwood.bookmarksService.getItemType(node.itemId) !=
1813- Bindwood.bookmarksService.TYPE_BOOKMARK) {
1814- continue;
1815- }
1816-
1817- var title = Bindwood.bookmarksService.getItemTitle(node.itemId);
1818- try {
1819- var metadata = Bindwood.bookmarksService.getBookmarkURI(node.itemId);
1820- } catch(e) {
1821- Bindwood.writeError("problem fetching metadata for bookmark '" + title + "': ", e);
1822- continue;
1823- }
1824- var uuid = Bindwood.uuidForItemId(node.itemId);
1825- retval.push({
1826- title: title,
1827- metadata: metadata,
1828- uuid: uuid,
1829- id: node.itemId});
1830+ // If node is a folder, descend into it, looking for its contents
1831+ if (node.type == 6) { // RESULT_TYPE_FOLDER
1832+ Bindwood.writeMessage("Descending into folder: " + node.title);
1833+ accum = accum.concat(Bindwood.getBookmarksFromFolder(node.title, node.itemId, accum));
1834+ } else if (node.type == 0 || node.type == 7) { // RESULT_TYPE_URI or RESULT_TYPE_SEPARATOR
1835+ Bindwood.writeMessage("Adding current node: " + node.title);
1836+ accum.push([node.itemId, Bindwood.couchReadyObjectForNodeInBookmarksFolder(node, bookmarksFolderName)]);
1837+ }
1838 }
1839 rootNode.containerOpen = false;
1840- return retval;
1841+ return accum;
1842 },
1843
1844- pushBookmarksFromList: function(bookmarksList, bookmarksListName, db) {
1845- var bookmarkData = Bindwood.getBookmarksFromList(bookmarksList);
1846+ pushBookmarksFromFolder: function(bookmarksFolderName, bookmarksFolder) {
1847+ Bindwood.writeMessage("Pushing bookmarks from " + bookmarksFolderName);
1848+ var bookmarkData = Bindwood.getBookmarksFromFolder(bookmarksFolderName, bookmarksFolder, new Array());
1849 for (var i = 0; i < bookmarkData.length; i++) {
1850 // find this bookmark in CouchDB
1851- var uuid = Bindwood.uuidForItemId(bookmarkData[i].id);
1852- var uri = bookmarkData[i].metadata.spec;
1853- var title = bookmarkData[i].title;
1854-
1855- var results = db.query(function(doc) {
1856- if (doc.application_annotations &&
1857- doc.application_annotations.Firefox &&
1858- doc.application_annotations.Firefox.uuid) {
1859- emit(doc.application_annotations.Firefox.uuid, doc);
1860- }
1861- }, null, {
1862- startkey: uuid, endkey: uuid
1863- });
1864+ var itemId = bookmarkData[i][0];
1865+ var bookmark = bookmarkData[i][1];
1866+ var uuid = bookmark.application_annotations.Firefox.uuid;
1867+ Bindwood.writeMessage("Bookmark so far: " + JSON.stringify(bookmark));
1868+
1869+ // Even though this happens in annotateItemWithUUID as well,
1870+ // we're priming the cache here for first run.
1871+ Bindwood.uuidItemIdMap[uuid] = itemId;
1872+
1873+ try {
1874+ var results = Bindwood.couch.query(function(doc) {
1875+ if (doc.application_annotations &&
1876+ doc.application_annotations.Firefox &&
1877+ doc.application_annotations.Firefox.uuid) {
1878+ emit(doc.application_annotations.Firefox.uuid, doc);
1879+ }
1880+ }, null, {
1881+ startkey: uuid, endkey: uuid
1882+ });
1883+ } catch(e) {
1884+ Bindwood.writeError("Error querying couch: ", e);
1885+ }
1886
1887 if (results.rows.length === 0) {
1888 // this bookmark is not in CouchDB, so write it
1889- var record = {
1890- record_type: "http://example.com/bookmark",
1891- uri: uri,
1892- title: title,
1893- application_annotations: {
1894- Firefox: {
1895- uuid: uuid,
1896- list: bookmarksListName
1897- }
1898- }
1899- };
1900 try {
1901- db.save(record);
1902+ Bindwood.couch.save(bookmark);
1903+ Bindwood.writeMessage("Saved bookmark (" + uuid + ") to Couch.");
1904 } catch(e) {
1905- Bindwood.writeError("Problem saving bookmark to CouchDB; bookmark is " + JSON.stringify(record) + ": ", e);
1906+ Bindwood.writeError("Problem saving bookmark to CouchDB; bookmark is " + JSON.stringify(bookmark) + ": ", e);
1907 }
1908 } else {
1909+ Bindwood.writeMessage("This bookmark (" + uuid + ") is already in Couch, skipping");
1910 // bookmark is already in CouchDB, so do nothing
1911 }
1912 }
1913 },
1914
1915 pullBookmarks: function() {
1916- var couch = new CouchDB('bookmarks');
1917 // Fetch all bookmark documents from the database
1918- // the query function is evaluated by Couch, which doesn't know
1919+ // The query function is evaluated by Couch, which doesn't know
1920 // what Bindwood.RECORD_TYPE is, so we string-encode it first to
1921 // include the literal value
1922 try {
1923- var rows = couch.query(function (doc) {
1924- if (doc.record_type == "http://example.com/bookmark") {
1925+ var rows = Bindwood.couch.query(function (doc) {
1926+ if (doc.record_type == "http://www.freedesktop.org/wiki/Specifications/desktopcouch/bookmark" &&
1927+ doc.application_annotations &&
1928+ doc.application_annotations.Firefox &&
1929+ doc.application_annotations.Firefox.profile &&
1930+ doc.application_annotations.Firefox.profile == Bindwood.currentProfile) {
1931 emit(doc._id,doc);
1932 }
1933- });
1934+ });
1935 } catch(e) {
1936 Bindwood.writeError("Problem fetching all bookmarks from Couch: ", e);
1937 }
1938 for (var i = 0; i < rows.rows.length; i++) {
1939 var recordid = rows.rows[i].id;
1940+ Bindwood.writeMessage("Pulling record: " + recordid);
1941 var bm = rows.rows[i].value;
1942- if (bm.application_annotations &&
1943- bm.application_annotations.Firefox &&
1944- bm.application_annotations.Firefox.uuid) {
1945- // this bookmark has a uuid, so check its values haven't changed
1946- // find the bookmark with this uuid
1947- var couch_uuid = bm.application_annotations.Firefox.uuid;
1948- Bindwood.writeMessage("Row uuid: " + couch_uuid);
1949- var itemId = Bindwood.itemIdForUUID(couch_uuid);
1950- if (!itemId) {
1951- // This bookmark has a uuid, but it's not one of ours.
1952- // We need to work out whether (a) it's the same as one
1953- // of ours but with a different uuid (so we need to
1954- // make the uuids the same), or (b) it's a new one
1955- // that happens to have been created on a different
1956- // machine.
1957- try {
1958- var uri = Bindwood.ioService.newURI(bm.uri, null, null);
1959- } catch(e) {
1960- Bindwood.writeError("Problem creating URI (" + bm.uri + ") for bookmark: ", e);
1961- continue;
1962- }
1963- var ids = Bindwood.bookmarksService.getBookmarkIdsForURI(uri, {});
1964- if (ids.length > 1) {
1965- // punt for now, too many problems.
1966- } else if (ids.length) {
1967- // Found one local bookmark. Replace its uuid to
1968- // be the one from Couch.
1969- var itemId = ids[0];
1970- var old_uuid = Bindwood.uuidForItemId(itemId);
1971- delete Bindwood.uuidItemIdMap[old_uuid];
1972- Bindwood.annotateItemWithUUID(itemId, couch_uuid);
1973- } else {
1974- /// No local bookmarks
1975- Bindwood.addLocalBookmark(bm, recordid, couch_uuid);
1976- }
1977- } else {
1978- if (bm.deleted) {
1979- // This bookmark exists on Couch, but has been
1980- // flagged for deletion by another Client. We
1981- // want to respect that, and delete it
1982- // locally.
1983- Bindwood.bookmarksService.removeItem(itemId);
1984- } else {
1985- var title = Bindwood.bookmarksService.getItemTitle(itemId);
1986- var metadata = Bindwood.bookmarksService.getBookmarkURI(itemId);
1987- if (title != bm.title) {
1988- Bindwood.bookmarksService.setItemTitle(itemId, bm.title);
1989- }
1990- if (metadata.spec != bm.uri) {
1991- try {
1992- metadata = Bindwood.ioService.newURI(bm.uri, null, null);
1993- Bindwood.bookmarksService.changeBookmarkURI(itemId, metadata);
1994- } catch(e) {
1995- Bindwood.writeError("Problem creating a new URI for bookmark: ", e);
1996- }
1997- }
1998- }
1999- }
2000+
2001+ // First, check to see if the bookmark we've pulled down is flagged as deleted.
2002+ // If so, we should make sure any local copy we have of this bookmark has also been deleted.
2003+ if (bm.deleted) {
2004+ Bindwood.writeMessage("Bookmark in Couch marked as deleted; attempting to delete local copy.");
2005+ Bindwood.deleteLocalBookmark(bm);
2006+ } else if (bm.application_annotations &&
2007+ bm.application_annotations.Firefox &&
2008+ bm.application_annotations.Firefox.uuid) {
2009+ Bindwood.writeMessage("Bookmark in Couch has UUID, merging local copy.");
2010+ Bindwood.mergeLocalBookmark(bm, recordid);
2011 } else {
2012- // This bookmark has no uuid, so create it from fresh
2013- // in Firefox. Passing in null to addLocalBookmar will
2014- // generate a new uuid.
2015+ Bindwood.writeMessage("Bookmark in Couch has no UUID, creating new local copy, annotating, and pushing back.");
2016 Bindwood.addLocalBookmark(bm, recordid, null);
2017 }
2018 }
2019 // reschedule ourself
2020+ Bindwood.writeMessage("Successful run, rescheduling ourself");
2021 setTimeout(Bindwood.pullBookmarks, 30000);
2022 },
2023
2024+ deleteLocalBookmark: function(bm) {
2025+ // If we can't resolve the itemId, even by looking up URI, assume it's already gone.
2026+ var itemId = Bindwood.resolveLocalBookmark(bm);
2027+ if (itemId) {
2028+ Bindwood.makeLocalChangeOnly(
2029+ function() { return Bindwood.bookmarksService.removeItem(itemId); });
2030+ }
2031+ },
2032+
2033+ mergeLocalBookmark: function(bm, recordid) {
2034+ // this bookmark has a uuid, so check its values haven't changed
2035+ // find the bookmark with this uuid
2036+ var itemId = Bindwood.resolveLocalBookmark(bm);
2037+ var couch_uuid = bm.application_annotations.Firefox.uuid;
2038+
2039+ if (itemId) {
2040+ // Found one local bookmark. Replace its uuid to
2041+ // be the one from Couch.
2042+ var old_uuid = Bindwood.uuidForItemId(itemId);
2043+ if (old_uuid != couch_uuid) {
2044+ delete Bindwood.uuidItemIdMap[old_uuid];
2045+ Bindwood.makeLocalChangeOnly(
2046+ function() { return Bindwood.annotateItemWithUUID(itemId, couch_uuid); } );
2047+ }
2048+
2049+ // XXX: I'm still not positive about this. While I'm far more sure that we're seeding
2050+ // Couch properly from the beginning and properly making new bookmarks, I still wonder
2051+ // if we'll see cases where Couch has null for title and uri, and how best to manage
2052+ // them. I'm thinking timestamps. - urbanape
2053+ var title = Bindwood.bookmarksService.getItemTitle(itemId);
2054+ if (title != bm.title) {
2055+ Bindwood.writeMessage("Resetting local title to title from Couch");
2056+ Bindwood.makeLocalChangeOnly(
2057+ function() { return Bindwood.bookmarksService.setItemTitle(itemId, bm.title); });
2058+ }
2059+
2060+ var metadata = Bindwood.bookmarksService.getBookmarkURI(itemId);
2061+ if (metadata.spec != bm.uri) {
2062+ Bindwood.writeMessage("The URI from Couch (" + bm.uri + ") is different from local (" + metadata.spec + ")");
2063+ try {
2064+ var new_uri = Bindwood.ioService.newURI(bm.uri, null, null);
2065+ Bindwood.writeMessage("Creating a new URI for our local bookmark");
2066+ Bindwood.makeLocalChangeOnly(
2067+ function() { return Bindwood.bookmarksService.changeBookmarkURI(itemId, new_uri); });
2068+ } catch(e) {
2069+ Bindwood.writeError("Problem creating a new URI for bookmark: ", e);
2070+ }
2071+ }
2072+ } else {
2073+ /// No local bookmarks
2074+ Bindwood.writeMessage("No local bookmark found, must be a new entry in Couch. Creating locally.");
2075+ Bindwood.addLocalBookmark(bm, recordid, couch_uuid);
2076+ }
2077+ },
2078+
2079 addLocalBookmark: function(bm, recordid, uuid) {
2080- var couch = new CouchDB('bookmarks');
2081- var list;
2082+ // If uuid is present, we only need create the bookmark locally,
2083+ // as it already exists (with uuid) in Couch.
2084+ //
2085+ // If uuid is null, then after we create it locally,
2086+ // we want to annotate it and push back to Couch.
2087+ var folder;
2088 if (bm.application_annotations &&
2089 bm.application_annotations.Firefox &&
2090- bm.application_annotations.Firefox.list) {
2091- switch (bm.application_annotations.Firefox.list) {
2092+ bm.application_annotations.Firefox.folder) {
2093+ switch (bm.application_annotations.Firefox.folder) {
2094 case "toolbarFolder":
2095- list = Bindwood.bookmarksService.toolbarFolder;
2096+ folder = Bindwood.bookmarksService.toolbarFolder;
2097 break;
2098 case "bookmarksMenuFolder":
2099- list = Bindwood.bookmarksService.bookmarksMenuFolder;
2100+ folder = Bindwood.bookmarksService.bookmarksMenuFolder;
2101+ break;
2102+ case "unfiledBookmarksFolder":
2103+ folder = Bindwood.bookmarksService.unfiledBookmarksFolder;
2104 break;
2105 default:
2106- list = Bindwood.bookmarksService.toolbarFolder;
2107 break;
2108 }
2109+ }
2110+
2111+ try {
2112+ var new_uri = Bindwood.ioService.newURI(bm.uri, null, null);
2113+ } catch(e) {
2114+ Bindwood.writeError("Problem creating a new URI for bookmark: ", e);
2115+ }
2116+
2117+ var itemId = Bindwood.makeLocalChangeOnly(
2118+ function() { return Bindwood.bookmarksService.insertBookmark(
2119+ folder, new_uri, -1, bm.title); });
2120+
2121+ if (uuid) { // We were provided a uuid, so no need to send back to Couch, just annotate locally
2122+ Bindwood.makeLocalChangeOnly(
2123+ function() { return Bindwood.annotateItemWithUUID(itemId, uuid); } );
2124+ } else { // This is an unadorned bookmark from Couch. Save it to Couch once we establish its uuid
2125+ var new_uuid = Bindwood.uuidForItemId(itemId);
2126+ var doc = Bindwood.couch.open(recordid);
2127+ if (!doc.application_annotations) {
2128+ doc.application_annotations = {};
2129+ }
2130+ if (!doc.application_annotations.Firefox) {
2131+ doc.application_annotations.Firefox = {};
2132+ }
2133+ doc.application_annotations.Firefox.uuid = new_uuid;
2134+ try {
2135+ Bindwood.couch.save(doc);
2136+ Bindwood.writeMessage("Saved the newly annotated doc back to Couch");
2137+ } catch(e) {
2138+ Bindwood.writeError("Problem writing record for new bookmark: ",e);
2139+ }
2140+ }
2141+ },
2142+
2143+ findDocumentByUUID: function(uuid) {
2144+ Bindwood.writeMessage("Looking up a document in Couch by uuid: " + uuid);
2145+ var results = Bindwood.couch.query(function(doc) {
2146+ if (doc.application_annotations &&
2147+ doc.application_annotations.Firefox &&
2148+ doc.application_annotations.Firefox.uuid) {
2149+ emit(doc.application_annotations.Firefox.uuid, doc);
2150+ }
2151+ }, null, {
2152+ startkey: uuid, endkey: uuid
2153+ });
2154+ if (results.rows.length === 0) {
2155+ Bindwood.writeMessage("Problem finding document");
2156+ return;
2157+ }
2158+ Bindwood.writeMessage("Found: " + JSON.stringify(results));
2159+ return results;
2160+ },
2161+
2162+ updateDocAndSave: function(uuid, attribute, value, callback) {
2163+ Bindwood.writeMessage("Updating a document (" + uuid + ") setting (" + attribute + ") to (" + value + ")");
2164+ // Some attributes that we track should remain inside the application_annotations object
2165+ var attrMap = {
2166+ title: false,
2167+ uri: false,
2168+ deleted: false,
2169+ uuid: true,
2170+ folder: true,
2171+ favicon: true,
2172+ profle: true };
2173+ var results = Bindwood.findDocumentByUUID(uuid);
2174+ if (!results) {
2175+ throw {error: "Could not find document in Couch"};
2176+ }
2177+ var doc = Bindwood.couch.open(results.rows[0].id);
2178+ if (attrMap[attribute.toString()]) { // belongs on annotations
2179+ if (!doc.application_annotations) {
2180+ doc.application_annotations = {};
2181+ }
2182+ if (!doc.application_annotations.Firefox) {
2183+ doc.application_annotations.Firefox = {};
2184+ }
2185+ doc.application_annotations.Firefox[attribute.toString()] = value.toString();
2186 } else {
2187- list = Bindwood.bookmarksService.toolbarFolder;
2188- }
2189- var metadata = Bindwood.ioService.newURI(bm.uri, null, null);
2190-
2191- var itemId = Bindwood.bookmarksService.insertBookmark(list,
2192- metadata, -1, bm.title);
2193- // and then write the new uuid back to the record
2194- var uuid = uuid ? uuid : Bindwood.uuidForItemId(itemId);
2195- var doc = couch.open(recordid);
2196- if (!doc.application_annotations) {
2197- doc.application_annotations = {};
2198- }
2199- if (!doc.application_annotations.Firefox) {
2200- doc.application_annotations.Firefox = {};
2201- }
2202- doc.application_annotations.Firefox.uuid = uuid;
2203+ doc[attribute.toString()] = value.toString();
2204+ }
2205 try {
2206- couch.save(doc);
2207+ var result = Bindwood.couch.save(doc);
2208+ Bindwood.writeMessage("Successfully save document (" + uuid + ") back to Couch.");
2209 } catch(e) {
2210- Bindwood.writeError("Problem writing record for new bookmark: ",e);
2211- }
2212+ Bindwood.writeError("Problem saving document to Couch", e);
2213+ throw e;
2214+ }
2215+
2216+ if (callback) {
2217+ Bindwood.writeMessage("We've got a callback to run: " + callback.toString());
2218+ callback();
2219+ }
2220+
2221+ return result;
2222 },
2223
2224 Observer: {
2225@@ -448,126 +719,156 @@
2226 onItemAdded: function(aItemId, aFolder, aIndex) {
2227 // A bookmark has been added, so we create a blank entry
2228 // in Couch with our local itemId attached.
2229+ Bindwood.writeMessage("A new bookmark was created. Its id is: " + aItemId +
2230+ " at location: " + aIndex +
2231+ " in folder: " + aFolder );
2232 netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
2233
2234- var couch = new CouchDB('bookmarks');
2235-
2236 var uuid = Bindwood.uuidForItemId(aItemId);
2237+ Bindwood.writeMessage("Determined uuid for new bookmark: " + uuid);
2238
2239- var list;
2240+ var folder = "toolbarFolder";
2241 switch(aFolder) {
2242 case Bindwood.bookmarksService.toolbarFolder:
2243- list = "toolbarFolder";
2244+ folder = "toolbarFolder";
2245 break;
2246 case Bindwood.bookmarksService.bookmarksMenuFolder:
2247- list = "bookmarksMenuFolder";
2248+ folder = "bookmarksMenuFolder";
2249+ break;
2250+ case Bindwood.bookmarksService.unfiledBookmarksFolder:
2251+ folder = "unfiledBookmarksFolder";
2252 break;
2253 default:
2254- list = "toolbarFolder";
2255 break;
2256 }
2257+ var uri;
2258+ var title;
2259+
2260+ Bindwood.writeMessage("Going to look up bookmark URI");
2261+ try {
2262+ uri = Bindwood.bookmarksService.getBookmarkURI(aItemId).spec;
2263+ } catch(e) {
2264+ Bindwood.writeError("Problem with something: ", error);
2265+ uri = '';
2266+ }
2267+
2268+ Bindwood.writeMessage("Going to look up bookmark Title");
2269+ try {
2270+ title = Bindwood.bookmarksService.getItemTitle(aItemId);
2271+ } catch(e) {
2272+ Bindwood.writeError("Problem with something: ", error);
2273+ title = '';
2274+ }
2275
2276 var doc = {
2277- record_type: "http://example.com/bookmark",
2278+ record_type: "http://www.freedesktop.org/wiki/Specifications/desktopcouch/bookmark",
2279+ uri: uri,
2280+ title: title,
2281 application_annotations: {
2282 Firefox: {
2283 uuid: uuid,
2284- list: list
2285+ folder: folder,
2286+ profile: Bindwood.currentProfile
2287 }
2288 }
2289 };
2290
2291- try {
2292- var result = couch.save(doc);
2293- } catch(e) {
2294- Bindwood.writeError("Problem saving new bookmark to Couch: ", e);
2295+ Bindwood.writeMessage("Created a minimal record document with our uuid: " + JSON.stringify(doc));
2296+
2297+ switch (Bindwood.push) {
2298+ case 'DISABLED':
2299+ Bindwood.writeMessage("Added, but not saving to Couch.");
2300+ break;
2301+ case 'ENABLED':
2302+ try {
2303+ var result = Bindwood.couch.save(doc);
2304+ Bindwood.writeMessage("Saved new, bare record to Couch.");
2305+ } catch(e) {
2306+ Bindwood.writeError("Problem saving new bookmark to Couch: ", e);
2307+ }
2308+ break;
2309+ default:
2310+ break;
2311 }
2312 },
2313 onBeforeItemRemoved: function(aItemId) {
2314 // A bookmark has been removed. This is called before it's
2315 // been removed locally, though we're passed the itemId,
2316 // which we use to delete from Couch.
2317- netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
2318-
2319- var couch = new CouchDB('bookmarks');
2320-
2321+ Bindwood.writeMessage("Record " + aItemId + " is about to be removed locally.");
2322 var uuid = Bindwood.uuidForItemId(aItemId);
2323
2324- var results = couch.query(function(doc) {
2325- if (doc.application_annotations &&
2326- doc.application_annotations.Firefox &&
2327- doc.application_annotations.Firefox.uuid) {
2328- emit(doc.application_annotations.Firefox.uuid, doc);
2329- }
2330- }, null, {
2331- startkey: uuid, endkey: uuid
2332- });
2333-
2334- if (results.rows.length === 0) {
2335- Bindwood.writeMessage("A bookmark was deleted, but this bookmark isn't in CouchDB. This isn't supposed to happen.");
2336- return;
2337- }
2338-
2339- var doc = couch.open(results.rows[0].id);
2340- // Update the doc in Couch to inform other clients that it
2341- // should be deleted locally.
2342- doc.deleted = true;
2343-
2344- try {
2345- var result = couch.save(doc);
2346- // Also remove from our local cache and remove
2347- // annotation from service.
2348+ switch (Bindwood.push) {
2349+ case 'DISABLED':
2350 delete Bindwood.uuidItemIdMap[uuid];
2351- } catch(e) {
2352- Bindwood.writeError("Problem pushing deleted record to Couch: ", e);
2353+ Bindwood.writeMessage("Deleted from local uuid map, but not saving back to Couch.");
2354+ break;
2355+ case 'ENABLED':
2356+ netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
2357+
2358+ try {
2359+ // Also remove from our local cache and remove
2360+ // annotation from service.
2361+ var result = Bindwood.updateDocAndSave(
2362+ uuid, 'deleted', true,
2363+ function() {
2364+ delete Bindwood.uuidItemIdMap[uuid];
2365+ Bindwood.writeMessage("Deleted local reference in the uuid-itemId mapping."); });
2366+ Bindwood.writeMessage("Saved document back to Couch with deleted flag set.");
2367+ } catch(e) {
2368+ Bindwood.writeError("Problem pushing deleted record to Couch: ", e);
2369+ }
2370+ break;
2371+ default:
2372+ break;
2373 }
2374 },
2375 onItemRemoved: function(aItemId, aFolder, aIndex) {
2376- Bindwood.annotationService.removeItemAnnotation(aItemId, Bindwood.annotationKey);
2377+ // This only happens locally, so there's never a need to push
2378+ Bindwood.makeLocalChangeOnly(
2379+ function() { return Bindwood.annotationService.removeItemAnnotation(aItemId, Bindwood.annotationKey); });
2380+ Bindwood.writeMessage("Removed annotations from bookmark identified by: " + aItemId);
2381 },
2382 onItemChanged: function(aBookmarkId, aProperty, aIsAnnotationProperty, aValue) {
2383 // A property of a bookmark has changed. On multiple
2384 // property updates, this will be called multiple times,
2385 // once per property (i.e., for title and URI)
2386- netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
2387-
2388- var couch = new CouchDB('bookmarks');
2389-
2390- var uuid = Bindwood.uuidForItemId(aBookmarkId);
2391-
2392- var results = couch.query(function(doc) {
2393- if (doc.application_annotations &&
2394- doc.application_annotations.Firefox &&
2395- doc.application_annotations.Firefox.uuid) {
2396- emit(doc.application_annotations.Firefox.uuid, doc);
2397+ if (!aIsAnnotationProperty) { // We only care if these are bookmark properties (for right now)
2398+ Bindwood.writeMessage("A property (" + aProperty + ") on bookmark id: " + aBookmarkId + " has been set to: " + aValue);
2399+ var uuid = Bindwood.uuidForItemId(aBookmarkId);
2400+
2401+ switch (Bindwood.push) {
2402+ case 'DISABLED':
2403+ Bindwood.writeMessage("Updated, but not saving back to Couch.");
2404+ break;
2405+ case 'ENABLED':
2406+ Bindwood.writeMessage("We will push this change back to Couch.");
2407+ netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
2408+ try {
2409+ var result = Bindwood.updateDocAndSave(
2410+ uuid, aProperty.toString(), aValue.toString(),
2411+ function() { Bindwood.writeMessage("Saved the document back to Couch"); });
2412+ } catch(e) {
2413+ Bindwood.writeError("Problem saving updated bookmark to Couch: ", e);
2414+ }
2415+ break;
2416+ default:
2417+ break;
2418 }
2419- }, null, {
2420- startkey: uuid, endkey: uuid
2421- });
2422-
2423- if (results.rows.length === 0) {
2424- Bindwood.writeMessage("a bookmark has changed, but this bookmark isn't in CouchDB. this isn't supposed to happen.");
2425- return;
2426- }
2427-
2428- var doc = couch.open(results.rows[0].id);
2429- doc[aProperty.toString()] = aValue.toString();
2430-
2431- try {
2432- var result = couch.save(doc);
2433- } catch(e) {
2434- Bindwood.writeError("Problem saving updated bookmark to Couch: ", e);
2435 }
2436 },
2437
2438+ // Currently unhandled
2439 onBeginUpdateBatch: function() {},
2440 onEndUpdateBatch: function() {},
2441+ onItemMoved: function(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex) {},
2442 onItemVisited: function(aBookmarkId, aVisitID, time) {},
2443- onItemMoved: function(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex) {},
2444+
2445+ // Query Interface
2446 QueryInterface: function(iid) {
2447- if (iid.equals(Components.interfaces.nsINavBookmarkObserver) ||
2448- iid.equals(Components.interfaces.nsINavBookmarkObserver_MOZILLA_1_9_1_ADDITIONS) ||
2449- iid.equals(Components.interfaces.nsISupports)) {
2450+ if (iid.equals(Ci.nsINavBookmarkObserver) ||
2451+ iid.equals(Ci.nsINavBookmarkObserver_MOZILLA_1_9_1_ADDITIONS) ||
2452+ iid.equals(Ci.nsISupports)) {
2453 return this;
2454 }
2455 throw Cr.NS_ERROR_NO_INTERFACE;
2456
2457=== renamed file 'dbus.sh' => 'couchdb_env.sh'
2458--- dbus.sh 2009-08-18 14:17:30 +0000
2459+++ couchdb_env.sh 2009-09-25 16:00:27 +0000
2460@@ -13,12 +13,16 @@
2461 # You should have received a copy of the GNU General Public License along
2462 # with this program. If not, see <http://www.gnu.org/licenses/>.
2463 OUT=$1
2464-PORT=$(dbus-send --session --dest=org.desktopcouch.CouchDB --print-reply --type=method_call / org.desktopcouch.CouchDB.getPort 2>/dev/null | grep int32 | awk '{print $2}' )
2465+
2466+PORT=$(dbus-send --session --dest=org.desktopcouch.CouchDB --print-reply --type=method_call / org.desktopcouch.CouchDB.getPort 2>/dev/null | grep int32 | awk '{print $2}')
2467+
2468+TOKENS=$(python -c "import gnomekeyring,gobject; gobject.set_application_name('Bindwood'); print gnomekeyring.find_items_sync(gnomekeyring.ITEM_GENERIC_SECRET, {'desktopcouch': 'oauth'})[0].secret" 2>/dev/null)
2469+
2470 if [ -z "$PORT" ]; then
2471- # D-Bus call failed for some reason, so use default port
2472- echo 5984 > $OUT
2473+ # D-Bus call failed for some reason, so just punt
2474+ echo ENOCOUCH > $OUT
2475 else
2476- echo $PORT > $OUT
2477+ echo $PORT:$TOKENS > $OUT
2478 fi
2479
2480
2481
2482=== added directory 'debian'
2483=== removed directory 'debian'
2484=== added file 'debian/changelog'
2485--- debian/changelog 1970-01-01 00:00:00 +0000
2486+++ debian/changelog 2009-09-25 16:00:27 +0000
2487@@ -0,0 +1,28 @@
2488+bindwood (0.3-0ubuntu1) UNRELEASED; urgency=low
2489+
2490+ * New upstream release (LP: #436705)
2491+ - Properly handle nested folders' bookmarks, and not just
2492+ the three top-level bookmark folders. (LP: #404193)
2493+ - Handle multiple firefox profiles (LP: #408282)
2494+ - Update record_type (LP: #406839)
2495+ - Show "Bindwood" as application name when Bindwood D-Bus Python
2496+ script requests access to the keyring
2497+ - added thorough debugging when BINDWOOD_DEBUG is set
2498+ * debian/copyright:
2499+ - Added license reference for BSD sha1.js
2500+ - Added license reference for Apache-2.0 oauth.js
2501+
2502+ -- Ken VanDine <ken.vandine@canonical.com> Fri, 25 Sep 2009 10:40:23 -0400
2503+
2504+bindwood (0.2~~rev9-0ubuntu2) karmic; urgency=low
2505+
2506+ * Depend on couchdb-bin instead of couchdb (LP: #427036)
2507+
2508+ -- Elliot Murphy <elliot@ubuntu.com> Wed, 16 Sep 2009 17:25:46 -0400
2509+
2510+bindwood (0.2~~rev9-0ubuntu1) karmic; urgency=low
2511+
2512+ [ Elliot Murphy ]
2513+ * Initial packaging. (LP: #408758)
2514+
2515+ -- Elliot Murphy <elliot@ubuntu.com> Tue, 18 Aug 2009 10:21:57 -0400
2516
2517=== removed file 'debian/changelog'
2518--- debian/changelog 2009-08-18 19:51:45 +0000
2519+++ debian/changelog 1970-01-01 00:00:00 +0000
2520@@ -1,6 +0,0 @@
2521-bindwood (0.2~~rev9-0ubuntu1) karmic; urgency=low
2522-
2523- [ Elliot Murphy ]
2524- * Initial packaging. (LP: #408758)
2525-
2526- -- Elliot Murphy <elliot@ubuntu.com> Tue, 18 Aug 2009 10:21:57 -0400
2527
2528=== added file 'debian/compat'
2529--- debian/compat 1970-01-01 00:00:00 +0000
2530+++ debian/compat 2009-09-25 16:00:27 +0000
2531@@ -0,0 +1,1 @@
2532+7
2533
2534=== removed file 'debian/compat'
2535--- debian/compat 2009-08-06 10:17:44 +0000
2536+++ debian/compat 1970-01-01 00:00:00 +0000
2537@@ -1,1 +0,0 @@
2538-7
2539
2540=== added file 'debian/control'
2541--- debian/control 1970-01-01 00:00:00 +0000
2542+++ debian/control 2009-09-25 16:00:27 +0000
2543@@ -0,0 +1,22 @@
2544+Source: bindwood
2545+Section: web
2546+Priority: optional
2547+Maintainer: Ubuntu MOTU Developers <ubuntu-motu@lists.ubuntu.com>
2548+XSBC-Original-Maintainer: Elliot Murphy <elliot@ubuntu.com>
2549+Build-Depends: debhelper (> 7), cdbs, mozilla-devscripts (>= 0.14), zip
2550+Vcs-Bzr: lp:~ubuntu-dev/firefox-extensions/bindwood.ubuntu
2551+Vcs-Browser: https://code.launchpad.net/~ubuntu-dev/firefox-extensions/bindwood.ubuntu
2552+Standards-Version: 3.8.3
2553+
2554+Package: bindwood
2555+Architecture: all
2556+Depends: ${xpi:Depends},
2557+ couchdb-bin,
2558+ python-desktopcouch
2559+Description: Firefox bookmark syncing with desktop couchdb.
2560+ Extension package for firefox provides bookmark syncing with local desktop
2561+ couchdb, bookmarks can then be replicated to other couchdb instances.
2562+ .
2563+ You can uninstall this package if you don't want to sync bookmarks with your
2564+ local desktop couchdb.
2565+
2566
2567=== removed file 'debian/control'
2568--- debian/control 2009-08-18 19:51:07 +0000
2569+++ debian/control 1970-01-01 00:00:00 +0000
2570@@ -1,22 +0,0 @@
2571-Source: bindwood
2572-Section: web
2573-Priority: optional
2574-Maintainer: Ubuntu MOTU Developers <ubuntu-motu@lists.ubuntu.com>
2575-XSBC-Original-Maintainer: Elliot Murphy <elliot@ubuntu.com>
2576-Build-Depends: debhelper (> 7), cdbs, mozilla-devscripts (>= 0.14), zip
2577-Vcs-Bzr: lp:~ubuntu-dev/firefox-extensions/bindwood.ubuntu
2578-Vcs-Browser: https://code.launchpad.net/~ubuntu-dev/firefox-extensions/bindwood.ubuntu
2579-Standards-Version: 3.8.3
2580-
2581-Package: bindwood
2582-Architecture: all
2583-Depends: ${xpi:Depends},
2584- couchdb,
2585- python-desktopcouch
2586-Description: Firefox bookmark syncing with desktop couchdb.
2587- Extension package for firefox provides bookmark syncing with local desktop
2588- couchdb, bookmarks can then be replicated to other couchdb instances.
2589- .
2590- You can uninstall this package if you don't want to sync bookmarks with your
2591- local desktop couchdb.
2592-
2593
2594=== added file 'debian/copyright'
2595--- debian/copyright 1970-01-01 00:00:00 +0000
2596+++ debian/copyright 2009-09-25 16:00:27 +0000
2597@@ -0,0 +1,80 @@
2598+This is the Ubuntu package of bindwood, a Firefox extension for
2599+syncing your bookmarks to desktopcouch.
2600+
2601+It was packaged by Elliot Murphy <elliot@ubuntu.com>
2602+
2603+The upstream sources can be found at:
2604+
2605+ https://code.launchpad.net/bindwood
2606+
2607+content/couch.js:
2608+
2609+Copyright (C) 2009 Apache Software Foundation
2610+
2611+License:
2612+
2613+ Licensed under the Apache License, Version 2.0 (the "License");
2614+ you may not use this software except in compliance with the License
2615+ You may obtain a copy of the License at
2616+
2617+ http://www.apache.org/licenses/LICENSE-2.0
2618+
2619+
2620+ Unless required by applicable law or agreed to in writing, software
2621+ distributed under the License is distributed on an "AS IS" BASIS,
2622+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2623+ See the License for the specific language governing permissions and
2624+ limitations under the License.
2625+
2626+content/oauth.js:
2627+
2628+Copyright 2008 Netflix, Inc.
2629+
2630+License:
2631+
2632+ Licensed under the Apache License, Version 2.0 (the "License");
2633+ you may not use this file except in compliance with the License.
2634+ You may obtain a copy of the License at
2635+
2636+ http://www.apache.org/licenses/LICENSE-2.0
2637+
2638+ Unless required by applicable law or agreed to in writing,
2639+ software distributed under the License is distributed on an "AS
2640+ IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
2641+ express or implied. See the License for the specific language
2642+ governing permissions and limitations under the License.
2643+
2644+On Debian systems, the complete text of the Apache License can be found
2645+in `/usr/share/common-licenses/Apache-2.0'.
2646+
2647+content/sha1.js:
2648+
2649+Copyright 2002 Paul Johnston
2650+
2651+License:
2652+
2653+ A JavaScript implementation of the Secure Hash Algorithm, SHA-1,
2654+ as defined in FIPS PUB 180-1
2655+
2656+ Version 2.1a Copyright Paul Johnston 2000 - 2002.
2657+
2658+ Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
2659+
2660+ Distributed under the BSD License
2661+
2662+ See http://pajhome.org.uk/crypt/md5 for details.
2663+
2664+On Debian systems, the complete text of the BSD License can be found
2665+in `/usr/share/common-licenses/BSD'.
2666+
2667+All other files and debian packaging:
2668+
2669+Copyright (C) 2009 Canonical Ltd.
2670+
2671+License:
2672+
2673+GNU GENERAL PUBLIC LICENSE Version 3
2674+
2675+The full text can be found at:
2676+ /usr/share/common-licenses/GPL-3 or http://www.gnu.org/licenses/gpl.txt
2677+
2678
2679=== removed file 'debian/copyright'
2680--- debian/copyright 2009-08-06 10:17:44 +0000
2681+++ debian/copyright 1970-01-01 00:00:00 +0000
2682@@ -1,43 +0,0 @@
2683-This is the Ubuntu package of bindwood, a Firefox extension for
2684-syncing your bookmarks to desktopcouch.
2685-
2686-It was packaged by Elliot Murphy <elliot@ubuntu.com>
2687-
2688-The upstream sources can be found at:
2689-
2690- https://code.launchpad.net/bindwood
2691-
2692-content/couch.js:
2693-
2694-Copyright (C) 2009 Apache Software Foundation
2695-
2696-License:
2697-
2698- Licensed under the Apache License, Version 2.0 (the "License");
2699- you may not use this software except in compliance with the License
2700- You may obtain a copy of the License at
2701-
2702- http://www.apache.org/licenses/LICENSE-2.0
2703-
2704-
2705- Unless required by applicable law or agreed to in writing, software
2706- distributed under the License is distributed on an "AS IS" BASIS,
2707- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2708- See the License for the specific language governing permissions and
2709- limitations under the License.
2710-
2711-On Debian systems, the complete text of the Apache License can be found
2712-in `/usr/share/common-licenses/Apache-2.0'.
2713-
2714-
2715-All other files and debian packaging:
2716-
2717-Copyright (C) 2009 Canonical Ltd.
2718-
2719-License:
2720-
2721-GNU GENERAL PUBLIC LICENSE Version 3
2722-
2723-The full text can be found at:
2724- /usr/share/common-licenses/GPL-3 or http://www.gnu.org/licenses/gpl.txt
2725-
2726
2727=== added file 'debian/rules'
2728--- debian/rules 1970-01-01 00:00:00 +0000
2729+++ debian/rules 2009-09-25 16:00:27 +0000
2730@@ -0,0 +1,8 @@
2731+#!/usr/bin/make -f
2732+
2733+MOZ_EXTENSION_PKG := bindwood
2734+MOZ_XPI_BUILD_COMMAND = sh build.sh
2735+
2736+include /usr/share/cdbs/1/rules/debhelper.mk
2737+include /usr/share/mozilla-devscripts/xpi.mk
2738+
2739
2740=== removed file 'debian/rules'
2741--- debian/rules 2009-08-06 10:17:44 +0000
2742+++ debian/rules 1970-01-01 00:00:00 +0000
2743@@ -1,8 +0,0 @@
2744-#!/usr/bin/make -f
2745-
2746-MOZ_EXTENSION_PKG := bindwood
2747-MOZ_XPI_BUILD_COMMAND = sh build.sh
2748-
2749-include /usr/share/cdbs/1/rules/debhelper.mk
2750-include /usr/share/mozilla-devscripts/xpi.mk
2751-
2752
2753=== modified file 'install.rdf'
2754--- install.rdf 2009-08-18 18:09:24 +0000
2755+++ install.rdf 2009-09-25 16:00:27 +0000
2756@@ -4,7 +4,7 @@
2757
2758 <Description about="urn:mozilla:install-manifest">
2759 <em:id>bindwood@ubuntu.com</em:id>
2760- <em:version>0.2pre</em:version>
2761+ <em:version>0.3</em:version>
2762 <em:type>2</em:type>
2763
2764 <!-- Target Application this extension can install into,

Subscribers

People subscribed via source and target branches

to all changes: