Added migration test cases for migrating the objectBox data to libkiwix.

* Fixed: `ImportBookmarkTest` which was failing on CI.
* Refactored UI and unit test cases according to the new codebase.
* Fixed: Selecting a ZIM file would select all ZIM files displayed on the Local Library screen.
* Fixed: `network result & language db results activate a combined network + db result`, `library marks files over 4GB as can't download if file system state says to` and `books found on filesystem are filtered by books already in db`, which sometimes fails on the CI.
This commit is contained in:
MohitMaliFtechiz 2025-06-12 00:18:50 +05:30
parent 42da475867
commit c2d35e17d4
11 changed files with 268 additions and 88 deletions

View File

@ -39,9 +39,11 @@ import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.kiwix.kiwixmobile.core.CoreApp import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.dao.entities.BookOnDiskEntity
import org.kiwix.kiwixmobile.core.dao.entities.BookmarkEntity import org.kiwix.kiwixmobile.core.dao.entities.BookmarkEntity
import org.kiwix.kiwixmobile.core.data.remote.ObjectBoxToLibkiwixMigrator import org.kiwix.kiwixmobile.core.data.remote.ObjectBoxToLibkiwixMigrator
import org.kiwix.kiwixmobile.core.di.modules.DatabaseModule import org.kiwix.kiwixmobile.core.di.modules.DatabaseModule
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
import org.kiwix.kiwixmobile.core.utils.LanguageUtils.Companion.handleLocaleChange import org.kiwix.kiwixmobile.core.utils.LanguageUtils.Companion.handleLocaleChange
@ -51,6 +53,7 @@ import org.kiwix.kiwixmobile.testutils.RetryRule
import org.kiwix.kiwixmobile.testutils.TestUtils import org.kiwix.kiwixmobile.testutils.TestUtils
import org.kiwix.kiwixmobile.utils.KiwixIdlingResource import org.kiwix.kiwixmobile.utils.KiwixIdlingResource
import org.kiwix.libkiwix.Book import org.kiwix.libkiwix.Book
import org.kiwix.libzim.Archive
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.OutputStream import java.io.OutputStream
@ -62,7 +65,8 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() {
// take the existing boxStore object // take the existing boxStore object
private var boxStore: BoxStore? = null private var boxStore: BoxStore? = null
private lateinit var zimFile: File private lateinit var zimFile: File
private lateinit var box: Box<BookmarkEntity> private lateinit var bookOnDiskBox: Box<BookOnDiskEntity>
private lateinit var bookmarkBox: Box<BookmarkEntity>
private val expectedZimName = "Alpine_Linux" private val expectedZimName = "Alpine_Linux"
private val expectedZimId = "60094d1e-1c9a-a60b-2011-4fb02f8db6c3" private val expectedZimId = "60094d1e-1c9a-a60b-2011-4fb02f8db6c3"
private val expectedZimFilePath: String by lazy { zimFile.canonicalPath } private val expectedZimFilePath: String by lazy { zimFile.canonicalPath }
@ -82,6 +86,27 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() {
) )
} }
private val bookOnDiskEntity: BookOnDiskEntity by lazy {
BookOnDiskEntity(
id = 0,
file = zimFile,
zimReaderSource = ZimReaderSource(zimFile),
bookId = expectedZimId,
title = "",
description = "",
language = "",
creator = "",
publisher = "",
date = "",
url = "",
articleCount = "",
mediaCount = "",
size = "",
name = expectedZimName,
favIcon = ""
)
}
@Rule @Rule
@JvmField @JvmField
var retryRule = RetryRule() var retryRule = RetryRule()
@ -128,18 +153,26 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() {
" check is your application running" " check is your application running"
) )
} }
box = boxStore!!.boxFor(BookmarkEntity::class.java) bookmarkBox = boxStore!!.boxFor(BookmarkEntity::class.java)
// clear the data before running the test case // clear the data before running the test case
runBlocking { clearBookmarks() } runBlocking { clearBookmarks() }
bookOnDiskBox = boxStore!!.boxFor(BookOnDiskEntity::class.java)
// clear the data before running the test case
runBlocking { clearBookOnDisk() }
// add a file in fileSystem because we need to actual file path for making object of Archive. // add a file in fileSystem because we need to actual file path for making object of Archive.
zimFile = getZimFile("testzim.zim")
}
private fun getZimFile(zimFileName: String): File {
val loadFileStream = val loadFileStream =
ObjectBoxToLibkiwixMigratorTest::class.java.classLoader.getResourceAsStream("testzim.zim") ObjectBoxToLibkiwixMigratorTest::class.java.classLoader.getResourceAsStream(zimFileName)
zimFile = val zimFile =
File( File(
context.getExternalFilesDirs(null)[0], context.getExternalFilesDirs(null)[0],
"testzim.zim" zimFileName
) )
if (zimFile.exists()) zimFile.delete() if (zimFile.exists()) zimFile.delete()
zimFile.createNewFile() zimFile.createNewFile()
@ -153,15 +186,16 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() {
} }
} }
} }
return zimFile
} }
@Test @Test
fun testSingleDataMigration(): Unit = fun testSingleDataMigration(): Unit =
runBlocking { runBlocking {
box.put(bookmarkEntity) bookmarkBox.put(bookmarkEntity)
// migrate data into room database // migrate data into room libkiwix.
objectBoxToLibkiwixMigrator.migrateBookMarks(box) objectBoxToLibkiwixMigrator.migrateBookMarks(bookmarkBox)
// check if data successfully migrated to room // check if data successfully migrated to libkiwix.
val actualDataAfterMigration = val actualDataAfterMigration =
objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first() objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first()
assertEquals(1, actualDataAfterMigration.size) assertEquals(1, actualDataAfterMigration.size)
@ -173,11 +207,89 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() {
clearBookmarks() clearBookmarks()
} }
@Test
fun migrateBookOnDisk_ShouldInsertDataInLibkiwix(): Unit =
runBlocking {
// test with single entity
bookOnDiskBox.put(bookOnDiskEntity)
// migrate data into libkiwix
objectBoxToLibkiwixMigrator.migrateLocalBooks(bookOnDiskBox)
// check if data successfully migrated to libkiwix.
var actualDataAfterMigration =
objectBoxToLibkiwixMigrator.libkiwixBookOnDisk.getBooks()
assertEquals(1, actualDataAfterMigration.size)
assertEquals(
actualDataAfterMigration[0].book.zimReaderSource.toDatabase(),
expectedZimFilePath
)
assertEquals(actualDataAfterMigration[0].book.id, expectedZimId)
assertEquals(actualDataAfterMigration[0].book.title, "Test_Zim")
// Clear the bookOnDisk list from device to not affect the other test cases.
clearBookOnDisk()
// test with empty data
objectBoxToLibkiwixMigrator.migrateLocalBooks(bookOnDiskBox)
// check if data successfully migrated to libkiwix.
actualDataAfterMigration =
objectBoxToLibkiwixMigrator.libkiwixBookOnDisk.getBooks()
assertTrue(actualDataAfterMigration.isEmpty())
// Clear the bookOnDisk list from device to not affect the other test cases.
clearBookOnDisk()
// Test if data successfully migrated to libkiwix and existing data is preserved
zimFile = getZimFile("testzim.zim")
val secondZimFile = getZimFile("small.zim")
val archive = Archive(secondZimFile.path)
val book = Book().apply {
update(archive)
}
objectBoxToLibkiwixMigrator.libkiwixBookOnDisk.insert(listOf(book))
val thirdZim = getZimFile("characters_encoding.zim")
val thirdEntity = BookOnDiskEntity(
id = 0,
file = thirdZim,
zimReaderSource = ZimReaderSource(thirdZim),
bookId = expectedZimId,
title = "",
description = "",
language = "",
creator = "",
publisher = "",
date = "",
url = "",
articleCount = "",
mediaCount = "",
size = "",
name = expectedZimName,
favIcon = ""
)
bookOnDiskBox.put(thirdEntity)
bookOnDiskBox.put(bookOnDiskEntity)
// Migrate data into libkiwix
objectBoxToLibkiwixMigrator.migrateLocalBooks(bookOnDiskBox)
actualDataAfterMigration =
objectBoxToLibkiwixMigrator.libkiwixBookOnDisk.getBooks()
assertEquals(3, actualDataAfterMigration.size)
val existingItem =
actualDataAfterMigration.find {
it.book.zimReaderSource.toDatabase() == secondZimFile.path
}
assertNotNull(existingItem)
val newItem =
actualDataAfterMigration.find {
it.book.zimReaderSource.toDatabase() == expectedZimFilePath
}
assertNotNull(newItem)
// Clear the bookmarks list from device to not affect the other test cases.
clearBookmarks()
secondZimFile.delete()
}
@Test @Test
fun testMigrationWithEmptyData(): Unit = fun testMigrationWithEmptyData(): Unit =
runBlocking { runBlocking {
// Migrate data from empty ObjectBox database // Migrate data from empty ObjectBox database
objectBoxToLibkiwixMigrator.migrateBookMarks(box) objectBoxToLibkiwixMigrator.migrateBookMarks(bookmarkBox)
val actualDataAfterMigration = val actualDataAfterMigration =
objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first() objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first()
assertTrue(actualDataAfterMigration.isEmpty()) assertTrue(actualDataAfterMigration.isEmpty())
@ -209,9 +321,9 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() {
libkiwixBook libkiwixBook
) )
) )
box.put(bookmarkEntity) bookmarkBox.put(bookmarkEntity)
// Migrate data into Room database // Migrate data into libkiwix
objectBoxToLibkiwixMigrator.migrateBookMarks(box) objectBoxToLibkiwixMigrator.migrateBookMarks(bookmarkBox)
val actualDataAfterMigration = val actualDataAfterMigration =
objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first() objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first()
assertEquals(2, actualDataAfterMigration.size) assertEquals(2, actualDataAfterMigration.size)
@ -234,7 +346,7 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() {
runBlocking { runBlocking {
// Test large data migration for recent searches // Test large data migration for recent searches
for (i in 1..1000) { for (i in 1..1000) {
box.put( bookmarkBox.put(
BookmarkEntity( BookmarkEntity(
0, 0,
expectedZimId, expectedZimId,
@ -247,9 +359,9 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() {
) )
) )
} }
// Migrate data into Room database // Migrate data into libkiwix
objectBoxToLibkiwixMigrator.migrateBookMarks(box) objectBoxToLibkiwixMigrator.migrateBookMarks(bookmarkBox)
// Check if data successfully migrated to Room // Check if data successfully migrated to libkiwix
val actualDataAfterMigration = val actualDataAfterMigration =
objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first() objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first()
assertEquals(1000, actualDataAfterMigration.size) assertEquals(1000, actualDataAfterMigration.size)
@ -272,10 +384,10 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() {
expectedTitle, expectedTitle,
expectedFavicon expectedFavicon
) )
box.put(bookmarkEntity) bookmarkBox.put(bookmarkEntity)
// migrate data into room database // migrate data into libkiwix
objectBoxToLibkiwixMigrator.migrateBookMarks(box) objectBoxToLibkiwixMigrator.migrateBookMarks(bookmarkBox)
// check if data successfully migrated to room // check if data successfully migrated to libkiwix
val actualDataAfterMigration = val actualDataAfterMigration =
objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first() objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first()
assertEquals(1, actualDataAfterMigration.size) assertEquals(1, actualDataAfterMigration.size)
@ -303,10 +415,10 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() {
expectedTitle, expectedTitle,
expectedFavicon expectedFavicon
) )
box.put(bookmarkEntity) bookmarkBox.put(bookmarkEntity)
// migrate data into room database // migrate data into libkiwix
objectBoxToLibkiwixMigrator.migrateBookMarks(box) objectBoxToLibkiwixMigrator.migrateBookMarks(bookmarkBox)
// check if data successfully migrated to room // check if data successfully migrated to libkiwix
val actualDataAfterMigration = val actualDataAfterMigration =
objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first() objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first()
assertEquals(1, actualDataAfterMigration.size) assertEquals(1, actualDataAfterMigration.size)
@ -318,13 +430,24 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() {
clearBookmarks() clearBookmarks()
} }
private suspend fun clearBookOnDisk() {
objectBoxToLibkiwixMigrator.libkiwixBookOnDisk.delete(
objectBoxToLibkiwixMigrator.libkiwixBookOnDisk.getBooks()
.map { LibkiwixBook(it.book.nativeBook) }
)
bookOnDiskBox.removeAll()
if (::zimFile.isInitialized) {
zimFile.delete() // delete the temp ZIM file to free up the memory
}
}
private suspend fun clearBookmarks() { private suspend fun clearBookmarks() {
// delete bookmarks for testing other edge cases // delete bookmarks for testing other edge cases
objectBoxToLibkiwixMigrator.libkiwixBookmarks.deleteBookmarks( objectBoxToLibkiwixMigrator.libkiwixBookmarks.deleteBookmarks(
objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks() objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks()
.first() as List<LibkiwixBookmarkItem> .first() as List<LibkiwixBookmarkItem>
) )
box.removeAll() bookmarkBox.removeAll()
if (::zimFile.isInitialized) { if (::zimFile.isInitialized) {
zimFile.delete() // delete the temp ZIM file to free up the memory zimFile.delete() // delete the temp ZIM file to free up the memory
} }

View File

@ -40,9 +40,8 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.kiwix.kiwixmobile.BaseActivityTest import org.kiwix.kiwixmobile.BaseActivityTest
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.dao.entities.BookOnDiskEntity
import org.kiwix.kiwixmobile.core.di.modules.DatabaseModule import org.kiwix.kiwixmobile.core.di.modules.DatabaseModule
import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem
import org.kiwix.kiwixmobile.core.utils.LanguageUtils import org.kiwix.kiwixmobile.core.utils.LanguageUtils
@ -63,7 +62,7 @@ class ImportBookmarkTest : BaseActivityTest() {
private var boxStore: BoxStore? = null private var boxStore: BoxStore? = null
private val library = Library() private val library = Library()
private val manager = Manager(library) private val manager = Manager(library)
private lateinit var newBookDao: NewBookDao private lateinit var libkiwixBookOnDisk: LibkiwixBookOnDisk
private lateinit var libkiwixBookmarks: LibkiwixBookmarks private lateinit var libkiwixBookmarks: LibkiwixBookmarks
private val bookmarkXmlData = private val bookmarkXmlData =
@ -138,13 +137,14 @@ class ImportBookmarkTest : BaseActivityTest() {
} }
} }
boxStore = DatabaseModule.boxStore boxStore = DatabaseModule.boxStore
newBookDao = NewBookDao(boxStore!!.boxFor(BookOnDiskEntity::class.java)) val sharedPreferenceUtils = SharedPreferenceUtil(context)
libkiwixBookOnDisk = LibkiwixBookOnDisk(library, manager, sharedPreferenceUtils)
libkiwixBookmarks = libkiwixBookmarks =
LibkiwixBookmarks( LibkiwixBookmarks(
library, library,
manager, manager,
SharedPreferenceUtil(context), sharedPreferenceUtils,
newBookDao, libkiwixBookOnDisk,
null null
) )
} }

