EXPERIMENT - Adding OPDS stream

This commit is contained in:
Sean Mac Gillicuddy 2020-04-28 08:55:29 +01:00
parent d3a1be703f
commit fcd79b1d0e
15 changed files with 205 additions and 36 deletions

View File

@ -84,4 +84,10 @@ dependencies {
implementation(Libs.squidb_annotations)
implementation(Libs.ink_page_indicator)
add("kapt", Libs.squidb_processor)
val pagingVersion = "2.1.1"
implementation("androidx.paging:paging-runtime-ktx:$pagingVersion")
testImplementation("androidx.paging:paging-common-ktx:$pagingVersion")
implementation("androidx.paging:paging-rxjava2-ktx:$pagingVersion")
implementation ("com.hannesdorfmann:adapterdelegates4-pagination:4.2.0")
}

View File

@ -0,0 +1,25 @@
/*
* Kiwix Android
* Copyright (c) 2020 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
import org.kiwix.kiwixlib.Book
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
typealias NetworkBook = LibraryNetworkEntity.Book
typealias JniBook = Book

View File

@ -23,8 +23,8 @@ import android.app.Service
import android.content.Context
import dagger.Module
import dagger.Provides
import org.kiwix.kiwixlib.Library
import org.kiwix.kiwixlib.JNIKiwixServer
import org.kiwix.kiwixlib.Library
import org.kiwix.kiwixmobile.di.ServiceScope
import org.kiwix.kiwixmobile.webserver.WebServerHelper
import org.kiwix.kiwixmobile.webserver.wifi_hotspot.HotspotNotificationManager

View File

@ -26,8 +26,8 @@ import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import org.kiwix.kiwixlib.JNIKiwixException;
import org.kiwix.kiwixlib.Library;
import org.kiwix.kiwixlib.JNIKiwixServer;
import org.kiwix.kiwixlib.Library;
import org.kiwix.kiwixmobile.core.utils.ServerUtils;
import org.kiwix.kiwixmobile.webserver.wifi_hotspot.IpAddressCallbacks;

View File

@ -0,0 +1,53 @@
/*
* Kiwix Android
* Copyright (c) 2020 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.zim_manager
import org.kiwix.kiwixlib.Filter
import org.kiwix.kiwixlib.Library
import org.kiwix.kiwixlib.Manager
import org.kiwix.kiwixmobile.core.di.modules.NetworkModule.KIWIX_DOWNLOAD_URL
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import javax.inject.Inject
class NetworkResponseMapper @Inject constructor() {
fun map(rawStream: String): List<Book> =
with(Library()) {
Manager(this).readOpds(rawStream, KIWIX_DOWNLOAD_URL)
val filter = filter(Filter().query("wikipedia articles about"))
filter.map(this::getBookById).map(::OpdsBook).map {
Book().apply {
id = it.id
title = it.title
description = it.description
language = it.language
creator = it.creator
publisher = it.publisher
date = it.date
url = it.url
articleCount = it.articleCount.toString()
mediaCount = it.mediaCount.toString()
size = it.size.toString()
bookName = it.name
favicon = it.favicon
tags = it.tags
}
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Kiwix Android
* Copyright (c) 2020 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.zim_manager
import org.kiwix.kiwixmobile.JniBook
data class OpdsBook(
val id: String,
val path: String,
val isPathValid: Boolean,
val title: String,
val description: String,
val language: String,
val creator: String,
val publisher: String,
val date: String,
val url: String,
val name: String,
val flavour: String,
val tags: String,
val articleCount: Long,
val mediaCount: Long,
val size: Long,
val favicon: String,
val faviconUrl: String,
val faviconMimeType: String
) {
constructor(book: JniBook) : this(
book.id,
book.path,
book.isPathValid,
book.title,
book.description,
book.language,
book.creator,
book.publisher,
book.date,
book.url,
book.name,
book.flavour,
book.tags,
book.articleCount,
book.mediaCount,
book.size,
book.favicon,
book.faviconUrl,
book.faviconMimeType
)
}

View File

@ -30,6 +30,8 @@ import io.reactivex.functions.Function6
import io.reactivex.processors.BehaviorProcessor
import io.reactivex.processors.PublishProcessor
import io.reactivex.schedulers.Schedulers
import org.kiwix.kiwixmobile.NetworkBook
import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.StorageObserver
import org.kiwix.kiwixmobile.core.base.SideEffect
@ -39,9 +41,9 @@ 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
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.core.extensions.calculateSearchMatches
import org.kiwix.kiwixmobile.core.extensions.locale
import org.kiwix.kiwixmobile.core.extensions.registerReceiver
import org.kiwix.kiwixmobile.core.utils.BookUtils
import org.kiwix.kiwixmobile.core.zim_manager.Language
@ -68,12 +70,19 @@ import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.BookItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.DividerItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.LibraryDownloadItem
import java.util.LinkedList
import java.util.Locale
import java.util.MissingResourceException
import java.util.concurrent.TimeUnit.MILLISECONDS
import java.util.concurrent.TimeUnit.SECONDS
import javax.inject.Inject
private val Locale.safeIsO3Language: String
get() = try {
isO3Language
} catch (_: MissingResourceException) {
language
}
class ZimManageViewModel @Inject constructor(
private val downloadDao: FetchDownloadDao,
private val bookDao: NewBookDao,
@ -85,7 +94,8 @@ class ZimManageViewModel @Inject constructor(
private val bookUtils: BookUtils,
private val fat32Checker: Fat32Checker,
private val defaultLanguageProvider: DefaultLanguageProvider,
private val dataSource: DataSource
private val dataSource: DataSource,
private val networkResponseMapper: NetworkResponseMapper
) : ViewModel() {
sealed class FileSelectActions {
data class RequestOpen(val bookOnDisk: BookOnDisk) : FileSelectActions()
@ -97,7 +107,7 @@ class ZimManageViewModel @Inject constructor(
object RestartActionMode : FileSelectActions()
}
val sideEffects = PublishProcessor.create<SideEffect<out Any?>>()
val sideEffects = PublishProcessor.create<SideEffect<Any?>>()
val libraryItems: MutableLiveData<List<LibraryListItem>> = MutableLiveData()
val fileSelectListStates: MutableLiveData<FileSelectListState> = MutableLiveData()
val deviceListIsRefreshing = MutableLiveData<Boolean>()
@ -133,7 +143,7 @@ class ZimManageViewModel @Inject constructor(
private fun disposables(): Array<Disposable> {
val downloads = downloadDao.downloads()
val booksFromDao = books()
val networkLibrary = PublishProcessor.create<LibraryNetworkEntity>()
val networkLibrary = PublishProcessor.create<List<NetworkBook>>()
val languages = languageDao.languages()
return arrayOf(
updateBookItems(),
@ -210,7 +220,7 @@ class ZimManageViewModel @Inject constructor(
}
private fun requestsAndConnectivtyChangesToLibraryRequests(
library: PublishProcessor<LibraryNetworkEntity>
library: PublishProcessor<List<NetworkBook>>
) =
Flowable.combineLatest(
requestDownloadLibrary,
@ -223,14 +233,19 @@ class ZimManageViewModel @Inject constructor(
.observeOn(Schedulers.io())
.subscribe(
{
kiwixService.library
kiwixService.getOpdsLibrary(
null,
CoreApp.getInstance().locale.safeIsO3Language,
null,
"1000",
null
)
.timeout(60, SECONDS)
.retry(5)
.subscribe(
library::onNext
) {
.map(networkResponseMapper::map)
.subscribe(library::onNext) {
it.printStackTrace()
library.onNext(LibraryNetworkEntity().apply { book = LinkedList() })
library.onNext(emptyList())
}
},
Throwable::printStackTrace
@ -244,7 +259,7 @@ class ZimManageViewModel @Inject constructor(
private fun updateLibraryItems(
booksFromDao: Flowable<List<BookOnDisk>>,
downloads: Flowable<List<DownloadModel>>,
library: Flowable<LibraryNetworkEntity>,
library: Flowable<List<NetworkBook>>,
languages: Flowable<List<Language>>
) = Flowable.combineLatest(
booksFromDao,
@ -269,11 +284,10 @@ class ZimManageViewModel @Inject constructor(
)
private fun updateLanguagesInDao(
library: Flowable<LibraryNetworkEntity>,
library: Flowable<List<NetworkBook>>,
languages: Flowable<List<Language>>
) = library
.subscribeOn(Schedulers.io())
.map { it.books }
.withLatestFrom(
languages,
BiFunction(::combineToLanguageList)
@ -340,7 +354,7 @@ class ZimManageViewModel @Inject constructor(
booksOnFileSystem: List<BookOnDisk>,
activeDownloads: List<DownloadModel>,
allLanguages: List<Language>,
libraryNetworkEntity: LibraryNetworkEntity,
networkBooks: List<NetworkBook>,
filter: String,
fileSystemState: FileSystemState
): List<LibraryListItem> {
@ -348,7 +362,7 @@ class ZimManageViewModel @Inject constructor(
.map(Language::languageCode)
val booksUnfilteredByLanguage =
applySearchFilter(
libraryNetworkEntity.books - booksOnFileSystem.map(BookOnDisk::book),
networkBooks - booksOnFileSystem.map(BookOnDisk::book),
filter
)

View File

@ -23,6 +23,7 @@ import android.view.View
import android.view.View.MeasureSpec
import android.widget.Toast
import androidx.annotation.StringRes
import eu.mhutti1.utils.storage.Bytes
import kotlinx.android.synthetic.main.item_download.downloadProgress
import kotlinx.android.synthetic.main.item_download.downloadState
import kotlinx.android.synthetic.main.item_download.eta
@ -50,7 +51,6 @@ import org.kiwix.kiwixmobile.core.extensions.setBitmap
import org.kiwix.kiwixmobile.core.extensions.setTextAndVisibility
import org.kiwix.kiwixmobile.core.utils.BookUtils
import org.kiwix.kiwixmobile.core.utils.NetworkUtils
import org.kiwix.kiwixmobile.core.zim_manager.KiloByte
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CannotWrite4GbFile
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.Unknown
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.BookItem
@ -71,7 +71,7 @@ sealed class LibraryViewHolder<in T : LibraryListItem>(containerView: View) :
libraryBookCreator.setTextAndVisibility(item.book.creator)
libraryBookPublisher.setTextAndVisibility(item.book.publisher)
libraryBookDate.setTextAndVisibility(item.book.date)
libraryBookSize.setTextAndVisibility(KiloByte(item.book.size).humanReadable)
libraryBookSize.setTextAndVisibility(Bytes(item.book.size.toLong()).humanReadable)
libraryBookLanguage.text = bookUtils.getLanguage(item.book.getLanguage())
libraryBookFileName.text = NetworkUtils.parseURL(CoreApp.getInstance(), item.book.url)
libraryBookFavicon.setBitmap(Base64String(item.book.favicon))

View File

@ -60,4 +60,5 @@ dependencies {
implementation(Libs.android_arch_lifecycle_extensions)
implementation(Libs.objectbox_kotlin)
implementation(Libs.objectbox_rxjava)
implementation("com.squareup.retrofit2:converter-scalars:2.1.0")
}

Binary file not shown.

View File

@ -20,20 +20,28 @@ package org.kiwix.kiwixmobile.core.data.remote;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
import java.util.List;
import okhttp3.OkHttpClient;
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity;
import org.kiwix.kiwixmobile.core.entity.MetaLinkNetworkEntity;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;
import retrofit2.converter.simplexml.SimpleXmlConverterFactory;
import retrofit2.http.GET;
import retrofit2.http.Query;
import retrofit2.http.Url;
public interface KiwixService {
String LIBRARY_NETWORK_PATH = "/library/library_zim.xml";
String LIBRARY_NETWORK_PATH = "/catalog/search";
@GET(LIBRARY_NETWORK_PATH) Single<LibraryNetworkEntity> getLibrary();
@GET(LIBRARY_NETWORK_PATH) Single<String> getOpdsLibrary(
@Query("q") String searchTerm,
@Query("lang") String language,
@Query("tag") List<String> tags,
@Query("count") String count,
@Query("start") String start
);
@GET Observable<MetaLinkNetworkEntity> getMetaLinks(@Url String url);
@ -44,6 +52,7 @@ public interface KiwixService {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(SimpleXmlConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
.build();

View File

@ -23,7 +23,7 @@ import dagger.Module
import dagger.Provides
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import okhttp3.logging.HttpLoggingInterceptor.Level.BASIC
import okhttp3.logging.HttpLoggingInterceptor.Level.BODY
import okhttp3.logging.HttpLoggingInterceptor.Level.NONE
import org.kiwix.kiwixmobile.core.BuildConfig
import org.kiwix.kiwixmobile.core.data.remote.KiwixService
@ -44,7 +44,7 @@ class NetworkModule {
.connectTimeout(CONNECTION_TIMEOUT, SECONDS)
.readTimeout(READ_TIMEOUT, SECONDS)
.addNetworkInterceptor(HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) BASIC else NONE
level = if (BuildConfig.DEBUG) BODY else NONE
})
.addNetworkInterceptor(UserAgentInterceptor(USER_AGENT))
.build()

View File

@ -21,11 +21,10 @@ package org.kiwix.kiwixmobile.core.extensions
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.util.TypedValue
import android.widget.Toast
import androidx.annotation.AttrRes
import androidx.core.os.ConfigurationCompat
import org.kiwix.kiwixmobile.core.base.BaseBroadcastReceiver
import java.util.Locale
@ -53,9 +52,7 @@ fun Context.registerReceiver(baseBroadcastReceiver: BaseBroadcastReceiver): Inte
registerReceiver(baseBroadcastReceiver, IntentFilter(baseBroadcastReceiver.action))
val Context.locale: Locale
get() =
if (VERSION.SDK_INT >= VERSION_CODES.N) resources.configuration.locales.get(0)
else resources.configuration.locale
get() = ConfigurationCompat.getLocales(resources.configuration).get(0)
fun Context.getColorAttribute(@AttrRes attributeRes: Int) = with(TypedValue()) {
if (theme.resolveAttribute(attributeRes, this, true))

View File

@ -219,7 +219,8 @@ class ZimFileReader constructor(
language = this@ZimFileReader.language
articleCount = this@ZimFileReader.articleCount.toString()
mediaCount = this@ZimFileReader.mediaCount.toString()
bookName = name
@Suppress("ExplicitThis")
bookName = this@ZimFileReader.name
tags = this@ZimFileReader.tags
}

View File

@ -30,7 +30,6 @@ import android.view.InflateException
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import androidx.core.os.ConfigurationCompat
import org.kiwix.kiwixmobile.core.extensions.locale
import org.kiwix.kiwixmobile.core.utils.files.FileUtils
import java.text.Collator
@ -191,10 +190,8 @@ class LanguageUtils(private val context: Context) {
@JvmStatic
fun handleLocaleChange(context: Context, language: String) {
val locale =
if (language == Locale.ROOT.toString())
ConfigurationCompat.getLocales(context.applicationContext.resources.configuration)[0]
else
Locale(language)
if (language == Locale.ROOT.toString()) context.applicationContext.locale
else Locale(language)
Locale.setDefault(locale)
val config = Configuration()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {