Help with favorites sync db locking
This commit is contained in:
parent
963b85f756
commit
9e80f47e9f
@ -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,14 +147,16 @@ class GalleryAdder {
|
|||||||
|
|
||||||
// Fetch and copy chapters
|
// Fetch and copy chapters
|
||||||
try {
|
try {
|
||||||
val chapterList = if (source is EHentai) {
|
maybeRunBlocking(protectTrans) {
|
||||||
source.getChapterList(manga.toMangaInfo(), throttleFunc)
|
val chapterList = if (source is EHentai) {
|
||||||
} else {
|
source.getChapterList(manga.toMangaInfo(), throttleFunc)
|
||||||
source.getChapterList(manga.toMangaInfo())
|
} else {
|
||||||
}.map { it.toSChapter() }
|
source.getChapterList(manga.toMangaInfo())
|
||||||
|
}.map { it.toSChapter() }
|
||||||
|
|
||||||
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)
|
||||||
@ -161,7 +164,7 @@ class GalleryAdder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -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) {
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user