|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 package org.mozilla.gecko.background.db; |
|
5 |
|
6 import java.util.ArrayList; |
|
7 import java.util.Collection; |
|
8 import java.util.Collections; |
|
9 import java.util.HashSet; |
|
10 import java.util.Iterator; |
|
11 |
|
12 import org.json.simple.JSONArray; |
|
13 import org.mozilla.gecko.R; |
|
14 import org.mozilla.gecko.background.common.log.Logger; |
|
15 import org.mozilla.gecko.background.helpers.AndroidSyncTestCase; |
|
16 import org.mozilla.gecko.background.sync.helpers.BookmarkHelpers; |
|
17 import org.mozilla.gecko.background.sync.helpers.SimpleSuccessBeginDelegate; |
|
18 import org.mozilla.gecko.background.sync.helpers.SimpleSuccessCreationDelegate; |
|
19 import org.mozilla.gecko.background.sync.helpers.SimpleSuccessFetchDelegate; |
|
20 import org.mozilla.gecko.background.sync.helpers.SimpleSuccessFinishDelegate; |
|
21 import org.mozilla.gecko.background.sync.helpers.SimpleSuccessStoreDelegate; |
|
22 import org.mozilla.gecko.db.BrowserContract; |
|
23 import org.mozilla.gecko.db.BrowserContract.Bookmarks; |
|
24 import org.mozilla.gecko.sync.Utils; |
|
25 import org.mozilla.gecko.sync.repositories.InactiveSessionException; |
|
26 import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException; |
|
27 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException; |
|
28 import org.mozilla.gecko.sync.repositories.NullCursorException; |
|
29 import org.mozilla.gecko.sync.repositories.RepositorySession; |
|
30 import org.mozilla.gecko.sync.repositories.RepositorySessionBundle; |
|
31 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksDataAccessor; |
|
32 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksRepository; |
|
33 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksRepositorySession; |
|
34 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers; |
|
35 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate; |
|
36 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate; |
|
37 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate; |
|
38 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate; |
|
39 import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord; |
|
40 import org.mozilla.gecko.sync.repositories.domain.Record; |
|
41 |
|
42 import android.content.ContentResolver; |
|
43 import android.content.ContentUris; |
|
44 import android.content.ContentValues; |
|
45 import android.database.Cursor; |
|
46 import android.net.Uri; |
|
47 |
|
48 public class TestBookmarks extends AndroidSyncTestCase { |
|
49 |
|
50 protected static final String LOG_TAG = "BookmarksTest"; |
|
51 |
|
52 /** |
|
53 * Trivial test that forbidden records (reading list prior to Bug 762109, pinned items…) |
|
54 * will be ignored if processed. |
|
55 */ |
|
56 public void testForbiddenItemsAreIgnored() { |
|
57 final AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); |
|
58 final long now = System.currentTimeMillis(); |
|
59 final String bookmarksCollection = "bookmarks"; |
|
60 |
|
61 final BookmarkRecord toRead = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now - 1, false); |
|
62 final BookmarkRecord pinned = new BookmarkRecord("pinpinpinpin", "bookmarks", now - 1, false); |
|
63 final BookmarkRecord normal = new BookmarkRecord("baaaaaaaaaaa", "bookmarks", now - 2, false); |
|
64 |
|
65 final BookmarkRecord readingList = new BookmarkRecord(Bookmarks.READING_LIST_FOLDER_GUID, |
|
66 bookmarksCollection, now - 3, false); |
|
67 final BookmarkRecord pinnedItems = new BookmarkRecord(Bookmarks.PINNED_FOLDER_GUID, |
|
68 bookmarksCollection, now - 4, false); |
|
69 |
|
70 toRead.type = normal.type = pinned.type = "bookmark"; |
|
71 readingList.type = "folder"; |
|
72 pinnedItems.type = "folder"; |
|
73 |
|
74 toRead.parentID = Bookmarks.READING_LIST_FOLDER_GUID; |
|
75 pinned.parentID = Bookmarks.PINNED_FOLDER_GUID; |
|
76 normal.parentID = Bookmarks.TOOLBAR_FOLDER_GUID; |
|
77 |
|
78 readingList.parentID = Bookmarks.PLACES_FOLDER_GUID; |
|
79 pinnedItems.parentID = Bookmarks.PLACES_FOLDER_GUID; |
|
80 |
|
81 inBegunSession(repo, new SimpleSuccessBeginDelegate() { |
|
82 @Override |
|
83 public void onBeginSucceeded(RepositorySession session) { |
|
84 assertTrue(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(toRead)); |
|
85 assertTrue(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(pinned)); |
|
86 assertTrue(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(readingList)); |
|
87 assertTrue(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(pinnedItems)); |
|
88 assertFalse(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(normal)); |
|
89 finishAndNotify(session); |
|
90 } |
|
91 }); |
|
92 } |
|
93 |
|
94 /** |
|
95 * Trivial test that pinned items will be skipped if present in the DB. |
|
96 */ |
|
97 public void testPinnedItemsAreNotRetrieved() { |
|
98 final AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); |
|
99 |
|
100 // Ensure that they exist. |
|
101 setUpFennecPinnedItemsRecord(); |
|
102 |
|
103 // They're there in the DB… |
|
104 final ArrayList<String> roots = fetchChildrenDirect(Bookmarks.FIXED_ROOT_ID); |
|
105 Logger.info(LOG_TAG, "Roots: " + roots); |
|
106 assertTrue(roots.contains(Bookmarks.PINNED_FOLDER_GUID)); |
|
107 |
|
108 final ArrayList<String> pinned = fetchChildrenDirect(Bookmarks.FIXED_PINNED_LIST_ID); |
|
109 Logger.info(LOG_TAG, "Pinned: " + pinned); |
|
110 assertTrue(pinned.contains("dapinneditem")); |
|
111 |
|
112 // … but not when we fetch. |
|
113 final ArrayList<String> guids = fetchGUIDs(repo); |
|
114 assertFalse(guids.contains(Bookmarks.PINNED_FOLDER_GUID)); |
|
115 assertFalse(guids.contains("dapinneditem")); |
|
116 } |
|
117 |
|
118 /** |
|
119 * Trivial test that reading list records will be skipped if present in the DB. |
|
120 */ |
|
121 public void testReadingListIsNotRetrieved() { |
|
122 final AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); |
|
123 |
|
124 // Ensure that it exists. |
|
125 setUpFennecReadingListRecord(); |
|
126 |
|
127 // It's there in the DB… |
|
128 final ArrayList<String> roots = fetchChildrenDirect(BrowserContract.Bookmarks.FIXED_ROOT_ID); |
|
129 Logger.info(LOG_TAG, "Roots: " + roots); |
|
130 assertTrue(roots.contains(Bookmarks.READING_LIST_FOLDER_GUID)); |
|
131 |
|
132 // … but not when we fetch. |
|
133 assertFalse(fetchGUIDs(repo).contains(Bookmarks.READING_LIST_FOLDER_GUID)); |
|
134 } |
|
135 |
|
136 public void testRetrieveFolderHasAccurateChildren() { |
|
137 AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); |
|
138 |
|
139 final long now = System.currentTimeMillis(); |
|
140 |
|
141 final String folderGUID = "eaaaaaaaafff"; |
|
142 BookmarkRecord folder = new BookmarkRecord(folderGUID, "bookmarks", now - 5, false); |
|
143 BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now - 1, false); |
|
144 BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now - 3, false); |
|
145 BookmarkRecord bookmarkC = new BookmarkRecord("aaaaaaaaaccc", "bookmarks", now - 2, false); |
|
146 |
|
147 folder.children = childrenFromRecords(bookmarkA, bookmarkB, bookmarkC); |
|
148 folder.sortIndex = 150; |
|
149 folder.title = "Test items"; |
|
150 folder.parentID = "toolbar"; |
|
151 folder.parentName = "Bookmarks Toolbar"; |
|
152 folder.type = "folder"; |
|
153 |
|
154 bookmarkA.parentID = folderGUID; |
|
155 bookmarkA.bookmarkURI = "http://example.com/A"; |
|
156 bookmarkA.title = "Title A"; |
|
157 bookmarkA.type = "bookmark"; |
|
158 |
|
159 bookmarkB.parentID = folderGUID; |
|
160 bookmarkB.bookmarkURI = "http://example.com/B"; |
|
161 bookmarkB.title = "Title B"; |
|
162 bookmarkB.type = "bookmark"; |
|
163 |
|
164 bookmarkC.parentID = folderGUID; |
|
165 bookmarkC.bookmarkURI = "http://example.com/C"; |
|
166 bookmarkC.title = "Title C"; |
|
167 bookmarkC.type = "bookmark"; |
|
168 |
|
169 BookmarkRecord[] folderOnly = new BookmarkRecord[1]; |
|
170 BookmarkRecord[] children = new BookmarkRecord[3]; |
|
171 |
|
172 folderOnly[0] = folder; |
|
173 |
|
174 children[0] = bookmarkA; |
|
175 children[1] = bookmarkB; |
|
176 children[2] = bookmarkC; |
|
177 |
|
178 wipe(); |
|
179 Logger.debug(getName(), "Storing just folder..."); |
|
180 storeRecordsInSession(repo, folderOnly, null); |
|
181 |
|
182 // We don't have any children, despite our insistence upon storing. |
|
183 assertChildrenAreOrdered(repo, folderGUID, new Record[] {}); |
|
184 |
|
185 // Now store the children. |
|
186 Logger.debug(getName(), "Storing children..."); |
|
187 storeRecordsInSession(repo, children, null); |
|
188 |
|
189 // Now we have children, but their order is not determined, because |
|
190 // they were stored out-of-session with the original folder. |
|
191 assertChildrenAreUnordered(repo, folderGUID, children); |
|
192 |
|
193 // Now if we store the folder record again, they'll be put in the |
|
194 // right place. |
|
195 folder.lastModified++; |
|
196 Logger.debug(getName(), "Storing just folder again..."); |
|
197 storeRecordsInSession(repo, folderOnly, null); |
|
198 Logger.debug(getName(), "Fetching children yet again..."); |
|
199 assertChildrenAreOrdered(repo, folderGUID, children); |
|
200 |
|
201 // Now let's see what happens when we see records in the same session. |
|
202 BookmarkRecord[] parentMixed = new BookmarkRecord[4]; |
|
203 BookmarkRecord[] parentFirst = new BookmarkRecord[4]; |
|
204 BookmarkRecord[] parentLast = new BookmarkRecord[4]; |
|
205 |
|
206 // None of our records have a position set. |
|
207 assertTrue(bookmarkA.androidPosition <= 0); |
|
208 assertTrue(bookmarkB.androidPosition <= 0); |
|
209 assertTrue(bookmarkC.androidPosition <= 0); |
|
210 |
|
211 parentMixed[1] = folder; |
|
212 parentMixed[0] = bookmarkA; |
|
213 parentMixed[2] = bookmarkC; |
|
214 parentMixed[3] = bookmarkB; |
|
215 |
|
216 parentFirst[0] = folder; |
|
217 parentFirst[1] = bookmarkC; |
|
218 parentFirst[2] = bookmarkA; |
|
219 parentFirst[3] = bookmarkB; |
|
220 |
|
221 parentLast[3] = folder; |
|
222 parentLast[0] = bookmarkB; |
|
223 parentLast[1] = bookmarkA; |
|
224 parentLast[2] = bookmarkC; |
|
225 |
|
226 wipe(); |
|
227 storeRecordsInSession(repo, parentMixed, null); |
|
228 assertChildrenAreOrdered(repo, folderGUID, children); |
|
229 |
|
230 wipe(); |
|
231 storeRecordsInSession(repo, parentFirst, null); |
|
232 assertChildrenAreOrdered(repo, folderGUID, children); |
|
233 |
|
234 wipe(); |
|
235 storeRecordsInSession(repo, parentLast, null); |
|
236 assertChildrenAreOrdered(repo, folderGUID, children); |
|
237 |
|
238 // Ensure that records are ordered even if we re-process the folder. |
|
239 wipe(); |
|
240 storeRecordsInSession(repo, parentLast, null); |
|
241 folder.lastModified++; |
|
242 storeRecordsInSession(repo, folderOnly, null); |
|
243 assertChildrenAreOrdered(repo, folderGUID, children); |
|
244 } |
|
245 |
|
246 public void testMergeFoldersPreservesSaneOrder() { |
|
247 AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); |
|
248 |
|
249 final long now = System.currentTimeMillis(); |
|
250 final String folderGUID = "mobile"; |
|
251 |
|
252 wipe(); |
|
253 final long mobile = setUpFennecMobileRecord(); |
|
254 |
|
255 // No children. |
|
256 assertChildrenAreUnordered(repo, folderGUID, new Record[] {}); |
|
257 |
|
258 // Add some, as Fennec would. |
|
259 fennecAddBookmark("Bookmark One", "http://example.com/fennec/One"); |
|
260 fennecAddBookmark("Bookmark Two", "http://example.com/fennec/Two"); |
|
261 |
|
262 Logger.debug(getName(), "Fetching children..."); |
|
263 JSONArray folderChildren = fetchChildrenForGUID(repo, folderGUID); |
|
264 |
|
265 assertTrue(folderChildren != null); |
|
266 Logger.debug(getName(), "Children are " + folderChildren.toJSONString()); |
|
267 assertEquals(2, folderChildren.size()); |
|
268 String guidOne = (String) folderChildren.get(0); |
|
269 String guidTwo = (String) folderChildren.get(1); |
|
270 |
|
271 // Make sure positions were saved. |
|
272 assertChildrenAreDirect(mobile, new String[] { |
|
273 guidOne, |
|
274 guidTwo |
|
275 }); |
|
276 |
|
277 // Add some through Sync. |
|
278 BookmarkRecord folder = new BookmarkRecord(folderGUID, "bookmarks", now, false); |
|
279 BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now, false); |
|
280 BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now, false); |
|
281 |
|
282 folder.children = childrenFromRecords(bookmarkA, bookmarkB); |
|
283 folder.sortIndex = 150; |
|
284 folder.title = "Mobile Bookmarks"; |
|
285 folder.parentID = "places"; |
|
286 folder.parentName = ""; |
|
287 folder.type = "folder"; |
|
288 |
|
289 bookmarkA.parentID = folderGUID; |
|
290 bookmarkA.parentName = "Mobile Bookmarks"; // Using this title exercises Bug 748898. |
|
291 bookmarkA.bookmarkURI = "http://example.com/A"; |
|
292 bookmarkA.title = "Title A"; |
|
293 bookmarkA.type = "bookmark"; |
|
294 |
|
295 bookmarkB.parentID = folderGUID; |
|
296 bookmarkB.parentName = "mobile"; |
|
297 bookmarkB.bookmarkURI = "http://example.com/B"; |
|
298 bookmarkB.title = "Title B"; |
|
299 bookmarkB.type = "bookmark"; |
|
300 |
|
301 BookmarkRecord[] parentMixed = new BookmarkRecord[3]; |
|
302 parentMixed[0] = bookmarkA; |
|
303 parentMixed[1] = folder; |
|
304 parentMixed[2] = bookmarkB; |
|
305 |
|
306 storeRecordsInSession(repo, parentMixed, null); |
|
307 |
|
308 BookmarkRecord expectedOne = new BookmarkRecord(guidOne, "bookmarks", now - 10, false); |
|
309 BookmarkRecord expectedTwo = new BookmarkRecord(guidTwo, "bookmarks", now - 10, false); |
|
310 |
|
311 // We want the server to win in this case, and otherwise to preserve order. |
|
312 // TODO |
|
313 assertChildrenAreOrdered(repo, folderGUID, new Record[] { |
|
314 bookmarkA, |
|
315 bookmarkB, |
|
316 expectedOne, |
|
317 expectedTwo |
|
318 }); |
|
319 |
|
320 // Furthermore, the children of that folder should be correct in the DB. |
|
321 ContentResolver cr = getApplicationContext().getContentResolver(); |
|
322 final long folderId = fennecGetFolderId(cr, folderGUID); |
|
323 Logger.debug(getName(), "Folder " + folderGUID + " => " + folderId); |
|
324 |
|
325 assertChildrenAreDirect(folderId, new String[] { |
|
326 bookmarkA.guid, |
|
327 bookmarkB.guid, |
|
328 expectedOne.guid, |
|
329 expectedTwo.guid |
|
330 }); |
|
331 } |
|
332 |
|
333 /** |
|
334 * Apply a folder record whose children array is already accurately |
|
335 * stored in the database. Verify that the parent folder is not flagged |
|
336 * for reupload (i.e., that its modified time is *ahem* unmodified). |
|
337 */ |
|
338 public void testNoReorderingMeansNoReupload() { |
|
339 AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); |
|
340 |
|
341 final long now = System.currentTimeMillis(); |
|
342 |
|
343 final String folderGUID = "eaaaaaaaafff"; |
|
344 BookmarkRecord folder = new BookmarkRecord(folderGUID, "bookmarks", now -5, false); |
|
345 BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now -1, false); |
|
346 BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now -3, false); |
|
347 |
|
348 folder.children = childrenFromRecords(bookmarkA, bookmarkB); |
|
349 folder.sortIndex = 150; |
|
350 folder.title = "Test items"; |
|
351 folder.parentID = "toolbar"; |
|
352 folder.parentName = "Bookmarks Toolbar"; |
|
353 folder.type = "folder"; |
|
354 |
|
355 bookmarkA.parentID = folderGUID; |
|
356 bookmarkA.bookmarkURI = "http://example.com/A"; |
|
357 bookmarkA.title = "Title A"; |
|
358 bookmarkA.type = "bookmark"; |
|
359 |
|
360 bookmarkB.parentID = folderGUID; |
|
361 bookmarkB.bookmarkURI = "http://example.com/B"; |
|
362 bookmarkB.title = "Title B"; |
|
363 bookmarkB.type = "bookmark"; |
|
364 |
|
365 BookmarkRecord[] abf = new BookmarkRecord[3]; |
|
366 BookmarkRecord[] justFolder = new BookmarkRecord[1]; |
|
367 |
|
368 abf[0] = bookmarkA; |
|
369 abf[1] = bookmarkB; |
|
370 abf[2] = folder; |
|
371 |
|
372 justFolder[0] = folder; |
|
373 |
|
374 final String[] abGUIDs = new String[] { bookmarkA.guid, bookmarkB.guid }; |
|
375 final Record[] abRecords = new Record[] { bookmarkA, bookmarkB }; |
|
376 final String[] baGUIDs = new String[] { bookmarkB.guid, bookmarkA.guid }; |
|
377 final Record[] baRecords = new Record[] { bookmarkB, bookmarkA }; |
|
378 |
|
379 wipe(); |
|
380 Logger.debug(getName(), "Storing A, B, folder..."); |
|
381 storeRecordsInSession(repo, abf, null); |
|
382 |
|
383 ContentResolver cr = getApplicationContext().getContentResolver(); |
|
384 final long folderID = fennecGetFolderId(cr, folderGUID); |
|
385 assertChildrenAreOrdered(repo, folderGUID, abRecords); |
|
386 assertChildrenAreDirect(folderID, abGUIDs); |
|
387 |
|
388 // To ensure an interval. |
|
389 try { |
|
390 Thread.sleep(100); |
|
391 } catch (InterruptedException e) { |
|
392 } |
|
393 |
|
394 // Store the same folder record again, and check the tracking. |
|
395 // Because the folder array didn't change, |
|
396 // the item is still tracked to not be uploaded. |
|
397 folder.lastModified = System.currentTimeMillis() + 1; |
|
398 HashSet<String> tracked = new HashSet<String>(); |
|
399 storeRecordsInSession(repo, justFolder, tracked); |
|
400 assertChildrenAreOrdered(repo, folderGUID, abRecords); |
|
401 assertChildrenAreDirect(folderID, abGUIDs); |
|
402 |
|
403 assertTrue(tracked.contains(folderGUID)); |
|
404 |
|
405 // Store again, but with a different order. |
|
406 tracked = new HashSet<String>(); |
|
407 folder.children = childrenFromRecords(bookmarkB, bookmarkA); |
|
408 folder.lastModified = System.currentTimeMillis() + 1; |
|
409 storeRecordsInSession(repo, justFolder, tracked); |
|
410 assertChildrenAreOrdered(repo, folderGUID, baRecords); |
|
411 assertChildrenAreDirect(folderID, baGUIDs); |
|
412 |
|
413 // Now it's going to be reuploaded. |
|
414 assertFalse(tracked.contains(folderGUID)); |
|
415 } |
|
416 |
|
417 /** |
|
418 * Exercise the deletion of folders when their children have not been |
|
419 * marked as deleted. In a database with constraints, this would fail |
|
420 * if we simply deleted the records, so we move them first. |
|
421 */ |
|
422 public void testFolderDeletionOrphansChildren() { |
|
423 AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); |
|
424 |
|
425 long now = System.currentTimeMillis(); |
|
426 |
|
427 // Add a folder and four children. |
|
428 final String folderGUID = "eaaaaaaaafff"; |
|
429 BookmarkRecord folder = new BookmarkRecord(folderGUID, "bookmarks", now -5, false); |
|
430 BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now -1, false); |
|
431 BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now -3, false); |
|
432 BookmarkRecord bookmarkC = new BookmarkRecord("daaaaaaaaccc", "bookmarks", now -7, false); |
|
433 BookmarkRecord bookmarkD = new BookmarkRecord("baaaaaaaaddd", "bookmarks", now -4, false); |
|
434 |
|
435 folder.children = childrenFromRecords(bookmarkA, bookmarkB, bookmarkC, bookmarkD); |
|
436 folder.sortIndex = 150; |
|
437 folder.title = "Test items"; |
|
438 folder.parentID = "toolbar"; |
|
439 folder.parentName = "Bookmarks Toolbar"; |
|
440 folder.type = "folder"; |
|
441 |
|
442 bookmarkA.parentID = folderGUID; |
|
443 bookmarkA.bookmarkURI = "http://example.com/A"; |
|
444 bookmarkA.title = "Title A"; |
|
445 bookmarkA.type = "bookmark"; |
|
446 |
|
447 bookmarkB.parentID = folderGUID; |
|
448 bookmarkB.bookmarkURI = "http://example.com/B"; |
|
449 bookmarkB.title = "Title B"; |
|
450 bookmarkB.type = "bookmark"; |
|
451 |
|
452 bookmarkC.parentID = folderGUID; |
|
453 bookmarkC.bookmarkURI = "http://example.com/C"; |
|
454 bookmarkC.title = "Title C"; |
|
455 bookmarkC.type = "bookmark"; |
|
456 |
|
457 bookmarkD.parentID = folderGUID; |
|
458 bookmarkD.bookmarkURI = "http://example.com/D"; |
|
459 bookmarkD.title = "Title D"; |
|
460 bookmarkD.type = "bookmark"; |
|
461 |
|
462 BookmarkRecord[] abfcd = new BookmarkRecord[5]; |
|
463 BookmarkRecord[] justFolder = new BookmarkRecord[1]; |
|
464 abfcd[0] = bookmarkA; |
|
465 abfcd[1] = bookmarkB; |
|
466 abfcd[2] = folder; |
|
467 abfcd[3] = bookmarkC; |
|
468 abfcd[4] = bookmarkD; |
|
469 |
|
470 justFolder[0] = folder; |
|
471 |
|
472 final String[] abcdGUIDs = new String[] { bookmarkA.guid, bookmarkB.guid, bookmarkC.guid, bookmarkD.guid }; |
|
473 final Record[] abcdRecords = new Record[] { bookmarkA, bookmarkB, bookmarkC, bookmarkD }; |
|
474 |
|
475 wipe(); |
|
476 Logger.debug(getName(), "Storing A, B, folder, C, D..."); |
|
477 storeRecordsInSession(repo, abfcd, null); |
|
478 |
|
479 // Verify that it worked. |
|
480 ContentResolver cr = getApplicationContext().getContentResolver(); |
|
481 final long folderID = fennecGetFolderId(cr, folderGUID); |
|
482 assertChildrenAreOrdered(repo, folderGUID, abcdRecords); |
|
483 assertChildrenAreDirect(folderID, abcdGUIDs); |
|
484 |
|
485 now = System.currentTimeMillis(); |
|
486 |
|
487 // Add one child to unsorted bookmarks. |
|
488 BookmarkRecord unsortedA = new BookmarkRecord("yiamunsorted", "bookmarks", now, false); |
|
489 unsortedA.parentID = "unfiled"; |
|
490 unsortedA.title = "Unsorted A"; |
|
491 unsortedA.type = "bookmark"; |
|
492 unsortedA.androidPosition = 0; |
|
493 |
|
494 BookmarkRecord[] ua = new BookmarkRecord[1]; |
|
495 ua[0] = unsortedA; |
|
496 |
|
497 storeRecordsInSession(repo, ua, null); |
|
498 |
|
499 // Ensure that the database is in this state. |
|
500 assertChildrenAreOrdered(repo, "unfiled", ua); |
|
501 |
|
502 // Delete the second child, the folder, and then the third child. |
|
503 bookmarkB.bookmarkURI = bookmarkC.bookmarkURI = folder.bookmarkURI = null; |
|
504 bookmarkB.deleted = bookmarkC.deleted = folder.deleted = true; |
|
505 bookmarkB.title = bookmarkC.title = folder.title = null; |
|
506 |
|
507 // Nulling the type of folder is very important: it verifies |
|
508 // that the session can behave correctly according to local type. |
|
509 bookmarkB.type = bookmarkC.type = folder.type = null; |
|
510 |
|
511 bookmarkB.lastModified = bookmarkC.lastModified = folder.lastModified = now = System.currentTimeMillis(); |
|
512 |
|
513 BookmarkRecord[] deletions = new BookmarkRecord[] { bookmarkB, folder, bookmarkC }; |
|
514 storeRecordsInSession(repo, deletions, null); |
|
515 |
|
516 // Verify that the unsorted bookmarks folder contains its child and the |
|
517 // first and fourth children of the now-deleted folder. |
|
518 // Also verify that the folder is gone. |
|
519 long unsortedID = fennecGetFolderId(cr, "unfiled"); |
|
520 long toolbarID = fennecGetFolderId(cr, "toolbar"); |
|
521 String[] expected = new String[] { unsortedA.guid, bookmarkA.guid, bookmarkD.guid }; |
|
522 |
|
523 // This will trigger positioning. |
|
524 assertChildrenAreUnordered(repo, "unfiled", new Record[] { unsortedA, bookmarkA, bookmarkD }); |
|
525 assertChildrenAreDirect(unsortedID, expected); |
|
526 assertChildrenAreDirect(toolbarID, new String[] {}); |
|
527 } |
|
528 |
|
529 /** |
|
530 * A test where we expect to replace a local folder with a new folder (with a |
|
531 * new GUID), whilst adding children to it. Verifies that replace and insert |
|
532 * co-operate. |
|
533 */ |
|
534 public void testInsertAndReplaceGuid() { |
|
535 AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); |
|
536 wipe(); |
|
537 |
|
538 BookmarkRecord folder1 = BookmarkHelpers.createFolder1(); |
|
539 BookmarkRecord folder2 = BookmarkHelpers.createFolder2(); // child of folder1 |
|
540 BookmarkRecord folder3 = BookmarkHelpers.createFolder3(); // child of folder2 |
|
541 BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1(); // child of folder1 |
|
542 BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2(); // child of folder1 |
|
543 BookmarkRecord bmk3 = BookmarkHelpers.createBookmark3(); // child of folder2 |
|
544 BookmarkRecord bmk4 = BookmarkHelpers.createBookmark4(); // child of folder3 |
|
545 |
|
546 BookmarkRecord[] records = new BookmarkRecord[] { |
|
547 folder1, folder2, folder3, |
|
548 bmk1, bmk4 |
|
549 }; |
|
550 storeRecordsInSession(repo, records, null); |
|
551 |
|
552 assertChildrenAreUnordered(repo, folder1.guid, new Record[] { bmk1, folder2 }); |
|
553 assertChildrenAreUnordered(repo, folder2.guid, new Record[] { folder3 }); |
|
554 assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 }); |
|
555 |
|
556 // Replace folder3 with a record with a new GUID, and add bmk4 as folder3's child. |
|
557 final long now = System.currentTimeMillis(); |
|
558 folder3.guid = Utils.generateGuid(); |
|
559 folder3.lastModified = now; |
|
560 bmk4.title = bmk4.title + "/NEW"; |
|
561 bmk4.parentID = folder3.guid; // Incoming child knows its parent. |
|
562 bmk4.parentName = folder3.title; |
|
563 bmk4.lastModified = now; |
|
564 |
|
565 // Order of store should not matter. |
|
566 ArrayList<BookmarkRecord> changedRecords = new ArrayList<BookmarkRecord>(); |
|
567 changedRecords.add(bmk2); changedRecords.add(bmk3); changedRecords.add(bmk4); changedRecords.add(folder3); |
|
568 Collections.shuffle(changedRecords); |
|
569 storeRecordsInSession(repo, changedRecords.toArray(new BookmarkRecord[changedRecords.size()]), null); |
|
570 |
|
571 assertChildrenAreUnordered(repo, folder1.guid, new Record[] { bmk1, bmk2, folder2 }); |
|
572 assertChildrenAreUnordered(repo, folder2.guid, new Record[] { bmk3, folder3 }); |
|
573 assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 }); |
|
574 |
|
575 assertNotNull(fetchGUID(repo, folder3.guid)); |
|
576 assertEquals(bmk4.title, fetchGUID(repo, bmk4.guid).title); |
|
577 } |
|
578 |
|
579 /** |
|
580 * A test where we expect to replace a local folder with a new folder (with a |
|
581 * new title but the same GUID), whilst adding children to it. Verifies that |
|
582 * replace and insert co-operate. |
|
583 */ |
|
584 public void testInsertAndReplaceTitle() { |
|
585 AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); |
|
586 wipe(); |
|
587 |
|
588 BookmarkRecord folder1 = BookmarkHelpers.createFolder1(); |
|
589 BookmarkRecord folder2 = BookmarkHelpers.createFolder2(); // child of folder1 |
|
590 BookmarkRecord folder3 = BookmarkHelpers.createFolder3(); // child of folder2 |
|
591 BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1(); // child of folder1 |
|
592 BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2(); // child of folder1 |
|
593 BookmarkRecord bmk3 = BookmarkHelpers.createBookmark3(); // child of folder2 |
|
594 BookmarkRecord bmk4 = BookmarkHelpers.createBookmark4(); // child of folder3 |
|
595 |
|
596 BookmarkRecord[] records = new BookmarkRecord[] { |
|
597 folder1, folder2, folder3, |
|
598 bmk1, bmk4 |
|
599 }; |
|
600 storeRecordsInSession(repo, records, null); |
|
601 |
|
602 assertChildrenAreUnordered(repo, folder1.guid, new Record[] { bmk1, folder2 }); |
|
603 assertChildrenAreUnordered(repo, folder2.guid, new Record[] { folder3 }); |
|
604 assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 }); |
|
605 |
|
606 // Rename folder1, and add bmk2 as folder1's child. |
|
607 final long now = System.currentTimeMillis(); |
|
608 folder1.title = folder1.title + "/NEW"; |
|
609 folder1.lastModified = now; |
|
610 bmk2.title = bmk2.title + "/NEW"; |
|
611 bmk2.parentID = folder1.guid; // Incoming child knows its parent. |
|
612 bmk2.parentName = folder1.title; |
|
613 bmk2.lastModified = now; |
|
614 |
|
615 // Order of store should not matter. |
|
616 ArrayList<BookmarkRecord> changedRecords = new ArrayList<BookmarkRecord>(); |
|
617 changedRecords.add(bmk2); changedRecords.add(bmk3); changedRecords.add(folder1); |
|
618 Collections.shuffle(changedRecords); |
|
619 storeRecordsInSession(repo, changedRecords.toArray(new BookmarkRecord[changedRecords.size()]), null); |
|
620 |
|
621 assertChildrenAreUnordered(repo, folder1.guid, new Record[] { bmk1, bmk2, folder2 }); |
|
622 assertChildrenAreUnordered(repo, folder2.guid, new Record[] { bmk3, folder3 }); |
|
623 assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 }); |
|
624 |
|
625 assertEquals(folder1.title, fetchGUID(repo, folder1.guid).title); |
|
626 assertEquals(bmk2.title, fetchGUID(repo, bmk2.guid).title); |
|
627 } |
|
628 |
|
629 /** |
|
630 * Create and begin a new session, handing control to the delegate when started. |
|
631 * Returns when the delegate has notified. |
|
632 */ |
|
633 public void inBegunSession(final AndroidBrowserBookmarksRepository repo, |
|
634 final RepositorySessionBeginDelegate beginDelegate) { |
|
635 Runnable go = new Runnable() { |
|
636 @Override |
|
637 public void run() { |
|
638 RepositorySessionCreationDelegate delegate = new SimpleSuccessCreationDelegate() { |
|
639 @Override |
|
640 public void onSessionCreated(final RepositorySession session) { |
|
641 try { |
|
642 session.begin(beginDelegate); |
|
643 } catch (InvalidSessionTransitionException e) { |
|
644 performNotify(e); |
|
645 } |
|
646 } |
|
647 }; |
|
648 repo.createSession(delegate, getApplicationContext()); |
|
649 } |
|
650 }; |
|
651 performWait(go); |
|
652 } |
|
653 |
|
654 /** |
|
655 * Finish the provided session, notifying on success. |
|
656 * |
|
657 * @param session |
|
658 */ |
|
659 public void finishAndNotify(final RepositorySession session) { |
|
660 try { |
|
661 session.finish(new SimpleSuccessFinishDelegate() { |
|
662 @Override |
|
663 public void onFinishSucceeded(RepositorySession session, |
|
664 RepositorySessionBundle bundle) { |
|
665 performNotify(); |
|
666 } |
|
667 }); |
|
668 } catch (InactiveSessionException e) { |
|
669 performNotify(e); |
|
670 } |
|
671 } |
|
672 |
|
673 /** |
|
674 * Simple helper class for fetching all records. |
|
675 * The fetched records' GUIDs are stored in `fetchedGUIDs`. |
|
676 */ |
|
677 public class SimpleFetchAllBeginDelegate extends SimpleSuccessBeginDelegate { |
|
678 public final ArrayList<String> fetchedGUIDs = new ArrayList<String>(); |
|
679 |
|
680 @Override |
|
681 public void onBeginSucceeded(final RepositorySession session) { |
|
682 RepositorySessionFetchRecordsDelegate fetchDelegate = new SimpleSuccessFetchDelegate() { |
|
683 |
|
684 @Override |
|
685 public void onFetchedRecord(Record record) { |
|
686 fetchedGUIDs.add(record.guid); |
|
687 } |
|
688 |
|
689 @Override |
|
690 public void onFetchCompleted(long end) { |
|
691 finishAndNotify(session); |
|
692 } |
|
693 }; |
|
694 session.fetchSince(0, fetchDelegate); |
|
695 } |
|
696 } |
|
697 |
|
698 /** |
|
699 * Simple helper class for fetching a single record by GUID. |
|
700 * The fetched record is stored in `fetchedRecord`. |
|
701 */ |
|
702 public class SimpleFetchOneBeginDelegate extends SimpleSuccessBeginDelegate { |
|
703 public final String guid; |
|
704 public Record fetchedRecord = null; |
|
705 |
|
706 public SimpleFetchOneBeginDelegate(String guid) { |
|
707 this.guid = guid; |
|
708 } |
|
709 |
|
710 @Override |
|
711 public void onBeginSucceeded(final RepositorySession session) { |
|
712 RepositorySessionFetchRecordsDelegate fetchDelegate = new SimpleSuccessFetchDelegate() { |
|
713 |
|
714 @Override |
|
715 public void onFetchedRecord(Record record) { |
|
716 fetchedRecord = record; |
|
717 } |
|
718 |
|
719 @Override |
|
720 public void onFetchCompleted(long end) { |
|
721 finishAndNotify(session); |
|
722 } |
|
723 }; |
|
724 try { |
|
725 session.fetch(new String[] { guid }, fetchDelegate); |
|
726 } catch (InactiveSessionException e) { |
|
727 performNotify("Session is inactive.", e); |
|
728 } |
|
729 } |
|
730 } |
|
731 |
|
732 /** |
|
733 * Create a new session for the given repository, storing each record |
|
734 * from the provided array. Notifies on failure or success. |
|
735 * |
|
736 * Optionally populates a provided Collection with tracked items. |
|
737 * @param repo |
|
738 * @param records |
|
739 * @param tracked |
|
740 */ |
|
741 public void storeRecordsInSession(AndroidBrowserBookmarksRepository repo, |
|
742 final BookmarkRecord[] records, |
|
743 final Collection<String> tracked) { |
|
744 SimpleSuccessBeginDelegate beginDelegate = new SimpleSuccessBeginDelegate() { |
|
745 @Override |
|
746 public void onBeginSucceeded(final RepositorySession session) { |
|
747 RepositorySessionStoreDelegate storeDelegate = new SimpleSuccessStoreDelegate() { |
|
748 |
|
749 @Override |
|
750 public void onStoreCompleted(final long storeEnd) { |
|
751 // Pass back whatever we tracked. |
|
752 if (tracked != null) { |
|
753 Iterator<String> iter = session.getTrackedRecordIDs(); |
|
754 while (iter.hasNext()) { |
|
755 tracked.add(iter.next()); |
|
756 } |
|
757 } |
|
758 finishAndNotify(session); |
|
759 } |
|
760 |
|
761 @Override |
|
762 public void onRecordStoreSucceeded(String guid) { |
|
763 } |
|
764 }; |
|
765 session.setStoreDelegate(storeDelegate); |
|
766 for (BookmarkRecord record : records) { |
|
767 try { |
|
768 session.store(record); |
|
769 } catch (NoStoreDelegateException e) { |
|
770 // Never happens. |
|
771 } |
|
772 } |
|
773 session.storeDone(); |
|
774 } |
|
775 }; |
|
776 inBegunSession(repo, beginDelegate); |
|
777 } |
|
778 |
|
779 public ArrayList<String> fetchGUIDs(AndroidBrowserBookmarksRepository repo) { |
|
780 SimpleFetchAllBeginDelegate beginDelegate = new SimpleFetchAllBeginDelegate(); |
|
781 inBegunSession(repo, beginDelegate); |
|
782 return beginDelegate.fetchedGUIDs; |
|
783 } |
|
784 |
|
785 public BookmarkRecord fetchGUID(AndroidBrowserBookmarksRepository repo, |
|
786 final String guid) { |
|
787 Logger.info(LOG_TAG, "Fetching for " + guid); |
|
788 SimpleFetchOneBeginDelegate beginDelegate = new SimpleFetchOneBeginDelegate(guid); |
|
789 inBegunSession(repo, beginDelegate); |
|
790 Logger.info(LOG_TAG, "Fetched " + beginDelegate.fetchedRecord); |
|
791 assertTrue(beginDelegate.fetchedRecord != null); |
|
792 return (BookmarkRecord) beginDelegate.fetchedRecord; |
|
793 } |
|
794 |
|
795 public JSONArray fetchChildrenForGUID(AndroidBrowserBookmarksRepository repo, |
|
796 final String guid) { |
|
797 return fetchGUID(repo, guid).children; |
|
798 } |
|
799 |
|
800 @SuppressWarnings("unchecked") |
|
801 protected static JSONArray childrenFromRecords(BookmarkRecord... records) { |
|
802 JSONArray children = new JSONArray(); |
|
803 for (BookmarkRecord record : records) { |
|
804 children.add(record.guid); |
|
805 } |
|
806 return children; |
|
807 } |
|
808 |
|
809 |
|
810 protected void updateRow(ContentValues values) { |
|
811 Uri uri = BrowserContractHelpers.BOOKMARKS_CONTENT_URI; |
|
812 final String where = BrowserContract.Bookmarks.GUID + " = ?"; |
|
813 final String[] args = new String[] { values.getAsString(BrowserContract.Bookmarks.GUID) }; |
|
814 getApplicationContext().getContentResolver().update(uri, values, where, args); |
|
815 } |
|
816 |
|
817 protected Uri insertRow(ContentValues values) { |
|
818 Uri uri = BrowserContractHelpers.BOOKMARKS_CONTENT_URI; |
|
819 return getApplicationContext().getContentResolver().insert(uri, values); |
|
820 } |
|
821 |
|
822 protected static ContentValues specialFolder() { |
|
823 ContentValues values = new ContentValues(); |
|
824 |
|
825 final long now = System.currentTimeMillis(); |
|
826 values.put(Bookmarks.DATE_CREATED, now); |
|
827 values.put(Bookmarks.DATE_MODIFIED, now); |
|
828 values.put(Bookmarks.TYPE, BrowserContract.Bookmarks.TYPE_FOLDER); |
|
829 |
|
830 return values; |
|
831 } |
|
832 |
|
833 protected static ContentValues fennecMobileRecordWithoutTitle() { |
|
834 ContentValues values = specialFolder(); |
|
835 values.put(BrowserContract.SyncColumns.GUID, "mobile"); |
|
836 values.putNull(BrowserContract.Bookmarks.TITLE); |
|
837 |
|
838 return values; |
|
839 } |
|
840 |
|
841 protected ContentValues fennecPinnedItemsRecord() { |
|
842 final ContentValues values = specialFolder(); |
|
843 final String title = getApplicationContext().getResources().getString(R.string.bookmarks_folder_pinned); |
|
844 |
|
845 values.put(BrowserContract.SyncColumns.GUID, Bookmarks.PINNED_FOLDER_GUID); |
|
846 values.put(Bookmarks._ID, Bookmarks.FIXED_PINNED_LIST_ID); |
|
847 values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID); |
|
848 values.put(Bookmarks.TITLE, title); |
|
849 return values; |
|
850 } |
|
851 |
|
852 protected static ContentValues fennecPinnedChildItemRecord() { |
|
853 ContentValues values = new ContentValues(); |
|
854 |
|
855 final long now = System.currentTimeMillis(); |
|
856 |
|
857 values.put(BrowserContract.SyncColumns.GUID, "dapinneditem"); |
|
858 values.put(Bookmarks.DATE_CREATED, now); |
|
859 values.put(Bookmarks.DATE_MODIFIED, now); |
|
860 values.put(Bookmarks.TYPE, BrowserContract.Bookmarks.TYPE_BOOKMARK); |
|
861 values.put(Bookmarks.URL, "user-entered:foobar"); |
|
862 values.put(Bookmarks.PARENT, Bookmarks.FIXED_PINNED_LIST_ID); |
|
863 values.put(Bookmarks.TITLE, "Foobar"); |
|
864 return values; |
|
865 } |
|
866 |
|
867 protected ContentValues fennecReadingListRecord() { |
|
868 final ContentValues values = specialFolder(); |
|
869 final String title = getApplicationContext().getResources().getString(R.string.bookmarks_folder_reading_list); |
|
870 |
|
871 values.put(BrowserContract.SyncColumns.GUID, Bookmarks.READING_LIST_FOLDER_GUID); |
|
872 values.put(Bookmarks._ID, Bookmarks.FIXED_READING_LIST_ID); |
|
873 values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID); |
|
874 values.put(Bookmarks.TITLE, title); |
|
875 return values; |
|
876 } |
|
877 |
|
878 protected long setUpFennecMobileRecordWithoutTitle() { |
|
879 ContentResolver cr = getApplicationContext().getContentResolver(); |
|
880 ContentValues values = fennecMobileRecordWithoutTitle(); |
|
881 updateRow(values); |
|
882 return fennecGetMobileBookmarksFolderId(cr); |
|
883 } |
|
884 |
|
885 protected long setUpFennecMobileRecord() { |
|
886 ContentResolver cr = getApplicationContext().getContentResolver(); |
|
887 ContentValues values = fennecMobileRecordWithoutTitle(); |
|
888 values.put(BrowserContract.Bookmarks.PARENT, BrowserContract.Bookmarks.FIXED_ROOT_ID); |
|
889 String title = getApplicationContext().getResources().getString(R.string.bookmarks_folder_mobile); |
|
890 values.put(BrowserContract.Bookmarks.TITLE, title); |
|
891 updateRow(values); |
|
892 return fennecGetMobileBookmarksFolderId(cr); |
|
893 } |
|
894 |
|
895 protected void setUpFennecPinnedItemsRecord() { |
|
896 insertRow(fennecPinnedItemsRecord()); |
|
897 insertRow(fennecPinnedChildItemRecord()); |
|
898 } |
|
899 |
|
900 protected void setUpFennecReadingListRecord() { |
|
901 insertRow(fennecReadingListRecord()); |
|
902 } |
|
903 |
|
904 // |
|
905 // Fennec fake layer. |
|
906 // |
|
907 private Uri appendProfile(Uri uri) { |
|
908 final String defaultProfile = "default"; // Fennec constant removed in Bug 715307. |
|
909 return uri.buildUpon().appendQueryParameter(BrowserContract.PARAM_PROFILE, defaultProfile).build(); |
|
910 } |
|
911 |
|
912 private long fennecGetFolderId(ContentResolver cr, String guid) { |
|
913 Cursor c = null; |
|
914 try { |
|
915 c = cr.query(appendProfile(BrowserContractHelpers.BOOKMARKS_CONTENT_URI), |
|
916 new String[] { BrowserContract.Bookmarks._ID }, |
|
917 BrowserContract.Bookmarks.GUID + " = ?", |
|
918 new String[] { guid }, |
|
919 null); |
|
920 |
|
921 if (c.moveToFirst()) { |
|
922 return c.getLong(c.getColumnIndexOrThrow(BrowserContract.Bookmarks._ID)); |
|
923 } |
|
924 return -1; |
|
925 } finally { |
|
926 if (c != null) { |
|
927 c.close(); |
|
928 } |
|
929 } |
|
930 } |
|
931 |
|
932 private long fennecGetMobileBookmarksFolderId(ContentResolver cr) { |
|
933 return fennecGetFolderId(cr, BrowserContract.Bookmarks.MOBILE_FOLDER_GUID); |
|
934 } |
|
935 |
|
936 public void fennecAddBookmark(String title, String uri) { |
|
937 ContentResolver cr = getApplicationContext().getContentResolver(); |
|
938 |
|
939 long folderId = fennecGetMobileBookmarksFolderId(cr); |
|
940 if (folderId < 0) { |
|
941 return; |
|
942 } |
|
943 |
|
944 ContentValues values = new ContentValues(); |
|
945 values.put(BrowserContract.Bookmarks.TITLE, title); |
|
946 values.put(BrowserContract.Bookmarks.URL, uri); |
|
947 values.put(BrowserContract.Bookmarks.PARENT, folderId); |
|
948 |
|
949 // Restore deleted record if possible |
|
950 values.put(BrowserContract.Bookmarks.IS_DELETED, 0); |
|
951 |
|
952 Logger.debug(getName(), "Adding bookmark " + title + ", " + uri + " in " + folderId); |
|
953 int updated = cr.update(appendProfile(BrowserContractHelpers.BOOKMARKS_CONTENT_URI), |
|
954 values, |
|
955 BrowserContract.Bookmarks.URL + " = ?", |
|
956 new String[] { uri }); |
|
957 |
|
958 if (updated == 0) { |
|
959 Uri insert = cr.insert(appendProfile(BrowserContractHelpers.BOOKMARKS_CONTENT_URI), values); |
|
960 long idFromUri = ContentUris.parseId(insert); |
|
961 Logger.debug(getName(), "Inserted " + uri + " as " + idFromUri); |
|
962 Logger.debug(getName(), "Position is " + getPosition(idFromUri)); |
|
963 } |
|
964 } |
|
965 |
|
966 private long getPosition(long idFromUri) { |
|
967 ContentResolver cr = getApplicationContext().getContentResolver(); |
|
968 Cursor c = cr.query(appendProfile(BrowserContractHelpers.BOOKMARKS_CONTENT_URI), |
|
969 new String[] { BrowserContract.Bookmarks.POSITION }, |
|
970 BrowserContract.Bookmarks._ID + " = ?", |
|
971 new String[] { String.valueOf(idFromUri) }, |
|
972 null); |
|
973 if (!c.moveToFirst()) { |
|
974 return -2; |
|
975 } |
|
976 return c.getLong(0); |
|
977 } |
|
978 |
|
979 protected AndroidBrowserBookmarksDataAccessor dataAccessor = null; |
|
980 protected AndroidBrowserBookmarksDataAccessor getDataAccessor() { |
|
981 if (dataAccessor == null) { |
|
982 dataAccessor = new AndroidBrowserBookmarksDataAccessor(getApplicationContext()); |
|
983 } |
|
984 return dataAccessor; |
|
985 } |
|
986 |
|
987 protected void wipe() { |
|
988 Logger.debug(getName(), "Wiping."); |
|
989 getDataAccessor().wipe(); |
|
990 } |
|
991 |
|
992 protected void assertChildrenAreOrdered(AndroidBrowserBookmarksRepository repo, String guid, Record[] expected) { |
|
993 Logger.debug(getName(), "Fetching children..."); |
|
994 JSONArray folderChildren = fetchChildrenForGUID(repo, guid); |
|
995 |
|
996 assertTrue(folderChildren != null); |
|
997 Logger.debug(getName(), "Children are " + folderChildren.toJSONString()); |
|
998 assertEquals(expected.length, folderChildren.size()); |
|
999 for (int i = 0; i < expected.length; ++i) { |
|
1000 assertEquals(expected[i].guid, ((String) folderChildren.get(i))); |
|
1001 } |
|
1002 } |
|
1003 |
|
1004 protected void assertChildrenAreUnordered(AndroidBrowserBookmarksRepository repo, String guid, Record[] expected) { |
|
1005 Logger.debug(getName(), "Fetching children..."); |
|
1006 JSONArray folderChildren = fetchChildrenForGUID(repo, guid); |
|
1007 |
|
1008 assertTrue(folderChildren != null); |
|
1009 Logger.debug(getName(), "Children are " + folderChildren.toJSONString()); |
|
1010 assertEquals(expected.length, folderChildren.size()); |
|
1011 for (Record record : expected) { |
|
1012 folderChildren.contains(record.guid); |
|
1013 } |
|
1014 } |
|
1015 |
|
1016 /** |
|
1017 * Return a sequence of children GUIDs for the provided folder ID. |
|
1018 */ |
|
1019 protected ArrayList<String> fetchChildrenDirect(long id) { |
|
1020 Logger.debug(getName(), "Fetching children directly from DB..."); |
|
1021 final ArrayList<String> out = new ArrayList<String>(); |
|
1022 final AndroidBrowserBookmarksDataAccessor accessor = new AndroidBrowserBookmarksDataAccessor(getApplicationContext()); |
|
1023 Cursor cur = null; |
|
1024 try { |
|
1025 cur = accessor.getChildren(id); |
|
1026 } catch (NullCursorException e) { |
|
1027 fail("Got null cursor."); |
|
1028 } |
|
1029 try { |
|
1030 if (!cur.moveToFirst()) { |
|
1031 return out; |
|
1032 } |
|
1033 final int guidCol = cur.getColumnIndex(BrowserContract.SyncColumns.GUID); |
|
1034 while (!cur.isAfterLast()) { |
|
1035 out.add(cur.getString(guidCol)); |
|
1036 cur.moveToNext(); |
|
1037 } |
|
1038 } finally { |
|
1039 cur.close(); |
|
1040 } |
|
1041 return out; |
|
1042 } |
|
1043 |
|
1044 /** |
|
1045 * Assert that the children of the provided ID are correct and positioned in the database. |
|
1046 * @param id |
|
1047 * @param guids |
|
1048 */ |
|
1049 protected void assertChildrenAreDirect(long id, String[] guids) { |
|
1050 Logger.debug(getName(), "Fetching children directly from DB..."); |
|
1051 AndroidBrowserBookmarksDataAccessor accessor = new AndroidBrowserBookmarksDataAccessor(getApplicationContext()); |
|
1052 Cursor cur = null; |
|
1053 try { |
|
1054 cur = accessor.getChildren(id); |
|
1055 } catch (NullCursorException e) { |
|
1056 fail("Got null cursor."); |
|
1057 } |
|
1058 try { |
|
1059 if (guids == null || guids.length == 0) { |
|
1060 assertFalse(cur.moveToFirst()); |
|
1061 return; |
|
1062 } |
|
1063 |
|
1064 assertTrue(cur.moveToFirst()); |
|
1065 int i = 0; |
|
1066 final int guidCol = cur.getColumnIndex(BrowserContract.SyncColumns.GUID); |
|
1067 final int posCol = cur.getColumnIndex(BrowserContract.Bookmarks.POSITION); |
|
1068 while (!cur.isAfterLast()) { |
|
1069 assertTrue(i < guids.length); |
|
1070 final String guid = cur.getString(guidCol); |
|
1071 final int pos = cur.getInt(posCol); |
|
1072 Logger.debug(getName(), "Fetched child: " + guid + " has position " + pos); |
|
1073 assertEquals(guids[i], guid); |
|
1074 assertEquals(i, pos); |
|
1075 |
|
1076 ++i; |
|
1077 cur.moveToNext(); |
|
1078 } |
|
1079 assertEquals(guids.length, i); |
|
1080 } finally { |
|
1081 cur.close(); |
|
1082 } |
|
1083 } |
|
1084 } |
|
1085 |
|
1086 /** |
|
1087 TODO |
|
1088 |
|
1089 Test for storing a record that will reconcile to mobile; postcondition is |
|
1090 that there's still a directory called mobile that includes all the items that |
|
1091 it used to. |
|
1092 |
|
1093 mobile folder created without title. |
|
1094 Unsorted put in mobile??? |
|
1095 Tests for children retrieval |
|
1096 Tests for children merge |
|
1097 Tests for modify retrieve parent when child added, removed, reordered (oh, reorder is hard! Any change, then.) |
|
1098 Safety mode? |
|
1099 Test storing folder first, contents first. |
|
1100 Store folder in next session. Verify order recovery. |
|
1101 |
|
1102 |
|
1103 */ |