From ba5da43c89a1a627a28e13a37d570aaf8ab3cda6 Mon Sep 17 00:00:00 2001 From: Gouri Panda Date: Wed, 12 Apr 2023 02:40:54 +0530 Subject: [PATCH] Added room database in project, and refactored/migrated the data from objectBox to room. --- .../core/dao/RecentSearchRoomDao.kt | 98 +++++++++++++++++++ .../core/dao/entities/RecentSearchEntity.kt | 1 + .../dao/entities/RecentSearchRoomEntity.kt | 29 ++++++ .../core/data/KiwixRoomDatabase.kt | 78 +++++++++++++++ .../kiwix/kiwixmobile/core/data/Repository.kt | 8 +- .../core/di/components/CoreComponent.kt | 2 + .../core/di/modules/DatabaseModule.kt | 16 +++ .../core/search/viewmodel/SearchViewModel.kt | 12 ++- .../viewmodel/effects/DeleteRecentSearch.kt | 12 ++- .../viewmodel/effects/SaveSearchToRecents.kt | 6 +- .../search/viewmodel/SearchViewModelTest.kt | 20 ++-- 11 files changed, 262 insertions(+), 20 deletions(-) create mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/dao/RecentSearchRoomDao.kt create mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/RecentSearchRoomEntity.kt create mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/data/KiwixRoomDatabase.kt diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/RecentSearchRoomDao.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/RecentSearchRoomDao.kt new file mode 100644 index 000000000..0dd36db25 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/RecentSearchRoomDao.kt @@ -0,0 +1,98 @@ +/* + * Kiwix Android + * Copyright (c) 2023 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.dao + +import androidx.room.Dao +import androidx.room.Query +import io.objectbox.Box +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchEntity +import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchRoomEntity +import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem + +@Dao +abstract class RecentSearchRoomDao { + + @Query( + "SELECT * FROM RecentSearchRoomEntity WHERE zimId LIKE :zimId ORDER BY" + + " RecentSearchRoomEntity.id DESC" + ) + abstract fun search(zimId: String?): Flow> + + @Query( + "SELECT * FROM RecentSearchRoomEntity" + ) + abstract fun fullSearch(): Flow> + + fun recentSearches(zimId: String? = ""): Flow> { + return if (zimId != "") { + search(zimId).map { searchEntities -> + searchEntities.distinctBy(RecentSearchRoomEntity::searchTerm).take(NUM_RECENT_RESULTS) + .map { searchEntity -> + SearchListItem.RecentSearchListItem( + searchEntity.searchTerm, + searchEntity.url + ) + } + } + } else { + return fullSearch().map { searchEntities -> + searchEntities.distinctBy(RecentSearchRoomEntity::searchTerm) + .take(NUM_RECENT_RESULTS) + .map { searchEntity -> + SearchListItem.RecentSearchListItem( + searchEntity.searchTerm, + searchEntity.url + ) + } + } + } + } + + @Query( + "INSERT INTO RecentSearchRoomEntity(searchTerm, zimId, url) VALUES (:title , :zimId, :url)" + ) + abstract fun saveSearch(title: String, zimId: String, url: String?) + + @Query("DELETE FROM RecentSearchRoomEntity WHERE searchTerm=:searchTerm") + abstract fun deleteSearchString(searchTerm: String) + + @Query("DELETE FROM RecentSearchRoomEntity") + abstract fun deleteSearchHistory() + + fun migrationToRoomInsert( + box: Box + ) { + val searchRoomEntityList = box.all + searchRoomEntityList.forEachIndexed { _, recentSearchEntity -> + CoroutineScope(Dispatchers.IO).launch { + saveSearch(recentSearchEntity.searchTerm, recentSearchEntity.zimId, recentSearchEntity.url) + // Todo Should we remove object store data now? + } + } + } + + companion object { + private const val NUM_RECENT_RESULTS = 100 + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/RecentSearchEntity.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/RecentSearchEntity.kt index c767ed0a7..1ed66c1c4 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/RecentSearchEntity.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/RecentSearchEntity.kt @@ -20,6 +20,7 @@ package org.kiwix.kiwixmobile.core.dao.entities import io.objectbox.annotation.Entity import io.objectbox.annotation.Id +@Deprecated(message = "Replaced with Room") @Entity data class RecentSearchEntity( @Id var id: Long = 0L, diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/RecentSearchRoomEntity.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/RecentSearchRoomEntity.kt new file mode 100644 index 000000000..1c6c9402a --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/RecentSearchRoomEntity.kt @@ -0,0 +1,29 @@ +/* + * Kiwix Android + * Copyright (c) 2023 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.dao.entities + +import androidx.room.PrimaryKey + +@androidx.room.Entity +data class RecentSearchRoomEntity( + @PrimaryKey var id: Long = 0L, + val searchTerm: String, + val zimId: String, + val url: String? +) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/data/KiwixRoomDatabase.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/data/KiwixRoomDatabase.kt new file mode 100644 index 000000000..77c5066fe --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/data/KiwixRoomDatabase.kt @@ -0,0 +1,78 @@ +/* + * Kiwix Android + * Copyright (c) 2023 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.data + +import android.content.Context +import android.util.Log +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.sqlite.db.SupportSQLiteDatabase +import io.objectbox.BoxStore +import io.objectbox.kotlin.boxFor +import org.kiwix.kiwixmobile.core.BuildConfig +import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao +import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchRoomEntity + +@Suppress("UnnecessaryAbstractClass") +@Database(entities = [RecentSearchRoomEntity::class], version = 1) +abstract class KiwixRoomDatabase : RoomDatabase() { + abstract fun recentSearchRoomDao(): RecentSearchRoomDao + + companion object { + private var db: KiwixRoomDatabase? = null + fun getInstance(context: Context, boxStore: BoxStore): KiwixRoomDatabase { + return db ?: synchronized(KiwixRoomDatabase::class) { + return@getInstance db + ?: Room.databaseBuilder(context, KiwixRoomDatabase::class.java, "KiwixRoom.db") + // We have already database name called kiwix.db in order to avoid complexity we named as + // kiwixRoom.db + .addCallback(object : RoomDatabase.Callback() { + override fun onCreate(db: SupportSQLiteDatabase) { + super.onCreate(db) + Log.d("gouri", "onCreate") + } + + override fun onOpen(db: SupportSQLiteDatabase) { + super.onOpen(db) + Log.d("gouri", "onOpen") + } + + override fun onDestructiveMigration(db: SupportSQLiteDatabase) { + super.onDestructiveMigration(db) + Log.d("gouri", "onDestructiveMigration") + } + }) + .build().also { + if (!BuildConfig.BUILD_TYPE.contentEquals("fdroid")) { + it.migrateRecentSearch(boxStore) + } + } + } + } + + fun destroyInstance() { + db = null + } + } + + fun migrateRecentSearch(boxStore: BoxStore) { + recentSearchRoomDao().migrationToRoomInsert(boxStore.boxFor()) + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/data/Repository.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/data/Repository.kt index 179d6ed6e..99c1da369 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/data/Repository.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/data/Repository.kt @@ -27,7 +27,7 @@ import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks import org.kiwix.kiwixmobile.core.dao.NewBookDao import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao import org.kiwix.kiwixmobile.core.dao.NewNoteDao -import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao +import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao import org.kiwix.kiwixmobile.core.di.qualifiers.IO import org.kiwix.kiwixmobile.core.di.qualifiers.MainThread import org.kiwix.kiwixmobile.core.extensions.HeaderizableList @@ -57,7 +57,7 @@ class Repository @Inject internal constructor( private val historyDao: HistoryDao, private val notesDao: NewNoteDao, private val languageDao: NewLanguagesDao, - private val recentSearchDao: NewRecentSearchDao, + private val recentSearchRoomDao: RecentSearchRoomDao, private val zimReaderContainer: ZimReaderContainer ) : DataSource { @@ -101,8 +101,8 @@ class Repository @Inject internal constructor( override fun clearHistory() = Completable.fromAction { historyDao.deleteAllHistory() - recentSearchDao.deleteSearchHistory() - } + recentSearchRoomDao.deleteSearchHistory() + }.subscribeOn(io) override fun getBookmarks() = libkiwixBookmarks.bookmarks() as Flowable> diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt index 8b550a9ca..6ad8d5ef5 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt @@ -35,6 +35,7 @@ import org.kiwix.kiwixmobile.core.dao.NewBookmarksDao import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao import org.kiwix.kiwixmobile.core.dao.NewNoteDao import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao +import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao import org.kiwix.kiwixmobile.core.data.DataModule import org.kiwix.kiwixmobile.core.data.DataSource import org.kiwix.kiwixmobile.core.data.remote.KiwixService @@ -97,6 +98,7 @@ interface CoreComponent { fun wifiManager(): WifiManager fun objectBoxToLibkiwixMigrator(): ObjectBoxToLibkiwixMigrator fun libkiwixBookmarks(): LibkiwixBookmarks + fun recentSearchRoomDao(): RecentSearchRoomDao fun context(): Context fun downloader(): Downloader fun notificationManager(): NotificationManager diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt index 7de1934d4..e7ea1985f 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt @@ -31,6 +31,7 @@ import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao import org.kiwix.kiwixmobile.core.dao.NewNoteDao import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao import org.kiwix.kiwixmobile.core.dao.entities.MyObjectBox +import org.kiwix.kiwixmobile.core.data.KiwixRoomDatabase import javax.inject.Singleton @Module @@ -73,4 +74,19 @@ open class DatabaseModule { newBookDao: NewBookDao ): FetchDownloadDao = FetchDownloadDao(boxStore.boxFor(), newBookDao) + + @Singleton + @Provides + fun provideYourDatabase( + context: Context, + boxStore: BoxStore + ) = + KiwixRoomDatabase.getInstance( + context = context, + boxStore + ) // The reason we can construct a database for the repo + + @Singleton + @Provides + fun provideNewRecentSearchRoomDao(db: KiwixRoomDatabase) = db.recentSearchRoomDao() } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModel.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModel.kt index 4c3f1ea14..7bcb67e9a 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModel.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModel.kt @@ -36,7 +36,7 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.base.SideEffect -import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao +import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ActivityResultReceived @@ -67,7 +67,7 @@ import javax.inject.Inject @OptIn(ExperimentalCoroutinesApi::class) class SearchViewModel @Inject constructor( - private val recentSearchDao: NewRecentSearchDao, + private val recentSearchDao: RecentSearchRoomDao, private val zimReaderContainer: ZimReaderContainer, private val searchResultGenerator: SearchResultGenerator ) : ViewModel() { @@ -152,7 +152,13 @@ class SearchViewModel @Inject constructor( } private fun deleteItemAndShowToast(it: ConfirmedDelete) { - _effects.trySend(DeleteRecentSearch(it.searchListItem, recentSearchDao)).isSuccess + _effects.trySend( + DeleteRecentSearch( + it.searchListItem, + recentSearchDao, + viewModelScope + ) + ).isSuccess _effects.trySend(ShowToast(R.string.delete_specific_search_toast)).isSuccess } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/effects/DeleteRecentSearch.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/effects/DeleteRecentSearch.kt index 694cd66f0..83afdc5db 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/effects/DeleteRecentSearch.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/effects/DeleteRecentSearch.kt @@ -19,15 +19,21 @@ package org.kiwix.kiwixmobile.core.search.viewmodel.effects import androidx.appcompat.app.AppCompatActivity +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.kiwix.kiwixmobile.core.base.SideEffect -import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao +import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem data class DeleteRecentSearch( private val searchListItem: SearchListItem, - private val recentSearchDao: NewRecentSearchDao + private val recentSearchRoomDao: RecentSearchRoomDao, + private val viewModelScope: CoroutineScope ) : SideEffect { override fun invokeWith(activity: AppCompatActivity) { - recentSearchDao.deleteSearchString(searchListItem.value) + viewModelScope.launch(Dispatchers.IO) { + recentSearchRoomDao.deleteSearchString(searchListItem.value) + } } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/effects/SaveSearchToRecents.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/effects/SaveSearchToRecents.kt index 724f114ba..f31903b98 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/effects/SaveSearchToRecents.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/effects/SaveSearchToRecents.kt @@ -23,12 +23,12 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.kiwix.kiwixmobile.core.base.SideEffect -import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao +import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao import org.kiwix.kiwixmobile.core.reader.addContentPrefix import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem data class SaveSearchToRecents( - private val recentSearchDao: NewRecentSearchDao, + private val recentSearchRoomDao: RecentSearchRoomDao, private val searchListItem: SearchListItem, private val id: String?, private val viewModelScope: CoroutineScope @@ -36,7 +36,7 @@ data class SaveSearchToRecents( override fun invokeWith(activity: AppCompatActivity) { id?.let { viewModelScope.launch(Dispatchers.IO) { - recentSearchDao.saveSearch( + recentSearchRoomDao.saveSearch( searchListItem.value, it, searchListItem.url?.addContentPrefix diff --git a/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModelTest.kt b/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModelTest.kt index f8f3dea19..0ed0c68fb 100644 --- a/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModelTest.kt +++ b/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModelTest.kt @@ -47,7 +47,7 @@ import org.junit.jupiter.api.Test import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R.string import org.kiwix.kiwixmobile.core.base.SideEffect -import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao +import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao import org.kiwix.kiwixmobile.core.reader.ZimFileReader import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem.RecentSearchListItem @@ -79,7 +79,7 @@ import org.kiwix.libzim.SuggestionSearch @OptIn(ExperimentalCoroutinesApi::class) internal class SearchViewModelTest { - private val recentSearchDao: NewRecentSearchDao = mockk() + private val recentSearchRoomDao: RecentSearchRoomDao = mockk() private val zimReaderContainer: ZimReaderContainer = mockk() private val searchResultGenerator: SearchResultGenerator = mockk() private val zimFileReader: ZimFileReader = mockk() @@ -105,8 +105,8 @@ internal class SearchViewModelTest { searchResultGenerator.generateSearchResults("", zimFileReader) } returns null every { zimReaderContainer.id } returns "id" - every { recentSearchDao.recentSearches("id") } returns recentsFromDb.consumeAsFlow() - viewModel = SearchViewModel(recentSearchDao, zimReaderContainer, searchResultGenerator) + every { recentSearchRoomDao.recentSearches("id") } returns recentsFromDb.consumeAsFlow() + viewModel = SearchViewModel(recentSearchRoomDao, zimReaderContainer, searchResultGenerator) } @Nested @@ -158,7 +158,10 @@ internal class SearchViewModelTest { val searchListItem = RecentSearchListItem("", "") actionResultsInEffects( OnItemClick(searchListItem), - SaveSearchToRecents(recentSearchDao, searchListItem, "id", viewModel.viewModelScope), + SaveSearchToRecents( + recentSearchRoomDao, searchListItem, "id", + viewModel.viewModelScope + ), OpenSearchItem(searchListItem, false) ) } @@ -168,7 +171,10 @@ internal class SearchViewModelTest { val searchListItem = RecentSearchListItem("", "") actionResultsInEffects( OnOpenInNewTabClick(searchListItem), - SaveSearchToRecents(recentSearchDao, searchListItem, "id", viewModel.viewModelScope), + SaveSearchToRecents( + recentSearchRoomDao, searchListItem, "id", + viewModel.viewModelScope + ), OpenSearchItem(searchListItem, true) ) } @@ -192,7 +198,7 @@ internal class SearchViewModelTest { val searchListItem = RecentSearchListItem("", "") actionResultsInEffects( ConfirmedDelete(searchListItem), - DeleteRecentSearch(searchListItem, recentSearchDao), + DeleteRecentSearch(searchListItem, recentSearchRoomDao, viewModel.viewModelScope), ShowToast(R.string.delete_specific_search_toast) ) }