Queue tracking updates when offline (closes #1497)
Co-authored-by: Jays2Kings <Jays2Kings@users.noreply.github.com>
This commit is contained in:
parent
5ea8d0546e
commit
5ae4621da1
5 changed files with 154 additions and 4 deletions
|
@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.data.track.job.DelayedTrackingStore
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
|
@ -23,6 +24,8 @@ class AppModule(val app: Application) : InjektModule {
|
|||
override fun InjektRegistrar.registerInjectables() {
|
||||
addSingleton(app)
|
||||
|
||||
addSingletonFactory { Json { ignoreUnknownKeys = true } }
|
||||
|
||||
addSingletonFactory { PreferencesHelper(app) }
|
||||
|
||||
addSingletonFactory { DatabaseHelper(app) }
|
||||
|
@ -41,7 +44,7 @@ class AppModule(val app: Application) : InjektModule {
|
|||
|
||||
addSingletonFactory { TrackManager(app) }
|
||||
|
||||
addSingletonFactory { Json { ignoreUnknownKeys = true } }
|
||||
addSingletonFactory { DelayedTrackingStore(app) }
|
||||
|
||||
// Asynchronously init expensive components for a faster cold start
|
||||
ContextCompat.getMainExecutor(app).execute {
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package eu.kanade.tachiyomi.data.track.job
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import timber.log.Timber
|
||||
|
||||
class DelayedTrackingStore(context: Context) {
|
||||
|
||||
/**
|
||||
* Preference file where queued tracking updates are stored.
|
||||
*/
|
||||
private val preferences = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
|
||||
|
||||
fun addItem(track: Track) {
|
||||
val trackId = track.id.toString()
|
||||
val (_, lastChapterRead) = preferences.getString(trackId, "0:0.0")!!.split(":")
|
||||
if (track.last_chapter_read > lastChapterRead.toFloat()) {
|
||||
val value = "${track.manga_id}:${track.last_chapter_read}"
|
||||
Timber.i("Queuing track item: $trackId, $value")
|
||||
preferences.edit {
|
||||
putString(trackId, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
preferences.edit {
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun getItems(): List<DelayedTrackingItem> {
|
||||
return (preferences.all as Map<String, String>).entries
|
||||
.map {
|
||||
val (mangaId, lastChapterRead) = it.value.split(":")
|
||||
DelayedTrackingItem(
|
||||
trackId = it.key.toLong(),
|
||||
mangaId = mangaId.toLong(),
|
||||
lastChapterRead = lastChapterRead.toFloat(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class DelayedTrackingItem(
|
||||
val trackId: Long,
|
||||
val mangaId: Long,
|
||||
val lastChapterRead: Float,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package eu.kanade.tachiyomi.data.track.job
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val db = Injekt.get<DatabaseHelper>()
|
||||
val trackManager = Injekt.get<TrackManager>()
|
||||
val delayedTrackingStore = Injekt.get<DelayedTrackingStore>()
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
val tracks = delayedTrackingStore.getItems().mapNotNull {
|
||||
val manga = db.getManga(it.mangaId).executeAsBlocking() ?: return@withContext
|
||||
db.getTracks(manga).executeAsBlocking()
|
||||
.find { track -> track.id == it.trackId }
|
||||
?.also { track ->
|
||||
track.last_chapter_read = it.lastChapterRead
|
||||
}
|
||||
}
|
||||
|
||||
tracks.forEach { track ->
|
||||
try {
|
||||
val service = trackManager.getService(track.sync_id)
|
||||
if (service != null && service.isLogged) {
|
||||
service.update(track, true)
|
||||
db.insertTrack(track).executeAsBlocking()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
|
||||
delayedTrackingStore.clear()
|
||||
}
|
||||
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "DelayedTrackingUpdate"
|
||||
|
||||
fun setupTask(context: Context) {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
|
||||
val request = OneTimeWorkRequestBuilder<DelayedTrackingUpdateJob>()
|
||||
.setConstraints(constraints)
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 20, TimeUnit.SECONDS)
|
||||
.addTag(TAG)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context)
|
||||
.enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, request)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,8 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.data.track.job.DelayedTrackingStore
|
||||
import eu.kanade.tachiyomi.data.track.job.DelayedTrackingUpdateJob
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
|
@ -31,6 +33,7 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
|
|||
import eu.kanade.tachiyomi.util.storage.getPicturesDir
|
||||
import eu.kanade.tachiyomi.util.storage.getTempShareDir
|
||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||
import eu.kanade.tachiyomi.util.system.isOnline
|
||||
import eu.kanade.tachiyomi.util.updateCoverLastModified
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
|
@ -53,7 +56,8 @@ class ReaderPresenter(
|
|||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
private val downloadManager: DownloadManager = Injekt.get(),
|
||||
private val coverCache: CoverCache = Injekt.get(),
|
||||
private val preferences: PreferencesHelper = Injekt.get()
|
||||
private val preferences: PreferencesHelper = Injekt.get(),
|
||||
private val delayedTrackingStore: DelayedTrackingStore = Injekt.get(),
|
||||
) : BasePresenter<ReaderActivity>() {
|
||||
|
||||
/**
|
||||
|
@ -701,6 +705,7 @@ class ReaderPresenter(
|
|||
val chapterRead = readerChapter.chapter.chapter_number
|
||||
|
||||
val trackManager = Injekt.get<TrackManager>()
|
||||
val context = Injekt.get<Application>()
|
||||
|
||||
launchIO {
|
||||
db.getTracks(manga).executeAsBlocking()
|
||||
|
@ -713,8 +718,13 @@ class ReaderPresenter(
|
|||
// for a while. The view can still be garbage collected.
|
||||
async {
|
||||
runCatching {
|
||||
if (context.isOnline()) {
|
||||
service.update(track, true)
|
||||
db.insertTrack(track).executeAsBlocking()
|
||||
} else {
|
||||
delayedTrackingStore.addItem(track)
|
||||
DelayedTrackingUpdateJob.setupTask(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -15,6 +15,7 @@ import android.content.res.Configuration
|
|||
import android.content.res.Resources
|
||||
import android.graphics.Color
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.Uri
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.Build
|
||||
|
@ -365,3 +366,14 @@ fun Context.createReaderThemeContext(): Context {
|
|||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun Context.isOnline(): Boolean {
|
||||
val networkCapabilities = connectivityManager.activeNetwork ?: return false
|
||||
val actNw = connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
|
||||
val maxTransport = when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 -> NetworkCapabilities.TRANSPORT_LOWPAN
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> NetworkCapabilities.TRANSPORT_WIFI_AWARE
|
||||
else -> NetworkCapabilities.TRANSPORT_VPN
|
||||
}
|
||||
return (NetworkCapabilities.TRANSPORT_CELLULAR..maxTransport).any(actNw::hasTransport)
|
||||
}
|
||||
|
|
Reference in a new issue