Merge lp:~jamesh/bindwood/refactor-desktopcouch-access into lp:bindwood

Proposed by James Henstridge
Status: Merged
Approved by: dobey
Approved revision: 31
Merged at revision: 28
Proposed branch: lp:~jamesh/bindwood/refactor-desktopcouch-access
Merge into: lp:bindwood
Prerequisite: lp:~jamesh/bindwood/test-with-mozmill
Diff against target: 964 lines (+529/-365)
5 files modified
Makefile (+2/-0)
modules/bindwood.jsm (+260/-351)
modules/couch.jsm (+1/-14)
modules/desktopcouch.jsm (+202/-0)
mozmill/tests/test_desktopcouch.js (+64/-0)
To merge this branch: bzr merge lp:~jamesh/bindwood/refactor-desktopcouch-access
Reviewer Review Type Date Requested Status
Zachery Bir (community) Approve
Eric Casteleijn (community) Approve
Review via email: mp+48896@code.launchpad.net

Commit message

Split DesktopCouch connection code out from Bindwood. Refactor Bindwood.Observer to make it instantiatable.

Description of the change

Currently, the code to connect to DesktopCouch is embedded in the Bindwood object. To make testing easier, it would be nice to be able to connect to DesktopCouch without messing with the state of the Bindwood object.

This branch splits out the DesktopCouch connection code into a separate module, and adds some simple mozmill tests to verify that it behaves correctly. I've also changed the way the OAuth authentication code is integrated. Previously there were some nasty cross-dependencies between the CouchDB and Bindwood objects. I've removed those hooks from couchdb.jsm, and made the desktopcouch.jsm code add the authentication handler to the new CouchDB instance it creates.

This branch also includes a simple refactoring of the Bindwood.Observer code so that multiple instances can be created. This is a first step towards making it easier to create multiple instances of the Bindwood object to aid testing (e.g. create new instance, substitute a few methods, run).

To post a comment you must log in.
Revision history for this message
Eric Casteleijn (thisfred) wrote :

Awesome, +1

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

Perfect, this is exactly the direction I had intended to take Bindwood in the future. Abstracting out these services will make it easier to work with a variety of browsers.

review: Approve
Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :

