Convert EH Gallery Updater from a JobService to WorkManager
This commit is contained in:
parent
0d8af29eeb
commit
e9e3340c08
@ -34,7 +34,7 @@ android {
|
|||||||
minSdkVersion(AndroidConfig.minSdk)
|
minSdkVersion(AndroidConfig.minSdk)
|
||||||
targetSdkVersion(AndroidConfig.targetSdk)
|
targetSdkVersion(AndroidConfig.targetSdk)
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
versionCode = 20
|
versionCode = 21
|
||||||
versionName = "1.7.0"
|
versionName = "1.7.0"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
|
@ -189,10 +189,6 @@
|
|||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<!-- EH -->
|
<!-- EH -->
|
||||||
<service
|
|
||||||
android:name="exh.eh.EHentaiUpdateWorker"
|
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
|
||||||
android:exported="true" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name="exh.ui.intercept.InterceptActivity"
|
android:name="exh.ui.intercept.InterceptActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
@ -30,6 +30,7 @@ import eu.kanade.tachiyomi.ui.library.LibrarySort
|
|||||||
import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
|
import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
|
||||||
import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
|
import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||||
|
import exh.eh.EHentaiUpdateWorker
|
||||||
import exh.log.xLogE
|
import exh.log.xLogE
|
||||||
import exh.log.xLogW
|
import exh.log.xLogW
|
||||||
import exh.merged.sql.models.MergedMangaReference
|
import exh.merged.sql.models.MergedMangaReference
|
||||||
@ -331,6 +332,10 @@ object EXHMigrations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (oldVersion under 21) {
|
||||||
|
// Setup EH updater task after migrating to WorkManager
|
||||||
|
EHentaiUpdateWorker.scheduleBackground(context)
|
||||||
|
}
|
||||||
|
|
||||||
// if (oldVersion under 1) { } (1 is current release version)
|
// if (oldVersion under 1) { } (1 is current release version)
|
||||||
// do stuff here when releasing changed crap
|
// do stuff here when releasing changed crap
|
||||||
|
@ -192,8 +192,8 @@ object DebugFunctions {
|
|||||||
|
|
||||||
fun convertAllExhentaiGalleriesToEhentai() = convertSources(EXH_SOURCE_ID, EH_SOURCE_ID)
|
fun convertAllExhentaiGalleriesToEhentai() = convertSources(EXH_SOURCE_ID, EH_SOURCE_ID)
|
||||||
|
|
||||||
fun testLaunchEhentaiBackgroundUpdater(): String {
|
fun testLaunchEhentaiBackgroundUpdater() {
|
||||||
return EHentaiUpdateWorker.launchBackgroundTest(app)
|
EHentaiUpdateWorker.launchBackgroundTest(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rescheduleEhentaiBackgroundUpdater() {
|
fun rescheduleEhentaiBackgroundUpdater() {
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
package exh.eh
|
package exh.eh
|
||||||
|
|
||||||
import android.app.job.JobInfo
|
|
||||||
import android.app.job.JobParameters
|
|
||||||
import android.app.job.JobScheduler
|
|
||||||
import android.app.job.JobService
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import androidx.work.Constraints
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.ExistingPeriodicWorkPolicy
|
||||||
|
import androidx.work.NetworkType
|
||||||
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
|
import androidx.work.PeriodicWorkRequestBuilder
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
import com.elvishew.xlog.Logger
|
import com.elvishew.xlog.Logger
|
||||||
import com.elvishew.xlog.XLog
|
import com.elvishew.xlog.XLog
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
@ -14,7 +16,9 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
|
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateNotifier
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateNotifier
|
||||||
|
import eu.kanade.tachiyomi.data.preference.CHARGING
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.data.preference.UNMETERED_NETWORK
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.toSChapter
|
import eu.kanade.tachiyomi.source.model.toSChapter
|
||||||
import eu.kanade.tachiyomi.source.model.toSManga
|
import eu.kanade.tachiyomi.source.model.toSManga
|
||||||
@ -30,103 +34,35 @@ import exh.source.isEhBasedManga
|
|||||||
import exh.util.cancellable
|
import exh.util.cancellable
|
||||||
import exh.util.days
|
import exh.util.days
|
||||||
import exh.util.executeOnIO
|
import exh.util.executeOnIO
|
||||||
import exh.util.hours
|
|
||||||
import exh.util.jobScheduler
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
|
||||||
import kotlinx.coroutines.flow.asFlow
|
import kotlinx.coroutines.flow.asFlow
|
||||||
import kotlinx.coroutines.flow.mapNotNull
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
import kotlinx.coroutines.flow.single
|
import kotlinx.coroutines.flow.single
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.util.ArrayList
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class EHentaiUpdateWorker : JobService() {
|
|
||||||
private val scope = CoroutineScope(Dispatchers.Default + Job())
|
|
||||||
|
|
||||||
|
class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerParameters) :
|
||||||
|
CoroutineWorker(context, workerParams) {
|
||||||
private val db: DatabaseHelper by injectLazy()
|
private val db: DatabaseHelper by injectLazy()
|
||||||
private val prefs: PreferencesHelper by injectLazy()
|
private val prefs: PreferencesHelper by injectLazy()
|
||||||
private val sourceManager: SourceManager by injectLazy()
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
private val updateHelper: EHentaiUpdateHelper by injectLazy()
|
private val updateHelper: EHentaiUpdateHelper by injectLazy()
|
||||||
private val logger: Logger = xLog()
|
private val logger: Logger = xLog()
|
||||||
|
|
||||||
private val updateNotifier by lazy { LibraryUpdateNotifier(this) }
|
private val updateNotifier by lazy { LibraryUpdateNotifier(context) }
|
||||||
|
|
||||||
/**
|
override suspend fun doWork(): Result {
|
||||||
* This method is called if the system has determined that you must stop execution of your job
|
return try {
|
||||||
* even before you've had a chance to call [.jobFinished].
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* This will happen if the requirements specified at schedule time are no longer met. For
|
|
||||||
* example you may have requested WiFi with
|
|
||||||
* [android.app.job.JobInfo.Builder.setRequiredNetworkType], yet while your
|
|
||||||
* job was executing the user toggled WiFi. Another example is if you had specified
|
|
||||||
* [android.app.job.JobInfo.Builder.setRequiresDeviceIdle], and the phone left its
|
|
||||||
* idle maintenance window. You are solely responsible for the behavior of your application
|
|
||||||
* upon receipt of this message; your app will likely start to misbehave if you ignore it.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Once this method returns, the system releases the wakelock that it is holding on
|
|
||||||
* behalf of the job.
|
|
||||||
*
|
|
||||||
* @param params The parameters identifying this job, as supplied to
|
|
||||||
* the job in the [.onStartJob] callback.
|
|
||||||
* @return `true` to indicate to the JobManager whether you'd like to reschedule
|
|
||||||
* this job based on the retry criteria provided at job creation-time; or `false`
|
|
||||||
* to end the job entirely. Regardless of the value returned, your job must stop executing.
|
|
||||||
*/
|
|
||||||
override fun onStopJob(params: JobParameters?): Boolean {
|
|
||||||
runBlocking { scope.coroutineContext[Job]?.cancelAndJoin() }
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to indicate that the job has begun executing. Override this method with the
|
|
||||||
* logic for your job. Like all other component lifecycle callbacks, this method executes
|
|
||||||
* on your application's main thread.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Return `true` from this method if your job needs to continue running. If you
|
|
||||||
* do this, the job remains active until you call
|
|
||||||
* [.jobFinished] to tell the system that it has completed
|
|
||||||
* its work, or until the job's required constraints are no longer satisfied. For
|
|
||||||
* example, if the job was scheduled using
|
|
||||||
* [setRequiresCharging(true)][JobInfo.Builder.setRequiresCharging],
|
|
||||||
* it will be immediately halted by the system if the user unplugs the device from power,
|
|
||||||
* the job's [.onStopJob] callback will be invoked, and the app
|
|
||||||
* will be expected to shut down all ongoing work connected with that job.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* The system holds a wakelock on behalf of your app as long as your job is executing.
|
|
||||||
* This wakelock is acquired before this method is invoked, and is not released until either
|
|
||||||
* you call [.jobFinished], or after the system invokes
|
|
||||||
* [.onStopJob] to notify your job that it is being shut down
|
|
||||||
* prematurely.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Returning `false` from this method means your job is already finished. The
|
|
||||||
* system's wakelock for the job will be released, and [.onStopJob]
|
|
||||||
* will not be invoked.
|
|
||||||
*
|
|
||||||
* @param params Parameters specifying info about this job, including the optional
|
|
||||||
* extras configured with [ This object serves to identify this specific running job instance when calling][JobInfo.Builder.setExtras]
|
|
||||||
*/
|
|
||||||
override fun onStartJob(params: JobParameters): Boolean {
|
|
||||||
scope.launch {
|
|
||||||
startUpdating()
|
startUpdating()
|
||||||
logger.d("Update job completed!")
|
logger.d("Update job completed!")
|
||||||
jobFinished(params, false)
|
Result.success()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun startUpdating() {
|
private suspend fun startUpdating() {
|
||||||
@ -165,7 +101,7 @@ class EHentaiUpdateWorker : JobService() {
|
|||||||
|
|
||||||
var failuresThisIteration = 0
|
var failuresThisIteration = 0
|
||||||
var updatedThisIteration = 0
|
var updatedThisIteration = 0
|
||||||
val updatedManga = ArrayList<Pair<Manga, Array<Chapter>>>()
|
val updatedManga = mutableListOf<Pair<Manga, Array<Chapter>>>()
|
||||||
val modifiedThisIteration = mutableSetOf<Long>()
|
val modifiedThisIteration = mutableSetOf<Long>()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -290,86 +226,50 @@ class EHentaiUpdateWorker : JobService() {
|
|||||||
|
|
||||||
private val MIN_BACKGROUND_UPDATE_FREQ = 1.days.inWholeMilliseconds
|
private val MIN_BACKGROUND_UPDATE_FREQ = 1.days.inWholeMilliseconds
|
||||||
|
|
||||||
private const val JOB_ID_UPDATE_BACKGROUND = 700000
|
private const val TAG = "EHBackgroundUpdater"
|
||||||
private const val JOB_ID_UPDATE_BACKGROUND_TEST = 700001
|
|
||||||
|
|
||||||
private val logger by lazy { XLog.tag("EHUpdaterScheduler") }
|
private val logger by lazy { XLog.tag("EHUpdaterScheduler") }
|
||||||
|
|
||||||
private fun Context.componentName(): ComponentName =
|
fun launchBackgroundTest(context: Context) {
|
||||||
ComponentName(this, EHentaiUpdateWorker::class.java)
|
WorkManager.getInstance(context).enqueue(OneTimeWorkRequestBuilder<EHentaiUpdateWorker>().build())
|
||||||
|
}
|
||||||
private fun Context.baseBackgroundJobInfo(isTest: Boolean): JobInfo.Builder =
|
|
||||||
JobInfo.Builder(
|
|
||||||
if (isTest) JOB_ID_UPDATE_BACKGROUND_TEST
|
|
||||||
else JOB_ID_UPDATE_BACKGROUND,
|
|
||||||
componentName()
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun Context.periodicBackgroundJobInfo(
|
|
||||||
period: Long,
|
|
||||||
requireCharging: Boolean,
|
|
||||||
requireUnmetered: Boolean
|
|
||||||
): JobInfo = baseBackgroundJobInfo(false)
|
|
||||||
.setPeriodic(period)
|
|
||||||
.setPersisted(true)
|
|
||||||
.setRequiredNetworkType(
|
|
||||||
if (requireUnmetered) JobInfo.NETWORK_TYPE_UNMETERED
|
|
||||||
else JobInfo.NETWORK_TYPE_ANY
|
|
||||||
)
|
|
||||||
.apply {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
setRequiresBatteryNotLow(true)
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
||||||
setEstimatedNetworkBytes(
|
|
||||||
15000L * UPDATES_PER_ITERATION,
|
|
||||||
1000L * UPDATES_PER_ITERATION
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.setRequiresCharging(requireCharging)
|
|
||||||
// .setRequiresDeviceIdle(true) Job never seems to run with this
|
|
||||||
.build()
|
|
||||||
|
|
||||||
private fun Context.testBackgroundJobInfo(): JobInfo = baseBackgroundJobInfo(true)
|
|
||||||
.setOverrideDeadline(1)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
fun launchBackgroundTest(context: Context): String =
|
|
||||||
if (context.jobScheduler.schedule(context.testBackgroundJobInfo()) == JobScheduler.RESULT_FAILURE) {
|
|
||||||
logger.e("Failed to schedule background test job!")
|
|
||||||
"Failed"
|
|
||||||
} else {
|
|
||||||
logger.d("Successfully scheduled background test job!")
|
|
||||||
"Success"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun scheduleBackground(context: Context, prefInterval: Int? = null) {
|
fun scheduleBackground(context: Context, prefInterval: Int? = null) {
|
||||||
cancelBackground(context)
|
|
||||||
|
|
||||||
val preferences = Injekt.get<PreferencesHelper>()
|
val preferences = Injekt.get<PreferencesHelper>()
|
||||||
val duration = prefInterval ?: preferences.exhAutoUpdateFrequency().get()
|
val interval = prefInterval ?: preferences.exhAutoUpdateFrequency().get()
|
||||||
if (duration > 0) {
|
if (interval > 0) {
|
||||||
val restrictions = preferences.exhAutoUpdateRequirements().get()
|
val restrictions = preferences.exhAutoUpdateRequirements().get()
|
||||||
val acRestriction = "ac" in restrictions
|
val acRestriction = CHARGING in restrictions
|
||||||
val wifiRestriction = "wifi" in restrictions
|
val wifiRestriction = if (UNMETERED_NETWORK in restrictions) {
|
||||||
|
NetworkType.UNMETERED
|
||||||
val jobInfo = context.periodicBackgroundJobInfo(
|
|
||||||
duration.hours.inWholeMilliseconds,
|
|
||||||
acRestriction,
|
|
||||||
wifiRestriction
|
|
||||||
)
|
|
||||||
|
|
||||||
if (context.jobScheduler.schedule(jobInfo) == JobScheduler.RESULT_FAILURE) {
|
|
||||||
logger.e("Failed to schedule background update job!")
|
|
||||||
} else {
|
} else {
|
||||||
logger.d("Successfully scheduled background update job!")
|
NetworkType.CONNECTED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val constraints = Constraints.Builder()
|
||||||
|
.setRequiredNetworkType(wifiRestriction)
|
||||||
|
.setRequiresCharging(acRestriction)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val request = PeriodicWorkRequestBuilder<EHentaiUpdateWorker>(
|
||||||
|
interval.toLong(),
|
||||||
|
TimeUnit.HOURS,
|
||||||
|
10,
|
||||||
|
TimeUnit.MINUTES
|
||||||
|
)
|
||||||
|
.addTag(TAG)
|
||||||
|
.setConstraints(constraints)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
WorkManager.getInstance(context).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request)
|
||||||
|
logger.d("Successfully scheduled background update job!")
|
||||||
|
} else {
|
||||||
|
cancelBackground(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelBackground(context: Context) {
|
fun cancelBackground(context: Context) {
|
||||||
context.jobScheduler.cancel(JOB_ID_UPDATE_BACKGROUND)
|
WorkManager.getInstance(context).cancelAllWorkByTag(TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user