Merge lp:~jamesh/bindwood/switch-to-new-synchroniser into lp:bindwood

Proposed by James Henstridge
Status: Merged
Approved by: James Henstridge
Approved revision: 44
Merged at revision: 39
Proposed branch: lp:~jamesh/bindwood/switch-to-new-synchroniser
Merge into: lp:bindwood
Prerequisite: lp:~jamesh/bindwood/sync-all
Diff against target: 1573 lines (+137/-1349)
4 files modified
modules/bindwood.jsm (+51/-1307)
modules/sync.jsm (+15/-0)
mozmill/tests/test_addbookmark.js (+0/-42)
mozmill/tests/test_bindwood.js (+71/-0)
To merge this branch: bzr merge lp:~jamesh/bindwood/switch-to-new-synchroniser
Reviewer Review Type Date Requested Status
Manuel de la Peña (community) Approve
Eric Casteleijn (community) Approve
Review via email: mp+51263@code.launchpad.net

Commit message

Plug the new synchronisation code into the Bindwood object.

Description of the change

Plug the new synchronisation code into the Bindwood object. It should be possible to do manual testing with this branch.

If you've got two machines successfully synchronising with each other, you could try running this version of Bindwood on each system. Alternatively, you can sync between two local instances of Bindwood (which means you don't have to deal with replication lag or errors). Here are the steps I used to do this:

1. run "firefox -no-remote -ProfileManager" and create a profile called "bindwood", start firefox, and then exit.

2. Run the previous command again and create a profile called "bindwood2", but manually set the profile folder so that it ends in ".bindwood" (Bindwood is using the profile directory name to guess the profile name). Start and exit this instance of Firefox.

3. From a checkout of this branch, run the following commands:
    ./dev-install bindwood
    ./dev-install bindwood2

4. Start the two Firefox instances with:
    BINDWOOD_DB=test_bookmarks firefox -no-remote -P bindwood
    BINDWOOD_DB=test_bookmarks firefox -no-remote -P bindwood2

You should then be able to make changes in one Firefox window and see them appear in the other window after a delay.

There are a few caveats:

1. The delay for change propagation will be about 30 seconds on average with a maximum of a minute. The synchronisation code runs every 30 seconds, and you'll need to wait for a sync from one instance and then one from the other.

2. I am not performing any duplicate detection at the moment, so the default set of bookmarks will end up being duplicated in the two instances once the two sides quiesce. Deleting one of the duplicates will resolve this.

3. There is no code to migrate old bookmark databases yet. That's why I suggested running against a scratch database above.

To post a comment you must log in.
Revision history for this message
James Henstridge (jamesh) wrote :

One other error I've noticed with this code is moving a bookmark between folders. The reorder_children() routine will get confused and try to set the bookmark's position in the old folder leading to an error.

This error needs to be fixed but is not related to the changes in this branch, so I'd like to address it in a future branch.

Revision history for this message
Eric Casteleijn (thisfred) wrote :

Looks good, like the amount of red diff lines ;)

review: Approve
Revision history for this message
Manuel de la Peña (mandel) :
review: Approve
Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :

