Rewrite E-H favorites sync database, fixes:

- Freezing issues
- Build times
- Probably fixes bloated app size
This commit is contained in:
Jobobby04 2022-01-23 16:40:15 -05:00
parent 5224988265
commit 254d739d12
19 changed files with 271 additions and 780 deletions

View File

@ -11,9 +11,6 @@ plugins {
kotlin("plugin.parcelize") kotlin("plugin.parcelize")
kotlin("plugin.serialization") kotlin("plugin.serialization")
id("com.github.zellius.shortcut-helper") id("com.github.zellius.shortcut-helper")
// Realm (EH)
kotlin("kapt")
id("realm-android")
} }
if (!gradle.startParameter.taskRequests.toString().contains("Debug")) { if (!gradle.startParameter.taskRequests.toString().contains("Debug")) {
@ -32,7 +29,7 @@ android {
applicationId = "eu.kanade.tachiyomi.sy" applicationId = "eu.kanade.tachiyomi.sy"
minSdk = AndroidConfig.minSdk minSdk = AndroidConfig.minSdk
targetSdk = AndroidConfig.targetSdk targetSdk = AndroidConfig.targetSdk
versionCode = 23 versionCode = 24
versionName = "1.7.0" versionName = "1.7.0"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")

View File

@ -57,7 +57,6 @@ import exh.log.XLogLogcatLogger
import exh.log.xLogD import exh.log.xLogD
import exh.log.xLogE import exh.log.xLogE
import exh.syDebugVersion import exh.syDebugVersion
import io.realm.Realm
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import logcat.LogPriority import logcat.LogPriority
@ -99,7 +98,6 @@ open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
Injekt.importModule(AppModule(this)) Injekt.importModule(AppModule(this))
setupNotificationChannels() setupNotificationChannels()
Realm.init(this)
if ((BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "releaseTest") && DebugToggles.ENABLE_DEBUG_OVERLAY.enabled) { if ((BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "releaseTest") && DebugToggles.ENABLE_DEBUG_OVERLAY.enabled) {
setupDebugOverlay() setupDebugOverlay()
} }

View File

@ -21,6 +21,9 @@ import eu.kanade.tachiyomi.data.database.queries.HistoryQueries
import eu.kanade.tachiyomi.data.database.queries.MangaCategoryQueries import eu.kanade.tachiyomi.data.database.queries.MangaCategoryQueries
import eu.kanade.tachiyomi.data.database.queries.MangaQueries import eu.kanade.tachiyomi.data.database.queries.MangaQueries
import eu.kanade.tachiyomi.data.database.queries.TrackQueries import eu.kanade.tachiyomi.data.database.queries.TrackQueries
import exh.favorites.sql.mappers.FavoriteEntryTypeMapping
import exh.favorites.sql.models.FavoriteEntry
import exh.favorites.sql.queries.FavoriteEntryQueries
import exh.merged.sql.mappers.MergedMangaTypeMapping import exh.merged.sql.mappers.MergedMangaTypeMapping
import exh.merged.sql.models.MergedMangaReference import exh.merged.sql.models.MergedMangaReference
import exh.merged.sql.queries.MergedQueries import exh.merged.sql.queries.MergedQueries
@ -39,7 +42,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
* This class provides operations to manage the database through its interfaces. * This class provides operations to manage the database through its interfaces.
*/ */
open class DatabaseHelper(context: Context) : open class DatabaseHelper(context: Context) :
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* SY --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries, MergedQueries /* SY <-- */ { MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* SY --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries, MergedQueries, FavoriteEntryQueries /* SY <-- */ {
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context) private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
.name(DbOpenCallback.DATABASE_NAME) .name(DbOpenCallback.DATABASE_NAME)
@ -59,6 +62,7 @@ open class DatabaseHelper(context: Context) :
.addTypeMapping(SearchTag::class.java, SearchTagTypeMapping()) .addTypeMapping(SearchTag::class.java, SearchTagTypeMapping())
.addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping()) .addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping())
.addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping()) .addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping())
.addTypeMapping(FavoriteEntry::class.java, FavoriteEntryTypeMapping())
// SY <-- // SY <--
.build() .build()

View File

