Help with favorites sync db locking

This commit is contained in:
Jobobby04 2021-06-10 17:20:29 -04:00
parent 963b85f756
commit 9e80f47e9f
4 changed files with 48 additions and 28 deletions

View File

@ -16,7 +16,7 @@ import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import exh.log.xLogStack import exh.log.xLogStack
import exh.source.getMainSource import exh.source.getMainSource
import exh.util.executeOnIO import exh.util.maybeRunBlocking
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
@ -53,7 +53,8 @@ class GalleryAdder {
url: String, url: String,
fav: Boolean = false, fav: Boolean = false,
forceSource: UrlImportableSource? = null, forceSource: UrlImportableSource? = null,
throttleFunc: suspend () -> Unit = {} throttleFunc: suspend () -> Unit = {},
protectTrans: Boolean = false
): GalleryAddEvent { ): GalleryAddEvent {
logger.d(context.getString(R.string.gallery_adder_importing_manga, url, fav.toString(), forceSource)) logger.d(context.getString(R.string.gallery_adder_importing_manga, url, fav.toString(), forceSource))
try { try {
@ -118,7 +119,7 @@ class GalleryAdder {
} ?: return GalleryAddEvent.Fail.UnknownType(url, context) } ?: return GalleryAddEvent.Fail.UnknownType(url, context)
// Use manga in DB if possible, otherwise, make a new manga // Use manga in DB if possible, otherwise, make a new manga
val manga = db.getManga(cleanedMangaUrl, source.id).executeOnIO() val manga = db.getManga(cleanedMangaUrl, source.id).executeAsBlocking()
?: Manga.create(source.id).apply { ?: Manga.create(source.id).apply {
this.url = cleanedMangaUrl this.url = cleanedMangaUrl
title = realMangaUrl title = realMangaUrl
@ -146,6 +147,7 @@ class GalleryAdder {
// Fetch and copy chapters // Fetch and copy chapters
try { try {
maybeRunBlocking(protectTrans) {
val chapterList = if (source is EHentai) { val chapterList = if (source is EHentai) {
source.getChapterList(manga.toMangaInfo(), throttleFunc) source.getChapterList(manga.toMangaInfo(), throttleFunc)
} else { } else {
@ -155,13 +157,14 @@ class GalleryAdder {
if (chapterList.isNotEmpty()) { if (chapterList.isNotEmpty()) {
syncChaptersWithSource(db, chapterList, manga, source) syncChaptersWithSource(db, chapterList, manga, source)
} }
}
} catch (e: Exception) { } catch (e: Exception) {
logger.w(context.getString(R.string.gallery_adder_chapter_fetch_error, manga.title), e) logger.w(context.getString(R.string.gallery_adder_chapter_fetch_error, manga.title), e)
return GalleryAddEvent.Fail.Error(url, context.getString(R.string.gallery_adder_chapter_fetch_error, url)) return GalleryAddEvent.Fail.Error(url, context.getString(R.string.gallery_adder_chapter_fetch_error, url))
} }
return if (cleanedChapterUrl != null) { return if (cleanedChapterUrl != null) {
val chapter = db.getChapter(cleanedChapterUrl, manga.id!!).executeOnIO() val chapter = db.getChapter(cleanedChapterUrl, manga.id!!).executeAsBlocking()
if (chapter != null) { if (chapter != null) {
GalleryAddEvent.Success(url, manga, context, chapter) GalleryAddEvent.Success(url, manga, context, chapter)
} else { } else {

View File

@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.system.powerManager import eu.kanade.tachiyomi.util.system.powerManager
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import exh.GalleryAddEvent import exh.GalleryAddEvent
@ -24,7 +25,6 @@ import exh.log.xLog
import exh.source.EH_SOURCE_ID import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID import exh.source.EXH_SOURCE_ID
import exh.source.isEhBasedManga import exh.source.isEhBasedManga
import exh.util.executeOnIO
import exh.util.ignore import exh.util.ignore
import exh.util.trans import exh.util.trans
import exh.util.wifiManager import exh.util.wifiManager
@ -85,13 +85,13 @@ class FavoritesSyncHelper(val context: Context) {
// Validate library state // Validate library state
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_verifying_library), context = context) status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_verifying_library), context = context)
val libraryManga = db.getLibraryMangas().executeOnIO() val libraryManga = db.getLibraryMangas().executeAsBlocking()
val seenManga = HashSet<Long>(libraryManga.size) val seenManga = HashSet<Long>(libraryManga.size)
libraryManga.forEach { libraryManga.forEach {
if (!it.isEhBasedManga()) return@forEach if (!it.isEhBasedManga()) return@forEach
if (it.id in seenManga) { if (it.id in seenManga) {
val inCategories = db.getCategoriesForManga(it).executeOnIO() val inCategories = db.getCategoriesForManga(it).executeAsBlocking()
status.value = FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories(it, inCategories, context) status.value = FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories(it, inCategories, context)
logger.w(context.getString(R.string.favorites_sync_manga_multiple_categories_error, it.id)) logger.w(context.getString(R.string.favorites_sync_manga_multiple_categories_error, it.id))
@ -194,8 +194,8 @@ class FavoritesSyncHelper(val context: Context) {
} }
} }
private suspend fun applyRemoteCategories(categories: List<String>) { private fun applyRemoteCategories(categories: List<String>) {
val localCategories = db.getCategories().executeOnIO() val localCategories = db.getCategories().executeAsBlocking()
val newLocalCategories = localCategories.toMutableList() val newLocalCategories = localCategories.toMutableList()
@ -233,7 +233,7 @@ class FavoritesSyncHelper(val context: Context) {
// Only insert categories if changed // Only insert categories if changed
if (changed) { if (changed) {
db.insertCategories(newLocalCategories).executeOnIO() db.insertCategories(newLocalCategories).executeAsBlocking()
} }
} }
@ -267,7 +267,7 @@ class FavoritesSyncHelper(val context: Context) {
for (i in 1..retryCount) { for (i in 1..retryCount) {
try { try {
val resp = exh.client.newCall(request).await() val resp = withIOContext { exh.client.newCall(request).await() }
if (resp.isSuccessful) { if (resp.isSuccessful) {
success = true success = true
@ -340,7 +340,7 @@ class FavoritesSyncHelper(val context: Context) {
db.getManga(url, EXH_SOURCE_ID), db.getManga(url, EXH_SOURCE_ID),
db.getManga(url, EH_SOURCE_ID) db.getManga(url, EH_SOURCE_ID)
).forEach { ).forEach {
val manga = it.executeOnIO() val manga = it.executeAsBlocking()
if (manga?.favorite == true) { if (manga?.favorite == true) {
manga.favorite = false manga.favorite = false
@ -357,7 +357,7 @@ class FavoritesSyncHelper(val context: Context) {
} }
val insertedMangaCategories = mutableListOf<Pair<MangaCategory, Manga>>() val insertedMangaCategories = mutableListOf<Pair<MangaCategory, Manga>>()
val categories = db.getCategories().executeOnIO() val categories = db.getCategories().executeAsBlocking()
// Apply additions // Apply additions
throttleManager.resetThrottle() throttleManager.resetThrottle()
@ -376,7 +376,8 @@ class FavoritesSyncHelper(val context: Context) {
"${exh.baseUrl}${it.getUrl()}", "${exh.baseUrl}${it.getUrl()}",
true, true,
exh, exh,
throttleManager::throttle throttleManager::throttle,
true
) )
if (result is GalleryAddEvent.Fail) { if (result is GalleryAddEvent.Fail) {

View File

@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.EHentai
import exh.metadata.metadata.EHentaiSearchMetadata import exh.metadata.metadata.EHentaiSearchMetadata
import exh.source.isEhBasedManga import exh.source.isEhBasedManga
import exh.util.executeOnIO
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -20,13 +19,13 @@ class LocalFavoritesStorage {
fun getRealm(): Realm = Realm.getInstance(realmConfig) fun getRealm(): Realm = Realm.getInstance(realmConfig)
suspend fun getChangedDbEntries(realm: Realm) = fun getChangedDbEntries(realm: Realm) =
getChangedEntries( getChangedEntries(
realm, realm,
parseToFavoriteEntries( parseToFavoriteEntries(
loadDbCategories( loadDbCategories(
db.getFavoriteMangas() db.getFavoriteMangas()
.executeOnIO() .executeAsBlocking()
.asSequence() .asSequence()
) )
) )
@ -45,11 +44,11 @@ class LocalFavoritesStorage {
) )
) )
suspend fun snapshotEntries(realm: Realm) { fun snapshotEntries(realm: Realm) {
val dbMangas = parseToFavoriteEntries( val dbMangas = parseToFavoriteEntries(
loadDbCategories( loadDbCategories(
db.getFavoriteMangas() db.getFavoriteMangas()
.executeOnIO() .executeAsBlocking()
.asSequence() .asSequence()
) )
) )
@ -97,8 +96,8 @@ class LocalFavoritesStorage {
it.category == entry.category it.category == entry.category
} }
private suspend fun loadDbCategories(manga: Sequence<Manga>): Sequence<Pair<Int, Manga>> { private fun loadDbCategories(manga: Sequence<Manga>): Sequence<Pair<Int, Manga>> {
val dbCategories = db.getCategories().executeOnIO() val dbCategories = db.getCategories().executeAsBlocking()
return manga.filter(this::validateDbManga).mapNotNull { return manga.filter(this::validateDbManga).mapNotNull {
val category = db.getCategoriesForManga(it).executeAsBlocking() val category = db.getCategoriesForManga(it).executeAsBlocking()

View File

@ -3,8 +3,25 @@ package exh.util
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
fun <T> Flow<T>.cancellable() = onEach { fun <T> Flow<T>.cancellable() = onEach {
coroutineContext.ensureActive() coroutineContext.ensureActive()
} }
@Suppress("BlockingMethodInNonBlockingContext")
@OptIn(ExperimentalContracts::class)
suspend inline fun <T> maybeRunBlocking(runBlocking: Boolean, crossinline block: suspend () -> T): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return if (runBlocking) {
runBlocking { block() }
} else {
block()
}
}