Merge lp:~urbanape/bindwood/manifest into lp:bindwood

Proposed by Zachery Bir
Status: Merged
Approved by: Guillermo Gonzalez
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~urbanape/bindwood/manifest
Merge into: lp:bindwood
Diff against target: 2221 lines (+1100/-769)
11 files modified
content/bindwood.js (+852/-562)
content/browserOverlay.xul (+1/-2)
content/couch.js (+15/-0)
content/sha1.js (+0/-202)
couchdb_env.sh (+0/-2)
install.rdf (+1/-1)
tests/run_test.sh (+20/-0)
tests/test_get_views.js (+135/-0)
tests/test_oauth.js (+40/-0)
tests/test_oauth.py (+20/-0)
tests/view_test.sh (+16/-0)
To merge this branch: bzr merge lp:~urbanape/bindwood/manifest
Reviewer Review Type Date Requested Status
Guillermo Gonzalez Approve
dobey (community) Approve
Review via email: mp+17914@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Zachery Bir (urbanape) wrote :

I apologize in advance for the huge diff this is going to generate.

This branch does a lot, but only for brand new users:

  * Completely sync the structure of the bookmarks tree(s): Bookmarks, folders, feeds, and separators

  * Properly handle moves

To test:

  This branch includes an environment variable that will designate the database name to use. If you don't want the database to be replicated to Couch, first do the following:

  * Open your desktop couch futon, and edit the pair record in your management database (not sure what the id will be) to include a field called "excluded_names" if it's not already there. Create the value as a list, including the name of a fictional database you'll be testing with (I called mine 'test_boomarks' (clever, no?)).

  * I've moved the debugging information setting to a preference, so launch Firefox and add this boolean key to about:config:

    * bindwood.debug true

  * If you've used Bindwood before (particularly the Bindwood from my bindwood-exp PPA), you'll also want to reset (duplicating first) the bindwood.last_seq and bindwood.latest_modified preferences as well.

  * With this branch in place, and all wired up (see: https://wiki.ubuntu.com/TestingBindwood), quit Firefox and re-launch it from the command line as so:

    * BINDWOOD_DB=fictional_database_name firefox &

When Bindwood starts up, and your initial lag is over, you should be able to visit your Futon, and see more records. In particular, you'll see a root folder record at root_default (assuming you're using your default Firefox profile). It will have a 'children' field with three entries corresponding to the toolbar folder, the bookmarks menu, and the unfiled bookmarks. These will be important for testing the moving code.