The prerequisite lp:~jamesh/bindwood/test-with-mozmill has not yet been merged into lp:bindwood.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2011-02-08 10:38:53 +0000
3+++ Makefile 2011-02-08 10:38:53 +0000
4@@ -5,3 +5,5 @@
5 BINDWOOD_DB=test_bookmarks mozmill \
6 --addons=$(shell pwd) --app-arg=about:blank \
7 --show-errors --test=mozmill/tests/
8+
9+.PHONY: build test
10
11=== modified file 'modules/bindwood.jsm'
12--- modules/bindwood.jsm 2010-04-14 17:56:54 +0000
13+++ modules/bindwood.jsm 2011-02-08 10:38:53 +0000
14@@ -16,7 +16,7 @@
15 /* Lots and lots of debugging information */
16
17 Components.utils.import("resource://gre/modules/utils.js");
18-Components.utils.import("resource://bindwood/couch.jsm");
19+Components.utils.import("resource://bindwood/desktopcouch.jsm");
20
21 var EXPORTED_SYMBOLS = ["Bindwood"];
22
23@@ -145,137 +145,39 @@
24 Bindwood.writeMessage("Got our profile: " + Bindwood.currentProfile);
25
26 Bindwood.running = true;
27+ Bindwood.Observer = new BindwoodBookmarkObserver(this);
28 bookmarksService.addObserver(Bindwood.Observer, false);
29- Bindwood.getCouchEnvironment();
30+ Bindwood.connectToCouchDB();
31 }
32 },
33
34- getCouchEnvironment: function() {
35- // find the desktop Couch port number by making a D-Bus call
36- // we call D-Bus by shelling out to a bash script which calls
37- // it for us, and writes the port number into a temp file
38-
39- // find OS temp dir to put the tempfile in
40- // https://developer.mozilla.org/index.php?title=File_I%2F%2FO#Getting_special_files
41- var tmpdir = Cc["@mozilla.org/file/directory_service;1"]
42- .getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
43- // create a randomly named tempfile in the tempdir
44- var tmpfile = Cc["@mozilla.org/file/local;1"]
45- .createInstance(Ci.nsILocalFile);
46- tmpfile.initWithPath(tmpdir.path + "/desktopcouch." + Math.random());
47- tmpfile.createUnique(tmpfile.NORMAL_FILE_TYPE, 0600);
48-
49- // find the D-Bus bash script, which is in our extension folder
50- var MY_ID = "bindwood@ubuntu.com";
51- var em = Cc["@mozilla.org/extensions/manager;1"]
52- .getService(Ci.nsIExtensionManager);
53- var couchdb_env_script = em.getInstallLocation(MY_ID)
54- .getItemFile(MY_ID, "couchdb_env.sh");
55- // create an nsILocalFile for the executable
56- var nsifile = Cc["@mozilla.org/file/local;1"]
57- .createInstance(Ci.nsILocalFile);
58- nsifile.initWithPath(couchdb_env_script.path);
59- nsifile.permissions = 0755;
60-
61- // create an nsIProcess2 to execute this bash script
62- var process = Cc["@mozilla.org/process/util;1"]
63- .createInstance(Ci.nsIProcess2 || Ci.nsIProcess);
64- process.init(nsifile);
65-
66- // Run the process, passing the tmpfile path
67- var args = [tmpfile.path];
68- process.runAsync(args, args.length, {
69- observe: function(process, finishState, unused_data) {
70- // If the script exists cleanly, we should have a file
71- // containing the port couch is running on as well as
72- // the various OAuth tokens necessary to talk to it.
73- var shouldProceed = true;
74- if (finishState == "process-finished") {
75- // read temp file to find couch environment
76- // https://developer.mozilla.org/en/Code_snippets/File_I%2f%2fO#Reading_from_a_file
77- var environment;
78- var fstream = Cc["@mozilla.org/network/file-input-stream;1"]
79- .createInstance(Ci.nsIFileInputStream);
80- var cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]
81- .createInstance(Ci.nsIConverterInputStream);
82- fstream.init(tmpfile, -1, 0, 0);
83- cstream.init(fstream, "UTF-8", 0, 0);
84- let (str = {}) {
85- // read the whole file and put it in str.value
86- cstream.readString(-1, str);
87- environment = str.value;
88- };
89- cstream.close(); // this closes fstream
90- environment = environment.replace(/^\s\s*/, '')
91- .replace(/\s\s*$/, '');
92- } else {
93- // If we fail, we should just return
94- Bindwood.writeMessage("D-Bus port find failed");
95- shouldProceed = false;
96- }
97- tmpfile.remove(false);
98-
99- if (environment == 'ENOCOUCH') {
100- // No Couch environment found. Just spit out a
101- // message and return, stopping Bindwood from
102- // doing anything further.
103- Bindwood.writeError(
104- "No suitable Couch environment found." +
105- " Not proceeding.", e);
106- shouldProceed = false;
107- }
108-
109- if (shouldProceed && environment) {
110- Bindwood.writeMessage("Got our environment, proceeding.");
111- Bindwood.setUpEnvironment(environment);
112- } else {
113- // Unregister our observer for bookmark events; we're done
114- Bindwood.writeMessage("No environment. Unregistering observer.");
115- bookmarksService.removeObserver(Bindwood.Observer);
116- }
117- }
118- });
119- },
120-
121- setUpEnvironment: function(couchEnvironment) {
122- var env_array = couchEnvironment.split(':');
123- var port = env_array[0];
124- var consumer_key = env_array[1];
125- var consumer_secret = env_array[2];
126- var token = env_array[3];
127- var token_secret = env_array[4];
128-
129- CouchDB.port = port;
130- CouchDB.accessor = {
131- consumerSecret: consumer_secret,
132- tokenSecret: token_secret
133- };
134- CouchDB.message = {
135- parameters: {
136- oauth_callback: "None",
137- oauth_consumer_key: consumer_key,
138- oauth_signature_method: "PLAINTEXT",
139- oauth_token: token,
140- oauth_verifier: "None",
141- oauth_version: "1.0"
142- }
143- };
144-
145+ connectToCouchDB: function() {
146 var db_name = 'bookmarks';
147 if (envService.exists('BINDWOOD_DB')) {
148 db_name = envService.get('BINDWOOD_DB');
149 }
150
151- Bindwood.writeMessage("Got our db name: " + db_name);
152- Bindwood.couch = new CouchDB(db_name);
153+ connect_desktopcouch(
154+ db_name,
155+ function (database) {
156+ if (database) {
157+ Bindwood.writeMessage("Got our db name: " + database.name);
158+ Bindwood.couch = database;
159
160- try {
161- Bindwood.startProcess();
162- } catch(e) {
163- Bindwood.writeError(
164- "Something wrong with the process, exiting.", e);
165- return;
166- }
167+ try {
168+ Bindwood.startProcess();
169+ } catch(e) {
170+ Bindwood.writeError(
171+ "Something wrong with the process, exiting.", e);
172+ return;
173+ }
174+ } else {
175+ // Could not connect: disable our observer.
176+ bookmarksService.removeObserver(Bindwood.Observer);
177+ }
178+ }, function (message) {
179+ Bindwood.writeMessage(message);
180+ });
181 },
182
183 getLastSequence: function() {
184@@ -1427,233 +1329,240 @@
185 }
186 return true;
187 },
188-
189- Observer: {
190- // An nsINavBookmarkObserver
191- onItemAdded: function(aItemId, aFolder, aIndex) {
192- Bindwood.writeMessage(
193- "onItemAdded: called when push is " + Bindwood.push);
194- // An item has been added, so we create a blank entry
195- // in Couch with our local itemId attached.
196- if (!Bindwood.itemWeCareAbout(aItemId)) {
197- Bindwood.writeMessage("Ignoring this add event");
198- return;
199- }
200-
201- Bindwood.writeMessage(
202- "A new item was created. Its id is: " + aItemId +
203- " at location: " + aIndex +
204- " in folder: " + aFolder );
205-
206- switch (Bindwood.push) {
207- case 'DISABLED':
208- Bindwood.writeMessage("Added, but not saving to Couch.");
209- break;
210- case 'ENABLED':
211- try {
212- var doc = Bindwood.couchRecordForItemId(aItemId);
213- var response = Bindwood.couch.save(doc);
214- Bindwood.writeMessage("Saved new, bare record to Couch.");
215- Bindwood.seen_revisions[response.rev] = true;
216- Bindwood.pushFolderChildren(aFolder);
217- } catch(e) {
218- Bindwood.writeError(
219- "Problem saving new bookmark to Couch: ", e);
220- }
221- break;
222- default:
223- break;
224- }
225-
226- Bindwood.setLatestModified(
227- bookmarksService.getItemLastModified(aItemId));
228- },
229- onBeforeItemRemoved: function(aItemId) {
230- Bindwood.writeMessage(
231- "onBeforeItemRemoved: called when push is " + Bindwood.push);
232- // A bookmark has been removed. This is called before it's
233- // been removed locally, though we're passed the itemId,
234- // which we use to delete from Couch.
235- var folderId = bookmarksService.getFolderIdForItem(aItemId);
236- if (!Bindwood.itemWeCareAbout(aItemId)) {
237- Bindwood.writeMessage("Ignoring this before remove event");
238- return;
239- }
240-
241- Bindwood.writeMessage(
242- "Record " + aItemId + " is about to be removed locally.");
243- var uuid = Bindwood.uuidForItemId(aItemId);
244-
245- switch (Bindwood.push) {
246- case 'DISABLED':
247- delete Bindwood.uuidItemIdMap[uuid];
248- Bindwood.writeMessage(
249- "Deleted from local uuid map, but not saving back to Couch.");
250- break;
251- case 'ENABLED':
252-
253- var doc = Bindwood.couch.open(uuid);
254- if (!doc.application_annotations) {
255- doc.application_annotations = {};
256- }
257- if (!doc.application_annotations['Ubuntu One']) {
258- doc.application_annotations['Ubuntu One'] = {};
259- }
260- if (!doc.application_annotations['Ubuntu One'].private_application_annotations) {
261- doc.application_annotations['Ubuntu One'].private_application_annotations = {};
262- }
263- doc.application_annotations['Ubuntu One'].private_application_annotations.deleted = true;
264-
265- try {
266- // Also remove from our local cache and remove
267- // annotation from service.
268- var response = Bindwood.couch.save(doc);
269- Bindwood.seen_revisions[response.rev] = true;
270- delete Bindwood.uuidItemIdMap[uuid];
271- Bindwood.writeMessage(
272- "Deleted local reference in the" +
273- " uuid-itemId mapping.");
274- Bindwood.writeMessage(
275- "Saved document back to Couch with deleted flag set.");
276- var new_children = Bindwood.getUUIDsFromFolder(folderId);
277- new_children.splice(new_children.indexOf(uuid), 1);
278- Bindwood.pushFolderChildren(folderId, new_children);
279- } catch(e) {
280- Bindwood.writeError(
281- "Problem pushing deleted record to Couch: ", e);
282- }
283- break;
284- default:
285- break;
286- }
287- },
288- onItemRemoved: function(aItemId, aFolder, aIndex) {
289- Bindwood.writeMessage(
290- "onItemRemoved: called when push is " + Bindwood.push);
291- // This only happens locally, so there's never a need to push
292- if (!Bindwood.itemWeCareAbout(aItemId)) {
293- Bindwood.writeMessage("Ignoring this remove event");
294- return;
295- }
296-
297- Bindwood.makeLocalChangeOnly(
298- function() {
299- return annotationService.removeItemAnnotation(
300- aItemId, Bindwood.annotationKey); });
301- Bindwood.writeMessage(
302- "Removed annotations from bookmark identified by: " + aItemId);
303- },
304- onItemChanged: function(aItemId, aProperty, aIsAnnotationProperty, aValue) {
305- Bindwood.writeMessage(
306- "onItemChanged: called when push is " + Bindwood.push);
307- // A property of a bookmark has changed. On multiple
308- // property updates, this will be called multiple times,
309- // once per property (i.e., for title and URI)
310- if (!Bindwood.itemWeCareAbout(aItemId)) {
311- Bindwood.writeMessage("Ignoring this change event");
312- return;
313- }
314-
315- Bindwood.writeMessage(
316- "A property (" +
317- aProperty +
318- ") on item id: " + aItemId +
319- " has been set to: " + aValue);
320- var uuid = Bindwood.uuidForItemId(aItemId);
321-
322- switch (Bindwood.push) {
323- case 'DISABLED':
324- Bindwood.writeMessage(
325- "Updated, but not saving back to Couch.");
326- break;
327- case 'ENABLED':
328- Bindwood.writeMessage(
329- "We will push this change back to Couch.");
330- try {
331- var result = Bindwood.updateDocAndSave(
332- uuid, aProperty.toString(), aValue.toString(),
333- function() {
334- Bindwood.writeMessage(
335- "Saved the document back to Couch"); });
336- } catch(e) {
337- Bindwood.writeError(
338- "Problem saving updated bookmark to Couch: ", e);
339- }
340- break;
341- default:
342- break;
343- }
344-
345- Bindwood.setLatestModified(
346- bookmarksService.getItemLastModified(aItemId));
347- },
348-
349- onItemMoved: function(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex) {
350- Bindwood.writeMessage(
351- "onItemMoved: called when push is " + Bindwood.push);
352- Bindwood.writeMessage(
353- "The item: " + aItemId + " was moved from (" +
354- aOldParent + ", " + aOldIndex +
355- ") to (" + aNewParent + ", " + aNewIndex + ")"
356- );
357- switch (Bindwood.push) {
358- case 'DISABLED':
359- Bindwood.writeMessage(
360- "Moved, but not saving back to Couch.");
361- break;
362- case 'ENABLED':
363- var uuid = Bindwood.uuidForItemId(aItemId);
364- var old_parent_uuid = Bindwood.uuidForItemId(aOldParent);
365- var old_parent_doc = Bindwood.couch.open(old_parent_uuid);
366- old_parent_doc.children = Bindwood.getUUIDsFromFolder(
367- aOldParent);
368- try {
369- var response = Bindwood.couch.save(old_parent_doc);
370- Bindwood.seen_revisions[response.rev] = true;
371- } catch(e) {
372- Bindwood.writeError(
373- "Problem saving updated old parent doc to Couch: ", e);
374- }
375- if (aOldParent != aNewParent) {
376- var new_parent_uuid = Bindwood.uuidForItemId(aNewParent);
377- var new_parent_doc = Bindwood.couch.open(new_parent_uuid);
378- new_parent_doc.children = Bindwood.getUUIDsFromFolder(
379- aNewParent);
380- try {
381- var response = Bindwood.couch.save(new_parent_doc);
382- Bindwood.seen_revisions[response.rev] = true;
383- } catch(e) {
384- Bindwood.writeError(
385- "Problem saving updated new parent doc to Couch: ",
386- e);
387- }
388- }
389- break;
390- default:
391- break;
392- }
393-
394- // Set the latest modified to the greatest of aItemId,
395- // aOldParent, or aNewParent's last_modified
396- Bindwood.setLatestModified(
397- [bookmarksService.getItemLastModified(aItemId),
398- bookmarksService.getItemLastModified(aOldParent),
399- bookmarksService.getItemLastModified(aNewParent)].sort()[2]);
400- },
401-
402- // Currently unhandled
403- onBeginUpdateBatch: function() {},
404- onEndUpdateBatch: function() {},
405- onItemVisited: function(aBookmarkId, aVisitID, time) {},
406-
407- // Query Interface
408- QueryInterface: function(iid) {
409- if (iid.equals(Ci.nsINavBookmarkObserver) ||
410- iid.equals(Ci.nsINavBookmarkObserver_MOZILLA_1_9_1_ADDITIONS) ||
411- iid.equals(Ci.nsISupports)) {
412- return this;
413- }
414- throw Cr.NS_ERROR_NO_INTERFACE;
415- }
416- }
417+};
418+
419+var BindwoodBookmarkObserver = function(bindwood) {
420+ this.bw = bindwood;
421+};
422+BindwoodBookmarkObserver.prototype = {
423+ // Query Interface
424+ QueryInterface: function(iid) {
425+ if (iid.equals(Ci.nsINavBookmarkObserver) ||
426+ iid.equals(Ci.nsINavBookmarkObserver_MOZILLA_1_9_1_ADDITIONS) ||
427+ iid.equals(Ci.nsISupports)) {
428+ return this;
429+ }
430+ throw Cr.NS_ERROR_NO_INTERFACE;
431+ },
432+
433+ // An nsINavBookmarkObserver
434+ onItemAdded: function(aItemId, aFolder, aIndex) {
435+ this.bw.writeMessage(
436+ "onItemAdded: called when push is " + this.bw.push);
437+ // An item has been added, so we create a blank entry
438+ // in Couch with our local itemId attached.
439+ if (!this.bw.itemWeCareAbout(aItemId)) {
440+ this.bw.writeMessage("Ignoring this add event");
441+ return;
442+ }
443+
444+ this.bw.writeMessage(
445+ "A new item was created. Its id is: " + aItemId +
446+ " at location: " + aIndex +
447+ " in folder: " + aFolder );
448+
449+ switch (this.bw.push) {
450+ case 'DISABLED':
451+ this.bw.writeMessage("Added, but not saving to Couch.");
452+ break;
453+ case 'ENABLED':
454+ try {
455+ var doc = this.bw.couchRecordForItemId(aItemId);
456+ var response = this.bw.couch.save(doc);
457+ this.bw.writeMessage("Saved new, bare record to Couch.");
458+ this.bw.seen_revisions[response.rev] = true;
459+ this.bw.pushFolderChildren(aFolder);
460+ } catch(e) {
461+ this.bw.writeError(
462+ "Problem saving new bookmark to Couch: ", e);
463+ }
464+ break;
465+ default:
466+ break;
467+ }
468+
469+ this.bw.setLatestModified(
470+ bookmarksService.getItemLastModified(aItemId));
471+ },
472+
473+ onBeforeItemRemoved: function(aItemId) {
474+ this.bw.writeMessage(
475+ "onBeforeItemRemoved: called when push is " + this.bw.push);
476+ // A bookmark has been removed. This is called before it's
477+ // been removed locally, though we're passed the itemId,
478+ // which we use to delete from Couch.
479+ var folderId = bookmarksService.getFolderIdForItem(aItemId);
480+ if (!this.bw.itemWeCareAbout(aItemId)) {
481+ this.bw.writeMessage("Ignoring this before remove event");
482+ return;
483+ }
484+
485+ this.bw.writeMessage(
486+ "Record " + aItemId + " is about to be removed locally.");
487+ var uuid = this.bw.uuidForItemId(aItemId);
488+
489+ switch (this.bw.push) {
490+ case 'DISABLED':
491+ delete this.bw.uuidItemIdMap[uuid];
492+ this.bw.writeMessage(
493+ "Deleted from local uuid map, but not saving back to Couch.");
494+ break;
495+
496+ case 'ENABLED':
497+ var doc = this.bw.couch.open(uuid);
498+ if (!doc.application_annotations) {
499+ doc.application_annotations = {};
500+ }
501+ if (!doc.application_annotations['Ubuntu One']) {
502+ doc.application_annotations['Ubuntu One'] = {};
503+ }
504+ if (!doc.application_annotations['Ubuntu One'].private_application_annotations) {
505+ doc.application_annotations['Ubuntu One'].private_application_annotations = {};
506+ }
507+ doc.application_annotations['Ubuntu One'].private_application_annotations.deleted = true;
508+
509+ try {
510+ // Also remove from our local cache and remove
511+ // annotation from service.
512+ var response = this.bw.couch.save(doc);
513+ this.bw.seen_revisions[response.rev] = true;
514+ delete this.bw.uuidItemIdMap[uuid];
515+ this.bw.writeMessage(
516+ "Deleted local reference in the" +
517+ " uuid-itemId mapping.");
518+ this.bw.writeMessage(
519+ "Saved document back to Couch with deleted flag set.");
520+ var new_children = this.bw.getUUIDsFromFolder(folderId);
521+ new_children.splice(new_children.indexOf(uuid), 1);
522+ this.bw.pushFolderChildren(folderId, new_children);
523+ } catch(e) {
524+ this.bw.writeError(
525+ "Problem pushing deleted record to Couch: ", e);
526+ }
527+ break;
528+ default:
529+ break;
530+ }
531+ },
532+
533+ onItemRemoved: function(aItemId, aFolder, aIndex) {
534+ this.bw.writeMessage(
535+ "onItemRemoved: called when push is " + this,bw.push);
536+ // This only happens locally, so there's never a need to push
537+ if (!this.bw.itemWeCareAbout(aItemId)) {
538+ this.bw.writeMessage("Ignoring this remove event");
539+ return;
540+ }
541+
542+ this.bw.makeLocalChangeOnly(
543+ function() {
544+ return annotationService.removeItemAnnotation(
545+ aItemId, this.bw.annotationKey); });
546+ this.bw.writeMessage(
547+ "Removed annotations from bookmark identified by: " + aItemId);
548+ },
549+
550+ onItemChanged: function(aItemId, aProperty, aIsAnnotationProperty, aValue) {
551+ this.bw.writeMessage(
552+ "onItemChanged: called when push is " + this.bw.push);
553+ // A property of a bookmark has changed. On multiple
554+ // property updates, this will be called multiple times,
555+ // once per property (i.e., for title and URI)
556+ if (!this.bw.itemWeCareAbout(aItemId)) {
557+ this.bw.writeMessage("Ignoring this change event");
558+ return;
559+ }
560+
561+ this.bw.writeMessage(
562+ "A property (" +
563+ aProperty +
564+ ") on item id: " + aItemId +
565+ " has been set to: " + aValue);
566+ var uuid = this.bw.uuidForItemId(aItemId);
567+
568+ switch (this.bw.push) {
569+ case 'DISABLED':
570+ this.bw.writeMessage(
571+ "Updated, but not saving back to Couch.");
572+ break;
573+ case 'ENABLED':
574+ this.bw.writeMessage(
575+ "We will push this change back to Couch.");
576+ try {
577+ var result = Bindwood.updateDocAndSave(
578+ uuid, aProperty.toString(), aValue.toString(),
579+ function() {
580+ this.bw.writeMessage(
581+ "Saved the document back to Couch");
582+ });
583+ } catch(e) {
584+ this.bw.writeError(
585+ "Problem saving updated bookmark to Couch: ", e);
586+ }
587+ break;
588+ default:
589+ break;
590+ }
591+
592+ this.bw.setLatestModified(
593+ bookmarksService.getItemLastModified(aItemId));
594+ },
595+
596+ onItemMoved: function(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex) {
597+ this.bw.writeMessage(
598+ "onItemMoved: called when push is " + this.bw.push);
599+ this.bw.writeMessage(
600+ "The item: " + aItemId + " was moved from (" +
601+ aOldParent + ", " + aOldIndex +
602+ ") to (" + aNewParent + ", " + aNewIndex + ")"
603+ );
604+ switch (this.bw.push) {
605+ case 'DISABLED':
606+ this.bw.writeMessage(
607+ "Moved, but not saving back to Couch.");
608+ break;
609+ case 'ENABLED':
610+ var uuid = this.bw.uuidForItemId(aItemId);
611+ var old_parent_uuid = this.bw.uuidForItemId(aOldParent);
612+ var old_parent_doc = this.bw.couch.open(old_parent_uuid);
613+ old_parent_doc.children = this.bw.getUUIDsFromFolder(
614+ aOldParent);
615+ try {
616+ var response = this.bw.couch.save(old_parent_doc);
617+ this.bw.seen_revisions[response.rev] = true;
618+ } catch(e) {
619+ this.bw.writeError(
620+ "Problem saving updated old parent doc to Couch: ", e);
621+ }
622+ if (aOldParent != aNewParent) {
623+ var new_parent_uuid = this.bw.uuidForItemId(aNewParent);
624+ var new_parent_doc = this.bw.couch.open(new_parent_uuid);
625+ new_parent_doc.children = this.bw.getUUIDsFromFolder(
626+ aNewParent);
627+ try {
628+ var response = this.bw.couch.save(new_parent_doc);
629+ this.bw.seen_revisions[response.rev] = true;
630+ } catch(e) {
631+ this.bw.writeError(
632+ "Problem saving updated new parent doc to Couch: ",
633+ e);
634+ }
635+ }
636+ break;
637+ default:
638+ break;
639+ }
640+
641+ // Set the latest modified to the greatest of aItemId,
642+ // aOldParent, or aNewParent's last_modified
643+ this.bw.setLatestModified(
644+ [bookmarksService.getItemLastModified(aItemId),
645+ bookmarksService.getItemLastModified(aOldParent),
646+ bookmarksService.getItemLastModified(aNewParent)].sort()[2]);
647+ },
648+
649+ // Currently unhandled
650+ onBeginUpdateBatch: function() {},
651+ onEndUpdateBatch: function() {},
652+ onItemVisited: function(aBookmarkId, aVisitID, time) {},
653 };
654
655=== modified file 'modules/couch.jsm'
656--- modules/couch.jsm 2010-03-24 19:34:01 +0000
657+++ modules/couch.jsm 2011-02-08 10:38:53 +0000
658@@ -10,8 +10,6 @@
659 // License for the specific language governing permissions and limitations under
660 // the License.
661
662-Components.utils.import("resource://bindwood/oauth.jsm");
663-
664 var EXPORTED_SYMBOLS = ["CouchDB", "XMLHttpRequest"];
665
666 var XMLHttpRequest = function () {
667@@ -419,20 +417,9 @@
668 };
669
670 CouchDB.request = function(method, uri, options) {
671- CouchDB.message.action = uri;
672- CouchDB.message.method = method;
673- CouchDB.message.parameters.oauth_timestamp = null;
674- CouchDB.message.parameters.oauth_nonce = null;
675- OAuth.completeRequest(CouchDB.message, CouchDB.accessor);
676- var parameters = CouchDB.message.parameters;
677-
678 options = options || {};
679-
680- options.headers = CouchDB.combine(options.headers, {Authorization: OAuth.getAuthorizationHeader('', parameters)});
681-
682 var req = CouchDB.newXhr();
683- var computed_uri = "http://localhost:" + CouchDB.port + uri;
684- req.open(method, computed_uri, false);
685+ req.open(method, uri, false);
686 if (options.headers) {
687 var headers = options.headers;
688 for (var headerName in headers) {
689
690=== added file 'modules/desktopcouch.jsm'
691--- modules/desktopcouch.jsm 1970-01-01 00:00:00 +0000
692+++ modules/desktopcouch.jsm 2011-02-08 10:38:53 +0000
693@@ -0,0 +1,202 @@
694+/*
695+ * Copyright 2009-2011 Canonical Ltd.
696+ *
697+ * This program is free software: you can redistribute it and/or modify it
698+ * under the terms of the GNU General Public License version 3, as published
699+ * by the Free Software Foundation.
700+ *
701+ * This program is distributed in the hope that it will be useful, but
702+ * WITHOUT ANY WARRANTY; without even the implied warranties of
703+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
704+ * PURPOSE. See the GNU General Public License for more details.
705+ *
706+ * You should have received a copy of the GNU General Public License along
707+ * with this program. If not, see <http://www.gnu.org/licenses/>.
708+ */
709+/* Utility functions to establish a connection to a DesktopCouch
710+ * CouchDB instance.
711+ */
712+
713+var Cc = Components.classes;
714+var Ci = Components.interfaces;
715+
716+Components.utils.import("resource://bindwood/couch.jsm");
717+Components.utils.import("resource://bindwood/oauth.jsm");
718+
719+var EXPORTED_SYMBOLS = [
720+ "connect_desktopcouch", "_get_desktopcouch_environment"];
721+
722+/**
723+ * Retrieve the DesktopCouch environment.
724+ *
725+ * @param callback
726+ * A callback that will be passed the environment on success, or
727+ * null on failure.
728+ * @param log
729+ * A callback that will be called to log debug messages.
730+ */
731+function _get_desktopcouch_environment(callback, log) {
732+ // find OS temp dir to put the tempfile in
733+ // https://developer.mozilla.org/index.php?title=File_I%2F%2FO#Getting_special_files
734+ var tmpdir = Cc["@mozilla.org/file/directory_service;1"]
735+ .getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
736+ // create a randomly named tempfile in the tempdir
737+ var tmpfile = Cc["@mozilla.org/file/local;1"]
738+ .createInstance(Ci.nsILocalFile);
739+ tmpfile.initWithPath(tmpdir.path + "/desktopcouch." + Math.random());
740+ tmpfile.createUnique(tmpfile.NORMAL_FILE_TYPE, 0600);
741+
742+ // find the D-Bus bash script, which is in our extension folder
743+ var MY_ID = "bindwood@ubuntu.com";
744+ var em = Cc["@mozilla.org/extensions/manager;1"]
745+ .getService(Ci.nsIExtensionManager);
746+ var couchdb_env_script = em.getInstallLocation(MY_ID)
747+ .getItemFile(MY_ID, "couchdb_env.sh");
748+ // create an nsILocalFile for the executable
749+ var nsifile = Cc["@mozilla.org/file/local;1"]
750+ .createInstance(Ci.nsILocalFile);
751+ nsifile.initWithPath(couchdb_env_script.path);
752+ nsifile.permissions = 0755;
753+
754+ // create an nsIProcess2 to execute this bash script
755+ var process = Cc["@mozilla.org/process/util;1"]
756+ .createInstance(Ci.nsIProcess2 || Ci.nsIProcess);
757+ process.init(nsifile);
758+
759+ // Run the process, passing the tmpfile path
760+ var args = [tmpfile.path];
761+ process.runAsync(args, args.length, {
762+ observe: function(process, finishState, unused_data) {
763+ // If the script exists cleanly, we should have a file
764+ // containing the port couch is running on as well as
765+ // the various OAuth tokens necessary to talk to it.
766+ var shouldProceed = true;
767+ if (finishState == "process-finished") {
768+ // read temp file to find couch environment
769+ // https://developer.mozilla.org/en/Code_snippets/File_I%2f%2fO#Reading_from_a_file
770+ var environment;
771+ var fstream = Cc["@mozilla.org/network/file-input-stream;1"]
772+ .createInstance(Ci.nsIFileInputStream);
773+ var cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]
774+ .createInstance(Ci.nsIConverterInputStream);
775+ fstream.init(tmpfile, -1, 0, 0);
776+ cstream.init(fstream, "UTF-8", 0, 0);
777+ let (str = {}) {
778+ // read the whole file and put it in str.value
779+ cstream.readString(-1, str);
780+ environment = str.value;
781+ };
782+ cstream.close(); // this closes fstream
783+ environment = environment.replace(/^\s\s*/, '')
784+ .replace(/\s\s*$/, '');
785+ } else {
786+ // If we fail, we should just return
787+ log("D-Bus port find failed");
788+ shouldProceed = false;
789+ }
790+ tmpfile.remove(false);
791+
792+ if (environment == 'ENOCOUCH') {
793+ // No Couch environment found. Just spit out a
794+ // message and return, stopping Bindwood from
795+ // doing anything further.
796+ log(
797+ "No suitable Couch environment found." +
798+ " Not proceeding.", e);
799+ shouldProceed = false;
800+ }
801+
802+ if (shouldProceed && environment) {
803+ log("Got our environment, proceeding.");
804+ callback(environment);
805+ } else {
806+ // Unregister our observer for bookmark events; we're done
807+ log("No environment. Unregistering observer.");
808+ callback(null);
809+ }
810+ }
811+ });
812+}
813+
814+/**
815+ * Connect to the given database in DesktopCouch using the given environment.
816+ *
817+ * @param name
818+ * The name of the database in DesktopCouch
819+ * @param environment
820+ * The DesktopCouch environment, as produced by
821+ * _get_desktopcouch_environment.
822+ * @returns A new CouchDB instance configured to talk to DesktopCouch.
823+ */
824+function _connect(name, environment) {
825+ var env_array = environment.split(":");
826+ var host = "http://localhost:" + env_array[0];
827+ var consumer_key = env_array[1];
828+ var consumer_secret = env_array[2];
829+ var token = env_array[3];
830+ var token_secret = env_array[4];
831+
832+ var db = new CouchDB(name);
833+ var accessor = {
834+ consumerKey: consumer_key,
835+ consumerSecret: consumer_secret,
836+ token: token,
837+ tokenSecret: token_secret,
838+ };
839+ db.request = function(method, uri, requestOptions) {
840+ // prepend full host name to URI.
841+ uri = host + uri;
842+
843+ // Construct OAuth signature, and add to request headers.
844+ var message = {
845+ method: method,
846+ action: uri,
847+ parameters: {
848+ oauth_signature_method: "PLAINTEXT",
849+ oauth_version: "1.0",
850+ },
851+ };
852+ OAuth.setTimestampAndNonce(message);
853+ OAuth.completeRequest(message, accessor);
854+ var auth_header = OAuth.getAuthorizationHeader(
855+ "desktopcouch", message.parameters);
856+
857+ requestOptions = requestOptions || {};
858+ requestOptions.headers = CouchDB.combine(
859+ requestOptions.headers, {Authorization: auth_header});
860+
861+ // Issue request.
862+ return CouchDB.request(method, uri, requestOptions);
863+ }
864+ return db;
865+}
866+
867+var _desktopcouch_environment = null;
868+
869+/**
870+ * Connect to the given database in DesktopCouch.
871+ *
872+ * @param name
873+ * The name of the database in DesktopCouch
874+ * @param callback
875+ * A callback called with the CouchDB instance on succes, or null
876+ * on failure.
877+ * @param log
878+ * A callback that will be called to log debug messages.
879+ */
880+function connect_desktopcouch(name, callback, log) {
881+ if (_desktopcouch_environment == null) {
882+ _get_desktopcouch_environment(function(environment) {
883+ _desktopcouch_environment = environment;
884+ if (environment) {
885+ var db = _connect(name, environment);
886+ } else {
887+ db = null;
888+ }
889+ callback(db);
890+ }, log);
891+ return;
892+ }
893+ log("Connecting to DesktopCouch using saved environment.");
894+ callback(_connect(name, _desktopcouch_environment));
895+}
896
897=== added file 'mozmill/tests/test_desktopcouch.js'
898--- mozmill/tests/test_desktopcouch.js 1970-01-01 00:00:00 +0000
899+++ mozmill/tests/test_desktopcouch.js 2011-02-08 10:38:53 +0000
900@@ -0,0 +1,64 @@
901+const TIMEOUT = 5000;
902+
903+var setupModule = function(module) {
904+ module.controller = mozmill.getBrowserController();
905+ module.desktopcouch = {};
906+ module.jum = {};
907+ Components.utils.import("resource://bindwood/desktopcouch.jsm", module.desktopcouch);
908+ Components.utils.import("resource://mozmill/modules/jum.js", module.jum);
909+};
910+
911+var test_get_desktopcouch_environment = function() {
912+ var log_messages = [];
913+ var environment = null;
914+ var done = false;
915+
916+ var callback = function(env) {
917+ environment = env;
918+ done = true;
919+ };
920+ var log = function(msg) {
921+ log_messages.push(msg);
922+ };
923+ desktopcouch._get_desktopcouch_environment(
924+ function(env) {
925+ environment = env;
926+ done = true;
927+ }, function(message) {
928+ log_messages.push(message);
929+ });
930+ controller.waitFor(
931+ function() { return done;}, "Environment loaded", TIMEOUT);
932+ jum.assertEquals(done, true);
933+ jum.assertNotEquals(environment, null);
934+ jum.assertEquals(log_messages.length, 1);
935+ jum.assertEquals(log_messages[0], "Got our environment, proceeding.");
936+};
937+
938+var test_connect_desktopcouch = function() {
939+ var database = null;
940+ var done = false;
941+
942+ desktopcouch.connect_desktopcouch(
943+ "test_bookmarks",
944+ function (db) {
945+ database = db;
946+ done = true;
947+ }, function (message) { });
948+ controller.waitFor(
949+ function() { return done;}, "Connected to CouchDB", TIMEOUT);
950+ jum.assertEquals(done, true);
951+ jum.assertNotEquals(database, null);
952+
953+ // Ensure the database exists.
954+ try {
955+ database.createDb();
956+ } catch (e) {
957+ if (e.error != 'file_exists') {
958+ throw(e);
959+ }
960+ }
961+
962+ var info = database.info();
963+ jum.assertEquals(info.db_name, "test_bookmarks");
964+};

Subscribers

People subscribed via source and target branches