Refactored all functionalities to use LibkiwixBookOnDisk instead of NewBookDao.

* Fixed: Saved books in the library were not appearing on the Local Library screen.
* Fixed: Opening a ZIM file via the download notification was not working.
* Fixed: Selecting a ZIM file would select all ZIM files displayed on the Local Library screen.
* Improved: Migration logic now properly migrates books that were stored in the file system before introducing `ZimReaderSource`.
This commit is contained in:
MohitMaliFtechiz 2025-06-11 00:53:35 +05:30
parent 8370f72d35
commit 42da475867
24 changed files with 154 additions and 134 deletions

View File

@ -42,6 +42,7 @@ import androidx.navigation.ui.setupWithNavController
import com.google.android.material.navigation.NavigationView
import eu.mhutti1.utils.storage.StorageDevice
import eu.mhutti1.utils.storage.StorageDeviceUtils
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.BuildConfig
import org.kiwix.kiwixmobile.R
@ -50,7 +51,7 @@ import org.kiwix.kiwixmobile.core.R.id
import org.kiwix.kiwixmobile.core.R.mipmap
import org.kiwix.kiwixmobile.core.R.string
import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions
import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DOWNLOAD_NOTIFICATION_TITLE
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
import org.kiwix.kiwixmobile.core.extensions.applyEdgeToEdgeInsets
@ -67,7 +68,6 @@ import org.kiwix.kiwixmobile.kiwixActivityComponent
import org.kiwix.kiwixmobile.nav.destination.reader.KiwixReaderFragmentDirections
import javax.inject.Inject
const val NAVIGATE_TO_ZIM_HOST_FRAGMENT = "navigate_to_zim_host_fragment"
const val ACTION_GET_CONTENT = "GET_CONTENT"
const val OPENING_ZIM_FILE_DELAY = 300L
const val GET_CONTENT_SHORTCUT_ID = "get_content_shortcut"
@ -100,7 +100,7 @@ class KiwixMainActivity : CoreMainActivity() {
activityKiwixMainBinding.navHostFragment
}
@Inject lateinit var newBookDao: NewBookDao
@Inject lateinit var libkiwixBookOnDisk: LibkiwixBookOnDisk
override val mainActivity: AppCompatActivity by lazy { this }
override val appName: String by lazy { getString(R.string.app_name) }
@ -323,16 +323,14 @@ class KiwixMainActivity : CoreMainActivity() {
private fun handleNotificationIntent(intent: Intent) {
if (intent.hasExtra(DOWNLOAD_NOTIFICATION_TITLE)) {
Handler(Looper.getMainLooper()).postDelayed(
{
intent.getStringExtra(DOWNLOAD_NOTIFICATION_TITLE)?.let {
newBookDao.bookMatching(it)?.let { bookOnDiskEntity ->
openZimFromFilePath(bookOnDiskEntity.zimReaderSource.toDatabase())
}
lifecycleScope.launch {
delay(OPENING_ZIM_FILE_DELAY)
intent.getStringExtra(DOWNLOAD_NOTIFICATION_TITLE)?.let {
libkiwixBookOnDisk.bookMatching(it)?.let { bookOnDiskEntity ->
openZimFromFilePath(bookOnDiskEntity.zimReaderSource.toDatabase())
}
},
OPENING_ZIM_FILE_DELAY
)
}
}
}
}

View File

@ -108,6 +108,7 @@ import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel.FileSelectActions.Req
import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel.FileSelectActions.RequestNavigateTo
import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel.FileSelectActions.RequestSelect
import org.kiwix.kiwixmobile.zimManager.fileselectView.FileSelectListState
import org.kiwix.libkiwix.Book
import java.io.File
import java.util.Locale
import javax.inject.Inject
@ -488,10 +489,9 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
CoroutineScope(Dispatchers.IO).launch {
zimReaderFactory.create(ZimReaderSource(file))
?.let { zimFileReader ->
BookOnDisk(zimFileReader).also {
mainRepositoryActions.saveBook(it)
zimFileReader.dispose()
}
val book = Book().apply { update(zimFileReader.jniKiwixReader) }
mainRepositoryActions.saveBook(book)
zimFileReader.dispose()
}
}
activity?.navigate(

View File

@ -66,7 +66,7 @@ import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.convertToLocal
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.isWifi
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.core.data.DataSource
import org.kiwix.kiwixmobile.core.data.remote.KiwixService
@ -120,6 +120,7 @@ import org.kiwix.kiwixmobile.zimManager.libraryView.adapter.LibraryListItem
import org.kiwix.kiwixmobile.zimManager.libraryView.adapter.LibraryListItem.BookItem
import org.kiwix.kiwixmobile.zimManager.libraryView.adapter.LibraryListItem.DividerItem
import org.kiwix.kiwixmobile.zimManager.libraryView.adapter.LibraryListItem.LibraryDownloadItem
import org.kiwix.libkiwix.Book
import org.kiwix.libkiwix.Library
import org.kiwix.libkiwix.Manager
import java.util.Locale
@ -135,7 +136,7 @@ const val FOUR = 4
@Suppress("LongParameterList")
class ZimManageViewModel @Inject constructor(
private val downloadDao: DownloadRoomDao,
private val bookDao: NewBookDao,
private val libkiwixBookOnDisk: LibkiwixBookOnDisk,
private val languageDao: NewLanguagesDao,
private val storageObserver: StorageObserver,
private var kiwixService: KiwixService,
@ -318,7 +319,7 @@ class ZimManageViewModel @Inject constructor(
private fun scanBooksFromStorage(dispatcher: CoroutineDispatcher = Dispatchers.IO) =
checkFileSystemForBooksOnRequest(books())
.catch { it.printStackTrace() }
.onEach { books -> bookDao.insert(books) }
.onEach { books -> libkiwixBookOnDisk.insert(books) }
.flowOn(dispatcher)
.launchIn(viewModelScope)
@ -480,7 +481,7 @@ class ZimManageViewModel @Inject constructor(
@Suppress("UNCHECKED_CAST")
@OptIn(FlowPreview::class)
private fun updateLibraryItems(
booksFromDao: Flow<List<BookOnDisk>>,
localBooksFromLibkiwix: Flow<List<Book>>,
downloads: Flow<List<DownloadModel>>,
library: MutableSharedFlow<List<LibkiwixBook>>,
languages: Flow<List<Language>>,
@ -495,14 +496,14 @@ class ZimManageViewModel @Inject constructor(
)
combine(
booksFromDao,
localBooksFromLibkiwix,
downloads,
languages.filter { it.isNotEmpty() },
library,
requestFilteringFlow,
fat32Checker.fileSystemStates
) { args ->
val books = args[ZERO] as List<BookOnDisk>
val books = args[ZERO] as List<Book>
val activeDownloads = args[ONE] as List<DownloadModel>
val languageList = args[TWO] as List<Language>
val libraryNetworkEntity = args[THREE] as List<LibkiwixBook>
@ -604,7 +605,7 @@ class ZimManageViewModel @Inject constructor(
@Suppress("UnsafeCallOnNullableType")
private fun combineLibrarySources(
booksOnFileSystem: List<BookOnDisk>,
booksOnFileSystem: List<Book>,
activeDownloads: List<DownloadModel>,
allLanguages: List<Language>,
onlineBooks: List<LibkiwixBook>,
@ -614,7 +615,7 @@ class ZimManageViewModel @Inject constructor(
val activeLanguageCodes =
allLanguages.filter(Language::active)
.map(Language::languageCode)
val allBooks = onlineBooks - booksOnFileSystem.map(BookOnDisk::book).toSet()
val allBooks = onlineBooks - booksOnFileSystem.map { LibkiwixBook(it) }.toSet()
val downloadingBooks =
activeDownloads.mapNotNull { download ->
allBooks.firstOrNull { it.id == download.book.id }
@ -686,8 +687,8 @@ class ZimManageViewModel @Inject constructor(
@OptIn(ExperimentalCoroutinesApi::class)
private fun checkFileSystemForBooksOnRequest(
booksFromDao: Flow<List<BookOnDisk>>
): Flow<List<BookOnDisk>> = requestFileSystemCheck
booksFromDao: Flow<List<Book>>
): Flow<List<Book>> = requestFileSystemCheck
.flatMapLatest {
// Initial progress
deviceListScanningProgress.postValue(DEFAULT_PROGRESS)
@ -708,25 +709,28 @@ class ZimManageViewModel @Inject constructor(
deviceListScanningProgress.postValue(MAX_PROGRESS)
}
.filter { it.isNotEmpty() }
.map { books -> books.distinctBy { it.book.id } }
.map { books -> books.distinctBy { it.id } }
private fun books() =
bookDao.books()
.map { it.sortedBy { book -> book.book.title } }
private fun books(): Flow<List<Book>> =
libkiwixBookOnDisk.books().map { bookOnDiskList ->
bookOnDiskList
.sortedBy { it.book.title }
.mapNotNull { it.book.nativeBook }
}
private fun booksFromStorageNotIn(
booksFromDao: Flow<List<BookOnDisk>>,
localBooksFromLibkiwix: Flow<List<Book>>,
scanningProgressListener: ScanningProgressListener
): Flow<List<BookOnDisk>> = flow {
): Flow<List<Book>> = flow {
val scannedBooks = storageObserver.getBooksOnFileSystem(scanningProgressListener).first()
val daoBookIds = booksFromDao.first().map { it.book.id }
val daoBookIds = localBooksFromLibkiwix.first().map { it.id }
emit(removeBooksAlreadyInDao(scannedBooks, daoBookIds))
}
private fun removeBooksAlreadyInDao(
booksFromFileSystem: Collection<BookOnDisk>,
booksFromFileSystem: Collection<Book>,
idsInDao: List<String>
) = booksFromFileSystem.filterNot { idsInDao.contains(it.book.id) }
) = booksFromFileSystem.filterNot { idsInDao.contains(it.id) }
private fun updateBookItems() =
dataSource.booksOnDiskAsListItems()

View File

@ -27,7 +27,7 @@ import org.kiwix.kiwixmobile.cachedComponent
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk
import org.kiwix.kiwixmobile.core.extensions.isFileExist
import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
@ -42,7 +42,7 @@ data class DeleteFiles(
private val dialogShower: DialogShower
) :
SideEffect<Unit> {
@Inject lateinit var newBookDao: NewBookDao
@Inject lateinit var libkiwixBookOnDisk: LibkiwixBookOnDisk
@Inject lateinit var zimReaderContainer: ZimReaderContainer
@ -89,7 +89,7 @@ data class DeleteFiles(
if (file?.isFileExist() == true) {
return false
}
newBookDao.delete(book.databaseId)
libkiwixBookOnDisk.delete(book.book.id)
return true
}
}

View File

@ -28,7 +28,6 @@ import app.cash.turbine.TurbineTestContext
import app.cash.turbine.test
import com.jraska.livedata.test
import io.mockk.clearAllMocks
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
@ -54,7 +53,7 @@ import org.junit.jupiter.api.extension.ExtendWith
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.StorageObserver
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.core.data.DataSource
import org.kiwix.kiwixmobile.core.data.remote.KiwixService
@ -88,6 +87,7 @@ import org.kiwix.kiwixmobile.zimManager.fileselectView.effects.None
import org.kiwix.kiwixmobile.zimManager.fileselectView.effects.ShareFiles
import org.kiwix.kiwixmobile.zimManager.fileselectView.effects.StartMultiSelection
import org.kiwix.kiwixmobile.zimManager.libraryView.adapter.LibraryListItem
import org.kiwix.libkiwix.Book
import org.kiwix.sharedFunctions.InstantExecutorExtension
import org.kiwix.sharedFunctions.bookOnDisk
import org.kiwix.sharedFunctions.downloadModel
@ -99,7 +99,7 @@ import java.util.Locale
@ExtendWith(InstantExecutorExtension::class)
class ZimManageViewModelTest {
private val downloadRoomDao: DownloadRoomDao = mockk()
private val newBookDao: NewBookDao = mockk()
private val libkiwixBookOnDisk: LibkiwixBookOnDisk = mockk()
private val newLanguagesDao: NewLanguagesDao = mockk()
private val storageObserver: StorageObserver = mockk()
private val kiwixService: KiwixService = mockk()
@ -118,7 +118,7 @@ class ZimManageViewModelTest {
lateinit var viewModel: ZimManageViewModel
private val downloads = MutableStateFlow<List<DownloadModel>>(emptyList())
private val booksOnFileSystem = MutableStateFlow<List<BookOnDisk>>(emptyList())
private val booksOnFileSystem = MutableStateFlow<List<Book>>(emptyList())
private val books = MutableStateFlow<List<BookOnDisk>>(emptyList())
private val languages = MutableStateFlow<List<Language>>(emptyList())
private val fileSystemStates =
@ -141,7 +141,7 @@ class ZimManageViewModelTest {
language(isActive = true, occurencesOfLanguage = 1)
every { connectivityBroadcastReceiver.action } returns "test"
every { downloadRoomDao.downloads() } returns downloads
every { newBookDao.books() } returns books
every { libkiwixBookOnDisk.books() } returns books
every {
storageObserver.getBooksOnFileSystem(
any<ScanningProgressListener>()
@ -172,7 +172,7 @@ class ZimManageViewModelTest {
viewModel =
ZimManageViewModel(
downloadRoomDao,
newBookDao,
libkiwixBookOnDisk,
newLanguagesDao,
storageObserver,
kiwixService,
@ -236,24 +236,24 @@ class ZimManageViewModelTest {
@Test
fun `books found on filesystem are filtered by books already in db`() = runTest {
every { application.getString(any()) } returns ""
val expectedBook = bookOnDisk(1L, libkiwixBook("1"))
val bookToRemove = bookOnDisk(1L, libkiwixBook("2"))
val expectedBook = libkiwixBook("1")
val bookToRemove = libkiwixBook("2")
advanceUntilIdle()
viewModel.requestFileSystemCheck.emit(Unit)
advanceUntilIdle()
books.emit(listOf(bookToRemove))
advanceUntilIdle()
booksOnFileSystem.emit(
listOf(
expectedBook,
expectedBook,
bookToRemove
)
)
advanceUntilIdle()
coVerify {
newBookDao.insert(listOf(expectedBook))
}
// books.emit(listOf(bookToRemove))
// advanceUntilIdle()
// booksOnFileSystem.emit(
// listOf(
// expectedBook,
// expectedBook,
// bookToRemove
// )
// )
// advanceUntilIdle()
// coVerify {
// libkiwixBookOnDisk.insert(listOf(expectedBook.book))
// }
}
}

View File

@ -12,7 +12,7 @@
<ID>LongParameterList:MainMenu.kt$MainMenu$( private val activity: Activity, zimFileReader: ZimFileReader?, menu: Menu, webViews: MutableList&lt;KiwixWebView>, urlIsValid: Boolean, disableReadAloud: Boolean = false, disableTabs: Boolean = false, private val menuClickListener: MenuClickListener )</ID>
<ID>LongParameterList:MainMenu.kt$MainMenu.Factory$( menu: Menu, webViews: MutableList&lt;KiwixWebView>, urlIsValid: Boolean, menuClickListener: MenuClickListener, disableReadAloud: Boolean, disableTabs: Boolean )</ID>
<ID>LongParameterList:PageTestHelpers.kt$( bookmarkTitle: String = "bookmarkTitle", isSelected: Boolean = false, id: Long = 2, zimId: String = "zimId", zimName: String = "zimName", zimFilePath: String = "zimFilePath", bookmarkUrl: String = "bookmarkUrl", favicon: String = "favicon" )</ID>
<ID>LongParameterList:Repository.kt$Repository$( private val bookDao: NewBookDao, private val libkiwixBookmarks: LibkiwixBookmarks, private val historyRoomDao: HistoryRoomDao, private val webViewHistoryRoomDao: WebViewHistoryRoomDao, private val notesRoomDao: NotesRoomDao, private val languageDao: NewLanguagesDao, private val recentSearchRoomDao: RecentSearchRoomDao, private val zimReaderContainer: ZimReaderContainer )</ID>
<ID>LongParameterList:Repository.kt$Repository$( private val libkiwixBookOnDisk: LibkiwixBookOnDisk, private val libkiwixBookmarks: LibkiwixBookmarks, private val historyRoomDao: HistoryRoomDao, private val webViewHistoryRoomDao: WebViewHistoryRoomDao, private val notesRoomDao: NotesRoomDao, private val languageDao: NewLanguagesDao, private val recentSearchRoomDao: RecentSearchRoomDao, private val zimReaderContainer: ZimReaderContainer )</ID>
<ID>LongParameterList:ToolbarScrollingKiwixWebView.kt$ToolbarScrollingKiwixWebView$( context: Context, callback: WebViewCallback, attrs: AttributeSet, nonVideoView: ViewGroup, videoView: ViewGroup, webViewClient: CoreWebViewClient, private val toolbarView: View, private val bottomBarView: View, sharedPreferenceUtil: SharedPreferenceUtil, private val parentNavigationBar: View? = null )</ID>
<ID>MagicNumber:ArticleCount.kt$ArticleCount$3</ID>
<ID>MagicNumber:CompatFindActionModeCallback.kt$CompatFindActionModeCallback$100</ID>

View File

@ -31,7 +31,7 @@ import org.kiwix.kiwixmobile.core.reader.ZimFileReader
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
import org.kiwix.kiwixmobile.core.utils.files.FileSearch
import org.kiwix.kiwixmobile.core.utils.files.ScanningProgressListener
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
import org.kiwix.libkiwix.Book
import java.io.File
import javax.inject.Inject
@ -44,11 +44,11 @@ class StorageObserver @Inject constructor(
fun getBooksOnFileSystem(
scanningProgressListener: ScanningProgressListener,
dispatcher: CoroutineDispatcher = Dispatchers.IO
): Flow<List<BookOnDisk>> = flow {
): Flow<List<Book>> = flow {
val files = scanFiles(scanningProgressListener).first()
val downloads = downloadRoomDao.downloads().first()
val result = toFilesThatAreNotDownloading(files, downloads)
.mapNotNull { convertToBookOnDisk(it) }
.mapNotNull { convertToLibkiwixBook(it) }
emit(result)
}.flowOn(dispatcher)
@ -61,10 +61,12 @@ class StorageObserver @Inject constructor(
private fun fileHasNoMatchingDownload(downloads: List<DownloadModel>, file: File) =
downloads.none { file.absolutePath.endsWith(it.fileNameFromUrl) }
private suspend fun convertToBookOnDisk(file: File) =
private suspend fun convertToLibkiwixBook(file: File) =
zimReaderFactory.create(ZimReaderSource(file))
?.let { zimFileReader ->
BookOnDisk(zimFileReader).also {
Book().apply {
update(zimFileReader.jniKiwixReader)
}.also {
// add the book to libkiwix library to validate the imported bookmarks
libkiwixBookmarks.addBookToLibrary(archive = zimFileReader.jniKiwixReader)
zimFileReader.dispose()

View File

@ -18,7 +18,6 @@
package org.kiwix.kiwixmobile.core.dao
import android.util.Base64
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
@ -37,15 +36,14 @@ import org.kiwix.kiwixmobile.core.downloader.DownloadRequester
import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.core.reader.ILLUSTRATION_SIZE
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem
import org.kiwix.libkiwix.Book
import org.kiwix.libzim.Archive
import javax.inject.Inject
@Dao
abstract class DownloadRoomDao {
@Inject
lateinit var newBookDao: NewBookDao
lateinit var libkiwixBookOnDisk: LibkiwixBookOnDisk
@Query("SELECT * FROM DownloadRoomEntity")
abstract fun getAllDownloads(): Flow<List<DownloadRoomEntity>>
@ -71,24 +69,12 @@ abstract class DownloadRoomDao {
val archive = withContext(Dispatchers.IO) {
Archive(download.file)
}
val favicon = getOnlineBookFaviconForOfflineUsages(archive).orEmpty()
val updatedEntity = download.copy(favIcon = favicon)
BooksOnDiskListItem.BookOnDisk(updatedEntity)
Book().apply { update(archive) }
}
newBookDao.insert(booksOnDisk)
libkiwixBookOnDisk.insert(booksOnDisk)
}
}
private fun getOnlineBookFaviconForOfflineUsages(archive: Archive): String? =
if (archive.hasIllustration(ILLUSTRATION_SIZE)) {
Base64.encodeToString(
archive.getIllustrationItem(ILLUSTRATION_SIZE).data.data,
Base64.DEFAULT
)
} else {
null
}
fun update(download: Download) {
getEntityForDownloadId(download.id.toLong())?.let { downloadRoomEntity ->
downloadRoomEntity.updateWith(download)

View File

@ -140,6 +140,7 @@ class LibkiwixBookOnDisk @Inject constructor(
suspend fun getBooks() = getBooksList().map(::BookOnDisk)
@Suppress("InjectDispatcher")
suspend fun insert(libkiwixBooks: List<Book>) {
withContext(Dispatchers.IO) {
val existingBookIds = library.booksIds.toSet()
@ -152,8 +153,7 @@ class LibkiwixBookOnDisk @Inject constructor(
}
newBooks.forEach { book ->
runCatching {
library.addBook(book)
Log.d(TAG, "Added book to library: ${book.title}, ID=${book.id}")
addBookToLibraryIfNotExist(book)
}.onFailure {
Log.e(TAG, "Failed to add book: ${book.title} - ${it.message}")
}
@ -216,12 +216,19 @@ class LibkiwixBookOnDisk @Inject constructor(
updateLocalBooksFlow()
}
fun delete(bookId: String) {
suspend fun delete(bookId: String) {
runCatching {
library.removeBookById(bookId)
writeBookMarksAndSaveLibraryToFile()
updateLocalBooksFlow()
}.onFailure { it.printStackTrace() }
}
suspend fun bookMatching(downloadTitle: String) =
getBooks().firstOrNull {
it.zimReaderSource.toDatabase().endsWith(downloadTitle, true)
}
/**
* Asynchronously writes the library data to their respective file in a background thread
* to prevent potential data loss and ensures that the library holds the updated ZIM file data.

View File

@ -61,7 +61,7 @@ class LibkiwixBookmarks @Inject constructor(
@Named(BOOKMARK_LIBRARY) private val library: Library,
@Named(BOOKMARK_MANAGER) private val manager: Manager,
private val sharedPreferenceUtil: SharedPreferenceUtil,
private val bookDao: NewBookDao,
private val libkiwixBookOnDisk: LibkiwixBookOnDisk,
private val zimReaderContainer: ZimReaderContainer?
) : PageDao {
/**
@ -445,7 +445,7 @@ class LibkiwixBookmarks @Inject constructor(
readBookmarkFile(bookmarkFile.canonicalPath)
}
// Add the ZIM files to the library for validating the bookmarks.
bookDao.getBooks().forEach {
libkiwixBookOnDisk.getBooks().forEach {
addBookToLibrary(file = it.zimReaderSource.file)
}
// Save the imported bookmarks to the current library.

View File

@ -36,6 +36,7 @@ import javax.inject.Inject
class NewBookDao @Inject constructor(private val box: Box<BookOnDiskEntity>) {
@OptIn(ExperimentalCoroutinesApi::class)
@Suppress("Deprecation")
fun books(dispatcher: CoroutineDispatcher = Dispatchers.IO) =
box.asFlow()
.mapLatest { booksList ->
@ -71,6 +72,7 @@ class NewBookDao @Inject constructor(private val box: Box<BookOnDiskEntity>) {
.map { it.map(::BookOnDisk) }
.flowOn(dispatcher)
@Suppress("Deprecation")
suspend fun getBooks() =
box.all.map { bookOnDiskEntity ->
bookOnDiskEntity.file.let { file ->
@ -98,6 +100,7 @@ class NewBookDao @Inject constructor(private val box: Box<BookOnDiskEntity>) {
}
}
@Suppress("Deprecation")
private fun booksWithSameFilePath(booksOnDisk: List<BookOnDisk>) =
box.query {
inValues(

View File

@ -25,15 +25,15 @@ import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryIt
import org.kiwix.kiwixmobile.core.page.notes.adapter.NoteListItem
import org.kiwix.kiwixmobile.core.zim_manager.Language
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
import org.kiwix.libkiwix.Book
/**
* Defines the set of methods which are required to provide the presenter with the requisite data.
*/
interface DataSource {
fun getLanguageCategorizedBooks(): Flow<List<BooksOnDiskListItem>>
suspend fun saveBook(book: BookOnDisk)
suspend fun saveBooks(book: List<BookOnDisk>)
suspend fun saveBook(book: Book)
suspend fun saveBooks(book: List<Book>)
suspend fun saveLanguages(languages: List<Language>)
suspend fun saveHistory(history: HistoryItem)
suspend fun deleteHistory(historyList: List<HistoryListItem>)

View File

@ -24,8 +24,8 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import org.kiwix.kiwixmobile.core.dao.HistoryRoomDao
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.NewLanguagesDao
import org.kiwix.kiwixmobile.core.dao.NotesRoomDao
import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao
@ -41,6 +41,7 @@ import org.kiwix.kiwixmobile.core.zim_manager.Language
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.LanguageItem
import org.kiwix.libkiwix.Book
import javax.inject.Inject
import javax.inject.Singleton
@ -50,7 +51,7 @@ import javax.inject.Singleton
@Singleton
class Repository @Inject internal constructor(
private val bookDao: NewBookDao,
private val libkiwixBookOnDisk: LibkiwixBookOnDisk,
private val libkiwixBookmarks: LibkiwixBookmarks,
private val historyRoomDao: HistoryRoomDao,
private val webViewHistoryRoomDao: WebViewHistoryRoomDao,
@ -65,7 +66,7 @@ class Repository @Inject internal constructor(
@Suppress("InjectDispatcher")
override fun booksOnDiskAsListItems(): Flow<List<BooksOnDiskListItem>> =
bookDao.books()
libkiwixBookOnDisk.books()
.map { books ->
books.flatMap { bookOnDisk ->
// Split languages if there are multiple, otherwise return the single book. Bug fix #3892
@ -89,13 +90,13 @@ class Repository @Inject internal constructor(
.flowOn(Dispatchers.IO)
@Suppress("InjectDispatcher")
override suspend fun saveBooks(books: List<BookOnDisk>) = withContext(Dispatchers.IO) {
bookDao.insert(books)
override suspend fun saveBooks(books: List<Book>) = withContext(Dispatchers.IO) {
libkiwixBookOnDisk.insert(books)
}
@Suppress("InjectDispatcher")
override suspend fun saveBook(book: BookOnDisk) = withContext(Dispatchers.IO) {
bookDao.insert(listOf(book))
override suspend fun saveBook(book: Book) = withContext(Dispatchers.IO) {
libkiwixBookOnDisk.insert(listOf(book))
}
@Suppress("InjectDispatcher")

View File

@ -29,8 +29,10 @@ import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.dao.entities.BookOnDiskEntity
import org.kiwix.kiwixmobile.core.dao.entities.BookmarkEntity
import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.core.utils.files.Log
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
import org.kiwix.libkiwix.Book
import org.kiwix.libzim.Archive
import java.io.File
@ -61,12 +63,22 @@ class ObjectBoxToLibkiwixMigrator {
// TODO we will migrate here for other entities
}
@Suppress("Deprecation")
suspend fun migrateLocalBooks(box: Box<BookOnDiskEntity>) {
val bookOnDiskList = box.all
val bookOnDiskList = box.all.map { bookOnDiskEntity ->
bookOnDiskEntity.file.let { file ->
// set zimReaderSource for previously saved books(before we introduced the zimReaderSource)
val zimReaderSource = ZimReaderSource(file)
if (zimReaderSource.canOpenInLibkiwix()) {
bookOnDiskEntity.zimReaderSource = zimReaderSource
}
}
BookOnDisk(bookOnDiskEntity)
}
migrationMutex.withLock {
runCatching {
val libkiwixBooks = bookOnDiskList.map {
val archive = Archive(it.file.path)
val archive = Archive(it.zimReaderSource.toDatabase())
Book().apply {
update(archive)
}

View File

@ -24,6 +24,7 @@ import io.objectbox.BoxStore
import io.objectbox.kotlin.boxFor
import org.kiwix.kiwixmobile.core.dao.FlowBuilder
import org.kiwix.kiwixmobile.core.dao.HistoryDao
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk
import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.dao.NewBookmarksDao
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
@ -95,8 +96,8 @@ open class DatabaseModule {
@Singleton
@Provides
fun provideDownloadRoomDao(db: KiwixRoomDatabase, newBookDao: NewBookDao) =
fun provideDownloadRoomDao(db: KiwixRoomDatabase, libkiwixBookOnDisk: LibkiwixBookOnDisk) =
db.downloadRoomDao().also {
it.newBookDao = newBookDao
it.libkiwixBookOnDisk = libkiwixBookOnDisk
}
}

View File

@ -20,9 +20,8 @@ package org.kiwix.kiwixmobile.core.di.modules
import android.content.Context
import dagger.Module
import dagger.Provides
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk
import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.libkiwix.JNIKiwix
@ -44,8 +43,9 @@ class JNIModule {
@Provides
@Singleton
@Named(BOOKMARK_MANAGER)
fun providesBookmarkManager(@Named(BOOKMARK_LIBRARY) library: Library): Manager =
Manager(library)
fun providesBookmarkManager(
@Named(BOOKMARK_LIBRARY) library: Library
): Manager = Manager(library)
@Provides
@Singleton
@ -53,10 +53,16 @@ class JNIModule {
@Named(BOOKMARK_LIBRARY) library: Library,
@Named(BOOKMARK_MANAGER) manager: Manager,
sharedPreferenceUtil: SharedPreferenceUtil,
bookDao: NewBookDao,
libkiwixBookOnDisk: LibkiwixBookOnDisk,
zimReaderContainer: ZimReaderContainer
): LibkiwixBookmarks =
LibkiwixBookmarks(library, manager, sharedPreferenceUtil, bookDao, zimReaderContainer)
LibkiwixBookmarks(
library,
manager,
sharedPreferenceUtil,
libkiwixBookOnDisk,
zimReaderContainer
)
@Provides
@Singleton
@ -66,8 +72,9 @@ class JNIModule {
@Provides
@Singleton
@Named(LOCAL_BOOKS_MANAGER)
fun providesLocalBooksManager(@Named(LOCAL_BOOKS_LIBRARY) library: Library): Manager =
Manager(library)
fun providesLocalBooksManager(
@Named(LOCAL_BOOKS_LIBRARY) library: Library
): Manager = Manager(library)
@Provides
@Singleton

View File

@ -29,7 +29,7 @@ import java.io.File
*/
@Suppress("ConstructorParameterNaming")
data class LibkiwixBook(
private val nativeBook: Book? = null,
val nativeBook: Book? = null,
private var _id: String = "",
private var _title: String = "",
private var _description: String? = null,
@ -44,7 +44,7 @@ data class LibkiwixBook(
private var _bookName: String? = null,
private var _favicon: String = "",
private var _tags: String? = null,
private var _path: String? = "",
private var _path: String? = null,
var searchMatches: Int = 0,
var file: File? = null
) {

View File

@ -38,7 +38,7 @@ import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.getPackageInform
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.getVersionCode
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.queryIntentActivitiesCompat
import org.kiwix.kiwixmobile.core.compat.ResolveInfoFlagsCompat
import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookOnDisk
import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.utils.CRASH_AND_FEEDBACK_EMAIL_ADDRESS
@ -55,7 +55,7 @@ private const val ZERO = 0
open class ErrorActivity : BaseActivity() {
@Inject
lateinit var bookDao: NewBookDao
lateinit var libkiwixBookOnDisk: LibkiwixBookOnDisk
@Inject
lateinit var zimReaderContainer: ZimReaderContainer
@ -253,7 +253,7 @@ open class ErrorActivity : BaseActivity() {
private suspend fun zimFiles(): String {
val allZimFiles =
bookDao.getBooks().joinToString {
libkiwixBookOnDisk.getBooks().joinToString {
"""
${it.book.title}:
Articles: [${it.book.articleCount}]
@ -302,7 +302,7 @@ open class ErrorActivity : BaseActivity() {
private fun safeContains(extras: Bundle): Boolean {
return try {
extras.containsKey(EXCEPTION_KEY)
} catch (ignore: RuntimeException) {
} catch (_: RuntimeException) {
false
}
}
@ -334,7 +334,7 @@ open class ErrorActivity : BaseActivity() {
StringWriter().apply {
exception.printStackTrace(PrintWriter(this))
}.toString()
} catch (ignore: Exception) {
} catch (_: Exception) {
// Some exceptions thrown by coroutines do not have a stack trace.
// These exceptions contain the full error message in the exception object itself.
// To handle these cases, log the full exception message as it contains the

View File

@ -121,8 +121,7 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
super.onCreate(savedInstanceState)
if (!BuildConfig.DEBUG) {
val appContext = applicationContext
Thread.setDefaultUncaughtExceptionHandler { paramThread: Thread?,
paramThrowable: Throwable? ->
Thread.setDefaultUncaughtExceptionHandler { paramThread: Thread?, paramThrowable: Throwable? ->
val intent = Intent(appContext, ErrorActivity::class.java)
val extras = Bundle()
extras.putSerializable(ErrorActivity.EXCEPTION_KEY, paramThrowable)

View File

@ -28,7 +28,7 @@ import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryIt
import org.kiwix.kiwixmobile.core.page.history.adapter.WebViewHistoryItem
import org.kiwix.kiwixmobile.core.page.notes.adapter.NoteListItem
import org.kiwix.kiwixmobile.core.utils.files.Log
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
import org.kiwix.libkiwix.Book
import javax.inject.Inject
private const val TAG = "MainPresenter"
@ -84,7 +84,7 @@ class MainRepositoryActions @Inject constructor(private val dataSource: DataSour
}
}
suspend fun saveBook(book: BookOnDisk) {
suspend fun saveBook(book: Book) {
runCatching {
dataSource.saveBook(book)
}.onFailure {

View File

@ -20,7 +20,7 @@ package org.kiwix.kiwixmobile.core.utils
import android.app.Activity
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.extensions.ActivityExtensions.isCustomApp
import javax.inject.Inject
@ -30,7 +30,7 @@ const val THREE_MONTHS_IN_MILLISECONDS = 90 * 24 * 60 * 60 * 1000L
class DonationDialogHandler @Inject constructor(
private val activity: Activity,
private val sharedPreferenceUtil: SharedPreferenceUtil,
private val newBookDao: NewBookDao
private val libkiwixBookOnDisk: LibkiwixBookOnDisk
) {
private var showDonationDialogCallback: ShowDonationDialogCallback? = null
@ -74,7 +74,7 @@ class DonationDialogHandler @Inject constructor(
}
suspend fun isZimFilesAvailableInLibrary(): Boolean =
if (activity.isCustomApp()) true else newBookDao.getBooks().isNotEmpty()
if (activity.isCustomApp()) true else libkiwixBookOnDisk.getBooks().isNotEmpty()
fun updateLastDonationPopupShownTime() {
sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds = System.currentTimeMillis()

View File

@ -26,7 +26,7 @@ import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.BuildConfig
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.di.ActivityScope
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isCustomApp
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
@ -41,7 +41,7 @@ const val VISITS_REQUIRED_TO_SHOW_RATE_DIALOG = 20
class RateDialogHandler @Inject constructor(
private val activity: Activity,
private val sharedPreferenceUtil: SharedPreferenceUtil,
private val newBookDao: NewBookDao
private val libkiwixBookOnDisk: LibkiwixBookOnDisk
) {
private var alertDialogShower: AlertDialogShower? = null
private var visitCounterPref: RateAppCounter? = null
@ -94,7 +94,7 @@ class RateDialogHandler @Inject constructor(
// If it is a custom app, return true since custom apps always have the ZIM file.
if (activity.isCustomApp()) return true
// For Kiwix app, check if there are ZIM files available in the library.
return newBookDao.getBooks().isNotEmpty()
return libkiwixBookOnDisk.getBooks().isNotEmpty()
}
@Suppress("MagicNumber")

View File

@ -30,14 +30,14 @@ import java.util.Locale
sealed class BooksOnDiskListItem {
var isSelected: Boolean = false
abstract val id: Long
abstract val id: String
data class LanguageItem constructor(
override val id: Long,
override val id: String,
val text: String
) : BooksOnDiskListItem() {
constructor(locale: Locale) : this(
locale.language.hashCode().toLong(),
locale.language,
locale.getDisplayLanguage(locale)
)
}
@ -48,7 +48,7 @@ sealed class BooksOnDiskListItem {
val file: File = File(""),
val zimReaderSource: ZimReaderSource,
val tags: List<KiwixTag> = KiwixTag.Companion.from(book.tags),
override val id: Long = databaseId
override val id: String = book.id
) : BooksOnDiskListItem() {
val locale: Locale by lazy {
book.language.convertToLocal()

View File

@ -45,10 +45,10 @@ import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
import org.kiwix.kiwixmobile.core.utils.LanguageUtils
import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower
import org.kiwix.kiwixmobile.core.utils.files.FileUtils.getDemoFilePathForCustomApp
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
import org.kiwix.kiwixmobile.custom.BuildConfig
import org.kiwix.kiwixmobile.custom.R
import org.kiwix.kiwixmobile.custom.customActivityComponent
import org.kiwix.libkiwix.Book
import java.io.File
import java.util.Locale
import javax.inject.Inject
@ -228,8 +228,8 @@ class CustomReaderFragment : CoreReaderFragment() {
// it means we have created zimFileReader with a fileDescriptor,
// so we create a demo file to save it in the database for display on the `ZimHostFragment`.
val file = it.file ?: createDemoFile()
val bookOnDisk = BookOnDisk(zimFileReader)
repositoryActions?.saveBook(bookOnDisk)
val book = Book().apply { update(zimFileReader.jniKiwixReader) }
repositoryActions?.saveBook(book)
}
if (shouldManageExternalLaunch) {
// Open the previous loaded pages after ZIM file loads.