2016-03-19 17:48:55 +01:00
|
|
|
package eu.kanade.tachiyomi.data.source.base
|
|
|
|
|
|
|
|
import android.content.Context
|
|
|
|
import eu.kanade.tachiyomi.App
|
|
|
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
|
|
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
|
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
|
|
import eu.kanade.tachiyomi.data.network.NetworkHelper
|
|
|
|
import eu.kanade.tachiyomi.data.network.get
|
|
|
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
|
|
import eu.kanade.tachiyomi.data.source.model.MangasPage
|
|
|
|
import eu.kanade.tachiyomi.data.source.model.Page
|
2016-05-10 15:09:44 +02:00
|
|
|
import okhttp3.OkHttpClient
|
2016-03-19 17:48:55 +01:00
|
|
|
import okhttp3.Request
|
|
|
|
import okhttp3.Response
|
|
|
|
import org.jsoup.Jsoup
|
|
|
|
import rx.Observable
|
|
|
|
import rx.schedulers.Schedulers
|
|
|
|
import java.util.*
|
|
|
|
import javax.inject.Inject
|
|
|
|
|
|
|
|
abstract class Source(context: Context) : BaseSource() {
|
|
|
|
|
|
|
|
@Inject protected lateinit var networkService: NetworkHelper
|
|
|
|
@Inject protected lateinit var chapterCache: ChapterCache
|
|
|
|
@Inject protected lateinit var prefs: PreferencesHelper
|
|
|
|
|
|
|
|
val requestHeaders by lazy { headersBuilder().build() }
|
|
|
|
|
|
|
|
init {
|
|
|
|
App.get(context).component.inject(this)
|
|
|
|
}
|
|
|
|
|
2016-05-10 15:09:44 +02:00
|
|
|
open val networkClient: OkHttpClient
|
|
|
|
get() = networkService.defaultClient
|
|
|
|
|
2016-03-19 17:48:55 +01:00
|
|
|
override fun isLoginRequired(): Boolean {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
protected fun popularMangaRequest(page: MangasPage): Request {
|
|
|
|
if (page.page == 1) {
|
|
|
|
page.url = initialPopularMangasUrl
|
|
|
|
}
|
|
|
|
|
|
|
|
return get(page.url, requestHeaders)
|
|
|
|
}
|
|
|
|
|
|
|
|
protected open fun searchMangaRequest(page: MangasPage, query: String): Request {
|
|
|
|
if (page.page == 1) {
|
|
|
|
page.url = getInitialSearchUrl(query)
|
|
|
|
}
|
|
|
|
|
|
|
|
return get(page.url, requestHeaders)
|
|
|
|
}
|
|
|
|
|
|
|
|
protected open fun mangaDetailsRequest(mangaUrl: String): Request {
|
|
|
|
return get(baseUrl + mangaUrl, requestHeaders)
|
|
|
|
}
|
|
|
|
|
|
|
|
protected fun chapterListRequest(mangaUrl: String): Request {
|
|
|
|
return get(baseUrl + mangaUrl, requestHeaders)
|
|
|
|
}
|
|
|
|
|
|
|
|
protected open fun pageListRequest(chapterUrl: String): Request {
|
|
|
|
return get(baseUrl + chapterUrl, requestHeaders)
|
|
|
|
}
|
|
|
|
|
|
|
|
protected open fun imageUrlRequest(page: Page): Request {
|
|
|
|
return get(page.url, requestHeaders)
|
|
|
|
}
|
|
|
|
|
|
|
|
protected open fun imageRequest(page: Page): Request {
|
|
|
|
return get(page.imageUrl, requestHeaders)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the most popular mangas from the source
|
2016-03-19 19:30:55 +01:00
|
|
|
open fun pullPopularMangasFromNetwork(page: MangasPage): Observable<MangasPage> {
|
2016-05-10 15:09:44 +02:00
|
|
|
return networkService.requestBody(popularMangaRequest(page), networkClient)
|
2016-03-19 17:48:55 +01:00
|
|
|
.map { Jsoup.parse(it) }
|
|
|
|
.doOnNext { doc -> page.mangas = parsePopularMangasFromHtml(doc) }
|
|
|
|
.doOnNext { doc -> page.nextPageUrl = parseNextPopularMangasUrl(doc, page) }
|
|
|
|
.map { response -> page }
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get mangas from the source with a query
|
2016-03-19 19:30:55 +01:00
|
|
|
open fun searchMangasFromNetwork(page: MangasPage, query: String): Observable<MangasPage> {
|
2016-05-10 15:09:44 +02:00
|
|
|
return networkService.requestBody(searchMangaRequest(page, query), networkClient)
|
2016-03-19 17:48:55 +01:00
|
|
|
.map { Jsoup.parse(it) }
|
|
|
|
.doOnNext { doc -> page.mangas = parseSearchFromHtml(doc) }
|
|
|
|
.doOnNext { doc -> page.nextPageUrl = parseNextSearchUrl(doc, page, query) }
|
|
|
|
.map { response -> page }
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get manga details from the source
|
2016-03-19 19:30:55 +01:00
|
|
|
open fun pullMangaFromNetwork(mangaUrl: String): Observable<Manga> {
|
2016-05-10 15:09:44 +02:00
|
|
|
return networkService.requestBody(mangaDetailsRequest(mangaUrl), networkClient)
|
2016-03-19 17:48:55 +01:00
|
|
|
.flatMap { Observable.just(parseHtmlToManga(mangaUrl, it)) }
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get chapter list of a manga from the source
|
|
|
|
open fun pullChaptersFromNetwork(mangaUrl: String): Observable<List<Chapter>> {
|
2016-05-10 15:09:44 +02:00
|
|
|
return networkService.requestBody(chapterListRequest(mangaUrl), networkClient)
|
2016-03-19 17:48:55 +01:00
|
|
|
.flatMap { unparsedHtml ->
|
|
|
|
val chapters = parseHtmlToChapters(unparsedHtml)
|
|
|
|
if (!chapters.isEmpty())
|
|
|
|
Observable.just(chapters)
|
|
|
|
else
|
|
|
|
Observable.error(Exception("No chapters found"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-19 19:30:55 +01:00
|
|
|
open fun getCachedPageListOrPullFromNetwork(chapterUrl: String): Observable<List<Page>> {
|
2016-03-19 17:48:55 +01:00
|
|
|
return chapterCache.getPageListFromCache(getChapterCacheKey(chapterUrl))
|
|
|
|
.onErrorResumeNext { pullPageListFromNetwork(chapterUrl) }
|
|
|
|
.onBackpressureBuffer()
|
|
|
|
}
|
|
|
|
|
2016-03-19 19:30:55 +01:00
|
|
|
open fun pullPageListFromNetwork(chapterUrl: String): Observable<List<Page>> {
|
2016-05-10 15:09:44 +02:00
|
|
|
return networkService.requestBody(pageListRequest(chapterUrl), networkClient)
|
2016-03-19 17:48:55 +01:00
|
|
|
.flatMap { unparsedHtml ->
|
|
|
|
val pages = convertToPages(parseHtmlToPageUrls(unparsedHtml))
|
|
|
|
if (!pages.isEmpty())
|
|
|
|
Observable.just(parseFirstPage(pages, unparsedHtml))
|
|
|
|
else
|
|
|
|
Observable.error(Exception("Page list is empty"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-19 19:30:55 +01:00
|
|
|
open fun getAllImageUrlsFromPageList(pages: List<Page>): Observable<Page> {
|
2016-03-19 17:48:55 +01:00
|
|
|
return Observable.from(pages)
|
|
|
|
.filter { page -> page.imageUrl != null }
|
|
|
|
.mergeWith(getRemainingImageUrlsFromPageList(pages))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the URLs of the images of a chapter
|
2016-03-19 19:30:55 +01:00
|
|
|
open fun getRemainingImageUrlsFromPageList(pages: List<Page>): Observable<Page> {
|
2016-03-19 17:48:55 +01:00
|
|
|
return Observable.from(pages)
|
|
|
|
.filter { page -> page.imageUrl == null }
|
|
|
|
.concatMap { getImageUrlFromPage(it) }
|
|
|
|
}
|
|
|
|
|
2016-03-19 19:30:55 +01:00
|
|
|
open fun getImageUrlFromPage(page: Page): Observable<Page> {
|
2016-03-19 17:48:55 +01:00
|
|
|
page.status = Page.LOAD_PAGE
|
2016-05-10 15:09:44 +02:00
|
|
|
return networkService.requestBody(imageUrlRequest(page), networkClient)
|
2016-03-19 17:48:55 +01:00
|
|
|
.flatMap { unparsedHtml -> Observable.just(parseHtmlToImageUrl(unparsedHtml)) }
|
|
|
|
.onErrorResumeNext { e ->
|
|
|
|
page.status = Page.ERROR
|
|
|
|
Observable.just<String>(null)
|
|
|
|
}
|
|
|
|
.flatMap { imageUrl ->
|
|
|
|
page.imageUrl = imageUrl
|
|
|
|
Observable.just(page)
|
|
|
|
}
|
|
|
|
.subscribeOn(Schedulers.io())
|
|
|
|
}
|
|
|
|
|
2016-03-19 19:30:55 +01:00
|
|
|
open fun getCachedImage(page: Page): Observable<Page> {
|
2016-03-19 17:48:55 +01:00
|
|
|
val pageObservable = Observable.just(page)
|
|
|
|
if (page.imageUrl == null)
|
|
|
|
return pageObservable
|
|
|
|
|
|
|
|
return pageObservable
|
|
|
|
.flatMap { p ->
|
|
|
|
if (!chapterCache.isImageInCache(page.imageUrl)) {
|
|
|
|
return@flatMap cacheImage(page)
|
|
|
|
}
|
|
|
|
Observable.just(page)
|
|
|
|
}
|
|
|
|
.flatMap { p ->
|
|
|
|
page.imagePath = chapterCache.getImagePath(page.imageUrl)
|
|
|
|
page.status = Page.READY
|
|
|
|
Observable.just(page)
|
|
|
|
}
|
|
|
|
.onErrorResumeNext { e ->
|
|
|
|
page.status = Page.ERROR
|
|
|
|
Observable.just(page)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun cacheImage(page: Page): Observable<Page> {
|
|
|
|
page.status = Page.DOWNLOAD_IMAGE
|
|
|
|
return getImageProgressResponse(page)
|
|
|
|
.flatMap { resp ->
|
2016-04-21 15:31:07 +02:00
|
|
|
chapterCache.putImageToCache(page.imageUrl, resp, prefs.reencodeImage())
|
2016-03-19 17:48:55 +01:00
|
|
|
Observable.just(page)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-19 19:30:55 +01:00
|
|
|
open fun getImageProgressResponse(page: Page): Observable<Response> {
|
2016-03-19 17:48:55 +01:00
|
|
|
return networkService.requestBodyProgress(imageRequest(page), page)
|
2016-04-20 17:10:10 +02:00
|
|
|
.doOnNext {
|
|
|
|
if (!it.isSuccessful) {
|
|
|
|
it.body().close()
|
|
|
|
throw RuntimeException("Not a valid response")
|
|
|
|
}
|
|
|
|
}
|
2016-03-19 17:48:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fun savePageList(chapterUrl: String, pages: List<Page>?) {
|
|
|
|
if (pages != null)
|
|
|
|
chapterCache.putPageListToCache(getChapterCacheKey(chapterUrl), pages)
|
|
|
|
}
|
|
|
|
|
2016-03-19 19:30:55 +01:00
|
|
|
protected open fun convertToPages(pageUrls: List<String>): List<Page> {
|
2016-03-19 17:48:55 +01:00
|
|
|
val pages = ArrayList<Page>()
|
|
|
|
for (i in pageUrls.indices) {
|
|
|
|
pages.add(Page(i, pageUrls[i]))
|
|
|
|
}
|
|
|
|
return pages
|
|
|
|
}
|
|
|
|
|
|
|
|
protected open fun parseFirstPage(pages: List<Page>, unparsedHtml: String): List<Page> {
|
|
|
|
val firstImage = parseHtmlToImageUrl(unparsedHtml)
|
|
|
|
pages[0].imageUrl = firstImage
|
|
|
|
return pages
|
|
|
|
}
|
|
|
|
|
|
|
|
protected fun getChapterCacheKey(chapterUrl: String): String {
|
|
|
|
return "$id$chapterUrl"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Overridable method to allow custom parsing.
|
|
|
|
open fun parseChapterNumber(chapter: Chapter) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|