Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use libkiwix to store bookmarks #3653

Merged
merged 38 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4fe6d11
Created ObjectBox to libkiwix migrator for Bookmarks.
MohitMaliDeveloper Aug 25, 2023
da3241b
Created LibkiwixBooks to store and retrieve bookmarks
MohitMaliDeveloper Aug 29, 2023
9855624
Storing Bookmarks via libkiwix instead of Objectbox
MohitMaliDeveloper Aug 30, 2023
45531af
Created saved deleted feature and implemented the functionality, crea…
MohitMaliDeveloper Sep 1, 2023
627851a
Fixed ObjectBoxToLibkiwixMigrator implementation not found
MohitMaliDeveloper Sep 4, 2023
de900fd
Implemented Save/Delete functionality with libkiwix.
MohitMaliDeveloper Sep 4, 2023
88e30e1
Refactored the code to display saved bookmarks in the bookmarks scree…
MohitMaliDeveloper Sep 4, 2023
d68c890
Save bookmark when it does not exist in the file since bookmarks is n…
MohitMaliDeveloper Sep 4, 2023
c384849
Improved url loading for current book
MohitMaliDeveloper Sep 4, 2023
7a375e4
Fixed bookmark not redable from file if there is no book added to the…
MohitMaliDeveloper Sep 5, 2023
9d69aeb
Improved saving bookmark, add book to the libkiwix library if not alr…
MohitMaliDeveloper Sep 5, 2023
0c750fc
Fixed all bookmarks automatically selected if we select only one book…
MohitMaliDeveloper Sep 5, 2023
e85b717
Migrating bookmarks from objectbox to libkiwix
MohitMaliDeveloper Sep 5, 2023
b2c1c1e
Improved seleted items method for avoid duplicacy
MohitMaliDeveloper Sep 5, 2023
7c23207
Fixed detekt errors
MohitMaliDeveloper Sep 5, 2023
19085d6
Fixed after `delete/save` bookmark list is not updating.
MohitMaliDeveloper Sep 6, 2023
429502a
Refactored unit coverage test cases for testing bookmarks with `Libki…
MohitMaliDeveloper Sep 6, 2023
3d7a4af
Fixed application crash while running application on the emulators.
MohitMaliDeveloper Sep 6, 2023
07e40ee
Improved LibkiwixBookTest and added new ObjectBoxToLibkiwixMigratorTe…
MohitMaliDeveloper Sep 7, 2023
94a529c
Introducing the `writeFile` method of the Library class allows us to …
Sep 16, 2023
a78c456
Removed the unnecessary wrapper classes of libkiwix from the codebase…
MohitMaliDeveloper Sep 18, 2023
a3af901
Resolved bookmark saving issue, which causes the bug when we try to r…
MohitMaliDeveloper Sep 28, 2023
1f3cfd6
Fixed, compilation error
MohitMaliDeveloper Nov 3, 2023
6c5afa9
The writing of the library to a file when retrieving bookmarks for th…
MohitMaliDeveloper Nov 3, 2023
aaa9cc3
Added logs while reading reading the bookmarks/library data, and adde…
MohitMaliDeveloper Nov 7, 2023
d821d5a
Fixed path and improved the logs
MohitMaliDeveloper Nov 7, 2023
0d48e23
Instead of creating an archive object for every book and retrieving t…
MohitMaliDeveloper Nov 10, 2023
ecc0d4b
Removed unnecessary comments
MohitMaliDeveloper Nov 10, 2023
8666675
Storing bookmarks/library inside our app-specific directory
MohitMaliDeveloper Nov 10, 2023
60966b9
Getting zimFilePath efficiently for `LibkiwixBookmarkItem`.
MohitMaliDeveloper Nov 16, 2023
e866dd2
Writing bookmarks and library data to a file in the background to avo…
Jan 4, 2024
f45e321
Refactored the test cases.
Jan 5, 2024
efd8caf
Enhanced the migration process to handle exceptions more effectively.
Jan 9, 2024
99cf75b
Improved the large data migration.
Jan 10, 2024
50f92be
Fixed the failing LibkiwixBookmarkTest.
Jan 11, 2024
ed2a9e0
Fixed the failing `LibkiwixBookmarkTest` on Android 33 and 30.
MohitMaliDeveloper Jan 15, 2024
1460e0d
Fixed Migration test was failing on the API level 24.
MohitMaliDeveloper Jan 16, 2024
8199480
Upgraded java-libkiwix to 2.0.0
MohitMaliDeveloper Feb 6, 2024
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,328 @@
/*
* Kiwix Android
* Copyright (c) 2024 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/

package org.kiwix.kiwixmobile

import androidx.core.content.edit
import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceManager
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.IdlingPolicies
import androidx.test.espresso.IdlingRegistry
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import io.objectbox.Box
import io.objectbox.BoxStore
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.junit.After
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.dao.entities.BookmarkEntity
import org.kiwix.kiwixmobile.core.data.remote.ObjectBoxToLibkiwixMigrator
import org.kiwix.kiwixmobile.core.di.modules.DatabaseModule
import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.main.KiwixMainActivity
import org.kiwix.kiwixmobile.testutils.RetryRule
import org.kiwix.kiwixmobile.testutils.TestUtils
import org.kiwix.kiwixmobile.utils.KiwixIdlingResource
import org.kiwix.libkiwix.Book
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
import java.util.concurrent.TimeUnit

class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() {
private val objectBoxToLibkiwixMigrator = ObjectBoxToLibkiwixMigrator()

// take the existing boxStore object
private val boxStore: BoxStore? = DatabaseModule.boxStore
private lateinit var zimFile: File
private lateinit var box: Box<BookmarkEntity>
private val expectedZimName = "Alpine_Linux"
private val expectedZimId = "60094d1e-1c9a-a60b-2011-4fb02f8db6c3"
private val expectedZimFilePath: String by lazy { zimFile.canonicalPath }
private val expectedTitle = "Installing"
private val expectedBookmarkUrl = "https://alpine_linux/InstallingPage"
private val expectedFavicon = ""
private val bookmarkEntity: BookmarkEntity by lazy {
BookmarkEntity(
0,
expectedZimId,
expectedZimName,
expectedZimFilePath,
expectedBookmarkUrl,
expectedTitle,
expectedFavicon
)
}

@Rule
@JvmField
var retryRule = RetryRule()

@Before
override fun waitForIdle() {
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).apply {
if (TestUtils.isSystemUINotRespondingDialogVisible(this)) {
TestUtils.closeSystemDialogs(context)
}
waitForIdle()
}
PreferenceManager.getDefaultSharedPreferences(context).edit {
putBoolean(SharedPreferenceUtil.PREF_SHOW_INTRO, false)
putBoolean(SharedPreferenceUtil.PREF_IS_TEST, true)
putBoolean(SharedPreferenceUtil.IS_PLAY_STORE_BUILD, true)
putBoolean(SharedPreferenceUtil.PREF_PLAY_STORE_RESTRICTION, false)
}
activityScenario = ActivityScenario.launch(KiwixMainActivity::class.java).apply {
moveToState(Lifecycle.State.RESUMED)
onActivity {
it.navigate(R.id.libraryFragment)
}
}
CoreApp.coreComponent.inject(objectBoxToLibkiwixMigrator)
setUpObjectBoxAndData()
}

private fun setUpObjectBoxAndData() {
if (boxStore == null) {
throw RuntimeException(
"BoxStore is not available for testing," +
" check is your application running"
)
}
box = boxStore.boxFor(BookmarkEntity::class.java)

// add a file in fileSystem because we need to actual file path for making object of Archive.
val loadFileStream =
ObjectBoxToLibkiwixMigratorTest::class.java.classLoader.getResourceAsStream("testzim.zim")
zimFile = File(context.cacheDir, "testzim.zim")
if (zimFile.exists()) zimFile.delete()
zimFile.createNewFile()
loadFileStream.use { inputStream ->
val outputStream: OutputStream = FileOutputStream(zimFile)
outputStream.use { it ->
val buffer = ByteArray(inputStream.available())
var length: Int
while (inputStream.read(buffer).also { length = it } > 0) {
it.write(buffer, 0, length)
}
}
}

// clear the data before running the test case
clearBookmarks()
}

@Test
fun testSingleDataMigration(): Unit = runBlocking {
val expectedZimName = "Alpine_Linux"
val expectedZimId = "60094d1e-1c9a-a60b-2011-4fb02f8db6c3"
val expectedZimFilePath = zimFile.canonicalPath
val expectedTitle = "Installing"
val expectedBookmarkUrl = "https://alpine_linux/InstallingPage"
val expectedFavicon = ""
val bookmarkEntity = BookmarkEntity(
0,
expectedZimId,
expectedZimName,
expectedZimFilePath,
expectedBookmarkUrl,
expectedTitle,
expectedFavicon
)
box.put(bookmarkEntity)
withContext(Dispatchers.IO) {
// migrate data into room database
objectBoxToLibkiwixMigrator.migrateBookMarks(box)
}
// check if data successfully migrated to room
objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ actualDataAfterMigration ->
assertEquals(1, actualDataAfterMigration.size)
assertEquals(actualDataAfterMigration[0].zimFilePath, expectedZimFilePath)
assertEquals(actualDataAfterMigration[0].zimId, expectedZimId)
assertEquals(actualDataAfterMigration[0].title, expectedTitle)
assertEquals(actualDataAfterMigration[0].url, expectedBookmarkUrl)
},
{
throw RuntimeException(
"Exception occurred during migration. Original Exception ${it.printStackTrace()}"
)
}
)
}

@Test
fun testMigrationWithEmptyData(): Unit = runBlocking {
withContext(Dispatchers.IO) {
// Migrate data from empty ObjectBox database
objectBoxToLibkiwixMigrator.migrateBookMarks(box)
}
objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ actualDataAfterMigration ->
assertTrue(actualDataAfterMigration.isEmpty())
},
{
throw RuntimeException(
"Exception occurred during migration. Original Exception ${it.printStackTrace()}"
)
}
)
}

@Test
fun testMigrationWithExistingData(): Unit = runBlocking {
// Test if data successfully migrated to Room and existing data is preserved
val existingTitle = "Home Page"
val existingBookmarkUrl = "https://alpine_linux/HomePage"
val secondBookmarkEntity = BookmarkEntity(
0,
expectedZimId,
expectedZimName,
expectedZimFilePath,
existingBookmarkUrl,
existingTitle,
expectedFavicon
)
val libkiwixBook = Book()
withContext(Dispatchers.IO) {
objectBoxToLibkiwixMigrator.libkiwixBookmarks.saveBookmark(
LibkiwixBookmarkItem(
secondBookmarkEntity,
libkiwixBook
)
)
box.put(bookmarkEntity)
}
withContext(Dispatchers.IO) {
// Migrate data into Room database
objectBoxToLibkiwixMigrator.migrateBookMarks(box)
}
objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ actualDataAfterMigration ->
assertEquals(2, actualDataAfterMigration.size)
val existingItem =
actualDataAfterMigration.find {
it.url == existingBookmarkUrl && it.title == existingTitle
}
assertNotNull(existingItem)
val newItem =
actualDataAfterMigration.find {
it.url == expectedBookmarkUrl && it.title == expectedTitle
}
assertNotNull(newItem)
},
{
throw RuntimeException(
"Exception occurred during migration. Original Exception ${it.printStackTrace()}"
)
}
)
}

@Test
fun testLargeDataMigration(): Unit = runBlocking {
// Test large data migration for recent searches
val numEntities = 10000
// Insert a large number of recent search entities into ObjectBox
(1..numEntities)
.asSequence()
.map {
BookmarkEntity(
0,
expectedZimId,
expectedZimName,
expectedZimFilePath,
"https://alpine_linux/search_$it",
"title_$it",
expectedFavicon
)
}
.forEach(box::put)
withContext(Dispatchers.IO) {
// Migrate data into Room database
objectBoxToLibkiwixMigrator.migrateBookMarks(box)
}
// Check if data successfully migrated to Room
objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ actualDataAfterMigration ->
assertEquals(numEntities, actualDataAfterMigration.size)
// Clear the bookmarks list from device to not affect the other test cases.
clearBookmarks()
},
{
// Clear the bookmarks list from device to not affect the other test cases.
clearBookmarks()
throw RuntimeException(
"Exception occurred during migration. Original Exception ${it.printStackTrace()}"
)
}
)
}

private fun clearBookmarks() {
// delete bookmarks for testing other edge cases
objectBoxToLibkiwixMigrator.libkiwixBookmarks.deleteBookmarks(
objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks()
.blockingFirst() as List<LibkiwixBookmarkItem>
)
box.removeAll()
}

@After
fun finish() {
IdlingRegistry.getInstance().unregister(KiwixIdlingResource.getInstance())
PreferenceManager.getDefaultSharedPreferences(context).edit {
putBoolean(SharedPreferenceUtil.PREF_PLAY_STORE_RESTRICTION, true)
}
}

companion object {

@BeforeClass
fun beforeClass() {
IdlingPolicies.setMasterPolicyTimeout(180, TimeUnit.SECONDS)
IdlingPolicies.setIdlingResourceTimeout(180, TimeUnit.SECONDS)
IdlingRegistry.getInstance().register(KiwixIdlingResource.getInstance())
}
}
}
Loading
Loading