Merge lp:~jamesh/bindwood/switch-to-new-synchroniser into lp:bindwood
- switch-to-new-synchroniser
- Merge into trunk
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 |
Related bugs: |
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_
BINDWOOD_
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.
James Henstridge (jamesh) wrote : | # |
Eric Casteleijn (thisfred) wrote : | # |
Looks good, like the amount of red diff lines ;)
Manuel de la Peña (mandel) : | # |
Ubuntu One Auto Pilot (otto-pilot) wrote : | # |
No proposals found for merge of lp:~jamesh/bindwood/sync-all into lp:bindwood.
Preview Diff
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 | +} |
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.