TESTING

  * Structure: Follow the children ids in the root_default folder record, and you should be able to suss out the entire tree (if you're using folders).

  * Moving records: If you go into the record for the toolbar folder, and edit its children to swap the first and second ids, and save it, you should see things shuffle around a bit when Bindwood performs its next pull sync, and the first and second entries in your toolbar should be swapped.

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

I should note that both the subsequent client sync and pre-existing user migration code paths are not accounted for in this branch, and will each be the target of a following (hopefully much smaller) branch.

Revision history for this message
dobey (dobey) wrote :

We don't need to include the COPYING.BSD file here. As a whole, bindwood is GPLv3. Also, The Regents of UC are not the copyright holders of the BSD licesned code that you're including. Mozilla is. That copyright needs to be retained in the bindwood.js file. Derivitve works are not required to be the same BSD license, and we're allowed to ship it as GPLv3 with our code, but the (C) Mozilla must be retained from the original sync.js in the license header, unless all of those parts have been removed. Otherwise, they still retain copyright on part of the code.

review: Needs Fixing
lp:~urbanape/bindwood/manifest updated
48. By Zachery Bir

Don't need this.

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

Well, the file in question (new sync.js) is no longer needed by the code, and has since been removed, along with COPYING.BSD. Diff should reflect that shortly.

Revision history for this message
dobey (dobey) :
review: Approve
Revision history for this message
Guillermo Gonzalez (verterok) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== renamed file 'content/sync.js' => 'content/bindwood.js'
2--- content/sync.js 2009-10-26 19:33:27 +0000
3+++ content/bindwood.js 2010-01-22 19:18:13 +0000
4@@ -20,6 +20,8 @@
5 var Bindwood = {
6 bookmarksService: Cc["@mozilla.org/browser/nav-bookmarks-service;1"]
7 .getService(Ci.nsINavBookmarksService),
8+ livemarkService: Cc["@mozilla.org/browser/livemark-service;2"]
9+ .getService(Ci.nsILivemarkService),
10 uuidService: Cc["@mozilla.org/uuid-generator;1"]
11 .getService(Ci.nsIUUIDGenerator),
12 annotationService: Cc["@mozilla.org/browser/annotation-service;1"]
13@@ -36,22 +38,54 @@
14 .getService(Ci.nsIProperties),
15 windowService: Cc["@mozilla.org/appshell/window-mediator;1"]
16 .getService(Ci.nsIWindowMediator),
17+ // Technically, a branch, rather than the actual service, but
18+ // consistency wins, I think
19+ prefsService: Cc["@mozilla.org/preferences-service;1"]
20+ .getService(Ci.nsIPrefService).getBranch("bindwood."),
21+
22+ TYPE_BOOKMARK: "http://www.freedesktop.org/wiki/Specifications/desktopcouch/bookmark",
23+ TYPE_FOLDER: "http://www.freedesktop.org/wiki/Specifications/desktopcouch/folder",
24+ TYPE_FEED: "http://www.freedesktop.org/wiki/Specifications/desktopcouch/feed",
25+ TYPE_SEPARATOR: "http://www.freedesktop.org/wiki/Specifications/desktopcouch/separator",
26
27 push: 'ENABLED', // Start off enabled
28 annotationKey: "bindwood/uuid",
29 uuidItemIdMap: {},
30+ records: [],
31+ seen_revisions: {},
32+ timestamps: {},
33
34 // Debugging/Console Behavior
35 writeMessage: function(aMessage) {
36 // convenience method for logging. Way better than alert()s.
37- if (Bindwood.envService.exists("BINDWOOD_DEBUG")) {
38- Bindwood.consoleService.logStringMessage("Bindwood: " + aMessage);
39+ if (Bindwood.prefsService.getBoolPref("debug")) {
40+ Bindwood.consoleService.logStringMessage(
41+ "Bindwood: " + aMessage);
42 }
43 },
44
45 writeError: function(aMessage, e) {
46 // This should fire whether we're in DEBUG or not
47- Bindwood.consoleService.logStringMessage("Bindwood: " + aMessage + " message: '" + e.message + "', reason: '" + e.reason + "', description: '" + e.description + "', error: '" + e.error + "'");
48+ Bindwood.consoleService.logStringMessage(
49+ "Bindwood: " + aMessage +
50+ " message: '" + e.message +
51+ "', reason: '" + e.reason +
52+ "', description: '" + e.description +
53+ "', error: '" + e.error +
54+ "', raw error: '" + JSON.stringify(e) + "'");
55+ },
56+
57+ noteStartTime: function(key) {
58+ var now = new Date();
59+ Bindwood.timestamps[key] = now.getTime();
60+ },
61+
62+ noteEndTime: function(key) {
63+ var now = new Date();
64+ var diff = now.getTime() - Bindwood.timestamps[key];
65+ Bindwood.writeMessage(
66+ key + " took " + diff + " milliseconds");
67+ delete Bindwood.timestamps[key];
68 },
69
70 extractProfileName: function(path) {
71@@ -69,7 +103,8 @@
72 var possible_profile = segments[segments.length - 1];
73 var first_period = possible_profile.indexOf('.');
74 if (first_period == -1) {
75- return possible_profile; // no periods in the last segment, return as is
76+ // no periods in the last segment, return as is
77+ return possible_profile;
78 } else {
79 return possible_profile.substr(first_period + 1);
80 }
81@@ -81,10 +116,11 @@
82 // http://forums.mozillazine.org/viewtopic.php?f=19&t=657911&start=0
83 // It ensures that we're only running that code on the first window.
84
85- Bindwood.currentProfile = Bindwood.extractProfileName(Bindwood.directoryService.get('ProfD', Ci.nsIFile).path);
86+ Bindwood.currentProfile = Bindwood.extractProfileName(
87+ Bindwood.directoryService.get('ProfD', Ci.nsIFile).path);
88
89 if(Bindwood.windowService.getEnumerator("").getNext() == window) {
90- Bindwood.getCouchEnvironment(Bindwood.startProcess);
91+ Bindwood.getCouchEnvironment(Bindwood.setUpEnvironment);
92 }
93 },
94
95@@ -96,22 +132,22 @@
96 // find OS temp dir to put the tempfile in
97 // https://developer.mozilla.org/index.php?title=File_I%2F%2FO#Getting_special_files
98 var tmpdir = Cc["@mozilla.org/file/directory_service;1"]
99- .getService(Ci.nsIProperties)
100- .get("TmpD", Ci.nsIFile);
101+ .getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
102 // create a randomly named tempfile in the tempdir
103 var tmpfile = Cc["@mozilla.org/file/local;1"]
104- .createInstance(Ci.nsILocalFile);
105+ .createInstance(Ci.nsILocalFile);
106 tmpfile.initWithPath(tmpdir.path + "/desktopcouch." + Math.random());
107 tmpfile.createUnique(tmpfile.NORMAL_FILE_TYPE, 0600);
108
109 // find the D-Bus bash script, which is in our extension folder
110 var MY_ID = "bindwood@ubuntu.com";
111- var em = Cc["@mozilla.org/extensions/manager;1"].
112- getService(Ci.nsIExtensionManager);
113- var couchdb_env_script = em.getInstallLocation(MY_ID).getItemFile(MY_ID, "couchdb_env.sh");
114+ var em = Cc["@mozilla.org/extensions/manager;1"]
115+ .getService(Ci.nsIExtensionManager);
116+ var couchdb_env_script = em.getInstallLocation(MY_ID)
117+ .getItemFile(MY_ID, "couchdb_env.sh");
118 // create an nsILocalFile for the executable
119 var nsifile = Cc["@mozilla.org/file/local;1"]
120- .createInstance(Ci.nsILocalFile);
121+ .createInstance(Ci.nsILocalFile);
122 nsifile.initWithPath(couchdb_env_script.path);
123
124 // create an nsIProcess2 to execute this bash script
125@@ -126,25 +162,29 @@
126 // If the script exists cleanly, we should have a file
127 // containing the port couch is running on as well as
128 // the various OAuth tokens necessary to talk to it.
129+ var shouldProceed = true;
130 if (finishState == "process-finished") {
131 // read temp file to find couch environment
132 // https://developer.mozilla.org/en/Code_snippets/File_I%2f%2fO#Reading_from_a_file
133 var environment;
134- var fstream = Cc["@mozilla.org/network/file-input-stream;1"].
135- createInstance(Ci.nsIFileInputStream);
136- var cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].
137- createInstance(Ci.nsIConverterInputStream);
138+ var fstream = Cc["@mozilla.org/network/file-input-stream;1"]
139+ .createInstance(Ci.nsIFileInputStream);
140+ var cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]
141+ .createInstance(Ci.nsIConverterInputStream);
142 fstream.init(tmpfile, -1, 0, 0);
143 cstream.init(fstream, "UTF-8", 0, 0);
144 let (str = {}) {
145- cstream.readString(-1, str); // read the whole file and put it in str.value
146- environment = str.value;
147+ // read the whole file and put it in str.value
148+ cstream.readString(-1, str);
149+ environment = str.value;
150 };
151 cstream.close(); // this closes fstream
152- environment = environment.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
153+ environment = environment.replace(/^\s\s*/, '')
154+ .replace(/\s\s*$/, '');
155 } else {
156 // If we fail, we should just return
157 Bindwood.writeMessage("D-Bus port find failed");
158+ shouldProceed = false;
159 }
160 tmpfile.remove(false);
161
162@@ -152,19 +192,23 @@
163 // No Couch environment found. Just spit out a
164 // message and return, stopping Bindwood from
165 // doing anything further.
166- Bindwood.writeError("No suitable Couch environment found. Not proceeding.", e);
167- } else if (environment) {
168- // If we don't have a Couch environment, don't bother
169- // trying to fall back on the system CouchDB.
170+ Bindwood.writeError(
171+ "No suitable Couch environment found." +
172+ " Not proceeding.", e);
173+ shouldProceed = false;
174+ }
175+
176+ if (shouldProceed && environment) {
177 continueFunction(environment);
178- } else { // No environment found at all
179- return;
180+ } else {
181+ // Unregister our observer for bookmark events; we're done
182+ Bindwood.bookmarksService.removeObserver(Bindwood.Observer);
183 }
184 }
185 });
186 },
187
188- startProcess: function(couchEnvironment) {
189+ setUpEnvironment: function(couchEnvironment) {
190 var env_array = couchEnvironment.split(':');
191 var port = env_array[0];
192 var consumer_key = env_array[1];
193@@ -188,81 +232,229 @@
194 }
195 };
196
197- Bindwood.couch = new CouchDB('bookmarks');
198-
199- try {
200- Bindwood.pushBookmarks();
201- } catch(e) {
202- Bindwood.writeError("Error when calling pushBookmarks: ", e);
203- }
204- Bindwood.createViews();
205- Bindwood.pullBookmarks();
206- },
207-
208- createViews: function() {
209- var views = [{ id: "_design/all_bookmarks",
210- map: "function(doc) { " +
211- "var scheme = doc.uri.split(':',1)[0]; " +
212- "var uri; " +
213- "if (scheme == 'http' || scheme == 'https') {" +
214- "uri = doc.uri.split('/')[2];" +
215- "if (uri.length < 30) {" +
216- " uri += '/' + " +
217- "doc.uri.split('/',4)[3].substr(0,30-uri.length) + '...';" +
218- "}" +
219- "} else {" +
220- "uri = scheme + ' URL';" +
221- "}" +
222- "if (!doc.deleted) {" +
223- "emit(doc.title, uri);" +
224- "}" +
225- "}" },
226- { id: "_design/deleted_bookmarks",
227- map: "function(doc) { if (doc.deleted) { emit (doc.title, doc.uri); } }" },
228- { id: "_design/" + Bindwood.currentProfile,
229- 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);}}" }];
230- for (var i = 0; i < views.length; i++) {
231- var view_info = views[i];
232- var dirty = false;
233+ var db_name = 'bookmarks';
234+ if (Bindwood.envService.exists('BINDWOOD_DB')) {
235+ db_name = Bindwood.envService.get('BINDWOOD_DB');
236+ }
237+
238+ Bindwood.couch = new CouchDB(db_name);
239+
240+ try {
241+ Bindwood.startProcess();
242+ } catch(e) {
243+ Bindwood.writeError(
244+ "Something wrong with the process, exiting.", e);
245+ return;
246+ }
247+ },
248+
249+ getLastSequence: function() {
250+ var seq;
251+ try {
252+ seq = Bindwood.prefsService.getIntPref('last_seq');
253+ } catch(e) {
254+ seq = 0;
255+ }
256+ return seq;
257+ },
258+
259+ setLastSequence: function(seq) {
260+ Bindwood.prefsService.setIntPref('last_seq', seq);
261+ return seq;
262+ },
263+
264+ getLatestModified: function() {
265+ var mod;
266+ try {
267+ mod = Number(Bindwood.prefsService.getCharPref('latest_modified'));
268+ } catch(e) {
269+ mod = 0;
270+ }
271+ return mod;
272+ },
273+
274+ setLatestModified: function(mod) {
275+ Bindwood.prefsService.setCharPref(
276+ 'latest_modified', Number(mod).toString());
277+ return mod;
278+ },
279+
280+ startProcess: function() {
281+ Bindwood.writeMessage("Starting process");
282+ Bindwood.last_seq = Bindwood.getLastSequence();
283+ Bindwood.writeMessage("Got our last known sequence number: " + Bindwood.last_seq);
284+ Bindwood.latest_modified = Bindwood.getLatestModified();
285+ Bindwood.writeMessage("Got our latest known last_modified: " + Bindwood.latest_modified);
286+
287+ Bindwood.writeMessage("Ensuring the database exisits");
288+ Bindwood.ensureDatabase();
289+ Bindwood.writeMessage("Ensuring the views exist");
290+ Bindwood.ensureViews();
291+ Bindwood.writeMessage("Ensuring our scratch folder exists");
292+ Bindwood.scratch_folder = Bindwood.ensureLocalScratchFolder();
293+
294+ var HAVE_LAST_SEQ = Bindwood.last_seq ? 1 : 0; // 0 or 1
295+ var HAVE_PROFILE_ROOT = Bindwood.profileExists(); // 0 or 2
296+
297+ switch(HAVE_LAST_SEQ | HAVE_PROFILE_ROOT) {
298+ case 0:
299+ // Neither the profile root exists, nor do we have a last_seq. Ergo, we are a first
300+ // time user. Proceed normally.
301+
302+ // Bindwood.display_first_sync_page();
303+ break;
304+ case 1:
305+ // We have a last_seq, but the profile root does not exist. Ergo, we are an old user
306+ // and must migrate to the new way of doing things.
307+
308+ /* Migration strategy:
309+
310+ Pull all records from Couch, and for each:
311+
312+ - get the uuid off the bookmark.
313+
314+ - look up itemId by uuid
315+
316+ - annotate the itemId with the document's _id
317+
318+ - delete the uuid field off the record
319+
320+ */
321+
322+ // Bindwood.display_migration_sync_page();
323+ break;
324+ case 2:
325+ // We have no last_seq, but the profile root exists. Ergo, we are starting up a
326+ // subsequent client. We must make our local bookmarks look like remote, and ensure
327+ // that any unaccounted for local bookmarks are sent to CouchDB.
328+
329+ // Bindwood.display_subsequent_client_sync_page();
330+ // Show a helpful page explaining what's happening, so the user doesn't feel
331+ // something is broken.
332+
333+ // Bindwood.move_local_records_to_scratch();
334+ // Move all local records (folders, bookmarks, &c) to a temporary folder
335+
336+ // Bindwood.situate_remote_records();
337+ // Pull all remote records, putting them in place according to pullRecords()
338+
339+ // Bindwood.situate_local_records();
340+ // Iterate through our tmp tree, moving back bookmarks that aren't present.
341+
342+ // Bindwood.remove_tmp_folder();
343+ // Anything left in the tmp folder has already been accounted for.
344+ break;
345+ case 3: // Should this just be default?
346+ // We have a last_seq, and the profile root exists. Ergo, we are a normally operating
347+ // client. Proceed normally.
348+ break;
349+ default:
350+ break;
351+ }
352+
353+ Bindwood.noteStartTime('Generating the manifest');
354+ Bindwood.generateManifest();
355+ Bindwood.noteEndTime('Generating the manifest');
356+ Bindwood.noteStartTime('Pushing records');
357+ Bindwood.pushLatestRecords();
358+ Bindwood.noteEndTime('Pushing records');
359+ Bindwood.pullChanges();
360+ },
361+
362+ ensureDatabase: function() {
363+ // This function will create the database if it does not
364+ // exist, but we return a boolean representing whether or not
365+ // the database existed prior.
366+ try {
367+ Bindwood.couch.createDb();
368+ } catch (e) {
369+ if (e.error != 'file_exists') {
370+ Bindwood.writeError("Error creating database: ", e);
371+ throw(e);
372+ }
373+ }
374+ },
375+
376+ ensureViews: function() {
377+ var view = {
378+ _id: "_design/bookmarks",
379+ views: {
380+ profile: {
381+ 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)) {emit(doc.application_annotations.Firefox.profile, [doc.title, uri]);}}"
382+ },
383+ live_bookmarks: {
384+ 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) {emit(doc.title, uri);}}"
385+ },
386+ deleted_bookmarks: {
387+ map: "function(doc) { if (doc.deleted) { emit (doc.title, doc.uri); } }"
388+ }
389+ }
390+ };
391+
392+ try {
393+ var doc = Bindwood.couch.open(view._id);
394+ if (!doc) {
395+ doc = view;
396+ }
397 try {
398- var new_doc;
399- var current_doc = Bindwood.couch.open(view_info.id);
400- if (current_doc !== null) {
401- var old_map = current_doc.views.display.map;
402- if (old_map != view_info.map) { // Ours is definitive
403- new_doc = current_doc;
404- new_doc.views.display.map = view_info.map;
405- dirty = true;
406- }
407- } else {
408- new_doc = {
409- _id: view_info.id,
410- views: {
411- display: {
412- map: view_info.map
413- }
414- }
415- };
416- dirty = true;
417- }
418- if (dirty) {
419- try {
420- Bindwood.couch.save(new_doc);
421- } catch(e) {
422- Bindwood.writeError("Problem saving view: ", e);
423- }
424- }
425+ Bindwood.couch.save(doc);
426 } catch(e) {
427- // some kind of error fetching the existing design doc
428- Bindwood.writeError("Problem checking for view: ", e);
429- }
430- }
431+ Bindwood.writeError("Problem saving view: ", e);
432+ }
433+ } catch(e) {
434+ // some kind of error fetching the existing design doc
435+ Bindwood.writeError("Problem checking for view: ", e);
436+ }
437+ },
438+
439+ ensureLocalScratchFolder: function() {
440+ // Because records come from CouchDB without a parent field, until
441+ // we get an updated folder record with a record situated properly
442+ // in its children field, we need a place to temporarily store
443+ // records from Couch. This is that place.
444+ var folder = Bindwood.bookmarksService.unfiledBookmarksFolder;
445+ var rootNode = Bindwood.getFolderRoot(folder);
446+ rootNode.containerOpen = true;
447+ for (var i=0; i<rootNode.childCount; i++) {
448+ var node = rootNode.getChild(i);
449+ if (node.title == 'Desktop Couch Scratch') {
450+ rootNode.containerOpen = false;
451+ return node.itemId;
452+ }
453+ }
454+ rootNode.containerOpen = false;
455+
456+ var folderId = Bindwood.bookmarksService.createFolder(
457+ folder, 'Desktop Couch Scratch', -1);
458+ return folderId;
459+ },
460+
461+ profileExists: function() {
462+ // Check to see if the current profile's manifest exists in
463+ // the database.
464+ try {
465+ Bindwood.couch.open('root_' + Bindwood.currentProfile);
466+ } catch(e) {
467+ if (e.error == 'not_found') {
468+ return 0;
469+ }
470+ }
471+ return 2;
472 },
473
474 // Looking up records locally
475 annotateItemWithUUID: function(itemId, seed_uuid) {
476- var uuid = seed_uuid ? seed_uuid : Bindwood.uuidService.generateUUID().toString();
477- Bindwood.annotationService.setItemAnnotation(itemId, Bindwood.annotationKey, uuid, 0, Bindwood.annotationService.EXPIRE_NEVER);
478+ var uuid = (seed_uuid ?
479+ seed_uuid :
480+ Bindwood.uuidService.generateUUID().toString());
481+ Bindwood.writeMessage("UUID We came up with: " + uuid);
482+ Bindwood.writeMessage("Annotating the item now.");
483+ Bindwood.annotationService.setItemAnnotation(
484+ itemId,
485+ Bindwood.annotationKey,
486+ uuid,
487+ 0,
488+ Bindwood.annotationService.EXPIRE_NEVER);
489 // Whenever we create a new UUID, stash it and the itemId in
490 // our local cache.
491 Bindwood.uuidItemIdMap[uuid] = itemId;
492@@ -275,16 +467,19 @@
493 var itemId = Bindwood.uuidItemIdMap[uuid];
494
495 if (!itemId) {
496- var items = Bindwood.annotationService.getItemsWithAnnotation(Bindwood.annotationKey, {});
497+ var items = Bindwood.annotationService.getItemsWithAnnotation(
498+ Bindwood.annotationKey, {});
499 var num_items = items.length;
500 for (var i = 0; i < items.length; i++) {
501- if (Bindwood.annotationService.getItemAnnotation(items[i], Bindwood.annotationKey) == uuid) {
502+ if (Bindwood.annotationService.getItemAnnotation(
503+ items[i], Bindwood.annotationKey) == uuid) {
504 Bindwood.uuidItemIdMap[uuid] = itemId = items[i];
505 break;
506 }
507 }
508 if (!itemId) {
509- Bindwood.writeMessage("XXX: Still haven't found the right itemId!");
510+ Bindwood.writeMessage(
511+ "XXX: Still haven't found the right itemId!");
512 }
513 }
514 return itemId;
515@@ -295,80 +490,65 @@
516 // and return it.
517 var uuid;
518 try {
519- uuid = Bindwood.annotationService.getItemAnnotation(itemId, Bindwood.annotationKey);
520+ uuid = Bindwood.annotationService.getItemAnnotation(
521+ itemId, Bindwood.annotationKey);
522+ Bindwood.uuidItemIdMap[uuid] = itemId;
523 } catch(e) {
524- Bindwood.writeError("Couldn't find a UUID for itemId: " + itemId, e);
525+ Bindwood.writeError(
526+ "Couldn't find a UUID for itemId: " + itemId, e);
527 uuid = Bindwood.makeLocalChangeOnly(
528- function() { return Bindwood.annotateItemWithUUID(itemId, null); } );
529+ function() { return Bindwood.annotateItemWithUUID(
530+ itemId, null); } );
531 }
532
533 return uuid;
534 },
535
536- resolveLocalBookmark: function(bm) {
537- // This function is only used to resolve bookmarks from Couch with uuids
538- var couch_uuid = bm.application_annotations.Firefox.uuid;
539- var itemId = Bindwood.itemIdForUUID(couch_uuid);
540- if (itemId) {
541- return itemId;
542- } else {
543- // This bookmark has a uuid, but it's not one of ours.
544- // We need to work out whether (a) it's the same as one
545- // of ours but with a different uuid (so we need to
546- // make the uuids the same), or (b) it's a new one
547- // that happens to have been created on a different
548- // machine.
549- try {
550- var uri = Bindwood.ioService.newURI(bm.uri, null, null);
551- } catch(e) {
552- Bindwood.writeError("Problem creating URI (" + bm.uri + ") for bookmark, skipping: ", e);
553- throw e;
554- }
555- var ids = Bindwood.bookmarksService.getBookmarkIdsForURI(uri, {});
556- if (ids.length == 0) {
557- return;
558- }
559- Bindwood.writeMessage("Returning the first bookmark for that URI");
560- return ids[0]; // XXX: Just return the first one. Right now, we don't worry too much about duplicates.
561- }
562- },
563-
564- couchReadyObjectForNodeInBookmarksFolder: function(node, bookmarksFolderName) {
565- var itemId = node.itemId;
566-
567- var title = Bindwood.bookmarksService.getItemTitle(itemId);
568+ couchRecordForItemId: function(itemId) {
569+ var bs = Bindwood.bookmarksService;
570+
571 var uuid = Bindwood.uuidForItemId(itemId);
572- var folder = bookmarksFolderName;
573 var profile = Bindwood.currentProfile;
574+ var last_modified = Bindwood.bookmarksService.getItemLastModified(itemId);
575
576- var bookmark = {
577- title: title,
578+ var record = {
579+ "_id": uuid,
580 application_annotations: {
581 Firefox: {
582- uuid: uuid,
583- folder: folder,
584- profile: profile
585+ profile: profile,
586+ last_modified: last_modified
587 }
588 }
589 };
590
591- switch(Bindwood.bookmarksService.getItemType(itemId)) {
592- case Bindwood.bookmarksService.TYPE_BOOKMARK:
593- bookmark.record_type = "http://www.freedesktop.org/wiki/Specifications/desktopcouch/bookmark";
594- bookmark.uri = Bindwood.bookmarksService.getBookmarkURI(itemId).spec;
595- break;
596- case Bindwood.bookmarksService.TYPE_FOLDER:
597- bookmark.record_type = "http://www.freedesktop.org/wiki/Specifications/desktopcouch/folder";
598- break;
599- case Bindwood.bookmarksService.TYPE_SEPARATOR:
600- bookmark.record_type = "http://www.freedesktop.org/wiki/Specifications/desktopcouch/separator";
601+ switch(bs.getItemType(itemId)) {
602+ case bs.TYPE_BOOKMARK:
603+ record.title = bs.getItemTitle(itemId);
604+ record.record_type = Bindwood.TYPE_BOOKMARK;
605+ record.uri = bs.getBookmarkURI(itemId).spec;
606+ break;
607+ case bs.TYPE_FOLDER:
608+ record.title = bs.getItemTitle(itemId);
609+
610+ // Firefox doesn't differentiate between regular folders
611+ // and livemark folders. *sigh* So, we override it here
612+ if (Bindwood.livemarkService.isLivemark(itemId)) {
613+ record.record_type = Bindwood.TYPE_FEED;
614+ record.site_uri = Bindwood.livemarkService.getSiteURI(itemId).spec;
615+ record.feed_uri = Bindwood.livemarkService.getFeedURI(itemId).spec;
616+ } else {
617+ record.record_type = Bindwood.TYPE_FOLDER;
618+ record.children = [];
619+ }
620+ break;
621+ case bs.TYPE_SEPARATOR:
622+ record.record_type = Bindwood.TYPE_SEPARATOR;
623 break;
624 default:
625 break;
626 }
627
628- Bindwood.writeMessage("Prepared bookmark (" + uuid + ") for Couch: " + JSON.stringify(bookmark));
629- return bookmark;
630+ return record;
631 },
632
633 makeLocalChangeOnly: function(func) {
634@@ -379,334 +559,419 @@
635 },
636
637 // Back and forth
638- pushBookmarks: function() {
639- // Prime the pump, so to speak, by uploading all our local
640- // bookmarks to CouchDB (if they're not there already).
641- // Create the DB if it doesn't already exist
642- try {
643- Bindwood.couch.createDb();
644- } catch (e) {
645- if (e.error == 'file_exists') {
646- Bindwood.writeMessage("Database already exists. We're okay.");
647- } else {
648- Bindwood.writeError("Error when creating database in pushBookmarks: ", e);
649- }
650- }
651-
652- try {
653- Bindwood.pushBookmarksFromFolder("toolbarFolder", Bindwood.bookmarksService.toolbarFolder);
654- } catch(e) {
655- Bindwood.writeError("Error pushing toolbarFolder bookmarks: ", e);
656- }
657-
658- try {
659- Bindwood.pushBookmarksFromFolder("bookmarksMenuFolder", Bindwood.bookmarksService.bookmarksMenuFolder);
660- } catch(e) {
661- Bindwood.writeError("Error pushing bookmarksMenuFolder bookmarks: ", e);
662- }
663-
664- try {
665- Bindwood.pushBookmarksFromFolder("unfiledBookmarksFolder", Bindwood.bookmarksService.unfiledBookmarksFolder);
666- } catch(e) {
667- Bindwood.writeError("Error pushing unfiledBookmarksFolder bookmarks: ", e);
668- }
669- },
670-
671- getBookmarksFolder: function(bookmarksFolder) {
672+ getFolderRoot: function(folder) {
673 var options = Bindwood.historyService.getNewQueryOptions();
674 var query = Bindwood.historyService.getNewQuery();
675- query.setFolders([bookmarksFolder], 1);
676+ query.setFolders([folder], 1);
677 var result = Bindwood.historyService.executeQuery(query, options);
678 return result.root;
679 },
680
681- getBookmarksFromFolder: function(bookmarksFolderName, bookmarksFolder, accum) {
682- Bindwood.writeMessage("Fetching bookmarks from: " + bookmarksFolderName);
683- var rootNode = Bindwood.getBookmarksFolder(bookmarksFolder);
684- rootNode.containerOpen = true;
685- for (var i=0; i<rootNode.childCount; i++) {
686- var node = rootNode.getChild(i);
687- // If node is a Livemark container or a Dynamic container, skip it
688- if (PlacesUtils.nodeIsDynamicContainer(node) ||
689- PlacesUtils.nodeIsLivemarkContainer(node)) {
690+ getUUIDsFromFolder: function(folder) {
691+ var folderRoot = Bindwood.getFolderRoot(folder);
692+ folderRoot.containerOpen = true;
693+ var uuids = [];
694+
695+ for (var i=0; i<folderRoot.childCount; i++) {
696+ var node = folderRoot.getChild(i);
697+ uuids.push(Bindwood.uuidForItemId(node.itemId));
698+ }
699+
700+ return uuids;
701+ },
702+
703+ getRecordsFromFolder: function(folder) {
704+ // Make a record for us, populating a children field with the _ids of all our children
705+ var folderRoot = Bindwood.getFolderRoot(folder);
706+ folderRoot.containerOpen = true;
707+
708+ var folder_record = Bindwood.couchRecordForItemId(folder);
709+
710+ for (var i=0; i<folderRoot.childCount; i++) {
711+ var node = folderRoot.getChild(i);
712+
713+ var record = Bindwood.couchRecordForItemId(node.itemId);
714+ folder_record.children.push(record._id);
715+
716+ // If node is a folder (but not a Livemark or Dynamic container),
717+ // descend into it, looking for its contents
718+ if (record.record_type == Bindwood.TYPE_FOLDER) {
719+ Bindwood.getRecordsFromFolder(node.itemId)
720+ } else {
721+ Bindwood.records.push(record);
722+ }
723+ }
724+ folderRoot.containerOpen = false;
725+ Bindwood.records.push(folder_record);
726+ return folder_record;
727+ },
728+
729+ generateManifest: function() {
730+ // Fill up the Bindwood.manifest and initial push lists
731+ var primaryFolders = [Bindwood.bookmarksService.toolbarFolder,
732+ Bindwood.bookmarksService.bookmarksMenuFolder,
733+ Bindwood.bookmarksService.unfiledBookmarksFolder];
734+
735+ var profile_root = {
736+ "_id": "root_" + Bindwood.currentProfile,
737+ children: [],
738+ application_annotations: {
739+ Firefox: {
740+ profile: Bindwood.currentProfile,
741+ last_modified: 1
742+ }
743+ }
744+ };
745+
746+ for (var i=0; i<primaryFolders.length; i++) {
747+ var folder = primaryFolders[i];
748+ var folder_record = Bindwood.getRecordsFromFolder(folder);
749+
750+ profile_root.children.push(folder_record._id);
751+ }
752+
753+ Bindwood.records.push(profile_root);
754+ },
755+
756+ sortByLastModDesc: function(a, b) {
757+ var a_mod = a.application_annotations.Firefox.last_modified;
758+ var b_mod = b.application_annotations.Firefox.last_modified;
759+ return b_mod - a_mod; // descending
760+ },
761+
762+ pushLatestRecords: function() {
763+ Bindwood.records.sort(Bindwood.sortByLastModDesc);
764+ // Now that the record are all sorted descending by last
765+ // mod time, we can check each in turn to see if its mod time
766+ // is greater than our persisted mod time. Afterwards, we'll
767+ // set our persisted latest mod time to be the first record's
768+ // mod time.
769+ var newest = Bindwood.records[0];
770+ var newest_ff = newest.application_annotations.Firefox;
771+ var new_latest_modified = newest_ff.last_modified;
772+ for (var i = 0; i < Bindwood.records.length; i++) {
773+ // find this record in CouchDB
774+ var record = Bindwood.records[i];
775+ var ff = record.application_annotations.Firefox;
776+
777+ if (ff.last_modified <= Bindwood.latest_modified) {
778 Bindwood.writeMessage(
779- "Skipping Dynamic or Livemark container: " + node.title);
780- continue;
781- }
782- // If node is a folder, descend into it, looking for its contents
783- if (node.type == 6) { // RESULT_TYPE_FOLDER
784- Bindwood.writeMessage("Descending into folder: " + node.title);
785- accum = accum.concat(Bindwood.getBookmarksFromFolder(node.title, node.itemId, accum));
786- } else if (node.type == 0 || node.type == 7) { // RESULT_TYPE_URI or RESULT_TYPE_SEPARATOR
787- Bindwood.writeMessage("Adding current node: " + node.title);
788- accum.push([node.itemId, Bindwood.couchReadyObjectForNodeInBookmarksFolder(node, bookmarksFolderName)]);
789- }
790- }
791- rootNode.containerOpen = false;
792- return accum;
793- },
794-
795- pushBookmarksFromFolder: function(bookmarksFolderName, bookmarksFolder) {
796- Bindwood.writeMessage("Pushing bookmarks from " + bookmarksFolderName);
797- var bookmarkData = Bindwood.getBookmarksFromFolder(bookmarksFolderName, bookmarksFolder, new Array());
798- for (var i = 0; i < bookmarkData.length; i++) {
799- // find this bookmark in CouchDB
800- var itemId = bookmarkData[i][0];
801- var bookmark = bookmarkData[i][1];
802- var uuid = bookmark.application_annotations.Firefox.uuid;
803- Bindwood.writeMessage("Bookmark so far: " + JSON.stringify(bookmark));
804-
805- // Even though this happens in annotateItemWithUUID as well,
806- // we're priming the cache here for first run.
807- Bindwood.uuidItemIdMap[uuid] = itemId;
808-
809- try {
810- var results = Bindwood.couch.query(function(doc) {
811- if (doc.application_annotations &&
812- doc.application_annotations.Firefox &&
813- doc.application_annotations.Firefox.uuid) {
814- emit(doc.application_annotations.Firefox.uuid, doc);
815- }
816- }, null, {
817- startkey: uuid, endkey: uuid
818- });
819- } catch(e) {
820- Bindwood.writeError("Error querying couch: ", e);
821- }
822-
823- if (results.rows.length === 0) {
824- // this bookmark is not in CouchDB, so write it
825+ "We've reached records we've already dealt with." +
826+ " Breaking out of the loop.");
827+ break;
828+ }
829+
830+ var doc = Bindwood.couch.open(record._id);
831+ if (!doc) {
832+ // this record is not in CouchDB, so write it
833 try {
834- Bindwood.couch.save(bookmark);
835- Bindwood.writeMessage("Saved bookmark (" + uuid + ") to Couch.");
836+ var response = Bindwood.couch.save(record);
837+ // We can avoid having to process this revision when we pull it later
838+ Bindwood.seen_revisions[response.rev] = true;
839 } catch(e) {
840- Bindwood.writeError("Problem saving bookmark to CouchDB; bookmark is " + JSON.stringify(bookmark) + ": ", e);
841+ Bindwood.writeError(
842+ "Problem saving record to CouchDB; record is " +
843+ JSON.stringify(record) + ": ", e);
844 }
845 } else {
846- Bindwood.writeMessage("This bookmark (" + uuid + ") is already in Couch, skipping");
847- // bookmark is already in CouchDB, so do nothing
848- }
849- }
850- },
851-
852- pullBookmarks: function() {
853- // Fetch all bookmark documents from the database
854- // The query function is evaluated by Couch, which doesn't know
855- // what Bindwood.RECORD_TYPE is, so we string-encode it first to
856- // include the literal value
857- Bindwood.writeMessage("Fetching bookmarks for " + Bindwood.currentProfile);
858- var rows = {rows: []};
859- try {
860- rows = Bindwood.couch.query(
861- "function (doc) {" +
862- " if (doc.record_type == \"http://www.freedesktop.org/wiki/Specifications/desktopcouch/bookmark\" &&" +
863- " doc.application_annotations &&" +
864- " doc.application_annotations.Firefox &&" +
865- " doc.application_annotations.Firefox.profile &&" +
866- " doc.application_annotations.Firefox.profile == \"" + Bindwood.currentProfile + "\") {" +
867- " emit(doc._id,doc);}}");
868- } catch(e) {
869- Bindwood.writeError("Problem fetching all bookmarks from Couch: ", e);
870- }
871- Bindwood.writeMessage("We got back " + rows.rows.length + " rows");
872- for (var i = 0; i < rows.rows.length; i++) {
873- var recordid = rows.rows[i].id;
874- Bindwood.writeMessage("Pulling record: " + recordid);
875- var bm = rows.rows[i].value;
876-
877- // First, check to see if the bookmark we've pulled down is flagged as deleted.
878- // If so, we should make sure any local copy we have of this bookmark has also been deleted.
879- if (bm.deleted) {
880- Bindwood.writeMessage("Bookmark in Couch marked as deleted; attempting to delete local copy.");
881- Bindwood.deleteLocalBookmark(bm);
882- } else if (bm.application_annotations &&
883- bm.application_annotations.Firefox &&
884- bm.application_annotations.Firefox.uuid) {
885- Bindwood.writeMessage("Bookmark in Couch has UUID, merging local copy.");
886- Bindwood.mergeLocalBookmark(bm, recordid);
887- } else {
888- Bindwood.writeMessage("Bookmark in Couch has no UUID, creating new local copy, annotating, and pushing back.");
889- Bindwood.addLocalBookmark(bm, recordid, null);
890- }
891- }
892+ // record is already in CouchDB, so do nothing
893+ Bindwood.writeMessage(
894+ "This record (" + record._id + ") is already in Couch, skipping");
895+ }
896+ }
897+ Bindwood.latest_modified = Bindwood.setLatestModified(
898+ new_latest_modified);
899+ },
900+
901+ pushFolderChildren: function(folder, children) {
902+ var doc = Bindwood.couch.open(Bindwood.uuidForItemId(folder));
903+ var new_children = children;
904+ if (!new_children) {
905+ new_children = Bindwood.getUUIDsFromFolder(folder);
906+ }
907+ doc.children = new_children;
908+ var response = Bindwood.couch.save(doc);
909+ Bindwood.seen_revisions[response.rev] = true;
910+ },
911+
912+ pullChanges: function() {
913+ Bindwood.pullRecords();
914+
915 // reschedule ourself
916 Bindwood.writeMessage("Successful run, rescheduling ourself");
917- setTimeout(Bindwood.pullBookmarks, 30000);
918- },
919-
920- deleteLocalBookmark: function(bm) {
921- // If we can't resolve the itemId, even by looking up URI, assume it's already gone.
922- var itemId = Bindwood.resolveLocalBookmark(bm);
923- if (itemId) {
924- Bindwood.makeLocalChangeOnly(
925- function() { return Bindwood.bookmarksService.removeItem(itemId); });
926- }
927- },
928-
929- mergeLocalBookmark: function(bm, recordid) {
930- // this bookmark has a uuid, so check its values haven't changed
931- // find the bookmark with this uuid
932- var itemId = Bindwood.resolveLocalBookmark(bm);
933- var couch_uuid = bm.application_annotations.Firefox.uuid;
934-
935- if (itemId) {
936- // Found one local bookmark. Replace its uuid to
937- // be the one from Couch.
938- var old_uuid = Bindwood.uuidForItemId(itemId);
939- if (old_uuid != couch_uuid) {
940- delete Bindwood.uuidItemIdMap[old_uuid];
941- Bindwood.makeLocalChangeOnly(
942- function() { return Bindwood.annotateItemWithUUID(itemId, couch_uuid); } );
943- }
944-
945- // XXX: I'm still not positive about this. While I'm far more sure that we're seeding
946- // Couch properly from the beginning and properly making new bookmarks, I still wonder
947- // if we'll see cases where Couch has null for title and uri, and how best to manage
948- // them. I'm thinking timestamps. - urbanape
949- var title = Bindwood.bookmarksService.getItemTitle(itemId);
950- if (title != bm.title) {
951- Bindwood.writeMessage("Resetting local title to title from Couch");
952- Bindwood.makeLocalChangeOnly(
953- function() { return Bindwood.bookmarksService.setItemTitle(itemId, bm.title); });
954- }
955-
956- var metadata = Bindwood.bookmarksService.getBookmarkURI(itemId);
957- if (metadata.spec != bm.uri) {
958- Bindwood.writeMessage("The URI from Couch (" + bm.uri + ") is different from local (" + metadata.spec + ")");
959- try {
960- var new_uri = Bindwood.ioService.newURI(bm.uri, null, null);
961- Bindwood.writeMessage("Creating a new URI for our local bookmark");
962- Bindwood.makeLocalChangeOnly(
963- function() { return Bindwood.bookmarksService.changeBookmarkURI(itemId, new_uri); });
964- } catch(e) {
965- Bindwood.writeError("Problem creating a new URI for bookmark: ", e);
966- }
967- }
968- } else {
969- /// No local bookmarks
970- Bindwood.writeMessage("No local bookmark found, must be a new entry in Couch. Creating locally.");
971- Bindwood.addLocalBookmark(bm, recordid, couch_uuid);
972- }
973- },
974-
975- guaranteeDesktopCouchFolder: function() {
976- // If we're syncing from CouchDB and we have new bookmarks,
977- // until we have proper replication of hierarchy and folders,
978- // we should put all "new" bookmarks in a collected, sane
979- // place. Unfiled bookmarks are hard to get to (only available
980- // through "Organize bookmarks"), so we'll put them in a
981- // folder in the Boomarks Menu called "Desktop Couch",
982- // creating that folder first, if necessary.
983- var bookmarksFolder = Bindwood.bookmarksService.bookmarksMenuFolder;
984- var rootNode = Bindwood.getBookmarksFolder(bookmarksFolder);
985- rootNode.containerOpen = true;
986- for (var i=0; i<rootNode.childCount; i++) {
987- var node = rootNode.getChild(i);
988- if (node.title == 'Desktop Couch') {
989- rootNode.containerOpen = false;
990- return node.itemId;
991- }
992- }
993- rootNode.containerOpen = false;
994-
995- var folderId = Bindwood.bookmarksService.createFolder(
996- bookmarksFolder, 'Desktop Couch', -1);
997- return folderId;
998- },
999-
1000- addLocalBookmark: function(bm, recordid, uuid) {
1001- // If uuid is present, we only need create the bookmark locally,
1002- // as it already exists (with uuid) in Couch.
1003- //
1004- // If uuid is null, then after we create it locally,
1005- // we want to annotate it and push back to Couch.
1006- //
1007- // By default, store new bookmarks in the unfiledBookmarksFolder
1008- var folder;
1009- if (bm.application_annotations &&
1010- bm.application_annotations.Firefox &&
1011- bm.application_annotations.Firefox.folder) {
1012- switch (bm.application_annotations.Firefox.folder) {
1013- case "toolbarFolder":
1014- folder = Bindwood.bookmarksService.toolbarFolder;
1015- break;
1016- case "bookmarksMenuFolder":
1017- folder = Bindwood.bookmarksService.bookmarksMenuFolder;
1018- break;
1019- default:
1020- folder = Bindwood.guaranteeDesktopCouchFolder();
1021- break;
1022- }
1023- }
1024-
1025+ setTimeout(Bindwood.pullChanges, 30000);
1026+ },
1027+
1028+ pullRecords: function() {
1029+ // Check to see if our prefsService has a preference set (I
1030+ // know, bad form) for last_seq, which would designate the
1031+ // last Couch sequence we've seen (this might be 0 if we've
1032+ // never synced before, in which case, we'd get all
1033+ // changes). Afterwards, set the last known sequence in
1034+ // prefs. Then, future polls will use last_seq as the start
1035+ // for finding changes.
1036+
1037+ // XXX: currently, we do a single changes pull. Eventually, if
1038+ // we need to use threads, we can do long polling in a
1039+ // background thread.
1040+
1041+ Bindwood.noteStartTime('Pulling records');
1042+ var results = {results: [], last_seq: 0};
1043 try {
1044- var new_uri = Bindwood.ioService.newURI(bm.uri, null, null);
1045+ results = Bindwood.couch.changes(
1046+ {since: Bindwood.last_seq},
1047+ null
1048+ );
1049 } catch(e) {
1050- Bindwood.writeError("Problem creating a new URI for bookmark: ", e);
1051- }
1052-
1053- var itemId = Bindwood.makeLocalChangeOnly(
1054- function() { return Bindwood.bookmarksService.insertBookmark(
1055- folder, new_uri, -1, bm.title); });
1056-
1057- if (uuid) { // We were provided a uuid, so no need to send back to Couch, just annotate locally
1058- Bindwood.makeLocalChangeOnly(
1059- function() { return Bindwood.annotateItemWithUUID(itemId, uuid); } );
1060- } else { // This is an unadorned bookmark from Couch. Save it to Couch once we establish its uuid
1061- var new_uuid = Bindwood.uuidForItemId(itemId);
1062- var doc = Bindwood.couch.open(recordid);
1063- if (!doc.application_annotations) {
1064- doc.application_annotations = {};
1065- }
1066- if (!doc.application_annotations.Firefox) {
1067- doc.application_annotations.Firefox = {};
1068- }
1069- doc.application_annotations.Firefox.uuid = new_uuid;
1070- try {
1071- Bindwood.couch.save(doc);
1072- Bindwood.writeMessage("Saved the newly annotated doc back to Couch");
1073- } catch(e) {
1074- Bindwood.writeError("Problem writing record for new bookmark: ",e);
1075- }
1076- }
1077- },
1078-
1079- findDocumentByUUID: function(uuid) {
1080- Bindwood.writeMessage("Looking up a document in Couch by uuid: " + uuid);
1081- var results = Bindwood.couch.query(function(doc) {
1082- if (doc.application_annotations &&
1083- doc.application_annotations.Firefox &&
1084- doc.application_annotations.Firefox.uuid) {
1085- emit(doc.application_annotations.Firefox.uuid, doc);
1086- }
1087- }, null, {
1088- startkey: uuid, endkey: uuid
1089- });
1090- if (results.rows.length === 0) {
1091- Bindwood.writeMessage("Problem finding document");
1092- return;
1093- }
1094- Bindwood.writeMessage("Found: " + JSON.stringify(results));
1095- return results;
1096+ Bindwood.writeError(
1097+ "Problem long polling bookmarks from Couch: ", e);
1098+ }
1099+ var revisions = results.results;
1100+ for (var i = 0; i < revisions.length; i++) {
1101+ var rev = revisions[i];
1102+ var revno = rev.changes[0].rev;
1103+ var recordid = rev.id;
1104+
1105+ // Skip (for now) if we're dealing with a root folder or a
1106+ // design doc
1107+ if (recordid.indexOf('root_') === 0 ||
1108+ recordid.indexOf('_design') === 0) {
1109+ Bindwood.writeMessage("Root profile or design doc, skipping...");
1110+ continue;
1111+ }
1112+
1113+ // Skip any revisions we've already seen (because we just
1114+ // put them there)
1115+ if (Bindwood.seen_revisions[revno]) {
1116+ Bindwood.writeMessage("We've seen this revision (" + revno + ") before, when we created it.");
1117+ delete Bindwood.seen_revisions[revno];
1118+ continue;
1119+ }
1120+
1121+ var record = Bindwood.couch.open(recordid);
1122+
1123+ if (!Bindwood.recordInCurrentProfile(record)) {
1124+ Bindwood.writeMessage("Record isn't in our current profile. Skipping...");
1125+ continue;
1126+ }
1127+
1128+ // Next, check to see if the record we've pulled down
1129+ // is flagged as deleted. If so, we should make sure any
1130+ // local copy we have of this record has also been
1131+ // deleted.
1132+ if (Bindwood.isDeleted(record)) {
1133+ Bindwood.makeLocalChangeOnly(
1134+ function() {
1135+ Bindwood.writeMessage(
1136+ "Record in Couch marked as deleted;" +
1137+ " attempting to delete local copy.");
1138+ Bindwood.deleteLocalRecord(record);
1139+ });
1140+ continue; // Don't bother continuing to process anything further in this revision
1141+ }
1142+
1143+ switch(record.record_type) {
1144+ case Bindwood.TYPE_BOOKMARK:
1145+ Bindwood.makeLocalChangeOnly(
1146+ function() {
1147+ Bindwood.processCouchBookmarkRevision(record);
1148+ });
1149+ break;
1150+ case Bindwood.TYPE_FOLDER:
1151+ Bindwood.makeLocalChangeOnly(
1152+ function() {
1153+ Bindwood.processCouchFolderRevision(record);
1154+ });
1155+ break;
1156+ case Bindwood.TYPE_FEED:
1157+ Bindwood.makeLocalChangeOnly(
1158+ function() {
1159+ Bindwood.processCouchFeedRevision(record);
1160+ });
1161+ break;
1162+ case Bindwood.TYPE_SEPARATOR:
1163+ Bindwood.makeLocalChangeOnly(
1164+ function() {
1165+ Bindwood.processCouchSeparatorRevision(record);
1166+ });
1167+ break;
1168+ default:
1169+ break;
1170+ }
1171+ // STOPPING HERE - Time to do per-type dispatch
1172+ }
1173+
1174+ Bindwood.last_seq = Bindwood.setLastSequence(results.last_seq);
1175+ Bindwood.noteEndTime('Pulling records');
1176+ },
1177+
1178+ recordInCurrentProfile: function(record) {
1179+ if (record.application_annotations &&
1180+ record.application_annotations.Firefox &&
1181+ record.application_annotations.Firefox.profile &&
1182+ record.application_annotations.Firefox.profile == Bindwood.currentProfile) {
1183+ return true;
1184+ }
1185+ return false;
1186+ },
1187+
1188+ isDeleted: function(record) {
1189+ if (record.application_annotations &&
1190+ record.application_annotations["Ubuntu One"] &&
1191+ record.application_annotations["Ubuntu One"].private_application_annotations &&
1192+ record.application_annotations["Ubuntu One"].private_application_annotations.deleted) {
1193+ return true;
1194+ }
1195+ return false;
1196+ },
1197+
1198+ deleteLocalRecord: function(record) {
1199+ // If we can't resolve the itemId, even by looking up URI,
1200+ // assume it's already gone.
1201+ var itemId = Bindwood.itemIdForUUID(record._id);
1202+ if (itemId) {
1203+ return Bindwood.bookmarksService.removeItem(itemId);
1204+ }
1205+ },
1206+
1207+ processCouchBookmarkRevision: function(record) {
1208+ // Could be an add or change revision. Delete was handled earlier.
1209+ // If it's an addition (we can't resolve its _id to be one of our itemIds),
1210+ // add it to the Desktop Couch folder in unfiled.
1211+ Bindwood.writeMessage("Processing bookmark record: " + JSON.stringify(record));
1212+ var itemId = Bindwood.itemIdForUUID(record._id);
1213+ if (itemId) {
1214+ // It's a change. Stamp everything remote on the local bookmark
1215+ Bindwood.bookmarksService.setItemTitle(itemId, record.title);
1216+ Bindwood.bookmarksService.changeBookmarkURI(itemId,
1217+ Bindwood.ioService.newURI(record.uri, null, null));
1218+ } else {
1219+ // It's an addition. Add a new bookmark to our scratch folder,
1220+ // annotate it, and we're done.
1221+ itemId = Bindwood.bookmarksService.insertBookmark(
1222+ Bindwood.scratch_folder,
1223+ Bindwood.ioService.newURI(record.uri, null, null),
1224+ -1,
1225+ record.title);
1226+ Bindwood.annotateItemWithUUID(itemId, record._id);
1227+ }
1228+ },
1229+
1230+ processCouchFolderRevision: function(record) {
1231+ // Could be an add or change revision. Delete was handled earlier.
1232+ // If it's an addition (we can't resolve its _id to be one of our itemIds),
1233+ // add it to the Desktop Couch folder in unfiled.
1234+ Bindwood.writeMessage("Processing folder record: " + JSON.stringify(record));
1235+ var itemId = Bindwood.itemIdForUUID(record._id);
1236+ if (itemId) {
1237+ // It's a change. Stamp remote title on the folder, and deal with any
1238+ // changed children.
1239+ Bindwood.noteStartTime('Shuffling folder children');
1240+ Bindwood.bookmarksService.setItemTitle(itemId, record.title);
1241+ // Iterate through our current folder children, and compare with remote.
1242+ // Move all local children to the scratch folder, then move them back
1243+ // in the order of the remote children.
1244+ var local_children = Bindwood.getUUIDsFromFolder(itemId);
1245+ Bindwood.writeMessage("Moving local children " + JSON.stringify(local_children) + " to scratch folder");
1246+ for (var i = 0; i<local_children.length; i++) {
1247+ var child = local_children[i];
1248+ var child_itemId = Bindwood.itemIdForUUID(child);
1249+ try {
1250+ Bindwood.bookmarksService.moveItem(child_itemId, Bindwood.scratch_folder, -1);
1251+ } catch(e) {
1252+ Bindwood.writeError("Problem moving item to scratch folder: " + JSON.stringify(e), e);
1253+ }
1254+ }
1255+ Bindwood.writeMessage("Moving children identified by record " + JSON.stringify(record.children) + " to this folder");
1256+ for (var j = 0; j<record.children.length; j++) {
1257+ var new_child = record.children[j];
1258+ var new_child_itemId = Bindwood.itemIdForUUID(new_child);
1259+ try {
1260+ Bindwood.bookmarksService.moveItem(new_child_itemId, itemId, -1);
1261+ } catch(e) {
1262+ Bindwood.writeError("Problem moving item from scratch folder: " + JSON.stringify(e), e);
1263+ }
1264+ }
1265+ Bindwood.noteEndTime('Shuffling folder children');
1266+ } else {
1267+ // It's an addition. Add a new bookmark to our scratch folder,
1268+ // annotate it, and we're done.
1269+ itemId = Bindwood.bookmarksService.createFolder(
1270+ Bindwood.scratch_folder,
1271+ record.title,
1272+ -1);
1273+ Bindwood.annotateItemWithUUID(itemId, record._id);
1274+ }
1275+ },
1276+
1277+ processCouchFeedRevision: function(record) {
1278+ // Could be an add or change revision. Delete was handled earlier.
1279+ // If it's an addition (we can't resolve its _id to be one of our itemIds),
1280+ // add it to the Desktop Couch folder in unfiled.
1281+ Bindwood.writeMessage("Processing feed record: " + JSON.stringify(record));
1282+ var itemId = Bindwood.itemIdForUUID(record._id);
1283+ if (itemId) {
1284+ // It's a change. Stamp everything remote on the local bookmark
1285+ Bindwood.bookmarksService.setItemTitle(itemId, record.title);
1286+ Bindwood.livemarkService.setSiteURI(itemId,
1287+ Bindwood.ioService.newURI(record.site_uri, null, null));
1288+ Bindwood.livemarkService.setFeedURI(itemId,
1289+ Bindwood.ioService.newURI(record.feed_uri, null, null));
1290+ } else {
1291+ // It's an addition. Add a new bookmark to our scratch folder,
1292+ // annotate it, and we're done.
1293+ var newItemId = Bindwood.livemarkService.createLivemarkFolderOnly(
1294+ Bindwood.bookmarksService,
1295+ Bindwood.scratch_folder,
1296+ record.title,
1297+ Bindwood.ioService.newURI(record.site_uri, null, null),
1298+ Bindwood.ioService.newURI(record.feed_uri, null, null),
1299+ -1);
1300+ Bindwood.annotateItemWithUUID(newItemId, record._id);
1301+ }
1302+ },
1303+
1304+ processCouchSeparatorRevision: function(record) {
1305+ // Should only be an add revision. There's nothing to change, and delete was
1306+ // handled earlier.
1307+ // If it's an addition (we can't resolve its _id to be one of our itemIds),
1308+ // add it to the Desktop Couch folder in unfiled.
1309+ Bindwood.writeMessage("Processing separator record: " + JSON.stringify(record));
1310+ var itemId = Bindwood.itemIdForUUID(record._id);
1311+ if (!itemId) {
1312+ // There's nothing to change about a separator, so...
1313+ // It's an addition. Add a new bookmark to our scratch folder,
1314+ // annotate it, and we're done.
1315+ var newItemId = Bindwood.bookmarksService.insertSeparator(
1316+ Bindwood.scratch_folder,
1317+ -1);
1318+ Bindwood.annotateItemWithUUID(newItemId, record._id);
1319+ }
1320 },
1321
1322 updateDocAndSave: function(uuid, attribute, value, callback) {
1323- Bindwood.writeMessage("Updating a document (" + uuid + ") setting (" + attribute + ") to (" + value + ")");
1324- // Some attributes that we track should remain inside the application_annotations object
1325+ Bindwood.writeMessage(
1326+ "Updating a document (" +
1327+ uuid +
1328+ ") setting (" +
1329+ attribute +
1330+ ") to (" + value + ")");
1331+
1332+ // Some attributes that we track should remain inside the
1333+ // application_annotations object
1334 var attrMap = {
1335- title: false,
1336- uri: false,
1337- deleted: false,
1338- uuid: true,
1339- folder: true,
1340- favicon: true,
1341- profle: true };
1342- var results = Bindwood.findDocumentByUUID(uuid);
1343- if (!results) {
1344- throw {error: "Could not find document in Couch"};
1345- }
1346- var doc = Bindwood.couch.open(results.rows[0].id);
1347- if (attrMap[attribute.toString()]) { // belongs on annotations
1348+ title: true,
1349+ uri: true,
1350+ feed_uri: true,
1351+ site_uri: true,
1352+ children: true,
1353+ favicon: false,
1354+ profile: false };
1355+
1356+ var doc = Bindwood.couch.open(uuid);
1357+ if (attrMap[attribute.toString()] || false) { // belongs at top-level
1358+ doc[attribute.toString()] = value.toString();
1359+ } else {
1360 if (!doc.application_annotations) {
1361 doc.application_annotations = {};
1362 }
1363@@ -714,23 +979,20 @@
1364 doc.application_annotations.Firefox = {};
1365 }
1366 doc.application_annotations.Firefox[attribute.toString()] = value.toString();
1367- } else {
1368- doc[attribute.toString()] = value.toString();
1369 }
1370 try {
1371- var result = Bindwood.couch.save(doc);
1372- Bindwood.writeMessage("Successfully save document (" + uuid + ") back to Couch.");
1373+ var response = Bindwood.couch.save(doc);
1374+ Bindwood.seen_revisions[response.rev] = true;
1375 } catch(e) {
1376 Bindwood.writeError("Problem saving document to Couch", e);
1377 throw e;
1378 }
1379
1380 if (callback) {
1381- Bindwood.writeMessage("We've got a callback to run: " + callback.toString());
1382 callback();
1383 }
1384
1385- return result;
1386+ return response;
1387 },
1388
1389 itemWeCareAbout: function(itemId) {
1390@@ -740,6 +1002,7 @@
1391 var root = 0;
1392 var parent;
1393 while (parent != root) {
1394+ Bindwood.writeMessage("Looking for parent of " + itemId);
1395 parent = Bindwood.bookmarksService.getFolderIdForItem(itemId);
1396 if (parent != root &&
1397 Bindwood.annotationService.itemHasAnnotation(
1398@@ -754,69 +1017,20 @@
1399 Observer: {
1400 // An nsINavBookmarkObserver
1401 onItemAdded: function(aItemId, aFolder, aIndex) {
1402- // A bookmark has been added, so we create a blank entry
1403+ Bindwood.writeMessage("onItemAdded: called when push is " + Bindwood.push);
1404+ // An item has been added, so we create a blank entry
1405 // in Couch with our local itemId attached.
1406 if (!Bindwood.itemWeCareAbout(aItemId)) {
1407 Bindwood.writeMessage("Ignoring this add event");
1408 return;
1409 }
1410
1411- Bindwood.writeMessage("A new bookmark was created. Its id is: " + aItemId +
1412- " at location: " + aIndex +
1413- " in folder: " + aFolder );
1414- netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
1415-
1416- var uuid = Bindwood.uuidForItemId(aItemId);
1417- Bindwood.writeMessage("Determined uuid for new bookmark: " + uuid);
1418-
1419- var folder;
1420- switch(aFolder) {
1421- case Bindwood.bookmarksService.toolbarFolder:
1422- folder = "toolbarFolder";
1423- break;
1424- case Bindwood.bookmarksService.bookmarksMenuFolder:
1425- folder = "bookmarksMenuFolder";
1426- break;
1427- case Bindwood.bookmarksService.unfiledBookmarksFolder:
1428- folder = "unfiledBookmarksFolder";
1429- break;
1430- default:
1431- folder = Bindwood.bookmarksService.getItemTitle(aFolder);
1432- break;
1433- }
1434- var uri;
1435- var title;
1436-
1437- Bindwood.writeMessage("Going to look up bookmark URI");
1438- try {
1439- uri = Bindwood.bookmarksService.getBookmarkURI(aItemId).spec;
1440- } catch(e) {
1441- Bindwood.writeError("Problem with something: ", error);
1442- uri = '';
1443- }
1444-
1445- Bindwood.writeMessage("Going to look up bookmark Title");
1446- try {
1447- title = Bindwood.bookmarksService.getItemTitle(aItemId);
1448- } catch(e) {
1449- Bindwood.writeError("Problem with something: ", error);
1450- title = '';
1451- }
1452-
1453- var doc = {
1454- record_type: "http://www.freedesktop.org/wiki/Specifications/desktopcouch/bookmark",
1455- uri: uri,
1456- title: title,
1457- application_annotations: {
1458- Firefox: {
1459- uuid: uuid,
1460- folder: folder,
1461- profile: Bindwood.currentProfile
1462- }
1463- }
1464- };
1465-
1466- Bindwood.writeMessage("Created a minimal record document with our uuid: " + JSON.stringify(doc));
1467+ Bindwood.writeMessage(
1468+ "A new item was created. Its id is: " + aItemId +
1469+ " at location: " + aIndex +
1470+ " in folder: " + aFolder );
1471+ netscape.security.PrivilegeManager.enablePrivilege(
1472+ "UniversalBrowserRead UniversalBrowserWrite");
1473
1474 switch (Bindwood.push) {
1475 case 'DISABLED':
1476@@ -824,47 +1038,77 @@
1477 break;
1478 case 'ENABLED':
1479 try {
1480- var result = Bindwood.couch.save(doc);
1481+ var doc = Bindwood.couchRecordForItemId(aItemId);
1482+ var response = Bindwood.couch.save(doc);
1483 Bindwood.writeMessage("Saved new, bare record to Couch.");
1484+ Bindwood.seen_revisions[response.rev] = true;
1485+ Bindwood.pushFolderChildren(aFolder);
1486 } catch(e) {
1487- Bindwood.writeError("Problem saving new bookmark to Couch: ", e);
1488+ Bindwood.writeError(
1489+ "Problem saving new bookmark to Couch: ", e);
1490 }
1491 break;
1492 default:
1493 break;
1494 }
1495+
1496+ Bindwood.setLatestModified(
1497+ Bindwood.bookmarksService.getItemLastModified(aItemId));
1498 },
1499 onBeforeItemRemoved: function(aItemId) {
1500+ Bindwood.writeMessage("onBeforeItemRemoved: called when push is " + Bindwood.push);
1501 // A bookmark has been removed. This is called before it's
1502 // been removed locally, though we're passed the itemId,
1503 // which we use to delete from Couch.
1504+ var folderId = Bindwood.bookmarksService.getFolderIdForItem(aItemId);
1505 if (!Bindwood.itemWeCareAbout(aItemId)) {
1506 Bindwood.writeMessage("Ignoring this before remove event");
1507 return;
1508 }
1509
1510- Bindwood.writeMessage("Record " + aItemId + " is about to be removed locally.");
1511+ Bindwood.writeMessage(
1512+ "Record " + aItemId + " is about to be removed locally.");
1513 var uuid = Bindwood.uuidForItemId(aItemId);
1514
1515 switch (Bindwood.push) {
1516 case 'DISABLED':
1517 delete Bindwood.uuidItemIdMap[uuid];
1518- Bindwood.writeMessage("Deleted from local uuid map, but not saving back to Couch.");
1519+ Bindwood.writeMessage(
1520+ "Deleted from local uuid map, but not saving back to Couch.");
1521 break;
1522 case 'ENABLED':
1523- netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
1524+ netscape.security.PrivilegeManager.enablePrivilege(
1525+ "UniversalBrowserRead UniversalBrowserWrite");
1526
1527+ var doc = Bindwood.couch.open(uuid);
1528+ if (!doc.application_annotations) {
1529+ doc.application_annotations = {};
1530+ }
1531+ if (!doc.application_annotations['Ubuntu One']) {
1532+ doc.application_annotations['Ubuntu One'] = {};
1533+ }
1534+ if (!doc.application_annotations['Ubuntu One'].private_application_annotations) {
1535+ doc.application_annotations['Ubuntu One'].private_application_annotations = {};
1536+ }
1537+ doc.application_annotations['Ubuntu One'].private_application_annotations.deleted = true;
1538+
1539 try {
1540 // Also remove from our local cache and remove
1541 // annotation from service.
1542- var result = Bindwood.updateDocAndSave(
1543- uuid, 'deleted', true,
1544- function() {
1545- delete Bindwood.uuidItemIdMap[uuid];
1546- Bindwood.writeMessage("Deleted local reference in the uuid-itemId mapping."); });
1547- Bindwood.writeMessage("Saved document back to Couch with deleted flag set.");
1548+ var response = Bindwood.couch.save(doc);
1549+ Bindwood.seen_revisions[response.rev] = true;
1550+ delete Bindwood.uuidItemIdMap[uuid];
1551+ Bindwood.writeMessage(
1552+ "Deleted local reference in the" +
1553+ " uuid-itemId mapping.");
1554+ Bindwood.writeMessage(
1555+ "Saved document back to Couch with deleted flag set.");
1556+ var new_children = Bindwood.getUUIDsFromFolder(folderId);
1557+ new_children.splice(new_children.indexOf(uuid), 1);
1558+ Bindwood.pushFolderChildren(folderId, new_children);
1559 } catch(e) {
1560- Bindwood.writeError("Problem pushing deleted record to Couch: ", e);
1561+ Bindwood.writeError(
1562+ "Problem pushing deleted record to Couch: ", e);
1563 }
1564 break;
1565 default:
1566@@ -872,6 +1116,7 @@
1567 }
1568 },
1569 onItemRemoved: function(aItemId, aFolder, aIndex) {
1570+ Bindwood.writeMessage("onItemRemoved: called when push is " + Bindwood.push);
1571 // This only happens locally, so there's never a need to push
1572 if (!Bindwood.itemWeCareAbout(aItemId)) {
1573 Bindwood.writeMessage("Ignoring this remove event");
1574@@ -879,39 +1124,85 @@
1575 }
1576
1577 Bindwood.makeLocalChangeOnly(
1578- function() { return Bindwood.annotationService.removeItemAnnotation(aItemId, Bindwood.annotationKey); });
1579- Bindwood.writeMessage("Removed annotations from bookmark identified by: " + aItemId);
1580+ function() {
1581+ return Bindwood.annotationService.removeItemAnnotation(
1582+ aItemId, Bindwood.annotationKey); });
1583+ Bindwood.writeMessage(
1584+ "Removed annotations from bookmark identified by: " + aItemId);
1585 },
1586- onItemChanged: function(aBookmarkId, aProperty, aIsAnnotationProperty, aValue) {
1587+ onItemChanged: function(aItemId, aProperty, aIsAnnotationProperty, aValue) {
1588+ Bindwood.writeMessage("onItemChanged: called when push is " + Bindwood.push);
1589 // A property of a bookmark has changed. On multiple
1590 // property updates, this will be called multiple times,
1591 // once per property (i.e., for title and URI)
1592- if (!Bindwood.itemWeCareAbout(aBookmarkId)) {
1593+ if (!Bindwood.itemWeCareAbout(aItemId)) {
1594 Bindwood.writeMessage("Ignoring this change event");
1595 return;
1596 }
1597
1598- if (!aIsAnnotationProperty) { // We only care if these are bookmark properties (for right now)
1599- Bindwood.writeMessage("A property (" + aProperty + ") on bookmark id: " + aBookmarkId + " has been set to: " + aValue);
1600- var uuid = Bindwood.uuidForItemId(aBookmarkId);
1601-
1602- switch (Bindwood.push) {
1603- case 'DISABLED':
1604- Bindwood.writeMessage("Updated, but not saving back to Couch.");
1605- break;
1606- case 'ENABLED':
1607- Bindwood.writeMessage("We will push this change back to Couch.");
1608- netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite");
1609- try {
1610- var result = Bindwood.updateDocAndSave(
1611- uuid, aProperty.toString(), aValue.toString(),
1612- function() { Bindwood.writeMessage("Saved the document back to Couch"); });
1613- } catch(e) {
1614- Bindwood.writeError("Problem saving updated bookmark to Couch: ", e);
1615- }
1616- break;
1617- default:
1618- break;
1619+ Bindwood.writeMessage(
1620+ "A property (" +
1621+ aProperty +
1622+ ") on item id: " + aItemId +
1623+ " has been set to: " + aValue);
1624+ var uuid = Bindwood.uuidForItemId(aItemId);
1625+
1626+ switch (Bindwood.push) {
1627+ case 'DISABLED':
1628+ Bindwood.writeMessage(
1629+ "Updated, but not saving back to Couch.");
1630+ break;
1631+ case 'ENABLED':
1632+ Bindwood.writeMessage(
1633+ "We will push this change back to Couch.");
1634+ netscape.security.PrivilegeManager.enablePrivilege(
1635+ "UniversalBrowserRead UniversalBrowserWrite");
1636+ try {
1637+ var result = Bindwood.updateDocAndSave(
1638+ uuid, aProperty.toString(), aValue.toString(),
1639+ function() {
1640+ Bindwood.writeMessage(
1641+ "Saved the document back to Couch"); });
1642+ } catch(e) {
1643+ Bindwood.writeError(
1644+ "Problem saving updated bookmark to Couch: ", e);
1645+ }
1646+ break;
1647+ default:
1648+ break;
1649+ }
1650+
1651+ Bindwood.setLatestModified(
1652+ Bindwood.bookmarksService.getItemLastModified(aItemId));
1653+ },
1654+
1655+ onItemMoved: function(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex) {
1656+ Bindwood.writeMessage("onItemMoved: called when push is " + Bindwood.push);
1657+ Bindwood.writeMessage(
1658+ "The item: " + aItemId + " was moved from (" + aOldParent + ", " + aOldIndex +
1659+ ") to (" + aNewParent + ", " + aNewIndex + ")"
1660+ );
1661+ var uuid = Bindwood.uuidForItemId(aItemId);
1662+ var old_parent_uuid = Bindwood.uuidForItemId(aOldParent);
1663+ var old_parent_doc = Bindwood.couch.open(old_parent_uuid);
1664+ old_parent_doc.children = Bindwood.getUUIDsFromFolder(aOldParent);
1665+ try {
1666+ var response = Bindwood.couch.save(old_parent_doc);
1667+ Bindwood.seen_revisions[response.rev] = true;
1668+ } catch(e) {
1669+ Bindwood.writeError(
1670+ "Problem saving updated old parent doc to Couch: ", e);
1671+ }
1672+ if (aOldParent != aNewParent) {
1673+ var new_parent_uuid = Bindwood.uuidForItemId(aNewParent);
1674+ var new_parent_doc = Bindwood.couch.open(new_parent_uuid);
1675+ new_parent_doc.children = Bindwood.getUUIDsFromFolder(aNewParent);
1676+ try {
1677+ var response = Bindwood.couch.save(new_parent_doc);
1678+ Bindwood.seen_revisions[response.rev] = true;
1679+ } catch(e) {
1680+ Bindwood.writeError(
1681+ "Problem saving updated new parent doc to Couch: ", e);
1682 }
1683 }
1684 },
1685@@ -919,7 +1210,6 @@
1686 // Currently unhandled
1687 onBeginUpdateBatch: function() {},
1688 onEndUpdateBatch: function() {},
1689- onItemMoved: function(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex) {},
1690 onItemVisited: function(aBookmarkId, aVisitID, time) {},
1691
1692 // Query Interface
1693
1694=== modified file 'content/browserOverlay.xul'
1695--- content/browserOverlay.xul 2009-09-02 17:07:11 +0000
1696+++ content/browserOverlay.xul 2010-01-22 19:18:13 +0000
1697@@ -19,10 +19,9 @@
1698 <overlay id="bindwood"
1699 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
1700
1701- <script type="application/x-javascript" src="chrome://bindwood/content/sha1.js" />
1702 <script type="application/x-javascript" src="chrome://bindwood/content/oauth.js" />
1703 <script type="application/x-javascript" src="chrome://bindwood/content/couch.js" />
1704- <script type="application/x-javascript" src="chrome://bindwood/content/sync.js" />
1705+ <script type="application/x-javascript" src="chrome://bindwood/content/bindwood.js" />
1706
1707 <window id="main-window">
1708 <script type="application/x-javascript">
1709
1710=== modified file 'content/couch.js'
1711--- content/couch.js 2009-10-07 20:39:43 +0000
1712+++ content/couch.js 2010-01-22 19:18:13 +0000
1713@@ -208,6 +208,21 @@
1714 return this.allDocs({startkey:"_design", endkey:"_design0"});
1715 };
1716
1717+ this.changes = function(options,keys) {
1718+ var req = null;
1719+ if(!keys) {
1720+ req = this.request("GET", this.uri + "_changes" + encodeOptions(options));
1721+ } else {
1722+ req = this.request("POST", this.uri + "_changes" + encodeOptions(options),
1723+ {
1724+ headers: {"Content-Type": "application/json"},
1725+ body: JSON.stringify({keys:keys})
1726+ });
1727+ }
1728+ CouchDB.maybeThrowError(req);
1729+ return JSON.parse(req.responseText);
1730+ };
1731+
1732 this.allDocsBySeq = function(options,keys) {
1733 var req = null;
1734 if(!keys) {
1735
1736=== removed file 'content/sha1.js'
1737--- content/sha1.js 2009-09-02 17:07:11 +0000
1738+++ content/sha1.js 1970-01-01 00:00:00 +0000
1739@@ -1,202 +0,0 @@
1740-/*
1741- * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
1742- * in FIPS PUB 180-1
1743- * Version 2.1a Copyright Paul Johnston 2000 - 2002.
1744- * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
1745- * Distributed under the BSD License
1746- * See http://pajhome.org.uk/crypt/md5 for details.
1747- */
1748-
1749-/*
1750- * Configurable variables. You may need to tweak these to be compatible with
1751- * the server-side, but the defaults work in most cases.
1752- */
1753-var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
1754-var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
1755-var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
1756-
1757-/*
1758- * These are the functions you'll usually want to call
1759- * They take string arguments and return either hex or base-64 encoded strings
1760- */
1761-function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
1762-function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
1763-function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
1764-function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
1765-function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
1766-function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
1767-
1768-/*
1769- * Perform a simple self-test to see if the VM is working
1770- */
1771-function sha1_vm_test()
1772-{
1773- return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
1774-}
1775-
1776-/*
1777- * Calculate the SHA-1 of an array of big-endian words, and a bit length
1778- */
1779-function core_sha1(x, len)
1780-{
1781- /* append padding */
1782- x[len >> 5] |= 0x80 << (24 - len % 32);
1783- x[((len + 64 >> 9) << 4) + 15] = len;
1784-
1785- var w = Array(80);
1786- var a = 1732584193;
1787- var b = -271733879;
1788- var c = -1732584194;
1789- var d = 271733878;
1790- var e = -1009589776;
1791-
1792- for(var i = 0; i < x.length; i += 16)
1793- {
1794- var olda = a;
1795- var oldb = b;
1796- var oldc = c;
1797- var oldd = d;
1798- var olde = e;
1799-
1800- for(var j = 0; j < 80; j++)
1801- {
1802- if(j < 16) w[j] = x[i + j];
1803- else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
1804- var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
1805- safe_add(safe_add(e, w[j]), sha1_kt(j)));
1806- e = d;
1807- d = c;
1808- c = rol(b, 30);
1809- b = a;
1810- a = t;
1811- }
1812-
1813- a = safe_add(a, olda);
1814- b = safe_add(b, oldb);
1815- c = safe_add(c, oldc);
1816- d = safe_add(d, oldd);
1817- e = safe_add(e, olde);
1818- }
1819- return Array(a, b, c, d, e);
1820-
1821-}
1822-
1823-/*
1824- * Perform the appropriate triplet combination function for the current
1825- * iteration
1826- */
1827-function sha1_ft(t, b, c, d)
1828-{
1829- if(t < 20) return (b & c) | ((~b) & d);
1830- if(t < 40) return b ^ c ^ d;
1831- if(t < 60) return (b & c) | (b & d) | (c & d);
1832- return b ^ c ^ d;
1833-}
1834-
1835-/*
1836- * Determine the appropriate additive constant for the current iteration
1837- */
1838-function sha1_kt(t)
1839-{
1840- return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
1841- (t < 60) ? -1894007588 : -899497514;
1842-}
1843-
1844-/*
1845- * Calculate the HMAC-SHA1 of a key and some data
1846- */
1847-function core_hmac_sha1(key, data)
1848-{
1849- var bkey = str2binb(key);
1850- if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);
1851-
1852- var ipad = Array(16), opad = Array(16);
1853- for(var i = 0; i < 16; i++)
1854- {
1855- ipad[i] = bkey[i] ^ 0x36363636;
1856- opad[i] = bkey[i] ^ 0x5C5C5C5C;
1857- }
1858-
1859- var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
1860- return core_sha1(opad.concat(hash), 512 + 160);
1861-}
1862-
1863-/*
1864- * Add integers, wrapping at 2^32. This uses 16-bit operations internally
1865- * to work around bugs in some JS interpreters.
1866- */
1867-function safe_add(x, y)
1868-{
1869- var lsw = (x & 0xFFFF) + (y & 0xFFFF);
1870- var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
1871- return (msw << 16) | (lsw & 0xFFFF);
1872-}
1873-
1874-/*
1875- * Bitwise rotate a 32-bit number to the left.
1876- */
1877-function rol(num, cnt)
1878-{
1879- return (num << cnt) | (num >>> (32 - cnt));
1880-}
1881-
1882-/*
1883- * Convert an 8-bit or 16-bit string to an array of big-endian words
1884- * In 8-bit function, characters >255 have their hi-byte silently ignored.
1885- */
1886-function str2binb(str)
1887-{
1888- var bin = Array();
1889- var mask = (1 << chrsz) - 1;
1890- for(var i = 0; i < str.length * chrsz; i += chrsz)
1891- bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
1892- return bin;
1893-}
1894-
1895-/*
1896- * Convert an array of big-endian words to a string
1897- */
1898-function binb2str(bin)
1899-{
1900- var str = "";
1901- var mask = (1 << chrsz) - 1;
1902- for(var i = 0; i < bin.length * 32; i += chrsz)
1903- str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
1904- return str;
1905-}
1906-
1907-/*
1908- * Convert an array of big-endian words to a hex string.
1909- */
1910-function binb2hex(binarray)
1911-{
1912- var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
1913- var str = "";
1914- for(var i = 0; i < binarray.length * 4; i++)
1915- {
1916- str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
1917- hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
1918- }
1919- return str;
1920-}
1921-
1922-/*
1923- * Convert an array of big-endian words to a base-64 string
1924- */
1925-function binb2b64(binarray)
1926-{
1927- var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1928- var str = "";
1929- for(var i = 0; i < binarray.length * 4; i += 3)
1930- {
1931- var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16)
1932- | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
1933- | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
1934- for(var j = 0; j < 4; j++)
1935- {
1936- if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
1937- else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
1938- }
1939- }
1940- return str;
1941-}
1942
1943=== modified file 'couchdb_env.sh'
1944--- couchdb_env.sh 2009-10-06 17:36:56 +0000
1945+++ couchdb_env.sh 2010-01-22 19:18:13 +0000
1946@@ -24,5 +24,3 @@
1947 else
1948 echo $PORT:$TOKENS > $OUT
1949 fi
1950-
1951-
1952
1953=== modified file 'install.rdf'
1954--- install.rdf 2009-10-21 19:32:47 +0000
1955+++ install.rdf 2010-01-22 19:18:13 +0000
1956@@ -4,7 +4,7 @@
1957
1958 <Description about="urn:mozilla:install-manifest">
1959 <em:id>bindwood@ubuntu.com</em:id>
1960- <em:version>0.4.2</em:version>
1961+ <em:version>0.5</em:version>
1962 <em:type>2</em:type>
1963
1964 <!-- Target Application this extension can install into,
1965
1966=== added directory 'tests'
1967=== added file 'tests/run_test.sh'
1968--- tests/run_test.sh 1970-01-01 00:00:00 +0000
1969+++ tests/run_test.sh 2010-01-22 19:18:13 +0000
1970@@ -0,0 +1,20 @@
1971+#!/bin/bash
1972+
1973+CONSUMER_KEY=ck$RANDOM
1974+CONSUMER_SECRET=cs$RANDOM
1975+TOKEN=tk$RANDOM
1976+TOKEN_SECRET=ts$RANDOM
1977+METHOD=$1
1978+ACTION=$2
1979+SIGNATURE_METHOD=$3
1980+NONCE=$RANDOM
1981+TIMESTAMP=1251105028
1982+
1983+TESTDIR=$(dirname $0)
1984+js -f $TESTDIR/../content/oauth.js -f $TESTDIR/../content/sha1.js \
1985+ $TESTDIR/test_oauth.js $CONSUMER_KEY $CONSUMER_SECRET $TOKEN $TOKEN_SECRET \
1986+ http://$ACTION $METHOD $NONCE $TIMESTAMP $SIGNATURE_METHOD
1987+
1988+python $TESTDIR/test_oauth.py $CONSUMER_KEY $CONSUMER_SECRET $TOKEN $TOKEN_SECRET \
1989+ http://$ACTION $METHOD $NONCE $TIMESTAMP $SIGNATURE_METHOD
1990+
1991
1992=== added file 'tests/test_get_views.js'
1993--- tests/test_get_views.js 1970-01-01 00:00:00 +0000
1994+++ tests/test_get_views.js 2010-01-22 19:18:13 +0000
1995@@ -0,0 +1,135 @@
1996+var environment = arguments[0];
1997+var dbname = arguments[1];
1998+
1999+var env_array = environment.split(':');
2000+var port = env_array[0];
2001+var consumer_key = env_array[1];
2002+var consumer_secret = env_array[2];
2003+var token = env_array[3];
2004+var token_secret = env_array[4];
2005+
2006+if (XMLHttpRequest === undefined) {
2007+ var XMLHttpRequest = function () {
2008+ return Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
2009+ .getService(Components.interfaces.nsIXMLHttpRequest);
2010+ };
2011+}
2012+
2013+print("Assigning environment variables to CouchDB");
2014+CouchDB.port = port;
2015+CouchDB.accessor = {
2016+ consumerSecret: consumer_secret,
2017+ tokenSecret: token_secret
2018+};
2019+CouchDB.message = {
2020+ parameters: {
2021+ oauth_callback: "None",
2022+ oauth_consumer_key: consumer_key,
2023+ oauth_signature_method: "PLAINTEXT",
2024+ oauth_token: token,
2025+ oauth_verifier: "None",
2026+ oauth_version: "1.0"
2027+ }
2028+};
2029+
2030+print("Creating a CouchDB instance for " + dbname);
2031+var couch = new CouchDB(dbname);
2032+try {
2033+ couch.createDb();
2034+} catch (e) {
2035+ if (e.error == 'file_exists') {
2036+ print("Database already exists. We're okay.");
2037+ } else {
2038+ print("Error when creating database in pushBookmarks: " + JSON.stringify(e));
2039+ }
2040+}
2041+
2042+var views = [{ id: "_design/all_bookmarks",
2043+ map: "function(doc) { " +
2044+ "var scheme = doc.uri.split(':',1)[0]; " +
2045+ "var uri; " +
2046+ "if (scheme == 'http' || scheme == 'https') {" +
2047+ "uri = doc.uri.split('/')[2];" +
2048+ "if (uri.length < 30) {" +
2049+ " uri += '/' + " +
2050+ "doc.uri.split('/',4)[3].substr(0,30-uri.length) + '...';" +
2051+ "}" +
2052+ "} else {" +
2053+ "uri = scheme + ' URL';" +
2054+ "}" +
2055+ "if (!doc.deleted) {" +
2056+ "emit(doc.title, uri);" +
2057+ "}" +
2058+ "}" },
2059+ { id: "_design/deleted_bookmarks",
2060+ map: "function(doc) { if (doc.deleted) { emit (doc.title, doc.uri); } }" },
2061+ { id: "_design/default",
2062+ 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 == 'default')) {emit(doc.title, uri);}}" }];
2063+for (var i = 0; i < views.length; i++) {
2064+ var view_info = views[i];
2065+ var dirty = false;
2066+ try {
2067+ var new_doc;
2068+ var current_doc;
2069+ try {
2070+ print("Looking up " + view_info.id);
2071+ current_doc = couch.open(view_info.id);
2072+ } catch(e) {
2073+ print("Problem fetching " + view_info.id + " because of: " + JSON.stringify(e));
2074+ current_doc = null;
2075+ }
2076+ if (current_doc !== null) {
2077+ print("We've got an existing doc");
2078+ new_doc = current_doc;
2079+ var old_map = current_doc.views.display.map;
2080+ if (old_map != view_info.map) { // Ours is definitive
2081+ print("Existing map is different, clobbering.");
2082+ new_doc.views.display.map = view_info.map;
2083+ dirty = true;
2084+ }
2085+ } else {
2086+ print("Doc doesn't exist, making it from scratch");
2087+ new_doc = {
2088+ _id: view_info.id,
2089+ views: {
2090+ display: {
2091+ map: view_info.map
2092+ }
2093+ }
2094+ };
2095+ dirty = true;
2096+ }
2097+ if (dirty) {
2098+ print("Something changed with the doc, we're saving it back");
2099+ try {
2100+ couch.save(new_doc);
2101+ print("Successfully save view " + new_doc._id);
2102+ } catch(e) {
2103+ print("Problem saving view: " + JSON.stringify(e));
2104+ }
2105+ }
2106+ } catch(e) {
2107+ // some kind of error fetching the existing design doc
2108+ print("Problem checking for view: " + JSON.stringify(e));
2109+ }
2110+}
2111+
2112+try {
2113+ var current_profile = 'default';
2114+ var rows = couch.query(function (doc) {
2115+ if (doc.record_type == "http://www.freedesktop.org/wiki/Specifications/desktopcouch/bookmark" &&
2116+ doc.application_annotations &&
2117+ doc.application_annotations.Firefox &&
2118+ doc.application_annotations.Firefox.profile &&
2119+ doc.application_annotations.Firefox.profile == current_profile) {
2120+ emit(doc._id,doc);
2121+ }
2122+ });
2123+} catch(e) {
2124+ Bindwood.writeError("Problem fetching all bookmarks from Couch: ", e);
2125+}
2126+for (var i = 0; i < rows.rows.length; i++) {
2127+ var recordid = rows.rows[i].id;
2128+ print("Pulling record: " + recordid);
2129+}
2130+
2131
2132=== added file 'tests/test_oauth.js'
2133--- tests/test_oauth.js 1970-01-01 00:00:00 +0000
2134+++ tests/test_oauth.js 2010-01-22 19:18:13 +0000
2135@@ -0,0 +1,40 @@
2136+var consumer_key = arguments[0];
2137+var consumer_secret = arguments[1];
2138+var token = arguments[2];
2139+var token_secret = arguments[3];
2140+var action = arguments[4];
2141+var method = arguments[5];
2142+var nonce = arguments[6];
2143+var timestamp = arguments[7];
2144+var signature_method = arguments[8];
2145+
2146+if (method != 'GET') {
2147+ action = encodeURIComponent(action);
2148+}
2149+
2150+var message = {
2151+ parameters: {
2152+ oauth_signature_method: signature_method,
2153+ oauth_consumer_key: consumer_key,
2154+ oauth_token: token,
2155+ oauth_token_secret: token_secret,
2156+ oauth_version: "1.0",
2157+ oauth_nonce: nonce,
2158+ oauth_timestamp: timestamp
2159+ },
2160+ action: action,
2161+ method: method
2162+};
2163+var accessor = {
2164+ consumerSecret: consumer_secret,
2165+ tokenSecret: token_secret
2166+};
2167+OAuth.completeRequest(message, accessor);
2168+var hdr = OAuth.getAuthorizationHeader('', message.parameters);
2169+var parts = hdr.split(",");
2170+for (var i=0; i<parts.length; i++) {
2171+ var smallparts = parts[i].split("=");
2172+ if (smallparts[0] == "oauth_signature") {
2173+ print("Signature from JavaScript: " + smallparts[1]);
2174+ }
2175+}
2176
2177=== added file 'tests/test_oauth.py'
2178--- tests/test_oauth.py 1970-01-01 00:00:00 +0000
2179+++ tests/test_oauth.py 2010-01-22 19:18:13 +0000
2180@@ -0,0 +1,20 @@
2181+#!/usr/bin/python
2182+import sys
2183+from oauth import oauth
2184+
2185+(consumer_key, consumer_secret, actual_token, token_secret, url,
2186+ method, nonce, timestamp, signature_method) = sys.argv[1:]
2187+
2188+signature_function = oauth.OAuthSignatureMethod_PLAINTEXT if signature_method == 'PLAINTEXT' else oauth.OAuthSignatureMethod_HMAC_SHA1
2189+
2190+consumer=oauth.OAuthConsumer(consumer_key, consumer_secret)
2191+token=oauth.OAuthToken(actual_token, token_secret);
2192+rq=oauth.OAuthRequest.from_consumer_and_token(consumer, token=token,
2193+ http_method=method, http_url=url,
2194+ parameters={'oauth_nonce':nonce,'oauth_timestamp':timestamp});
2195+rq.sign_request(signature_function(),consumer, token);
2196+headers = rq.to_header()
2197+parts = [x.strip() for x in headers["Authorization"].split(",") if x.find("=") != -1]
2198+smallparts = dict([x.split("=") for x in parts])
2199+print "Signature from Python: ", smallparts["oauth_signature"]
2200+
2201
2202=== added file 'tests/view_test.sh'
2203--- tests/view_test.sh 1970-01-01 00:00:00 +0000
2204+++ tests/view_test.sh 2010-01-22 19:18:13 +0000
2205@@ -0,0 +1,16 @@
2206+#!/bin/bash
2207+
2208+
2209+
2210+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}')
2211+
2212+TOKENS=$(python -c "from desktopcouch import local_files; tokens = local_files.get_oauth_tokens(); print ':'.join([tokens['consumer_key'], tokens['consumer_secret'], tokens['token'], tokens['token_secret']])" 2>/dev/null)
2213+
2214+TESTDIR=$(dirname $0)
2215+
2216+LD_LIBRARY_PATH=/usr/lib/xulrunner-1.9.1.3 /usr/lib/xulrunner-1.9.1.3/xpcshell -f $TESTDIR/../content/oauth.js -f $TESTDIR/../content/sha1.js -f $TESTDIR/../content/couch.js \
2217+ $TESTDIR/test_get_views.js $PORT:$TOKENS bookmarks
2218+
2219+#python $TESTDIR/test_oauth.py $CONSUMER_KEY $CONSUMER_SECRET $TOKEN $TOKEN_SECRET \
2220+# http://$ACTION $METHOD $NONCE $TIMESTAMP $SIGNATURE_METHOD
2221+

Subscribers

People subscribed via source and target branches