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 #3474

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d02b202
Created ObjectBox to libkiwix migrator for Bookmarks
MohitMaliDeveloper Aug 25, 2023
7bc769a
Created LibkiwixBooks to store and retrieve bookmarks
MohitMaliDeveloper Aug 29, 2023
67601d3
Storing Bookmarks via libkiwix instead of Objectbox
MohitMaliDeveloper Aug 30, 2023
7f82ed6
Created saved deleted feature and implemented the functionality, crea…
MohitMaliDeveloper Sep 1, 2023
ea422b5
Fixed ObjectBoxToLibkiwixMigrator implementation not found
MohitMaliDeveloper Sep 4, 2023
11e254f
Implemented Save/Delete functionality with libkiwix
MohitMaliDeveloper Sep 4, 2023
4e6f4eb
Refactored the code to display saved bookmarks in the bookmarks scree…
MohitMaliDeveloper Sep 4, 2023
cfd9b82
Save bookmark when it does not exist in the file since bookmarks is n…
MohitMaliDeveloper Sep 4, 2023
8f6e916
Improved url loading for current book
MohitMaliDeveloper Sep 4, 2023
81c056a
Fixed bookmark not redable from file if there is no book added to the…
MohitMaliDeveloper Sep 5, 2023
a7c4fad
Improved saving bookmark, add book to the libkiwix library if not alr…
MohitMaliDeveloper Sep 5, 2023
f0aebc1
Fixed all bookmarks automatically selected if we select only one book…
MohitMaliDeveloper Sep 5, 2023
177d1f8
Migrating bookmarks from objectbox to libkiwix
MohitMaliDeveloper Sep 5, 2023
ca80d0e
Improved seleted items method for avoid duplicacy
MohitMaliDeveloper Sep 5, 2023
38bc849
Fixed detekt errors
MohitMaliDeveloper Sep 5, 2023
8f65df0
Fixed after `delete/save` bookmark list is not updating.
MohitMaliDeveloper Sep 6, 2023
c0d28ce
Refactored unit coverage test cases for testing bookmarks with `Libki…
MohitMaliDeveloper Sep 6, 2023
331081b
Fixed application crash while running application on the emulators.
MohitMaliDeveloper Sep 6, 2023
f43a6a8
Improved LibkiwixBookTest and added new ObjectBoxToLibkiwixMigratorTe…
MohitMaliDeveloper Sep 7, 2023
967e2df
Introducing the `writeFile` method of the Library class allows us to …
Sep 16, 2023
edbdeff
Removed the unnecessary wrapper classes of libkiwix from the codebase…
MohitMaliDeveloper Sep 18, 2023
b1d4d8b
Resolved bookmark saving issue, which causes the bug when we try to r…
MohitMaliDeveloper Sep 28, 2023
1ccdd76
Fixed, compilation error
MohitMaliDeveloper Nov 3, 2023
831d91d
The writing of the library to a file when retrieving bookmarks for th…
MohitMaliDeveloper Nov 3, 2023
f8ddf68
Added logs while reading reading the bookmarks/library data, and adde…
MohitMaliDeveloper Nov 7, 2023
31ece90
Fixed path and improved the logs
MohitMaliDeveloper Nov 7, 2023
ad6e043
Instead of creating an archive object for every book and retrieving t…
MohitMaliDeveloper Nov 10, 2023
4b55bc3
Removed unnecessary comments
MohitMaliDeveloper Nov 10, 2023
35830aa
Storing bookmarks/library inside our app-specific directory
MohitMaliDeveloper Nov 10, 2023
8c46646
Upgrading libkiwix to 1.0.1
MohitMaliDeveloper Nov 16, 2023
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,155 @@
/*
* Kiwix Android
* Copyright (c) 2023 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.test.ext.junit.runners.AndroidJUnit4
import io.mockk.mockk
import io.objectbox.Box
import io.objectbox.BoxStore
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.jupiter.api.Test
import org.junit.runner.RunWith
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.dao.entities.BookmarkEntity
import org.kiwix.kiwixmobile.core.data.remote.ObjectBoxToLibkiwixMigrator
import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.libkiwix.Book

@RunWith(AndroidJUnit4::class)
class ObjectBoxToLibkiwixMigratorTest {
private var boxStore: BoxStore = mockk()
private var libkiwixBookmarks: LibkiwixBookmarks = mockk(relaxed = true)
private lateinit var objectBoxToLibkiwixMigrator: ObjectBoxToLibkiwixMigrator

@Test
fun migrateBookmarkTest(): Unit = runBlocking {
val sharedPreferenceUtil: SharedPreferenceUtil = mockk(relaxed = true)
objectBoxToLibkiwixMigrator = ObjectBoxToLibkiwixMigrator()
objectBoxToLibkiwixMigrator.sharedPreferenceUtil = sharedPreferenceUtil
objectBoxToLibkiwixMigrator.boxStore = boxStore
objectBoxToLibkiwixMigrator.libkiwixBookmarks = libkiwixBookmarks
val box = boxStore.boxFor(BookmarkEntity::class.java)
val expectedZimName = "Alpine_Linux"
val expectedZimId = "8812214350305159407L"
val expectedZimFilePath = "data/Android/kiwix/alpine_linux_2022.zim"
val expectedTitle = "Installing"
val expectedBookmarkUrl = "https://alpine_linux/InstallingPage"
val expectedFavicon = ""
val bookmarkEntity = BookmarkEntity(
0,
expectedZimId,
expectedZimName,
expectedZimFilePath,
expectedBookmarkUrl,
expectedTitle,
expectedFavicon
)
box.put(bookmarkEntity)
// migrate data into room database
objectBoxToLibkiwixMigrator.migrateBookMarks(box)
// check if data successfully migrated to room
val actual = libkiwixBookmarks.bookmarks().blockingFirst()
assertEquals(actual.size, 1)
assertEquals(actual[0].zimFilePath, expectedZimFilePath)
assertEquals(actual[0].zimId, expectedZimId)
assertEquals(actual[0].favicon, expectedFavicon)
assertEquals(actual[0].title, expectedTitle)
assertEquals(actual[0].url, expectedBookmarkUrl)

// clear both databases for recent searches to test more edge cases
clearBookmarks(box, libkiwixBookmarks)
// Migrate data from empty ObjectBox database
objectBoxToLibkiwixMigrator.migrateBookMarks(box)
val actualData = libkiwixBookmarks.bookmarks().blockingFirst()
assertTrue(actualData.isEmpty())

// 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 = mockk(relaxed = true)
libkiwixBookmarks.saveBookmark(LibkiwixBookmarkItem(secondBookmarkEntity, libkiwixBook))
box.put(bookmarkEntity)
// Migrate data into Room database
objectBoxToLibkiwixMigrator.migrateBookMarks(box)
val actualDataAfterMigration = libkiwixBookmarks.bookmarks().blockingFirst()
assertEquals(2, actual.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)

clearBookmarks(box, libkiwixBookmarks)

// Test large data migration for recent searches
val numEntities = 10000
// Insert a large number of recent search entities into ObjectBox
for (i in 1..numEntities) {
val bookMarkUrl = "https://alpine_linux/search_$i"
val title = "title_$i"
val bookmarkEntity1 = BookmarkEntity(
0,
expectedZimId,
expectedZimName,
expectedZimFilePath,
bookMarkUrl,
title,
expectedFavicon
)
box.put(bookmarkEntity1)
}
val startTime = System.currentTimeMillis()
// Migrate data into Room database
objectBoxToLibkiwixMigrator.migrateBookMarks(box)
val endTime = System.currentTimeMillis()
val migrationTime = endTime - startTime
// Check if data successfully migrated to Room
val actualDataAfterLargeMigration =
libkiwixBookmarks.bookmarks().blockingFirst()
assertEquals(numEntities, actualDataAfterLargeMigration.size)
// Assert that the migration completes within a reasonable time frame
assertTrue("Migration took too long: $migrationTime ms", migrationTime < 5000)
}

private fun clearBookmarks(box: Box<BookmarkEntity>, libkiwixBookmark: LibkiwixBookmarks) {
// delete bookmarks for testing other edge cases
libkiwixBookmark.deleteBookmarks(
libkiwixBookmark.bookmarks().blockingFirst() as List<LibkiwixBookmarkItem>
)
box.removeAll()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,29 @@

package org.kiwix.kiwixmobile.page.bookmarks

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers.withText
import applyWithViewHierarchyPrinting
import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions.assertDisplayed
import com.adevinta.android.barista.interaction.BaristaSleepInteractions
import org.kiwix.kiwixmobile.BaseRobot
import org.kiwix.kiwixmobile.Findable.StringId.ContentDesc
import org.kiwix.kiwixmobile.Findable.StringId.TextId
import org.kiwix.kiwixmobile.Findable.Text
import org.kiwix.kiwixmobile.Findable.ViewId
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.testutils.TestUtils
import java.util.concurrent.TimeUnit

fun bookmarks(func: BookmarksRobot.() -> Unit) =
BookmarksRobot().applyWithViewHierarchyPrinting(func)

class BookmarksRobot : BaseRobot() {

private var retryCountForBookmarkAddedButton = 5

fun assertBookMarksDisplayed() {
assertDisplayed(R.string.bookmarks_from_current_book)
}
Expand All @@ -41,4 +52,45 @@ class BookmarksRobot : BaseRobot() {
fun assertDeleteBookmarksDialogDisplayed() {
isVisible(TextId(R.string.delete_bookmarks))
}

fun clickOnSaveBookmarkImage() {
pauseForBetterTestPerformance()
clickOn(ViewId(R.id.bottom_toolbar_bookmark))
}

fun longClickOnSaveBookmarkImage() {
// wait for disappearing the snack-bar after removing the bookmark
BaristaSleepInteractions.sleep(5L, TimeUnit.SECONDS)
longClickOn(ViewId(R.id.bottom_toolbar_bookmark))
}

fun clickOnOpenSavedBookmarkButton() {
try {
onView(withText("OPEN")).perform(click())
} catch (runtimeException: RuntimeException) {
if (retryCountForBookmarkAddedButton > 0) {
retryCountForBookmarkAddedButton--
clickOnOpenSavedBookmarkButton()
} else {
throw RuntimeException(
"Unable to save the bookmark, original exception is" +
" ${runtimeException.localizedMessage}"
)
}
}
}

fun assertBookmarkSaved() {
pauseForBetterTestPerformance()
isVisible(Text("Test Zim"))
}

fun assertBookmarkRemoved() {
pauseForBetterTestPerformance()
onView(withText("Test Zim")).check(ViewAssertions.doesNotExist())
}

private fun pauseForBetterTestPerformance() {
BaristaSleepInteractions.sleep(TestUtils.TEST_PAUSE_MS_FOR_SEARCH_TEST.toLong())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Kiwix Android
* Copyright (c) 2023 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.page.bookmarks

import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceManager
import androidx.test.core.app.ActivityScenario
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.kiwix.kiwixmobile.BaseActivityTest
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.main.KiwixMainActivity
import org.kiwix.kiwixmobile.nav.destination.library.LocalLibraryFragmentDirections
import org.kiwix.kiwixmobile.search.SearchFragmentTest
import org.kiwix.kiwixmobile.testutils.RetryRule
import org.kiwix.kiwixmobile.testutils.TestUtils
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream

class LibkiwixBookmarkTest : BaseActivityTest() {

@Rule
@JvmField
var retryRule = RetryRule()

private lateinit var kiwixMainActivity: KiwixMainActivity

@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_WIFI_ONLY, false)
putBoolean(SharedPreferenceUtil.PREF_IS_TEST, true)
}
activityScenario = ActivityScenario.launch(KiwixMainActivity::class.java).apply {
moveToState(Lifecycle.State.RESUMED)
}
}

@Test
fun testBookmarks() {
activityScenario.onActivity {
kiwixMainActivity = it
kiwixMainActivity.navigate(R.id.libraryFragment)
}
val loadFileStream =
SearchFragmentTest::class.java.classLoader.getResourceAsStream("testzim.zim")
val 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)
}
}
}

UiThreadStatement.runOnUiThread {
kiwixMainActivity.navigate(
LocalLibraryFragmentDirections.actionNavigationLibraryToNavigationReader()
.apply { zimFileUri = zimFile.toUri().toString() }
)
}
bookmarks {
clickOnSaveBookmarkImage()
clickOnOpenSavedBookmarkButton()
assertBookmarkSaved()
pressBack()
clickOnSaveBookmarkImage()
longClickOnSaveBookmarkImage()
assertBookmarkRemoved()
}
}
}
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ object Versions {

const val core_ktx: String = "1.3.2"

const val libkiwix: String = "1.0.0"
const val libkiwix: String = "1.0.1"

const val material: String = "1.2.1"

Expand Down
1 change: 1 addition & 0 deletions core/detekt_baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
<ID>ReturnCount:ToolbarScrollingKiwixWebView.kt$ToolbarScrollingKiwixWebView$@SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean</ID>
<ID>TooGenericExceptionCaught:CompatFindActionModeCallback.kt$CompatFindActionModeCallback$exception: Exception</ID>
<ID>TooGenericExceptionCaught:JNIInitialiser.kt$JNIInitialiser$e: Exception</ID>
<ID>TooGenericExceptionCaught:LibkiwixBookmarks.kt$LibkiwixBookmarks$exception: Exception</ID>
<ID>TooGenericExceptionCaught:OnSwipeTouchListener.kt$OnSwipeTouchListener.GestureListener$exception: Exception</ID>
<ID>TooGenericExceptionCaught:ZimFileReader.kt$ZimFileReader$exception: Exception</ID>
<ID>TooGenericExceptionThrown:AdapterDelegateManager.kt$AdapterDelegateManager$throw RuntimeException("No delegate registered for $item")</ID>
Expand Down
Loading