No proposals found for merge of lp:~jamesh/bindwood/sync-all into lp:bindwood.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'modules/bindwood.jsm'
2--- modules/bindwood.jsm 2011-02-21 06:41:10 +0000
3+++ modules/bindwood.jsm 2011-03-01 10:47:38 +0000
4@@ -21,28 +21,14 @@
5 const Ci = Components.interfaces;
6 const Cu = Components.utils;
7
8-Cu.import("resource://gre/modules/utils.js");
9 Cu.import("resource://bindwood/desktopcouch.jsm");
10 Cu.import("resource://bindwood/logging.jsm");
11+Cu.import("resource://bindwood/sync.jsm");
12
13-var bookmarksService = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]
14- .getService(Ci.nsINavBookmarksService);
15-var livemarkService = Cc["@mozilla.org/browser/livemark-service;2"]
16- .getService(Ci.nsILivemarkService);
17-var uuidService = Cc["@mozilla.org/uuid-generator;1"]
18- .getService(Ci.nsIUUIDGenerator);
19-var annotationService = Cc["@mozilla.org/browser/annotation-service;1"]
20- .getService(Ci.nsIAnnotationService);
21-var historyService = Cc["@mozilla.org/browser/nav-history-service;1"]
22- .getService(Ci.nsINavHistoryService);
23-var ioService = Cc["@mozilla.org/network/io-service;1"]
24- .getService(Ci.nsIIOService);
25 var envService = Cc["@mozilla.org/process/environment;1"]
26 .getService(Ci.nsIEnvironment);
27 var directoryService = Cc["@mozilla.org/file/directory_service;1"]
28 .getService(Ci.nsIProperties);
29-var windowService = Cc["@mozilla.org/embedcomp/window-watcher;1"]
30- .getService(Ci.nsIWindowWatcher);
31 // Technically, a branch, rather than the actual service, but
32 // consistency wins, I think
33 var prefsService = Cc["@mozilla.org/preferences-service;1"]
34@@ -50,37 +36,12 @@
35
36 var Bindwood = {
37
38- pull_changes_timer: Cc["@mozilla.org/timer;1"]
39- .createInstance(Ci.nsITimer),
40- status_timer: Cc["@mozilla.org/timer;1"]
41- .createInstance(Ci.nsITimer),
42-
43- SCHEMA_VERSION: 1,
44-
45- TYPE_BOOKMARK: "http://www.freedesktop.org/wiki/Specifications/desktopcouch/bookmark",
46- TYPE_FOLDER: "http://www.freedesktop.org/wiki/Specifications/desktopcouch/folder",
47- TYPE_FEED: "http://www.freedesktop.org/wiki/Specifications/desktopcouch/feed",
48- TYPE_SEPARATOR: "http://www.freedesktop.org/wiki/Specifications/desktopcouch/separator",
49-
50+ sync_timer: Cc["@mozilla.org/timer;1"]
51+ .createInstance(Ci.nsITimer),
52+
53+ couch: null,
54+ synchroniser: null,
55 running: false,
56- push: 'ENABLED', // Start off enabled
57- annotationKey: "bindwood/uuid",
58- uuidItemIdMap: {},
59- records: [],
60- seen_revisions: {},
61- timestamps: {},
62-
63- noteStartTime: function(key) {
64- var now = new Date();
65- Bindwood.timestamps[key] = now.getTime();
66- },
67-
68- noteEndTime: function(key) {
69- var now = new Date();
70- var diff = now.getTime() - Bindwood.timestamps[key];
71- Log.debug(key + " took " + diff + " milliseconds");
72- delete Bindwood.timestamps[key];
73- },
74
75 extractProfileName: function(path) {
76 // We want the last part of the profile path
77@@ -110,17 +71,26 @@
78 // http://forums.mozillazine.org/viewtopic.php?f=19&t=657911&start=0
79 // It ensures that we're only running that code on the first window.
80
81- if (!Bindwood.running) {
82+ if (!this.running) {
83 Log.debug("Getting started in init.");
84- Bindwood.currentProfile = Bindwood.extractProfileName(
85+ this.currentProfile = Bindwood.extractProfileName(
86 directoryService.get('ProfD', Ci.nsIFile).path);
87
88- Log.debug("Got our profile: " + Bindwood.currentProfile);
89-
90- Bindwood.running = true;
91- Bindwood.Observer = new BindwoodBookmarkObserver(this);
92- bookmarksService.addObserver(Bindwood.Observer, false);
93- Bindwood.connectToCouchDB();
94+ Log.debug("Got our profile: " + this.currentProfile);
95+
96+ this.running = true;
97+ this.connectToCouchDB();
98+ }
99+ },
100+
101+ uninit: function() {
102+ if (this.running) {
103+ this.running = false;
104+ this.sync_timer.cancel();
105+ if (this.synchroniser) {
106+ this.synchroniser.uninit();
107+ this.synchroniser = null;
108+ }
109 }
110 },
111
112@@ -144,9 +114,6 @@
113 "Something wrong with the process, exiting.", e);
114 return;
115 }
116- } else {
117- // Could not connect: disable our observer.
118- bookmarksService.removeObserver(Bindwood.Observer);
119 }
120 }, function (message) {
121 Log.debug(message);
122@@ -168,215 +135,39 @@
123 return seq;
124 },
125
126- getLatestModified: function() {
127- var mod;
128- try {
129- mod = Number(prefsService.getCharPref('latest_modified'));
130- } catch(e) {
131- mod = 0;
132- }
133- return mod;
134- },
135-
136- setLatestModified: function(mod) {
137- prefsService.setCharPref(
138- 'latest_modified', Number(mod).toString());
139- return mod;
140- },
141-
142 startProcess: function() {
143- Log.debug("Starting process");
144- Bindwood.last_seq = Bindwood.getLastSequence();
145- Log.debug("Got our last known sequence number: " + Bindwood.last_seq);
146- Bindwood.latest_modified = Bindwood.getLatestModified();
147- Log.debug(
148- "Got our latest known last_modified: " + Bindwood.latest_modified);
149-
150- Log.debug("Ensuring the database exisits");
151- Bindwood.ensureDatabase();
152- Log.debug("Ensuring the views exist");
153- Bindwood.ensureViews();
154- Log.debug("Ensuring our scratch folder exists");
155- Bindwood.scratch_folder = Bindwood.ensureLocalScratchFolder();
156-
157- var profile_exists = Bindwood.profileExists();
158- Log.debug(
159- "Determined whether profile already exists: " + profile_exists);
160-
161- var HAVE_LAST_SEQ = Bindwood.last_seq ? 1 : 0; // 0 or 1
162- var HAVE_PROFILE_ROOT = profile_exists ? 2 : 0; // 0 or 2
163-
164- var additional = []; // for case 0 and 3, nothing else needs to be done
165- var should_only_push_latest = true;
166-
167- switch(HAVE_LAST_SEQ | HAVE_PROFILE_ROOT) {
168- case 0:
169- // Neither the profile root exists, nor do we have a last_seq.
170- // Ergo, we are a first time user. Proceed normally.
171-
172- /* Disabling the pop-up window for the time being
173-
174- Bindwood.statusWindow = windowService.openWindow(
175- null,
176- "chrome://bindwood/content/first-time.html",
177- "firstTime",
178- "chrome,centerscreen,outerwidth=640,outerheight=480", null);
179- Bindwood.status_timer.initWithCallback(
180- { notify: function(timer) {
181- var div = Bindwood.statusWindow.document.getElementById('status');
182- var dots = div.innerHTML;
183- div.innerHTML = dots + ".";
184- } }, 1000, Ci.nsITimer.TYPE_REPEATING_SLACK);
185- */
186- break;
187- case 1:
188- // We have a last_seq, but the profile root does not exist.
189- // Ergo, we are an old user and must migrate to the new way of
190- // doing things.
191-
192- /* Disabling the pop-up window for the time being
193-
194- Bindwood.statusWindow = windowService.openWindow(
195- null,
196- "chrome://bindwood/content/migrate-old-bookmarks.html",
197- "migrate",
198- "chrome,centerscreen,outerwidth=640,outerheight=480", null);
199- Bindwood.status_timer.initWithCallback(
200- { notify: function(timer) {
201- var div = Bindwood.statusWindow.document.getElementById('status');
202- var dots = div.innerHTML;
203- div.innerHTML = dots + ".";
204- } }, 1000, Ci.nsITimer.TYPE_REPEATING_SLACK);
205- */
206- // Migrate all existing records in Couch
207- Bindwood.migrateOlderBookmarkRecords();
208- // Ensure that all local records are pushed
209- should_only_push_latest = false;
210- break;
211- case 2:
212- // We have no last_seq, but the profile root exists. Ergo, we are
213- // starting up a subsequent client. We must make our local
214- // bookmarks look like remote, and ensure that any unaccounted for
215- // local bookmarks are sent to CouchDB.
216-
217- /* Disabling the pop-up window for the time being
218-
219- Bindwood.statusWindow = windowService.openWindow(
220- null,
221- "chrome://bindwood/content/subsequent-client-first-time-sync.html",
222- "subsequentClient",
223- "chrome,centerscreen,outerwidth=640,outerheight=480", null);
224- Bindwood.status_timer.initWithCallback(
225- { notify: function(timer) {
226- var div = Bindwood.statusWindow.document.getElementById('status');
227- var dots = div.innerHTML;
228- div.innerHTML = dots + ".";
229- } }, 1000, Ci.nsITimer.TYPE_REPEATING_SLACK);
230- */
231- // Sync all existing records in Couch and local records
232- Bindwood.handleSubsequentClientFirstTimeSync();
233- // Ensure that all local records are pushed
234- should_only_push_latest = false;
235- break;
236- case 3: // Should this just be default?
237- // We have a last_seq, and the profile root exists. Ergo, we are
238- // a normally operating client. Proceed normally.
239-
240- break;
241- default:
242- break;
243- }
244-
245- Bindwood.noteStartTime('Generating the manifest');
246- Bindwood.generateManifest();
247- Bindwood.noteEndTime('Generating the manifest');
248- Bindwood.noteStartTime('Pushing records');
249- Bindwood.pushLatestRecords(should_only_push_latest);
250- Bindwood.noteEndTime('Pushing records');
251-
252- try {
253- Bindwood.pullChanges();
254- } catch(e) {
255- Log.exception("Problem pulling changes!", e);
256- }
257- },
258-
259- ensureDatabase: function() {
260- // This function will create the database if it does not
261- // exist, but we return a boolean representing whether or not
262- // the database existed prior.
263- try {
264- Bindwood.couch.createDb();
265+ // If we were shut down while initialising CouchDB, just return.
266+ if (!this.running)
267+ return;
268+ Log.debug("Setting up synchroniser.");
269+ this.synchroniser = new Synchroniser(this.couch, this.currentProfile);
270+ this.synchroniser.last_seq = this.getLastSequence();
271+ Log.debug("Got our last known sequence number: " +
272+ this.synchroniser.last_seq);
273+ this.synchroniser.init();
274+
275+ var repeater = {
276+ notify: function(timer) {
277+ Bindwood.synchroniser.sync();
278+ Bindwood.setLastSequence(Bindwood.synchroniser.last_seq);
279+ Log.debug(
280+ "Successful run, rescheduling ourself");
281+ }
282+ };
283+ try {
284+ repeater.notify(); // Prime the pump, then schedule us out.
285 } catch (e) {
286- if (e.error != 'file_exists') {
287- Log.exception("Error creating database: ", e);
288- throw(e);
289- }
290+ Log.exception("Problem synchronising bookmarks", e);
291+ return;
292 }
293- },
294-
295- ensureViews: function() {
296- var view = {
297- _id: "_design/bookmarks",
298- views: {
299- profile: {
300- 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.application_annotations && doc.application_annotations['Ubuntu One'] && doc.application_annotations['Ubuntu One'].private_application_annotations && doc.application_annotations['Ubuntu One'].private_application_annotations.deleted)) && (doc.application_annotations.Firefox.profile)) {emit(doc.application_annotations.Firefox.profile, [doc.title, uri]);}}"
301- },
302- live_bookmarks: {
303- 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.application_annotations && doc.application_annotations['Ubuntu One'] && doc.application_annotations['Ubuntu One'].private_application_annotations && doc.application_annotations['Ubuntu One'].private_application_annotations.deleted)) {emit(doc.title, uri);}}"
304- },
305- deleted_bookmarks: {
306- map: "function(doc) { if (doc.application_annotations && doc.application_annotations['Ubuntu One'] && doc.application_annotations['Ubuntu One'].private_application_annotations && doc.application_annotations['Ubuntu One'].private_application_annotations.deleted) { emit (doc.title, doc.uri); } }"
307- }
308- }
309- };
310-
311+
312+ // reschedule ourself
313 try {
314- var doc = Bindwood.couch.open(view._id);
315- if (!doc) {
316- doc = view;
317- }
318- try {
319- Bindwood.couch.save(doc);
320- } catch(e) {
321- Log.exception("Problem saving view: ", e);
322- }
323+ this.sync_timer.initWithCallback(
324+ repeater, 30000, Ci.nsITimer.TYPE_REPEATING_SLACK);
325 } catch(e) {
326- // some kind of error fetching the existing design doc
327- Log.exception("Problem checking for view: ", e);
328- }
329- },
330-
331- ensureLocalScratchFolder: function() {
332- // Because records come from CouchDB without a parent field, until
333- // we get an updated folder record with a record situated properly
334- // in its children field, we need a place to temporarily store
335- // records from Couch. This is that place.
336- var folder = bookmarksService.unfiledBookmarksFolder;
337- var rootNode = Bindwood.getFolderRoot(folder);
338- rootNode.containerOpen = true;
339- for (var i=0; i<rootNode.childCount; i++) {
340- var node = rootNode.getChild(i);
341- if (node.title == 'Desktop Couch Scratch') {
342- rootNode.containerOpen = false;
343- return node.itemId;
344- }
345- }
346- rootNode.containerOpen = false;
347-
348- var folderId = bookmarksService.createFolder(
349- folder, 'Desktop Couch Scratch', -1);
350- return folderId;
351- },
352-
353- profileExists: function() {
354- // Check to see if the current profile's manifest exists in
355- // the database.
356- var root = Bindwood.couch.open('root_' + Bindwood.currentProfile);
357- if (root) {
358- return 2;
359- }
360- return 0;
361+ Log.exception("Problem setting up repeater.", e);
362+ }
363 },
364
365 migrateOlderBookmarkRecords: function() {
366@@ -489,1051 +280,4 @@
367 }
368 }
369 },
370-
371- handleSubsequentClientFirstTimeSync: function() {
372- Log.debug(
373- "We're a subsequent client. Let's merge the remote and the local.");
374-
375- // get the remote root from Couch.
376- var remote_root = Bindwood.couch.open(
377- 'root_' + Bindwood.currentProfile);
378- var local_roots = [bookmarksService.toolbarFolder,
379- bookmarksService.bookmarksMenuFolder,
380- bookmarksService.unfiledBookmarksFolder];
381-
382- var records_needing_pushing = [];
383-
384- for (var i = 0; i < local_roots.length; i++) {
385- Bindwood.syncRemoteAndLocal(
386- remote_root.children[i],
387- local_roots[i],
388- records_needing_pushing);
389- }
390-
391- for (var i = 0; i < records_needing_pushing.length; i++) {
392- try {
393- var response = Bindwood.couch.save(records_needing_pushing[i]);
394- // We can avoid having to process this revision when we
395- // pull it later
396- Bindwood.seen_revisions[response.rev] = true;
397- } catch(e) {
398- Log.exception(
399- "Problem saving record to CouchDB; record is " +
400- JSON.stringify(records_needing_pushing[i]) + ": ", e);
401- }
402- }
403- },
404-
405- syncRemoteAndLocal: function(remote_folder, local_folder, accum) {
406- Log.debug(
407- "Syncing remote folder: " + remote_folder +
408- " to local folder: " + local_folder);
409- var local_needs_pushing = false;
410-
411- // get the local folder's root, and open it for iteration
412- var local = Bindwood.getFolderRoot(local_folder);
413- local.containerOpen = true;
414-
415- // walk the remote folder's children, and ask for each one:
416- var remote = Bindwood.couch.open(remote_folder);
417- var remote_children = remote.children;
418- Log.debug("Beginning to walk remote children");
419- for (var i = 0; i < remote_children.length; i++) {
420- Log.debug(
421- "Getting remote child: " + remote_children[i]);
422- var remote_child = Bindwood.couch.open(remote_children[i]);
423- var local_child;
424- var found_local = false;
425-
426- Log.debug(
427- "Looking for record type: " + remote_child.record_type +
428- " identified by " +
429- (remote_child.record_type != Bindwood.TYPE_SEPARATOR ?
430- remote_child.title :
431- "being a separator"));
432-
433- // does my local toolbarFolder have this child anywhere in it?
434- for (var j = 0; j < local.childCount; j++) {
435- local_child = local.getChild(j);
436-
437- // Check to see whether we're testing a separator (which
438- // has no title) or whether we're testing the same type
439- // and title
440- if (Bindwood.sameType(local_child, remote_child) &&
441- (remote_child.record_type == Bindwood.TYPE_SEPARATOR ||
442- Bindwood.sameTitle(local_child, remote_child))) {
443- found_local = true;
444- Log.debug("Found the record.");
445- Bindwood.annotateItemWithUUID(
446- local_child.itemId, remote_child._id);
447- // If we're dealing with a folder, we'll process it
448- // recursively, and the only other thing we'd impose
449- // would be the title, which already matches..
450- if (remote_child.record_type != Bindwood.TYPE_FOLDER) {
451- Bindwood.processCouchRecord(remote_child, null, null);
452- }
453- if (i != j) {
454- // If yes, but in a different location, move the
455- // local to the proper index within the same
456- // folder.
457- Log.debug(
458- "Moving local record to same index as remote.");
459- Bindwood.makeLocalChangeOnly(
460- function() {
461- bookmarksService.moveItem(
462- local_child.itemId, local_folder, i);
463- }
464- );
465- local_needs_pushing = true;
466- }
467- }
468- }
469-
470- if (!found_local) {
471- // Add the record locally, annotate it, and place it in
472- // the correct index.
473-
474- // Add current local folder as one that needs to be
475- // pushed back (changing its children)
476-
477- Log.debug(
478- "Remote record doesn't exist here, recreating it in " +
479- local_folder + " at index " + i + ".");
480- Bindwood.processCouchRecord(remote_child, local_folder, i);
481- local_needs_pushing = true;
482- }
483-
484- // is the child a folder?
485- if (remote_child.record_type == Bindwood.TYPE_FOLDER) {
486- // Recurse into the function with remote id and local
487- // folder id
488- Bindwood.syncRemoteAndLocal(
489- remote_child._id, local_child.itemId, accum);
490- }
491- }
492-
493- local.containerOpen = false;
494-
495- if (local_needs_pushing) {
496- accum.push(Bindwood.couchRecordForItemId(local_folder));
497- }
498- },
499-
500- sameType: function(localNode, remoteDoc) {
501- switch(remoteDoc.record_type) {
502- case Bindwood.TYPE_BOOKMARK:
503- return PlacesUtils.nodeIsBookmark(localNode);
504- case Bindwood.TYPE_FEED:
505- return PlacesUtils.nodeIsLivemarkContainer(localNode);
506- case Bindwood.TYPE_FOLDER:
507- return PlacesUtils.nodeIsFolder(localNode);
508- case Bindwood.TYPE_SEPARATOR:
509- return PlacesUtils.nodeIsSeparator(localNode);
510- default:
511- return false;
512- }
513- },
514-
515- sameTitle: function(localNode, remoteDoc) {
516- return localNode.title == remoteDoc.title;
517- },
518-
519- // Looking up records locally
520- annotateItemWithUUID: function(itemId, seed_uuid) {
521- var uuid = (seed_uuid ?
522- seed_uuid :
523- uuidService.generateUUID().toString());
524- Log.debug("UUID We came up with: " + uuid);
525- Log.debug("Annotating the item now.");
526- annotationService.setItemAnnotation(
527- itemId,
528- Bindwood.annotationKey,
529- uuid,
530- 0,
531- annotationService.EXPIRE_NEVER);
532- // Whenever we create a new UUID, stash it and the itemId in
533- // our local cache.
534- Bindwood.uuidItemIdMap[uuid] = itemId;
535- return uuid;
536- },
537-
538- itemIdForUUID: function(uuid) {
539- // First, try to look it up in our local cache, barring that
540- // (which shouldn't happen), look it up slowly.
541- var itemId = Bindwood.uuidItemIdMap[uuid];
542-
543- if (!itemId) {
544- var items = annotationService.getItemsWithAnnotation(
545- Bindwood.annotationKey, {});
546- var num_items = items.length;
547- Log.debug(
548- "Found " + num_items + " records with the annotation key");
549- for (var i = 0; i < items.length; i++) {
550- Log.debug("Item #" + i + ": ItemId: " + items[i]);
551- var anno = annotationService.getItemAnnotation(
552- items[i], Bindwood.annotationKey);
553- Log.debug(
554- "Annotation on " + items[i] + ": " + anno);
555- if (anno == uuid) {
556- var itemId = items[i];
557- Bindwood.uuidItemIdMap[uuid] = itemId;
558- break;
559- }
560- }
561- if (!itemId) {
562- Log.debug(
563- "XXX: Still haven't found the right itemId!");
564- }
565- }
566- return itemId;
567- },
568-
569- uuidForItemId: function(itemId) {
570- // Try to look up the uuid, and failing that, assign a new one
571- // and return it.
572- var uuid;
573- try {
574- uuid = annotationService.getItemAnnotation(
575- itemId, Bindwood.annotationKey);
576- Bindwood.uuidItemIdMap[uuid] = itemId;
577- } catch(e) {
578- Log.exception(
579- "Couldn't find a UUID for itemId: " + itemId, e);
580- uuid = Bindwood.makeLocalChangeOnly(
581- function() { return Bindwood.annotateItemWithUUID(
582- itemId, null); } );
583- }
584-
585- return uuid;
586- },
587-
588- couchRecordForItemId: function(itemId) {
589- var uuid = Bindwood.uuidForItemId(itemId);
590- var profile = Bindwood.currentProfile;
591- var last_modified = bookmarksService.getItemLastModified(itemId);
592-
593- var record = {
594- "_id": uuid,
595- record_type_version: Bindwood.SCHEMA_VERSION,
596- application_annotations: {
597- Firefox: {
598- profile: profile,
599- last_modified: last_modified
600- }
601- }
602- };
603-
604- record = Bindwood.decorateRecordByType(record, itemId);
605-
606- return record;
607- },
608-
609- decorateRecordByType: function(record, itemId) {
610- var bs = bookmarksService;
611-
612- switch(bs.getItemType(itemId)) {
613- case bs.TYPE_BOOKMARK:
614- record.title = bs.getItemTitle(itemId);
615- record.record_type = Bindwood.TYPE_BOOKMARK;
616- record.uri = bs.getBookmarkURI(itemId).spec;
617- break;
618- case bs.TYPE_FOLDER:
619- record.title = bs.getItemTitle(itemId);
620-
621- // Firefox doesn't differentiate between regular folders
622- // and livemark folders. *sigh* So, we override it here
623- if (livemarkService.isLivemark(itemId)) {
624- record.record_type = Bindwood.TYPE_FEED;
625- record.site_uri = livemarkService.getSiteURI(itemId).spec;
626- record.feed_uri = livemarkService.getFeedURI(itemId).spec;
627- } else {
628- record.record_type = Bindwood.TYPE_FOLDER;
629- record.children = [];
630- }
631- break;
632- case bs.TYPE_SEPARATOR:
633- record.record_type = Bindwood.TYPE_SEPARATOR;
634- break;
635- default:
636- break;
637- }
638-
639- return record;
640- },
641-
642- makeLocalChangeOnly: function(func) {
643- Bindwood.push = 'DISABLED';
644- var results = func();
645- Bindwood.push = 'ENABLED';
646- return results;
647- },
648-
649- // Back and forth
650- getFolderRoot: function(folder) {
651- var options = historyService.getNewQueryOptions();
652- var query = historyService.getNewQuery();
653- query.setFolders([folder], 1);
654- var result = historyService.executeQuery(query, options);
655- return result.root;
656- },
657-
658- getUUIDsFromFolder: function(folder) {
659- var folderRoot = Bindwood.getFolderRoot(folder);
660- folderRoot.containerOpen = true;
661- var uuids = [];
662-
663- for (var i=0; i<folderRoot.childCount; i++) {
664- var node = folderRoot.getChild(i);
665- uuids.push(Bindwood.uuidForItemId(node.itemId));
666- }
667-
668- return uuids;
669- },
670-
671- getRecordsFromFolder: function(folder) {
672- // Make a record for us, populating a children field with the
673- // _ids of all our children
674- var folderRoot = Bindwood.getFolderRoot(folder);
675- folderRoot.containerOpen = true;
676-
677- var folder_record = Bindwood.couchRecordForItemId(folder);
678- Log.debug(
679- "Building up a record for " + folder_record.title);
680-
681- for (var i=0; i<folderRoot.childCount; i++) {
682- var node = folderRoot.getChild(i);
683-
684- var record = Bindwood.couchRecordForItemId(node.itemId);
685- folder_record.children.push(record._id);
686- Log.debug(
687- "Added child record " + (record.title || record.record_type));
688-
689- // If node is a folder (but not a Livemark or Dynamic container),
690- // descend into it, looking for its contents
691- if (record.record_type == Bindwood.TYPE_FOLDER) {
692- Bindwood.getRecordsFromFolder(node.itemId)
693- } else {
694- Bindwood.records.push(record);
695- }
696- }
697- Log.debug(
698- "Done collecting children. Folder's children is now: " +
699- JSON.stringify(folder_record.children));
700- folderRoot.containerOpen = false;
701- Bindwood.records.push(folder_record);
702- return folder_record;
703- },
704-
705- generateManifest: function() {
706- // Fill up the Bindwood.manifest and initial push lists
707- var primaryFolders = [bookmarksService.toolbarFolder,
708- bookmarksService.bookmarksMenuFolder,
709- bookmarksService.unfiledBookmarksFolder];
710-
711- var profile_root = {
712- "_id": "root_" + Bindwood.currentProfile,
713- children: [],
714- application_annotations: {
715- Firefox: {
716- profile: Bindwood.currentProfile,
717- last_modified: 1
718- }
719- }
720- };
721-
722- for (var i=0; i<primaryFolders.length; i++) {
723- var folder = primaryFolders[i];
724- var folder_record = Bindwood.getRecordsFromFolder(folder);
725-
726- profile_root.children.push(folder_record._id);
727- }
728-
729- Bindwood.records.push(profile_root);
730- },
731-
732- sortByLastModDesc: function(a, b) {
733- var a_mod = a.application_annotations.Firefox.last_modified;
734- var b_mod = b.application_annotations.Firefox.last_modified;
735- return b_mod - a_mod; // descending
736- },
737-
738- pushLatestRecords: function(only_latest) {
739- Bindwood.records.sort(Bindwood.sortByLastModDesc);
740- // Now that the record are all sorted descending by last
741- // mod time, we can check each in turn to see if its mod time
742- // is greater than our persisted mod time. Afterwards, we'll
743- // set our persisted latest mod time to be the first record's
744- // mod time.
745- var newest = Bindwood.records[0];
746- var newest_ff = newest.application_annotations.Firefox;
747- var new_latest_modified = newest_ff.last_modified;
748- for (var i = 0; i < Bindwood.records.length; i++) {
749- // find this record in CouchDB
750- var record = Bindwood.records[i];
751- var ff = record.application_annotations.Firefox;
752-
753- if (only_latest &&
754- (ff.last_modified <= Bindwood.latest_modified)) {
755- Log.debug(
756- "We've reached records we've already dealt with." +
757- " Breaking out of the loop.");
758- Log.debug(
759- "Record we've seen: " + record._id);
760- break;
761- }
762-
763- var doc = Bindwood.couch.open(record._id);
764- if (!doc) {
765- // this record is not in CouchDB, so write it
766- try {
767- var response = Bindwood.couch.save(record);
768- // We can avoid having to process this revision when
769- // we pull it later
770- Bindwood.seen_revisions[response.rev] = true;
771- } catch(e) {
772- Log.exception(
773- "Problem saving record to CouchDB; record is " +
774- JSON.stringify(record) + ": ", e);
775- }
776- } else {
777- // record is already in CouchDB, so do nothing
778- Log.debug(
779- "This record (" + record._id +
780- ") is already in Couch, skipping");
781- }
782- }
783- Bindwood.latest_modified = Bindwood.setLatestModified(
784- new_latest_modified);
785- },
786-
787- pushFolderChildren: function(folder, children) {
788- var doc = Bindwood.couch.open(Bindwood.uuidForItemId(folder));
789- var new_children = children;
790- if (!new_children) {
791- new_children = Bindwood.getUUIDsFromFolder(folder);
792- }
793- doc.children = new_children;
794- var response = Bindwood.couch.save(doc);
795- Bindwood.seen_revisions[response.rev] = true;
796- },
797-
798- pullChanges: function() {
799- var repeater = {
800- notify: function(timer) {
801- Bindwood.pullRecords();
802- Log.debug(
803- "Successful run, rescheduling ourself");
804- }
805- };
806-
807- repeater.notify(); // Prime the pump, then schedule us out.
808-
809- // reschedule ourself
810- try {
811- Bindwood.pull_changes_timer.initWithCallback(
812- repeater, 30000, Ci.nsITimer.TYPE_REPEATING_SLACK);
813- } catch(e) {
814- Log.exception("Problem setting up repeater.", e);
815- }
816-
817- /* Disabling pop-up window for the time being.
818-
819- if (Bindwood.statusWindow) {
820- Bindwood.reportDoneInWindow();
821- }
822- */
823- },
824-
825- reportDoneInWindow: function() {
826- Bindwood.status_timer.cancel();
827- var div = Bindwood.statusWindow.document.getElementById('status');
828- var dots = div.innerHTML;
829- div.innerHTML = (dots +
830- "<br/><br/> Finished, you can close this window and proceed. " +
831- "Thanks for your patience.");
832- },
833-
834- pullRecords: function() {
835- // Check to see if our prefsService has a preference set (I
836- // know, bad form) for last_seq, which would designate the
837- // last Couch sequence we've seen (this might be 0 if we've
838- // never synced before, in which case, we'd get all
839- // changes). Afterwards, set the last known sequence in
840- // prefs. Then, future polls will use last_seq as the start
841- // for finding changes.
842-
843- // XXX: currently, we do a single changes pull. Eventually, if
844- // we need to use threads, we can do long polling in a
845- // background thread.
846-
847- Bindwood.noteStartTime('Pulling records');
848- var results = {results: [], last_seq: 0};
849- try {
850- results = Bindwood.couch.changes(
851- {since: Bindwood.last_seq},
852- null
853- );
854- } catch(e) {
855- Log.exception(
856- "Problem long polling bookmarks from Couch: ", e);
857- }
858- var revisions = results.results;
859- for (var i = 0; i < revisions.length; i++) {
860- var rev = revisions[i];
861- var revno = rev.changes[0].rev;
862- var recordid = rev.id;
863-
864- // Skip (for now) if we're dealing with a root folder or a
865- // design doc
866- if (recordid.indexOf('root_') === 0 ||
867- recordid.indexOf('_design') === 0) {
868- Log.debug(
869- "Root profile or design doc, skipping...");
870- continue;
871- }
872-
873- // Skip any revisions we've already seen (because we just
874- // put them there)
875- if (Bindwood.seen_revisions[revno]) {
876- Log.debug(
877- "We've seen this revision (" + revno +
878- ") before, when we created it.");
879- delete Bindwood.seen_revisions[revno];
880- continue;
881- }
882-
883- var record = Bindwood.couch.open(recordid);
884-
885- if (!Bindwood.recordInCurrentProfile(record)) {
886- Log.debug(
887- "Record isn't in our current profile. Skipping...");
888- continue;
889- }
890-
891- // Next, check to see if the record we've pulled down
892- // is flagged as deleted. If so, we should make sure any
893- // local copy we have of this record has also been
894- // deleted.
895- if (Bindwood.isDeleted(record)) {
896- Bindwood.makeLocalChangeOnly(
897- function() {
898- Log.debug(
899- "Record in Couch marked as deleted;" +
900- " attempting to delete local copy.");
901- Bindwood.deleteLocalRecord(record);
902- });
903- // Don't bother continuing to process anything further in
904- // this revision
905- continue;
906- }
907-
908- Bindwood.processCouchRecord(record, null, null);
909- }
910-
911- Bindwood.last_seq = Bindwood.setLastSequence(results.last_seq);
912- Bindwood.noteEndTime('Pulling records');
913- },
914-
915- recordInCurrentProfile: function(record) {
916- if (record.application_annotations &&
917- record.application_annotations.Firefox &&
918- record.application_annotations.Firefox.profile &&
919- record.application_annotations.Firefox.profile == Bindwood.currentProfile) {
920- return true;
921- }
922- return false;
923- },
924-
925- isDeleted: function(record) {
926- if (record.application_annotations &&
927- record.application_annotations["Ubuntu One"] &&
928- record.application_annotations["Ubuntu One"].private_application_annotations &&
929- record.application_annotations["Ubuntu One"].private_application_annotations.deleted) {
930- return true;
931- }
932- return false;
933- },
934-
935- deleteLocalRecord: function(record) {
936- // If we can't resolve the itemId, even by looking up URI,
937- // assume it's already gone.
938- var itemId = Bindwood.itemIdForUUID(record._id);
939- if (itemId) {
940- return bookmarksService.removeItem(itemId);
941- }
942- },
943-
944- processCouchRecord: function(record, aParent, aIndex) {
945- var aParent = aParent ? aParent : Bindwood.scratch_folder;
946- var aIndex = aIndex ? aIndex : -1;
947-
948- Log.debug(
949- "Processing Couch Record: " + record + " placing it in " +
950- aParent + " at location " + aIndex);
951-
952- switch(record.record_type) {
953- case Bindwood.TYPE_BOOKMARK:
954- Bindwood.makeLocalChangeOnly(
955- function() {
956- Bindwood.processCouchBookmarkRevision(
957- record, aParent, aIndex);
958- });
959- break;
960- case Bindwood.TYPE_FOLDER:
961- Bindwood.makeLocalChangeOnly(
962- function() {
963- Bindwood.processCouchFolderRevision(
964- record, aParent, aIndex);
965- });
966- break;
967- case Bindwood.TYPE_FEED:
968- Bindwood.makeLocalChangeOnly(
969- function() {
970- Bindwood.processCouchFeedRevision(
971- record, aParent, aIndex);
972- });
973- break;
974- case Bindwood.TYPE_SEPARATOR:
975- Bindwood.makeLocalChangeOnly(
976- function() {
977- Bindwood.processCouchSeparatorRevision(
978- record, aParent, aIndex);
979- });
980- break;
981- default:
982- break;
983- }
984- },
985-
986- processCouchBookmarkRevision: function(record, aParent, aIndex) {
987- // Could be an add or change revision. Delete was handled earlier.
988- // If it's an addition (we can't resolve its _id to be one of our
989- // itemIds), add it to the Desktop Couch folder in unfiled.
990- Log.debug(
991- "Processing bookmark record: " + JSON.stringify(record));
992- var itemId = Bindwood.itemIdForUUID(record._id);
993- if (itemId) {
994- // It's a change. Stamp everything remote on the local bookmark
995- bookmarksService.setItemTitle(itemId, record.title);
996- bookmarksService.changeBookmarkURI(itemId,
997- ioService.newURI(record.uri, null, null));
998- } else {
999- // It's an addition. Add a new bookmark to our scratch folder,
1000- // annotate it, and we're done.
1001- itemId = bookmarksService.insertBookmark(
1002- aParent,
1003- ioService.newURI(record.uri, null, null),
1004- aIndex,
1005- record.title);
1006- Bindwood.annotateItemWithUUID(itemId, record._id);
1007- }
1008- },
1009-
1010- processCouchFolderRevision: function(record, aParent, aIndex) {
1011- // Could be an add or change revision. Delete was handled
1012- // earlier. If it's an addition (we can't resolve its _id to be
1013- // one of our itemIds), add it to the Desktop Couch folder in
1014- // unfiled.
1015- Log.debug(
1016- "Processing folder record: " + JSON.stringify(record));
1017- var itemId = Bindwood.itemIdForUUID(record._id);
1018- if (itemId) {
1019- // It's a change. Stamp remote title on the folder, and deal
1020- // with any changed children.
1021- Bindwood.noteStartTime('Shuffling folder children');
1022- bookmarksService.setItemTitle(itemId, record.title);
1023- // Iterate through our current folder children, and compare
1024- // with remote. Move all local children to the scratch
1025- // folder, then move them back in the order of the remote
1026- // children.
1027- var local_children = Bindwood.getUUIDsFromFolder(itemId);
1028- Log.debug(
1029- "Moving local children " + JSON.stringify(local_children) +
1030- " to scratch folder");
1031- for (var i = 0; i<local_children.length; i++) {
1032- var child = local_children[i];
1033- // XXX: Probably needs to be made more robust
1034- var child_itemId = Bindwood.itemIdForUUID(child);
1035- try {
1036- bookmarksService.moveItem(
1037- child_itemId, Bindwood.scratch_folder, -1);
1038- } catch(e) {
1039- Log.exception(
1040- "Problem moving item to scratch folder: " +
1041- JSON.stringify(e), e);
1042- }
1043- }
1044- Log.debug(
1045- "Moving children identified by record " +
1046- JSON.stringify(record.children) + " to this folder");
1047- for (var j = 0; j<record.children.length; j++) {
1048- var new_child = record.children[j];
1049- // XXX: Probably needs to be made more robust
1050- var new_child_itemId = Bindwood.itemIdForUUID(new_child);
1051- try {
1052- bookmarksService.moveItem(new_child_itemId, itemId, -1);
1053- } catch(e) {
1054- Log.exception(
1055- "Problem moving item from scratch folder: " +
1056- JSON.stringify(e), e);
1057- }
1058- }
1059- Bindwood.noteEndTime('Shuffling folder children');
1060- } else {
1061- // It's an addition. Add a new bookmark to our scratch folder,
1062- // annotate it, and we're done.
1063- itemId = bookmarksService.createFolder(
1064- aParent,
1065- record.title,
1066- aIndex);
1067- Bindwood.annotateItemWithUUID(itemId, record._id);
1068- }
1069- },
1070-
1071- processCouchFeedRevision: function(record, aParent, aIndex) {
1072- // Could be an add or change revision. Delete was handled
1073- // earlier. If it's an addition (we can't resolve its _id to be
1074- // one of our itemIds), add it to the Desktop Couch folder in
1075- // unfiled.
1076- Log.debug(
1077- "Processing feed record: " + JSON.stringify(record));
1078- var itemId = Bindwood.itemIdForUUID(record._id);
1079- if (itemId) {
1080- // It's a change. Stamp everything remote on the local bookmark
1081- bookmarksService.setItemTitle(itemId, record.title);
1082- livemarkService.setSiteURI(itemId,
1083- ioService.newURI(record.site_uri, null, null));
1084- livemarkService.setFeedURI(itemId,
1085- ioService.newURI(record.feed_uri, null, null));
1086- } else {
1087- // It's an addition. Add a new bookmark to our scratch folder,
1088- // annotate it, and we're done.
1089- var newItemId = livemarkService.createLivemark(
1090- aParent,
1091- record.title,
1092- ioService.newURI(record.site_uri, null, null),
1093- ioService.newURI(record.feed_uri, null, null),
1094- aIndex);
1095- Bindwood.annotateItemWithUUID(newItemId, record._id);
1096- }
1097- },
1098-
1099- processCouchSeparatorRevision: function(record, aParent, aIndex) {
1100- // Should only be an add revision. There's nothing to change, and
1101- // delete was handled earlier. If it's an addition (we can't
1102- // resolve its _id to be one of our itemIds), add it to the
1103- // Desktop Couch folder in unfiled.
1104- Log.debug(
1105- "Processing separator record: " + JSON.stringify(record));
1106- var itemId = Bindwood.itemIdForUUID(record._id);
1107- if (!itemId) {
1108- // There's nothing to change about a separator, so...
1109- // It's an addition. Add a new bookmark to our scratch folder,
1110- // annotate it, and we're done.
1111- var newItemId = bookmarksService.insertSeparator(
1112- aParent,
1113- aIndex);
1114- Bindwood.annotateItemWithUUID(newItemId, record._id);
1115- }
1116- },
1117-
1118- updateDocAndSave: function(uuid, attribute, value, callback) {
1119- Log.debug(
1120- "Updating a document (" +
1121- uuid +
1122- ") setting (" +
1123- attribute +
1124- ") to (" + value + ")");
1125-
1126- // Some attributes that we track should remain inside the
1127- // application_annotations object
1128- var attrMap = {
1129- title: true,
1130- uri: true,
1131- feed_uri: true,
1132- site_uri: true,
1133- children: true,
1134- favicon: false,
1135- profile: false };
1136-
1137- var doc = Bindwood.couch.open(uuid);
1138- if (attrMap[attribute.toString()] || false) { // belongs at top-level
1139- doc[attribute.toString()] = value.toString();
1140- } else {
1141- if (!doc.application_annotations) {
1142- doc.application_annotations = {};
1143- }
1144- if (!doc.application_annotations.Firefox) {
1145- doc.application_annotations.Firefox = {};
1146- }
1147- doc.application_annotations.Firefox[attribute.toString()] = value.toString();
1148- }
1149- try {
1150- var response = Bindwood.couch.save(doc);
1151- Bindwood.seen_revisions[response.rev] = true;
1152- } catch(e) {
1153- Log.exception("Problem saving document to Couch", e);
1154- throw e;
1155- }
1156-
1157- if (callback) {
1158- callback();
1159- }
1160-
1161- return response;
1162- },
1163-
1164- itemWeCareAbout: function(itemId) {
1165- // Traverse from the itemId up its parent chain. If at any
1166- // level the parent is a livemark container or a dynamic
1167- // container, return false, otherwise, return true.
1168- var root = 0;
1169- var parent;
1170- while (parent != root) {
1171- Log.debug("Looking for parent of " + itemId);
1172- parent = bookmarksService.getFolderIdForItem(itemId);
1173- if (parent != root &&
1174- annotationService.itemHasAnnotation(
1175- parent, 'livemark/feedURI')) {
1176- return false;
1177- }
1178- itemId = parent;
1179- }
1180- return true;
1181- },
1182-};
1183-
1184-var BindwoodBookmarkObserver = function(bindwood) {
1185- this.bw = bindwood;
1186-};
1187-BindwoodBookmarkObserver.prototype = {
1188- // Query Interface
1189- QueryInterface: function(iid) {
1190- if (iid.equals(Ci.nsINavBookmarkObserver) ||
1191- iid.equals(Ci.nsINavBookmarkObserver_MOZILLA_1_9_1_ADDITIONS) ||
1192- iid.equals(Ci.nsISupports)) {
1193- return this;
1194- }
1195- throw Cr.NS_ERROR_NO_INTERFACE;
1196- },
1197-
1198- // An nsINavBookmarkObserver
1199- onItemAdded: function(aItemId, aFolder, aIndex) {
1200- Log.debug("onItemAdded: called when push is " + this.bw.push);
1201- // An item has been added, so we create a blank entry
1202- // in Couch with our local itemId attached.
1203- if (!this.bw.itemWeCareAbout(aItemId)) {
1204- Log.debug("Ignoring this add event");
1205- return;
1206- }
1207-
1208- Log.debug(
1209- "A new item was created. Its id is: " + aItemId +
1210- " at location: " + aIndex +
1211- " in folder: " + aFolder );
1212-
1213- switch (this.bw.push) {
1214- case 'DISABLED':
1215- Log.debug("Added, but not saving to Couch.");
1216- break;
1217- case 'ENABLED':
1218- try {
1219- var doc = this.bw.couchRecordForItemId(aItemId);
1220- var response = this.bw.couch.save(doc);
1221- Log.debug("Saved new, bare record to Couch.");
1222- this.bw.seen_revisions[response.rev] = true;
1223- this.bw.pushFolderChildren(aFolder);
1224- } catch(e) {
1225- Log.exception(
1226- "Problem saving new bookmark to Couch: ", e);
1227- }
1228- break;
1229- default:
1230- break;
1231- }
1232-
1233- this.bw.setLatestModified(
1234- bookmarksService.getItemLastModified(aItemId));
1235- },
1236-
1237- onBeforeItemRemoved: function(aItemId) {
1238- Log.debug(
1239- "onBeforeItemRemoved: called when push is " + this.bw.push);
1240- // A bookmark has been removed. This is called before it's
1241- // been removed locally, though we're passed the itemId,
1242- // which we use to delete from Couch.
1243- var folderId = bookmarksService.getFolderIdForItem(aItemId);
1244- if (!this.bw.itemWeCareAbout(aItemId)) {
1245- Log.debug("Ignoring this before remove event");
1246- return;
1247- }
1248-
1249- Log.debug(
1250- "Record " + aItemId + " is about to be removed locally.");
1251- var uuid = this.bw.uuidForItemId(aItemId);
1252-
1253- switch (this.bw.push) {
1254- case 'DISABLED':
1255- delete this.bw.uuidItemIdMap[uuid];
1256- Log.debug(
1257- "Deleted from local uuid map, but not saving back to Couch.");
1258- break;
1259-
1260- case 'ENABLED':
1261- var doc = this.bw.couch.open(uuid);
1262- if (!doc.application_annotations) {
1263- doc.application_annotations = {};
1264- }
1265- if (!doc.application_annotations['Ubuntu One']) {
1266- doc.application_annotations['Ubuntu One'] = {};
1267- }
1268- if (!doc.application_annotations['Ubuntu One'].private_application_annotations) {
1269- doc.application_annotations['Ubuntu One'].private_application_annotations = {};
1270- }
1271- doc.application_annotations['Ubuntu One'].private_application_annotations.deleted = true;
1272-
1273- try {
1274- // Also remove from our local cache and remove
1275- // annotation from service.
1276- var response = this.bw.couch.save(doc);
1277- this.bw.seen_revisions[response.rev] = true;
1278- delete this.bw.uuidItemIdMap[uuid];
1279- Log.debug(
1280- "Deleted local reference in the" +
1281- " uuid-itemId mapping.");
1282- Log.debug(
1283- "Saved document back to Couch with deleted flag set.");
1284- var new_children = this.bw.getUUIDsFromFolder(folderId);
1285- new_children.splice(new_children.indexOf(uuid), 1);
1286- this.bw.pushFolderChildren(folderId, new_children);
1287- } catch(e) {
1288- Log.exception(
1289- "Problem pushing deleted record to Couch: ", e);
1290- }
1291- break;
1292- default:
1293- break;
1294- }
1295- },
1296-
1297- onItemRemoved: function(aItemId, aFolder, aIndex) {
1298- Log.debug(
1299- "onItemRemoved: called when push is " + this,bw.push);
1300- // This only happens locally, so there's never a need to push
1301- if (!this.bw.itemWeCareAbout(aItemId)) {
1302- Log.debug("Ignoring this remove event");
1303- return;
1304- }
1305-
1306- this.bw.makeLocalChangeOnly(
1307- function() {
1308- return annotationService.removeItemAnnotation(
1309- aItemId, this.bw.annotationKey); });
1310- Log.debug(
1311- "Removed annotations from bookmark identified by: " + aItemId);
1312- },
1313-
1314- onItemChanged: function(aItemId, aProperty, aIsAnnotationProperty, aValue) {
1315- Log.debug(
1316- "onItemChanged: called when push is " + this.bw.push);
1317- // A property of a bookmark has changed. On multiple
1318- // property updates, this will be called multiple times,
1319- // once per property (i.e., for title and URI)
1320- if (!this.bw.itemWeCareAbout(aItemId)) {
1321- Log.debug("Ignoring this change event");
1322- return;
1323- }
1324-
1325- Log.debug(
1326- "A property (" +
1327- aProperty +
1328- ") on item id: " + aItemId +
1329- " has been set to: " + aValue);
1330- var uuid = this.bw.uuidForItemId(aItemId);
1331-
1332- switch (this.bw.push) {
1333- case 'DISABLED':
1334- Log.debug(
1335- "Updated, but not saving back to Couch.");
1336- break;
1337- case 'ENABLED':
1338- Log.debug(
1339- "We will push this change back to Couch.");
1340- try {
1341- var result = Bindwood.updateDocAndSave(
1342- uuid, aProperty.toString(), aValue.toString(),
1343- function() {
1344- Log.debug(
1345- "Saved the document back to Couch");
1346- });
1347- } catch(e) {
1348- Log.exception(
1349- "Problem saving updated bookmark to Couch: ", e);
1350- }
1351- break;
1352- default:
1353- break;
1354- }
1355-
1356- this.bw.setLatestModified(
1357- bookmarksService.getItemLastModified(aItemId));
1358- },
1359-
1360- onItemMoved: function(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex) {
1361- Log.debug(
1362- "onItemMoved: called when push is " + this.bw.push);
1363- Log.debug(
1364- "The item: " + aItemId + " was moved from (" +
1365- aOldParent + ", " + aOldIndex +
1366- ") to (" + aNewParent + ", " + aNewIndex + ")"
1367- );
1368- switch (this.bw.push) {
1369- case 'DISABLED':
1370- Log.debug(
1371- "Moved, but not saving back to Couch.");
1372- break;
1373- case 'ENABLED':
1374- var uuid = this.bw.uuidForItemId(aItemId);
1375- var old_parent_uuid = this.bw.uuidForItemId(aOldParent);
1376- var old_parent_doc = this.bw.couch.open(old_parent_uuid);
1377- old_parent_doc.children = this.bw.getUUIDsFromFolder(
1378- aOldParent);
1379- try {
1380- var response = this.bw.couch.save(old_parent_doc);
1381- this.bw.seen_revisions[response.rev] = true;
1382- } catch(e) {
1383- Log.exception(
1384- "Problem saving updated old parent doc to Couch: ", e);
1385- }
1386- if (aOldParent != aNewParent) {
1387- var new_parent_uuid = this.bw.uuidForItemId(aNewParent);
1388- var new_parent_doc = this.bw.couch.open(new_parent_uuid);
1389- new_parent_doc.children = this.bw.getUUIDsFromFolder(
1390- aNewParent);
1391- try {
1392- var response = this.bw.couch.save(new_parent_doc);
1393- this.bw.seen_revisions[response.rev] = true;
1394- } catch(e) {
1395- Log.exception(
1396- "Problem saving updated new parent doc to Couch: ",
1397- e);
1398- }
1399- }
1400- break;
1401- default:
1402- break;
1403- }
1404-
1405- // Set the latest modified to the greatest of aItemId,
1406- // aOldParent, or aNewParent's last_modified
1407- this.bw.setLatestModified(
1408- [bookmarksService.getItemLastModified(aItemId),
1409- bookmarksService.getItemLastModified(aOldParent),
1410- bookmarksService.getItemLastModified(aNewParent)].sort()[2]);
1411- },
1412-
1413- // Currently unhandled
1414- onBeginUpdateBatch: function() {},
1415- onEndUpdateBatch: function() {},
1416- onItemVisited: function(aBookmarkId, aVisitID, time) {},
1417 };
1418
1419=== modified file 'modules/sync.jsm'
1420--- modules/sync.jsm 2011-03-01 10:47:38 +0000
1421+++ modules/sync.jsm 2011-03-01 10:47:38 +0000
1422@@ -60,6 +60,7 @@
1423 Synchroniser.prototype = {
1424 init: function() {
1425 Log.debug("Initialising synchroniser.");
1426+ this.ensureDatabase();
1427 this.ensureCouchViews();
1428 this.first_push = true;
1429 this.observer = new BookmarksObserver(this);
1430@@ -74,6 +75,20 @@
1431 }
1432 },
1433
1434+ ensureDatabase: function() {
1435+ // This function will create the database if it does not
1436+ // exist, but we return a boolean representing whether or not
1437+ // the database existed prior.
1438+ try {
1439+ this.couch.createDb();
1440+ } catch (e) {
1441+ if (e.error != 'file_exists') {
1442+ Log.exception("Error creating database: ", e);
1443+ throw(e);
1444+ }
1445+ }
1446+ },
1447+
1448 ensureCouchViews: function() {
1449 var design_doc_id = "_design/bindwood", doc;
1450
1451
1452=== removed file 'mozmill/tests/test_addbookmark.js'
1453--- mozmill/tests/test_addbookmark.js 2011-02-22 10:56:32 +0000
1454+++ mozmill/tests/test_addbookmark.js 1970-01-01 00:00:00 +0000
1455@@ -1,42 +0,0 @@
1456-var bm = require("../shared-modules/bookmarks");
1457-
1458-const TIMEOUT = 5000;
1459-
1460-const LOCAL_TEST_FOLDER = collector.addHttpResource('../test-files/');
1461-const LOCAL_TEST_PAGE = LOCAL_TEST_FOLDER + 'test.html';
1462-
1463-var setupModule = function(module) {
1464- module.controller = mozmill.getBrowserController();
1465- module.bindwood = {}
1466- module.jum = {}
1467- Components.utils.import("resource://bindwood/bindwood.jsm", module.bindwood);
1468- Components.utils.import("resource://mozmill/modules/jum.js", module.jum);
1469- module.bm.clearBookmarks();
1470-};
1471-
1472-var teardownModule = function(module) {
1473- module.bm.clearBookmarks();
1474-};
1475-
1476-// XXX: Test disabled until new sync code is in place.
1477-var __testAddBookmark = function() {
1478- var uri = bm.createURI(LOCAL_TEST_PAGE);
1479- jum.assertEquals(bm.bookmarksService.isBookmarked(uri), false)
1480-
1481- var bookmark_id = bm.bookmarksService.insertBookmark(
1482- bm.bookmarksService.toolbarFolder,
1483- uri,
1484- bm.bookmarksService.DEFAULT_INDEX,
1485- "Bookmark title");
1486-
1487- var uuid = bindwood.Bindwood.uuidForItemId(bookmark_id);
1488- var record;
1489- controller.waitFor(function() {
1490- record = bindwood.Bindwood.couch.open(uuid);
1491- return record != null;
1492- }, "Bookmark synchronised", TIMEOUT);
1493- jum.assertEquals(record.uri, uri.spec);
1494- jum.assertEquals(record.title, "Bookmark title");
1495- jum.assertEquals(record.record_type, bindwood.Bindwood.TYPE_BOOKMARK);
1496- jum.assertEquals(record.record_type_version, bindwood.Bindwood.SCHEMA_VERSION);
1497-};
1498
1499=== added file 'mozmill/tests/test_bindwood.js'
1500--- mozmill/tests/test_bindwood.js 1970-01-01 00:00:00 +0000
1501+++ mozmill/tests/test_bindwood.js 2011-03-01 10:47:38 +0000
1502@@ -0,0 +1,71 @@
1503+var bm = require("../shared-modules/bookmarks");
1504+
1505+const TIMEOUT = 5000;
1506+
1507+const LOCAL_TEST_FOLDER = collector.addHttpResource('../test-files/');
1508+const LOCAL_TEST_PAGE = LOCAL_TEST_FOLDER + 'test.html';
1509+const LOCAL_TEST_FEED = LOCAL_TEST_FOLDER + 'feed.atom';
1510+
1511+var setupModule = function(module) {
1512+ module.controller = mozmill.getBrowserController();
1513+ module.jum = {};
1514+ module.desktopcouch = {};
1515+ module.bindwood = {};
1516+ Cu.import("resource://mozmill/modules/jum.js", module.jum);
1517+ Cu.import("resource://bindwood/desktopcouch.jsm", module.desktopcouch);
1518+ Cu.import("resource://bindwood/bindwood.jsm", module.bindwood);
1519+ bm.clearBookmarks();
1520+ module.couch = null;
1521+ module.synchroniser = null;
1522+};
1523+
1524+
1525+var setupTest = function(test) {
1526+ var done = false;
1527+ desktopcouch.connect_desktopcouch("test_bookmarks", function(db) {
1528+ couch = db;
1529+ done = true;
1530+ }, function (message) {});
1531+ controller.waitFor(
1532+ function() { return done; }, "Could not connect to CouchDB", TIMEOUT);
1533+ jum.assertNotEquals(couch, null);
1534+
1535+ try {
1536+ couch.createDb();
1537+ } catch (e) {
1538+ if (e.error != 'file_exists')
1539+ throw(e);
1540+ }
1541+};
1542+
1543+
1544+var teardownTest = function(test) {
1545+ //bindwood.Bindwood.uninit();
1546+ bm.clearBookmarks();
1547+ couch.deleteDb();
1548+};
1549+
1550+var test_bindwood_init = function() {
1551+ bindwood.Bindwood.init();
1552+ controller.waitFor(function() {
1553+ return bindwood.Bindwood.synchroniser != null;
1554+ }, "Bindwood did not initialise", TIMEOUT);
1555+
1556+ // XXX: this check causes mozmill to crash. Possibly when it
1557+ //tries to serialise the synchroniser?
1558+ //jum.assertNotEquals(bindwood.Bindwood.synchroniser, null);
1559+
1560+ // The synchronisation timer has been initialised.
1561+ jum.assertNotNull(bindwood.Bindwood.sync_timer.callback);
1562+};
1563+
1564+var test_bindwood_uninit = function() {
1565+ bindwood.Bindwood.init();
1566+ controller.waitFor(function() {
1567+ return bindwood.Bindwood.synchroniser != null;
1568+ }, "Bindwood did not initialise", TIMEOUT);
1569+ bindwood.Bindwood.uninit();
1570+
1571+ jum.assertNull(bindwood.Bindwood.sync_timer.callback);
1572+ jum.assertNull(bindwood.Bindwood.synchroniser);
1573+}

Subscribers

People subscribed via source and target branches