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
+20
View File
@@ -350,6 +350,26 @@ object EXHMigrations {
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)
// do stuff here when releasing changed crap
+10 -15
View File
@@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import exh.log.xLogStack
import exh.source.getMainSource
import exh.util.maybeRunBlocking
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
@@ -52,8 +51,7 @@ class GalleryAdder {
url: String,
fav: Boolean = false,
forceSource: UrlImportableSource? = null,
throttleFunc: suspend () -> Unit = {},
protectTrans: Boolean = false
throttleFunc: suspend () -> Unit = {}
): GalleryAddEvent {
logger.d(context.getString(R.string.gallery_adder_importing_manga, url, fav.toString(), forceSource))
try {
@@ -132,9 +130,8 @@ class GalleryAdder {
}
// Fetch and copy details
val newManga = maybeRunBlocking(protectTrans) {
source.getMangaDetails(manga.toMangaInfo())
}
val newManga = source.getMangaDetails(manga.toMangaInfo())
manga.copyFrom(newManga.toSManga())
manga.initialized = true
@@ -147,16 +144,14 @@ class GalleryAdder {
// Fetch and copy chapters
try {
maybeRunBlocking(protectTrans) {
val chapterList = if (source is EHentai) {
source.getChapterList(manga.toMangaInfo(), throttleFunc)
} else {
source.getChapterList(manga.toMangaInfo())
}.map { it.toSChapter() }
val chapterList = if (source is EHentai) {
source.getChapterList(manga.toMangaInfo(), throttleFunc)
} else {
source.getChapterList(manga.toMangaInfo())
}.map { it.toSChapter() }
if (chapterList.isNotEmpty()) {
syncChaptersWithSource(db, chapterList, manga, source)
}
if (chapterList.isNotEmpty()) {
syncChaptersWithSource(db, chapterList, manga, source)
}
} catch (e: Exception) {
logger.w(context.getString(R.string.gallery_adder_chapter_fetch_error, manga.title), e)
@@ -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)
}
@@ -21,19 +21,21 @@ import exh.GalleryAddEvent
import exh.GalleryAdder
import exh.eh.EHentaiThrottleManager
import exh.eh.EHentaiUpdateWorker
import exh.favorites.sql.models.FavoriteEntry
import exh.log.xLog
import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID
import exh.source.isEhBasedManga
import exh.util.ignore
import exh.util.trans
import exh.util.wifiManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.newSingleThreadContext
import okhttp3.FormBody
import okhttp3.Request
import uy.kohesive.injekt.Injekt
@@ -48,6 +50,9 @@ class FavoritesSyncHelper(val context: Context) {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
@OptIn(DelicateCoroutinesApi::class)
private val dispatcher = newSingleThreadContext("Favorites-sync-worker")
private val exh by lazy {
Injekt.get<SourceManager>().get(EXH_SOURCE_ID) as? EHentai
?: EHentai(0, true, context)
@@ -74,7 +79,7 @@ class FavoritesSyncHelper(val context: Context) {
status.value = FavoritesSyncStatus.Initializing(context)
scope.launch(Dispatchers.IO) { beginSync() }
scope.launch(dispatcher) { beginSync() }
}
private suspend fun beginSync() {
@@ -134,32 +139,28 @@ class FavoritesSyncHelper(val context: Context) {
// Do not update galleries while syncing favorites
EHentaiUpdateWorker.cancelBackground(context)
storage.getRealm().use { realm ->
realm.trans {
db.inTransaction {
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_calculating_remote_changes), context = context)
val remoteChanges = storage.getChangedRemoteEntries(realm, favorites.first)
val localChanges = if (prefs.exhReadOnlySync().get()) {
null // Do not build local changes if they are not going to be applied
} else {
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_calculating_local_changes), context = context)
storage.getChangedDbEntries(realm)
}
// Apply remote categories
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_syncing_category_names), context = context)
applyRemoteCategories(favorites.second)
// Apply change sets
applyChangeSetToLocal(errorList, remoteChanges)
if (localChanges != null) {
applyChangeSetToRemote(errorList, localChanges)
}
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_cleaning_up), context = context)
storage.snapshotEntries(realm)
}
db.inTransaction {
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_calculating_remote_changes), context = context)
val remoteChanges = storage.getChangedRemoteEntries(favorites.first)
val localChanges = if (prefs.exhReadOnlySync().get()) {
null // Do not build local changes if they are not going to be applied
} else {
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_calculating_local_changes), context = context)
storage.getChangedDbEntries()
}
// Apply remote categories
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_syncing_category_names), context = context)
applyRemoteCategories(favorites.second)
// Apply change sets
applyChangeSetToLocal(errorList, remoteChanges)
if (localChanges != null) {
applyChangeSetToRemote(errorList, localChanges)
}
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_cleaning_up), context = context)
storage.snapshotEntries()
}
launchUI {
@@ -378,8 +379,7 @@ class FavoritesSyncHelper(val context: Context) {
"${exh.baseUrl}${it.getUrl()}",
true,
exh,
throttleManager::throttle,
true
throttleManager::throttle
)
if (result is GalleryAddEvent.Fail) {
@@ -424,6 +424,7 @@ class FavoritesSyncHelper(val context: Context) {
fun onDestroy() {
scope.cancel()
dispatcher.close()
}
companion object {
@@ -3,92 +3,70 @@ package exh.favorites
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.online.all.EHentai
import exh.favorites.sql.models.FavoriteEntry
import exh.metadata.metadata.EHentaiSearchMetadata
import exh.source.isEhBasedManga
import io.realm.Realm
import io.realm.RealmConfiguration
import uy.kohesive.injekt.injectLazy
class LocalFavoritesStorage {
private val db: DatabaseHelper by injectLazy()
private val realmConfig = RealmConfiguration.Builder()
.name("fav-sync")
.deleteRealmIfMigrationNeeded()
.build()
fun getChangedDbEntries() = db.getFavoriteMangas()
.executeAsBlocking()
.asSequence()
.loadDbCategories()
.parseToFavoriteEntries()
.getChangedEntries()
fun getRealm(): Realm = Realm.getInstance(realmConfig)
fun getChangedRemoteEntries(entries: List<EHentai.ParsedManga>) = entries
.asSequence()
.map {
it.fav to it.manga.apply {
favorite = true
date_added = System.currentTimeMillis()
}
}
.parseToFavoriteEntries()
.getChangedEntries()
fun getChangedDbEntries(realm: Realm) =
getChangedEntries(
realm,
parseToFavoriteEntries(
loadDbCategories(
db.getFavoriteMangas()
.executeAsBlocking()
.asSequence()
)
)
)
fun getChangedRemoteEntries(realm: Realm, entries: List<EHentai.ParsedManga>) =
getChangedEntries(
realm,
parseToFavoriteEntries(
entries.asSequence().map {
it.fav to it.manga.apply {
favorite = true
date_added = System.currentTimeMillis()
}
}
)
)
fun snapshotEntries(realm: Realm) {
val dbMangas = parseToFavoriteEntries(
loadDbCategories(
db.getFavoriteMangas()
.executeAsBlocking()
.asSequence()
)
)
fun snapshotEntries() {
val dbMangas = db.getFavoriteMangas()
.executeAsBlocking()
.asSequence()
.loadDbCategories()
.parseToFavoriteEntries()
// Delete old snapshot
realm.delete(FavoriteEntry::class.java)
db.deleteAllFavoriteEntries().executeAsBlocking()
// Insert new snapshots
realm.copyToRealm(dbMangas.toList())
db.insertFavoriteEntries(dbMangas.toList()).executeAsBlocking()
}
fun clearSnapshots(realm: Realm) {
realm.delete(FavoriteEntry::class.java)
fun clearSnapshots() {
db.deleteAllFavoriteEntries().executeAsBlocking()
}
private fun getChangedEntries(realm: Realm, entries: Sequence<FavoriteEntry>): ChangeSet {
val terminated = entries.toList()
private fun Sequence<FavoriteEntry>.getChangedEntries(): ChangeSet {
val terminated = toList()
val databaseEntries = db.getFavoriteEntries().executeAsBlocking()
val added = terminated.filter {
realm.queryRealmForEntry(it) == null
queryListForEntry(databaseEntries, it) == null
}
val removed = realm.where(FavoriteEntry::class.java)
.findAll()
val removed = databaseEntries
.filter {
queryListForEntry(terminated, it) == null
}.map {
} /*.map {
todo see what this does
realm.copyFromRealm(it)
}
}*/
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) =
list.find {
it.gid == entry.gid &&
@@ -96,10 +74,10 @@ class LocalFavoritesStorage {
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()
return manga.filter(this::validateDbManga).mapNotNull {
return filter(::validateDbManga).mapNotNull {
val category = db.getCategoriesForManga(it).executeAsBlocking()
dbCategories.indexOf(
@@ -109,17 +87,17 @@ class LocalFavoritesStorage {
}
}
private fun parseToFavoriteEntries(manga: Sequence<Pair<Int, Manga>>) =
manga.filter {
validateDbManga(it.second)
}.mapNotNull {
FavoriteEntry().apply {
title = it.second.originalTitle
gid = EHentaiSearchMetadata.galleryId(it.second.url)
token = EHentaiSearchMetadata.galleryToken(it.second.url)
category = it.first
if (this.category > MAX_CATEGORIES) {
private fun Sequence<Pair<Int, Manga>>.parseToFavoriteEntries() =
filter { (_, manga) ->
validateDbManga(manga)
}.mapNotNull { (categoryId, manga) ->
FavoriteEntry(
title = manga.originalTitle,
gid = EHentaiSearchMetadata.galleryId(manga.url),
token = EHentaiSearchMetadata.galleryToken(manga.url),
category = categoryId
).also {
if (it.category > MAX_CATEGORIES) {
return@mapNotNull null
}
}
@@ -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()
}
@@ -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)
}
@@ -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()
}
@@ -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
)"""
}
@@ -3,25 +3,8 @@ package exh.util
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.Flow
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
fun <T> Flow<T>.cancellable() = onEach {
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()
}
}
@@ -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()
}
}
-56
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)