Added the com.squareup.retrofit2:converter-scalars converter to Retrofit to parse OPDS network responses.

* Created `LibkiwixBook` to wrap the `Book` class from `libkiwix`, allowing us to set custom values from the database and manage books received from the `OPDS` stream. This wrapper is necessary because the original Book class does not provide setters. We'll use `LibkiwixBook` throughout the codebase.
* Refactored the codebase to replace `LibraryNetworkEntity.Book` with `LibkiwixBook`.
This commit is contained in:
MohitMaliFtechiz 2025-06-04 22:16:09 +05:30
parent 20722fe15b
commit 7a10528d19
25 changed files with 249 additions and 93 deletions

View File

@ -82,7 +82,7 @@ import org.kiwix.kiwixmobile.core.downloader.downloadManager.DEFAULT_INT_VALUE
import org.kiwix.kiwixmobile.core.downloader.downloadManager.FIVE
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.core.extensions.calculateSearchMatches
import org.kiwix.kiwixmobile.core.extensions.registerReceiver
import org.kiwix.kiwixmobile.core.ui.components.ONE
@ -131,6 +131,7 @@ const val MAX_PROGRESS = 100
const val THREE = 3
const val FOUR = 4
@Suppress("LongParameterList")
class ZimManageViewModel @Inject constructor(
private val downloadDao: DownloadRoomDao,
private val bookDao: NewBookDao,
@ -168,7 +169,7 @@ class ZimManageViewModel @Inject constructor(
val onlineLibraryDownloading = MutableStateFlow(false)
val shouldShowWifiOnlyDialog = MutableLiveData<Boolean>()
val networkStates = MutableLiveData<NetworkState>()
val networkLibrary = MutableSharedFlow<List<org.kiwix.libkiwix.Book>>(replay = 0)
val networkLibrary = MutableSharedFlow<List<LibkiwixBook>>(replay = 0)
val requestFileSystemCheck = MutableSharedFlow<Unit>(replay = 0)
val fileSelectActions = MutableSharedFlow<FileSelectActions>()
private val requestDownloadLibrary = MutableSharedFlow<Unit>(
@ -391,7 +392,7 @@ class ZimManageViewModel @Inject constructor(
@OptIn(ExperimentalCoroutinesApi::class)
private fun requestsAndConnectivityChangesToLibraryRequests(
library: MutableSharedFlow<List<org.kiwix.libkiwix.Book>>,
library: MutableSharedFlow<List<LibkiwixBook>>,
dispatcher: CoroutineDispatcher = Dispatchers.IO
) = requestDownloadLibrary.flatMapConcat {
connectivityBroadcastReceiver.networkStates
@ -442,12 +443,16 @@ class ZimManageViewModel @Inject constructor(
private fun downloadLibraryFlow(
kiwixService: KiwixService
): Flow<List<org.kiwix.libkiwix.Book>> = flow {
): Flow<List<LibkiwixBook>> = flow {
downloadProgress.postValue(context.getString(R.string.starting_downloading_remote_library))
val response = kiwixService.getLibrary()
downloadProgress.postValue(context.getString(R.string.parsing_remote_library))
onlineLibraryManager.parseOPDSStream(response, KIWIX_OPDS_LIBRARY_URL)
val isLibraryParsed = onlineLibraryManager.parseOPDSStream(response, KIWIX_OPDS_LIBRARY_URL)
if (isLibraryParsed) {
emit(onlineLibraryManager.getOnlineBooks())
} else {
emit(emptyList())
}
}
.retry(5)
.catch { e ->
@ -464,7 +469,7 @@ class ZimManageViewModel @Inject constructor(
private fun updateLibraryItems(
booksFromDao: Flow<List<BookOnDisk>>,
downloads: Flow<List<DownloadModel>>,
library: MutableSharedFlow<List<org.kiwix.libkiwix.Book>>,
library: MutableSharedFlow<List<LibkiwixBook>>,
languages: Flow<List<Language>>,
dispatcher: CoroutineDispatcher = Dispatchers.IO
) = viewModelScope.launch(dispatcher) {
@ -487,14 +492,14 @@ class ZimManageViewModel @Inject constructor(
val books = args[ZERO] as List<BookOnDisk>
val activeDownloads = args[ONE] as List<DownloadModel>
val languageList = args[TWO] as List<Language>
val libraryNetworkEntity = args[THREE] as List<org.kiwix.libkiwix.Book>
val libraryNetworkEntity = args[THREE] as List<LibkiwixBook>
val filter = args[FOUR] as String
val fileSystemState = args[FIVE] as FileSystemState
combineLibrarySources(
booksOnFileSystem = books,
activeDownloads = activeDownloads,
allLanguages = languageList,
libraryNetworkEntity = libraryNetworkEntity,
onlineBooks = libraryNetworkEntity,
filter = filter,
fileSystemState = fileSystemState
)
@ -509,7 +514,7 @@ class ZimManageViewModel @Inject constructor(
}
private fun updateLanguagesInDao(
library: MutableSharedFlow<List<org.kiwix.libkiwix.Book>>,
library: MutableSharedFlow<List<LibkiwixBook>>,
languages: Flow<List<Language>>,
dispatcher: CoroutineDispatcher = Dispatchers.IO
) =
@ -527,7 +532,7 @@ class ZimManageViewModel @Inject constructor(
.launchIn(viewModelScope)
private fun combineToLanguageList(
booksFromNetwork: List<org.kiwix.libkiwix.Book>,
booksFromNetwork: List<LibkiwixBook>,
allLanguages: List<Language>
) = when {
booksFromNetwork.isEmpty() -> {
@ -551,8 +556,8 @@ class ZimManageViewModel @Inject constructor(
)
}
private fun networkLanguageCounts(booksFromNetwork: List<org.kiwix.libkiwix.Book>) =
booksFromNetwork.mapNotNull { it.language }
private fun networkLanguageCounts(booksFromNetwork: List<LibkiwixBook>) =
booksFromNetwork.map { it.language }
.fold(
mutableMapOf<String, Int>()
) { acc, language -> acc.increment(language) }
@ -589,7 +594,7 @@ class ZimManageViewModel @Inject constructor(
booksOnFileSystem: List<BookOnDisk>,
activeDownloads: List<DownloadModel>,
allLanguages: List<Language>,
onlineBooks: List<org.kiwix.libkiwix.Book>,
onlineBooks: List<LibkiwixBook>,
filter: String,
fileSystemState: FileSystemState
): List<LibraryListItem> {
@ -634,7 +639,7 @@ class ZimManageViewModel @Inject constructor(
}
private fun createLibrarySection(
books: List<Book>,
books: List<LibkiwixBook>,
activeDownloads: List<DownloadModel>,
fileSystemState: FileSystemState,
sectionStringId: Int,
@ -648,7 +653,7 @@ class ZimManageViewModel @Inject constructor(
}
private fun applySearchFilter(
unDownloadedBooks: List<Book>,
unDownloadedBooks: List<LibkiwixBook>,
filter: String
) = if (filter.isEmpty()) {
unDownloadedBooks
@ -657,7 +662,7 @@ class ZimManageViewModel @Inject constructor(
unDownloadedBooks.filter { it.searchMatches > 0 }
}
private fun List<Book>.asLibraryItems(
private fun List<LibkiwixBook>.asLibraryItems(
activeDownloads: List<DownloadModel>,
fileSystemState: FileSystemState
) = map { book ->

View File

@ -24,7 +24,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.core.settings.StorageCalculator
import org.kiwix.kiwixmobile.zimManager.libraryView.adapter.LibraryListItem
import javax.inject.Inject
@ -49,6 +49,6 @@ class AvailableSpaceCalculator @Inject constructor(
}
}
suspend fun hasAvailableSpaceForBook(book: Book) =
suspend fun hasAvailableSpaceForBook(book: LibkiwixBook) =
book.size.toLong() * KB < storageCalculator.availableBytes()
}

View File

@ -24,14 +24,14 @@ import org.kiwix.kiwixmobile.core.downloader.model.Base64String
import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.core.downloader.model.DownloadState
import org.kiwix.kiwixmobile.core.downloader.model.Seconds
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.core.zim_manager.KiwixTag
import org.kiwix.kiwixmobile.zimManager.Fat32Checker
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.CanWrite4GbFile
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.CannotWrite4GbFile
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.NotEnoughSpaceFor4GbFile
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.DetectingFileSystem
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.NotEnoughSpaceFor4GbFile
sealed class LibraryListItem {
abstract val id: Long
@ -42,7 +42,7 @@ sealed class LibraryListItem {
) : LibraryListItem()
data class BookItem constructor(
val book: Book,
val book: LibkiwixBook,
val fileSystemState: FileSystemState,
val tags: List<KiwixTag> = KiwixTag.from(book.tags),
override val id: Long = book.id.hashCode().toLong()
@ -54,7 +54,7 @@ sealed class LibraryListItem {
}
companion object {
private fun Book.isLessThan4GB() =
private fun LibkiwixBook.isLessThan4GB() =
size.toLongOrNull() ?: 0L < Fat32Checker.FOUR_GIGABYTES_IN_KILOBYTES
}
}

View File

@ -63,6 +63,12 @@ object Libs {
const val tracing: String = "androidx.tracing:tracing:" + Versions.tracing
/**
* https://github.com/square/retrofit
*/
const val converter_scalars: String = "com.squareup.retrofit2:converter-scalars:" +
Versions.com_squareup_retrofit2
/**
* https://github.com/square/retrofit
*/

View File

@ -47,6 +47,11 @@ dependencies {
implementation(Libs.select_folder_document_file)
// Square
implementation(Libs.converter_scalars) {
exclude(group = "xpp3", module = "xpp3")
exclude(group = "stax", module = "stax-api")
exclude(group = "stax", module = "stax")
}
implementation(Libs.converter_simplexml) {
exclude(group = "xpp3", module = "xpp3")
exclude(group = "stax", module = "stax-api")

View File

@ -50,6 +50,7 @@
<ID>PackageNaming:TagsView.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID>
<ID>PackageNaming:ConnectivityBroadcastReceiver.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID>
<ID>PackageNaming:NetworkState.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID>
<ID>PackageNaming:OnlineLibraryManager.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID>
<ID>ReturnCount:FileUtils.kt$FileUtils$@JvmStatic fun getAllZimParts(book: Book): List&lt;File></ID>
<ID>ReturnCount:FileUtils.kt$FileUtils$@JvmStatic suspend fun getLocalFilePathByUri( context: Context, uri: Uri ): String?</ID>
<ID>ReturnCount:FileUtils.kt$FileUtils$@JvmStatic suspend fun hasPart(file: File): Boolean</ID>

View File

@ -33,7 +33,7 @@ import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity
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.LibraryNetworkEntity
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem
import javax.inject.Inject
@ -100,7 +100,7 @@ abstract class DownloadRoomDao {
fun addIfDoesNotExist(
url: String,
book: LibraryNetworkEntity.Book,
book: LibkiwixBook,
downloadRequester: DownloadRequester
) {
if (doesNotAlreadyExist(book)) {
@ -113,6 +113,6 @@ abstract class DownloadRoomDao {
}
}
private fun doesNotAlreadyExist(book: LibraryNetworkEntity.Book) =
private fun doesNotAlreadyExist(book: LibkiwixBook) =
count(book.id) == 0
}

View File

@ -20,7 +20,6 @@ package org.kiwix.kiwixmobile.core.dao
import android.os.Build
import android.os.Environment
import android.util.Base64
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -36,11 +35,11 @@ import org.kiwix.kiwixmobile.core.DarkModeConfig
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isCustomApp
import org.kiwix.kiwixmobile.core.extensions.deleteFile
import org.kiwix.kiwixmobile.core.extensions.getFavicon
import org.kiwix.kiwixmobile.core.extensions.isFileExist
import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.page.adapter.Page
import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem
import org.kiwix.kiwixmobile.core.reader.ILLUSTRATION_SIZE
import org.kiwix.kiwixmobile.core.reader.ZimFileReader
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
@ -285,10 +284,7 @@ class LibkiwixBookmarks @Inject constructor(
}
// Check if the book has an illustration of the specified size and encode it to Base64.
val favicon =
book?.getIllustration(ILLUSTRATION_SIZE)?.data?.let {
Base64.encodeToString(it, Base64.DEFAULT)
}
val favicon = book?.getFavicon()
val zimReaderSource = book?.path?.let { ZimReaderSource(File(it)) }
// Return the LibkiwixBookmarkItem, filtering out null results.

View File

@ -29,7 +29,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import org.kiwix.kiwixmobile.core.dao.entities.BookOnDiskEntity
import org.kiwix.kiwixmobile.core.dao.entities.BookOnDiskEntity_
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
import javax.inject.Inject
@ -124,7 +124,7 @@ class NewBookDao @Inject constructor(private val box: Box<BookOnDiskEntity>) {
}
@Suppress("UnsafeCallOnNullableType")
fun migrationInsert(books: List<Book>) {
fun migrationInsert(books: List<LibkiwixBook>) {
insert(books.map { BookOnDisk(book = it, zimReaderSource = ZimReaderSource(it.file!!)) })
}

View File

@ -22,7 +22,7 @@ import io.objectbox.annotation.Convert
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
import io.objectbox.converter.PropertyConverter
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource.Companion.fromDatabaseValue
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
@ -70,7 +70,7 @@ data class BookOnDiskEntity(
bookOnDisk.book.tags
)
fun toBook() = Book().apply {
fun toBook() = LibkiwixBook().apply {
id = bookId
title = this@BookOnDiskEntity.title
description = this@BookOnDiskEntity.description

View File

@ -20,12 +20,12 @@ package org.kiwix.kiwixmobile.core.dao.entities
import androidx.room.Entity
import androidx.room.PrimaryKey
import io.objectbox.annotation.Convert
import io.objectbox.converter.PropertyConverter
import com.tonyodev.fetch2.Download
import com.tonyodev.fetch2.Error
import com.tonyodev.fetch2.Status
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import io.objectbox.annotation.Convert
import io.objectbox.converter.PropertyConverter
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
@Entity
data class DownloadRoomEntity(
@ -56,7 +56,7 @@ data class DownloadRoomEntity(
val favIcon: String,
val tags: String? = null
) {
constructor(downloadId: Long, book: Book) : this(
constructor(downloadId: Long, book: LibkiwixBook) : this(
downloadId = downloadId,
bookId = book.id,
title = book.title,
@ -75,7 +75,7 @@ data class DownloadRoomEntity(
)
fun toBook() =
Book().apply {
LibkiwixBook().apply {
id = bookId
title = this@DownloadRoomEntity.title
description = this@DownloadRoomEntity.description

View File

@ -71,7 +71,7 @@ class Repository @Inject internal constructor(
// Split languages if there are multiple, otherwise return the single book. Bug fix #3892
if (bookOnDisk.book.language.contains(',')) {
bookOnDisk.book.language.split(',').map { lang ->
bookOnDisk.copy(book = bookOnDisk.book.copy(language = lang.trim()))
bookOnDisk.copy(book = bookOnDisk.book.copy(_language = lang.trim()))
}
} else {
listOf(bookOnDisk)

View File

@ -22,6 +22,7 @@ package org.kiwix.kiwixmobile.core.data.remote
import okhttp3.OkHttpClient
import org.kiwix.kiwixmobile.core.entity.MetaLinkNetworkEntity
import retrofit2.Retrofit
import retrofit2.converter.scalars.ScalarsConverterFactory
import retrofit2.http.GET
import retrofit2.http.Url
@ -41,6 +42,8 @@ interface KiwixService {
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(ScalarsConverterFactory.create())
// .addConverterFactory(SimpleXmlConverterFactory.create())
.build()
return retrofit.create(KiwixService::class.java)
}
@ -49,6 +52,6 @@ interface KiwixService {
companion object {
// To fetch the full OPDS catalog.
// TODO we will change this to pagination later once we migrate to OPDS properly.
const val OPDS_LIBRARY_NETWORK_PATH = "/entries?count=-1"
const val OPDS_LIBRARY_NETWORK_PATH = "entries?count=-1"
}
}

View File

@ -58,6 +58,7 @@ import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.search.viewmodel.SearchResultGenerator
import org.kiwix.kiwixmobile.core.utils.BookUtils
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.core.zim_manager.OnlineLibraryManager
import javax.inject.Singleton
@Singleton
@ -99,6 +100,7 @@ interface CoreComponent {
fun connectivityManager(): ConnectivityManager
fun objectBoxToLibkiwixMigrator(): ObjectBoxToLibkiwixMigrator
fun libkiwixBookmarks(): LibkiwixBookmarks
fun onlineLibraryManager(): OnlineLibraryManager
fun recentSearchRoomDao(): RecentSearchRoomDao
fun historyRoomDao(): HistoryRoomDao
fun webViewHistoryRoomDao(): WebViewHistoryRoomDao

View File

@ -28,7 +28,6 @@ import org.kiwix.kiwixmobile.core.zim_manager.OnlineLibraryManager
import org.kiwix.libkiwix.JNIKiwix
import org.kiwix.libkiwix.Library
import org.kiwix.libkiwix.Manager
import javax.inject.Named
import javax.inject.Singleton
@Module
@ -38,39 +37,27 @@ class JNIModule {
@Provides
@Singleton
@Named("bookmarks")
fun provideLibrary(): Library = Library()
@Provides
@Singleton
@Named("bookmarks")
fun providesManager(library: Library): Manager = Manager(library)
@Provides
@Singleton
fun providesLibkiwixBookmarks(
@Named("bookmarks") library: Library,
@Named("bookmarks") manager: Manager,
library: Library,
manager: Manager,
sharedPreferenceUtil: SharedPreferenceUtil,
bookDao: NewBookDao,
zimReaderContainer: ZimReaderContainer
): LibkiwixBookmarks =
LibkiwixBookmarks(library, manager, sharedPreferenceUtil, bookDao, zimReaderContainer)
@Provides
@Singleton
@Named("onlineLibrary")
fun provideOnlineLibrary(): Library = Library()
@Provides
@Singleton
@Named("onlineLibrary")
fun providesOnlineManager(library: Library): Manager = Manager(library)
@Provides
@Singleton
fun provideOnlineLibraryParser(
@Named("onlineLibrary") library: Library,
@Named("onlineLibrary") manager: Manager
) = OnlineLibraryManager(library, manager)
library: Library,
manager: Manager
): OnlineLibraryManager = OnlineLibraryManager(library, manager)
}

View File

@ -38,7 +38,7 @@ const val CONNECTION_TIMEOUT = 10L
const val READ_TIMEOUT = 300L
const val CALL_TIMEOUT = 300L
const val USER_AGENT = "kiwix-android-version:${BuildConfig.VERSION_CODE}"
const val KIWIX_OPDS_LIBRARY_URL = "https://opds.library.kiwix.org/v2"
const val KIWIX_OPDS_LIBRARY_URL = "https://opds.library.kiwix.org/v2/"
@Module
class NetworkModule {

View File

@ -17,10 +17,10 @@
*/
package org.kiwix.kiwixmobile.core.downloader
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
interface Downloader {
fun download(book: LibraryNetworkEntity.Book)
fun download(book: LibkiwixBook)
fun cancelDownload(downloadId: Long)
fun retryDownload(downloadId: Long)
fun pauseResumeDownload(downloadId: Long, isPause: Boolean)

View File

@ -23,8 +23,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
import org.kiwix.kiwixmobile.core.data.remote.KiwixService
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import javax.inject.Inject
class DownloaderImpl @Inject constructor(
@ -33,7 +32,7 @@ class DownloaderImpl @Inject constructor(
private val kiwixService: KiwixService
) : Downloader {
@Suppress("InjectDispatcher")
override fun download(book: LibraryNetworkEntity.Book) {
override fun download(book: LibkiwixBook) {
CoroutineScope(Dispatchers.IO).launch {
runCatching {
urlProvider(book)?.let {
@ -46,7 +45,7 @@ class DownloaderImpl @Inject constructor(
}
@Suppress("UnsafeCallOnNullableType")
private suspend fun urlProvider(book: Book): String? =
private suspend fun urlProvider(book: LibkiwixBook): String? =
if (book.url?.endsWith("meta4") == true) {
kiwixService.getMetaLinks(book.url!!)?.relevantUrl?.value
} else {

View File

@ -17,10 +17,10 @@
*/
package org.kiwix.kiwixmobile.core.downloader.model
import com.tonyodev.fetch2.Status
import com.tonyodev.fetch2.Error
import com.tonyodev.fetch2.Status
import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.core.utils.StorageUtils
data class DownloadModel(
@ -33,7 +33,7 @@ data class DownloadModel(
val state: Status,
val error: Error,
val progress: Int,
val book: Book
val book: LibkiwixBook
) {
val bytesRemaining: Long by lazy { totalSizeOfDownload - bytesDownloaded }
val fileNameFromUrl: String by lazy { StorageUtils.getFileNameFromUrl(book.url) }

View File

@ -0,0 +1,145 @@
/*
* Kiwix Android
* Copyright (c) 2025 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.core.entity
import org.kiwix.kiwixmobile.core.extensions.getFavicon
import org.kiwix.libkiwix.Book
import java.io.File
/**
* Wrapper around libkiwix's [Book] that allows setting custom values (e.g. from DB or UI),
* while still falling back to the original [nativeBook]'s properties when not provided.
*/
@Suppress("ConstructorParameterNaming")
data class LibkiwixBook(
private val nativeBook: Book? = null,
private var _id: String = "",
private var _title: String = "",
private var _description: String? = null,
private var _language: String = "",
private var _creator: String = "",
private var _publisher: String = "",
private var _date: String = "",
private var _url: String? = null,
private var _articleCount: String? = null,
private var _mediaCount: String? = null,
private var _size: String = "",
private var _bookName: String? = null,
private var _favicon: String = "",
private var _tags: String? = null,
var searchMatches: Int = 0,
var file: File? = null
) {
var id: String
get() = _id.ifEmpty { nativeBook?.id.orEmpty() }
set(id) {
_id = id
}
var title: String
get() = _title.ifEmpty { nativeBook?.title.orEmpty() }
set(title) {
_title = title
}
var description: String?
get() = _description ?: nativeBook?.description
set(description) {
_description = description
}
var language: String
get() = _language.ifEmpty { nativeBook?.language.orEmpty() }
set(language) {
_language = language
}
var creator: String
get() = _creator.ifEmpty { nativeBook?.creator.orEmpty() }
set(creator) {
_creator = creator
}
var publisher: String
get() = _publisher.ifEmpty { nativeBook?.publisher.orEmpty() }
set(publisher) {
_publisher = publisher
}
var date: String
get() = _date.ifEmpty { nativeBook?.date.orEmpty() }
set(date) {
_date = date
}
var url: String?
get() = _url ?: nativeBook?.url
set(url) {
_url = url
}
var articleCount: String?
get() = _articleCount ?: nativeBook?.articleCount?.toString()
set(articleCount) {
_articleCount = articleCount
}
var mediaCount: String?
get() = _mediaCount ?: nativeBook?.mediaCount?.toString()
set(mediaCount) {
_mediaCount = mediaCount
}
var size: String
get() = _size.ifEmpty { nativeBook?.size?.toString().orEmpty() }
set(size) {
_size = size
}
var bookName: String?
get() = _bookName ?: nativeBook?.name
set(bookName) {
_bookName = bookName
}
var favicon: String
get() = _favicon.ifEmpty { nativeBook?.getFavicon().orEmpty() }
set(favicon) {
_favicon = favicon
}
var tags: String?
get() = _tags ?: nativeBook?.tags
set(tags) {
_tags = tags
}
// Two books are equal if their ids match
override fun equals(other: Any?): Boolean {
if (other is LibkiwixBook) {
if (other.id == id) {
return true
}
}
return false
}
// Only use the book's id to generate a hash code
override fun hashCode(): Int = id.hashCode()
}

View File

@ -18,30 +18,32 @@
package org.kiwix.kiwixmobile.core.extensions
import android.util.Base64
import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.core.reader.ILLUSTRATION_SIZE
import org.kiwix.kiwixmobile.core.utils.BookUtils
import org.kiwix.kiwixmobile.core.utils.NetworkUtils
import org.kiwix.libkiwix.Book
fun Book.calculateSearchMatches(
fun LibkiwixBook.calculateSearchMatches(
filter: String,
bookUtils: BookUtils
) {
val searchableText = buildSearchableText(bookUtils)
searchMatches = filter.split("\\s+")
.foldRight(
0,
{ filterWord, acc ->
0
) { filterWord, acc ->
if (searchableText.contains(filterWord, true)) {
acc + 1
} else {
acc
}
}
)
}
fun Book.buildSearchableText(bookUtils: BookUtils): String =
fun LibkiwixBook.buildSearchableText(bookUtils: BookUtils): String =
StringBuilder().apply {
append(title)
append("|")
@ -54,3 +56,7 @@ fun Book.buildSearchableText(bookUtils: BookUtils): String =
append("|")
}
}.toString()
fun Book.getFavicon(): String? = getIllustration(ILLUSTRATION_SIZE)?.data?.let {
Base64.encodeToString(it, Base64.DEFAULT)
}

View File

@ -28,7 +28,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.DarkModeConfig
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.core.main.UNINITIALISER_ADDRESS
import org.kiwix.kiwixmobile.core.main.UNINITIALISE_HTML
import org.kiwix.kiwixmobile.core.reader.ZimFileReader.Companion.CONTENT_PREFIX
@ -377,7 +377,7 @@ class ZimFileReader constructor(
@Suppress("ExplicitThis") // this@ZimFileReader.name is required
fun toBook() =
Book().apply {
LibkiwixBook().apply {
title = this@ZimFileReader.title
id = this@ZimFileReader.id
size = "$fileSize"

View File

@ -18,11 +18,12 @@
package org.kiwix.kiwixmobile.core.zim_manager
import org.kiwix.libkiwix.Book
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.libkiwix.Library
import org.kiwix.libkiwix.Manager
import javax.inject.Inject
class OnlineLibraryManager(
class OnlineLibraryManager @Inject constructor(
val library: Library,
val manager: Manager
) {
@ -33,12 +34,12 @@ class OnlineLibraryManager(
it.printStackTrace()
}.isSuccess
suspend fun getOnlineBooks(): List<Book> {
val onlineBooksList = arrayListOf<Book>()
suspend fun getOnlineBooks(): List<LibkiwixBook> {
val onlineBooksList = arrayListOf<LibkiwixBook>()
runCatching {
library.booksIds.forEach { bookId ->
val book = library.getBookById(bookId)
onlineBooksList.add(book)
onlineBooksList.add(LibkiwixBook(book))
}
}.onFailure { it.printStackTrace() }
return onlineBooksList

View File

@ -21,7 +21,7 @@ package org.kiwix.kiwixmobile.core.zim_manager.fileselect_view
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.convertToLocal
import org.kiwix.kiwixmobile.core.dao.entities.BookOnDiskEntity
import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.core.reader.ZimFileReader
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
import org.kiwix.kiwixmobile.core.zim_manager.KiwixTag
@ -44,7 +44,7 @@ sealed class BooksOnDiskListItem {
data class BookOnDisk constructor(
val databaseId: Long = 0L,
val book: LibraryNetworkEntity.Book,
val book: LibkiwixBook,
val file: File = File(""),
val zimReaderSource: ZimReaderSource,
val tags: List<KiwixTag> = KiwixTag.Companion.from(book.tags),

View File

@ -21,7 +21,7 @@ package org.kiwix.kiwixmobile.custom.download.effects
import androidx.appcompat.app.AppCompatActivity
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.downloader.Downloader
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.custom.BuildConfig
import javax.inject.Inject
@ -45,7 +45,7 @@ data class DownloadCustom @Inject constructor(val downloader: Downloader) : Side
name: String = "",
favIcon: String = ""
) =
Book().apply {
LibkiwixBook().apply {
this.id = id
this.title = title
this.description = description