@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.tables.HistoryTable
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
import eu.kanade.tachiyomi.data.database.tables.MangaTable import eu.kanade.tachiyomi.data.database.tables.MangaTable
import eu.kanade.tachiyomi.data.database.tables.TrackTable import eu.kanade.tachiyomi.data.database.tables.TrackTable
import exh.favorites.sql.tables.FavoriteEntryTable
import exh.merged.sql.tables.MergedTable import exh.merged.sql.tables.MergedTable
import exh.metadata.sql.tables.SearchMetadataTable import exh.metadata.sql.tables.SearchMetadataTable
import exh.metadata.sql.tables.SearchTagTable import exh.metadata.sql.tables.SearchTagTable
@ -24,7 +25,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
/** /**
* Version of the database. * Version of the database.
*/ */
const val DATABASE_VERSION = /* SY --> */ 10 /* SY <-- */ const val DATABASE_VERSION = /* SY --> */ 11 /* SY <-- */
} }
override fun onCreate(db: SupportSQLiteDatabase) = with(db) { override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
@ -93,6 +94,9 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
if (oldVersion < 10) { if (oldVersion < 10) {
db.execSQL(ChapterTable.fixDateUploadIfNeeded) db.execSQL(ChapterTable.fixDateUploadIfNeeded)
} }
if (oldVersion < 11) {
db.execSQL(FavoriteEntryTable.createTableQuery)
}
} }
override fun onConfigure(db: SupportSQLiteDatabase) { override fun onConfigure(db: SupportSQLiteDatabase) {

View File

@ -25,6 +25,7 @@ import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.lang.runAsObservable import eu.kanade.tachiyomi.util.lang.runAsObservable
import eu.kanade.tachiyomi.util.lang.withIOContext
import exh.debug.DebugToggles import exh.debug.DebugToggles
import exh.eh.EHTags import exh.eh.EHTags
import exh.eh.EHentaiUpdateHelper import exh.eh.EHentaiUpdateHelper
@ -716,7 +717,7 @@ class EHentai(
throw UnsupportedOperationException("Unused method was called somehow!") throw UnsupportedOperationException("Unused method was called somehow!")
} }
fun fetchFavorites(): Pair<List<ParsedManga>, List<String>> { suspend fun fetchFavorites(): Pair<List<ParsedManga>, List<String>> {
val favoriteUrl = "$baseUrl/favorites.php" val favoriteUrl = "$baseUrl/favorites.php"
val result = mutableListOf<ParsedManga>() val result = mutableListOf<ParsedManga>()
var page = 1 var page = 1
@ -724,13 +725,15 @@ class EHentai(
var favNames: List<String>? = null var favNames: List<String>? = null
do { do {
val response2 = client.newCall( val response2 = withIOContext {
client.newCall(
exGet( exGet(
favoriteUrl, favoriteUrl,
page = page, page = page,
cache = false cache = false
) )
).execute() ).awaitResponse()
}
val doc = response2.asJsoup() val doc = response2.asJsoup()
// Parse favorites // Parse favorites

View File

@ -40,7 +40,6 @@ import exh.eh.EHentaiUpdateWorker
import exh.eh.EHentaiUpdateWorkerConstants import exh.eh.EHentaiUpdateWorkerConstants
import exh.eh.EHentaiUpdaterStats import exh.eh.EHentaiUpdaterStats
import exh.favorites.FavoritesIntroDialog import exh.favorites.FavoritesIntroDialog
import exh.favorites.LocalFavoritesStorage
import exh.log.xLogD import exh.log.xLogD
import exh.metadata.metadata.EHentaiSearchMetadata import exh.metadata.metadata.EHentaiSearchMetadata
import exh.metadata.metadata.base.getFlatMetadataForManga import exh.metadata.metadata.base.getFlatMetadataForManga
@ -49,7 +48,6 @@ import exh.uconfig.WarnConfigureDialogController
import exh.ui.login.EhLoginActivity import exh.ui.login.EhLoginActivity
import exh.util.executeOnIO import exh.util.executeOnIO
import exh.util.nullIfBlank import exh.util.nullIfBlank
import exh.util.trans
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -362,12 +360,8 @@ class SettingsEhController : SettingsController() {
.setTitle(R.string.favorites_sync_reset) .setTitle(R.string.favorites_sync_reset)
.setMessage(R.string.favorites_sync_reset_message) .setMessage(R.string.favorites_sync_reset_message)
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
LocalFavoritesStorage().apply { db.inTransaction {
getRealm().use { db.deleteAllFavoriteEntries().executeAsBlocking()
it.trans {
clearSnapshots(it)
}
}
} }
activity.toast(context.getString(R.string.sync_state_reset), Toast.LENGTH_LONG) activity.toast(context.getString(R.string.sync_state_reset), Toast.LENGTH_LONG)
} }

View File

@ -350,6 +350,26 @@ object EXHMigrations {
preferences.libraryUpdateMangaRestriction() -= MANGA_ONGOING preferences.libraryUpdateMangaRestriction() -= MANGA_ONGOING
} }
} }
if (oldVersion under 24) {
try {
sequenceOf(
"fav-sync",
"fav-sync.management",
"fav-sync.lock",
"fav-sync.note"
).map {
File(context.filesDir, it)
}.filter(File::exists).forEach {
if (it.isDirectory) {
it.deleteRecursively()
} else {
it.delete()
}
}
} catch (e: Exception) {
xLogE("Failed to delete old favorites database", e)
}
}
// if (oldVersion under 1) { } (1 is current release version) // if (oldVersion under 1) { } (1 is current release version)
// do stuff here when releasing changed crap // do stuff here when releasing changed crap

View File

@ -16,7 +16,6 @@ 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.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
@ -52,8 +51,7 @@ 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 {
@ -132,9 +130,8 @@ class GalleryAdder {
} }
// Fetch and copy details // Fetch and copy details
val newManga = maybeRunBlocking(protectTrans) { val newManga = source.getMangaDetails(manga.toMangaInfo())
source.getMangaDetails(manga.toMangaInfo())
}
manga.copyFrom(newManga.toSManga()) manga.copyFrom(newManga.toSManga())
manga.initialized = true manga.initialized = true
@ -147,7 +144,6 @@ 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 {
@ -157,7 +153,6 @@ 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))

View File

@ -1,23 +0,0 @@
package exh.favorites
import exh.metadata.metadata.EHentaiSearchMetadata
import io.realm.RealmObject
import io.realm.annotations.Index
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
import java.util.UUID
@RealmClass
open class FavoriteEntry : RealmObject() {
@PrimaryKey var id: String = UUID.randomUUID().toString()
var title: String? = null
@Index lateinit var gid: String
@Index lateinit var token: String
@Index var category: Int = -1
fun getUrl() = EHentaiSearchMetadata.idAndTokenToUrl(gid, token)
}

View File

@ -21,19 +21,21 @@ import exh.GalleryAddEvent
import exh.GalleryAdder import exh.GalleryAdder
import exh.eh.EHentaiThrottleManager import exh.eh.EHentaiThrottleManager
import exh.eh.EHentaiUpdateWorker import exh.eh.EHentaiUpdateWorker
import exh.favorites.sql.models.FavoriteEntry
import exh.log.xLog 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.ignore import exh.util.ignore
import exh.util.trans
import exh.util.wifiManager import exh.util.wifiManager
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.newSingleThreadContext
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Request import okhttp3.Request
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -48,6 +50,9 @@ class FavoritesSyncHelper(val context: Context) {
private val scope = CoroutineScope(Job() + Dispatchers.Main) private val scope = CoroutineScope(Job() + Dispatchers.Main)
@OptIn(DelicateCoroutinesApi::class)
private val dispatcher = newSingleThreadContext("Favorites-sync-worker")
private val exh by lazy { private val exh by lazy {
Injekt.get<SourceManager>().get(EXH_SOURCE_ID) as? EHentai Injekt.get<SourceManager>().get(EXH_SOURCE_ID) as? EHentai
?: EHentai(0, true, context) ?: EHentai(0, true, context)
@ -74,7 +79,7 @@ class FavoritesSyncHelper(val context: Context) {
status.value = FavoritesSyncStatus.Initializing(context) status.value = FavoritesSyncStatus.Initializing(context)
scope.launch(Dispatchers.IO) { beginSync() } scope.launch(dispatcher) { beginSync() }
} }
private suspend fun beginSync() { private suspend fun beginSync() {
@ -134,16 +139,14 @@ class FavoritesSyncHelper(val context: Context) {
// Do not update galleries while syncing favorites // Do not update galleries while syncing favorites
EHentaiUpdateWorker.cancelBackground(context) EHentaiUpdateWorker.cancelBackground(context)
storage.getRealm().use { realm ->
realm.trans {
db.inTransaction { db.inTransaction {
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_calculating_remote_changes), context = context) status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_calculating_remote_changes), context = context)
val remoteChanges = storage.getChangedRemoteEntries(realm, favorites.first) val remoteChanges = storage.getChangedRemoteEntries(favorites.first)
val localChanges = if (prefs.exhReadOnlySync().get()) { val localChanges = if (prefs.exhReadOnlySync().get()) {
null // Do not build local changes if they are not going to be applied null // Do not build local changes if they are not going to be applied
} else { } else {
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_calculating_local_changes), context = context) status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_calculating_local_changes), context = context)
storage.getChangedDbEntries(realm) storage.getChangedDbEntries()
} }
// Apply remote categories // Apply remote categories
@ -157,9 +160,7 @@ class FavoritesSyncHelper(val context: Context) {
} }
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_cleaning_up), context = context) status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_cleaning_up), context = context)
storage.snapshotEntries(realm) storage.snapshotEntries()
}
}
} }
launchUI { launchUI {
@ -378,8 +379,7 @@ 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) {
@ -424,6 +424,7 @@ class FavoritesSyncHelper(val context: Context) {
fun onDestroy() { fun onDestroy() {
scope.cancel() scope.cancel()
dispatcher.close()
} }
companion object { companion object {

View File

@ -3,92 +3,70 @@ package exh.favorites
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga 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.favorites.sql.models.FavoriteEntry
import exh.metadata.metadata.EHentaiSearchMetadata import exh.metadata.metadata.EHentaiSearchMetadata
import exh.source.isEhBasedManga import exh.source.isEhBasedManga
import io.realm.Realm
import io.realm.RealmConfiguration
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class LocalFavoritesStorage { class LocalFavoritesStorage {
private val db: DatabaseHelper by injectLazy() private val db: DatabaseHelper by injectLazy()
private val realmConfig = RealmConfiguration.Builder() fun getChangedDbEntries() = db.getFavoriteMangas()
.name("fav-sync")
.deleteRealmIfMigrationNeeded()
.build()
fun getRealm(): Realm = Realm.getInstance(realmConfig)
fun getChangedDbEntries(realm: Realm) =
getChangedEntries(
realm,
parseToFavoriteEntries(
loadDbCategories(
db.getFavoriteMangas()
.executeAsBlocking() .executeAsBlocking()
.asSequence() .asSequence()
) .loadDbCategories()
) .parseToFavoriteEntries()
) .getChangedEntries()
fun getChangedRemoteEntries(realm: Realm, entries: List<EHentai.ParsedManga>) = fun getChangedRemoteEntries(entries: List<EHentai.ParsedManga>) = entries
getChangedEntries( .asSequence()
realm, .map {
parseToFavoriteEntries(
entries.asSequence().map {
it.fav to it.manga.apply { it.fav to it.manga.apply {
favorite = true favorite = true
date_added = System.currentTimeMillis() date_added = System.currentTimeMillis()
} }
} }
) .parseToFavoriteEntries()
) .getChangedEntries()
fun snapshotEntries(realm: Realm) { fun snapshotEntries() {
val dbMangas = parseToFavoriteEntries( val dbMangas = db.getFavoriteMangas()
loadDbCategories(
db.getFavoriteMangas()
.executeAsBlocking() .executeAsBlocking()
.asSequence() .asSequence()
) .loadDbCategories()
) .parseToFavoriteEntries()
// Delete old snapshot // Delete old snapshot
realm.delete(FavoriteEntry::class.java) db.deleteAllFavoriteEntries().executeAsBlocking()
// Insert new snapshots // Insert new snapshots
realm.copyToRealm(dbMangas.toList()) db.insertFavoriteEntries(dbMangas.toList()).executeAsBlocking()
} }
fun clearSnapshots(realm: Realm) { fun clearSnapshots() {
realm.delete(FavoriteEntry::class.java) db.deleteAllFavoriteEntries().executeAsBlocking()
} }
private fun getChangedEntries(realm: Realm, entries: Sequence<FavoriteEntry>): ChangeSet { private fun Sequence<FavoriteEntry>.getChangedEntries(): ChangeSet {
val terminated = entries.toList() val terminated = toList()
val databaseEntries = db.getFavoriteEntries().executeAsBlocking()
val added = terminated.filter { val added = terminated.filter {
realm.queryRealmForEntry(it) == null queryListForEntry(databaseEntries, it) == null
} }
val removed = realm.where(FavoriteEntry::class.java) val removed = databaseEntries
.findAll()
.filter { .filter {
queryListForEntry(terminated, it) == null queryListForEntry(terminated, it) == null
}.map { } /*.map {
todo see what this does
realm.copyFromRealm(it) realm.copyFromRealm(it)
} }*/
return ChangeSet(added, removed) return ChangeSet(added, removed)
} }
private fun Realm.queryRealmForEntry(entry: FavoriteEntry) =
where(FavoriteEntry::class.java)
.equalTo(FavoriteEntry::gid.name, entry.gid)
.equalTo(FavoriteEntry::token.name, entry.token)
.equalTo(FavoriteEntry::category.name, entry.category)
.findFirst()
private fun queryListForEntry(list: List<FavoriteEntry>, entry: FavoriteEntry) = private fun queryListForEntry(list: List<FavoriteEntry>, entry: FavoriteEntry) =
list.find { list.find {
it.gid == entry.gid && it.gid == entry.gid &&
@ -96,10 +74,10 @@ class LocalFavoritesStorage {
it.category == entry.category it.category == entry.category
} }
private fun loadDbCategories(manga: Sequence<Manga>): Sequence<Pair<Int, Manga>> { private fun Sequence<Manga>.loadDbCategories(): Sequence<Pair<Int, Manga>> {
val dbCategories = db.getCategories().executeAsBlocking() val dbCategories = db.getCategories().executeAsBlocking()
return manga.filter(this::validateDbManga).mapNotNull { return filter(::validateDbManga).mapNotNull {
val category = db.getCategoriesForManga(it).executeAsBlocking() val category = db.getCategoriesForManga(it).executeAsBlocking()
dbCategories.indexOf( dbCategories.indexOf(
@ -109,17 +87,17 @@ class LocalFavoritesStorage {
} }
} }
private fun parseToFavoriteEntries(manga: Sequence<Pair<Int, Manga>>) = private fun Sequence<Pair<Int, Manga>>.parseToFavoriteEntries() =
manga.filter { filter { (_, manga) ->
validateDbManga(it.second) validateDbManga(manga)
}.mapNotNull { }.mapNotNull { (categoryId, manga) ->
FavoriteEntry().apply { FavoriteEntry(
title = it.second.originalTitle title = manga.originalTitle,
gid = EHentaiSearchMetadata.galleryId(it.second.url) gid = EHentaiSearchMetadata.galleryId(manga.url),
token = EHentaiSearchMetadata.galleryToken(it.second.url) token = EHentaiSearchMetadata.galleryToken(manga.url),
category = it.first category = categoryId
).also {
if (this.category > MAX_CATEGORIES) { if (it.category > MAX_CATEGORIES) {
return@mapNotNull null return@mapNotNull null
} }
} }

View File

@ -0,0 +1,65 @@
package exh.favorites.sql.mappers
import android.database.Cursor
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import exh.favorites.sql.models.FavoriteEntry
import exh.favorites.sql.tables.FavoriteEntryTable.COL_CATEGORY
import exh.favorites.sql.tables.FavoriteEntryTable.COL_GID
import exh.favorites.sql.tables.FavoriteEntryTable.COL_ID
import exh.favorites.sql.tables.FavoriteEntryTable.COL_TITLE
import exh.favorites.sql.tables.FavoriteEntryTable.COL_TOKEN
import exh.favorites.sql.tables.FavoriteEntryTable.TABLE
class FavoriteEntryTypeMapping : SQLiteTypeMapping<FavoriteEntry>(
FavoriteEntryPutResolver(),
FavoriteEntryGetResolver(),
FavoriteEntryDeleteResolver()
)
class FavoriteEntryPutResolver : DefaultPutResolver<FavoriteEntry>() {
override fun mapToInsertQuery(obj: FavoriteEntry) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: FavoriteEntry) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: FavoriteEntry) = contentValuesOf(
COL_ID to obj.id,
COL_TITLE to obj.title,
COL_GID to obj.gid,
COL_TOKEN to obj.token,
COL_CATEGORY to obj.category
)
}
class FavoriteEntryGetResolver : DefaultGetResolver<FavoriteEntry>() {
override fun mapFromCursor(cursor: Cursor): FavoriteEntry = FavoriteEntry(
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID)),
title = cursor.getString(cursor.getColumnIndexOrThrow(COL_TITLE)),
gid = cursor.getString(cursor.getColumnIndexOrThrow(COL_GID)),
token = cursor.getString(cursor.getColumnIndexOrThrow(COL_TOKEN)),
category = cursor.getInt(cursor.getColumnIndexOrThrow(COL_CATEGORY))
)
}
class FavoriteEntryDeleteResolver : DefaultDeleteResolver<FavoriteEntry>() {
override fun mapToDeleteQuery(obj: FavoriteEntry) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

View File

@ -0,0 +1,17 @@
package exh.favorites.sql.models
import exh.metadata.metadata.EHentaiSearchMetadata
data class FavoriteEntry(
val id: Long? = null,
val title: String,
val gid: String,
val token: String,
val category: Int = -1,
) {
fun getUrl() = EHentaiSearchMetadata.idAndTokenToUrl(gid, token)
}

View File

@ -0,0 +1,30 @@
package exh.favorites.sql.queries
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.Query
import eu.kanade.tachiyomi.data.database.DbProvider
import exh.favorites.sql.models.FavoriteEntry
import exh.favorites.sql.tables.FavoriteEntryTable
interface FavoriteEntryQueries : DbProvider {
fun getFavoriteEntries() = db.get()
.listOfObjects(FavoriteEntry::class.java)
.withQuery(
Query.builder()
.table(FavoriteEntryTable.TABLE)
.build()
)
.prepare()
fun insertFavoriteEntries(favoriteEntries: List<FavoriteEntry>) = db.put()
.objects(favoriteEntries)
.prepare()
fun deleteAllFavoriteEntries() = db.delete()
.byQuery(
DeleteQuery.builder()
.table(FavoriteEntryTable.TABLE)
.build()
)
.prepare()
}

View File

@ -0,0 +1,26 @@
package exh.favorites.sql.tables
object FavoriteEntryTable {
const val TABLE = "eh_favorites"
const val COL_ID = "_id"
const val COL_TITLE = "title"
const val COL_GID = "gid"
const val COL_TOKEN = "token"
const val COL_CATEGORY = "category"
val createTableQuery: String
get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_TITLE TEXT NOT NULL,
$COL_GID TEXT NOT NULL,
$COL_TOKEN TEXT NOT NULL,
$COL_CATEGORY INTEGER NOT NULL
)"""
}

View File

@ -3,25 +3,8 @@ 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()
}
}

View File

@ -1,542 +0,0 @@
package exh.util
import io.realm.Case
import io.realm.RealmModel
import io.realm.RealmQuery
import io.realm.RealmResults
import java.util.Date
/**
* Realm query with logging
*
* @author nulldev
*/
inline fun <reified E : RealmModel> RealmQuery<out E>.beginLog(
clazz: Class<out E>? =
E::class.java
): LoggingRealmQuery<out E> =
LoggingRealmQuery.fromQuery(this, clazz)
class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
companion object {
fun <E : RealmModel> fromQuery(q: RealmQuery<out E>, clazz: Class<out E>?) =
LoggingRealmQuery(q).apply {
log += "SELECT * FROM ${clazz?.name ?: "???"} WHERE"
}
}
private val log = mutableListOf<String>()
private fun sec(section: String) = "{$section}"
fun log() = log.joinToString(separator = " ")
fun isValid(): Boolean {
return query.isValid
}
fun isNull(fieldName: String): RealmQuery<E> {
log += sec("\"$fieldName\" IS NULL")
return query.isNull(fieldName)
}
fun isNotNull(fieldName: String): RealmQuery<E> {
log += sec("\"$fieldName\" IS NOT NULL")
return query.isNotNull(fieldName)
}
private fun appendEqualTo(fieldName: String, value: String, casing: Case? = null) {
log += sec(
"\"$fieldName\" == \"$value\"" + (
casing?.let {
" CASE ${casing.name}"
}.orEmpty()
)
)
}
fun equalTo(fieldName: String, value: String): RealmQuery<E> {
appendEqualTo(fieldName, value)
return query.equalTo(fieldName, value)
}
fun equalTo(fieldName: String, value: String, casing: Case): RealmQuery<E> {
appendEqualTo(fieldName, value, casing)
return query.equalTo(fieldName, value, casing)
}
fun equalTo(fieldName: String, value: Byte?): RealmQuery<E> {
appendEqualTo(fieldName, value.toString())
return query.equalTo(fieldName, value)
}
fun equalTo(fieldName: String, value: ByteArray): RealmQuery<E> {
appendEqualTo(fieldName, value.toString())
return query.equalTo(fieldName, value)
}
fun equalTo(fieldName: String, value: Short?): RealmQuery<E> {
appendEqualTo(fieldName, value.toString())
return query.equalTo(fieldName, value)
}
fun equalTo(fieldName: String, value: Int?): RealmQuery<E> {
appendEqualTo(fieldName, value.toString())
return query.equalTo(fieldName, value)
}
fun equalTo(fieldName: String, value: Long?): RealmQuery<E> {
appendEqualTo(fieldName, value.toString())
return query.equalTo(fieldName, value)
}
fun equalTo(fieldName: String, value: Double?): RealmQuery<E> {
appendEqualTo(fieldName, value.toString())
return query.equalTo(fieldName, value)
}
fun equalTo(fieldName: String, value: Float?): RealmQuery<E> {
appendEqualTo(fieldName, value.toString())
return query.equalTo(fieldName, value)
}
fun equalTo(fieldName: String, value: Boolean?): RealmQuery<E> {
appendEqualTo(fieldName, value.toString())
return query.equalTo(fieldName, value)
}
fun equalTo(fieldName: String, value: Date): RealmQuery<E> {
appendEqualTo(fieldName, value.toString())
return query.equalTo(fieldName, value)
}
fun appendIn(fieldName: String, values: Array<out Any?>, casing: Case? = null) {
log += sec(
"[${values.joinToString(
separator = ", ",
transform = {
"\"$it\""
}
)}] IN \"$fieldName\"" + (
casing?.let {
" CASE ${casing.name}"
}.orEmpty()
)
)
}
fun `in`(fieldName: String, values: Array<String>): RealmQuery<E> {
appendIn(fieldName, values)
return query.`in`(fieldName, values)
}
fun `in`(fieldName: String, values: Array<String>, casing: Case): RealmQuery<E> {
appendIn(fieldName, values, casing)
return query.`in`(fieldName, values, casing)
}
fun `in`(fieldName: String, values: Array<Byte>): RealmQuery<E> {
appendIn(fieldName, values)
return query.`in`(fieldName, values)
}
fun `in`(fieldName: String, values: Array<Short>): RealmQuery<E> {
appendIn(fieldName, values)
return query.`in`(fieldName, values)
}
fun `in`(fieldName: String, values: Array<Int>): RealmQuery<E> {
appendIn(fieldName, values)
return query.`in`(fieldName, values)
}
fun `in`(fieldName: String, values: Array<Long>): RealmQuery<E> {
appendIn(fieldName, values)
return query.`in`(fieldName, values)
}
fun `in`(fieldName: String, values: Array<Double>): RealmQuery<E> {
appendIn(fieldName, values)
return query.`in`(fieldName, values)
}
fun `in`(fieldName: String, values: Array<Float>): RealmQuery<E> {
appendIn(fieldName, values)
return query.`in`(fieldName, values)
}
fun `in`(fieldName: String, values: Array<Boolean>): RealmQuery<E> {
appendIn(fieldName, values)
return query.`in`(fieldName, values)
}
fun `in`(fieldName: String, values: Array<Date>): RealmQuery<E> {
appendIn(fieldName, values)
return query.`in`(fieldName, values)
}
private fun appendNotEqualTo(fieldName: String, value: Any?, casing: Case? = null) {
log += sec(
"\"$fieldName\" != \"$value\"" + (
casing?.let {
" CASE ${casing.name}"
}.orEmpty()
)
)
}
fun notEqualTo(fieldName: String, value: String): RealmQuery<E> {
appendNotEqualTo(fieldName, value)
return query.notEqualTo(fieldName, value)
}
fun notEqualTo(fieldName: String, value: String, casing: Case): RealmQuery<E> {
appendNotEqualTo(fieldName, value, casing)
return query.notEqualTo(fieldName, value, casing)
}
fun notEqualTo(fieldName: String, value: Byte?): RealmQuery<E> {
appendNotEqualTo(fieldName, value)
return query.notEqualTo(fieldName, value)
}
fun notEqualTo(fieldName: String, value: ByteArray): RealmQuery<E> {
appendNotEqualTo(fieldName, value)
return query.notEqualTo(fieldName, value)
}
fun notEqualTo(fieldName: String, value: Short?): RealmQuery<E> {
appendNotEqualTo(fieldName, value)
return query.notEqualTo(fieldName, value)
}
fun notEqualTo(fieldName: String, value: Int?): RealmQuery<E> {
appendNotEqualTo(fieldName, value)
return query.notEqualTo(fieldName, value)
}
fun notEqualTo(fieldName: String, value: Long?): RealmQuery<E> {
appendNotEqualTo(fieldName, value)
return query.notEqualTo(fieldName, value)
}
fun notEqualTo(fieldName: String, value: Double?): RealmQuery<E> {
appendNotEqualTo(fieldName, value)
return query.notEqualTo(fieldName, value)
}
fun notEqualTo(fieldName: String, value: Float?): RealmQuery<E> {
appendNotEqualTo(fieldName, value)
return query.notEqualTo(fieldName, value)
}
fun notEqualTo(fieldName: String, value: Boolean?): RealmQuery<E> {
appendNotEqualTo(fieldName, value)
return query.notEqualTo(fieldName, value)
}
fun notEqualTo(fieldName: String, value: Date): RealmQuery<E> {
appendNotEqualTo(fieldName, value)
return query.notEqualTo(fieldName, value)
}
private fun appendGreaterThan(fieldName: String, value: Any?) {
log += sec("\"$fieldName\" > $value")
}
fun greaterThan(fieldName: String, value: Int): RealmQuery<E> {
appendGreaterThan(fieldName, value)
return query.greaterThan(fieldName, value)
}
fun greaterThan(fieldName: String, value: Long): RealmQuery<E> {
appendGreaterThan(fieldName, value)
return query.greaterThan(fieldName, value)
}
fun greaterThan(fieldName: String, value: Double): RealmQuery<E> {
appendGreaterThan(fieldName, value)
return query.greaterThan(fieldName, value)
}
fun greaterThan(fieldName: String, value: Float): RealmQuery<E> {
appendGreaterThan(fieldName, value)
return query.greaterThan(fieldName, value)
}
fun greaterThan(fieldName: String, value: Date): RealmQuery<E> {
appendGreaterThan(fieldName, value)
return query.greaterThan(fieldName, value)
}
private fun appendGreaterThanOrEqualTo(fieldName: String, value: Any?) {
log += sec("\"$fieldName\" >= $value")
}
fun greaterThanOrEqualTo(fieldName: String, value: Int): RealmQuery<E> {
appendGreaterThanOrEqualTo(fieldName, value)
return query.greaterThanOrEqualTo(fieldName, value)
}
fun greaterThanOrEqualTo(fieldName: String, value: Long): RealmQuery<E> {
appendGreaterThanOrEqualTo(fieldName, value)
return query.greaterThanOrEqualTo(fieldName, value)
}
fun greaterThanOrEqualTo(fieldName: String, value: Double): RealmQuery<E> {
appendGreaterThanOrEqualTo(fieldName, value)
return query.greaterThanOrEqualTo(fieldName, value)
}
fun greaterThanOrEqualTo(fieldName: String, value: Float): RealmQuery<E> {
appendGreaterThanOrEqualTo(fieldName, value)
return query.greaterThanOrEqualTo(fieldName, value)
}
fun greaterThanOrEqualTo(fieldName: String, value: Date): RealmQuery<E> {
appendGreaterThanOrEqualTo(fieldName, value)
return query.greaterThanOrEqualTo(fieldName, value)
}
private fun appendLessThan(fieldName: String, value: Any?) {
log += sec("\"$fieldName\" < $value")
}
fun lessThan(fieldName: String, value: Int): RealmQuery<E> {
appendLessThan(fieldName, value)
return query.lessThan(fieldName, value)
}
fun lessThan(fieldName: String, value: Long): RealmQuery<E> {
appendLessThan(fieldName, value)
return query.lessThan(fieldName, value)
}
fun lessThan(fieldName: String, value: Double): RealmQuery<E> {
appendLessThan(fieldName, value)
return query.lessThan(fieldName, value)
}
fun lessThan(fieldName: String, value: Float): RealmQuery<E> {
appendLessThan(fieldName, value)
return query.lessThan(fieldName, value)
}
fun lessThan(fieldName: String, value: Date): RealmQuery<E> {
appendLessThan(fieldName, value)
return query.lessThan(fieldName, value)
}
private fun appendLessThanOrEqualTo(fieldName: String, value: Any?) {
log += sec("\"$fieldName\" <= $value")
}
fun lessThanOrEqualTo(fieldName: String, value: Int): RealmQuery<E> {
appendLessThanOrEqualTo(fieldName, value)
return query.lessThanOrEqualTo(fieldName, value)
}
fun lessThanOrEqualTo(fieldName: String, value: Long): RealmQuery<E> {
appendLessThanOrEqualTo(fieldName, value)
return query.lessThanOrEqualTo(fieldName, value)
}
fun lessThanOrEqualTo(fieldName: String, value: Double): RealmQuery<E> {
appendLessThanOrEqualTo(fieldName, value)
return query.lessThanOrEqualTo(fieldName, value)
}
fun lessThanOrEqualTo(fieldName: String, value: Float): RealmQuery<E> {
appendLessThanOrEqualTo(fieldName, value)
return query.lessThanOrEqualTo(fieldName, value)
}
fun lessThanOrEqualTo(fieldName: String, value: Date): RealmQuery<E> {
appendLessThanOrEqualTo(fieldName, value)
return query.lessThanOrEqualTo(fieldName, value)
}
private fun appendBetween(fieldName: String, from: Any?, to: Any?) {
log += sec("\"$fieldName\" BETWEEN $from - $to")
}
fun between(fieldName: String, from: Int, to: Int): RealmQuery<E> {
appendBetween(fieldName, from, to)
return query.between(fieldName, from, to)
}
fun between(fieldName: String, from: Long, to: Long): RealmQuery<E> {
appendBetween(fieldName, from, to)
return query.between(fieldName, from, to)
}
fun between(fieldName: String, from: Double, to: Double): RealmQuery<E> {
appendBetween(fieldName, from, to)
return query.between(fieldName, from, to)
}
fun between(fieldName: String, from: Float, to: Float): RealmQuery<E> {
appendBetween(fieldName, from, to)
return query.between(fieldName, from, to)
}
fun between(fieldName: String, from: Date, to: Date): RealmQuery<E> {
appendBetween(fieldName, from, to)
return query.between(fieldName, from, to)
}
private fun appendContains(fieldName: String, value: Any?, casing: Case? = null) {
log += sec(
"\"$fieldName\" CONTAINS \"$value\"" + (
casing?.let {
" CASE ${casing.name}"
}.orEmpty()
)
)
}
fun contains(fieldName: String, value: String): RealmQuery<E> {
appendContains(fieldName, value)
return query.contains(fieldName, value)
}
fun contains(fieldName: String, value: String, casing: Case): RealmQuery<E> {
appendContains(fieldName, value, casing)
return query.contains(fieldName, value, casing)
}
private fun appendBeginsWith(fieldName: String, value: Any?, casing: Case? = null) {
log += sec(
"\"$fieldName\" BEGINS WITH \"$value\"" + (
casing?.let {
" CASE ${casing.name}"
}.orEmpty()
)
)
}
fun beginsWith(fieldName: String, value: String): RealmQuery<E> {
appendBeginsWith(fieldName, value)
return query.beginsWith(fieldName, value)
}
fun beginsWith(fieldName: String, value: String, casing: Case): RealmQuery<E> {
appendBeginsWith(fieldName, value, casing)
return query.beginsWith(fieldName, value, casing)
}
private fun appendEndsWith(fieldName: String, value: Any?, casing: Case? = null) {
log += sec(
"\"$fieldName\" ENDS WITH \"$value\"" + (
casing?.let {
" CASE ${casing.name}"
}.orEmpty()
)
)
}
fun endsWith(fieldName: String, value: String): RealmQuery<E> {
appendEndsWith(fieldName, value)
return query.endsWith(fieldName, value)
}
fun endsWith(fieldName: String, value: String, casing: Case): RealmQuery<E> {
appendEndsWith(fieldName, value, casing)
return query.endsWith(fieldName, value, casing)
}
private fun appendLike(fieldName: String, value: Any?, casing: Case? = null) {
log += sec(
"\"$fieldName\" LIKE \"$value\"" + (
casing?.let {
" CASE ${casing.name}"
}.orEmpty()
)
)
}
fun like(fieldName: String, value: String): RealmQuery<E> {
appendLike(fieldName, value)
return query.like(fieldName, value)
}
fun like(fieldName: String, value: String, casing: Case): RealmQuery<E> {
appendLike(fieldName, value, casing)
return query.like(fieldName, value, casing)
}
fun beginGroup(): RealmQuery<E> {
log += "("
return query.beginGroup()
}
fun endGroup(): RealmQuery<E> {
log += ")"
return query.endGroup()
}
fun or(): RealmQuery<E> {
log += "OR"
return query.or()
}
operator fun not(): RealmQuery<E> {
log += "NOT"
return query.not()
}
fun isEmpty(fieldName: String): RealmQuery<E> {
log += "\"$fieldName\" IS EMPTY"
return query.isEmpty(fieldName)
}
fun isNotEmpty(fieldName: String): RealmQuery<E> {
log += "\"$fieldName\" IS NOT EMPTY"
return query.isNotEmpty(fieldName)
}
fun sum(fieldName: String): Number {
return query.sum(fieldName)
}
fun average(fieldName: String): Double {
return query.average(fieldName)
}
fun min(fieldName: String): Number? {
return query.min(fieldName)
}
fun minimumDate(fieldName: String): Date? {
return query.minimumDate(fieldName)
}
fun max(fieldName: String): Number? {
return query.max(fieldName)
}
fun maximumDate(fieldName: String): Date? {
return query.maximumDate(fieldName)
}
fun count(): Long {
return query.count()
}
fun findAll(): RealmResults<E> {
return query.findAll()
}
fun findAllAsync(): RealmResults<E> {
return query.findAllAsync()
}
fun findFirst(): E? {
return query.findFirst()
}
fun findFirstAsync(): E {
return query.findFirstAsync()
}
}

View File

@ -1,56 +0,0 @@
package exh.util
import io.realm.Realm
import io.realm.RealmModel
import io.realm.log.RealmLog
import java.util.UUID
inline fun <T> realmTrans(block: (Realm) -> T): T {
return defRealm {
it.trans {
block(it)
}
}
}
inline fun <T> defRealm(block: (Realm) -> T): T {
return Realm.getDefaultInstance().use {
block(it)
}
}
inline fun <T> Realm.trans(block: () -> T): T {
beginTransaction()
try {
val res = block()
commitTransaction()
return res
} catch (t: Throwable) {
if (isInTransaction) {
cancelTransaction()
} else {
RealmLog.warn("Could not cancel transaction, not currently in a transaction.")
}
throw t
} finally {
// Just in case
if (isInTransaction) {
cancelTransaction()
}
}
}
inline fun <T> Realm.useTrans(block: (Realm) -> T): T {
return use {
trans {
block(this)
}
}
}
fun <T : RealmModel> Realm.createUUIDObj(clazz: Class<T>) =
createObject(clazz, UUID.randomUUID().toString())!!
inline fun <reified T : RealmModel> Realm.createUUIDObj() =
createUUIDObj(T::class.java)

View File

@ -31,9 +31,6 @@ buildscript {
classpath("com.google.gms:google-services:4.3.10") classpath("com.google.gms:google-services:4.3.10")
classpath("com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:${BuildPluginsVersion.ABOUTLIB_PLUGIN}") classpath("com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:${BuildPluginsVersion.ABOUTLIB_PLUGIN}")
classpath(kotlin("serialization", version = BuildPluginsVersion.KOTLIN)) classpath(kotlin("serialization", version = BuildPluginsVersion.KOTLIN))
// Realm (EH)
classpath("io.realm:realm-gradle-plugin:10.8.0")
// Firebase Crashlytics // Firebase Crashlytics
classpath("com.google.firebase:firebase-crashlytics-gradle:2.8.0") classpath("com.google.firebase:firebase-crashlytics-gradle:2.8.0")
} }