Queue tracking updates when offline (closes #1497)

Co-authored-by: Jays2Kings <Jays2Kings@users.noreply.github.com>
This commit is contained in:
arkon 2021-09-04 16:37:35 -04:00
parent 5ea8d0546e
commit 5ae4621da1
5 changed files with 154 additions and 4 deletions

View file

@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager 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.extension.ExtensionManager
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
@ -23,6 +24,8 @@ class AppModule(val app: Application) : InjektModule {
override fun InjektRegistrar.registerInjectables() { override fun InjektRegistrar.registerInjectables() {
addSingleton(app) addSingleton(app)
addSingletonFactory { Json { ignoreUnknownKeys = true } }
addSingletonFactory { PreferencesHelper(app) } addSingletonFactory { PreferencesHelper(app) }
addSingletonFactory { DatabaseHelper(app) } addSingletonFactory { DatabaseHelper(app) }
@ -41,7 +44,7 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { TrackManager(app) } addSingletonFactory { TrackManager(app) }
addSingletonFactory { Json { ignoreUnknownKeys = true } } addSingletonFactory { DelayedTrackingStore(app) }
// Asynchronously init expensive components for a faster cold start // Asynchronously init expensive components for a faster cold start
ContextCompat.getMainExecutor(app).execute { ContextCompat.getMainExecutor(app).execute {

View file

@ -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,
)
}

View file

@ -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)
}
}
}

View file

@ -11,6 +11,8 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager 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.LocalSource
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.Page 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.getPicturesDir
import eu.kanade.tachiyomi.util.storage.getTempShareDir import eu.kanade.tachiyomi.util.storage.getTempShareDir
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.isOnline
import eu.kanade.tachiyomi.util.updateCoverLastModified import eu.kanade.tachiyomi.util.updateCoverLastModified
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
@ -53,7 +56,8 @@ class ReaderPresenter(
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get(),
private val coverCache: CoverCache = 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>() { ) : BasePresenter<ReaderActivity>() {
/** /**
@ -701,6 +705,7 @@ class ReaderPresenter(
val chapterRead = readerChapter.chapter.chapter_number val chapterRead = readerChapter.chapter.chapter_number
val trackManager = Injekt.get<TrackManager>() val trackManager = Injekt.get<TrackManager>()
val context = Injekt.get<Application>()
launchIO { launchIO {
db.getTracks(manga).executeAsBlocking() db.getTracks(manga).executeAsBlocking()
@ -713,8 +718,13 @@ class ReaderPresenter(
// for a while. The view can still be garbage collected. // for a while. The view can still be garbage collected.
async { async {
runCatching { runCatching {
service.update(track, true) if (context.isOnline()) {
db.insertTrack(track).executeAsBlocking() service.update(track, true)
db.insertTrack(track).executeAsBlocking()
} else {
delayedTrackingStore.addItem(track)
DelayedTrackingUpdateJob.setupTask(context)
}
} }
} }
} else { } else {

View file

@ -15,6 +15,7 @@ import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Color import android.graphics.Color
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.Uri import android.net.Uri
import android.net.wifi.WifiManager import android.net.wifi.WifiManager
import android.os.Build import android.os.Build
@ -365,3 +366,14 @@ fun Context.createReaderThemeContext(): Context {
} }
return this 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)
}