Refactor: Provide manga parsers via DI

This commit is contained in:
Koitharu
2020-10-20 21:45:15 +03:00
parent 6f3ae19345
commit a5fba83510
24 changed files with 95 additions and 104 deletions

View File

@@ -12,6 +12,7 @@ import org.koitharu.kotatsu.core.github.githubModule
import org.koitharu.kotatsu.core.local.PagesCache
import org.koitharu.kotatsu.core.network.networkModule
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
import org.koitharu.kotatsu.core.parser.parserModule
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.domain.MangaDataRepository
import org.koitharu.kotatsu.domain.MangaLoaderContext
@@ -59,11 +60,12 @@ class KotatsuApp : Application() {
networkModule,
databaseModule,
githubModule,
parserModule,
uiModule,
module {
single { FavouritesRepository(get()) }
single { HistoryRepository(get()) }
single { TrackingRepository(get()) }
single { TrackingRepository(get(), get()) }
single { MangaDataRepository(get()) }
single { MangaSearchRepository() }
single { MangaLoaderContext() }

View File

@@ -2,6 +2,8 @@ package org.koitharu.kotatsu.core.model
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import org.koin.core.context.GlobalContext
import org.koin.core.error.NoBeanDefFoundException
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.site.*
@@ -24,6 +26,10 @@ enum class MangaSource(
MANGATOWN("MangaTown", "en", MangaTownRepository::class.java),
MANGALIB("MangaLib", "ru", MangaLibRepository::class.java),
NUDEMOON("Nude-Moon", "ru", NudeMoonRepository::class.java),
MANGAREAD("MangaRead", "en", MangareadRepository::class.java),
MANGAREAD("MangaRead", "en", MangareadRepository::class.java);
// HENTAILIB("HentaiLib", "ru", HentaiLibRepository::class.java)
@get:Throws(NoBeanDefFoundException::class)
val repository: MangaRepository
get() = GlobalContext.get().get(cls.kotlin)
}

View File

@@ -7,8 +7,6 @@ import android.webkit.MimeTypeMap
import androidx.collection.ArraySet
import androidx.core.net.toFile
import androidx.core.net.toUri
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koitharu.kotatsu.core.local.CbzFilter
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.domain.local.MangaIndex
@@ -23,9 +21,7 @@ import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
class LocalMangaRepository : MangaRepository, KoinComponent {
private val context by inject<Context>()
class LocalMangaRepository(private val context: Context) : MangaRepository {
override suspend fun getList(
offset: Int,

View File

@@ -2,8 +2,21 @@ package org.koitharu.kotatsu.core.parser
import org.koin.dsl.bind
import org.koin.dsl.module
import org.koitharu.kotatsu.core.parser.site.*
val parserModule
get() = module {
single { LocalMangaRepository() } bind MangaRepository::class
single { LocalMangaRepository(get()) } bind MangaRepository::class
factory { ReadmangaRepository(get()) } bind MangaRepository::class
factory { MintMangaRepository(get()) } bind MangaRepository::class
factory { SelfMangaRepository(get()) } bind MangaRepository::class
factory { MangaChanRepository(get()) } bind MangaRepository::class
factory { DesuMeRepository(get()) } bind MangaRepository::class
factory { HenChanRepository(get()) } bind MangaRepository::class
factory { YaoiChanRepository(get()) } bind MangaRepository::class
factory { MangaTownRepository(get()) } bind MangaRepository::class
factory { MangaLibRepository(get()) } bind MangaRepository::class
factory { NudeMoonRepository(get()) } bind MangaRepository::class
factory { MangareadRepository(get()) } bind MangaRepository::class
}

View File

@@ -2,20 +2,11 @@ package org.koitharu.kotatsu.domain
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.core.component.inject
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import java.lang.ref.WeakReference
import java.util.*
object MangaProviderFactory : KoinComponent {
private val loaderContext by inject<MangaLoaderContext>()
private val cache =
EnumMap<MangaSource, WeakReference<MangaRepository>>(MangaSource::class.java)
fun getSources(includeHidden: Boolean): List<MangaSource> {
val settings = get<AppSettings>()
val list = MangaSource.values().toList() - MangaSource.LOCAL
@@ -33,39 +24,4 @@ object MangaProviderFactory : KoinComponent {
}
}
}
@Deprecated("Use DI")
fun createLocal(): LocalMangaRepository {
var instance = cache[MangaSource.LOCAL]?.get()
if (instance == null) {
synchronized(cache) {
instance = cache[MangaSource.LOCAL]?.get()
if (instance == null) {
instance = LocalMangaRepository()
cache[MangaSource.LOCAL] = WeakReference<MangaRepository>(instance)
}
}
}
return instance as LocalMangaRepository
}
@Throws(Throwable::class)
fun create(source: MangaSource): MangaRepository {
var instance = cache[source]?.get()
if (instance == null) {
synchronized(cache) {
instance = cache[source]?.get()
if (instance == null) {
instance = try {
source.cls.getDeclaredConstructor(MangaLoaderContext::class.java)
.newInstance(loaderContext)
} catch (e: NoSuchMethodException) {
source.cls.newInstance()
}
cache[source] = WeakReference(instance!!)
}
}
}
return instance!!
}
}

View File

@@ -18,7 +18,7 @@ class MangaSearchRepository {
for (source in sources) {
val list = lists.getOrPut(source) {
try {
MangaProviderFactory.create(source).getList(0, query, SortOrder.POPULARITY)
source.repository.getList(0, query, SortOrder.POPULARITY)
} catch (e: Throwable) {
e.printStackTrace()
emptyList<Manga>()

View File

@@ -27,7 +27,7 @@ object MangaUtils : KoinComponent {
suspend fun determineReaderMode(pages: List<MangaPage>): ReaderMode? {
try {
val page = pages.medianOrNull() ?: return null
val url = MangaProviderFactory.create(page.source).getPageFullUrl(page)
val url = page.source.repository.getPageFullUrl(page)
val uri = Uri.parse(url)
val size = if (uri.scheme == "cbz") {
val zip = ZipFile(uri.schemeSpecificPart)

View File

@@ -5,10 +5,13 @@ import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.TrackEntity
import org.koitharu.kotatsu.core.db.entity.TrackLogEntity
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
import java.util.*
class TrackingRepository(private val db: MangaDatabase) {
class TrackingRepository(
private val db: MangaDatabase,
private val localMangaRepository: LocalMangaRepository
) {
suspend fun getNewChaptersCount(mangaId: Long): Int {
val entity = db.tracksDao.find(mangaId) ?: return 0
@@ -28,7 +31,7 @@ class TrackingRepository(private val db: MangaDatabase) {
.distinctBy { it.id }
.mapNotNull { me ->
val manga = if (me.source == MangaSource.LOCAL) {
MangaProviderFactory.createLocal().getRemoteManga(me)
localMangaRepository.getRemoteManga(me) // FIXME duplicating
} else {
me
} ?: return@mapNotNull null

View File

@@ -17,6 +17,7 @@ abstract class BaseFragment(
fun stringArg(name: String) = StringArgumentDelegate(name)
@Deprecated("Use extension", replaceWith = ReplaceWith("parcelableArgument(name)"))
fun <T : Parcelable> arg(name: String) = ParcelableArgumentDelegate<T>(name)
open fun getTitle(): CharSequence? = null

View File

@@ -7,14 +7,13 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import moxy.InjectViewState
import moxy.presenterScope
import org.koin.core.component.get
import org.koin.core.component.inject
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
import org.koitharu.kotatsu.domain.MangaDataRepository
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.domain.MangaSearchRepository
import org.koitharu.kotatsu.domain.favourites.FavouritesRepository
import org.koitharu.kotatsu.domain.favourites.OnFavouritesChangeListener
@@ -75,8 +74,8 @@ class MangaDetailsPresenter private constructor(private val key: Int) :
presenterScope.launch {
try {
viewState.onLoadingStateChanged(true)
val data = withContext(Dispatchers.IO) {
MangaProviderFactory.create(manga.source).getDetails(manga)
val data = withContext(Dispatchers.Default) {
manga.source.repository.getDetails(manga)
}
viewState.onMangaUpdated(data)
this@MangaDetailsPresenter.manga = data
@@ -98,8 +97,7 @@ class MangaDetailsPresenter private constructor(private val key: Int) :
viewState.onLoadingStateChanged(true)
try {
withContext(Dispatchers.IO) {
val repository =
MangaProviderFactory.create(MangaSource.LOCAL) as LocalMangaRepository
val repository = get<LocalMangaRepository>()
val original = repository.getRemoteManga(manga)
repository.delete(manga) || throw IOException("Unable to delete file")
safe {

View File

@@ -14,13 +14,14 @@ import kotlinx.coroutines.sync.Mutex
import okhttp3.OkHttpClient
import okhttp3.Request
import okio.IOException
import org.koin.android.ext.android.get
import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.local.PagesCache
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.domain.local.MangaZip
import org.koitharu.kotatsu.ui.base.BaseService
import org.koitharu.kotatsu.ui.base.dialog.CheckBoxAlertDialog
@@ -87,7 +88,7 @@ class DownloadService : BaseService() {
checkNotNull(destination) { getString(R.string.cannot_find_available_storage) }
var output: MangaZip? = null
try {
val repo = MangaProviderFactory.create(manga.source)
val repo = manga.source.repository
val cover = safe {
imageLoader.execute(
ImageRequest.Builder(this@DownloadService)
@@ -146,7 +147,7 @@ class DownloadService : BaseService() {
if (!output.compress()) {
throw RuntimeException("Cannot create target file")
}
val result = MangaProviderFactory.createLocal().getFromFile(output.file)
val result = get<LocalMangaRepository>().getFromFile(output.file)
notification.setDone(result)
notification.dismiss()
notification.update(manga.id.toInt().absoluteValue)

View File

@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.ui.list
import moxy.InjectViewState
import org.koin.core.component.inject
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.domain.history.HistoryRepository
import org.koitharu.kotatsu.ui.base.BasePresenter
import org.koitharu.kotatsu.ui.reader.ReaderState
@@ -19,7 +18,7 @@ class MainPresenter : BasePresenter<MainView>() {
?: throw EmptyHistoryException()
val history = historyRepository.getOne(manga) ?: throw EmptyHistoryException()
val state = ReaderState(
MangaProviderFactory.create(manga.source).getDetails(manga),
manga.source.repository.getDetails(manga),
history.chapterId, history.page, history.scroll
)
viewState.onOpenReader(state)

View File

@@ -9,16 +9,19 @@ import org.koitharu.kotatsu.core.model.MangaFilter
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.ui.list.MangaListFragment
import org.koitharu.kotatsu.ui.search.SearchActivity
import org.koitharu.kotatsu.utils.ext.parcelableArgument
import org.koitharu.kotatsu.utils.ext.withArgs
class RemoteListFragment : MangaListFragment<Unit>() {
private val presenter by moxyPresenter(factory = ::RemoteListPresenter)
private val presenter by moxyPresenter {
RemoteListPresenter(source)
}
private val source by arg<MangaSource>(ARG_SOURCE)
private val source by parcelableArgument<MangaSource>(ARG_SOURCE)
override fun onRequestMoreItems(offset: Int) {
presenter.loadList(source, offset)
presenter.loadList(offset)
}
override fun getTitle(): CharSequence? {
@@ -26,7 +29,7 @@ class RemoteListFragment : MangaListFragment<Unit>() {
}
override fun onFilterChanged(filter: MangaFilter) {
presenter.applyFilter(source, filter)
presenter.applyFilter(filter)
super.onFilterChanged(filter)
}

View File

@@ -9,22 +9,29 @@ import moxy.presenterScope
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.MangaFilter
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.ui.base.BasePresenter
import org.koitharu.kotatsu.ui.list.MangaListView
@InjectViewState
class RemoteListPresenter : BasePresenter<MangaListView<Unit>>() {
class RemoteListPresenter(source: MangaSource) : BasePresenter<MangaListView<Unit>>() {
private val repository by lazy(LazyThreadSafetyMode.PUBLICATION) {
source.repository
}
private var isFilterInitialized = false
private var filter: MangaFilter? = null
fun loadList(source: MangaSource, offset: Int) {
override fun onFirstViewAttach() {
super.onFirstViewAttach()
loadFilter()
}
fun loadList(offset: Int) {
presenterScope.launch {
viewState.onLoadingStateChanged(true)
try {
val list = withContext(Dispatchers.Default) {
MangaProviderFactory.create(source).getList(
repository.getList(
offset = offset,
sortOrder = filter?.sortOrder,
tag = filter?.tag
@@ -50,23 +57,23 @@ class RemoteListPresenter : BasePresenter<MangaListView<Unit>>() {
}
}
if (!isFilterInitialized) {
loadFilter(source)
loadFilter()
}
}
fun applyFilter(source: MangaSource, filter: MangaFilter) {
fun applyFilter(filter: MangaFilter) {
this.filter = filter
viewState.onListChanged(emptyList())
loadList(source, 0)
loadList(0)
}
private fun loadFilter(source: MangaSource) {
private fun loadFilter() {
isFilterInitialized = true
presenterScope.launch {
launchJob {
try {
val (sorts, tags) = withContext(Dispatchers.Default) {
val repo = MangaProviderFactory.create(source)
repo.sortOrders.sortedBy { it.ordinal } to repo.getTags().sortedBy { it.title }
repository.sortOrders.sortedBy { it.ordinal } to repository.getTags()
.sortedBy { it.title }
}
viewState.onInitFilter(sorts, tags, filter)
} catch (e: Exception) {

View File

@@ -15,7 +15,6 @@ import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.domain.MangaDataRepository
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.domain.MangaUtils
import org.koitharu.kotatsu.domain.history.HistoryRepository
import org.koitharu.kotatsu.ui.base.BasePresenter
@@ -35,7 +34,7 @@ class ReaderPresenter : BasePresenter<ReaderView>() {
viewState.onLoadingStateChanged(isLoading = true)
try {
val mode = withContext(Dispatchers.IO) {
val repo = MangaProviderFactory.create(manga.source)
val repo = manga.source.repository
val chapter =
(manga.chapters ?: throw RuntimeException("Chapters is null")).random()
var mode = dataRepository.getReaderMode(manga.id)
@@ -77,7 +76,7 @@ class ReaderPresenter : BasePresenter<ReaderView>() {
fun savePage(resolver: ContentResolver, page: MangaPage) {
presenterScope.launch(Dispatchers.IO) {
try {
val repo = MangaProviderFactory.create(page.source)
val repo = page.source.repository
val url = repo.getPageFullUrl(page)
val request = Request.Builder()
.url(url)

View File

@@ -11,7 +11,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.ui.base.BaseFragment
import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.ui.reader.ReaderListener
@@ -124,7 +123,7 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
val pages = withContext(Dispatchers.IO) {
val chapter = manga.chapters?.find { it.id == chapterId }
?: throw RuntimeException("Chapter $chapterId not found")
val repo = MangaProviderFactory.create(manga.source)
val repo = manga.source.repository
repo.getPages(chapter)
}
callback(pages)

View File

@@ -5,7 +5,6 @@ import androidx.core.net.toUri
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import kotlinx.coroutines.*
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.utils.ext.launchAfter
import org.koitharu.kotatsu.utils.ext.launchInstead
@@ -72,7 +71,7 @@ class PageHolderDelegate(
callback.onLoadingStarted()
try {
val file = withContext(Dispatchers.IO) {
val pageUrl = MangaProviderFactory.create(data.source).getPageFullUrl(data)
val pageUrl = data.source.repository.getPageFullUrl(data)
check(pageUrl.isNotEmpty()) { "Cannot obtain full image url" }
loader.loadFile(pageUrl, force)
}

View File

@@ -12,7 +12,6 @@ import org.koin.core.component.inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.local.PagesCache
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.ui.base.list.BaseViewHolder
import org.koitharu.kotatsu.utils.ext.IgnoreErrors
@@ -37,7 +36,7 @@ class PageThumbnailHolder(parent: ViewGroup, private val scope: CoroutineScope)
job?.cancel()
job = scope.launch(Dispatchers.IO + IgnoreErrors) {
val url = data.preview ?: data.url.let {
val pageUrl = MangaProviderFactory.create(data.source).getPageFullUrl(data)
val pageUrl = data.source.repository.getPageFullUrl(data)
extra[pageUrl]?.toUri()?.toString() ?: pageUrl
}
val drawable = coil.execute(

View File

@@ -4,7 +4,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import moxy.InjectViewState
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.ui.base.BasePresenter
import org.koitharu.kotatsu.ui.list.MangaListView
@@ -14,8 +13,7 @@ class SearchPresenter : BasePresenter<MangaListView<Unit>>() {
fun loadList(source: MangaSource, query: String, offset: Int) {
launchLoadingJob {
val list = withContext(Dispatchers.Default) {
MangaProviderFactory.create(source)
.getList(offset, query = query)
source.repository.getList(offset, query = query)
}
if (offset == 0) {
viewState.onListChanged(list)

View File

@@ -6,7 +6,6 @@ import androidx.preference.PreferenceFragmentCompat
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
@@ -23,7 +22,7 @@ class SourceSettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.sharedPreferencesName = source.name
val repo = MangaProviderFactory.create(source) as? RemoteMangaRepository ?: return
val repo = source.repository as? RemoteMangaRepository ?: return
val keys = repo.onCreatePreferences().map(::getString)
addPreferencesFromResource(R.xml.pref_source)
for (i in 0 until preferenceScreen.preferenceCount) {

View File

@@ -19,7 +19,6 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.domain.tracking.TrackingRepository
import org.koitharu.kotatsu.ui.details.MangaDetailsActivity
import org.koitharu.kotatsu.utils.ext.safe
@@ -53,8 +52,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
var success = 0
for (track in tracks) {
val details = safe {
MangaProviderFactory.create(track.manga.source)
.getDetails(track.manga)
track.manga.source.repository.getDetails(track.manga)
}
val chapters = details?.chapters ?: continue
when {

View File

@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.utils.ext
import android.os.Bundle
import android.os.Parcelable
import androidx.fragment.app.Fragment
import androidx.lifecycle.coroutineScope
@@ -12,4 +13,11 @@ inline fun <T : Fragment> T.withArgs(size: Int, block: Bundle.() -> Unit): T {
}
val Fragment.viewLifecycleScope
get() = viewLifecycleOwner.lifecycle.coroutineScope
get() = viewLifecycleOwner.lifecycle.coroutineScope
@Suppress("NOTHING_TO_INLINE")
inline fun <T : Parcelable> Fragment.parcelableArgument(name: String) =
lazy<T>(LazyThreadSafetyMode.NONE) {
requireArguments().getParcelable(name)
?: error("No argument $name passed in ${javaClass.simpleName}")
}