Merge lp:~jamesh/bindwood/sync-pull-changes into lp:bindwood

Proposed by James Henstridge
Status: Merged
Approved by: Zachery Bir
Approved revision: 37
Merged at revision: 31
Proposed branch: lp:~jamesh/bindwood/sync-pull-changes
Merge into: lp:bindwood
Prerequisite: lp:~jamesh/bindwood/recording-observer
Diff against target: 263 lines (+240/-1)
2 files modified
modules/sync.jsm (+100/-1)
mozmill/tests/test_sync_pullchanges.js (+140/-0)
To merge this branch: bzr merge lp:~jamesh/bindwood/sync-pull-changes
Reviewer Review Type Date Requested Status
Zachery Bir (community) Approve
Eric Casteleijn (community) Approve
Review via email: mp+49407@code.launchpad.net

Commit message

Add a Synchroniser.pullChanges() method to read changed records from CouchDB.

Description of the change

Code to retrieve changes from CouchDB. There are two modes of operation:

1. on initial sync, or if the last sync point has been compacted out of the DB, return all rows. A view is used to filter to just the given profile.

2. on subsequent pulls, the changes feed is used to retrieve just the changed documents. A filter is used to remove changes to other profiles.

The changed records are fed to the Synchroniser.processRecord() method, which at this point is a no-op but will create/update local bookmarks. Also, I am not currently persisting the last_seq value, but plan to handle it similar to the existing Bindwood code.

Some of the complexity in this code comes from trying to handle records marked as deleted by desktopcouch. I've tried to make the handling of these as close to actual deleted records as I can.

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

Oh, you won't have to deal with documents marked deleted anymore (well as long as nothing breaks very badly when encountering them, as people may or may not have upgraded their desktopcouch software):

Documents that are deleted, should now *really* be deleted, and moved to the /dc_trash database, under an autogenerated new id, and with an added annotation, like:

        new_record.application_annotations['desktopcouch'] = {
            'private_application_annotations':
            {'original_database_name': self._database_name,
             'original_id': record_id}}

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

Approving though, as you may want to fix that on a different branch.

review: Approve
37. By James Henstridge

Don't bother supporting the old desktopcouch form of marking records as
deleted.

Revision history for this message
James Henstridge (jamesh) wrote :

I've updated the branch to remove the special handling of old-style deleted records.

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

Just an FYI: Line 73: "var new_last_seq;" is unused.

