mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-08-03 18:56:44 -04:00
Introduced support of OPDS catalog. * Created the OnlineLibraryManager class to manage the OPDS stream with libkiwix. * Refactored the ZimManageViewModel, and KiwixService code according to OPDS catalog.
This commit is contained in:
parent
ccffb07ae4
commit
20722fe15b
@ -5,7 +5,7 @@
|
||||
<ID>EmptyFunctionBlock:None.kt$None${ }</ID>
|
||||
<ID>EmptyFunctionBlock:SimplePageChangeListener.kt$SimplePageChangeListener${ }</ID>
|
||||
<ID>LongParameterList:ZimManageViewModel.kt$ZimManageViewModel$( booksOnFileSystem: List<BookOnDisk>, activeDownloads: List<DownloadModel>, allLanguages: List<Language>, libraryNetworkEntity: LibraryNetworkEntity, filter: String, fileSystemState: FileSystemState )</ID>
|
||||
<ID>LongParameterList:ZimManageViewModel.kt$ZimManageViewModel$( private val downloadDao: DownloadRoomDao, private val bookDao: NewBookDao, private val languageDao: NewLanguagesDao, private val storageObserver: StorageObserver, private var kiwixService: KiwixService, val context: Application, private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver, private val bookUtils: BookUtils, private val fat32Checker: Fat32Checker, private val defaultLanguageProvider: DefaultLanguageProvider, private val dataSource: DataSource, private val connectivityManager: ConnectivityManager, private val sharedPreferenceUtil: SharedPreferenceUtil )</ID>
|
||||
<ID>LongParameterList:ZimManageViewModel.kt$ZimManageViewModel$( private val downloadDao: DownloadRoomDao, private val bookDao: NewBookDao, private val languageDao: NewLanguagesDao, private val storageObserver: StorageObserver, private var kiwixService: KiwixService, val context: Application, private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver, private val bookUtils: BookUtils, private val fat32Checker: Fat32Checker, private val defaultLanguageProvider: DefaultLanguageProvider, private val dataSource: DataSource, private val connectivityManager: ConnectivityManager, private val sharedPreferenceUtil: SharedPreferenceUtil, private val onlineLibraryParser: OnlineLibraryParser )</ID>
|
||||
<ID>MagicNumber:LibraryListItem.kt$LibraryListItem.LibraryDownloadItem$1000L</ID>
|
||||
<ID>MagicNumber:PeerGroupHandshake.kt$PeerGroupHandshake$15000</ID>
|
||||
<ID>MagicNumber:ShareFiles.kt$ShareFiles$24</ID>
|
||||
|
@ -70,12 +70,12 @@ 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
|
||||
@ -83,7 +83,6 @@ 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.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,6 @@ 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 java.util.Locale
|
||||
import java.util.concurrent.TimeUnit.SECONDS
|
||||
import javax.inject.Inject
|
||||
@ -145,7 +144,8 @@ class ZimManageViewModel @Inject constructor(
|
||||
private val defaultLanguageProvider: DefaultLanguageProvider,
|
||||
private val dataSource: DataSource,
|
||||
private val connectivityManager: ConnectivityManager,
|
||||
private val sharedPreferenceUtil: SharedPreferenceUtil
|
||||
private val sharedPreferenceUtil: SharedPreferenceUtil,
|
||||
private val onlineLibraryManager: OnlineLibraryManager
|
||||
) : ViewModel() {
|
||||
sealed class FileSelectActions {
|
||||
data class RequestNavigateTo(val bookOnDisk: BookOnDisk) : FileSelectActions()
|
||||
@ -168,7 +168,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<org.kiwix.libkiwix.Book>>(replay = 0)
|
||||
val requestFileSystemCheck = MutableSharedFlow<Unit>(replay = 0)
|
||||
val fileSelectActions = MutableSharedFlow<FileSelectActions>()
|
||||
private val requestDownloadLibrary = MutableSharedFlow<Unit>(
|
||||
@ -238,7 +238,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 +252,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 +391,7 @@ class ZimManageViewModel @Inject constructor(
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun requestsAndConnectivityChangesToLibraryRequests(
|
||||
library: MutableSharedFlow<LibraryNetworkEntity>,
|
||||
library: MutableSharedFlow<List<org.kiwix.libkiwix.Book>>,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) = requestDownloadLibrary.flatMapConcat {
|
||||
connectivityBroadcastReceiver.networkStates
|
||||
@ -439,16 +442,17 @@ class ZimManageViewModel @Inject constructor(
|
||||
|
||||
private fun downloadLibraryFlow(
|
||||
kiwixService: KiwixService
|
||||
): Flow<LibraryNetworkEntity?> = flow {
|
||||
): Flow<List<org.kiwix.libkiwix.Book>> = flow {
|
||||
downloadProgress.postValue(context.getString(R.string.starting_downloading_remote_library))
|
||||
val response = kiwixService.getLibrary()
|
||||
downloadProgress.postValue(context.getString(R.string.parsing_remote_library))
|
||||
emit(response)
|
||||
onlineLibraryManager.parseOPDSStream(response, KIWIX_OPDS_LIBRARY_URL)
|
||||
emit(onlineLibraryManager.getOnlineBooks())
|
||||
}
|
||||
.retry(5)
|
||||
.catch { e ->
|
||||
e.printStackTrace()
|
||||
emit(LibraryNetworkEntity().apply { book = LinkedList() })
|
||||
emit(emptyList())
|
||||
}
|
||||
|
||||
private fun updateNetworkStates() = connectivityBroadcastReceiver.networkStates
|
||||
@ -460,7 +464,7 @@ class ZimManageViewModel @Inject constructor(
|
||||
private fun updateLibraryItems(
|
||||
booksFromDao: Flow<List<BookOnDisk>>,
|
||||
downloads: Flow<List<DownloadModel>>,
|
||||
library: MutableSharedFlow<LibraryNetworkEntity>,
|
||||
library: MutableSharedFlow<List<org.kiwix.libkiwix.Book>>,
|
||||
languages: Flow<List<Language>>,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) = viewModelScope.launch(dispatcher) {
|
||||
@ -483,7 +487,7 @@ 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<org.kiwix.libkiwix.Book>
|
||||
val filter = args[FOUR] as String
|
||||
val fileSystemState = args[FIVE] as FileSystemState
|
||||
combineLibrarySources(
|
||||
@ -505,12 +509,12 @@ class ZimManageViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun updateLanguagesInDao(
|
||||
library: MutableSharedFlow<LibraryNetworkEntity>,
|
||||
library: MutableSharedFlow<List<org.kiwix.libkiwix.Book>>,
|
||||
languages: Flow<List<Language>>,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) =
|
||||
combine(
|
||||
library.map { it.book }.filterNotNull(),
|
||||
library,
|
||||
languages
|
||||
) { books, existingLanguages ->
|
||||
combineToLanguageList(books, existingLanguages)
|
||||
@ -523,7 +527,7 @@ class ZimManageViewModel @Inject constructor(
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
private fun combineToLanguageList(
|
||||
booksFromNetwork: List<Book>,
|
||||
booksFromNetwork: List<org.kiwix.libkiwix.Book>,
|
||||
allLanguages: List<Language>
|
||||
) = when {
|
||||
booksFromNetwork.isEmpty() -> {
|
||||
@ -547,8 +551,8 @@ class ZimManageViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun networkLanguageCounts(booksFromNetwork: List<Book>) =
|
||||
booksFromNetwork.mapNotNull(Book::language)
|
||||
private fun networkLanguageCounts(booksFromNetwork: List<org.kiwix.libkiwix.Book>) =
|
||||
booksFromNetwork.mapNotNull { it.language }
|
||||
.fold(
|
||||
mutableMapOf<String, Int>()
|
||||
) { acc, language -> acc.increment(language) }
|
||||
@ -585,14 +589,14 @@ class ZimManageViewModel @Inject constructor(
|
||||
booksOnFileSystem: List<BookOnDisk>,
|
||||
activeDownloads: List<DownloadModel>,
|
||||
allLanguages: List<Language>,
|
||||
libraryNetworkEntity: LibraryNetworkEntity,
|
||||
onlineBooks: List<org.kiwix.libkiwix.Book>,
|
||||
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 }
|
||||
|
@ -20,16 +20,14 @@
|
||||
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.Retrofit
|
||||
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(): String?
|
||||
|
||||
@GET
|
||||
suspend fun getMetaLinks(
|
||||
@ -43,13 +41,14 @@ interface KiwixService {
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(SimpleXmlConverterFactory.create())
|
||||
.build()
|
||||
return retrofit.create(KiwixService::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
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 = "/entries?count=-1"
|
||||
}
|
||||
}
|
||||
|
@ -24,9 +24,11 @@ import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
|
||||
import org.kiwix.kiwixmobile.core.dao.NewBookDao
|
||||
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
|
||||
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
||||
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
|
||||
@ -36,20 +38,39 @@ 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(
|
||||
library: Library,
|
||||
manager: Manager,
|
||||
@Named("bookmarks") library: Library,
|
||||
@Named("bookmarks") 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)
|
||||
}
|
||||
|
@ -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/v2"
|
||||
|
||||
@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)
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.libkiwix.Book
|
||||
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<Book> {
|
||||
val onlineBooksList = arrayListOf<Book>()
|
||||
runCatching {
|
||||
library.booksIds.forEach { bookId ->
|
||||
val book = library.getBookById(bookId)
|
||||
onlineBooksList.add(book)
|
||||
}
|
||||
}.onFailure { it.printStackTrace() }
|
||||
return onlineBooksList
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user