Use coroutines for updater

This commit is contained in:
arkon 2020-02-09 22:24:56 -05:00
parent 340829bb71
commit 6a95ff56df
9 changed files with 67 additions and 81 deletions

View File

@ -110,16 +110,16 @@ dependencies {
// Android support library // Android support library
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.google.android.material:material:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.annotation:annotation:1.1.0' implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.browser:browser:1.2.0' implementation 'androidx.browser:browser:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
// UI library
implementation 'com.google.android.material:material:1.1.0'
standardImplementation 'com.google.firebase:firebase-core:17.2.2' standardImplementation 'com.google.firebase:firebase-core:17.2.2'
// ReactiveX // ReactiveX
@ -221,7 +221,7 @@ dependencies {
implementation "com.jakewharton.rxbinding:rxbinding-recyclerview-v7-kotlin:$rxbindings_version" implementation "com.jakewharton.rxbinding:rxbinding-recyclerview-v7-kotlin:$rxbindings_version"
// Tests // Tests
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.13'
testImplementation 'org.assertj:assertj-core:1.7.1' testImplementation 'org.assertj:assertj-core:1.7.1'
testImplementation 'org.mockito:mockito-core:1.10.19' testImplementation 'org.mockito:mockito-core:1.10.19'

View File

@ -53,12 +53,11 @@ class TrackSearch : Track {
result = 31 * result + media_id result = 31 * result + media_id
return result return result
} }
companion object {
companion object {
fun create(serviceId: Int): TrackSearch = TrackSearch().apply { fun create(serviceId: Int): TrackSearch = TrackSearch().apply {
sync_id = serviceId sync_id = serviceId
} }
} }
} }

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.updater
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.updater.devrepo.DevRepoUpdateChecker import eu.kanade.tachiyomi.data.updater.devrepo.DevRepoUpdateChecker
import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker
import rx.Observable
abstract class UpdateChecker { abstract class UpdateChecker {
@ -20,6 +19,6 @@ abstract class UpdateChecker {
/** /**
* Returns observable containing release information * Returns observable containing release information
*/ */
abstract fun checkForUpdate(): Observable<UpdateResult> abstract suspend fun checkForUpdate(): UpdateResult
} }

View File

@ -9,36 +9,37 @@ import com.evernote.android.job.JobRequest
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import kotlinx.coroutines.runBlocking
class UpdaterJob : Job() { class UpdaterJob : Job() {
override fun onRunJob(params: Params): Result { override fun onRunJob(params: Params): Result {
return UpdateChecker.getUpdateChecker() return runBlocking {
.checkForUpdate() try {
.map { result -> val result = UpdateChecker.getUpdateChecker().checkForUpdate()
if (result is UpdateResult.NewUpdate<*>) {
val url = result.release.downloadLink
val intent = Intent(context, UpdaterService::class.java).apply { if (result is UpdateResult.NewUpdate<*>) {
putExtra(UpdaterService.EXTRA_DOWNLOAD_URL, url) val url = result.release.downloadLink
}
NotificationCompat.Builder(context, Notifications.CHANNEL_COMMON).update { val intent = Intent(context, UpdaterService::class.java).apply {
setContentTitle(context.getString(R.string.app_name)) putExtra(UpdaterService.EXTRA_DOWNLOAD_URL, url)
setContentText(context.getString(R.string.update_check_notification_update_available)) }
setSmallIcon(android.R.drawable.stat_sys_download_done)
// Download action NotificationCompat.Builder(context, Notifications.CHANNEL_COMMON).update {
addAction(android.R.drawable.stat_sys_download_done, setContentTitle(context.getString(R.string.app_name))
context.getString(R.string.action_download), setContentText(context.getString(R.string.update_check_notification_update_available))
PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)) setSmallIcon(android.R.drawable.stat_sys_download_done)
} // Download action
addAction(android.R.drawable.stat_sys_download_done,
context.getString(R.string.action_download),
PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT))
} }
Result.SUCCESS
} }
.onErrorReturn { Result.FAILURE } Result.SUCCESS
// Sadly, the task needs to be synchronous. } catch (e: Exception) {
.toBlocking() Result.FAILURE
.single() }
}
} }
fun NotificationCompat.Builder.update(block: NotificationCompat.Builder.() -> Unit) { fun NotificationCompat.Builder.update(block: NotificationCompat.Builder.() -> Unit) {

View File

@ -5,9 +5,8 @@ import eu.kanade.tachiyomi.data.updater.UpdateChecker
import eu.kanade.tachiyomi.data.updater.UpdateResult import eu.kanade.tachiyomi.data.updater.UpdateResult
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.asObservable import eu.kanade.tachiyomi.network.await
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import rx.Observable
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -23,18 +22,17 @@ class DevRepoUpdateChecker : UpdateChecker() {
Regex("tachiyomi-r(\\d+).apk") Regex("tachiyomi-r(\\d+).apk")
} }
override fun checkForUpdate(): Observable<UpdateResult> { override suspend fun checkForUpdate(): UpdateResult {
return client.newCall(GET(DevRepoRelease.LATEST_URL)).asObservable() val response = client.newCall(GET(DevRepoRelease.LATEST_URL)).await(assertSuccess = false)
.map { response ->
// Get latest repo version number from header in format "Location: tachiyomi-r1512.apk"
val latestVersionNumber: String = versionRegex.find(response.header("Location")!!)!!.groupValues[1]
if (latestVersionNumber.toInt() > BuildConfig.COMMIT_COUNT.toInt()) { // Get latest repo version number from header in format "Location: tachiyomi-r1512.apk"
DevRepoUpdateResult.NewUpdate(DevRepoRelease("v$latestVersionNumber")) val latestVersionNumber: String = versionRegex.find(response.header("Location")!!)!!.groupValues[1]
} else {
DevRepoUpdateResult.NoNewUpdate() return if (latestVersionNumber.toInt() > BuildConfig.COMMIT_COUNT.toInt()) {
} DevRepoUpdateResult.NewUpdate(DevRepoRelease("v$latestVersionNumber"))
} } else {
DevRepoUpdateResult.NoNewUpdate()
}
} }
} }

