diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 4ae5cfade1..9a15967f4b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -43,7 +43,6 @@ import nl.adaptivity.xmlutil.serialization.XML import okhttp3.Response import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE import tachiyomi.core.metadata.comicinfo.ComicInfo -import tachiyomi.core.util.lang.awaitSingle import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchNow import tachiyomi.core.util.lang.withIOContext @@ -363,7 +362,7 @@ class Downloader( if (page.imageUrl.isNullOrEmpty()) { page.status = Page.State.LOAD_PAGE try { - page.imageUrl = download.source.fetchImageUrl(page).awaitSingle() + page.imageUrl = download.source.getImageUrl(page) } catch (e: Throwable) { page.status = Page.State.ERROR } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt index 55c2e151b7..276af54ee2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt @@ -19,7 +19,6 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import tachiyomi.core.util.lang.awaitSingle import tachiyomi.domain.manga.interactor.GetManga import tachiyomi.domain.manga.interactor.NetworkToLocalManga import tachiyomi.domain.manga.model.Manga @@ -140,7 +139,7 @@ abstract class SearchScreenModel( try { val page = withContext(coroutineDispatcher) { - source.fetchSearchManga(1, query, source.getFilterList()).awaitSingle() + source.getSearchManga(1, query, source.getFilterList()) } val titles = page.mangas.map { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt index 38ec9361df..4802708a65 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt @@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flow import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.suspendCancellableCoroutine -import tachiyomi.core.util.lang.awaitSingle import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.withIOContext import uy.kohesive.injekt.Injekt @@ -170,7 +169,7 @@ internal class HttpPageLoader( try { if (page.imageUrl.isNullOrEmpty()) { page.status = Page.State.LOAD_PAGE - page.imageUrl = source.fetchImageUrl(page).awaitSingle() + page.imageUrl = source.getImageUrl(page) } val imageUrl = page.imageUrl!! diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt b/core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt index 4cbb348128..e6eec02f4a 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt @@ -58,6 +58,15 @@ fun Call.asObservable(): Observable { } } +fun Call.asObservableSuccess(): Observable { + return asObservable().doOnNext { response -> + if (!response.isSuccessful) { + response.close() + throw HttpException(response.code) + } + } +} + // Based on https://github.com/gildor/kotlin-coroutines-okhttp @OptIn(ExperimentalCoroutinesApi::class) private suspend fun Call.await(callStack: Array): Response { @@ -95,6 +104,9 @@ suspend fun Call.await(): Response { return await(callStack) } +/** + * @since extensions-lib 1.5 + */ suspend fun Call.awaitSuccess(): Response { val callStack = Exception().stackTrace.run { copyOfRange(1, size) } val response = await(callStack) @@ -105,15 +117,6 @@ suspend fun Call.awaitSuccess(): Response { return response } -fun Call.asObservableSuccess(): Observable { - return asObservable().doOnNext { response -> - if (!response.isSuccessful) { - response.close() - throw HttpException(response.code) - } - } -} - fun OkHttpClient.newCachelessCallWithProgress(request: Request, listener: ProgressListener): Call { val progressClient = newBuilder() .cache(null) diff --git a/data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt b/data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt index e8295a2583..c87ec5cff9 100644 --- a/data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt +++ b/data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt @@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.SManga -import tachiyomi.core.util.lang.awaitSingle import tachiyomi.core.util.lang.withIOContext import tachiyomi.domain.source.repository.SourcePagingSourceType @@ -13,19 +12,19 @@ class SourceSearchPagingSource(source: CatalogueSource, val query: String, val f source, ) { override suspend fun requestNextPage(currentPage: Int): MangasPage { - return source.fetchSearchManga(currentPage, query, filters).awaitSingle() + return source.getSearchManga(currentPage, query, filters) } } class SourcePopularPagingSource(source: CatalogueSource) : SourcePagingSource(source) { override suspend fun requestNextPage(currentPage: Int): MangasPage { - return source.fetchPopularManga(currentPage).awaitSingle() + return source.getPopularManga(currentPage) } } class SourceLatestPagingSource(source: CatalogueSource) : SourcePagingSource(source) { override suspend fun requestNextPage(currentPage: Int): MangasPage { - return source.fetchLatestUpdates(currentPage).awaitSingle() + return source.getLatestUpdates(currentPage) } } diff --git a/domain/src/main/java/tachiyomi/domain/source/model/StubSource.kt b/domain/src/main/java/tachiyomi/domain/source/model/StubSource.kt index 38d38ef575..8b5f67ef5e 100644 --- a/domain/src/main/java/tachiyomi/domain/source/model/StubSource.kt +++ b/domain/src/main/java/tachiyomi/domain/source/model/StubSource.kt @@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga -import rx.Observable class StubSource( override val id: Long, @@ -14,36 +13,16 @@ class StubSource( private val isInvalid: Boolean = name.isBlank() || lang.isBlank() - override suspend fun getMangaDetails(manga: SManga): SManga { + override suspend fun getMangaDetails(manga: SManga): SManga = throw SourceNotInstalledException() - } - @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getMangaDetails")) - override fun fetchMangaDetails(manga: SManga): Observable { - return Observable.error(SourceNotInstalledException()) - } - - override suspend fun getChapterList(manga: SManga): List { + override suspend fun getChapterList(manga: SManga): List = throw SourceNotInstalledException() - } - - @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getChapterList")) - override fun fetchChapterList(manga: SManga): Observable> { - return Observable.error(SourceNotInstalledException()) - } - - override suspend fun getPageList(chapter: SChapter): List { + override suspend fun getPageList(chapter: SChapter): List = throw SourceNotInstalledException() - } - @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPageList")) - override fun fetchPageList(chapter: SChapter): Observable> { - return Observable.error(SourceNotInstalledException()) - } - - override fun toString(): String { - return if (isInvalid.not()) "$name (${lang.uppercase()})" else id.toString() - } + override fun toString(): String = + if (isInvalid.not()) "$name (${lang.uppercase()})" else id.toString() } class SourceNotInstalledException : Exception() diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt index f9e416def6..937fa28a9a 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.source import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage import rx.Observable +import tachiyomi.core.util.lang.awaitSingle interface CatalogueSource : Source { @@ -17,30 +18,63 @@ interface CatalogueSource : Source { val supportsLatest: Boolean /** - * Returns an observable containing a page with a list of manga. + * Get a page with a list of manga. * + * @since extensions-lib 1.5 * @param page the page number to retrieve. */ - fun fetchPopularManga(page: Int): Observable + @Suppress("DEPRECATION") + suspend fun getPopularManga(page: Int): MangasPage { + return fetchPopularManga(page).awaitSingle() + } /** - * Returns an observable containing a page with a list of manga. + * Get a page with a list of manga. * + * @since extensions-lib 1.5 * @param page the page number to retrieve. * @param query the search query. * @param filters the list of filters to apply. */ - fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable + @Suppress("DEPRECATION") + suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage { + return fetchSearchManga(page, query, filters).awaitSingle() + } /** - * Returns an observable containing a page with a list of latest manga updates. + * Get a page with a list of latest manga updates. * + * @since extensions-lib 1.5 * @param page the page number to retrieve. */ - fun fetchLatestUpdates(page: Int): Observable + @Suppress("DEPRECATION") + suspend fun getLatestUpdates(page: Int): MangasPage { + return fetchLatestUpdates(page).awaitSingle() + } /** * Returns the list of filters for the source. */ fun getFilterList(): FilterList + + @Deprecated( + "Use the non-RxJava API instead", + ReplaceWith("getPopularManga"), + ) + fun fetchPopularManga(page: Int): Observable = + throw IllegalStateException("Not used") + + @Deprecated( + "Use the non-RxJava API instead", + ReplaceWith("getSearchManga"), + ) + fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable = + throw IllegalStateException("Not used") + + @Deprecated( + "Use the non-RxJava API instead", + ReplaceWith("getLatestUpdates"), + ) + fun fetchLatestUpdates(page: Int): Observable = + throw IllegalStateException("Not used") } diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt index d42885f80f..e6a019ecca 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt @@ -1,6 +1,19 @@ package eu.kanade.tachiyomi.source +import android.app.Application +import android.content.SharedPreferences +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + interface ConfigurableSource : Source { + /** + * Gets instance of [SharedPreferences] scoped to the specific source. + * + * @since extensions-lib 1.5 + */ + fun getPreferences(): SharedPreferences = + Injekt.get().getSharedPreferences("source_$id", 0x0000) + fun setupPreferenceScreen(screen: PreferenceScreen) } diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/Source.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/Source.kt index f140eda74e..83fcd79624 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/Source.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/Source.kt @@ -27,7 +27,7 @@ interface Source { /** * Get the updated details for a manga. * - * @since extensions-lib 1.4 + * @since extensions-lib 1.5 * @param manga the manga to update. * @return the updated manga. */ @@ -39,7 +39,7 @@ interface Source { /** * Get all the available chapters for a manga. * - * @since extensions-lib 1.4 + * @since extensions-lib 1.5 * @param manga the manga to update. * @return the chapters for the manga. */ @@ -52,7 +52,7 @@ interface Source { * Get the list of pages a chapter has. Pages should be returned * in the expected order; the index is ignored. * - * @since extensions-lib 1.4 + * @since extensions-lib 1.5 * @param chapter the chapter. * @return the pages for the chapter. */ @@ -61,41 +61,24 @@ interface Source { return fetchPageList(chapter).awaitSingle() } - /** - * Returns an observable with the updated details for a manga. - * - * @param manga the manga to update. - */ @Deprecated( "Use the non-RxJava API instead", ReplaceWith("getMangaDetails"), ) - fun fetchMangaDetails(manga: SManga): Observable = throw IllegalStateException( - "Not used", - ) + fun fetchMangaDetails(manga: SManga): Observable = + throw IllegalStateException("Not used") - /** - * Returns an observable with all the available chapters for a manga. - * - * @param manga the manga to update. - */ @Deprecated( "Use the non-RxJava API instead", ReplaceWith("getChapterList"), ) - fun fetchChapterList(manga: SManga): Observable> = throw IllegalStateException( - "Not used", - ) + fun fetchChapterList(manga: SManga): Observable> = + throw IllegalStateException("Not used") - /** - * Returns an observable with the list of pages a chapter has. Pages should be returned - * in the expected order; the index is ignored. - * - * @param chapter the chapter. - */ @Deprecated( "Use the non-RxJava API instead", ReplaceWith("getPageList"), ) - fun fetchPageList(chapter: SChapter): Observable> = Observable.empty() + fun fetchPageList(chapter: SChapter): Observable> = + throw IllegalStateException("Not used") } diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt index 5a0acc50eb..957febceb9 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt @@ -16,6 +16,7 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import rx.Observable +import tachiyomi.core.util.lang.awaitSingle import uy.kohesive.injekt.injectLazy import java.net.URI import java.net.URISyntaxException @@ -24,6 +25,7 @@ import java.security.MessageDigest /** * A simple implementation for sources from a website. */ +@Suppress("unused") abstract class HttpSource : CatalogueSource { /** @@ -81,6 +83,7 @@ abstract class HttpSource : CatalogueSource { * @param versionId [Int] the version ID of the source * @return a unique ID for the source */ + @Suppress("MemberVisibilityCanBePrivate") protected fun generateId(name: String, lang: String, versionId: Int): Long { val key = "${name.lowercase()}/$lang/$versionId" val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray()) @@ -202,11 +205,18 @@ abstract class HttpSource : CatalogueSource { protected abstract fun latestUpdatesParse(response: Response): MangasPage /** - * Returns an observable with the updated details for a manga. Normally it's not needed to - * override this method. + * Get the updated details for a manga. + * Normally it's not needed to override this method. * - * @param manga the manga to be updated. + * @param manga the manga to update. + * @return the updated manga. */ + @Suppress("DEPRECATION") + override suspend fun getMangaDetails(manga: SManga): SManga { + return fetchMangaDetails(manga).awaitSingle() + } + + @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getMangaDetails")) override fun fetchMangaDetails(manga: SManga): Observable { return client.newCall(mangaDetailsRequest(manga)) .asObservableSuccess() @@ -233,11 +243,23 @@ abstract class HttpSource : CatalogueSource { protected abstract fun mangaDetailsParse(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 + * Get all the available chapters for a manga. + * Normally it's not needed to override this method. * - * @param manga the manga to look for chapters. + * @param manga the manga to update. + * @return the chapters for the manga. + * @throws LicensedMangaChaptersException if a manga is licensed and therefore no chapters are available. */ + @Suppress("DEPRECATION") + override suspend fun getChapterList(manga: SManga): List { + if (manga.status == SManga.LICENSED) { + throw LicensedMangaChaptersException() + } + + return fetchChapterList(manga).awaitSingle() + } + + @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getChapterList")) override fun fetchChapterList(manga: SManga): Observable> { return if (manga.status != SManga.LICENSED) { client.newCall(chapterListRequest(manga)) @@ -268,10 +290,18 @@ abstract class HttpSource : CatalogueSource { protected abstract fun chapterListParse(response: Response): List /** - * Returns an observable with the page list for a chapter. + * Get the list of pages a chapter has. Pages should be returned + * in the expected order; the index is ignored. * - * @param chapter the chapter whose page list has to be fetched. + * @param chapter the chapter. + * @return the pages for the chapter. */ + @Suppress("DEPRECATION") + override suspend fun getPageList(chapter: SChapter): List { + return fetchPageList(chapter).awaitSingle() + } + + @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPageList")) override fun fetchPageList(chapter: SChapter): Observable> { return client.newCall(pageListRequest(chapter)) .asObservableSuccess() @@ -301,8 +331,15 @@ abstract class HttpSource : CatalogueSource { * 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. * + * @since extensions-lib 1.5 * @param page the page whose source image has to be fetched. */ + @Suppress("DEPRECATION") + open suspend fun getImageUrl(page: Page): String { + return fetchImageUrl(page).awaitSingle() + } + + @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getImageUrl")) open fun fetchImageUrl(page: Page): Observable { return client.newCall(imageUrlRequest(page)) .asObservableSuccess() @@ -326,24 +363,14 @@ abstract class HttpSource : CatalogueSource { */ protected abstract fun imageUrlParse(response: Response): String - /** - * Returns an observable with the response of the source image. - * - * @param page the page whose source image has to be downloaded. - */ - fun fetchImage(page: Page): Observable { - // images will be cached or saved manually, so don't take up network cache - return client.newCachelessCallWithProgress(imageRequest(page), page) - .asObservableSuccess() - } - /** * Returns the response of the source image. + * Typically does not need to be overridden. * + * @since extensions-lib 1.5 * @param page the page whose source image has to be downloaded. */ - suspend fun getImage(page: Page): Response { - // images will be cached or saved manually, so don't take up network cache + open suspend fun getImage(page: Page): Response { return client.newCachelessCallWithProgress(imageRequest(page), page) .awaitSuccess() } diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt deleted file mode 100644 index 76c68e882f..0000000000 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt +++ /dev/null @@ -1,25 +0,0 @@ -package eu.kanade.tachiyomi.source.online - -import eu.kanade.tachiyomi.source.model.Page -import rx.Observable - -fun HttpSource.fetchAllImageUrlsFromPageList(pages: List): Observable { - return Observable.from(pages) - .filter { !it.imageUrl.isNullOrEmpty() } - .mergeWith(fetchRemainingImageUrlsFromPageList(pages)) -} - -private fun HttpSource.fetchRemainingImageUrlsFromPageList(pages: List): Observable { - return Observable.from(pages) - .filter { it.imageUrl.isNullOrEmpty() } - .concatMap { getImageUrl(it) } -} - -private fun HttpSource.getImageUrl(page: Page): Observable { - page.status = Page.State.LOAD_PAGE - return fetchImageUrl(page) - .doOnError { page.status = Page.State.ERROR } - .onErrorReturn { null } - .doOnNext { page.imageUrl = it } - .map { page } -} diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt index 403419df25..42879d559f 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt @@ -16,7 +16,6 @@ import kotlinx.serialization.json.decodeFromStream import logcat.LogPriority import nl.adaptivity.xmlutil.AndroidXmlReader import nl.adaptivity.xmlutil.serialization.XML -import rx.Observable import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE import tachiyomi.core.metadata.comicinfo.ComicInfo import tachiyomi.core.metadata.comicinfo.copyFromComicInfo @@ -66,11 +65,11 @@ actual class LocalSource( override val supportsLatest: Boolean = true // Browse related - override fun fetchPopularManga(page: Int) = fetchSearchManga(page, "", POPULAR_FILTERS) + override suspend fun getPopularManga(page: Int) = getSearchManga(page, "", POPULAR_FILTERS) - override fun fetchLatestUpdates(page: Int) = fetchSearchManga(page, "", LATEST_FILTERS) + override suspend fun getLatestUpdates(page: Int) = getSearchManga(page, "", LATEST_FILTERS) - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage { val baseDirsFiles = fileSystem.getFilesInBaseDirectories() val lastModifiedLimit by lazy { if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L } var mangaDirs = baseDirsFiles @@ -143,7 +142,7 @@ actual class LocalSource( } } - return Observable.just(MangasPage(mangas.toList(), false)) + return MangasPage(mangas.toList(), false) } // Manga details related