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.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 {
|
||||||
|
|
|
@ -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.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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
Reference in a new issue