View File

@ -5,7 +5,6 @@ import retrofit2.Retrofit
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET import retrofit2.http.GET
import rx.Observable
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -28,6 +27,6 @@ interface GithubService {
} }
@GET("/repos/inorichi/tachiyomi/releases/latest") @GET("/repos/inorichi/tachiyomi/releases/latest")
fun getLatestVersion(): Observable<GithubRelease> suspend fun getLatestVersion(): GithubRelease
} }

View File

@ -3,22 +3,21 @@ package eu.kanade.tachiyomi.data.updater.github
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.updater.UpdateChecker import eu.kanade.tachiyomi.data.updater.UpdateChecker
import eu.kanade.tachiyomi.data.updater.UpdateResult import eu.kanade.tachiyomi.data.updater.UpdateResult
import rx.Observable
class GithubUpdateChecker : UpdateChecker() { class GithubUpdateChecker : UpdateChecker() {
private val service: GithubService = GithubService.create() private val service: GithubService = GithubService.create()
override fun checkForUpdate(): Observable<UpdateResult> { override suspend fun checkForUpdate(): UpdateResult {
return service.getLatestVersion().map { release -> val release = service.getLatestVersion()
val newVersion = release.version.replace("[^\\d.]".toRegex(), "")
// Check if latest version is different from current version val newVersion = release.version.replace("[^\\d.]".toRegex(), "")
if (newVersion != BuildConfig.VERSION_NAME) {
GithubUpdateResult.NewUpdate(release) // Check if latest version is different from current version
} else { return if (newVersion != BuildConfig.VERSION_NAME) {
GithubUpdateResult.NoNewUpdate() GithubUpdateResult.NewUpdate(release)
} } else {
GithubUpdateResult.NoNewUpdate()
} }
} }

View File

@ -48,11 +48,11 @@ fun Call.asObservable(): Observable<Response> {
} }
// Based on https://github.com/gildor/kotlin-coroutines-okhttp // Based on https://github.com/gildor/kotlin-coroutines-okhttp
suspend fun Call.await(): Response { suspend fun Call.await(assertSuccess: Boolean = false): Response {
return suspendCancellableCoroutine { continuation -> return suspendCancellableCoroutine { continuation ->
enqueue(object : Callback { enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) { override fun onResponse(call: Call, response: Response) {
if (!response.isSuccessful) { if (assertSuccess && !response.isSuccessful) {
continuation.resumeWithException(Exception("HTTP error ${response.code}")) continuation.resumeWithException(Exception("HTTP error ${response.code}"))
return return
} }

View File

@ -4,7 +4,6 @@ import android.app.Dialog
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.View
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
@ -17,12 +16,11 @@ import eu.kanade.tachiyomi.data.updater.UpdaterJob
import eu.kanade.tachiyomi.data.updater.UpdaterService import eu.kanade.tachiyomi.data.updater.UpdaterService
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.main.ChangelogDialogController import eu.kanade.tachiyomi.ui.main.ChangelogDialogController
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.toTimestampString import eu.kanade.tachiyomi.util.lang.toTimestampString
import eu.kanade.tachiyomi.util.preference.* import eu.kanade.tachiyomi.util.preference.*
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.text.DateFormat import java.text.DateFormat
@ -43,11 +41,6 @@ class SettingsAboutController : SettingsController() {
private val dateFormat: DateFormat = userPreferences.dateFormat().getOrDefault() private val dateFormat: DateFormat = userPreferences.dateFormat().getOrDefault()
/**
* The subscribtion service of the obtained release object
*/
private var releaseSubscription: Subscription? = null
private val isUpdaterEnabled = BuildConfig.INCLUDE_UPDATER private val isUpdaterEnabled = BuildConfig.INCLUDE_UPDATER
override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
@ -118,12 +111,6 @@ class SettingsAboutController : SettingsController() {
} }
} }
override fun onDestroyView(view: View) {
super.onDestroyView(view)
releaseSubscription?.unsubscribe()
releaseSubscription = null
}
/** /**
* Checks version and shows a user prompt if an update is available. * Checks version and shows a user prompt if an update is available.
*/ */
@ -131,11 +118,11 @@ class SettingsAboutController : SettingsController() {
if (activity == null) return if (activity == null) return
activity?.toast(R.string.update_check_look_for_updates) activity?.toast(R.string.update_check_look_for_updates)
releaseSubscription?.unsubscribe()
releaseSubscription = updateChecker.checkForUpdate() launchIO {
.subscribeOn(Schedulers.io()) try {
.observeOn(AndroidSchedulers.mainThread()) val result = updateChecker.checkForUpdate()
.subscribe({ result -> launchUI {
when (result) { when (result) {
is UpdateResult.NewUpdate<*> -> { is UpdateResult.NewUpdate<*> -> {
val body = result.release.info val body = result.release.info
@ -148,10 +135,14 @@ class SettingsAboutController : SettingsController() {
activity?.toast(R.string.update_check_no_new_updates) activity?.toast(R.string.update_check_no_new_updates)
} }
} }
}, { error -> }
} catch (error: Exception) {
launchUI {
activity?.toast(error.message) activity?.toast(error.message)
Timber.e(error) Timber.e(error)
}) }
}
}
} }
class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundle) { class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundle) {