Move cache pruning to a WorkManager worker (#3649)
- Extend what was `NotificationWorkerFactory` to `WorkerFactory`. This can construct arbitrary Workers as long as they provide their own Factory for construction. The per-Worker factory contains any injected components just for that worker type, keeping `WorkerFactory` clean. - Move `NotificationWorkerFactory` to the new model. - Implement `PruneCacheWorker`, and remove the code from `CachedTimelineViewModel`. - Create the periodic worker in `TuskyApplication`, ensuring that the database is only pruned when the device is idle.
This commit is contained in:
parent
85b7caa887
commit
4025ab35ff
11 changed files with 227 additions and 71 deletions
|
@ -18,15 +18,19 @@ package com.keylesspalace.tusky
|
|||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import autodispose2.AutoDisposePlugins
|
||||
import com.keylesspalace.tusky.components.notifications.NotificationWorkerFactory
|
||||
import com.keylesspalace.tusky.di.AppInjector
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.settings.SCHEMA_VERSION
|
||||
import com.keylesspalace.tusky.util.APP_THEME_DEFAULT
|
||||
import com.keylesspalace.tusky.util.LocaleManager
|
||||
import com.keylesspalace.tusky.util.setAppNightMode
|
||||
import com.keylesspalace.tusky.worker.PruneCacheWorker
|
||||
import com.keylesspalace.tusky.worker.WorkerFactory
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import de.c1710.filemojicompat_defaults.DefaultEmojiPackList
|
||||
|
@ -35,6 +39,7 @@ import de.c1710.filemojicompat_ui.helpers.EmojiPreference
|
|||
import io.reactivex.rxjava3.plugins.RxJavaPlugins
|
||||
import org.conscrypt.Conscrypt
|
||||
import java.security.Security
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
class TuskyApplication : Application(), HasAndroidInjector {
|
||||
|
@ -42,7 +47,7 @@ class TuskyApplication : Application(), HasAndroidInjector {
|
|||
lateinit var androidInjector: DispatchingAndroidInjector<Any>
|
||||
|
||||
@Inject
|
||||
lateinit var notificationWorkerFactory: NotificationWorkerFactory
|
||||
lateinit var workerFactory: WorkerFactory
|
||||
|
||||
@Inject
|
||||
lateinit var localeManager: LocaleManager
|
||||
|
@ -93,9 +98,19 @@ class TuskyApplication : Application(), HasAndroidInjector {
|
|||
WorkManager.initialize(
|
||||
this,
|
||||
androidx.work.Configuration.Builder()
|
||||
.setWorkerFactory(notificationWorkerFactory)
|
||||
.setWorkerFactory(workerFactory)
|
||||
.build()
|
||||
)
|
||||
|
||||
// Prune the database every ~ 12 hours when the device is idle.
|
||||
val pruneCacheWorker = PeriodicWorkRequestBuilder<PruneCacheWorker>(12, TimeUnit.HOURS)
|
||||
.setConstraints(Constraints.Builder().setRequiresDeviceIdle(true).build())
|
||||
.build()
|
||||
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
|
||||
PruneCacheWorker.PERIODIC_WORK_TAG,
|
||||
ExistingPeriodicWorkPolicy.KEEP,
|
||||
pruneCacheWorker
|
||||
)
|
||||
}
|
||||
|
||||
override fun androidInjector() = androidInjector
|
||||
|
|
|
@ -63,6 +63,7 @@ import com.keylesspalace.tusky.entity.Status;
|
|||
import com.keylesspalace.tusky.receiver.SendStatusBroadcastReceiver;
|
||||
import com.keylesspalace.tusky.util.StringUtils;
|
||||
import com.keylesspalace.tusky.viewdata.PollViewDataKt;
|
||||
import com.keylesspalace.tusky.worker.NotificationWorker;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
/* Copyright 2020 Tusky Contributors
|
||||
*
|
||||
* This file is part of Tusky.
|
||||
*
|
||||
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* Lesser General Public License as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along with Tusky. If
|
||||
* not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package com.keylesspalace.tusky.components.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.ListenableWorker
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerFactory
|
||||
import androidx.work.WorkerParameters
|
||||
import javax.inject.Inject
|
||||
|
||||
class NotificationWorker(
|
||||
context: Context,
|
||||
params: WorkerParameters,
|
||||
private val notificationsFetcher: NotificationFetcher
|
||||
) : Worker(context, params) {
|
||||
|
||||
override fun doWork(): Result {
|
||||
notificationsFetcher.fetchAndShow()
|
||||
return Result.success()
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationWorkerFactory @Inject constructor(
|
||||
private val notificationsFetcher: NotificationFetcher
|
||||
) : WorkerFactory() {
|
||||
|
||||
override fun createWorker(
|
||||
appContext: Context,
|
||||
workerClassName: String,
|
||||
workerParameters: WorkerParameters
|
||||
): ListenableWorker? {
|
||||
if (workerClassName == NotificationWorker::class.java.name) {
|
||||
return NotificationWorker(appContext, workerParameters, notificationsFetcher)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -50,14 +50,11 @@ import com.keylesspalace.tusky.util.EmptyPagingSource
|
|||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asExecutor
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import retrofit2.HttpException
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
|
||||
/**
|
||||
* TimelineViewModel that caches all statuses in a local database
|
||||
|
@ -107,16 +104,6 @@ class CachedTimelineViewModel @Inject constructor(
|
|||
.flowOn(Dispatchers.Default)
|
||||
.cachedIn(viewModelScope)
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
delay(5.toDuration(DurationUnit.SECONDS)) // delay so the db is not locked during initial ui refresh
|
||||
accountManager.activeAccount?.id?.let { accountId ->
|
||||
db.timelineDao().cleanup(accountId, MAX_STATUSES_IN_CACHE)
|
||||
db.timelineDao().cleanupAccounts(accountId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun updatePoll(newPoll: Poll, status: StatusViewData.Concrete) {
|
||||
// handled by CacheUpdater
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@ import javax.inject.Singleton
|
|||
ActivitiesModule::class,
|
||||
ServicesModule::class,
|
||||
BroadcastReceiverModule::class,
|
||||
ViewModelModule::class
|
||||
ViewModelModule::class,
|
||||
WorkerModule::class
|
||||
]
|
||||
)
|
||||
interface AppComponent {
|
||||
|
|
45
app/src/main/java/com/keylesspalace/tusky/di/WorkerModule.kt
Normal file
45
app/src/main/java/com/keylesspalace/tusky/di/WorkerModule.kt
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.di
|
||||
|
||||
import androidx.work.ListenableWorker
|
||||
import com.keylesspalace.tusky.worker.ChildWorkerFactory
|
||||
import com.keylesspalace.tusky.worker.NotificationWorker
|
||||
import com.keylesspalace.tusky.worker.PruneCacheWorker
|
||||
import dagger.Binds
|
||||
import dagger.MapKey
|
||||
import dagger.Module
|
||||
import dagger.multibindings.IntoMap
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MapKey
|
||||
annotation class WorkerKey(val value: KClass<out ListenableWorker>)
|
||||
|
||||
@Module
|
||||
abstract class WorkerModule {
|
||||
@Binds
|
||||
@IntoMap
|
||||
@WorkerKey(NotificationWorker::class)
|
||||
internal abstract fun bindNotificationWorkerFactory(worker: NotificationWorker.Factory): ChildWorkerFactory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@WorkerKey(PruneCacheWorker::class)
|
||||
internal abstract fun bindPruneCacheWorkerFactory(worker: PruneCacheWorker.Factory): ChildWorkerFactory
|
||||
}
|
|
@ -20,11 +20,11 @@ import android.content.Intent
|
|||
import android.util.Log
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import com.keylesspalace.tusky.components.notifications.NotificationWorker
|
||||
import com.keylesspalace.tusky.components.notifications.registerUnifiedPushEndpoint
|
||||
import com.keylesspalace.tusky.components.notifications.unregisterUnifiedPushEndpoint
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.worker.NotificationWorker
|
||||
import dagger.android.AndroidInjection
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.worker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.keylesspalace.tusky.components.notifications.NotificationFetcher
|
||||
import javax.inject.Inject
|
||||
|
||||
/** Fetch and show new notifications. */
|
||||
class NotificationWorker(
|
||||
appContext: Context,
|
||||
params: WorkerParameters,
|
||||
private val notificationsFetcher: NotificationFetcher
|
||||
) : Worker(appContext, params) {
|
||||
override fun doWork(): Result {
|
||||
notificationsFetcher.fetchAndShow()
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
class Factory @Inject constructor(
|
||||
private val notificationsFetcher: NotificationFetcher
|
||||
) : ChildWorkerFactory {
|
||||
override fun createWorker(appContext: Context, params: WorkerParameters): Worker {
|
||||
return NotificationWorker(appContext, params, notificationsFetcher)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.worker
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ListenableWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.db.AppDatabase
|
||||
import javax.inject.Inject
|
||||
|
||||
/** Prune the database cache of old statuses. */
|
||||
class PruneCacheWorker(
|
||||
appContext: Context,
|
||||
workerParams: WorkerParameters,
|
||||
private val appDatabase: AppDatabase,
|
||||
private val accountManager: AccountManager
|
||||
) : CoroutineWorker(appContext, workerParams) {
|
||||
override suspend fun doWork(): Result {
|
||||
for (account in accountManager.accounts) {
|
||||
Log.d(TAG, "Pruning database using account ID: ${account.id}")
|
||||
appDatabase.timelineDao().cleanup(account.id, MAX_STATUSES_IN_CACHE)
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "PruneCacheWorker"
|
||||
private const val MAX_STATUSES_IN_CACHE = 1000
|
||||
const val PERIODIC_WORK_TAG = "PruneCacheWorker_periodic"
|
||||
}
|
||||
|
||||
class Factory @Inject constructor(
|
||||
private val appDatabase: AppDatabase,
|
||||
private val accountManager: AccountManager
|
||||
) : ChildWorkerFactory {
|
||||
override fun createWorker(appContext: Context, params: WorkerParameters): ListenableWorker {
|
||||
return PruneCacheWorker(appContext, params, appDatabase, accountManager)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.worker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.ListenableWorker
|
||||
import androidx.work.WorkerFactory
|
||||
import androidx.work.WorkerParameters
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
/**
|
||||
* Workers implement this and are added to the map in [com.keylesspalace.tusky.di.WorkerModule]
|
||||
* so they can be created by [WorkerFactory.createWorker].
|
||||
*/
|
||||
interface ChildWorkerFactory {
|
||||
/** Create a new instance of the given worker. */
|
||||
fun createWorker(appContext: Context, params: WorkerParameters): ListenableWorker
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates workers, delegating to each worker's [ChildWorkerFactory.createWorker] to do the
|
||||
* creation.
|
||||
*
|
||||
* @see [com.keylesspalace.tusky.components.notifications.NotificationWorker]
|
||||
*/
|
||||
class WorkerFactory @Inject constructor(
|
||||
val workerFactories: Map<Class<out ListenableWorker>, @JvmSuppressWildcards Provider<ChildWorkerFactory>>
|
||||
) : WorkerFactory() {
|
||||
override fun createWorker(
|
||||
appContext: Context,
|
||||
workerClassName: String,
|
||||
workerParameters: WorkerParameters
|
||||
): ListenableWorker? {
|
||||
val key = Class.forName(workerClassName)
|
||||
workerFactories[key]?.let {
|
||||
return it.get().createWorker(appContext, workerParameters)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -92,7 +92,7 @@ androidx-sharetarget = { module = "androidx.sharetarget:sharetarget", version.re
|
|||
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "androidx-swiperefresh-layout" }
|
||||
androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-junit" }
|
||||
androidx-viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "androidx-viewpager2" }
|
||||
androidx-work-runtime = { module = "androidx.work:work-runtime", version.ref = "androidx-work" }
|
||||
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-work" }
|
||||
androidx-work-testing = { module = "androidx.work:work-testing", version.ref = "androidx-work" }
|
||||
autodispose-android-lifecycle = { module = "com.uber.autodispose2:autodispose-androidx-lifecycle", version.ref = "autodispose" }
|
||||
autodispose-core = { module = "com.uber.autodispose2:autodispose", version.ref = "autodispose" }
|
||||
|
@ -146,7 +146,7 @@ androidx = ["androidx-core-ktx", "androidx-appcompat", "androidx-fragment-ktx",
|
|||
"androidx-recyclerview", "androidx-exifinterface", "androidx-cardview", "androidx-preference-ktx", "androidx-sharetarget",
|
||||
"androidx-emoji2-core", "androidx-emoji2-views-core", "androidx-emoji2-view-helper", "androidx-lifecycle-viewmodel-ktx",
|
||||
"androidx-lifecycle-livedata-ktx", "androidx-lifecycle-common-java8", "androidx-lifecycle-reactivestreams-ktx",
|
||||
"androidx-constraintlayout", "androidx-paging-runtime-ktx", "androidx-viewpager2", "androidx-work-runtime",
|
||||
"androidx-constraintlayout", "androidx-paging-runtime-ktx", "androidx-viewpager2", "androidx-work-runtime-ktx",
|
||||
"androidx-core-splashscreen", "androidx-activity"]
|
||||
autodispose = ["autodispose-core", "autodispose-android-lifecycle"]
|
||||
dagger = ["dagger-core", "dagger-android-core", "dagger-android-support"]
|
||||
|
|
Loading…
Reference in a new issue