Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright (c) 2018 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.migration

import android.arch.lifecycle.LiveData
import android.arch.persistence.room.Room
import android.content.ContentValues
import android.support.test.InstrumentationRegistry
import com.duckduckgo.app.bookmarks.db.BookmarkEntity
import com.duckduckgo.app.bookmarks.db.BookmarksDao
import com.duckduckgo.app.browser.DuckDuckGoRequestRewriter
import com.duckduckgo.app.browser.DuckDuckGoUrlDetector
import com.duckduckgo.app.browser.omnibar.QueryUrlConverter
import com.duckduckgo.app.global.db.AppDatabase
import com.duckduckgo.app.migration.legacy.LegacyDb
import com.duckduckgo.app.migration.legacy.LegacyDbContracts
import org.junit.After
import org.junit.Assert.*
import org.junit.Test

class LegacyMigrationTest {

// target context else we can't write a db file
val context = InstrumentationRegistry.getTargetContext()
val urlConverter = QueryUrlConverter(DuckDuckGoRequestRewriter(DuckDuckGoUrlDetector()))

var appDatabase: AppDatabase = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build()
var bookmarksDao = MockBookmarksDao()

@After
fun after() {
deleteLegacyDb()
}

@Test
fun whenNoLegacyDbExistsThenMigrationCompletesWithZeroMigratedItems() {

deleteLegacyDb()

val migration = LegacyMigration(appDatabase, bookmarksDao, context, urlConverter);

migration.start { favourites, searches ->
assertEquals(0, favourites)
assertEquals(0, searches)
}

assertEquals(0, bookmarksDao.bookmarks.size)

}

@Test
fun whenLegacyDbExistsThenMigrationCompletesWithCorrectNumberOfMigratedItems() {

populateLegacyDB()

// migrate
val migration = LegacyMigration(appDatabase, bookmarksDao, context, urlConverter);

migration.start { favourites, searches ->
assertEquals(1, favourites)
assertEquals(1, searches)
}

assertEquals(2, bookmarksDao.bookmarks.size)

migration.start { favourites, searches ->
assertEquals(0, favourites)
assertEquals(0, searches)
}

}

private fun deleteLegacyDb() {
context.getDatabasePath(LegacyDbContracts.DATABASE_NAME).delete()
}

private fun populateLegacyDB() {
val db = LegacyDb(context)

assertNotEquals(-1, db.sqLiteDB.insert(LegacyDbContracts.SAVED_SEARCH_TABLE.TABLE_NAME, null, searchEntry()))
assertNotEquals(-1, db.sqLiteDB.insert(LegacyDbContracts.FEED_TABLE.TABLE_NAME, null, favouriteEntry()))
assertNotEquals(-1, db.sqLiteDB.insert(LegacyDbContracts.FEED_TABLE.TABLE_NAME, null, notFavouriteEntry()))

db.close()
}

private fun notFavouriteEntry(): ContentValues {
val values = ContentValues()
values.put(LegacyDbContracts.FEED_TABLE._ID, "oops id")
values.put(LegacyDbContracts.FEED_TABLE.COLUMN_TITLE, "oops title")
values.put(LegacyDbContracts.FEED_TABLE.COLUMN_URL, "oops url")
values.put(LegacyDbContracts.FEED_TABLE.COLUMN_FAVORITE, "OOPS")
return values
}

private fun favouriteEntry(): ContentValues {
val values = ContentValues()
values.put(LegacyDbContracts.FEED_TABLE._ID, "favourite id")
values.put(LegacyDbContracts.FEED_TABLE.COLUMN_TITLE, "favourite title")
values.put(LegacyDbContracts.FEED_TABLE.COLUMN_URL, "favourite url")
values.put(LegacyDbContracts.FEED_TABLE.COLUMN_FAVORITE, "F")
return values
}

private fun searchEntry(): ContentValues {
val values = ContentValues()
values.put(LegacyDbContracts.SAVED_SEARCH_TABLE.COLUMN_TITLE, "search title")
values.put(LegacyDbContracts.SAVED_SEARCH_TABLE.COLUMN_QUERY, "search query")
return values
}

class MockBookmarksDao(): BookmarksDao {

var bookmarks = mutableListOf<BookmarkEntity>()

override fun insert(bookmark: BookmarkEntity) {
bookmarks.add(bookmark)
}

override fun bookmarks(): LiveData<List<BookmarkEntity>> {
throw UnsupportedOperationException()
}

override fun delete(bookmark: BookmarkEntity) {
throw UnsupportedOperationException()
}

}

}
2 changes: 1 addition & 1 deletion app/src/main/java/com/duckduckgo/app/di/StoreModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import dagger.Provides
class StoreModule {

@Provides
fun providesOnboaridngStore(context: Context): OnboardingStore {
fun providesOnboardingStore(context: Context): OnboardingStore {
return OnboardingSharedPreferences(context)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ import android.app.Service
import com.duckduckgo.app.browser.BuildConfig
import com.duckduckgo.app.di.DaggerAppComponent
import com.duckduckgo.app.job.AppConfigurationSyncer
import com.duckduckgo.app.migration.LegacyMigration
import com.duckduckgo.app.trackerdetection.TrackerDataLoader
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasActivityInjector
import dagger.android.HasServiceInjector
import io.reactivex.schedulers.Schedulers
import org.jetbrains.anko.doAsync
import timber.log.Timber
import javax.inject.Inject

Expand All @@ -48,6 +50,9 @@ class DuckDuckGoApplication : HasActivityInjector, HasServiceInjector, Applicati
@Inject
lateinit var appConfigurationSyncer: AppConfigurationSyncer

@Inject
lateinit var migration: LegacyMigration

override fun onCreate() {
super.onCreate()

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

loadTrackerData()
configureDataDownloader()

migrateLegacyDb()
}

private fun migrateLegacyDb() {
doAsync {
migration.start { favourites, searches ->
Timber.d("Migrated $favourites favourites, $searches")
}
}
}

private fun loadTrackerData() {
Expand Down
110 changes: 110 additions & 0 deletions app/src/main/java/com/duckduckgo/app/migration/LegacyMigration.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright (c) 2018 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.migration

import android.content.Context
import android.support.annotation.WorkerThread
import android.webkit.URLUtil
import com.duckduckgo.app.bookmarks.db.BookmarkEntity
import com.duckduckgo.app.bookmarks.db.BookmarksDao
import com.duckduckgo.app.browser.omnibar.QueryUrlConverter
import com.duckduckgo.app.global.db.AppDatabase
import com.duckduckgo.app.migration.legacy.LegacyDb
import com.duckduckgo.app.migration.legacy.LegacyDbContracts
import timber.log.Timber
import javax.inject.Inject

class LegacyMigration @Inject constructor(
val database: AppDatabase,
val bookmarksDao: BookmarksDao,
val context: Context,
val queryUrlConverter: QueryUrlConverter) {

@WorkerThread
fun start(completion: (favourites: Int, searches: Int) -> Unit) {

LegacyDb(context.applicationContext).use {
migrate(it, completion)
}

}

private fun migrate(legacyDb:LegacyDb, completion: (favourites: Int, searches: Int) -> Unit) {

var favourites = 0
var searches = 0

database.runInTransaction {
favourites = migrateFavourites(legacyDb)
searches = migrateSavedSearches(legacyDb)
legacyDb.deleteAll()
}

completion(favourites, searches)
}

private fun migrateSavedSearches(db: LegacyDb): Int {

var count = 0
db.cursorSavedSearch.use {

if (!it.moveToFirst()) {
Timber.d("No saved searches found")
return 0
}

val titleColumn = it.getColumnIndex(LegacyDbContracts.SAVED_SEARCH_TABLE.COLUMN_TITLE)
val queryColumn = it.getColumnIndex(LegacyDbContracts.SAVED_SEARCH_TABLE.COLUMN_QUERY)

do {

val title = it.getString(titleColumn)
val query = it.getString(queryColumn)

val url = if (URLUtil.isNetworkUrl(query)) query else queryUrlConverter.convertQueryToUri(query).toString()

bookmarksDao.insert(BookmarkEntity(title = title, url = url))

count += 1
} while (it.moveToNext())
}

return count
}

private fun migrateFavourites(db: LegacyDb) : Int {
val feedObjects = db.selectAll() ?: return 0

var count = 0
for (feedObject in feedObjects) {

if (!db.isSaved(feedObject.id)) {
continue
}

val title = feedObject.title
val url = feedObject.url

bookmarksDao.insert(BookmarkEntity(title = title, url = url))

count += 1
}

return count
}

}
Loading