Sources settings

This commit is contained in:
Koitharu
2020-03-14 10:20:56 +02:00
parent 68b196de52
commit 3c4e29149f
32 changed files with 178 additions and 106 deletions

View File

@@ -13,9 +13,9 @@ import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin
import org.koin.dsl.module
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.http.persistentcookiejar.PersistentCookieJar
import org.koitharu.kotatsu.core.http.persistentcookiejar.cache.SetCookieCache
import org.koitharu.kotatsu.core.http.persistentcookiejar.persistence.SharedPrefsCookiePersistor
import org.koitharu.kotatsu.core.local.cookies.PersistentCookieJar
import org.koitharu.kotatsu.core.local.cookies.cache.SetCookieCache
import org.koitharu.kotatsu.core.local.cookies.persistence.SharedPrefsCookiePersistor
import org.koitharu.kotatsu.core.local.CbzFetcher
import org.koitharu.kotatsu.core.local.PagesCache
import org.koitharu.kotatsu.core.prefs.AppSettings
@@ -47,19 +47,15 @@ class KotatsuApp : Application() {
.cache(CacheUtils.createHttpCache(applicationContext))
.build()
}
}, module {
single {
MangaLoaderContext()
}
}, module {
single {
mangaDb().build()
}
}, module {
single {
MangaLoaderContext()
}
factory {
AppSettings(applicationContext)
}
}, module {
single {
PagesCache(applicationContext)
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.koitharu.kotatsu.core.http.persistentcookiejar
package org.koitharu.kotatsu.core.local.cookies
import okhttp3.CookieJar

View File

@@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.koitharu.kotatsu.core.http.persistentcookiejar
package org.koitharu.kotatsu.core.local.cookies
import org.koitharu.kotatsu.core.http.persistentcookiejar.persistence.CookiePersistor
import org.koitharu.kotatsu.core.local.cookies.persistence.CookiePersistor
import okhttp3.Cookie
import okhttp3.HttpUrl
import org.koitharu.kotatsu.core.http.persistentcookiejar.cache.CookieCache
import org.koitharu.kotatsu.core.local.cookies.cache.CookieCache
import java.util.*
class PersistentCookieJar(

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.koitharu.kotatsu.core.http.persistentcookiejar.cache
package org.koitharu.kotatsu.core.local.cookies.cache
import okhttp3.Cookie

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.koitharu.kotatsu.core.http.persistentcookiejar.cache
package org.koitharu.kotatsu.core.local.cookies.cache
import okhttp3.Cookie
import java.util.*

View File

@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.koitharu.kotatsu.core.http.persistentcookiejar.cache
package org.koitharu.kotatsu.core.local.cookies.cache
import okhttp3.Cookie
import org.koitharu.kotatsu.core.http.persistentcookiejar.cache.IdentifiableCookie.Companion.decorateAll
import org.koitharu.kotatsu.core.local.cookies.cache.IdentifiableCookie.Companion.decorateAll
import java.util.*
import java.util.concurrent.ConcurrentHashMap

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.koitharu.kotatsu.core.http.persistentcookiejar.persistence
package org.koitharu.kotatsu.core.local.cookies.persistence
import okhttp3.Cookie

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.koitharu.kotatsu.core.http.persistentcookiejar.persistence
package org.koitharu.kotatsu.core.local.cookies.persistence
import android.util.Log
import okhttp3.Cookie

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.koitharu.kotatsu.core.http.persistentcookiejar.persistence
package org.koitharu.kotatsu.core.local.cookies.persistence
import android.annotation.SuppressLint
import android.content.Context

View File

@@ -1,16 +0,0 @@
package org.koitharu.kotatsu.core.parser
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.domain.MangaLoaderContext
abstract class BaseMangaRepository(protected val loaderContext: MangaLoaderContext) :
MangaRepository {
override val sortOrders: Set<SortOrder> get() = emptySet()
override suspend fun getPageFullUrl(page: MangaPage) : String = page.url
override suspend fun getTags(): Set<MangaTag> = emptySet()
}

View File

@@ -4,10 +4,10 @@ import android.content.Context
import android.net.Uri
import androidx.core.net.toFile
import androidx.core.net.toUri
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.koitharu.kotatsu.core.local.CbzFilter
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.domain.local.MangaIndex
import org.koitharu.kotatsu.domain.local.MangaZip
import org.koitharu.kotatsu.utils.AlphanumComparator
@@ -19,9 +19,9 @@ import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
class LocalMangaRepository(loaderContext: MangaLoaderContext) : BaseMangaRepository(loaderContext) {
class LocalMangaRepository : MangaRepository, KoinComponent {
private val context by loaderContext.inject<Context>()
private val context by inject<Context>()
override suspend fun getList(
offset: Int,
@@ -114,6 +114,12 @@ class LocalMangaRepository(loaderContext: MangaLoaderContext) : BaseMangaReposit
return list.firstOrNull()
}
override val sortOrders = emptySet<SortOrder>()
override suspend fun getPageFullUrl(page: MangaPage) = page.url
override suspend fun getTags() = emptySet<MangaTag>()
companion object {
fun isFileSupported(name: String): Boolean {

View File

@@ -0,0 +1,27 @@
package org.koitharu.kotatsu.core.parser
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.domain.MangaLoaderContext
abstract class RemoteMangaRepository : MangaRepository, KoinComponent {
protected abstract val source: MangaSource
protected val loaderContext by inject<MangaLoaderContext>()
protected val conf by lazy(LazyThreadSafetyMode.NONE) {
loaderContext.getSettings(source)
}
override val sortOrders: Set<SortOrder> get() = emptySet()
override suspend fun getPageFullUrl(page: MangaPage): String = page.url
override suspend fun getTags(): Set<MangaTag> = emptySet()
abstract fun onCreatePreferences(): Set<Int>
}

View File

@@ -1,17 +1,14 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.BaseMangaRepository
import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.utils.ext.*
abstract class ChanRepository(
private val source: MangaSource,
loaderContext: MangaLoaderContext
) : BaseMangaRepository(loaderContext) {
abstract class ChanRepository : RemoteMangaRepository() {
protected abstract val domain: String
protected abstract val defaultDomain: String
override val sortOrders = setOf(SortOrder.NEWEST, SortOrder.POPULARITY, SortOrder.ALPHABETICAL)
@@ -21,12 +18,13 @@ abstract class ChanRepository(
sortOrder: SortOrder?,
tag: MangaTag?
): List<Manga> {
val domain = conf.getDomain(defaultDomain)
val url = when {
query != null -> "https://$domain/?do=search&subaction=search&story=${query.urlEncoded()}"
tag != null -> "https://$domain/tags/${tag.key}&n=${getSortKey2(sortOrder)}?offset=$offset"
else -> "https://$domain/${getSortKey(sortOrder)}?offset=$offset"
}
val doc = loaderContext.get(url).parseHtml()
val doc = loaderContext.httpGet(url).parseHtml()
val root = doc.body().selectFirst("div.main_fon").getElementById("content")
?: throw ParseException("Cannot find root")
return root.select("div.content_row").mapNotNull { row ->
@@ -60,7 +58,8 @@ abstract class ChanRepository(
override suspend fun getDetails(manga: Manga): Manga {
val doc = loaderContext.get(manga.url).parseHtml()
val domain = conf.getDomain(defaultDomain)
val doc = loaderContext.httpGet(manga.url).parseHtml()
val root =
doc.body().getElementById("dle-content") ?: throw ParseException("Cannot find root")
return manga.copy(
@@ -83,7 +82,7 @@ abstract class ChanRepository(
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val doc = loaderContext.get(chapter.url).parseHtml()
val doc = loaderContext.httpGet(chapter.url).parseHtml()
val scripts = doc.select("script")
for (script in scripts) {
val data = script.html()
@@ -107,7 +106,8 @@ abstract class ChanRepository(
}
override suspend fun getTags(): Set<MangaTag> {
val doc = loaderContext.get("https://$domain/catalog").parseHtml()
val domain = conf.getDomain(defaultDomain)
val doc = loaderContext.httpGet("https://$domain/catalog").parseHtml()
val root = doc.body().selectFirst("div.main_fon").getElementById("side")
.select("ul").last()
return root.select("li.sidetag").map { li ->
@@ -120,6 +120,8 @@ abstract class ChanRepository(
}.toSet()
}
override fun onCreatePreferences() = setOf(R.string.key_parser_domain)
private fun getSortKey(sortOrder: SortOrder?) =
when (sortOrder ?: sortOrders.minBy { it.ordinal }) {
SortOrder.ALPHABETICAL -> "catalog"

View File

@@ -1,18 +1,14 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.BaseMangaRepository
import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.utils.ext.*
abstract class GroupleRepository(
private val source: MangaSource,
loaderContext: MangaLoaderContext
) :
BaseMangaRepository(loaderContext) {
abstract class GroupleRepository : RemoteMangaRepository() {
protected abstract val domain: String
protected abstract val defaultDomain: String
override val sortOrders = setOf(
SortOrder.UPDATED, SortOrder.POPULARITY,
@@ -26,13 +22,14 @@ abstract class GroupleRepository(
sortOrder: SortOrder?,
tag: MangaTag?
): List<Manga> {
val domain = conf.getDomain(defaultDomain)
val doc = when {
!query.isNullOrEmpty() -> loaderContext.post(
!query.isNullOrEmpty() -> loaderContext.httpPost(
"https://$domain/search",
mapOf("q" to query)
)
tag == null -> loaderContext.get("https://$domain/list?sortType=${getSortKey(sortOrder)}&offset=$offset")
else -> loaderContext.get(
tag == null -> loaderContext.httpGet("https://$domain/list?sortType=${getSortKey(sortOrder)}&offset=$offset")
else -> loaderContext.httpGet(
"https://$domain/list/genre/${tag.key}?sortType=${getSortKey(
sortOrder
)}&offset=$offset"
@@ -86,7 +83,8 @@ abstract class GroupleRepository(
}
override suspend fun getDetails(manga: Manga): Manga {
val doc = loaderContext.get(manga.url).parseHtml()
val domain = conf.getDomain(defaultDomain)
val doc = loaderContext.httpGet(manga.url).parseHtml()
val root = doc.body().getElementById("mangaBox") ?: throw ParseException("Cannot find root")
return manga.copy(
description = root.selectFirst("div.manga-description").firstChild()?.html(),
@@ -109,7 +107,7 @@ abstract class GroupleRepository(
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val doc = loaderContext.get(chapter.url + "?mtr=1").parseHtml()
val doc = loaderContext.httpGet(chapter.url + "?mtr=1").parseHtml()
val scripts = doc.select("script")
for (script in scripts) {
val data = script.html()
@@ -135,7 +133,8 @@ abstract class GroupleRepository(
}
override suspend fun getTags(): Set<MangaTag> {
val doc = loaderContext.get("https://$domain/list/genres/sort_name").parseHtml()
val domain = conf.getDomain(defaultDomain)
val doc = loaderContext.httpGet("https://$domain/list/genres/sort_name").parseHtml()
val root = doc.body().getElementById("mangaBox").selectFirst("div.leftContent")
.selectFirst("table.table")
return root.select("a.element-link").map { a ->
@@ -147,6 +146,8 @@ abstract class GroupleRepository(
}.toSet()
}
override fun onCreatePreferences() = setOf(R.string.key_parser_domain)
private fun getSortKey(sortOrder: SortOrder?) =
when (sortOrder ?: sortOrders.minBy { it.ordinal }) {
SortOrder.ALPHABETICAL -> "name"

View File

@@ -1,10 +1,9 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class HenChanRepository(loaderContext: MangaLoaderContext) :
ChanRepository(MangaSource.HENCHAN, loaderContext) {
class HenChanRepository : ChanRepository() {
override val domain: String = "h-chan.me"
override val defaultDomain = "h-chan.me"
override val source = MangaSource.HENCHAN
}

View File

@@ -1,10 +1,9 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class MangaChanRepository(loaderContext: MangaLoaderContext) :
ChanRepository(MangaSource.MANGACHAN, loaderContext) {
class MangaChanRepository : ChanRepository() {
override val domain: String = "manga-chan.me"
override val defaultDomain = "manga-chan.me"
override val source = MangaSource.MANGACHAN
}

View File

@@ -1,10 +1,9 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class MintMangaRepository(loaderContext: MangaLoaderContext) :
GroupleRepository(MangaSource.MINTMANGA, loaderContext) {
class MintMangaRepository : GroupleRepository() {
override val domain: String = "mintmanga.live"
override val source = MangaSource.MINTMANGA
override val defaultDomain: String = "mintmanga.live"
}

View File

@@ -1,11 +1,9 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class ReadmangaRepository(loaderContext: MangaLoaderContext) :
GroupleRepository(MangaSource.READMANGA_RU, loaderContext) {
override val domain = "readmanga.me"
class ReadmangaRepository : GroupleRepository() {
override val defaultDomain = "readmanga.me"
override val source = MangaSource.READMANGA_RU
}

View File

@@ -1,10 +1,9 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class SelfMangaRepository(loaderContext: MangaLoaderContext) :
GroupleRepository(MangaSource.SELFMANGA, loaderContext) {
class SelfMangaRepository : GroupleRepository() {
override val domain: String = "selfmanga.ru"
override val defaultDomain = "selfmanga.ru"
override val source = MangaSource.SELFMANGA
}

View File

@@ -1,10 +1,9 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class YaoiChanRepository(loaderContext: MangaLoaderContext) :
ChanRepository(MangaSource.YAOICHAN, loaderContext) {
class YaoiChanRepository : ChanRepository() {
override val domain: String = "yaoi-chan.me"
override val source = MangaSource.YAOICHAN
override val defaultDomain = "yaoi-chan.me"
}

View File

@@ -0,0 +1,15 @@
package org.koitharu.kotatsu.core.prefs
import android.content.Context
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource
class SourceConfig(context: Context, source: MangaSource) {
private val prefs = context.getSharedPreferences(source.name, Context.MODE_PRIVATE)
private val keyDomain = context.getString(R.string.key_parser_domain)
fun getDomain(defaultValue: String) = prefs.getString(keyDomain, defaultValue) ?: defaultValue
}

View File

@@ -5,14 +5,17 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.koin.core.KoinComponent
import org.koin.core.get
import org.koin.core.inject
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.prefs.SourceConfig
import org.koitharu.kotatsu.utils.ext.await
class MangaLoaderContext : KoinComponent {
private val okHttp by inject<OkHttpClient>()
suspend fun get(url: String, block: (Request.Builder.() -> Unit)? = null): Response {
suspend fun httpGet(url: String, block: (Request.Builder.() -> Unit)? = null): Response {
val request = Request.Builder()
.get()
.url(url)
@@ -22,7 +25,7 @@ class MangaLoaderContext : KoinComponent {
return okHttp.newCall(request.build()).await()
}
suspend fun post(
suspend fun httpPost(
url: String,
form: Map<String, String>,
block: (Request.Builder.() -> Unit)? = null
@@ -39,4 +42,6 @@ class MangaLoaderContext : KoinComponent {
}
return okHttp.newCall(request.build()).await()
}
fun getSettings(source: MangaSource) = SourceConfig(get(), source)
}

View File

@@ -2,7 +2,6 @@ package org.koitharu.kotatsu.domain
import org.koin.core.KoinComponent
import org.koin.core.get
import org.koin.core.inject
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
@@ -10,8 +9,6 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
object MangaProviderFactory : KoinComponent {
private val loaderContext by inject<MangaLoaderContext>()
val sources: List<MangaSource>
get() {
val list = MangaSource.values().toList() - MangaSource.LOCAL
@@ -22,10 +19,9 @@ object MangaProviderFactory : KoinComponent {
}
}
fun createLocal() = LocalMangaRepository(loaderContext)
fun createLocal() = LocalMangaRepository()
fun create(source: MangaSource): MangaRepository {
val constructor = source.cls.getConstructor(MangaLoaderContext::class.java)
return constructor.newInstance(loaderContext)
return source.cls.newInstance()
}
}

View File

@@ -30,6 +30,7 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
imageView_cover.load(manga.largeCoverUrl ?: manga.coverUrl) {
fallback(R.drawable.ic_placeholder)
crossfade(true)
lifecycle(this@MangaDetailsFragment)
}
textView_title.text = manga.title
textView_subtitle.textAndVisible = manga.altTitle

View File

@@ -5,7 +5,6 @@ import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatDelegate
import androidx.collection.arrayMapOf
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.SeekBarPreference
import org.koitharu.kotatsu.R
@@ -21,8 +20,6 @@ class AppearanceSettingsFragment : BasePreferenceFragment(R.string.appearance),
findPreference<Preference>(R.string.key_list_mode)?.summary =
listModes[settings.listMode]?.let(::getString)
findPreference<ListPreference>(R.string.key_theme)?.summaryProvider =
ListPreference.SimpleSummaryProvider.getInstance()
findPreference<SeekBarPreference>(R.string.key_grid_size)?.run {
summary = "%d%%".format(value)
setOnPreferenceChangeListener { preference, newValue ->

View File

@@ -1,18 +1,25 @@
package org.koitharu.kotatsu.ui.settings
import android.os.Bundle
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceFragmentCompat
import org.koin.core.KoinComponent
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.ui.settings.utils.EditTextSummaryProvider
import org.koitharu.kotatsu.utils.ext.withArgs
class SourceSettingsFragment : PreferenceFragmentCompat(), KoinComponent {
private lateinit var source: MangaSource
private val source by lazy(LazyThreadSafetyMode.NONE) {
requireArguments().getParcelable<MangaSource>(EXTRA_SOURCE)!!
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
source = requireArguments().getParcelable(EXTRA_SOURCE)!!
preferenceManager.sharedPreferencesName = source.name
}
override fun onResume() {
@@ -21,7 +28,15 @@ class SourceSettingsFragment : PreferenceFragmentCompat(), KoinComponent {
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
val repo = MangaProviderFactory.create(source) as? RemoteMangaRepository ?: return
val keys = repo.onCreatePreferences().map(::getString)
addPreferencesFromResource(R.xml.pref_source)
for (i in 0 until preferenceScreen.preferenceCount) {
val pref = preferenceScreen.getPreference(i)
pref.isVisible = pref.key in keys
}
findPreference<EditTextPreference>(getString(R.string.key_parser_domain))?.summaryProvider =
EditTextSummaryProvider(R.string._default)
}
companion object {

View File

@@ -0,0 +1,17 @@
package org.koitharu.kotatsu.ui.settings.utils
import androidx.annotation.StringRes
import androidx.preference.EditTextPreference
import androidx.preference.Preference
class EditTextSummaryProvider(@StringRes private val emptySummaryId: Int) :
Preference.SummaryProvider<EditTextPreference> {
override fun provideSummary(preference: EditTextPreference): CharSequence {
return if (preference.text.isNullOrEmpty()) {
preference.context.getString(emptySummaryId)
} else {
preference.text
}
}
}