Skip to content

Commit cb81977

Browse files
authored
favourites and saved searches migration (#120)
1 parent aef05ca commit cb81977

File tree

9 files changed

+911
-1
lines changed

9 files changed

+911
-1
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* Copyright (c) 2018 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.migration
18+
19+
import android.arch.lifecycle.LiveData
20+
import android.arch.persistence.room.Room
21+
import android.content.ContentValues
22+
import android.support.test.InstrumentationRegistry
23+
import com.duckduckgo.app.bookmarks.db.BookmarkEntity
24+
import com.duckduckgo.app.bookmarks.db.BookmarksDao
25+
import com.duckduckgo.app.browser.DuckDuckGoRequestRewriter
26+
import com.duckduckgo.app.browser.DuckDuckGoUrlDetector
27+
import com.duckduckgo.app.browser.omnibar.QueryUrlConverter
28+
import com.duckduckgo.app.global.db.AppDatabase
29+
import com.duckduckgo.app.migration.legacy.LegacyDb
30+
import com.duckduckgo.app.migration.legacy.LegacyDbContracts
31+
import org.junit.After
32+
import org.junit.Assert.*
33+
import org.junit.Test
34+
35+
class LegacyMigrationTest {
36+
37+
// target context else we can't write a db file
38+
val context = InstrumentationRegistry.getTargetContext()
39+
val urlConverter = QueryUrlConverter(DuckDuckGoRequestRewriter(DuckDuckGoUrlDetector()))
40+
41+
var appDatabase: AppDatabase = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build()
42+
var bookmarksDao = MockBookmarksDao()
43+
44+
@After
45+
fun after() {
46+
deleteLegacyDb()
47+
}
48+
49+
@Test
50+
fun whenNoLegacyDbExistsThenMigrationCompletesWithZeroMigratedItems() {
51+
52+
deleteLegacyDb()
53+
54+
val migration = LegacyMigration(appDatabase, bookmarksDao, context, urlConverter);
55+
56+
migration.start { favourites, searches ->
57+
assertEquals(0, favourites)
58+
assertEquals(0, searches)
59+
}
60+
61+
assertEquals(0, bookmarksDao.bookmarks.size)
62+
63+
}
64+
65+
@Test
66+
fun whenLegacyDbExistsThenMigrationCompletesWithCorrectNumberOfMigratedItems() {
67+
68+
populateLegacyDB()
69+
70+
// migrate
71+
val migration = LegacyMigration(appDatabase, bookmarksDao, context, urlConverter);
72+
73+
migration.start { favourites, searches ->
74+
assertEquals(1, favourites)
75+
assertEquals(1, searches)
76+
}
77+
78+
assertEquals(2, bookmarksDao.bookmarks.size)
79+
80+
migration.start { favourites, searches ->
81+
assertEquals(0, favourites)
82+
assertEquals(0, searches)
83+
}
84+
85+
}
86+
87+
private fun deleteLegacyDb() {
88+
context.getDatabasePath(LegacyDbContracts.DATABASE_NAME).delete()
89+
}
90+
91+
private fun populateLegacyDB() {
92+
val db = LegacyDb(context)
93+
94+
assertNotEquals(-1, db.sqLiteDB.insert(LegacyDbContracts.SAVED_SEARCH_TABLE.TABLE_NAME, null, searchEntry()))
95+
assertNotEquals(-1, db.sqLiteDB.insert(LegacyDbContracts.FEED_TABLE.TABLE_NAME, null, favouriteEntry()))
96+
assertNotEquals(-1, db.sqLiteDB.insert(LegacyDbContracts.FEED_TABLE.TABLE_NAME, null, notFavouriteEntry()))
97+
98+
db.close()
99+
}
100+
101+
private fun notFavouriteEntry(): ContentValues {
102+
val values = ContentValues()
103+
values.put(LegacyDbContracts.FEED_TABLE._ID, "oops id")
104+
values.put(LegacyDbContracts.FEED_TABLE.COLUMN_TITLE, "oops title")
105+
values.put(LegacyDbContracts.FEED_TABLE.COLUMN_URL, "oops url")
106+
values.put(LegacyDbContracts.FEED_TABLE.COLUMN_FAVORITE, "OOPS")
107+
return values
108+
}
109+
110+
private fun favouriteEntry(): ContentValues {
111+
val values = ContentValues()
112+
values.put(LegacyDbContracts.FEED_TABLE._ID, "favourite id")
113+
values.put(LegacyDbContracts.FEED_TABLE.COLUMN_TITLE, "favourite title")
114+
values.put(LegacyDbContracts.FEED_TABLE.COLUMN_URL, "favourite url")
115+
values.put(LegacyDbContracts.FEED_TABLE.COLUMN_FAVORITE, "F")
116+
return values
117+
}
118+
119+
private fun searchEntry(): ContentValues {
120+
val values = ContentValues()
121+
values.put(LegacyDbContracts.SAVED_SEARCH_TABLE.COLUMN_TITLE, "search title")
122+
values.put(LegacyDbContracts.SAVED_SEARCH_TABLE.COLUMN_QUERY, "search query")
123+
return values
124+
}
125+
126+
class MockBookmarksDao(): BookmarksDao {
127+
128+
var bookmarks = mutableListOf<BookmarkEntity>()
129+
130+
override fun insert(bookmark: BookmarkEntity) {
131+
bookmarks.add(bookmark)
132+
}
133+
134+
override fun bookmarks(): LiveData<List<BookmarkEntity>> {
135+
throw UnsupportedOperationException()
136+
}
137+
138+
override fun delete(bookmark: BookmarkEntity) {
139+
throw UnsupportedOperationException()
140+
}
141+
142+
}
143+
144+
}

app/src/main/java/com/duckduckgo/app/di/StoreModule.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import dagger.Provides
2626
class StoreModule {
2727

2828
@Provides
29-
fun providesOnboaridngStore(context: Context): OnboardingStore {
29+
fun providesOnboardingStore(context: Context): OnboardingStore {
3030
return OnboardingSharedPreferences(context)
3131
}
3232

app/src/main/java/com/duckduckgo/app/global/DuckDuckGoApplication.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ import android.app.Service
2222
import com.duckduckgo.app.browser.BuildConfig
2323
import com.duckduckgo.app.di.DaggerAppComponent
2424
import com.duckduckgo.app.job.AppConfigurationSyncer
25+
import com.duckduckgo.app.migration.LegacyMigration
2526
import com.duckduckgo.app.trackerdetection.TrackerDataLoader
2627
import dagger.android.AndroidInjector
2728
import dagger.android.DispatchingAndroidInjector
2829
import dagger.android.HasActivityInjector
2930
import dagger.android.HasServiceInjector
3031
import io.reactivex.schedulers.Schedulers
32+
import org.jetbrains.anko.doAsync
3133
import timber.log.Timber
3234
import javax.inject.Inject
3335

@@ -48,6 +50,9 @@ class DuckDuckGoApplication : HasActivityInjector, HasServiceInjector, Applicati
4850
@Inject
4951
lateinit var appConfigurationSyncer: AppConfigurationSyncer
5052

53+
@Inject
54+
lateinit var migration: LegacyMigration
55+
5156
override fun onCreate() {
5257
super.onCreate()
5358

@@ -57,6 +62,16 @@ class DuckDuckGoApplication : HasActivityInjector, HasServiceInjector, Applicati
5762

5863
loadTrackerData()
5964
configureDataDownloader()
65+
66+
migrateLegacyDb()
67+
}
68+
69+
private fun migrateLegacyDb() {
70+
doAsync {
71+
migration.start { favourites, searches ->
72+
Timber.d("Migrated $favourites favourites, $searches")
73+
}
74+
}
6075
}
6176

6277
private fun loadTrackerData() {
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright (c) 2018 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.migration
18+
19+
import android.content.Context
20+
import android.support.annotation.WorkerThread
21+
import android.webkit.URLUtil
22+
import com.duckduckgo.app.bookmarks.db.BookmarkEntity
23+
import com.duckduckgo.app.bookmarks.db.BookmarksDao
24+
import com.duckduckgo.app.browser.omnibar.QueryUrlConverter
25+
import com.duckduckgo.app.global.db.AppDatabase
26+
import com.duckduckgo.app.migration.legacy.LegacyDb
27+
import com.duckduckgo.app.migration.legacy.LegacyDbContracts
28+
import timber.log.Timber
29+
import javax.inject.Inject
30+
31+
class LegacyMigration @Inject constructor(
32+
val database: AppDatabase,
33+
val bookmarksDao: BookmarksDao,
34+
val context: Context,
35+
val queryUrlConverter: QueryUrlConverter) {
36+
37+
@WorkerThread
38+
fun start(completion: (favourites: Int, searches: Int) -> Unit) {
39+
40+
LegacyDb(context.applicationContext).use {
41+
migrate(it, completion)
42+
}
43+
44+
}
45+
46+
private fun migrate(legacyDb:LegacyDb, completion: (favourites: Int, searches: Int) -> Unit) {
47+
48+
var favourites = 0
49+
var searches = 0
50+
51+
database.runInTransaction {
52+
favourites = migrateFavourites(legacyDb)
53+
searches = migrateSavedSearches(legacyDb)
54+
legacyDb.deleteAll()
55+
}
56+
57+
completion(favourites, searches)
58+
}
59+
60+
private fun migrateSavedSearches(db: LegacyDb): Int {
61+
62+
var count = 0
63+
db.cursorSavedSearch.use {
64+
65+
if (!it.moveToFirst()) {
66+
Timber.d("No saved searches found")
67+
return 0
68+
}
69+
70+
val titleColumn = it.getColumnIndex(LegacyDbContracts.SAVED_SEARCH_TABLE.COLUMN_TITLE)
71+
val queryColumn = it.getColumnIndex(LegacyDbContracts.SAVED_SEARCH_TABLE.COLUMN_QUERY)
72+
73+
do {
74+
75+
val title = it.getString(titleColumn)
76+
val query = it.getString(queryColumn)
77+
78+
val url = if (URLUtil.isNetworkUrl(query)) query else queryUrlConverter.convertQueryToUri(query).toString()
79+
80+
bookmarksDao.insert(BookmarkEntity(title = title, url = url))
81+
82+
count += 1
83+
} while (it.moveToNext())
84+
}
85+
86+
return count
87+
}
88+
89+
private fun migrateFavourites(db: LegacyDb) : Int {
90+
val feedObjects = db.selectAll() ?: return 0
91+
92+
var count = 0
93+
for (feedObject in feedObjects) {
94+
95+
if (!db.isSaved(feedObject.id)) {
96+
continue
97+
}
98+
99+
val title = feedObject.title
100+
val url = feedObject.url
101+
102+
bookmarksDao.insert(BookmarkEntity(title = title, url = url))
103+
104+
count += 1
105+
}
106+
107+
return count
108+
}
109+
110+
}

0 commit comments

Comments
 (0)