View File

@ -28,6 +28,7 @@ import app.cash.turbine.TurbineTestContext
import app.cash.turbine.test import app.cash.turbine.test
import com.jraska.livedata.test import com.jraska.livedata.test
import io.mockk.clearAllMocks import io.mockk.clearAllMocks
import io.mockk.coVerify
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
@ -41,9 +42,9 @@ import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain import kotlinx.coroutines.test.setMain
import kotlinx.coroutines.withTimeout
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
@ -129,8 +130,8 @@ class ZimManageViewModelTest {
@AfterAll @AfterAll
fun teardown() { fun teardown() {
Dispatchers.resetMain()
viewModel.onClearedExposed() viewModel.onClearedExposed()
Dispatchers.resetMain()
} }
@BeforeEach @BeforeEach
@ -236,24 +237,24 @@ class ZimManageViewModelTest {
@Test @Test
fun `books found on filesystem are filtered by books already in db`() = runTest { fun `books found on filesystem are filtered by books already in db`() = runTest {
every { application.getString(any()) } returns "" every { application.getString(any()) } returns ""
val expectedBook = libkiwixBook("1") val expectedBook = bookOnDisk(1L, libkiwixBook("1", nativeBook = BookTestWrapper("1")))
val bookToRemove = libkiwixBook("2") val bookToRemove = bookOnDisk(1L, libkiwixBook("2", nativeBook = BookTestWrapper("2")))
advanceUntilIdle() advanceUntilIdle()
viewModel.requestFileSystemCheck.emit(Unit) viewModel.requestFileSystemCheck.emit(Unit)
advanceUntilIdle() advanceUntilIdle()
// books.emit(listOf(bookToRemove)) books.emit(listOf(bookToRemove))
// advanceUntilIdle() advanceUntilIdle()
// booksOnFileSystem.emit( booksOnFileSystem.emit(
// listOf( listOfNotNull(
// expectedBook, expectedBook.book.nativeBook,
// expectedBook, expectedBook.book.nativeBook,
// bookToRemove bookToRemove.book.nativeBook
// ) )
// ) )
// advanceUntilIdle() advanceUntilIdle()
// coVerify { coVerify(timeout = 500) {
// libkiwixBookOnDisk.insert(listOf(expectedBook.book)) libkiwixBookOnDisk.insert(listOfNotNull(expectedBook.book.nativeBook))
// } }
} }
} }
@ -358,7 +359,7 @@ class ZimManageViewModelTest {
language(isActive = true, occurencesOfLanguage = 1) language(isActive = true, occurencesOfLanguage = 1)
) )
advanceUntilIdle() advanceUntilIdle()
verify { verify(timeout = 500) {
newLanguagesDao.insert( newLanguagesDao.insert(
listOf( listOf(
dbLanguage.copy(occurencesOfLanguage = 2), dbLanguage.copy(occurencesOfLanguage = 2),
@ -384,9 +385,9 @@ class ZimManageViewModelTest {
every { application.getString(any(), any()) } returns "" every { application.getString(any(), any()) } returns ""
every { defaultLanguageProvider.provide() } returns defaultLanguage every { defaultLanguageProvider.provide() } returns defaultLanguage
viewModel.networkLibrary.emit(networkBooks) viewModel.networkLibrary.emit(networkBooks)
runCurrent() advanceUntilIdle()
languages.value = dbBooks languages.value = dbBooks
runCurrent() advanceUntilIdle()
networkStates.value = CONNECTED networkStates.value = CONNECTED
advanceUntilIdle() advanceUntilIdle()
} }
@ -475,13 +476,21 @@ class ZimManageViewModelTest {
viewModel.networkLibrary.emit(listOf(bookOver4Gb)) viewModel.networkLibrary.emit(listOf(bookOver4Gb))
}, },
assert = { assert = {
skipItems(1) withTimeout(5000) {
assertThat(awaitItem()).isEqualTo( while (true) {
val item = awaitItem()
val bookItem = item.filterIsInstance<LibraryListItem.BookItem>().firstOrNull()
if (bookItem?.fileSystemState == CannotWrite4GbFile) {
assertThat(item).isEqualTo(
listOf( listOf(
LibraryListItem.DividerItem(Long.MIN_VALUE, R.string.other_languages), LibraryListItem.DividerItem(Long.MIN_VALUE, R.string.other_languages),
LibraryListItem.BookItem(bookOver4Gb, CannotWrite4GbFile) LibraryListItem.BookItem(bookOver4Gb, CannotWrite4GbFile)
) )
) )
break
}
}
}
} }
) )
} }
@ -610,3 +619,9 @@ suspend fun <T> TestScope.testFlow(
} }
job.join() job.join()
} }
class BookTestWrapper(private val id: String) : Book(0L) {
override fun getId(): String = id
override fun equals(other: Any?): Boolean = other is BookTestWrapper && getId() == other.getId()
override fun hashCode(): Int = getId().hashCode()
}

