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