mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-22 03:54:18 -04:00
Merge pull request #4333 from kiwix/Fixes#4298
Introduced support of OPDS catalog.
This commit is contained in:
commit
18e3b3d9b6
7
app/proguard-rules.pro
vendored
7
app/proguard-rules.pro
vendored
@ -55,13 +55,6 @@
|
||||
<init>(...);
|
||||
}
|
||||
|
||||
## keep everything in LibraryNetworkEntity.kt
|
||||
-keep class org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity { *; }
|
||||
-keep class org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity$* { *; }
|
||||
-keepclassmembers class org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity$* {
|
||||
<init>(...);
|
||||
}
|
||||
|
||||
-keep class javax.xml.stream.** { *; }
|
||||
-dontwarn javax.xml.stream.Location
|
||||
-dontwarn javax.xml.stream.XMLEventReader
|
||||
|
@ -127,7 +127,7 @@ class DownloadRobot : BaseRobot() {
|
||||
testFlakyView({
|
||||
composeTestRule.apply {
|
||||
waitUntilTimeout()
|
||||
onAllNodesWithTag(ONLINE_BOOK_ITEM_TESTING_TAG)[0].performClick()
|
||||
onAllNodesWithTag(ONLINE_BOOK_ITEM_TESTING_TAG)[1].performClick()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ import org.kiwix.kiwixmobile.core.di.modules.CALL_TIMEOUT
|
||||
import org.kiwix.kiwixmobile.core.di.modules.CONNECTION_TIMEOUT
|
||||
import org.kiwix.kiwixmobile.core.di.modules.READ_TIMEOUT
|
||||
import org.kiwix.kiwixmobile.core.di.modules.USER_AGENT
|
||||
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
|
||||
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
|
||||
import org.kiwix.kiwixmobile.core.ui.components.SWIPE_REFRESH_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.core.utils.files.Log
|
||||
import java.io.File
|
||||
@ -150,7 +150,7 @@ object TestUtils {
|
||||
@JvmStatic fun withContent(content: String): Matcher<Any?> {
|
||||
return object : BoundedMatcher<Any?, Any?>(Any::class.java) {
|
||||
public override fun matchesSafely(myObj: Any?): Boolean {
|
||||
if (myObj !is LibraryNetworkEntity.Book) {
|
||||
if (myObj !is LibkiwixBook) {
|
||||
return false
|
||||
}
|
||||
return if (myObj.url != null) {
|
||||
|
@ -35,7 +35,7 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
|
||||
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
|
||||
import org.kiwix.kiwixmobile.core.utils.TestingUtils.RETRY_RULE_ORDER
|
||||
import org.kiwix.kiwixmobile.core.utils.files.DocumentResolverWrapper
|
||||
import org.kiwix.kiwixmobile.core.utils.files.FileUtils
|
||||
@ -109,7 +109,7 @@ class FileUtilsInstrumentationTest {
|
||||
}
|
||||
char1++
|
||||
}
|
||||
val book = LibraryNetworkEntity.Book()
|
||||
val book = LibkiwixBook()
|
||||
book.file = File(fileName + "bg")
|
||||
val files = getAllZimParts(book)
|
||||
|
||||
|
@ -44,7 +44,6 @@ import androidx.compose.ui.semantics.testTag
|
||||
import com.tonyodev.fetch2.Status
|
||||
import org.kiwix.kiwixmobile.R
|
||||
import org.kiwix.kiwixmobile.core.R.string
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.toPainter
|
||||
import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar
|
||||
import org.kiwix.kiwixmobile.core.ui.components.ProgressBarStyle
|
||||
import org.kiwix.kiwixmobile.core.ui.models.IconItem
|
||||
@ -104,7 +103,7 @@ private fun DownloadBookContent(
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
BookIcon(item.favIcon.toPainter())
|
||||
BookIcon(item.favIconUrl, isOnlineLibrary = true)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
|
@ -48,8 +48,6 @@ import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.zIndex
|
||||
import org.kiwix.kiwixmobile.R
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.Base64String
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.toPainter
|
||||
import org.kiwix.kiwixmobile.core.extensions.toast
|
||||
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
|
||||
import org.kiwix.kiwixmobile.core.ui.theme.PureGrey
|
||||
@ -58,7 +56,7 @@ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FIVE_DP
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.ONLINE_BOOK_DISABLED_COLOR_ALPHA
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.KiloByte
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.Byte
|
||||
import org.kiwix.kiwixmobile.ui.BookDate
|
||||
import org.kiwix.kiwixmobile.ui.BookDescription
|
||||
import org.kiwix.kiwixmobile.ui.BookIcon
|
||||
@ -167,7 +165,7 @@ private fun OnlineBookContent(item: BookItem, bookUtils: BookUtils) {
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
BookIcon(Base64String(item.book.favicon).toPainter())
|
||||
BookIcon(item.book.favicon, isOnlineLibrary = true)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
@ -207,7 +205,7 @@ private fun BookSizeAndDateRow(item: BookItem) {
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
BookSize(
|
||||
KiloByte(item.book.size).humanReadable,
|
||||
Byte(item.book.size).humanReadable,
|
||||
modifier = Modifier.weight(1f).testTag(ONLINE_BOOK_SIZE_TEXT_TESTING_TAG)
|
||||
)
|
||||
BookDate(item.book.date)
|
||||
|
@ -19,6 +19,7 @@
|
||||
package org.kiwix.kiwixmobile.ui
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@ -31,18 +32,17 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import coil3.compose.AsyncImage
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.Base64String
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.toPainter
|
||||
@ -53,7 +53,7 @@ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FIVE_DP
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOUR_DP
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
|
||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.KiloByte
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.Byte
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.ArticleCount
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode
|
||||
@ -116,7 +116,7 @@ private fun BookContent(
|
||||
if (selectionMode == SelectionMode.MULTI) {
|
||||
BookCheckbox(bookOnDisk, selectionMode, onMultiSelect, onClick, index)
|
||||
}
|
||||
BookIcon(Base64String(bookOnDisk.book.favicon).toPainter())
|
||||
BookIcon(bookOnDisk.book.favicon, isOnlineLibrary = false)
|
||||
BookDetails(Modifier.weight(1f), bookOnDisk)
|
||||
}
|
||||
}
|
||||
@ -142,14 +142,23 @@ private fun BookCheckbox(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BookIcon(painter: Painter) {
|
||||
Icon(
|
||||
painter = painter,
|
||||
contentDescription = stringResource(R.string.fav_icon),
|
||||
modifier = Modifier
|
||||
.size(BOOK_ICON_SIZE),
|
||||
tint = Color.Unspecified
|
||||
)
|
||||
fun BookIcon(iconSource: String, isOnlineLibrary: Boolean) {
|
||||
val modifier = Modifier.size(BOOK_ICON_SIZE)
|
||||
if (isOnlineLibrary) {
|
||||
AsyncImage(
|
||||
model = iconSource,
|
||||
contentDescription = stringResource(R.string.fav_icon),
|
||||
modifier = modifier,
|
||||
placeholder = painterResource(R.drawable.default_zim_file_icon),
|
||||
error = painterResource(R.drawable.default_zim_file_icon),
|
||||
)
|
||||
} else {
|
||||
Image(
|
||||
painter = Base64String(iconSource).toPainter(),
|
||||
contentDescription = stringResource(R.string.fav_icon),
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ -164,7 +173,7 @@ private fun BookDetails(modifier: Modifier, bookOnDisk: BookOnDisk) {
|
||||
) {
|
||||
BookDate(bookOnDisk.book.date)
|
||||
Spacer(modifier = Modifier.width(EIGHT_DP))
|
||||
BookSize(KiloByte(bookOnDisk.book.size).humanReadable)
|
||||
BookSize(Byte(bookOnDisk.book.size).humanReadable)
|
||||
Spacer(modifier = Modifier.width(EIGHT_DP))
|
||||
BookArticleCount(
|
||||
ArticleCount(bookOnDisk.book.articleCount.orEmpty())
|
||||
|
@ -22,18 +22,18 @@ import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.data.remote.OnlineLibraryProgressListener
|
||||
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DEFAULT_INT_VALUE
|
||||
import org.kiwix.kiwixmobile.core.downloader.downloadManager.HUNDERED
|
||||
import org.kiwix.kiwixmobile.core.downloader.downloadManager.NINE
|
||||
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
|
||||
|
||||
class AppProgressListenerProvider(
|
||||
private val zimManageViewModel: ZimManageViewModel
|
||||
) : OnlineLibraryProgressListener {
|
||||
@Suppress("MagicNumber")
|
||||
override fun onProgress(bytesRead: Long, contentLength: Long) {
|
||||
val progress =
|
||||
if (contentLength == DEFAULT_INT_VALUE.toLong()) {
|
||||
ZERO
|
||||
} else {
|
||||
(bytesRead * 3 * HUNDERED / contentLength).coerceAtMost(HUNDERED.toLong())
|
||||
(bytesRead * NINE * HUNDERED / contentLength).coerceAtMost(HUNDERED.toLong())
|
||||
}
|
||||
zimManageViewModel.downloadProgress.postValue(
|
||||
zimManageViewModel.context.getString(
|
||||
|
@ -70,20 +70,19 @@ import org.kiwix.kiwixmobile.core.dao.NewBookDao
|
||||
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
|
||||
import org.kiwix.kiwixmobile.core.data.DataSource
|
||||
import org.kiwix.kiwixmobile.core.data.remote.KiwixService
|
||||
import org.kiwix.kiwixmobile.core.data.remote.KiwixService.Companion.LIBRARY_NETWORK_PATH
|
||||
import org.kiwix.kiwixmobile.core.data.remote.KiwixService.Companion.OPDS_LIBRARY_NETWORK_PATH
|
||||
import org.kiwix.kiwixmobile.core.data.remote.ProgressResponseBody
|
||||
import org.kiwix.kiwixmobile.core.data.remote.UserAgentInterceptor
|
||||
import org.kiwix.kiwixmobile.core.di.modules.CALL_TIMEOUT
|
||||
import org.kiwix.kiwixmobile.core.di.modules.CONNECTION_TIMEOUT
|
||||
import org.kiwix.kiwixmobile.core.di.modules.KIWIX_DOWNLOAD_URL
|
||||
import org.kiwix.kiwixmobile.core.di.modules.KIWIX_OPDS_LIBRARY_URL
|
||||
import org.kiwix.kiwixmobile.core.di.modules.READ_TIMEOUT
|
||||
import org.kiwix.kiwixmobile.core.di.modules.USER_AGENT
|
||||
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.LibraryNetworkEntity.Book
|
||||
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
|
||||
@ -97,6 +96,7 @@ import org.kiwix.kiwixmobile.core.zim_manager.ConnectivityBroadcastReceiver
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.Language
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.NetworkState
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.NetworkState.CONNECTED
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.OnlineLibraryManager
|
||||
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.SelectionMode.MULTI
|
||||
@ -121,7 +121,8 @@ 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 java.util.LinkedList
|
||||
import org.kiwix.libkiwix.Library
|
||||
import org.kiwix.libkiwix.Manager
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit.SECONDS
|
||||
import javax.inject.Inject
|
||||
@ -132,6 +133,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,
|
||||
@ -145,7 +147,7 @@ class ZimManageViewModel @Inject constructor(
|
||||
private val defaultLanguageProvider: DefaultLanguageProvider,
|
||||
private val dataSource: DataSource,
|
||||
private val connectivityManager: ConnectivityManager,
|
||||
private val sharedPreferenceUtil: SharedPreferenceUtil
|
||||
private val sharedPreferenceUtil: SharedPreferenceUtil,
|
||||
) : ViewModel() {
|
||||
sealed class FileSelectActions {
|
||||
data class RequestNavigateTo(val bookOnDisk: BookOnDisk) : FileSelectActions()
|
||||
@ -158,6 +160,12 @@ class ZimManageViewModel @Inject constructor(
|
||||
object UserClickedDownloadBooksButton : FileSelectActions()
|
||||
}
|
||||
|
||||
private val library by lazy { Library() }
|
||||
private val manager by lazy { Manager(library) }
|
||||
|
||||
private val onlineLibraryManager by lazy {
|
||||
OnlineLibraryManager(library, manager)
|
||||
}
|
||||
private var isUnitTestCase: Boolean = false
|
||||
val sideEffects: MutableSharedFlow<SideEffect<*>> = MutableSharedFlow()
|
||||
private val _libraryItems = MutableStateFlow<List<LibraryListItem>>(emptyList())
|
||||
@ -168,7 +176,7 @@ class ZimManageViewModel @Inject constructor(
|
||||
val onlineLibraryDownloading = MutableStateFlow(false)
|
||||
val shouldShowWifiOnlyDialog = MutableLiveData<Boolean>()
|
||||
val networkStates = MutableLiveData<NetworkState>()
|
||||
val networkLibrary = MutableSharedFlow<LibraryNetworkEntity>(replay = 0)
|
||||
val networkLibrary = MutableSharedFlow<List<LibkiwixBook>>(replay = 0)
|
||||
val requestFileSystemCheck = MutableSharedFlow<Unit>(replay = 0)
|
||||
val fileSelectActions = MutableSharedFlow<FileSelectActions>()
|
||||
private val requestDownloadLibrary = MutableSharedFlow<Unit>(
|
||||
@ -238,7 +246,10 @@ class ZimManageViewModel @Inject constructor(
|
||||
} ?: originalResponse
|
||||
}
|
||||
.build()
|
||||
return KiwixService.ServiceCreator.newHackListService(customOkHttpClient, KIWIX_DOWNLOAD_URL)
|
||||
return KiwixService.ServiceCreator.newHackListService(
|
||||
customOkHttpClient,
|
||||
KIWIX_OPDS_LIBRARY_URL
|
||||
)
|
||||
.also {
|
||||
kiwixService = it
|
||||
}
|
||||
@ -249,7 +260,7 @@ class ZimManageViewModel @Inject constructor(
|
||||
private fun getContentLengthOfLibraryXmlFile(): Long {
|
||||
val headRequest =
|
||||
Request.Builder()
|
||||
.url("$KIWIX_DOWNLOAD_URL$LIBRARY_NETWORK_PATH")
|
||||
.url("$KIWIX_OPDS_LIBRARY_URL$OPDS_LIBRARY_NETWORK_PATH")
|
||||
.head()
|
||||
.header("Accept-Encoding", "identity")
|
||||
.build()
|
||||
@ -388,7 +399,7 @@ class ZimManageViewModel @Inject constructor(
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun requestsAndConnectivityChangesToLibraryRequests(
|
||||
library: MutableSharedFlow<LibraryNetworkEntity>,
|
||||
library: MutableSharedFlow<List<LibkiwixBook>>,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) = requestDownloadLibrary.flatMapConcat {
|
||||
connectivityBroadcastReceiver.networkStates
|
||||
@ -408,6 +419,7 @@ class ZimManageViewModel @Inject constructor(
|
||||
it.printStackTrace().also {
|
||||
isOnlineLibraryDownloading = false
|
||||
onlineLibraryDownloading.tryEmit(false)
|
||||
library.emit(emptyList())
|
||||
}
|
||||
}
|
||||
.onEach {
|
||||
@ -439,16 +451,27 @@ class ZimManageViewModel @Inject constructor(
|
||||
|
||||
private fun downloadLibraryFlow(
|
||||
kiwixService: KiwixService
|
||||
): Flow<LibraryNetworkEntity?> = flow {
|
||||
): Flow<List<LibkiwixBook>> = flow {
|
||||
downloadProgress.postValue(context.getString(R.string.starting_downloading_remote_library))
|
||||
val response = kiwixService.getLibrary()
|
||||
val resolvedUrl = response.raw().networkResponse?.request?.url
|
||||
?: response.raw().request.url
|
||||
val baseHostUrl = "${resolvedUrl.scheme}://${resolvedUrl.host}"
|
||||
downloadProgress.postValue(context.getString(R.string.parsing_remote_library))
|
||||
emit(response)
|
||||
val libraryXml = response.body()
|
||||
val isLibraryParsed = onlineLibraryManager.parseOPDSStream(libraryXml, baseHostUrl)
|
||||
emit(
|
||||
if (isLibraryParsed) {
|
||||
onlineLibraryManager.getOnlineBooks()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
)
|
||||
}
|
||||
.retry(5)
|
||||
.catch { e ->
|
||||
e.printStackTrace()
|
||||
emit(LibraryNetworkEntity().apply { book = LinkedList() })
|
||||
emit(emptyList())
|
||||
}
|
||||
|
||||
private fun updateNetworkStates() = connectivityBroadcastReceiver.networkStates
|
||||
@ -460,7 +483,7 @@ class ZimManageViewModel @Inject constructor(
|
||||
private fun updateLibraryItems(
|
||||
booksFromDao: Flow<List<BookOnDisk>>,
|
||||
downloads: Flow<List<DownloadModel>>,
|
||||
library: MutableSharedFlow<LibraryNetworkEntity>,
|
||||
library: MutableSharedFlow<List<LibkiwixBook>>,
|
||||
languages: Flow<List<Language>>,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) = viewModelScope.launch(dispatcher) {
|
||||
@ -483,14 +506,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 LibraryNetworkEntity
|
||||
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
|
||||
)
|
||||
@ -505,12 +528,12 @@ class ZimManageViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun updateLanguagesInDao(
|
||||
library: MutableSharedFlow<LibraryNetworkEntity>,
|
||||
library: MutableSharedFlow<List<LibkiwixBook>>,
|
||||
languages: Flow<List<Language>>,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) =
|
||||
combine(
|
||||
library.map { it.book }.filterNotNull(),
|
||||
library,
|
||||
languages
|
||||
) { books, existingLanguages ->
|
||||
combineToLanguageList(books, existingLanguages)
|
||||
@ -523,7 +546,7 @@ class ZimManageViewModel @Inject constructor(
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
private fun combineToLanguageList(
|
||||
booksFromNetwork: List<Book>,
|
||||
booksFromNetwork: List<LibkiwixBook>,
|
||||
allLanguages: List<Language>
|
||||
) = when {
|
||||
booksFromNetwork.isEmpty() -> {
|
||||
@ -547,8 +570,8 @@ class ZimManageViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun networkLanguageCounts(booksFromNetwork: List<Book>) =
|
||||
booksFromNetwork.mapNotNull(Book::language)
|
||||
private fun networkLanguageCounts(booksFromNetwork: List<LibkiwixBook>) =
|
||||
booksFromNetwork.map { it.language }
|
||||
.fold(
|
||||
mutableMapOf<String, Int>()
|
||||
) { acc, language -> acc.increment(language) }
|
||||
@ -585,14 +608,14 @@ class ZimManageViewModel @Inject constructor(
|
||||
booksOnFileSystem: List<BookOnDisk>,
|
||||
activeDownloads: List<DownloadModel>,
|
||||
allLanguages: List<Language>,
|
||||
libraryNetworkEntity: LibraryNetworkEntity,
|
||||
onlineBooks: List<LibkiwixBook>,
|
||||
filter: String,
|
||||
fileSystemState: FileSystemState
|
||||
): List<LibraryListItem> {
|
||||
val activeLanguageCodes =
|
||||
allLanguages.filter(Language::active)
|
||||
.map(Language::languageCode)
|
||||
val allBooks = libraryNetworkEntity.book!! - booksOnFileSystem.map(BookOnDisk::book).toSet()
|
||||
val allBooks = onlineBooks - booksOnFileSystem.map(BookOnDisk::book).toSet()
|
||||
val downloadingBooks =
|
||||
activeDownloads.mapNotNull { download ->
|
||||
allBooks.firstOrNull { it.id == download.book.id }
|
||||
@ -630,7 +653,7 @@ class ZimManageViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun createLibrarySection(
|
||||
books: List<Book>,
|
||||
books: List<LibkiwixBook>,
|
||||
activeDownloads: List<DownloadModel>,
|
||||
fileSystemState: FileSystemState,
|
||||
sectionStringId: Int,
|
||||
@ -644,7 +667,7 @@ class ZimManageViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun applySearchFilter(
|
||||
unDownloadedBooks: List<Book>,
|
||||
unDownloadedBooks: List<LibkiwixBook>,
|
||||
filter: String
|
||||
) = if (filter.isEmpty()) {
|
||||
unDownloadedBooks
|
||||
@ -653,7 +676,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 ->
|
||||
|
@ -19,12 +19,11 @@
|
||||
package org.kiwix.kiwixmobile.zimManager.libraryView
|
||||
|
||||
import eu.mhutti1.utils.storage.Bytes
|
||||
import eu.mhutti1.utils.storage.KB
|
||||
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
|
||||
@ -42,13 +41,13 @@ class AvailableSpaceCalculator @Inject constructor(
|
||||
.map { downloads -> downloads.sumOf(DownloadModel::bytesRemaining) }
|
||||
.map { bytesToBeDownloaded -> storageCalculator.availableBytes() - bytesToBeDownloaded }
|
||||
.first()
|
||||
if (bookItem.book.size.toLong() * KB < trueAvailableBytes) {
|
||||
if (bookItem.book.size.toLong() < trueAvailableBytes) {
|
||||
successAction.invoke(bookItem)
|
||||
} else {
|
||||
failureAction.invoke(Bytes(trueAvailableBytes).humanReadable)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun hasAvailableSpaceForBook(book: Book) =
|
||||
book.size.toLong() * KB < storageCalculator.availableBytes()
|
||||
suspend fun hasAvailableSpaceForBook(book: LibkiwixBook) =
|
||||
book.size.toLong() < storageCalculator.availableBytes()
|
||||
}
|
||||
|
@ -20,18 +20,17 @@ package org.kiwix.kiwixmobile.zimManager.libraryView.adapter
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.tonyodev.fetch2.Status
|
||||
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 +41,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,14 +53,14 @@ sealed class LibraryListItem {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun Book.isLessThan4GB() =
|
||||
private fun LibkiwixBook.isLessThan4GB() =
|
||||
size.toLongOrNull() ?: 0L < Fat32Checker.FOUR_GIGABYTES_IN_KILOBYTES
|
||||
}
|
||||
}
|
||||
|
||||
data class LibraryDownloadItem(
|
||||
val downloadId: Long,
|
||||
val favIcon: Base64String,
|
||||
val favIconUrl: String,
|
||||
val title: String,
|
||||
val description: String?,
|
||||
val bytesDownloaded: Long,
|
||||
@ -76,7 +75,7 @@ sealed class LibraryListItem {
|
||||
|
||||
constructor(downloadModel: DownloadModel) : this(
|
||||
downloadModel.downloadId,
|
||||
Base64String(downloadModel.book.favicon),
|
||||
downloadModel.book.favicon,
|
||||
downloadModel.book.title,
|
||||
downloadModel.book.description,
|
||||
downloadModel.bytesDownloaded,
|
||||
|
@ -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.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
@ -60,7 +59,7 @@ import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
|
||||
import org.kiwix.kiwixmobile.core.data.DataSource
|
||||
import org.kiwix.kiwixmobile.core.data.remote.KiwixService
|
||||
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.utils.BookUtils
|
||||
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
||||
import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower
|
||||
@ -90,11 +89,10 @@ 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.sharedFunctions.InstantExecutorExtension
|
||||
import org.kiwix.sharedFunctions.book
|
||||
import org.kiwix.sharedFunctions.bookOnDisk
|
||||
import org.kiwix.sharedFunctions.downloadModel
|
||||
import org.kiwix.sharedFunctions.language
|
||||
import org.kiwix.sharedFunctions.libraryNetworkEntity
|
||||
import org.kiwix.sharedFunctions.libkiwixBook
|
||||
import java.util.Locale
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@ -191,7 +189,7 @@ class ZimManageViewModelTest {
|
||||
setAlertDialogShower(alertDialogShower)
|
||||
}
|
||||
viewModel.fileSelectListStates.value = FileSelectListState(emptyList())
|
||||
runBlocking { viewModel.networkLibrary.emit(libraryNetworkEntity()) }
|
||||
runBlocking { viewModel.networkLibrary.emit(emptyList()) }
|
||||
}
|
||||
|
||||
@Nested
|
||||
@ -238,8 +236,8 @@ 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, book("1"))
|
||||
val bookToRemove = bookOnDisk(1L, book("2"))
|
||||
val expectedBook = bookOnDisk(1L, libkiwixBook("1"))
|
||||
val bookToRemove = bookOnDisk(1L, libkiwixBook("2"))
|
||||
advanceUntilIdle()
|
||||
viewModel.requestFileSystemCheck.emit(Unit)
|
||||
advanceUntilIdle()
|
||||
@ -314,9 +312,9 @@ class ZimManageViewModelTest {
|
||||
)
|
||||
expectNetworkDbAndDefault(
|
||||
listOf(
|
||||
book(language = "eng"),
|
||||
book(language = "eng"),
|
||||
book(language = "fra")
|
||||
libkiwixBook(language = "eng"),
|
||||
libkiwixBook(language = "eng"),
|
||||
libkiwixBook(language = "fra")
|
||||
),
|
||||
listOf(),
|
||||
defaultLanguage
|
||||
@ -352,9 +350,9 @@ class ZimManageViewModelTest {
|
||||
)
|
||||
expectNetworkDbAndDefault(
|
||||
listOf(
|
||||
book(language = "eng"),
|
||||
book(language = "eng"),
|
||||
book(language = "fra")
|
||||
libkiwixBook(language = "eng"),
|
||||
libkiwixBook(language = "eng"),
|
||||
libkiwixBook(language = "fra")
|
||||
),
|
||||
listOf(dbLanguage),
|
||||
language(isActive = true, occurencesOfLanguage = 1)
|
||||
@ -378,15 +376,14 @@ class ZimManageViewModelTest {
|
||||
}
|
||||
|
||||
private suspend fun TestScope.expectNetworkDbAndDefault(
|
||||
networkBooks: List<Book>,
|
||||
networkBooks: List<LibkiwixBook>,
|
||||
dbBooks: List<Language>,
|
||||
defaultLanguage: Language
|
||||
) {
|
||||
every { application.getString(any()) } returns ""
|
||||
every { application.getString(any(), any()) } returns ""
|
||||
coEvery { kiwixService.getLibrary() } returns libraryNetworkEntity(networkBooks)
|
||||
every { defaultLanguageProvider.provide() } returns defaultLanguage
|
||||
viewModel.networkLibrary.emit(libraryNetworkEntity(networkBooks))
|
||||
viewModel.networkLibrary.emit(networkBooks)
|
||||
runCurrent()
|
||||
languages.value = dbBooks
|
||||
runCurrent()
|
||||
@ -405,10 +402,10 @@ class ZimManageViewModelTest {
|
||||
|
||||
@Test
|
||||
fun `library update removes from sources and maps to list items`() = runTest {
|
||||
val bookAlreadyOnDisk = book(id = "0", url = "", language = Locale.ENGLISH.language)
|
||||
val bookDownloading = book(id = "1", url = "")
|
||||
val bookWithActiveLanguage = book(id = "3", language = "activeLanguage", url = "")
|
||||
val bookWithInactiveLanguage = book(id = "4", language = "inactiveLanguage", url = "")
|
||||
val bookAlreadyOnDisk = libkiwixBook(id = "0", url = "", language = Locale.ENGLISH.language)
|
||||
val bookDownloading = libkiwixBook(id = "1", url = "")
|
||||
val bookWithActiveLanguage = libkiwixBook(id = "3", language = "activeLanguage", url = "")
|
||||
val bookWithInactiveLanguage = libkiwixBook(id = "4", language = "inactiveLanguage", url = "")
|
||||
testFlow(
|
||||
flow = viewModel.libraryItems,
|
||||
triggerAction = {
|
||||
@ -429,13 +426,11 @@ class ZimManageViewModelTest {
|
||||
fileSystemStates.tryEmit(CanWrite4GbFile)
|
||||
advanceUntilIdle()
|
||||
viewModel.networkLibrary.emit(
|
||||
libraryNetworkEntity(
|
||||
listOf(
|
||||
bookAlreadyOnDisk,
|
||||
bookDownloading,
|
||||
bookWithActiveLanguage,
|
||||
bookWithInactiveLanguage
|
||||
)
|
||||
listOf(
|
||||
bookAlreadyOnDisk,
|
||||
bookDownloading,
|
||||
bookWithActiveLanguage,
|
||||
bookWithInactiveLanguage
|
||||
)
|
||||
)
|
||||
},
|
||||
@ -458,7 +453,7 @@ class ZimManageViewModelTest {
|
||||
@Test
|
||||
fun `library marks files over 4GB as can't download if file system state says to`() = runTest {
|
||||
val bookOver4Gb =
|
||||
book(
|
||||
libkiwixBook(
|
||||
id = "0",
|
||||
url = "",
|
||||
size = "${Fat32Checker.FOUR_GIGABYTES_IN_KILOBYTES + 1}"
|
||||
@ -477,7 +472,7 @@ class ZimManageViewModelTest {
|
||||
)
|
||||
)
|
||||
fileSystemStates.tryEmit(CannotWrite4GbFile)
|
||||
viewModel.networkLibrary.emit(libraryNetworkEntity(listOf(bookOver4Gb)))
|
||||
viewModel.networkLibrary.emit(listOf(bookOver4Gb))
|
||||
},
|
||||
assert = {
|
||||
skipItems(1)
|
||||
|
@ -24,17 +24,17 @@ import io.mockk.mockk
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
|
||||
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
|
||||
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
|
||||
import org.kiwix.kiwixmobile.zimManager.libraryView.adapter.LibraryListItem.BookItem
|
||||
|
||||
internal class LibraryListItemTest {
|
||||
private val book = mockk<Book>()
|
||||
private val book = mockk<LibkiwixBook>()
|
||||
|
||||
@BeforeEach
|
||||
fun init() {
|
||||
@ -76,6 +76,6 @@ internal class LibraryListItemTest {
|
||||
assertThat(canBeDownloaded(book, NotEnoughSpaceFor4GbFile)).isTrue
|
||||
}
|
||||
|
||||
private fun canBeDownloaded(book: Book, fileSystemState: FileSystemState) =
|
||||
private fun canBeDownloaded(book: LibkiwixBook, fileSystemState: FileSystemState) =
|
||||
BookItem(book, fileSystemState).canBeDownloaded
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
@ -363,4 +369,7 @@ object Libs {
|
||||
|
||||
const val COMPOSE_LIVE_DATA =
|
||||
"androidx.compose.runtime:runtime-livedata:${Versions.COMPOSE_VERSION}"
|
||||
|
||||
const val COIL3_COMPOSE = "io.coil-kt.coil3:coil-compose:${Versions.COIL_COMPOSE}"
|
||||
const val COIL3_OKHTTP_COMPOSE = "io.coil-kt.coil3:coil-network-okhttp:${Versions.COIL_COMPOSE}"
|
||||
}
|
||||
|
@ -115,6 +115,8 @@ object Versions {
|
||||
const val COMPOSE_MATERIAL3 = "1.3.1"
|
||||
|
||||
const val TURBINE_FLOW_TEST = "1.2.0"
|
||||
|
||||
const val COIL_COMPOSE = "3.2.0"
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -243,6 +243,8 @@ class AllProjectConfigurer {
|
||||
implementation(Libs.ANDROIDX_ACTIVITY_COMPOSE)
|
||||
implementation(Libs.COMPOSE_TOOLING_PREVIEW)
|
||||
implementation(Libs.COMPOSE_LIVE_DATA)
|
||||
implementation(Libs.COIL3_COMPOSE)
|
||||
implementation(Libs.COIL3_OKHTTP_COMPOSE)
|
||||
|
||||
// Compose UI test implementation
|
||||
androidTestImplementation(Libs.COMPOSE_UI_TEST_JUNIT)
|
||||
|
@ -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")
|
||||
|
@ -21,7 +21,7 @@
|
||||
<ID>MagicNumber:DownloaderModule.kt$DownloaderModule$5</ID>
|
||||
<ID>MagicNumber:FileUtils.kt$FileUtils$3</ID>
|
||||
<ID>MagicNumber:JNIInitialiser.kt$JNIInitialiser$1024</ID>
|
||||
<ID>MagicNumber:KiloByte.kt$KiloByte$1024.0</ID>
|
||||
<ID>MagicNumber:Byte.kt$Byte$1024.0</ID>
|
||||
<ID>MagicNumber:MainMenu.kt$MainMenu$99</ID>
|
||||
<ID>MagicNumber:OnSwipeTouchListener.kt$OnSwipeTouchListener.GestureListener$100</ID>
|
||||
<ID>MagicNumber:SearchResultGenerator.kt$ZimSearchResultGenerator$200</ID>
|
||||
@ -42,7 +42,7 @@
|
||||
<ID>NestedBlockDepth:StorageDeviceUtils.kt$StorageDeviceUtils$// Amazingly file.canWrite() does not always return the correct value private fun canWrite(file: File): Boolean</ID>
|
||||
<ID>PackageNaming:ArticleCount.kt$package org.kiwix.kiwixmobile.core.zim_manager.fileselect_view</ID>
|
||||
<ID>PackageNaming:BooksOnDiskListItem.kt$package org.kiwix.kiwixmobile.core.zim_manager.fileselect_view</ID>
|
||||
<ID>PackageNaming:KiloByte.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID>
|
||||
<ID>PackageNaming:Byte.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID>
|
||||
<ID>PackageNaming:KiwixTag.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID>
|
||||
<ID>PackageNaming:Language.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID>
|
||||
<ID>PackageNaming:MountPointProducer.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID>
|
||||
@ -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<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>
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
package org.kiwix.kiwixmobile.core.dao
|
||||
|
||||
import android.util.Base64
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
@ -25,16 +26,20 @@ import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import com.tonyodev.fetch2.Download
|
||||
import com.tonyodev.fetch2.Status.COMPLETED
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.withContext
|
||||
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.reader.ILLUSTRATION_SIZE
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem
|
||||
import org.kiwix.libzim.Archive
|
||||
import javax.inject.Inject
|
||||
|
||||
@Dao
|
||||
@ -53,15 +58,37 @@ abstract class DownloadRoomDao {
|
||||
|
||||
fun allDownloads() = getAllDownloads().map { it.map(::DownloadModel) }
|
||||
|
||||
private fun moveCompletedToBooksOnDiskDao(downloadEntities: List<DownloadRoomEntity>) {
|
||||
@Suppress("InjectDispatcher")
|
||||
private suspend fun moveCompletedToBooksOnDiskDao(downloadEntities: List<DownloadRoomEntity>) {
|
||||
downloadEntities.filter { it.status == COMPLETED }
|
||||
.takeIf(List<DownloadRoomEntity>::isNotEmpty)
|
||||
?.let {
|
||||
deleteDownloadsList(it)
|
||||
newBookDao.insert(it.map(BooksOnDiskListItem::BookOnDisk))
|
||||
?.let { completedDownloads ->
|
||||
deleteDownloadsList(completedDownloads)
|
||||
// We now use the OPDS stream instead of the custom library.xml handling.
|
||||
// In the OPDS stream, the favicon is a URL instead of a Base64 string.
|
||||
// So when a download is completed, we extract the illustration directly from the archive.
|
||||
val booksOnDisk = completedDownloads.map { download ->
|
||||
val archive = withContext(Dispatchers.IO) {
|
||||
Archive(download.file)
|
||||
}
|
||||
val favicon = getOnlineBookFaviconForOfflineUsages(archive).orEmpty()
|
||||
val updatedEntity = download.copy(favIcon = favicon)
|
||||
BooksOnDiskListItem.BookOnDisk(updatedEntity)
|
||||
}
|
||||
newBookDao.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)
|
||||
@ -100,7 +127,7 @@ abstract class DownloadRoomDao {
|
||||
|
||||
fun addIfDoesNotExist(
|
||||
url: String,
|
||||
book: LibraryNetworkEntity.Book,
|
||||
book: LibkiwixBook,
|
||||
downloadRequester: DownloadRequester
|
||||
) {
|
||||
if (doesNotAlreadyExist(book)) {
|
||||
@ -113,6 +140,6 @@ abstract class DownloadRoomDao {
|
||||
}
|
||||
}
|
||||
|
||||
private fun doesNotAlreadyExist(book: LibraryNetworkEntity.Book) =
|
||||
private fun doesNotAlreadyExist(book: LibkiwixBook) =
|
||||
count(book.id) == 0
|
||||
}
|
||||
|
@ -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
|
||||
@ -205,7 +204,7 @@ class LibkiwixBookmarks @Inject constructor(
|
||||
library.addBook(libKiwixBook).also {
|
||||
// now library has changed so update our library list.
|
||||
libraryBooksList = library.booksIds.toList()
|
||||
Log.d(
|
||||
Log.e(
|
||||
TAG,
|
||||
"Added Book to Library:\n" +
|
||||
"ZIM File Path: ${book.path}\n" +
|
||||
@ -234,6 +233,7 @@ class LibkiwixBookmarks @Inject constructor(
|
||||
CoroutineScope(dispatcher).launch {
|
||||
writeBookMarksAndSaveLibraryToFile()
|
||||
updateFlowBookmarkList()
|
||||
removeBookFromLibraryIfNoRelatedBookmarksAreStored(dispatcher, bookmarks)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -242,6 +242,34 @@ class LibkiwixBookmarks @Inject constructor(
|
||||
deleteBookmarks(listOf(LibkiwixBookmarkItem(zimId = bookId, bookmarkUrl = bookmarkUrl)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes books from the library that no longer have any associated bookmarks.
|
||||
*
|
||||
* This function checks if any of the books associated with the given deleted bookmarks
|
||||
* are still referenced by other existing bookmarks. If not, those books are removed from the library.
|
||||
*
|
||||
* @param dispatcher The coroutine dispatcher to run the operation on (typically Dispatchers.IO).
|
||||
* @param deletedBookmarks The list of bookmarks that were just deleted.
|
||||
*/
|
||||
private suspend fun removeBookFromLibraryIfNoRelatedBookmarksAreStored(
|
||||
dispatcher: CoroutineDispatcher,
|
||||
deletedBookmarks: List<LibkiwixBookmarkItem>
|
||||
) {
|
||||
withContext(dispatcher) {
|
||||
val currentBookmarks = getBookmarksList()
|
||||
val deletedZimIds = deletedBookmarks.map { it.zimId }.distinct()
|
||||
|
||||
deletedZimIds.forEach { zimId ->
|
||||
val stillExists = currentBookmarks.any { it.zimId == zimId }
|
||||
if (!stillExists) {
|
||||
library.removeBookById(zimId)
|
||||
Log.d(TAG, "Removed book from library since no bookmarks exist for: $zimId")
|
||||
}
|
||||
}
|
||||
}
|
||||
writeBookMarksAndSaveLibraryToFile()
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously writes the library and bookmarks data to their respective files in a background thread
|
||||
* to prevent potential data loss and ensures that the library holds the updated ZIM file paths and favicons.
|
||||
@ -285,10 +313,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.
|
||||
|
@ -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!!)) })
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -20,16 +20,17 @@
|
||||
package org.kiwix.kiwixmobile.core.data.remote
|
||||
|
||||
import okhttp3.OkHttpClient
|
||||
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
|
||||
import org.kiwix.kiwixmobile.core.entity.MetaLinkNetworkEntity
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.scalars.ScalarsConverterFactory
|
||||
import retrofit2.converter.simplexml.SimpleXmlConverterFactory
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Url
|
||||
|
||||
interface KiwixService {
|
||||
@GET(LIBRARY_NETWORK_PATH)
|
||||
suspend fun getLibrary(): LibraryNetworkEntity?
|
||||
@GET(OPDS_LIBRARY_NETWORK_PATH)
|
||||
suspend fun getLibrary(): Response<String>
|
||||
|
||||
@GET
|
||||
suspend fun getMetaLinks(
|
||||
@ -43,6 +44,7 @@ interface KiwixService {
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(ScalarsConverterFactory.create())
|
||||
.addConverterFactory(SimpleXmlConverterFactory.create())
|
||||
.build()
|
||||
return retrofit.create(KiwixService::class.java)
|
||||
@ -50,6 +52,8 @@ interface KiwixService {
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LIBRARY_NETWORK_PATH = "/library/library_zim.xml"
|
||||
// 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 = "v2/entries?count=-1"
|
||||
}
|
||||
}
|
||||
|
@ -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_DOWNLOAD_URL = "https://mirror.download.kiwix.org/"
|
||||
const val KIWIX_OPDS_LIBRARY_URL = "https://opds.library.kiwix.org/"
|
||||
|
||||
@Module
|
||||
class NetworkModule {
|
||||
@ -59,5 +59,5 @@ class NetworkModule {
|
||||
}
|
||||
|
||||
@Provides @Singleton fun provideKiwixService(okHttpClient: OkHttpClient): KiwixService =
|
||||
ServiceCreator.newHackListService(okHttpClient, KIWIX_DOWNLOAD_URL)
|
||||
ServiceCreator.newHackListService(okHttpClient, KIWIX_OPDS_LIBRARY_URL)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -37,6 +37,7 @@ import javax.inject.Inject
|
||||
const val ZERO = 0
|
||||
const val FIVE = 5
|
||||
const val SIX = 6
|
||||
const val NINE = 9
|
||||
const val HUNDERED = 100
|
||||
const val DEFAULT_INT_VALUE = -1
|
||||
|
||||
|
@ -38,7 +38,7 @@ value class Base64String(private val encodedString: String?) {
|
||||
BitmapFactory.decodeByteArray(it, 0, it.size)
|
||||
}
|
||||
}
|
||||
} catch (illegalArgumentException: IllegalArgumentException) {
|
||||
} catch (_: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ import org.kiwix.kiwixmobile.core.R
|
||||
|
||||
data class DownloadItem(
|
||||
val downloadId: Long,
|
||||
val favIcon: Base64String,
|
||||
val favIconUrl: String,
|
||||
val title: String,
|
||||
val description: String?,
|
||||
val bytesDownloaded: Long,
|
||||
@ -47,7 +47,7 @@ data class DownloadItem(
|
||||
|
||||
constructor(downloadModel: DownloadModel) : this(
|
||||
downloadModel.downloadId,
|
||||
Base64String(downloadModel.book.favicon),
|
||||
downloadModel.book.favicon,
|
||||
downloadModel.book.title,
|
||||
downloadModel.book.description,
|
||||
downloadModel.bytesDownloaded,
|
||||
|
@ -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) }
|
||||
|
@ -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()
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
/*
|
||||
* Kiwix Android
|
||||
* Copyright (c) 2019 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.simpleframework.xml.Attribute
|
||||
import org.simpleframework.xml.ElementList
|
||||
import org.simpleframework.xml.Root
|
||||
import java.io.File
|
||||
import java.io.Serializable
|
||||
import java.util.LinkedList
|
||||
|
||||
@Root(name = "library")
|
||||
class LibraryNetworkEntity {
|
||||
@field:ElementList(name = "book", inline = true, required = false)
|
||||
var book: LinkedList<Book>? = null
|
||||
|
||||
@field:Attribute(name = "version", required = false)
|
||||
var version: String? = null
|
||||
|
||||
@Root(name = "book", strict = false)
|
||||
class Book : Serializable {
|
||||
@field:Attribute(name = "id", required = false)
|
||||
var id: String = ""
|
||||
|
||||
@field:Attribute(name = "title", required = false)
|
||||
var title: String = ""
|
||||
|
||||
@field:Attribute(name = "description", required = false)
|
||||
var description: String? = null
|
||||
|
||||
@field:Attribute(name = "language", required = false)
|
||||
var language: String = ""
|
||||
|
||||
@field:Attribute(name = "creator", required = false)
|
||||
var creator: String = ""
|
||||
|
||||
@field:Attribute(name = "publisher", required = false)
|
||||
var publisher: String = ""
|
||||
|
||||
@field:Attribute(name = "favicon", required = false)
|
||||
var favicon: String = ""
|
||||
|
||||
@field:Attribute(name = "faviconMimeType", required = false)
|
||||
var faviconMimeType: String? = null
|
||||
|
||||
@field:Attribute(name = "date", required = false)
|
||||
var date: String = ""
|
||||
|
||||
@field:Attribute(name = "url", required = false)
|
||||
var url: String? = null
|
||||
|
||||
@field:Attribute(name = "articleCount", required = false)
|
||||
var articleCount: String? = null
|
||||
|
||||
@field:Attribute(name = "mediaCount", required = false)
|
||||
var mediaCount: String? = null
|
||||
|
||||
@field:Attribute(name = "size", required = false)
|
||||
var size: String = ""
|
||||
|
||||
@field:Attribute(name = "name", required = false)
|
||||
var bookName: String? = null
|
||||
|
||||
@field:Attribute(name = "tags", required = false)
|
||||
var tags: String? = null
|
||||
var searchMatches = 0
|
||||
|
||||
var file: File? = null
|
||||
|
||||
// Two books are equal if their ids match
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other is Book) {
|
||||
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()
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
fun copy(
|
||||
language: String = this.language,
|
||||
title: String = this.title,
|
||||
description: String? = this.description,
|
||||
creator: String = this.creator,
|
||||
publisher: String = this.publisher,
|
||||
favicon: String = this.favicon,
|
||||
faviconMimeType: String? = this.faviconMimeType,
|
||||
date: String = this.date,
|
||||
url: String? = this.url,
|
||||
articleCount: String? = this.articleCount,
|
||||
mediaCount: String? = this.mediaCount,
|
||||
size: String = this.size,
|
||||
bookName: String? = this.bookName,
|
||||
tags: String? = this.tags
|
||||
): Book {
|
||||
return Book().apply {
|
||||
this.id = this@Book.id
|
||||
this.title = title
|
||||
this.description = description
|
||||
this.language = language
|
||||
this.creator = creator
|
||||
this.publisher = publisher
|
||||
this.favicon = favicon
|
||||
this.faviconMimeType = faviconMimeType
|
||||
this.date = date
|
||||
this.url = url
|
||||
this.articleCount = articleCount
|
||||
this.mediaCount = mediaCount
|
||||
this.size = size
|
||||
this.bookName = bookName
|
||||
this.tags = tags
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -18,30 +18,33 @@
|
||||
|
||||
package org.kiwix.kiwixmobile.core.extensions
|
||||
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
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 ->
|
||||
if (searchableText.contains(filterWord, true)) {
|
||||
acc + 1
|
||||
} else {
|
||||
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 +57,21 @@ fun Book.buildSearchableText(bookUtils: BookUtils): String =
|
||||
append("|")
|
||||
}
|
||||
}.toString()
|
||||
|
||||
fun Book?.getFavicon(): String? =
|
||||
runCatching {
|
||||
val illustration = this?.getIllustration(ILLUSTRATION_SIZE)
|
||||
illustration?.url()?.ifBlank {
|
||||
illustration.data?.let {
|
||||
Base64.encodeToString(it, Base64.DEFAULT)
|
||||
}
|
||||
}
|
||||
}.getOrElse {
|
||||
it.printStackTrace().also {
|
||||
this?.illustrations?.forEach { illustration ->
|
||||
Log.e("BOOK", "getFavicon: ${illustration.data} and ${illustration.url()}")
|
||||
}
|
||||
Log.e("BOOK", "getFavicon: ${this?.title}")
|
||||
}
|
||||
""
|
||||
}
|
||||
|
@ -24,20 +24,6 @@ import androidx.annotation.ColorInt
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.widget.ImageViewCompat
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.Base64String
|
||||
|
||||
fun ImageView.setBitmap(base64String: Base64String) {
|
||||
base64String.toBitmap()
|
||||
?.let(::setImageBitmap)
|
||||
?: kotlin.run { setImageDrawableCompat(R.drawable.default_zim_file_icon) }
|
||||
}
|
||||
|
||||
// methods that accept inline classes as parameters are not allowed to be called from java
|
||||
// hence this facade
|
||||
fun ImageView.setBitmapFromString(string: String?) {
|
||||
setBitmap(Base64String(string))
|
||||
}
|
||||
|
||||
fun ImageView.setImageDrawableCompat(
|
||||
@DrawableRes id: Int
|
||||
|
@ -340,6 +340,7 @@ abstract class CoreReaderFragment :
|
||||
private val navigationHistoryList: MutableList<NavigationHistoryListItem> = ArrayList()
|
||||
private var isReadSelection = false
|
||||
private var isReadAloudServiceRunning = false
|
||||
private var libkiwixBook: Book? = null
|
||||
private var readerLifeCycleScope: CoroutineScope? = null
|
||||
|
||||
val coreReaderLifeCycleScope: CoroutineScope?
|
||||
@ -2038,9 +2039,7 @@ abstract class CoreReaderFragment :
|
||||
lifecycleScope.launch {
|
||||
getCurrentWebView()?.url?.let { articleUrl ->
|
||||
zimReaderContainer?.zimFileReader?.let { zimFileReader ->
|
||||
val libKiwixBook = Book().apply {
|
||||
update(zimFileReader.jniKiwixReader)
|
||||
}
|
||||
val libKiwixBook = getLibkiwixBook(zimFileReader)
|
||||
if (isBookmarked) {
|
||||
repositoryActions?.deleteBookmark(libKiwixBook.id, articleUrl)
|
||||
snackBarRoot?.snack(R.string.bookmark_removed)
|
||||
@ -2071,6 +2070,19 @@ abstract class CoreReaderFragment :
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the libkiwix book evertime when user saves or remove the bookmark.
|
||||
* the object will be created once to avoid creating it multiple times.
|
||||
*/
|
||||
private fun getLibkiwixBook(zimFileReader: ZimFileReader): Book {
|
||||
libkiwixBook?.let { return it }
|
||||
val book = Book().apply {
|
||||
update(zimFileReader.jniKiwixReader)
|
||||
}
|
||||
libkiwixBook = book
|
||||
return book
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
updateBottomToolbarVisibility()
|
||||
|
@ -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"
|
||||
|
@ -42,7 +42,7 @@ import kotlinx.coroutines.sync.withLock
|
||||
import org.kiwix.kiwixmobile.core.CoreApp
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.downloader.ChunkUtils
|
||||
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
|
||||
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
|
||||
import org.kiwix.kiwixmobile.core.extensions.deleteFile
|
||||
import org.kiwix.kiwixmobile.core.extensions.isFileExist
|
||||
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
|
||||
@ -494,7 +494,7 @@ object FileUtils {
|
||||
|
||||
@Suppress("NestedBlockDepth")
|
||||
@JvmStatic
|
||||
suspend fun getAllZimParts(book: Book): List<File> {
|
||||
suspend fun getAllZimParts(book: LibkiwixBook): List<File> {
|
||||
val files = ArrayList<File>()
|
||||
book.file?.let {
|
||||
if (it.path.endsWith(".zim") || it.path.endsWith(".zim.part")) {
|
||||
|
@ -23,10 +23,10 @@ import kotlin.math.log10
|
||||
import kotlin.math.pow
|
||||
|
||||
@JvmInline
|
||||
value class KiloByte(private val kilobyteString: String?) {
|
||||
value class Byte(private val byteString: String?) {
|
||||
val humanReadable
|
||||
get() = kilobyteString?.toLongOrNull()?.let {
|
||||
val units = arrayOf("KB", "MB", "GB", "TB")
|
||||
get() = byteString?.toLongOrNull()?.let {
|
||||
val units = arrayOf("B", "KB", "MB", "GB", "TB")
|
||||
val conversion = (log10(it.toDouble()) / log10(1024.0)).toInt()
|
||||
DecimalFormat("#,##0.#")
|
||||
.format(it / 1024.0.pow(conversion.toDouble())) +
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.zim_manager
|
||||
|
||||
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
|
||||
import org.kiwix.libkiwix.Library
|
||||
import org.kiwix.libkiwix.Manager
|
||||
|
||||
class OnlineLibraryManager(val library: Library, val manager: Manager) {
|
||||
suspend fun parseOPDSStream(content: String?, urlHost: String): Boolean =
|
||||
runCatching {
|
||||
manager.readOpds(content, urlHost)
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}.isSuccess
|
||||
|
||||
suspend fun getOnlineBooks(): List<LibkiwixBook> {
|
||||
val onlineBooksList = arrayListOf<LibkiwixBook>()
|
||||
runCatching {
|
||||
library.booksIds.forEach { bookId ->
|
||||
val book = library.getBookById(bookId)
|
||||
onlineBooksList.add(LibkiwixBook(book))
|
||||
}
|
||||
}.onFailure { it.printStackTrace() }
|
||||
return onlineBooksList
|
||||
}
|
||||
}
|
@ -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),
|
||||
|
@ -22,14 +22,12 @@ import com.tonyodev.fetch2.Status
|
||||
import com.tonyodev.fetch2.Status.NONE
|
||||
import org.kiwix.kiwixmobile.core.dao.entities.BookOnDiskEntity
|
||||
import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchEntity
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.Base64String
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.DownloadState
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.DownloadState.Pending
|
||||
import org.kiwix.kiwixmobile.core.downloader.model.Seconds
|
||||
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
|
||||
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
|
||||
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
|
||||
import org.kiwix.kiwixmobile.core.entity.MetaLinkNetworkEntity
|
||||
import org.kiwix.kiwixmobile.core.entity.MetaLinkNetworkEntity.FileElement
|
||||
import org.kiwix.kiwixmobile.core.entity.MetaLinkNetworkEntity.Pieces
|
||||
@ -38,11 +36,10 @@ import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.Language
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
|
||||
import java.io.File
|
||||
import java.util.LinkedList
|
||||
|
||||
fun bookOnDisk(
|
||||
databaseId: Long = 0L,
|
||||
book: Book = book(),
|
||||
book: LibkiwixBook = libkiwixBook(),
|
||||
zimReaderSource: ZimReaderSource = ZimReaderSource(File(""))
|
||||
) = BookOnDisk(databaseId, book, File(""), zimReaderSource)
|
||||
|
||||
@ -56,7 +53,7 @@ fun downloadModel(
|
||||
status: Status = NONE,
|
||||
error: Error = Error.NONE,
|
||||
progress: Int = 1,
|
||||
book: Book = book()
|
||||
book: LibkiwixBook = libkiwixBook()
|
||||
) = DownloadModel(
|
||||
databaseId, downloadId, file, etaInMilliSeconds, bytesDownloaded, totalSizeOfDownload,
|
||||
status, error, progress, book
|
||||
@ -64,7 +61,7 @@ fun downloadModel(
|
||||
|
||||
fun downloadItem(
|
||||
downloadId: Long = 1L,
|
||||
favIcon: Base64String = Base64String("favIcon"),
|
||||
favIcon: String = "favIcon",
|
||||
title: String = "title",
|
||||
description: String = "description",
|
||||
bytesDownloaded: Long = 1L,
|
||||
@ -135,7 +132,7 @@ fun url(
|
||||
this.value = value
|
||||
}
|
||||
|
||||
fun book(
|
||||
fun libkiwixBook(
|
||||
id: String = "id",
|
||||
title: String = "title",
|
||||
description: String = "description",
|
||||
@ -150,7 +147,7 @@ fun book(
|
||||
name: String = "name",
|
||||
favIcon: String = "favIcon",
|
||||
file: File = File("")
|
||||
) = Book().apply {
|
||||
) = LibkiwixBook().apply {
|
||||
this.id = id
|
||||
this.title = title
|
||||
this.description = description
|
||||
@ -167,11 +164,6 @@ fun book(
|
||||
favicon = favIcon
|
||||
}
|
||||
|
||||
fun libraryNetworkEntity(books: List<Book> = emptyList()) =
|
||||
LibraryNetworkEntity().apply {
|
||||
book = LinkedList(books)
|
||||
}
|
||||
|
||||
fun recentSearchEntity(
|
||||
id: Long = 0L,
|
||||
searchTerm: String = "",
|
||||
|
@ -40,7 +40,7 @@ import org.kiwix.kiwixmobile.core.utils.files.FileSearch
|
||||
import org.kiwix.kiwixmobile.core.utils.files.ScanningProgressListener
|
||||
import org.kiwix.kiwixmobile.core.utils.files.testFlow
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
|
||||
import org.kiwix.sharedFunctions.book
|
||||
import org.kiwix.sharedFunctions.libkiwixBook
|
||||
import org.kiwix.sharedFunctions.bookOnDisk
|
||||
import java.io.File
|
||||
|
||||
@ -85,7 +85,7 @@ class StorageObserverTest {
|
||||
@Test
|
||||
fun `zim files are read by the file reader`() = runTest {
|
||||
val expectedBook =
|
||||
book(
|
||||
libkiwixBook(
|
||||
"id", "title", "1", "favicon", "creator", "publisher", "date",
|
||||
"description", "language"
|
||||
)
|
||||
|
@ -40,13 +40,13 @@ import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.kiwix.kiwixmobile.core.dao.entities.BookOnDiskEntity
|
||||
import org.kiwix.kiwixmobile.core.dao.entities.BookOnDiskEntity_
|
||||
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
|
||||
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
|
||||
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
|
||||
import org.kiwix.kiwixmobile.core.utils.files.testFlow
|
||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
|
||||
import org.kiwix.sharedFunctions.book
|
||||
import org.kiwix.sharedFunctions.bookOnDisk
|
||||
import org.kiwix.sharedFunctions.bookOnDiskEntity
|
||||
import org.kiwix.sharedFunctions.libkiwixBook
|
||||
import java.io.File
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
@ -131,9 +131,9 @@ internal class NewBookDaoTest {
|
||||
fun `insert transaction adds books to the box that have distinct ids`() {
|
||||
val slot: CapturingSlot<Callable<Unit>> = slot()
|
||||
every { box.store.callInTx(capture(slot)) } returns Unit
|
||||
val distinctBook: BookOnDisk = bookOnDisk(databaseId = 0, book = book(id = "same"))
|
||||
val distinctBook: BookOnDisk = bookOnDisk(databaseId = 0, book = libkiwixBook(id = "same"))
|
||||
newBookDao.insert(
|
||||
listOf(distinctBook, bookOnDisk(databaseId = 1, book = book(id = "same")))
|
||||
listOf(distinctBook, bookOnDisk(databaseId = 1, book = libkiwixBook(id = "same")))
|
||||
)
|
||||
val queryBuilder: QueryBuilder<BookOnDiskEntity> = mockk(relaxed = true)
|
||||
every { box.query() } returns queryBuilder
|
||||
@ -216,7 +216,7 @@ internal class NewBookDaoTest {
|
||||
|
||||
@Test
|
||||
fun migrationInsert() {
|
||||
val book: LibraryNetworkEntity.Book = book()
|
||||
val book: LibkiwixBook = libkiwixBook()
|
||||
val slot: CapturingSlot<Callable<Unit>> = slot()
|
||||
every { box.store.callInTx(capture(slot)) } returns Unit
|
||||
newBookDao.migrationInsert(listOf(book))
|
||||
|
@ -22,16 +22,16 @@ import io.mockk.clearMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.kiwix.kiwixmobile.core.CoreApp
|
||||
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
|
||||
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
|
||||
import java.io.File
|
||||
|
||||
class FileUtilsTest {
|
||||
private val mockFile: File = mockk()
|
||||
private val testBook = Book().apply { file = mockFile }
|
||||
private val testBook = LibkiwixBook().apply { file = mockFile }
|
||||
private val testId = "8ce5775a-10a9-bbf3-178a-9df69f23263c"
|
||||
private val fileName = "/data/user/0/org.kiwix.kiwixmobile/files${File.separator}$testId"
|
||||
|
||||
|
@ -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
|
||||
|
@ -22,7 +22,7 @@ import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.kiwix.kiwixmobile.core.downloader.Downloader
|
||||
import org.kiwix.sharedFunctions.book
|
||||
import org.kiwix.sharedFunctions.libkiwixBook
|
||||
|
||||
internal class DownloadCustomTest {
|
||||
@Test
|
||||
@ -35,7 +35,7 @@ internal class DownloadCustomTest {
|
||||
}
|
||||
|
||||
private fun expectedBook() =
|
||||
book(
|
||||
libkiwixBook(
|
||||
"custom",
|
||||
"",
|
||||
"",
|
||||
|
Loading…
x
Reference in New Issue
Block a user