Convert tracker add/update/login methods to coroutines

This commit is contained in:
arkon 2020-12-24 17:50:28 -05:00
parent 6fcf6ae1f5
commit ea33179a95
15 changed files with 152 additions and 190 deletions

View File

@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.NetworkHelper
import okhttp3.OkHttpClient
import rx.Completable
import rx.Observable
import uy.kohesive.injekt.injectLazy
@ -43,9 +42,9 @@ abstract class TrackService(val id: Int) {
abstract fun displayScore(track: Track): String
abstract fun add(track: Track): Observable<Track>
abstract suspend fun add(track: Track): Track
abstract fun update(track: Track): Observable<Track>
abstract suspend fun update(track: Track): Track
abstract fun bind(track: Track): Observable<Track>
@ -53,7 +52,7 @@ abstract class TrackService(val id: Int) {
abstract fun refresh(track: Track): Observable<Track>
abstract fun login(username: String, password: String): Completable
abstract suspend fun login(username: String, password: String)
@CallSuper
open fun logout() {

View File

@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.util.lang.runAsObservable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import rx.Completable
import rx.Observable
import uy.kohesive.injekt.injectLazy
@ -132,23 +131,19 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
}
}
override fun add(track: Track): Observable<Track> {
return runAsObservable({ api.addLibManga(track) })
override suspend fun add(track: Track): Track {
return api.addLibManga(track)
}
override fun update(track: Track): Observable<Track> {
override suspend fun update(track: Track): Track {
// If user was using API v1 fetch library_id
if (track.library_id == null || track.library_id!! == 0L) {
return runAsObservable({ api.findLibManga(track, getUsername().toInt()) }).flatMap {
if (it == null) {
throw Exception("$track not found on user library")
}
track.library_id = it.library_id
runAsObservable({ api.updateLibManga(track) })
}
val libManga = api.findLibManga(track, getUsername().toInt())
?: throw Exception("$track not found on user library")
track.library_id = libManga.library_id
}
return runAsObservable({ api.updateLibManga(track) })
return api.updateLibManga(track)
}
override fun bind(track: Track): Observable<Track> {
@ -157,12 +152,12 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.library_id = remoteTrack.library_id
update(track)
runAsObservable({ update(track) })
} else {
// Set default fields if it's not found in the list
track.score = DEFAULT_SCORE.toFloat()
track.status = DEFAULT_STATUS
add(track)
runAsObservable({ add(track) })
}
}
}
@ -180,17 +175,18 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
}
}
override fun login(username: String, password: String) = login(password)
override suspend fun login(username: String, password: String) = login(password)
fun login(token: String): Completable {
val oauth = api.createOAuth(token)
interceptor.setAuth(oauth)
return runAsObservable({ api.getCurrentUser() }).map { (username, scoreType) ->
suspend fun login(token: String) {
try {
val oauth = api.createOAuth(token)
interceptor.setAuth(oauth)
val (username, scoreType) = api.getCurrentUser()
scorePreference.set(scoreType)
saveCredentials(username.toString(), oauth.access_token)
}.doOnError {
} catch (e: Throwable) {
logout()
}.toCompletable()
}
}
override fun logout() {

View File

@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.util.lang.runAsObservable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import rx.Completable
import rx.Observable
import uy.kohesive.injekt.injectLazy
@ -32,12 +31,12 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
return track.score.toInt().toString()
}
override fun add(track: Track): Observable<Track> {
return runAsObservable({ api.addLibManga(track) })
override suspend fun add(track: Track): Track {
return api.addLibManga(track)
}
override fun update(track: Track): Observable<Track> {
return runAsObservable({ api.updateLibManga(track) })
override suspend fun update(track: Track): Track {
return api.updateLibManga(track)
}
override fun bind(track: Track): Observable<Track> {
@ -54,8 +53,8 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
// Set default fields if it's not found in the list
track.score = DEFAULT_SCORE.toFloat()
track.status = DEFAULT_STATUS
add(track)
update(track)
runAsObservable({ add(track) })
runAsObservable({ update(track) })
}
}
}
@ -101,17 +100,16 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
override fun getCompletionStatus(): Int = COMPLETED
override fun login(username: String, password: String) = login(password)
override suspend fun login(username: String, password: String) = login(password)
fun login(code: String): Completable {
return runAsObservable({ api.accessToken(code) }).map { oauth: OAuth? ->
suspend fun login(code: String) {
try {
val oauth = api.accessToken(code)
interceptor.newAuth(oauth)
if (oauth != null) {
saveCredentials(oauth.user_id.toString(), oauth.access_token)
}
}.doOnError {
saveCredentials(oauth.user_id.toString(), oauth.access_token)
} catch (e: Throwable) {
logout()
}.toCompletable()
}
}
fun saveToken(oauth: OAuth?) {

View File

@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.util.lang.runAsObservable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import rx.Completable
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.DecimalFormat
@ -71,12 +70,12 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
return df.format(track.score)
}
override fun add(track: Track): Observable<Track> {
return runAsObservable({ api.addLibManga(track, getUserId()) })
override suspend fun add(track: Track): Track {
return api.addLibManga(track, getUserId())
}
override fun update(track: Track): Observable<Track> {
return runAsObservable({ api.updateLibManga(track) })
override suspend fun update(track: Track): Track {
return api.updateLibManga(track)
}
override fun bind(track: Track): Observable<Track> {
@ -85,11 +84,11 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.media_id = remoteTrack.media_id
update(track)
runAsObservable({ update(track) })
} else {
track.score = DEFAULT_SCORE
track.status = DEFAULT_STATUS
add(track)
runAsObservable({ add(track) })
}
}
}
@ -107,13 +106,15 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
}
}
override fun login(username: String, password: String): Completable {
return runAsObservable({ api.login(username, password) })
.doOnNext { interceptor.newAuth(it) }
.flatMap { runAsObservable({ api.getCurrentUser() }) }
.doOnNext { userId -> saveCredentials(username, userId) }
.doOnError { logout() }
.toCompletable()
override suspend fun login(username: String, password: String) {
try {
val token = api.login(username, password)
interceptor.newAuth(token)
val userId = api.getCurrentUser()
saveCredentials(username, userId)
} catch (e: Throwable) {
logout()
}
}
override fun logout() {

View File

@ -7,15 +7,10 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.util.lang.runAsObservable
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import rx.Completable
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import timber.log.Timber
import uy.kohesive.injekt.injectLazy
class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
@ -67,12 +62,12 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
return track.score.toInt().toString()
}
override fun add(track: Track): Observable<Track> {
return runAsObservable({ api.addItemToList(track) })
override suspend fun add(track: Track): Track {
return api.addItemToList(track)
}
override fun update(track: Track): Observable<Track> {
return runAsObservable({ api.updateItem(track) })
override suspend fun update(track: Track): Track {
return api.updateItem(track)
}
override fun bind(track: Track): Observable<Track> {
@ -88,21 +83,17 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
return runAsObservable({ api.getListItem(track) })
}
override fun login(username: String, password: String) = login(password)
override suspend fun login(username: String, password: String) = login(password)
fun login(authCode: String): Completable {
return Completable.fromCallable {
val oauth = runBlocking { api.getAccessToken(authCode) }
suspend fun login(authCode: String) {
try {
val oauth = api.getAccessToken(authCode)
interceptor.setAuth(oauth)
val username = runBlocking { api.getCurrentUser() }
val username = api.getCurrentUser()
saveCredentials(username, oauth.access_token)
} catch (e: Throwable) {
logout()
}
.doOnError {
Timber.e(it)
logout()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
override fun logout() {

View File

@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.util.lang.runAsObservable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import rx.Completable
import rx.Observable
import uy.kohesive.injekt.injectLazy
@ -44,12 +43,12 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
return track.score.toInt().toString()
}
override fun add(track: Track): Observable<Track> {
return runAsObservable({ api.addLibManga(track, getUsername()) })
override suspend fun add(track: Track): Track {
return api.addLibManga(track, getUsername())
}
override fun update(track: Track): Observable<Track> {
return runAsObservable({ api.updateLibManga(track, getUsername()) })
override suspend fun update(track: Track): Track {
return api.updateLibManga(track, getUsername())
}
override fun bind(track: Track): Observable<Track> {
@ -58,12 +57,12 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.library_id = remoteTrack.library_id
update(track)
runAsObservable({ update(track) })
} else {
// Set default fields if it's not found in the list
track.score = DEFAULT_SCORE.toFloat()
track.status = DEFAULT_STATUS
add(track)
runAsObservable({ add(track) })
}
}
}
@ -105,18 +104,17 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
override fun getCompletionStatus(): Int = COMPLETED
override fun login(username: String, password: String) = login(password)
override suspend fun login(username: String, password: String) = login(password)
fun login(code: String): Completable {
return runAsObservable({ api.accessToken(code) }).map { oauth: OAuth? ->
suspend fun login(code: String) {
try {
val oauth = api.accessToken(code)
interceptor.newAuth(oauth)
if (oauth != null) {
val user = api.getCurrentUser()
saveCredentials(user.toString(), oauth.access_token)
}
}.doOnError {
val user = api.getCurrentUser()
saveCredentials(user.toString(), oauth.access_token)
} catch (e: Throwable) {
logout()
}.toCompletable()
}
}
fun saveToken(oauth: OAuth?) {

View File

@ -131,7 +131,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
json.decodeFromString<OAuth>(responseBody)
json.decodeFromString(responseBody)
}
}

View File

@ -8,6 +8,9 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.lang.await
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.toast
import rx.Observable
import rx.Subscription
@ -107,19 +110,22 @@ class TrackPresenter(
}
private fun updateRemote(track: Track, service: TrackService) {
service.update(track)
.flatMap { db.insertTrack(track).asRxObservable() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst(
{ view, _ -> view.onRefreshDone() },
{ view, error ->
view.onRefreshError(error)
launchIO {
try {
service.update(track)
db.insertTrack(track).await()
launchUI {
view!!.onRefreshDone()
}
} catch (e: Throwable) {
launchUI {
view!!.onRefreshError(e)
// Restart on error to set old values
fetchTrackings()
}
)
}
}
}
fun setStatus(item: TrackItem, index: Int) {

View File

@ -21,13 +21,15 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
import eu.kanade.tachiyomi.util.isLocal
import eu.kanade.tachiyomi.util.lang.await
import eu.kanade.tachiyomi.util.lang.byteSize
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.takeBytes
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.updateCoverLastModified
import rx.Completable
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
@ -661,29 +663,25 @@ class ReaderPresenter(
val trackManager = Injekt.get<TrackManager>()
db.getTracks(manga).asRxSingle()
.flatMapCompletable { trackList ->
Completable.concat(
trackList.map { track ->
val service = trackManager.getService(track.sync_id)
if (service != null && service.isLogged && chapterRead > track.last_chapter_read) {
track.last_chapter_read = chapterRead
launchIO {
db.getTracks(manga).await()
.mapNotNull { track ->
val service = trackManager.getService(track.sync_id)
if (service != null && service.isLogged && chapterRead > track.last_chapter_read) {
track.last_chapter_read = chapterRead
// We wan't these to execute even if the presenter is destroyed and leaks
// for a while. The view can still be garbage collected.
Observable.defer { service.update(track) }
.map { db.insertTrack(track).executeAsBlocking() }
.toCompletable()
.onErrorComplete()
} else {
Completable.complete()
// We want these to execute even if the presenter is destroyed and leaks
// for a while. The view can still be garbage collected.
async {
service.update(track)
db.insertTrack(track).await()
}
} else {
null
}
)
}
.onErrorComplete()
.subscribeOn(Schedulers.io())
.subscribe()
}
.awaitAll()
}
}
/**

View File

@ -1,8 +1,8 @@
package eu.kanade.tachiyomi.ui.setting.track
import android.net.Uri
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
class AnilistLoginActivity : BaseOAuthLoginActivity() {
@ -10,17 +10,12 @@ class AnilistLoginActivity : BaseOAuthLoginActivity() {
val regex = "(?:access_token=)(.*?)(?:&)".toRegex()
val matchResult = regex.find(data?.fragment.toString())
if (matchResult?.groups?.get(1) != null) {
trackManager.aniList.login(matchResult.groups[1]!!.value)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
returnToSettings()
},
{
returnToSettings()
}
)
launchIO {
trackManager.aniList.login(matchResult.groups[1]!!.value)
launchUI {
returnToSettings()
}
}
} else {
trackManager.aniList.logout()
returnToSettings()

View File

@ -1,25 +1,20 @@
package eu.kanade.tachiyomi.ui.setting.track
import android.net.Uri
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
class BangumiLoginActivity : BaseOAuthLoginActivity() {
override fun handleResult(data: Uri?) {
val code = data?.getQueryParameter("code")
if (code != null) {
trackManager.bangumi.login(code)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
returnToSettings()
},
{
returnToSettings()
}
)
launchIO {
trackManager.bangumi.login(code)
launchUI {
returnToSettings()
}
}
} else {
trackManager.bangumi.logout()
returnToSettings()

View File

@ -1,25 +1,20 @@
package eu.kanade.tachiyomi.ui.setting.track
import android.net.Uri
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
class MyAnimeListLoginActivity : BaseOAuthLoginActivity() {
override fun handleResult(data: Uri?) {
val code = data?.getQueryParameter("code")
if (code != null) {
trackManager.myAnimeList.login(code)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
returnToSettings()
},
{
returnToSettings()
}
)
launchIO {
trackManager.myAnimeList.login(code)
launchUI {
returnToSettings()
}
}
} else {
trackManager.myAnimeList.logout()
returnToSettings()

View File

@ -1,25 +1,20 @@
package eu.kanade.tachiyomi.ui.setting.track
import android.net.Uri
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
class ShikimoriLoginActivity : BaseOAuthLoginActivity() {
override fun handleResult(data: Uri?) {
val code = data?.getQueryParameter("code")
if (code != null) {
trackManager.shikimori.login(code)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
returnToSettings()
},
{
returnToSettings()
}
)
launchIO {
trackManager.shikimori.login(code)
launchUI {
returnToSettings()
}
}
} else {
trackManager.shikimori.logout()
returnToSettings()

View File

@ -7,10 +7,10 @@ import androidx.core.os.bundleOf
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.widget.preference.LoginDialogPreference
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -34,8 +34,6 @@ class TrackLoginDialog(
}
override fun checkLogin() {
requestSubscription?.unsubscribe()
if (binding!!.username.text.isNullOrEmpty() || binding!!.password.text.isNullOrEmpty()) {
return
}
@ -44,20 +42,21 @@ class TrackLoginDialog(
val user = binding!!.username.text.toString()
val pass = binding!!.password.text.toString()
requestSubscription = service.login(user, pass)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
launchIO {
try {
service.login(user, pass)
launchUI {
dialog?.dismiss()
view?.context?.toast(R.string.login_success)
},
{ error ->
}
} catch (e: Throwable) {
launchUI {
binding!!.login.progress = -1
binding!!.login.setText(R.string.unknown_error)
error.message?.let { view?.context?.toast(it) }
e.message?.let { view?.context?.toast(it) }
}
)
}
}
}
override fun onDialogClosed() {

View File

@ -13,7 +13,6 @@ import com.dd.processbutton.iml.ActionProcessButton
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.PrefAccountLoginBinding
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import rx.Subscription
import uy.kohesive.injekt.injectLazy
abstract class LoginDialogPreference(
@ -28,8 +27,6 @@ abstract class LoginDialogPreference(
val preferences: PreferencesHelper by injectLazy()
var requestSubscription: Subscription? = null
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
binding = PrefAccountLoginBinding.inflate(LayoutInflater.from(activity!!))
var dialog = MaterialDialog(activity!!)
@ -64,7 +61,6 @@ abstract class LoginDialogPreference(
}
open fun onDialogClosed() {
requestSubscription?.unsubscribe()
binding = null
}