Do quite a bit of code cleanup

This commit is contained in:
Jobobby04 2021-01-17 22:17:15 -05:00
parent 8db57aef6c
commit 3b364c91f1
16 changed files with 95 additions and 480 deletions

View File

@ -1,374 +0,0 @@
package eu.kanade.tachiyomi.source.online
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.newCallWithProgress
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.lang.runAsObservable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import kotlin.jvm.Throws
/**
* A simple implementation for sources from a website, but for Coroutines.
*/
abstract class SuspendHttpSource : HttpSource() {
/**
* Returns an observable containing a page with a list of manga. Normally it's not needed to
* override this method.
*
* @param page the page number to retrieve.
*/
final override fun fetchPopularManga(page: Int): Observable<MangasPage> {
return runAsObservable({ fetchPopularMangaSuspended(page) })
}
open suspend fun fetchPopularMangaSuspended(page: Int): MangasPage {
return withContext(Dispatchers.IO) {
val response = client.newCall(popularMangaRequestSuspended(page)).await()
popularMangaParseSuspended(response)
}
}
/**
* Returns the request for the popular manga given the page.
*
* @param page the page number to retrieve.
*/
final override fun popularMangaRequest(page: Int): Request {
return runBlocking { popularMangaRequestSuspended(page) }
}
protected abstract suspend fun popularMangaRequestSuspended(page: Int): Request
/**
* Parses the response from the site and returns a [MangasPage] object.
*
* @param response the response from the site.
*/
final override fun popularMangaParse(response: Response): MangasPage {
return runBlocking { popularMangaParseSuspended(response) }
}
protected abstract suspend fun popularMangaParseSuspended(response: Response): MangasPage
/**
* Returns an observable containing a page with a list of manga. Normally it's not needed to
* override this method.
*
* @param page the page number to retrieve.
* @param query the search query.
* @param filters the list of filters to apply.
*/
final override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return runAsObservable({ fetchSearchMangaSuspended(page, query, filters) })
}
open suspend fun fetchSearchMangaSuspended(page: Int, query: String, filters: FilterList): MangasPage {
return withContext(Dispatchers.IO) {
val response = client.newCall(searchMangaRequestSuspended(page, query, filters)).await()
searchMangaParseSuspended(response)
}
}
/**
* Returns the request for the search manga given the page.
*
* @param page the page number to retrieve.
* @param query the search query.
* @param filters the list of filters to apply.
*/
final override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return runBlocking { searchMangaRequestSuspended(page, query, filters) }
}
protected abstract suspend fun searchMangaRequestSuspended(page: Int, query: String, filters: FilterList): Request
/**
* Parses the response from the site and returns a [MangasPage] object.
*
* @param response the response from the site.
*/
final override fun searchMangaParse(response: Response): MangasPage {
return runBlocking { searchMangaParseSuspended(response) }
}
protected abstract suspend fun searchMangaParseSuspended(response: Response): MangasPage
/**
* Returns an observable containing a page with a list of latest manga updates.
*
* @param page the page number to retrieve.
*/
final override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
return runAsObservable({ fetchLatestUpdatesSuspended(page) })
}
open suspend fun fetchLatestUpdatesSuspended(page: Int): MangasPage {
return withContext(Dispatchers.IO) {
val response = client.newCall(latestUpdatesRequestSuspended(page)).await()
latestUpdatesParseSuspended(response)
}
}
/**
* Returns the request for latest manga given the page.
*
* @param page the page number to retrieve.
*/
final override fun latestUpdatesRequest(page: Int): Request {
return runBlocking { latestUpdatesRequestSuspended(page) }
}
protected abstract suspend fun latestUpdatesRequestSuspended(page: Int): Request
/**
* Parses the response from the site and returns a [MangasPage] object.
*
* @param response the response from the site.
*/
final override fun latestUpdatesParse(response: Response): MangasPage {
return runBlocking { latestUpdatesParseSuspended(response) }
}
protected abstract suspend fun latestUpdatesParseSuspended(response: Response): MangasPage
/**
* Returns an observable with the updated details for a manga. Normally it's not needed to
* override this method.
*
* @param manga the manga to be updated.
*/
final override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return runAsObservable({ fetchMangaDetailsSuspended(manga) })
}
open suspend fun fetchMangaDetailsSuspended(manga: SManga): SManga {
return withContext(Dispatchers.IO) {
val response = client.newCall(mangaDetailsRequestSuspended(manga)).await()
mangaDetailsParseSuspended(response).apply { initialized = true }
}
}
/**
* Returns the request for the details of a manga. Override only if it's needed to change the
* url, send different headers or request method like POST.
*
* @param manga the manga to be updated.
*/
final override fun mangaDetailsRequest(manga: SManga): Request {
return runBlocking { mangaDetailsRequestSuspended(manga) }
}
open suspend fun mangaDetailsRequestSuspended(manga: SManga): Request {
return GET(baseUrl + manga.url, headers)
}
/**
* Parses the response from the site and returns the details of a manga.
*
* @param response the response from the site.
*/
final override fun mangaDetailsParse(response: Response): SManga {
return runBlocking { mangaDetailsParseSuspended(response) }
}
protected abstract suspend fun mangaDetailsParseSuspended(response: Response): SManga
/**
* Returns an observable with the updated chapter list for a manga. Normally it's not needed to
* override this method. If a manga is licensed an empty chapter list observable is returned
*
* @param manga the manga to look for chapters.
*/
final override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return try {
runAsObservable({ fetchChapterListSuspended(manga) })
} catch (e: LicencedException) {
Observable.error(Exception("Licensed - No chapters to show"))
}
}
@Throws(LicencedException::class)
open suspend fun fetchChapterListSuspended(manga: SManga): List<SChapter> {
return withContext(Dispatchers.IO) {
if (manga.status != SManga.LICENSED) {
val response = client.newCall(chapterListRequestSuspended(manga)).await()
chapterListParseSuspended(response)
} else {
throw LicencedException("Licensed - No chapters to show")
}
}
}
/**
* Returns the request for updating the chapter list. Override only if it's needed to override
* the url, send different headers or request method like POST.
*
* @param manga the manga to look for chapters.
*/
final override fun chapterListRequest(manga: SManga): Request {
return runBlocking { chapterListRequestSuspended(manga) }
}
protected open suspend fun chapterListRequestSuspended(manga: SManga): Request {
return GET(baseUrl + manga.url, headers)
}
/**
* Parses the response from the site and returns a list of chapters.
*
* @param response the response from the site.
*/
final override fun chapterListParse(response: Response): List<SChapter> {
return runBlocking { chapterListParseSuspended(response) }
}
protected abstract suspend fun chapterListParseSuspended(response: Response): List<SChapter>
/**
* Returns an observable with the page list for a chapter.
*
* @param chapter the chapter whose page list has to be fetched.
*/
final override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
return runAsObservable({ fetchPageListSuspended(chapter) })
}
open suspend fun fetchPageListSuspended(chapter: SChapter): List<Page> {
return withContext(Dispatchers.IO) {
val response = client.newCall(pageListRequestSuspended(chapter)).await()
pageListParseSuspended(response)
}
}
/**
* Returns the request for getting the page list. Override only if it's needed to override the
* url, send different headers or request method like POST.
*
* @param chapter the chapter whose page list has to be fetched.
*/
final override fun pageListRequest(chapter: SChapter): Request {
return runBlocking { pageListRequestSuspended(chapter) }
}
protected open suspend fun pageListRequestSuspended(chapter: SChapter): Request {
return GET(baseUrl + chapter.url, headers)
}
/**
* Parses the response from the site and returns a list of pages.
*
* @param response the response from the site.
*/
final override fun pageListParse(response: Response): List<Page> {
return runBlocking { pageListParseSuspended(response) }
}
protected abstract suspend fun pageListParseSuspended(response: Response): List<Page>
/**
* Returns an observable with the page containing the source url of the image. If there's any
* error, it will return null instead of throwing an exception.
*
* @param page the page whose source image has to be fetched.
*/
final override fun fetchImageUrl(page: Page): Observable<String> {
return runAsObservable({ fetchImageUrlSuspended(page) })
}
open suspend fun fetchImageUrlSuspended(page: Page): String {
return withContext(Dispatchers.IO) {
val response = client.newCall(imageUrlRequestSuspended(page)).await()
imageUrlParseSuspended(response)
}
}
/**
* Returns the request for getting the url to the source image. Override only if it's needed to
* override the url, send different headers or request method like POST.
*
* @param page the chapter whose page list has to be fetched
*/
final override fun imageUrlRequest(page: Page): Request {
return runBlocking { imageUrlRequestSuspended(page) }
}
protected open suspend fun imageUrlRequestSuspended(page: Page): Request {
return GET(page.url, headers)
}
/**
* Parses the response from the site and returns the absolute url to the source image.
*
* @param response the response from the site.
*/
final override fun imageUrlParse(response: Response): String {
return runBlocking { imageUrlParseSuspended(response) }
}
protected abstract suspend fun imageUrlParseSuspended(response: Response): String
/**
* Returns an observable with the response of the source image.
*
* @param page the page whose source image has to be downloaded.
*/
final override fun fetchImage(page: Page): Observable<Response> {
return runAsObservable({ fetchImageSuspended(page) })
}
open suspend fun fetchImageSuspended(page: Page): Response {
return withContext(Dispatchers.IO) {
client.newCallWithProgress(imageRequestSuspended(page), page).await()
}
}
/**
* Returns the request for getting the source image. Override only if it's needed to override
* the url, send different headers or request method like POST.
*
* @param page the chapter whose page list has to be fetched
*/
final override fun imageRequest(page: Page): Request {
return runBlocking { imageRequestSuspended(page) }
}
protected open suspend fun imageRequestSuspended(page: Page): Request {
return GET(page.imageUrl!!, headers)
}
/**
* Called before inserting a new chapter into database. Use it if you need to override chapter
* fields, like the title or the chapter number. Do not change anything to [manga].
*
* @param chapter the chapter to be added.
* @param manga the manga of the chapter.
*/
final override fun prepareNewChapter(chapter: SChapter, manga: SManga) {
runBlocking { prepareNewChapterSuspended(chapter, manga) }
}
open suspend fun prepareNewChapterSuspended(chapter: SChapter, manga: SManga) {
}
/**
* Returns the list of filters for the source.
*/
override fun getFilterList() = runBlocking { getFilterListSuspended() }
open suspend fun getFilterListSuspended() = FilterList()
companion object {
data class LicencedException(override val message: String?) : Exception()
}
}

