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