diff --git a/app/src/androidTest/java/com/duckduckgo/app/migration/LegacyMigrationTest.kt b/app/src/androidTest/java/com/duckduckgo/app/migration/LegacyMigrationTest.kt new file mode 100644 index 000000000000..ad8a3bffcfd8 --- /dev/null +++ b/app/src/androidTest/java/com/duckduckgo/app/migration/LegacyMigrationTest.kt @@ -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() + + override fun insert(bookmark: BookmarkEntity) { + bookmarks.add(bookmark) + } + + override fun bookmarks(): LiveData> { + throw UnsupportedOperationException() + } + + override fun delete(bookmark: BookmarkEntity) { + throw UnsupportedOperationException() + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/duckduckgo/app/di/StoreModule.kt b/app/src/main/java/com/duckduckgo/app/di/StoreModule.kt index 0b41a49bf0f5..72ed39cff5cb 100644 --- a/app/src/main/java/com/duckduckgo/app/di/StoreModule.kt +++ b/app/src/main/java/com/duckduckgo/app/di/StoreModule.kt @@ -26,7 +26,7 @@ import dagger.Provides class StoreModule { @Provides - fun providesOnboaridngStore(context: Context): OnboardingStore { + fun providesOnboardingStore(context: Context): OnboardingStore { return OnboardingSharedPreferences(context) } diff --git a/app/src/main/java/com/duckduckgo/app/global/DuckDuckGoApplication.kt b/app/src/main/java/com/duckduckgo/app/global/DuckDuckGoApplication.kt index 07f6b6b9e565..ddf4b46eb2f9 100644 --- a/app/src/main/java/com/duckduckgo/app/global/DuckDuckGoApplication.kt +++ b/app/src/main/java/com/duckduckgo/app/global/DuckDuckGoApplication.kt @@ -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 @@ -48,6 +50,9 @@ class DuckDuckGoApplication : HasActivityInjector, HasServiceInjector, Applicati @Inject lateinit var appConfigurationSyncer: AppConfigurationSyncer + @Inject + lateinit var migration: LegacyMigration + override fun onCreate() { super.onCreate() @@ -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() { diff --git a/app/src/main/java/com/duckduckgo/app/migration/LegacyMigration.kt b/app/src/main/java/com/duckduckgo/app/migration/LegacyMigration.kt new file mode 100644 index 000000000000..9a1147b2e589 --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/migration/LegacyMigration.kt @@ -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 + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/duckduckgo/app/migration/legacy/LegacyDb.java b/app/src/main/java/com/duckduckgo/app/migration/legacy/LegacyDb.java new file mode 100644 index 000000000000..285ea7ebf650 --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/migration/legacy/LegacyDb.java @@ -0,0 +1,313 @@ +/* + * 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.legacy; + +import android.content.ContentValues; +import android.content.Context; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.preference.PreferenceManager; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class LegacyDb implements Closeable { + + private SQLiteDatabase db; + + + public LegacyDb(Context context) { + OpenHelper openHelper = new OpenHelper(context); + this.db = openHelper.getWritableDatabase(); + } + + public void deleteAll() { + this.db.delete(LegacyDbContracts.FEED_TABLE.TABLE_NAME, null, null); + this.db.delete(LegacyDbContracts.SAVED_SEARCH_TABLE.TABLE_NAME, null, null); + } + + private LegacyFeedObject getFeedObject(Cursor c) { + final String id = c.getString(c.getColumnIndex(LegacyDbContracts.FEED_TABLE._ID)); + final String title = c.getString(c.getColumnIndex(LegacyDbContracts.FEED_TABLE.COLUMN_TITLE)); + final String description = c.getString(c.getColumnIndex(LegacyDbContracts.FEED_TABLE.COLUMN_DESCRIPTION)); + final String feed = c.getString(c.getColumnIndex(LegacyDbContracts.FEED_TABLE.COLUMN_FEED)); + final String url = c.getString(c.getColumnIndex(LegacyDbContracts.FEED_TABLE.COLUMN_URL)); + final String imageurl = c.getString(c.getColumnIndex(LegacyDbContracts.FEED_TABLE.COLUMN_IMAGE_URL)); + final String favicon = c.getString(c.getColumnIndex(LegacyDbContracts.FEED_TABLE.COLUMN_FAVICON)); + final String timestamp = c.getString(c.getColumnIndex(LegacyDbContracts.FEED_TABLE.COLUMN_TIMESTAMP)); + final String category = c.getString(c.getColumnIndex(LegacyDbContracts.FEED_TABLE.COLUMN_CATEGORY)); + final String type = c.getString(c.getColumnIndex(LegacyDbContracts.FEED_TABLE.COLUMN_TYPE)); + final String articleurl = c.getString(c.getColumnIndex(LegacyDbContracts.FEED_TABLE.COLUMN_ARTICLE_URL)); + final String hidden = c.getString(c.getColumnIndex(LegacyDbContracts.FEED_TABLE.COLUMN_HIDDEN)); + return new LegacyFeedObject(id, title, description, feed, url, imageurl, favicon, timestamp, category, type, articleurl, "", hidden); + } + + public ArrayList selectAll() { + ArrayList feeds = null; + Cursor c = null; + try { + c = this.db.query(LegacyDbContracts.FEED_TABLE.TABLE_NAME, null, "", null, null, null, null); + if (c.moveToFirst()) { + feeds = new ArrayList<>(30); + do { + feeds.add(getFeedObject(c)); + } while (c.moveToNext()); + } + } finally { + if (c != null) { + c.close(); + } + } + return feeds; + } + + public Cursor getCursorSavedSearch() { + return this.db.query(LegacyDbContracts.SAVED_SEARCH_TABLE.TABLE_NAME, null, null, null, null, null, LegacyDbContracts.SAVED_SEARCH_TABLE._ID + " DESC"); + } + + public boolean isSaved(String id) { + boolean out = false; + Cursor c = null; + try { + c = this.db.query(LegacyDbContracts.FEED_TABLE.TABLE_NAME, null, LegacyDbContracts.FEED_TABLE._ID + "=? AND " + LegacyDbContracts.FEED_TABLE.COLUMN_FAVORITE + "!='F'", new String[]{id}, null, null, null); + out = c.moveToFirst(); + } finally { + if (c != null) { + c.close(); + } + } + return out; + } + + private static class OpenHelper extends SQLiteOpenHelper { + + private final Context appContext; + + OpenHelper(Context context) { + super(context, LegacyDbContracts.DATABASE_NAME, null, LegacyDbContracts.DATABASE_VERSION); + this.appContext = context.getApplicationContext(); + } + + private void dropTables(SQLiteDatabase db) { + db.execSQL("DROP TABLE IF EXISTS " + LegacyDbContracts.FEED_TABLE.TABLE_NAME); + db.execSQL("DROP TABLE IF EXISTS " + LegacyDbContracts.APP_TABLE.TABLE_NAME); + db.execSQL("DROP TABLE IF EXISTS " + LegacyDbContracts.HISTORY_TABLE.TABLE_NAME); + db.execSQL("DROP TABLE IF EXISTS " + LegacyDbContracts.SAVED_SEARCH_TABLE.TABLE_NAME); + } + + private void createFeedTable(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + "(" + + LegacyDbContracts.FEED_TABLE._ID + " VARCHAR(300) UNIQUE, " + + LegacyDbContracts.FEED_TABLE.COLUMN_TITLE + " VARCHAR(300), " + + LegacyDbContracts.FEED_TABLE.COLUMN_DESCRIPTION + " VARCHAR(300), " + + LegacyDbContracts.FEED_TABLE.COLUMN_FEED + " VARCHAR(300), " + + LegacyDbContracts.FEED_TABLE.COLUMN_URL + " VARCHAR(300), " + + LegacyDbContracts.FEED_TABLE.COLUMN_IMAGE_URL + " VARCHAR(300), " + + LegacyDbContracts.FEED_TABLE.COLUMN_FAVICON + " VARCHAR(300), " + + LegacyDbContracts.FEED_TABLE.COLUMN_TIMESTAMP + " VARCHAR(300), " + + LegacyDbContracts.FEED_TABLE.COLUMN_CATEGORY + " VARCHAR(300), " + + LegacyDbContracts.FEED_TABLE.COLUMN_TYPE + " VARCHAR(300), " + + LegacyDbContracts.FEED_TABLE.COLUMN_ARTICLE_URL + " VARCHAR(300), " + //+"hidden CHAR(1)" + + LegacyDbContracts.FEED_TABLE.COLUMN_HIDDEN + " CHAR(1), " + + LegacyDbContracts.FEED_TABLE.COLUMN_FAVORITE + " VARCHAR(300)" + + ")" + ); + + db.execSQL("CREATE INDEX idx_id ON " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + " (" + LegacyDbContracts.FEED_TABLE._ID + ") "); + db.execSQL("CREATE INDEX idx_idtype ON " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + " (" + LegacyDbContracts.FEED_TABLE._ID + ", " + LegacyDbContracts.FEED_TABLE.COLUMN_TYPE + ") "); + } + + private void createAppTable(SQLiteDatabase db) { + + // Generates warning/error when inline to execSQL method + String sql = "CREATE VIRTUAL TABLE " + LegacyDbContracts.APP_TABLE.TABLE_NAME + " USING FTS3 (" + + LegacyDbContracts.APP_TABLE.COLUMN_TITLE + " VARCHAR(300), " + + LegacyDbContracts.APP_TABLE.COLUMN_PACKAGE + " VARCHAR(300) " + + ")"; + + db.execSQL(sql); + } + + private void createHistoryTable(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + LegacyDbContracts.HISTORY_TABLE.TABLE_NAME + "(" + + LegacyDbContracts.HISTORY_TABLE._ID + " INTEGER PRIMARY KEY, " + + LegacyDbContracts.HISTORY_TABLE.COLUMN_TYPE + " VARCHAR(300), " + + LegacyDbContracts.HISTORY_TABLE.COLUMN_DATA + " VARCHAR(300), " + + LegacyDbContracts.HISTORY_TABLE.COLUMN_URL + " VARCHAR(300), " + + LegacyDbContracts.HISTORY_TABLE.COLUMN_EXTRA_TYPE + " VARCHAR(300), " + + LegacyDbContracts.HISTORY_TABLE.COLUMN_FEED_ID + " VARCHAR(300)" + + ")" + ); + } + + private void createSavedSearchTable(SQLiteDatabase db) { + + // Generates warning/error when inline to execSQL method + String sql = "CREATE TABLE " + LegacyDbContracts.SAVED_SEARCH_TABLE.TABLE_NAME + "(" + + LegacyDbContracts.SAVED_SEARCH_TABLE._ID + " INTEGER PRIMARY KEY, " + + LegacyDbContracts.SAVED_SEARCH_TABLE.COLUMN_TITLE + " VARCHAR(300), " + + LegacyDbContracts.SAVED_SEARCH_TABLE.COLUMN_QUERY + " VARCHAR(300) UNIQUE)"; + db.execSQL(sql); + } + + @Override + public void onCreate(SQLiteDatabase db) { + createFeedTable(db); + createAppTable(db); + createHistoryTable(db); + createSavedSearchTable(db); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (oldVersion == 4 && newVersion >= 12) { + ContentValues contentValues = new ContentValues(); + + // shape old FEED_TABLE like the new, and rename it as FEED_TABLE_old + db.execSQL("DROP INDEX IF EXISTS idx_id"); + db.execSQL("DROP INDEX IF EXISTS idx_idtype"); + db.execSQL("ALTER TABLE " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + " RENAME TO " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + "_old"); + + dropTables(db); + onCreate(db); + + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext); + + // ***** recent queries ******* + List recentQueries = LegacyUtils.loadList(sharedPreferences, "recentsearch"); + Collections.reverse(recentQueries); + for (String query : recentQueries) { + // insertRecentSearch + contentValues.clear(); + contentValues.put(LegacyDbContracts.HISTORY_TABLE.COLUMN_TYPE, "R"); + contentValues.put(LegacyDbContracts.HISTORY_TABLE.COLUMN_DATA, query); + contentValues.put(LegacyDbContracts.HISTORY_TABLE.COLUMN_URL, ""); + contentValues.put(LegacyDbContracts.HISTORY_TABLE.COLUMN_EXTRA_TYPE, ""); + contentValues.put(LegacyDbContracts.HISTORY_TABLE.COLUMN_FEED_ID, ""); + db.insert(LegacyDbContracts.HISTORY_TABLE.TABLE_NAME, null, contentValues); + } + // **************************** + + // ****** saved search ******** + Cursor c = db.query(LegacyDbContracts.FEED_TABLE.TABLE_NAME + "_old", new String[]{"url"}, LegacyDbContracts.FEED_TABLE.COLUMN_FEED + "=''", null, null, null, null); + while (c.moveToNext()) { + final String url = c.getString(0); + final String query = LegacyUtils.getQueryIfSerp(url); + if (query == null) + continue; + contentValues.clear(); + contentValues.put(LegacyDbContracts.SAVED_SEARCH_TABLE.COLUMN_QUERY, query); + db.insert(LegacyDbContracts.SAVED_SEARCH_TABLE.TABLE_NAME, null, contentValues); + } + // ***************************** + + // ***** saved feed items ***** + db.execSQL("DELETE FROM " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + "_old WHERE " + LegacyDbContracts.FEED_TABLE.COLUMN_FEED + "='' "); + db.execSQL("INSERT INTO " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + " SELECT *,'','F' FROM " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + "_old"); + db.execSQL("DROP TABLE IF EXISTS " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + "_old"); + // **************************** + + } else if (oldVersion == 12 && newVersion >= 14) { + // shape old FEED_TABLE like the new, and rename it as FEED_TABLE_old + db.execSQL("DROP INDEX IF EXISTS idx_id"); + db.execSQL("DROP INDEX IF EXISTS idx_idtype"); + db.execSQL("ALTER TABLE " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + " RENAME TO " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + "_old"); + + db.execSQL("DROP TABLE IF EXISTS " + LegacyDbContracts.FEED_TABLE.TABLE_NAME); + createFeedTable(db); + + // ***** saved feed items ***** + db.execSQL("DELETE FROM " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + "_old WHERE " + LegacyDbContracts.FEED_TABLE.COLUMN_FEED + "='' "); + db.execSQL("INSERT INTO " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + " SELECT " + + LegacyDbContracts.FEED_TABLE._ID + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_TITLE + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_DESCRIPTION + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_FEED + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_URL + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_IMAGE_URL + "," + + LegacyDbContracts.FEED_TABLE.COLUMN_FAVICON + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_TIMESTAMP + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_CATEGORY + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_TYPE + ", " + + "'' AS " + LegacyDbContracts.FEED_TABLE.COLUMN_ARTICLE_URL + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_HIDDEN + " FROM " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + "_old"); + db.execSQL("DROP TABLE IF EXISTS " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + "_old"); + // **************************** + } else if (oldVersion == 14 && newVersion >= 15) { + // shape old FEED_TABLE like the new, and rename it as FEED_TABLE_old + db.execSQL("DROP INDEX IF EXISTS idx_id"); + db.execSQL("DROP INDEX IF EXISTS idx_idtype"); + db.execSQL("ALTER TABLE " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + " RENAME TO " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + "_old"); + + db.execSQL("DROP TABLE IF EXISTS " + LegacyDbContracts.FEED_TABLE.TABLE_NAME); + createFeedTable(db); + + // ***** saved feed items ***** + db.execSQL("DELETE FROM " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + "_old WHERE " + LegacyDbContracts.FEED_TABLE.COLUMN_FEED + "='' "); + db.execSQL("INSERT INTO " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + " SELECT " + + LegacyDbContracts.FEED_TABLE._ID + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_TITLE + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_DESCRIPTION + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_FEED + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_URL + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_IMAGE_URL + "," + + LegacyDbContracts.FEED_TABLE.COLUMN_FAVICON + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_TIMESTAMP + ", " + "" + + LegacyDbContracts.FEED_TABLE.COLUMN_CATEGORY + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_TYPE + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_ARTICLE_URL + ", " + + LegacyDbContracts.FEED_TABLE.COLUMN_HIDDEN + ", " + + "'F' FROM " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + "_old"); + db.execSQL("DROP TABLE IF EXISTS " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + "_old"); + //***** set new favlue for favorite ***** + String newFavoriteValue = String.valueOf(System.currentTimeMillis()); + db.execSQL("UPDATE " + LegacyDbContracts.FEED_TABLE.TABLE_NAME + " SET " + LegacyDbContracts.FEED_TABLE.COLUMN_FAVORITE + "=" + newFavoriteValue + " WHERE " + LegacyDbContracts.FEED_TABLE.COLUMN_HIDDEN + "='F'"); + // **************************** + } else if (oldVersion == 15 && newVersion >= 16) { + db.execSQL("ALTER TABLE " + LegacyDbContracts.SAVED_SEARCH_TABLE.TABLE_NAME + " RENAME TO " + LegacyDbContracts.SAVED_SEARCH_TABLE.TABLE_NAME + "_old"); + db.execSQL("DROP TABLE IF EXISTS " + LegacyDbContracts.SAVED_SEARCH_TABLE.TABLE_NAME); + createSavedSearchTable(db); + + // Generates warning/error when inline to execSQL method + String sql = "INSERT INTO " + LegacyDbContracts.SAVED_SEARCH_TABLE.TABLE_NAME + " SELECT " + + LegacyDbContracts.SAVED_SEARCH_TABLE._ID + ", " + + LegacyDbContracts.SAVED_SEARCH_TABLE.COLUMN_QUERY + ", " + + LegacyDbContracts.SAVED_SEARCH_TABLE.COLUMN_QUERY + " FROM " + LegacyDbContracts.SAVED_SEARCH_TABLE.TABLE_NAME + "_old"; + db.execSQL(sql); + + db.execSQL("DROP TABLE IF EXISTS " + LegacyDbContracts.SAVED_SEARCH_TABLE.TABLE_NAME + "_old"); + } else { + dropTables(db); + onCreate(db); + } + } + } + + public void close() { + db.close(); + } + + public SQLiteDatabase getSQLiteDB() { + return db; + } + +} diff --git a/app/src/main/java/com/duckduckgo/app/migration/legacy/LegacyDbContracts.java b/app/src/main/java/com/duckduckgo/app/migration/legacy/LegacyDbContracts.java new file mode 100644 index 000000000000..6b7f14eeadad --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/migration/legacy/LegacyDbContracts.java @@ -0,0 +1,104 @@ +/* + * 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.legacy; + +import android.provider.BaseColumns; + +public final class LegacyDbContracts { + public static final String DATABASE_NAME = "ddg.db"; + public static final int DATABASE_VERSION = 16; + + public static final class FEED_TABLE implements BaseColumns { + public static final String TABLE_NAME = "feed"; + + // THE TITLE OF THE FEED + public static final String COLUMN_TITLE = "title"; + + // DESCRIPTION OF THE FEED + public static final String COLUMN_DESCRIPTION = "description"; + + // FEED + public static final String COLUMN_FEED = "feed"; + + // URL OF THE FEED + public static final String COLUMN_URL = "url"; + + // IMAGE URL OF THE FEED + public static final String COLUMN_IMAGE_URL = "imageurl"; + + // FAVICON OF THE FEED + public static final String COLUMN_FAVICON = "favicon"; + + // TIMESTAMP OF THE FEED + public static final String COLUMN_TIMESTAMP = "timestamp"; + + // CATEGORY OF THE FEED + public static final String COLUMN_CATEGORY = "category"; + + // TYPE OF THE FEED + public static final String COLUMN_TYPE = "type"; + + // ARTICLE URL OF THE FEED + public static final String COLUMN_ARTICLE_URL = "articleurl"; + + // WHETHER FEED IS HIDDEN + public static final String COLUMN_HIDDEN = "hidden"; + + // WHETHER FEED IS FAVORITE + public static final String COLUMN_FAVORITE = "favorite"; + + } + + public static final class APP_TABLE { + public static final String TABLE_NAME = "apps"; + + // THE TITLE OF THE APP + public static final String COLUMN_TITLE = "title"; + + // PACKAGE NAME OF THE APP + public static final String COLUMN_PACKAGE = "package"; + } + + public static final class HISTORY_TABLE implements BaseColumns { + public static final String TABLE_NAME = "history"; + + // TYPE OF THE FEED + public static final String COLUMN_TYPE = "type"; + + // URL OF THE FEED + public static final String COLUMN_URL = "url"; + + // DATA OF THE FEED + public static final String COLUMN_DATA = "data"; + + // EXTRA TYPE OF THE FEED + public static final String COLUMN_EXTRA_TYPE = "extraType"; + + // ID OF THE FEED + public static final String COLUMN_FEED_ID = "feedId"; + } + + public static final class SAVED_SEARCH_TABLE implements BaseColumns { + public static final String TABLE_NAME = "saved_search"; + + // THE TITLE OF THE SEARCH + public static final String COLUMN_TITLE = "title"; + + // SEARCH QUERY + public static final String COLUMN_QUERY = "query"; + } +} diff --git a/app/src/main/java/com/duckduckgo/app/migration/legacy/LegacyFeedObject.java b/app/src/main/java/com/duckduckgo/app/migration/legacy/LegacyFeedObject.java new file mode 100644 index 000000000000..7bdb4736badd --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/migration/legacy/LegacyFeedObject.java @@ -0,0 +1,129 @@ +/* + * 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.legacy; + +public class LegacyFeedObject { + + private final String feed; + private final String favicon; + private final String description; + private final String timestamp; + private final String url; + private final String title; + private final String id; + private final String category; + private final String imageUrl; + private final String type; + private final String articleUrl; + private final String html; + private final String hidden; + + + public LegacyFeedObject(String id, String title, String description, String feed, String url, String imageUrl, + String favicon, String timestamp, String category, String type, String articleUrl, String html, String hidden) { + this.id = id; + this.title = title; + this.description = description; + this.feed = feed; + this.url = url; + this.imageUrl = imageUrl; + this.favicon = favicon; + this.timestamp = timestamp; + this.category = category; + this.type = type; + this.articleUrl = articleUrl; + this.html = html; + this.hidden = hidden; + } + + @Override + public String toString() { + String string = "{"; + + string = string.concat("feed:" + this.feed + "\n"); + string = string.concat("favicon:" + this.favicon + "\n"); + string = string.concat("description:" + this.description + "\n"); + string = string.concat("timestamp:" + this.timestamp + "\n"); + string = string.concat("url:" + this.url + "\n"); + string = string.concat("title:" + this.title + "\n"); + string = string.concat("id:" + this.id + "\n"); + string = string.concat("category:" + this.category + "\n"); + string = string.concat("image: " + this.imageUrl + "\n"); + string = string.concat("type: " + this.type + "\n"); + string = string.concat("article_url:" + this.articleUrl + "\n"); + string = string.concat("html:" + this.html + "\n"); + string = string.concat("hidden: " + this.hidden + "}"); + + return string; + } + + public String getFeed() { + return feed; + } + + public String getFavicon() { + return favicon; + } + + public String getTimestamp() { + return timestamp; + } + + public String getDescription() { + return description; + } + + public String getUrl() { + return url; + } + + public String getTitle() { + return title; + } + + public String getId() { + return id; + } + + public String getCategory() { + return category; + } + + public String getImageUrl() { + return imageUrl; + } + + public String getType() { + return type; + } + + public String getHtml() { + return html; + } + + public String getArticleUrl() { + return articleUrl; + } + + public String getHidden() { + return hidden; + } + + public boolean hasPossibleReadability() { + return getArticleUrl().length() != 0; + } +} diff --git a/app/src/main/java/com/duckduckgo/app/migration/legacy/LegacyUtils.java b/app/src/main/java/com/duckduckgo/app/migration/legacy/LegacyUtils.java new file mode 100644 index 000000000000..3380272da6fc --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/migration/legacy/LegacyUtils.java @@ -0,0 +1,69 @@ +/* + * 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.legacy; + +import java.util.LinkedList; +import android.content.SharedPreferences; +import android.net.Uri; + +/** + * This class contains utility static methods, such as loading preferences as an array or decoding bitmaps. + */ +public final class LegacyUtils { + + public static LinkedList loadList(SharedPreferences prefs, String listName) { + int size = prefs.getInt(listName + "_size", 0); + LinkedList list = new LinkedList(); + for(int i=0;i