View File

@ -30,6 +30,7 @@ import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.lang.runAsObservable import eu.kanade.tachiyomi.util.lang.runAsObservable
import eu.kanade.tachiyomi.util.lang.withIOContext
import exh.GalleryAddEvent import exh.GalleryAddEvent
import exh.GalleryAdder import exh.GalleryAdder
import exh.md.MangaDexFabHeaderAdapter import exh.md.MangaDexFabHeaderAdapter
@ -47,12 +48,10 @@ import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.MangaDexDescriptionAdapter import exh.ui.metadata.adapters.MangaDexDescriptionAdapter
import exh.util.urlImportFetchSearchManga import exh.util.urlImportFetchSearchManga
import exh.widget.preference.MangadexLoginDialog import exh.widget.preference.MangadexLoginDialog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.CacheControl import okhttp3.CacheControl
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
@ -184,7 +183,7 @@ class MangaDex(delegate: HttpSource, val context: Context) :
} }
override fun isLogged(): Boolean { override fun isLogged(): Boolean {
val httpUrl = MdUtil.baseUrl.toHttpUrlOrNull()!! val httpUrl = MdUtil.baseUrl.toHttpUrl()
return trackManager.mdList.isLogged && network.cookieManager.get(httpUrl).any { it.name == REMEMBER_ME } return trackManager.mdList.isLogged && network.cookieManager.get(httpUrl).any { it.name == REMEMBER_ME }
} }
@ -193,12 +192,13 @@ class MangaDex(delegate: HttpSource, val context: Context) :
password: String, password: String,
twoFactorCode: String twoFactorCode: String
): Boolean { ): Boolean {
return withContext(Dispatchers.IO) { return withIOContext {
val formBody = FormBody.Builder() val formBody = FormBody.Builder().apply {
.add("login_username", username) add("login_username", username)
.add("login_password", password) add("login_password", password)
.add("no_js", "1") add("no_js", "1")
.add("remember_me", "1") add("remember_me", "1")
}
twoFactorCode.let { twoFactorCode.let {
formBody.add("two_factor", it) formBody.add("two_factor", it)
@ -212,11 +212,11 @@ class MangaDex(delegate: HttpSource, val context: Context) :
) )
).await() ).await()
withContext(Dispatchers.IO) { response.body!!.string() }.let { withIOContext { response.body?.string() }.let { result ->
if (it.isEmpty()) { if (result != null && result.isEmpty()) {
true true
} else { } else {
val error = HtmlCompat.fromHtml(it, HtmlCompat.FROM_HTML_MODE_COMPACT).toString() val error = result?.let { HtmlCompat.fromHtml(it, HtmlCompat.FROM_HTML_MODE_COMPACT).toString() }
throw Exception(error) throw Exception(error)
} }
} }
@ -224,23 +224,23 @@ class MangaDex(delegate: HttpSource, val context: Context) :
} }
override suspend fun logout(): Boolean { override suspend fun logout(): Boolean {
return withContext(Dispatchers.IO) { return withIOContext {
// https://mangadex.org/ajax/actions.ajax.php?function=logout // https://mangadex.org/ajax/actions.ajax.php?function=logout
val httpUrl = MdUtil.baseUrl.toHttpUrlOrNull()!! val httpUrl = MdUtil.baseUrl.toHttpUrl()
val listOfDexCookies = network.cookieManager.get(httpUrl) val listOfDexCookies = network.cookieManager.get(httpUrl)
val cookie = listOfDexCookies.find { it.name == REMEMBER_ME } val cookie = listOfDexCookies.find { it.name == REMEMBER_ME }
val token = cookie?.value val token = cookie?.value
if (token.isNullOrEmpty()) { if (token.isNullOrEmpty()) {
return@withContext true return@withIOContext true
} }
val result = client.newCall( val result = client.newCall(
POST("${MdUtil.baseUrl}/ajax/actions.ajax.php?function=logout", headers).newBuilder().addHeader(REMEMBER_ME, token).build() POST("${MdUtil.baseUrl}/ajax/actions.ajax.php?function=logout", headers).newBuilder().addHeader(REMEMBER_ME, token).build()
).await() ).await()
val resultStr = withContext(Dispatchers.IO) { result.body?.string() } val resultStr = withIOContext { result.body?.string() }
if (resultStr?.contains("success", true) == true) { if (resultStr?.contains("success", true) == true) {
network.cookieManager.remove(httpUrl) network.cookieManager.remove(httpUrl)
trackManager.mdList.logout() trackManager.mdList.logout()
return@withContext true return@withIOContext true
} }
false false
@ -248,19 +248,19 @@ class MangaDex(delegate: HttpSource, val context: Context) :
} }
override suspend fun fetchAllFollows(forceHd: Boolean): List<Pair<SManga, MangaDexSearchMetadata>> { override suspend fun fetchAllFollows(forceHd: Boolean): List<Pair<SManga, MangaDexSearchMetadata>> {
return withContext(Dispatchers.IO) { FollowsHandler(client, headers, Injekt.get(), useLowQualityThumbnail()).fetchAllFollows(forceHd) } return withIOContext { FollowsHandler(client, headers, Injekt.get(), useLowQualityThumbnail()).fetchAllFollows(forceHd) }
} }
suspend fun updateReadingProgress(track: Track): Boolean { suspend fun updateReadingProgress(track: Track): Boolean {
return withContext(Dispatchers.IO) { FollowsHandler(client, headers, Injekt.get(), useLowQualityThumbnail()).updateReadingProgress(track) } return withIOContext { FollowsHandler(client, headers, Injekt.get(), useLowQualityThumbnail()).updateReadingProgress(track) }
} }
suspend fun updateRating(track: Track): Boolean { suspend fun updateRating(track: Track): Boolean {
return withContext(Dispatchers.IO) { FollowsHandler(client, headers, Injekt.get(), useLowQualityThumbnail()).updateRating(track) } return withIOContext { FollowsHandler(client, headers, Injekt.get(), useLowQualityThumbnail()).updateRating(track) }
} }
override suspend fun fetchTrackingInfo(url: String): Track { override suspend fun fetchTrackingInfo(url: String): Track {
return withContext(Dispatchers.IO) { return withIOContext {
if (!isLogged()) { if (!isLogged()) {
throw Exception("Not Logged in") throw Exception("Not Logged in")
} }
@ -269,7 +269,7 @@ class MangaDex(delegate: HttpSource, val context: Context) :
} }
override suspend fun updateFollowStatus(mangaID: String, followStatus: FollowStatus): Boolean { override suspend fun updateFollowStatus(mangaID: String, followStatus: FollowStatus): Boolean {
return withContext(Dispatchers.IO) { FollowsHandler(client, headers, Injekt.get(), useLowQualityThumbnail()).updateFollowStatus(mangaID, followStatus) } return withIOContext { FollowsHandler(client, headers, Injekt.get(), useLowQualityThumbnail()).updateFollowStatus(mangaID, followStatus) }
} }
override fun getFilterHeader(controller: BaseController<*>): MangaDexFabHeaderAdapter { override fun getFilterHeader(controller: BaseController<*>): MangaDexFabHeaderAdapter {
@ -292,13 +292,11 @@ class MangaDex(delegate: HttpSource, val context: Context) :
}) })
.map { res -> .map { res ->
MangasPage( MangasPage(
(
if (res is GalleryAddEvent.Success) { if (res is GalleryAddEvent.Success) {
listOf(res.manga) listOf(res.manga)
} else { } else {
emptyList() emptyList()
} },
),
false false
) )
} }

View File

@ -15,7 +15,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.toSChapter import eu.kanade.tachiyomi.source.model.toSChapter
import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.source.online.SuspendHttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.awaitSingle import eu.kanade.tachiyomi.util.lang.awaitSingle
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext
@ -25,9 +25,11 @@ import exh.merged.sql.models.MergedMangaReference
import exh.util.executeOnIO import exh.util.executeOnIO
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
import tachiyomi.source.model.ChapterInfo
import tachiyomi.source.model.MangaInfo
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class MergedSource : SuspendHttpSource() { class MergedSource : HttpSource() {
private val db: DatabaseHelper by injectLazy() private val db: DatabaseHelper by injectLazy()
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
private val downloadManager: DownloadManager by injectLazy() private val downloadManager: DownloadManager by injectLazy()
@ -37,40 +39,42 @@ class MergedSource : SuspendHttpSource() {
override val baseUrl = "" override val baseUrl = ""
override suspend fun popularMangaRequestSuspended(page: Int) = throw UnsupportedOperationException() override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException()
override suspend fun popularMangaParseSuspended(response: Response) = throw UnsupportedOperationException() override fun popularMangaParse(response: Response) = throw UnsupportedOperationException()
override suspend fun searchMangaRequestSuspended(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException() override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException()
override suspend fun searchMangaParseSuspended(response: Response) = throw UnsupportedOperationException() override fun searchMangaParse(response: Response) = throw UnsupportedOperationException()
override suspend fun latestUpdatesRequestSuspended(page: Int) = throw UnsupportedOperationException() override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
override suspend fun latestUpdatesParseSuspended(response: Response) = throw UnsupportedOperationException() override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
override suspend fun mangaDetailsParseSuspended(response: Response) = throw UnsupportedOperationException() override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException()
override suspend fun chapterListParseSuspended(response: Response) = throw UnsupportedOperationException() override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
override suspend fun pageListParseSuspended(response: Response) = throw UnsupportedOperationException() override fun pageListParse(response: Response) = throw UnsupportedOperationException()
override suspend fun imageUrlParseSuspended(response: Response) = throw UnsupportedOperationException() override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
override suspend fun fetchChapterListSuspended(manga: SManga) = throw UnsupportedOperationException() override fun fetchChapterList(manga: SManga) = throw UnsupportedOperationException()
override suspend fun fetchImageSuspended(page: Page) = throw UnsupportedOperationException() override suspend fun getChapterList(manga: MangaInfo) = throw UnsupportedOperationException()
override suspend fun fetchImageUrlSuspended(page: Page) = throw UnsupportedOperationException() override fun fetchImage(page: Page) = throw UnsupportedOperationException()
override suspend fun fetchPageListSuspended(chapter: SChapter) = throw UnsupportedOperationException() override fun fetchImageUrl(page: Page) = throw UnsupportedOperationException()
override suspend fun fetchLatestUpdatesSuspended(page: Int) = throw UnsupportedOperationException() override fun fetchPageList(chapter: SChapter) = throw UnsupportedOperationException()
override suspend fun fetchPopularMangaSuspended(page: Int) = throw UnsupportedOperationException() override suspend fun getPageList(chapter: ChapterInfo) = throw UnsupportedOperationException()
override fun fetchLatestUpdates(page: Int) = throw UnsupportedOperationException()
override fun fetchPopularManga(page: Int) = throw UnsupportedOperationException()
override suspend fun fetchMangaDetailsSuspended(manga: SManga): SManga { override suspend fun getMangaDetails(manga: MangaInfo): MangaInfo {
return withIOContext { return withIOContext {
val mergedManga = db.getManga(manga.url, id).executeAsBlocking() ?: throw Exception("merged manga not in db") val mergedManga = db.getManga(manga.key, id).executeAsBlocking() ?: throw Exception("merged manga not in db")
val mangaReferences = mergedManga.id?.let { db.getMergedMangaReferences(it).executeOnIO() } ?: throw Exception("merged manga id is null") val mangaReferences = db.getMergedMangaReferences(mergedManga.id ?: throw Exception("merged manga id is null")).executeOnIO()
if (mangaReferences.isEmpty()) throw IllegalArgumentException("Manga references are empty, info unavailable, merge is likely corrupted") if (mangaReferences.isEmpty()) throw IllegalArgumentException("Manga references are empty, info unavailable, merge is likely corrupted")
if (mangaReferences.size == 1 || run { if (mangaReferences.size == 1 &&
run {
val mangaReference = mangaReferences.firstOrNull() val mangaReference = mangaReferences.firstOrNull()
mangaReference == null || (mangaReference.mangaSourceId == MERGED_SOURCE_ID) mangaReference == null || mangaReference.mangaSourceId == MERGED_SOURCE_ID
} }
) throw IllegalArgumentException("Manga references contain only the merged reference, merge is likely corrupted") ) throw IllegalArgumentException("Manga references contain only the merged reference, merge is likely corrupted")
SManga.create().apply {
val mangaInfoReference = mangaReferences.firstOrNull { it.isInfoManga } ?: mangaReferences.firstOrNull { it.mangaId != it.mergeId } val mangaInfoReference = mangaReferences.firstOrNull { it.isInfoManga } ?: mangaReferences.firstOrNull { it.mangaId != it.mergeId }
val dbManga = mangaInfoReference?.let { db.getManga(it.mangaUrl, it.mangaSourceId).executeOnIO() } val dbManga = mangaInfoReference?.let { db.getManga(it.mangaUrl, it.mangaSourceId).executeOnIO()?.toMangaInfo() }
this.copyFrom(dbManga ?: mergedManga) (dbManga ?: mergedManga.toMangaInfo()).copy(
url = manga.url key = manga.key
} )
} }
} }

View File

@ -8,7 +8,7 @@ import exh.util.DeferredField
import exh.util.executeOnIO import exh.util.executeOnIO
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.ConflatedBroadcastChannel import kotlinx.coroutines.flow.MutableStateFlow
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class MigratingManga( class MigratingManga(
@ -20,7 +20,7 @@ class MigratingManga(
val searchResult = DeferredField<Long?>() val searchResult = DeferredField<Long?>()
// <MAX, PROGRESS> // <MAX, PROGRESS>
val progress = ConflatedBroadcastChannel(1 to 0) val progress = MutableStateFlow(1 to 0)
val migrationJob = parentContext + SupervisorJob() + Dispatchers.Default val migrationJob = parentContext + SupervisorJob() + Dispatchers.Default

View File

@ -37,6 +37,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.await import eu.kanade.tachiyomi.util.lang.await
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import exh.eh.EHentaiThrottleManager import exh.eh.EHentaiThrottleManager
@ -51,7 +52,6 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@ -187,7 +187,7 @@ class MigrationListController(bundle: Bundle? = null) :
} catch (e: Exception) { } catch (e: Exception) {
return@async2 null return@async2 null
} }
manga.progress.send(validSources.size to processedSources.incrementAndGet()) manga.progress.value = validSources.size to processedSources.incrementAndGet()
localManga to chapters.size localManga to chapters.size
} else { } else {
null null
@ -222,7 +222,7 @@ class MigrationListController(bundle: Bundle? = null) :
Timber.e(e) Timber.e(e)
emptyList() emptyList()
} }
withContext(Dispatchers.IO) { withIOContext {
syncChaptersWithSource(db, chapters, localManga, source) syncChaptersWithSource(db, chapters, localManga, source)
} }
localManga localManga
@ -233,7 +233,7 @@ class MigrationListController(bundle: Bundle? = null) :
} catch (e: Exception) { } catch (e: Exception) {
null null
} }
manga.progress.send(validSources.size to (index + 1)) manga.progress.value = validSources.size to (index + 1)
if (searchResult != null) return@async searchResult if (searchResult != null) return@async searchResult
} }

View File

@ -8,9 +8,8 @@ import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import kotlinx.coroutines.Dispatchers import eu.kanade.tachiyomi.util.lang.withIOContext
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class MigrationProcessAdapter( class MigrationProcessAdapter(
@ -48,7 +47,7 @@ class MigrationProcessAdapter(
fun mangasSkipped() = items.count { it.manga.migrationStatus == MigrationStatus.MANGA_NOT_FOUND } fun mangasSkipped() = items.count { it.manga.migrationStatus == MigrationStatus.MANGA_NOT_FOUND }
suspend fun performMigrations(copy: Boolean) { suspend fun performMigrations(copy: Boolean) {
withContext(Dispatchers.IO) { withIOContext {
db.inTransaction { db.inTransaction {
currentItems.forEach { migratingManga -> currentItems.forEach { migratingManga ->
val manga = migratingManga.manga val manga = migratingManga.manga

View File

@ -18,14 +18,13 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.setVectorCompat import eu.kanade.tachiyomi.util.view.setVectorCompat
import exh.MERGED_SOURCE_ID import exh.MERGED_SOURCE_ID
import exh.util.executeOnIO import exh.util.executeOnIO
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
import reactivecircus.flowbinding.android.view.clicks import reactivecircus.flowbinding.android.view.clicks
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.text.DecimalFormat import java.text.DecimalFormat
@ -70,7 +69,7 @@ class MigrationProcessHolder(
binding.skipManga.isVisible = true binding.skipManga.isVisible = true
binding.migrationMangaCardTo.resetManga() binding.migrationMangaCardTo.resetManga()
if (manga != null) { if (manga != null) {
withContext(Dispatchers.Main) { withUIContext {
binding.migrationMangaCardFrom.attachManga(manga, source) binding.migrationMangaCardFrom.attachManga(manga, source)
binding.migrationMangaCardFrom.root.clicks() binding.migrationMangaCardFrom.root.clicks()
.onEach { .onEach {
@ -85,7 +84,7 @@ class MigrationProcessHolder(
/*launchUI { /*launchUI {
item.manga.progress.asFlow().collect { (max, progress) -> item.manga.progress.asFlow().collect { (max, progress) ->
withContext(Dispatchers.Main) { withUIContext {
migration_manga_card_to.search_progress.let { progressBar -> migration_manga_card_to.search_progress.let { progressBar ->
progressBar.max = max progressBar.max = max
progressBar.progress = progress progressBar.progress = progress
@ -100,11 +99,11 @@ class MigrationProcessHolder(
val resultSource = searchResult?.source?.let { val resultSource = searchResult?.source?.let {
sourceManager.get(it) sourceManager.get(it)
} }
withContext(Dispatchers.Main) { withUIContext {
if (item.manga.mangaId != this@MigrationProcessHolder.item?.manga?.mangaId || if (item.manga.mangaId != this@MigrationProcessHolder.item?.manga?.mangaId ||
item.manga.migrationStatus == MigrationStatus.RUNNING item.manga.migrationStatus == MigrationStatus.RUNNING
) { ) {
return@withContext return@withUIContext
} }
if (searchResult != null && resultSource != null) { if (searchResult != null && resultSource != null) {
binding.migrationMangaCardTo.attachManga(searchResult, resultSource) binding.migrationMangaCardTo.attachManga(searchResult, resultSource)

View File

@ -19,9 +19,8 @@ import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationContr
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaController import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaController
import eu.kanade.tachiyomi.util.lang.await import eu.kanade.tachiyomi.util.lang.await
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.openInBrowser
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -100,7 +99,7 @@ class MigrationSourcesController :
launchUI { launchUI {
val manga = Injekt.get<DatabaseHelper>().getFavoriteMangas().asRxSingle().await(Schedulers.io()) val manga = Injekt.get<DatabaseHelper>().getFavoriteMangas().asRxSingle().await(Schedulers.io())
val sourceMangas = manga.asSequence().filter { it.source == item.source.id }.map { it.id!! }.toList() val sourceMangas = manga.asSequence().filter { it.source == item.source.id }.map { it.id!! }.toList()
withContext(Dispatchers.Main) { withUIContext {
PreMigrationController.navigateToMigration( PreMigrationController.navigateToMigration(
Injekt.get<PreferencesHelper>().skipPreMigration().get(), Injekt.get<PreferencesHelper>().skipPreMigration().get(),
if (parentController is BrowseController) { if (parentController is BrowseController) {

View File

@ -143,7 +143,7 @@ open class IndexController :
searchView.maxWidth = Int.MAX_VALUE searchView.maxWidth = Int.MAX_VALUE
val query = presenter.query val query = presenter.query
if (!query.isBlank()) { if (query.isNotBlank()) {
searchItem.expandActionView() searchItem.expandActionView()
searchView.setQuery(query, true) searchView.setQuery(query, true)
searchView.clearFocus() searchView.clearFocus()

View File

@ -15,12 +15,12 @@ import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter.Companion.toItems import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter.Companion.toItems
import eu.kanade.tachiyomi.util.lang.asFlow import eu.kanade.tachiyomi.util.lang.asFlow
import eu.kanade.tachiyomi.util.lang.runAsObservable import eu.kanade.tachiyomi.util.lang.runAsObservable
import eu.kanade.tachiyomi.util.lang.withUIContext
import exh.savedsearches.EXHSavedSearch import exh.savedsearches.EXHSavedSearch
import exh.savedsearches.JsonSavedSearch import exh.savedsearches.JsonSavedSearch
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.singleOrNull import kotlinx.coroutines.flow.singleOrNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import rx.Observable import rx.Observable
@ -99,7 +99,7 @@ open class IndexPresenter(
initializeFetchImageSubscription() initializeFetchImageSubscription()
presenterScope.launch(Dispatchers.IO) { presenterScope.launch(Dispatchers.IO) {
withContext(Dispatchers.Main) { withUIContext {
Observable.just(null).subscribeLatestCache({ view, results -> Observable.just(null).subscribeLatestCache({ view, results ->
view.setLatestManga(results) view.setLatestManga(results)
}) })
@ -118,7 +118,7 @@ open class IndexPresenter(
} }
fetchImage(results, true) fetchImage(results, true)
withContext(Dispatchers.Main) { withUIContext {
Observable.just(results.map { IndexCardItem(it) }).subscribeLatestCache({ view, results -> Observable.just(results.map { IndexCardItem(it) }).subscribeLatestCache({ view, results ->
view.setLatestManga(results) view.setLatestManga(results)
}) })
@ -127,7 +127,7 @@ open class IndexPresenter(
} }
presenterScope.launch(Dispatchers.IO) { presenterScope.launch(Dispatchers.IO) {
withContext(Dispatchers.Main) { withUIContext {
Observable.just(null).subscribeLatestCache({ view, results -> Observable.just(null).subscribeLatestCache({ view, results ->
view.setBrowseManga(results) view.setBrowseManga(results)
}) })
@ -146,7 +146,7 @@ open class IndexPresenter(
} }
fetchImage(results, false) fetchImage(results, false)
withContext(Dispatchers.Main) { withUIContext {
Observable.just(results.map { IndexCardItem(it) }).subscribeLatestCache({ view, results -> Observable.just(results.map { IndexCardItem(it) }).subscribeLatestCache({ view, results ->
view.setBrowseManga(results) view.setBrowseManga(results)
}) })

View File

@ -36,7 +36,6 @@ import reactivecircus.flowbinding.recyclerview.scrollStateChanges
import reactivecircus.flowbinding.swiperefreshlayout.refreshes import reactivecircus.flowbinding.swiperefreshlayout.refreshes
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.subscriptions.CompositeSubscription import rx.subscriptions.CompositeSubscription
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit

View File

@ -30,6 +30,7 @@ import eu.kanade.tachiyomi.util.lang.awaitSingle
import eu.kanade.tachiyomi.util.lang.byteSize import eu.kanade.tachiyomi.util.lang.byteSize
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.takeBytes import eu.kanade.tachiyomi.util.lang.takeBytes
import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.updateCoverLastModified import eu.kanade.tachiyomi.util.updateCoverLastModified
@ -44,11 +45,9 @@ import exh.metadata.metadata.base.getFlatMetadataForManga
import exh.source.getMainSource import exh.source.getMainSource
import exh.util.defaultReaderType import exh.util.defaultReaderType
import exh.util.shouldDeleteChapters import exh.util.shouldDeleteChapters
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
@ -310,7 +309,7 @@ class ReaderPresenter(
// SY --> // SY -->
suspend fun getChapters(context: Context): List<ReaderChapterItem> { suspend fun getChapters(context: Context): List<ReaderChapterItem> {
return withContext(Dispatchers.IO) { return withIOContext {
val currentChapter = getCurrentChapter() val currentChapter = getCurrentChapter()
val decimalFormat = DecimalFormat( val decimalFormat = DecimalFormat(
"#.###", "#.###",

View File

@ -18,12 +18,6 @@ import exh.metadata.metadata.base.insertFlatMetadata
import exh.util.await import exh.util.await
import exh.util.floor import exh.util.floor
import exh.util.nullIfZero import exh.util.nullIfZero
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Response import okhttp3.Response
import rx.Completable import rx.Completable
import rx.Single import rx.Single

View File

@ -10,7 +10,7 @@ import exh.md.utils.MdUtil
import exh.md.utils.setMDUrlWithoutDomain import exh.md.utils.setMDUrlWithoutDomain
import okhttp3.CacheControl import okhttp3.CacheControl
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -75,7 +75,7 @@ class SearchHandler(val client: OkHttpClient, private val headers: Headers, val
val demographics = mutableListOf<String>() val demographics = mutableListOf<String>()
// Do traditional search // Do traditional search
val url = "${MdUtil.baseUrl}/?page=search".toHttpUrlOrNull()!!.newBuilder() val url = "${MdUtil.baseUrl}/?page=search".toHttpUrl().newBuilder()
.addQueryParameter("p", page.toString()) .addQueryParameter("p", page.toString())
.addQueryParameter("title", query.replace(WHITESPACE_REGEX, " ")) .addQueryParameter("title", query.replace(WHITESPACE_REGEX, " "))

View File

@ -49,13 +49,11 @@ object SourceTagsUtil {
} }
fun parseTag(tag: String) = RaisedTag( fun parseTag(tag: String) = RaisedTag(
(
if (tag.startsWith("-")) { if (tag.startsWith("-")) {
tag.substringAfter("-") tag.substringAfter("-")
} else { } else {
tag tag
} }.substringBefore(':', missingDelimiterValue = "").trimOrNull(),
).substringBefore(':', missingDelimiterValue = "").trimOrNull(),
tag.substringAfter(':', missingDelimiterValue = tag).trim(), tag.substringAfter(':', missingDelimiterValue = tag).trim(),
if (tag.startsWith("-")) TAG_TYPE_EXCLUDE else TAG_TYPE_DEFAULT if (tag.startsWith("-")) TAG_TYPE_EXCLUDE else TAG_TYPE_DEFAULT
) )
@ -94,6 +92,6 @@ object SourceTagsUtil {
private const val TAG_TYPE_DEFAULT = 1 private const val TAG_TYPE_DEFAULT = 1
} }
fun Manga.getRaisedTags(genres: List<String>? = null): List<RaisedTag>? = (genres ?: this.getGenres())?.map { fun Manga.getRaisedTags(genres: List<String>? = getGenres()): List<RaisedTag>? = genres?.map {
SourceTagsUtil.parseTag(it) SourceTagsUtil.parseTag(it)
} }