Use coroutines for Bangumi and Shikimori APIs

This commit is contained in:
arkon 2020-12-24 17:23:10 -05:00
parent f2a9247b68
commit 6fcf6ae1f5
4 changed files with 107 additions and 139 deletions

View File

@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.util.lang.runAsObservable
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -32,17 +33,17 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
} }
override fun add(track: Track): Observable<Track> { override fun add(track: Track): Observable<Track> {
return api.addLibManga(track) return runAsObservable({ api.addLibManga(track) })
} }
override fun update(track: Track): Observable<Track> { override fun update(track: Track): Observable<Track> {
return api.updateLibManga(track) return runAsObservable({ api.updateLibManga(track) })
} }
override fun bind(track: Track): Observable<Track> { override fun bind(track: Track): Observable<Track> {
return api.statusLibManga(track) return runAsObservable({ api.statusLibManga(track) })
.flatMap { .flatMap {
api.findLibManga(track).flatMap { remoteTrack -> runAsObservable({ api.findLibManga(track) }).flatMap { remoteTrack ->
if (remoteTrack != null && it != null) { if (remoteTrack != null && it != null) {
track.copyPersonalFrom(remoteTrack) track.copyPersonalFrom(remoteTrack)
track.library_id = remoteTrack.library_id track.library_id = remoteTrack.library_id
@ -61,14 +62,14 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
} }
override fun search(query: String): Observable<List<TrackSearch>> { override fun search(query: String): Observable<List<TrackSearch>> {
return api.search(query) return runAsObservable({ api.search(query) })
} }
override fun refresh(track: Track): Observable<Track> { override fun refresh(track: Track): Observable<Track> {
return api.statusLibManga(track) return runAsObservable({ api.statusLibManga(track) })
.flatMap { .flatMap {
track.copyPersonalFrom(it!!) track.copyPersonalFrom(it!!)
api.findLibManga(track) runAsObservable({ api.findLibManga(track) })
.map { remoteTrack -> .map { remoteTrack ->
if (remoteTrack != null) { if (remoteTrack != null) {
track.total_chapters = remoteTrack.total_chapters track.total_chapters = remoteTrack.total_chapters
@ -103,7 +104,7 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
override fun login(username: String, password: String) = login(password) override fun login(username: String, password: String) = login(password)
fun login(code: String): Completable { fun login(code: String): Completable {
return api.accessToken(code).map { oauth: OAuth? -> return runAsObservable({ api.accessToken(code) }).map { oauth: OAuth? ->
interceptor.newAuth(oauth) interceptor.newAuth(oauth)
if (oauth != null) { if (oauth != null) {
saveCredentials(oauth.user_id.toString(), oauth.access_token) saveCredentials(oauth.user_id.toString(), oauth.access_token)

View File

@ -7,7 +7,7 @@ import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.await
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
@ -20,7 +20,6 @@ import okhttp3.CacheControl
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.net.URLEncoder import java.net.URLEncoder
@ -30,59 +29,48 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
private val authClient = client.newBuilder().addInterceptor(interceptor).build() private val authClient = client.newBuilder().addInterceptor(interceptor).build()
fun addLibManga(track: Track): Observable<Track> { suspend fun addLibManga(track: Track): Track {
val body = FormBody.Builder() val body = FormBody.Builder()
.add("rating", track.score.toInt().toString()) .add("rating", track.score.toInt().toString())
.add("status", track.toBangumiStatus()) .add("status", track.toBangumiStatus())
.build() .build()
return authClient.newCall(POST("$apiUrl/collection/${track.media_id}/update", body = body)) authClient.newCall(POST("$apiUrl/collection/${track.media_id}/update", body = body)).await()
.asObservableSuccess() return track
.map {
track
}
} }
fun updateLibManga(track: Track): Observable<Track> { suspend fun updateLibManga(track: Track): Track {
// read status update // read status update
val sbody = FormBody.Builder() val sbody = FormBody.Builder()
.add("status", track.toBangumiStatus()) .add("status", track.toBangumiStatus())
.build() .build()
return authClient.newCall(POST("$apiUrl/collection/${track.media_id}/update", body = sbody)) authClient.newCall(POST("$apiUrl/collection/${track.media_id}/update", body = sbody)).await()
.asObservableSuccess()
.map { // chapter update
track val body = FormBody.Builder()
}.flatMap { .add("watched_eps", track.last_chapter_read.toString())
// chapter update .build()
val body = FormBody.Builder() authClient.newCall(POST("$apiUrl/subject/${track.media_id}/update/watched_eps", body = body)).await()
.add("watched_eps", track.last_chapter_read.toString())
.build() return track
authClient.newCall(POST("$apiUrl/subject/${track.media_id}/update/watched_eps", body = body))
.asObservableSuccess()
.map {
track
}
}
} }
fun search(search: String): Observable<List<TrackSearch>> { suspend fun search(search: String): List<TrackSearch> {
val url = "$apiUrl/search/subject/${URLEncoder.encode(search, Charsets.UTF_8.name())}" val url = "$apiUrl/search/subject/${URLEncoder.encode(search, Charsets.UTF_8.name())}"
.toUri() .toUri()
.buildUpon() .buildUpon()
.appendQueryParameter("max_results", "20") .appendQueryParameter("max_results", "20")
.build() .build()
return authClient.newCall(GET(url.toString())) return authClient.newCall(GET(url.toString())).await().use {
.asObservableSuccess() var responseBody = it.body?.string().orEmpty()
.map { netResponse -> if (responseBody.isEmpty()) {
var responseBody = netResponse.body?.string().orEmpty() throw Exception("Null Response")
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
if (responseBody.contains("\"code\":404")) {
responseBody = "{\"results\":0,\"list\":[]}"
}
val response = json.decodeFromString<JsonObject>(responseBody)["list"]?.jsonArray
response?.filter { it.jsonObject["type"]?.jsonPrimitive?.int == 1 }?.map { jsonToSearch(it.jsonObject) }
} }
if (responseBody.contains("\"code\":404")) {
responseBody = "{\"results\":0,\"list\":[]}"
}
val response = json.decodeFromString<JsonObject>(responseBody)["list"]?.jsonArray
response?.filter { it.jsonObject["type"]?.jsonPrimitive?.int == 1 }?.map { jsonToSearch(it.jsonObject) }.orEmpty()
}
} }
private fun jsonToSearch(obj: JsonObject): TrackSearch { private fun jsonToSearch(obj: JsonObject): TrackSearch {
@ -109,17 +97,15 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
} }
} }
fun findLibManga(track: Track): Observable<Track?> { suspend fun findLibManga(track: Track): Track? {
return authClient.newCall(GET("$apiUrl/subject/${track.media_id}")) return authClient.newCall(GET("$apiUrl/subject/${track.media_id}")).await().use {
.asObservableSuccess() // get comic info
.map { netResponse -> val responseBody = it.body?.string().orEmpty()
// get comic info jsonToTrack(json.decodeFromString(responseBody))
val responseBody = netResponse.body?.string().orEmpty() }
jsonToTrack(json.decodeFromString(responseBody))
}
} }
fun statusLibManga(track: Track): Observable<Track?> { suspend fun statusLibManga(track: Track): Track? {
val urlUserRead = "$apiUrl/collection/${track.media_id}" val urlUserRead = "$apiUrl/collection/${track.media_id}"
val requestUserRead = Request.Builder() val requestUserRead = Request.Builder()
.url(urlUserRead) .url(urlUserRead)
@ -127,30 +113,24 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
.get() .get()
.build() .build()
// todo get user readed chapter here // TODO: get user readed chapter here
return authClient.newCall(requestUserRead) return authClient.newCall(requestUserRead).await().use {
.asObservableSuccess() val resp = it.body?.string()
.map { netResponse -> val coll = json.decodeFromString<Collection>(resp!!)
val resp = netResponse.body?.string() track.status = coll.status?.id!!
val coll = json.decodeFromString<Collection>(resp!!) track.last_chapter_read = coll.ep_status!!
track.status = coll.status?.id!! track
track.last_chapter_read = coll.ep_status!! }
track
}
} }
fun accessToken(code: String): Observable<OAuth> { suspend fun accessToken(code: String): OAuth {
return client.newCall(accessTokenRequest(code)) return client.newCall(accessTokenRequest(code)).await().use {
.asObservableSuccess() val responseBody = it.body?.string().orEmpty()
.map { netResponse -> if (responseBody.isEmpty()) {
netResponse.use { throw Exception("Null Response")
val responseBody = it.body?.string().orEmpty()
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
json.decodeFromString<OAuth>(responseBody)
}
} }
json.decodeFromString<OAuth>(responseBody)
}
} }
private fun accessTokenRequest(code: String) = POST( private fun accessTokenRequest(code: String) = POST(

View File

@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.util.lang.runAsObservable
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -44,15 +45,15 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
} }
override fun add(track: Track): Observable<Track> { override fun add(track: Track): Observable<Track> {
return api.addLibManga(track, getUsername()) return runAsObservable({ api.addLibManga(track, getUsername()) })
} }
override fun update(track: Track): Observable<Track> { override fun update(track: Track): Observable<Track> {
return api.updateLibManga(track, getUsername()) return runAsObservable({ api.updateLibManga(track, getUsername()) })
} }
override fun bind(track: Track): Observable<Track> { override fun bind(track: Track): Observable<Track> {
return api.findLibManga(track, getUsername()) return runAsObservable({ api.findLibManga(track, getUsername()) })
.flatMap { remoteTrack -> .flatMap { remoteTrack ->
if (remoteTrack != null) { if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack) track.copyPersonalFrom(remoteTrack)
@ -68,11 +69,11 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
} }
override fun search(query: String): Observable<List<TrackSearch>> { override fun search(query: String): Observable<List<TrackSearch>> {
return api.search(query) return runAsObservable({ api.search(query) })
} }
override fun refresh(track: Track): Observable<Track> { override fun refresh(track: Track): Observable<Track> {
return api.findLibManga(track, getUsername()) return runAsObservable({ api.findLibManga(track, getUsername()) })
.map { remoteTrack -> .map { remoteTrack ->
if (remoteTrack != null) { if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack) track.copyPersonalFrom(remoteTrack)
@ -107,7 +108,7 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
override fun login(username: String, password: String) = login(password) override fun login(username: String, password: String) = login(password)
fun login(code: String): Completable { fun login(code: String): Completable {
return api.accessToken(code).map { oauth: OAuth? -> return runAsObservable({ api.accessToken(code) }).map { oauth: OAuth? ->
interceptor.newAuth(oauth) interceptor.newAuth(oauth)
if (oauth != null) { if (oauth != null) {
val user = api.getCurrentUser() val user = api.getCurrentUser()

View File

@ -6,7 +6,7 @@ import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.await
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonArray
@ -22,7 +22,6 @@ import okhttp3.FormBody
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInterceptor) { class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInterceptor) {
@ -32,7 +31,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
private val jsonMime = "application/json; charset=utf-8".toMediaType() private val jsonMime = "application/json; charset=utf-8".toMediaType()
private val authClient = client.newBuilder().addInterceptor(interceptor).build() private val authClient = client.newBuilder().addInterceptor(interceptor).build()
fun addLibManga(track: Track, user_id: String): Observable<Track> { suspend fun addLibManga(track: Track, user_id: String): Track {
val payload = buildJsonObject { val payload = buildJsonObject {
putJsonObject("user_rate") { putJsonObject("user_rate") {
put("user_id", user_id) put("user_id", user_id)
@ -43,31 +42,26 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
put("status", track.toShikimoriStatus()) put("status", track.toShikimoriStatus())
} }
} }
return authClient.newCall(POST("$apiUrl/v2/user_rates", body = payload.toString().toRequestBody(jsonMime))) authClient.newCall(POST("$apiUrl/v2/user_rates", body = payload.toString().toRequestBody(jsonMime))).await()
.asObservableSuccess() return track
.map {
track
}
} }
fun updateLibManga(track: Track, user_id: String): Observable<Track> = addLibManga(track, user_id) suspend fun updateLibManga(track: Track, user_id: String): Track = addLibManga(track, user_id)
fun search(search: String): Observable<List<TrackSearch>> { suspend fun search(search: String): List<TrackSearch> {
val url = "$apiUrl/mangas".toUri().buildUpon() val url = "$apiUrl/mangas".toUri().buildUpon()
.appendQueryParameter("order", "popularity") .appendQueryParameter("order", "popularity")
.appendQueryParameter("search", search) .appendQueryParameter("search", search)
.appendQueryParameter("limit", "20") .appendQueryParameter("limit", "20")
.build() .build()
return authClient.newCall(GET(url.toString())) return authClient.newCall(GET(url.toString())).await().use {
.asObservableSuccess() val responseBody = it.body?.string().orEmpty()
.map { netResponse -> if (responseBody.isEmpty()) {
val responseBody = netResponse.body?.string().orEmpty() throw Exception("Null Response")
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
val response = json.decodeFromString<JsonArray>(responseBody)
response.map { jsonToSearch(it.jsonObject) }
} }
val response = json.decodeFromString<JsonArray>(responseBody)
response.map { jsonToSearch(it.jsonObject) }
}
} }
private fun jsonToSearch(obj: JsonObject): TrackSearch { private fun jsonToSearch(obj: JsonObject): TrackSearch {
@ -96,38 +90,34 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
} }
} }
fun findLibManga(track: Track, user_id: String): Observable<Track?> { suspend fun findLibManga(track: Track, user_id: String): Track? {
val urlMangas = "$apiUrl/mangas".toUri().buildUpon() val urlMangas = "$apiUrl/mangas".toUri().buildUpon()
.appendPath(track.media_id.toString()) .appendPath(track.media_id.toString())
.build() .build()
return authClient.newCall(GET(urlMangas.toString())) val mangas = authClient.newCall(GET(urlMangas.toString())).await().use {
.asObservableSuccess() val responseBody = it.body?.string().orEmpty()
.map { netResponse -> json.decodeFromString<JsonObject>(responseBody)
val responseBody = netResponse.body?.string().orEmpty() }
json.decodeFromString<JsonObject>(responseBody)
}.flatMap { mangas -> val url = "$apiUrl/v2/user_rates".toUri().buildUpon()
val url = "$apiUrl/v2/user_rates".toUri().buildUpon() .appendQueryParameter("user_id", user_id)
.appendQueryParameter("user_id", user_id) .appendQueryParameter("target_id", track.media_id.toString())
.appendQueryParameter("target_id", track.media_id.toString()) .appendQueryParameter("target_type", "Manga")
.appendQueryParameter("target_type", "Manga") .build()
.build() return authClient.newCall(GET(url.toString())).await().use {
authClient.newCall(GET(url.toString())) val responseBody = it.body?.string().orEmpty()
.asObservableSuccess() if (responseBody.isEmpty()) {
.map { netResponse -> throw Exception("Null Response")
val responseBody = netResponse.body?.string().orEmpty()
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
val response = json.decodeFromString<JsonArray>(responseBody)
if (response.size > 1) {
throw Exception("Too much mangas in response")
}
val entry = response.map {
jsonToTrack(it.jsonObject, mangas)
}
entry.firstOrNull()
}
} }
val response = json.decodeFromString<JsonArray>(responseBody)
if (response.size > 1) {
throw Exception("Too much mangas in response")
}
val entry = response.map {
jsonToTrack(it.jsonObject, mangas)
}
entry.firstOrNull()
}
} }
fun getCurrentUser(): Int { fun getCurrentUser(): Int {
@ -135,18 +125,14 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
return json.decodeFromString<JsonObject>(user)["id"]!!.jsonPrimitive.int return json.decodeFromString<JsonObject>(user)["id"]!!.jsonPrimitive.int
} }
fun accessToken(code: String): Observable<OAuth> { suspend fun accessToken(code: String): OAuth {
return client.newCall(accessTokenRequest(code)) return client.newCall(accessTokenRequest(code)).await().use {
.asObservableSuccess() val responseBody = it.body?.string().orEmpty()
.map { netResponse -> if (responseBody.isEmpty()) {
netResponse.use { throw Exception("Null Response")
val responseBody = it.body?.string().orEmpty()
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
json.decodeFromString<OAuth>(responseBody)
}
} }
json.decodeFromString<OAuth>(responseBody)
}
} }
private fun accessTokenRequest(code: String) = POST( private fun accessTokenRequest(code: String) = POST(