View File

@ -39,7 +39,8 @@ class StorageObserver @Inject constructor(
private val downloadRoomDao: DownloadRoomDao, private val downloadRoomDao: DownloadRoomDao,
private val fileSearch: FileSearch, private val fileSearch: FileSearch,
private val zimReaderFactory: ZimFileReader.Factory, private val zimReaderFactory: ZimFileReader.Factory,
private val libkiwixBookmarks: LibkiwixBookmarks private val libkiwixBookmarks: LibkiwixBookmarks,
private val libkiwixBookFactory: LibkiwixBookFactory
) { ) {
fun getBooksOnFileSystem( fun getBooksOnFileSystem(
scanningProgressListener: ScanningProgressListener, scanningProgressListener: ScanningProgressListener,
@ -64,7 +65,7 @@ class StorageObserver @Inject constructor(
private suspend fun convertToLibkiwixBook(file: File) = private suspend fun convertToLibkiwixBook(file: File) =
zimReaderFactory.create(ZimReaderSource(file)) zimReaderFactory.create(ZimReaderSource(file))
?.let { zimFileReader -> ?.let { zimFileReader ->
Book().apply { libkiwixBookFactory.create().apply {
update(zimFileReader.jniKiwixReader) update(zimFileReader.jniKiwixReader)
}.also { }.also {
// add the book to libkiwix library to validate the imported bookmarks // add the book to libkiwix library to validate the imported bookmarks
@ -73,3 +74,7 @@ class StorageObserver @Inject constructor(
} }
} }
} }
interface LibkiwixBookFactory {
fun create(): Book
}

View File

@ -205,7 +205,7 @@ class LibkiwixBookOnDisk @Inject constructor(
private suspend fun isInTrashFolder(filePath: String) = private suspend fun isInTrashFolder(filePath: String) =
Regex("/\\.Trash/").containsMatchIn(filePath) Regex("/\\.Trash/").containsMatchIn(filePath)
private suspend fun delete(books: List<LibkiwixBook>) { suspend fun delete(books: List<LibkiwixBook>) {
runCatching { runCatching {
books.forEach { books.forEach {
library.removeBookById(it.id) library.removeBookById(it.id)

View File

@ -25,12 +25,13 @@ import dagger.BindsInstance
import dagger.Component import dagger.Component
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import org.kiwix.kiwixmobile.core.CoreApp import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.LibkiwixBookFactory
import org.kiwix.kiwixmobile.core.StorageObserver import org.kiwix.kiwixmobile.core.StorageObserver
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
import org.kiwix.kiwixmobile.core.dao.HistoryDao import org.kiwix.kiwixmobile.core.dao.HistoryDao
import org.kiwix.kiwixmobile.core.dao.HistoryRoomDao import org.kiwix.kiwixmobile.core.dao.HistoryRoomDao
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.dao.NewBookDao import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.dao.NewBookmarksDao import org.kiwix.kiwixmobile.core.dao.NewBookmarksDao
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
@ -85,6 +86,7 @@ interface CoreComponent {
fun zimReaderContainer(): ZimReaderContainer fun zimReaderContainer(): ZimReaderContainer
fun sharedPrefUtil(): SharedPreferenceUtil fun sharedPrefUtil(): SharedPreferenceUtil
fun zimFileReaderFactory(): ZimFileReader.Factory fun zimFileReaderFactory(): ZimFileReader.Factory
fun libkiwixBookFactory(): LibkiwixBookFactory
fun storageObserver(): StorageObserver fun storageObserver(): StorageObserver
fun kiwixService(): KiwixService fun kiwixService(): KiwixService
fun application(): Application fun application(): Application

View File

@ -20,10 +20,12 @@ package org.kiwix.kiwixmobile.core.di.modules
import android.content.Context import android.content.Context
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import org.kiwix.kiwixmobile.core.LibkiwixBookFactory
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.libkiwix.Book
import org.kiwix.libkiwix.JNIKiwix import org.kiwix.libkiwix.JNIKiwix
import org.kiwix.libkiwix.Library import org.kiwix.libkiwix.Library
import org.kiwix.libkiwix.Manager import org.kiwix.libkiwix.Manager
@ -83,6 +85,14 @@ class JNIModule {
@Named(LOCAL_BOOKS_MANAGER) manager: Manager, @Named(LOCAL_BOOKS_MANAGER) manager: Manager,
sharedPreferenceUtil: SharedPreferenceUtil, sharedPreferenceUtil: SharedPreferenceUtil,
): LibkiwixBookOnDisk = LibkiwixBookOnDisk(library, manager, sharedPreferenceUtil) ): LibkiwixBookOnDisk = LibkiwixBookOnDisk(library, manager, sharedPreferenceUtil)
/**
* We are not making this singleton because we need multiple objects of this.
*/
@Provides
fun provideBookFactory(): LibkiwixBookFactory = object : LibkiwixBookFactory {
override fun create(): Book = Book()
}
} }
const val BOOKMARK_LIBRARY = "bookmarkLibrary" const val BOOKMARK_LIBRARY = "bookmarkLibrary"

View File

@ -29,7 +29,7 @@ import java.io.File
*/ */
@Suppress("ConstructorParameterNaming") @Suppress("ConstructorParameterNaming")
data class LibkiwixBook( data class LibkiwixBook(
val nativeBook: Book? = null, var nativeBook: Book? = null,
private var _id: String = "", private var _id: String = "",
private var _title: String = "", private var _title: String = "",
private var _description: String? = null, private var _description: String? = null,

View File

@ -35,6 +35,7 @@ import org.kiwix.kiwixmobile.core.entity.MetaLinkNetworkEntity.Url
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
import org.kiwix.kiwixmobile.core.zim_manager.Language import org.kiwix.kiwixmobile.core.zim_manager.Language
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
import org.kiwix.libkiwix.Book
import java.io.File import java.io.File
fun bookOnDisk( fun bookOnDisk(
@ -146,8 +147,11 @@ fun libkiwixBook(
size: String = "1024", size: String = "1024",
name: String = "name", name: String = "name",
favIcon: String = "favIcon", favIcon: String = "favIcon",
file: File = File("") file: File = File(""),
nativeBook: Book? = null,
tags: String? = ""
) = LibkiwixBook().apply { ) = LibkiwixBook().apply {
this.nativeBook = nativeBook
this.id = id this.id = id
this.title = title this.title = title
this.description = description this.description = description
@ -162,6 +166,7 @@ fun libkiwixBook(
this.file = file this.file = file
bookName = name bookName = name
favicon = favIcon favicon = favIcon
this.tags = tags
} }
fun recentSearchEntity( fun recentSearchEntity(

View File

@ -19,6 +19,8 @@
package org.kiwix.kiwixmobile.core package org.kiwix.kiwixmobile.core
import io.mockk.clearAllMocks import io.mockk.clearAllMocks
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
@ -39,9 +41,9 @@ import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.core.utils.files.FileSearch import org.kiwix.kiwixmobile.core.utils.files.FileSearch
import org.kiwix.kiwixmobile.core.utils.files.ScanningProgressListener import org.kiwix.kiwixmobile.core.utils.files.ScanningProgressListener
import org.kiwix.kiwixmobile.core.utils.files.testFlow import org.kiwix.kiwixmobile.core.utils.files.testFlow
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk import org.kiwix.libkiwix.Book
import org.kiwix.libzim.Archive
import org.kiwix.sharedFunctions.libkiwixBook import org.kiwix.sharedFunctions.libkiwixBook
import org.kiwix.sharedFunctions.bookOnDisk
import java.io.File import java.io.File
class StorageObserverTest { class StorageObserverTest {
@ -58,6 +60,8 @@ class StorageObserverTest {
private val files = MutableStateFlow<List<File>>(emptyList()) private val files = MutableStateFlow<List<File>>(emptyList())
private val downloads = MutableStateFlow<List<DownloadModel>>(emptyList()) private val downloads = MutableStateFlow<List<DownloadModel>>(emptyList())
private val libkiwixBookFactory: LibkiwixBookFactory = mockk()
private val libkiwixBook: Book = BookTestWrapper("id")
private lateinit var storageObserver: StorageObserver private lateinit var storageObserver: StorageObserver
@ -66,9 +70,17 @@ class StorageObserverTest {
every { sharedPreferenceUtil.prefStorage } returns "a" every { sharedPreferenceUtil.prefStorage } returns "a"
every { fileSearch.scan(scanningProgressListener) } returns files every { fileSearch.scan(scanningProgressListener) } returns files
every { downloadRoomDao.downloads() } returns downloads every { downloadRoomDao.downloads() } returns downloads
coEvery { libkiwixBookmarks.addBookToLibrary(any()) } returns Unit
every { zimFileReader.jniKiwixReader } returns mockk() every { zimFileReader.jniKiwixReader } returns mockk()
every { runBlocking { readerFactory.create(zimReaderSource) } } returns zimFileReader every { runBlocking { readerFactory.create(zimReaderSource) } } returns zimFileReader
storageObserver = StorageObserver(downloadRoomDao, fileSearch, readerFactory, libkiwixBookmarks) every { libkiwixBookFactory.create() } returns libkiwixBook
storageObserver = StorageObserver(
downloadRoomDao,
fileSearch,
readerFactory,
libkiwixBookmarks,
libkiwixBookFactory
)
} }
@Test @Test
@ -77,7 +89,7 @@ class StorageObserverTest {
testFlow( testFlow(
flow = booksOnFileSystem(), flow = booksOnFileSystem(),
triggerAction = {}, triggerAction = {},
assert = { assertThat(awaitItem()).isEqualTo(listOf<BookOnDisk>()) } assert = { assertThat(awaitItem()).isEqualTo(listOf<Book>()) }
) )
} }
@ -87,7 +99,7 @@ class StorageObserverTest {
val expectedBook = val expectedBook =
libkiwixBook( libkiwixBook(
"id", "title", "1", "favicon", "creator", "publisher", "date", "id", "title", "1", "favicon", "creator", "publisher", "date",
"description", "language" "description", "language", nativeBook = libkiwixBook
) )
withNoFiltering() withNoFiltering()
every { zimFileReader.toBook() } returns expectedBook every { zimFileReader.toBook() } returns expectedBook
@ -97,15 +109,14 @@ class StorageObserverTest {
triggerAction = {}, triggerAction = {},
assert = { assert = {
assertThat(awaitItem()).isEqualTo( assertThat(awaitItem()).isEqualTo(
listOf<BookOnDisk>( listOfNotNull<Book>(
bookOnDisk( expectedBook.nativeBook
book = expectedBook,
zimReaderSource = zimReaderSource
)
) )
) )
} }
) )
// test the book is added to bookmark's library.
coVerify { libkiwixBookmarks.addBookToLibrary(archive = any()) }
verify { zimFileReader.dispose() } verify { zimFileReader.dispose() }
} }
@ -128,3 +139,12 @@ class StorageObserverTest {
every { zimReaderSource.file } returns file every { zimReaderSource.file } returns file
} }
} }
class BookTestWrapper(private val id: String) : Book(0L) {
override fun getId(): String = id
override fun equals(other: Any?): Boolean = other is BookTestWrapper && getId() == other.getId()
override fun hashCode(): Int = getId().hashCode()
override fun update(archive: Archive?) {
// do nothing
}
}

View File

@ -36,7 +36,7 @@ import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.getPackageInformation import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.getPackageInformation
import org.kiwix.kiwixmobile.core.dao.NewBookDao import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions
@ -44,7 +44,7 @@ import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions
class DonationDialogHandlerTest { class DonationDialogHandlerTest {
private lateinit var activity: Activity private lateinit var activity: Activity
private lateinit var sharedPreferenceUtil: SharedPreferenceUtil private lateinit var sharedPreferenceUtil: SharedPreferenceUtil
private lateinit var newBookDao: NewBookDao private lateinit var libkiwixBookOnDisk: LibkiwixBookOnDisk
private lateinit var donationDialogHandler: DonationDialogHandler private lateinit var donationDialogHandler: DonationDialogHandler
private lateinit var showDonationDialogCallback: DonationDialogHandler.ShowDonationDialogCallback private lateinit var showDonationDialogCallback: DonationDialogHandler.ShowDonationDialogCallback
private lateinit var packageManager: PackageManager private lateinit var packageManager: PackageManager
@ -56,10 +56,10 @@ class DonationDialogHandlerTest {
every { activity.packageManager } returns packageManager every { activity.packageManager } returns packageManager
every { activity.packageName } returns "org.kiwix.kiwixmobile" every { activity.packageName } returns "org.kiwix.kiwixmobile"
sharedPreferenceUtil = mockk(relaxed = true) sharedPreferenceUtil = mockk(relaxed = true)
newBookDao = mockk(relaxed = true) libkiwixBookOnDisk = mockk(relaxed = true)
showDonationDialogCallback = mockk(relaxed = true) showDonationDialogCallback = mockk(relaxed = true)
donationDialogHandler = donationDialogHandler =
DonationDialogHandler(activity, sharedPreferenceUtil, newBookDao) DonationDialogHandler(activity, sharedPreferenceUtil, libkiwixBookOnDisk)
donationDialogHandler.setDonationDialogCallBack(showDonationDialogCallback) donationDialogHandler.setDonationDialogCallBack(showDonationDialogCallback)
} }
@ -69,7 +69,7 @@ class DonationDialogHandlerTest {
donationDialogHandler = spyk(donationDialogHandler) donationDialogHandler = spyk(donationDialogHandler)
every { sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds } returns 0L every { sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds } returns 0L
every { sharedPreferenceUtil.laterClickedMilliSeconds } returns 0L every { sharedPreferenceUtil.laterClickedMilliSeconds } returns 0L
coEvery { newBookDao.getBooks() } returns listOf(mockk()) coEvery { libkiwixBookOnDisk.getBooks() } returns listOf(mockk())
every { every {
donationDialogHandler.shouldShowInitialPopup(any()) donationDialogHandler.shouldShowInitialPopup(any())
} returns true } returns true
@ -81,7 +81,7 @@ class DonationDialogHandlerTest {
fun `should not show donation popup if app is not three month old`() = fun `should not show donation popup if app is not three month old`() =
runTest { runTest {
every { sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds } returns 0L every { sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds } returns 0L
coEvery { newBookDao.getBooks() } returns listOf(mockk()) coEvery { libkiwixBookOnDisk.getBooks() } returns listOf(mockk())
val currentMillis = System.currentTimeMillis() val currentMillis = System.currentTimeMillis()
val installTime = currentMillis - 1000 val installTime = currentMillis - 1000
val packageInfo = val packageInfo =
@ -99,7 +99,7 @@ class DonationDialogHandlerTest {
fun `should not show donation popup when no ZIM files available in library`() = fun `should not show donation popup when no ZIM files available in library`() =
runTest { runTest {
every { sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds } returns 0L every { sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds } returns 0L
coEvery { newBookDao.getBooks() } returns emptyList() coEvery { libkiwixBookOnDisk.getBooks() } returns emptyList()
val currentMillis = System.currentTimeMillis() val currentMillis = System.currentTimeMillis()
val threeMonthsAgo = currentMillis - THREE_MONTHS_IN_MILLISECONDS val threeMonthsAgo = currentMillis - THREE_MONTHS_IN_MILLISECONDS
val packageInfo = val packageInfo =
@ -119,7 +119,7 @@ class DonationDialogHandlerTest {
every { every {
sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds
} returns currentMilliSeconds - (THREE_MONTHS_IN_MILLISECONDS / 2) } returns currentMilliSeconds - (THREE_MONTHS_IN_MILLISECONDS / 2)
coEvery { newBookDao.getBooks() } returns listOf(mockk()) coEvery { libkiwixBookOnDisk.getBooks() } returns listOf(mockk())
donationDialogHandler.attemptToShowDonationPopup() donationDialogHandler.attemptToShowDonationPopup()
verify(exactly = 0) { showDonationDialogCallback.showDonationDialog() } verify(exactly = 0) { showDonationDialogCallback.showDonationDialog() }
} }
@ -131,7 +131,7 @@ class DonationDialogHandlerTest {
every { every {
sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds
} returns currentMilliSeconds - (THREE_MONTHS_IN_MILLISECONDS + 1000) } returns currentMilliSeconds - (THREE_MONTHS_IN_MILLISECONDS + 1000)
coEvery { newBookDao.getBooks() } returns listOf(mockk()) coEvery { libkiwixBookOnDisk.getBooks() } returns listOf(mockk())
donationDialogHandler.attemptToShowDonationPopup() donationDialogHandler.attemptToShowDonationPopup()
verify { showDonationDialogCallback.showDonationDialog() } verify { showDonationDialogCallback.showDonationDialog() }
} }
@ -145,7 +145,7 @@ class DonationDialogHandlerTest {
sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds
} returns 0L } returns 0L
every { donationDialogHandler.shouldShowInitialPopup(any()) } returns true every { donationDialogHandler.shouldShowInitialPopup(any()) } returns true
coEvery { newBookDao.getBooks() } returns listOf(mockk()) coEvery { libkiwixBookOnDisk.getBooks() } returns listOf(mockk())
every { every {
sharedPreferenceUtil.laterClickedMilliSeconds sharedPreferenceUtil.laterClickedMilliSeconds
} returns currentMilliSeconds - (THREE_MONTHS_IN_MILLISECONDS + 1000) } returns currentMilliSeconds - (THREE_MONTHS_IN_MILLISECONDS + 1000)
@ -162,7 +162,7 @@ class DonationDialogHandlerTest {
sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds
} returns 0L } returns 0L
every { donationDialogHandler.shouldShowInitialPopup(any()) } returns true every { donationDialogHandler.shouldShowInitialPopup(any()) } returns true
coEvery { newBookDao.getBooks() } returns listOf(mockk()) coEvery { libkiwixBookOnDisk.getBooks() } returns listOf(mockk())
every { every {
sharedPreferenceUtil.laterClickedMilliSeconds sharedPreferenceUtil.laterClickedMilliSeconds
} returns currentMilliSeconds - 10000L } returns currentMilliSeconds - 10000L
@ -243,7 +243,7 @@ class DonationDialogHandlerTest {
with(mockk<ActivityExtensions>()) { with(mockk<ActivityExtensions>()) {
every { activity.packageName } returns "org.kiwix.kiwixmobile" every { activity.packageName } returns "org.kiwix.kiwixmobile"
every { activity.isCustomApp() } returns false every { activity.isCustomApp() } returns false
coEvery { newBookDao.getBooks() } returns emptyList() coEvery { libkiwixBookOnDisk.getBooks() } returns emptyList()
val result = donationDialogHandler.isZimFilesAvailableInLibrary() val result = donationDialogHandler.isZimFilesAvailableInLibrary()
assertFalse(result) assertFalse(result)
} }
@ -255,7 +255,7 @@ class DonationDialogHandlerTest {
with(mockk<ActivityExtensions>()) { with(mockk<ActivityExtensions>()) {
every { activity.packageName } returns "org.kiwix.kiwixmobile" every { activity.packageName } returns "org.kiwix.kiwixmobile"
every { activity.isCustomApp() } returns false every { activity.isCustomApp() } returns false
coEvery { newBookDao.getBooks() } returns listOf(mockk()) coEvery { libkiwixBookOnDisk.getBooks() } returns listOf(mockk())
val result = donationDialogHandler.isZimFilesAvailableInLibrary() val result = donationDialogHandler.isZimFilesAvailableInLibrary()
assertTrue(result) assertTrue(result)
} }