Other than that, looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'modules/sync.jsm'
2--- modules/sync.jsm 2011-02-14 03:17:47 +0000
3+++ modules/sync.jsm 2011-02-14 03:17:47 +0000
4@@ -14,7 +14,7 @@
5 * with this program. If not, see <http://www.gnu.org/licenses/>.
6 */
7
8-const EXPORTED_SYMBOLS = ["BookmarksObserver"];
9+const EXPORTED_SYMBOLS = ["Synchroniser", "BookmarksObserver"];
10
11 const Cc = Components.classes;
12 const Ci = Components.interfaces;
13@@ -28,6 +28,105 @@
14 .getService(Ci.nsIAnnotationService);
15
16
17+function Synchroniser(couch, profile) {
18+ this.couch = couch;
19+ this.profile = profile;
20+ this.last_seq = 0;
21+}
22+
23+Synchroniser.prototype = {
24+ ensureCouchViews: function() {
25+ var design_doc_id = "_design/bindwood", doc;
26+
27+ try {
28+ doc = this.couch.open(design_doc_id);
29+ } catch (e) {
30+ dump("Problem retrieving view view:" + e + "\n");
31+ return;
32+ }
33+ if (!doc) {
34+ doc = { _id: design_doc_id };
35+ }
36+ if (!doc.views)
37+ doc.views = {};
38+ doc.views.bookmarks = {
39+ map: ("function(doc) {\n" +
40+ " try {\n" +
41+ " var annot = doc.application_annotations.Firefox;\n" +
42+ " if (annot.profile)\n" +
43+ " emit(annot.profile, doc._id);\n" +
44+ " } catch (e) { /* ignore error */ }\n" +
45+ "}")
46+ };
47+ if (!doc.filters)
48+ doc.filters = {};
49+ doc.filters.by_profile = (
50+ "function (doc, req) {\n" +
51+ " if (doc._deleted)\n" +
52+ " return true;\n" +
53+ " try {\n" +
54+ " return (doc.application_annotations.Firefox.profile == \n" +
55+ " req.query.profile);\n" +
56+ " } catch (e) { /* ignore error */ }\n" +
57+ "}")
58+ try {
59+ this.couch.save(doc);
60+ } catch (e) {
61+ dump("Problem saving view:" + e + "\n");
62+ }
63+ },
64+
65+ sync: function() {
66+ //this.recordChangedIds();
67+ this.pullChanges();
68+ //this.pushChanges();
69+ //this.cleanup();
70+ },
71+
72+ pullChanges: function() {
73+ var new_last_seq;
74+ var info = this.couch.info();
75+ if (this.last_seq <= info.purge_seq) {
76+ // We haven't pulled any records before, or the database
77+ // has been compacted past the recorded last_seq.
78+ this.last_seq = info.update_seq;
79+ var result = this.couch.view("bindwood/bookmarks", {
80+ key: this.profile,
81+ include_docs: true,
82+ });
83+ for each (var row in result.rows) {
84+ this.processRecord(row.doc);
85+ }
86+ } else {
87+ var changes = this.couch.changes({
88+ since: this.last_seq,
89+ filter: "bindwood/by_profile",
90+ profile: this.profile,
91+ include_docs: true,
92+ });
93+ this.last_seq = changes.last_seq;
94+ // Iterate backwards through the changes list, keeping
95+ // only the last change for each record ID.
96+ var seen_ids = {};
97+ for (var i = changes.results.length-1; i >= 0; i--) {
98+ var rev_id = changes.results[i].changes[0].rev;
99+ if (seen_ids[rev_id]) {
100+ changes.results.splice(i, 1);
101+ }
102+ seen_ids[rev_id] = true;
103+ }
104+ for each (var row in changes.results) {
105+ this.processRecord(row.doc);
106+ }
107+ }
108+ },
109+
110+ processRecord: function (doc) {
111+ /* Process incoming document */
112+ }
113+};
114+
115+
116 function BookmarksObserver(sync) {
117 this.sync = sync;
118 this.changed_guids = {}
119
120=== added file 'mozmill/tests/test_sync_pullchanges.js'
121--- mozmill/tests/test_sync_pullchanges.js 1970-01-01 00:00:00 +0000
122+++ mozmill/tests/test_sync_pullchanges.js 2011-02-14 03:17:47 +0000
123@@ -0,0 +1,140 @@
124+var bm = require("../shared-modules/bookmarks");
125+
126+const TIMEOUT = 5000;
127+
128+const LOCAL_TEST_FOLDER = collector.addHttpResource('../test-files/');
129+const LOCAL_TEST_PAGE = LOCAL_TEST_FOLDER + 'test.html';
130+const LOCAL_TEST_FEED = LOCAL_TEST_FOLDER + 'feed.atom';
131+
132+var setupModule = function(module) {
133+ module.controller = mozmill.getBrowserController();
134+ module.jum = {};
135+ module.desktopcouch = {};
136+ module.sync = {};
137+ Cu.import("resource://mozmill/modules/jum.js", module.jum);
138+ Cu.import("resource://bindwood/desktopcouch.jsm", module.desktopcouch);
139+ Cu.import("resource://bindwood/sync.jsm", module.sync);
140+ bm.clearBookmarks();
141+ module.couch = null;
142+};
143+
144+
145+var setupTest = function(test) {
146+ var done = false;
147+ desktopcouch.connect_desktopcouch("test_bookmarks", function(db) {
148+ couch = db;
149+ done = true;
150+ }, function (message) {});
151+ controller.waitFor(
152+ function() { return done; }, "Could not connect to CouchDB", TIMEOUT);
153+ jum.assertNotEquals(couch, null);
154+
155+ try {
156+ couch.createDb();
157+ } catch (e) {
158+ if (e.error != 'file_exists')
159+ throw(e);
160+ }
161+};
162+
163+
164+var teardownTest = function(test) {
165+ bm.clearBookmarks();
166+ couch.deleteDb();
167+};
168+
169+var compareIds = function(doc1, doc2) {
170+ if (doc1._id < doc2._id)
171+ return -1;
172+ if (doc1._id > doc2._id)
173+ return 1;
174+ return 0;
175+}
176+
177+var test_ensureViews = function() {
178+ var synchroniser = new sync.Synchroniser(couch, "profile_name");
179+ synchroniser.ensureCouchViews();
180+
181+ var doc = couch.open("_design/bindwood");
182+ jum.assertNotEquals(doc, null);
183+ jum.assert(
184+ doc.views.bookmarks, "Design doc should have bookmarks view");
185+ jum.assert(
186+ doc.filters.by_profile, "Design doc should have by_profile filter");
187+};
188+
189+var test_pullChanges = function() {
190+ var synchroniser = new sync.Synchroniser(couch, "profile_name");
191+ synchroniser.ensureCouchViews();
192+
193+ var documents = [];
194+ synchroniser.processRecord = function(doc) {
195+ documents.push(doc);
196+ };
197+
198+ couch.save({
199+ _id: 'guid-1',
200+ application_annotations: {
201+ Firefox: {
202+ profile: 'profile_name',
203+ },
204+ },
205+ });
206+ couch.save({
207+ _id: 'guid-2',
208+ application_annotations: {
209+ Firefox: {
210+ profile: 'some_other_profile',
211+ },
212+ },
213+ });
214+ couch.save({
215+ _id: 'guid-3',
216+ application_annotations: {
217+ Firefox: {
218+ profile: 'profile_name',
219+ },
220+ },
221+ });
222+ synchroniser.pullChanges();
223+ jum.assertEquals(documents.length, 2);
224+ documents.sort(compareIds);
225+ jum.assertEquals(documents[0]._id, 'guid-1');
226+ jum.assertEquals(documents[1]._id, 'guid-3');
227+
228+ // Delete one of the documents.
229+ couch.deleteDoc(couch.open('guid-1'));
230+
231+ // Create a new document, and then modify it.
232+ couch.save({
233+ _id: 'guid-4',
234+ application_annotations: {
235+ Firefox: {
236+ profile: 'profile_name',
237+ },
238+ },
239+ })
240+ doc = couch.open('guid-4');
241+ doc.attr = 'value';
242+ couch.save(doc);
243+
244+ // Create another record belonging to a different profile:
245+ couch.save({
246+ _id: 'guid-5',
247+ application_annotations: {
248+ Firefox: {
249+ profile: 'some_other_profile',
250+ },
251+ },
252+ });
253+
254+
255+ // Calling pullChanges() again gives us incremental changes.
256+ documents = []
257+ synchroniser.pullChanges();
258+ jum.assertEquals(documents.length, 2);
259+ documents.sort(compareIds);
260+ jum.assertEquals(documents[0]._id, 'guid-1');
261+ jum.assertEquals(documents[0]._deleted, true);
262+ jum.assertEquals(documents[1]._id, 'guid-4');
263+};

Subscribers

People subscribed via source and target branches