Migrate to realm for metadata

This commit is contained in:
NerdNumber9
2017-08-25 17:31:38 -04:00
parent bb6b88a703
commit cd291f0a27
31 changed files with 1394 additions and 588 deletions

View File

@ -10,8 +10,13 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.util.syncChaptersWithSource
import exh.metadata.MetadataHelper
import exh.metadata.copyTo
import exh.metadata.loadEh
import exh.metadata.loadNhentai
import exh.metadata.models.ExGalleryMetadata
import exh.metadata.models.NHentaiMetadata
import exh.util.defRealm
import io.realm.Realm
import okhttp3.MediaType
import okhttp3.Request
import okhttp3.RequestBody
@ -27,8 +32,6 @@ class GalleryAdder {
private val sourceManager: SourceManager by injectLazy()
private val metadataHelper = MetadataHelper()
private val networkHelper: NetworkHelper by injectLazy()
companion object {
@ -119,11 +122,17 @@ class GalleryAdder {
manga.copyFrom(sourceObj.fetchMangaDetails(manga).toBlocking().first())
//Apply metadata
when(source) {
EH_SOURCE_ID, EXH_SOURCE_ID ->
metadataHelper.fetchEhMetadata(realUrl, isExSource(source))?.copyTo(manga)
NHENTAI_SOURCE_ID ->
metadataHelper.fetchNhentaiMetadata(realUrl)?.copyTo(manga)
defRealm { realm ->
when (source) {
EH_SOURCE_ID, EXH_SOURCE_ID ->
realm.loadEh(ExGalleryMetadata.galleryId(realUrl),
ExGalleryMetadata.galleryToken(realUrl),
isExSource(source))?.copyTo(manga)
NHENTAI_SOURCE_ID ->
realm.loadNhentai(NHentaiMetadata.nhIdFromUrl(realUrl))
?.copyTo(manga)
else -> return GalleryAddEvent.Fail.UnknownType(url)
}
}
if (fav) manga.favorite = true

View File

@ -1,5 +0,0 @@
package exh
import ru.lanwen.verbalregex.VerbalExpression
fun VerbalExpression.Builder.anyChar() = add(".")!!

View File

@ -1,62 +1,114 @@
package exh.metadata
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.model.SManga
import exh.*
import exh.metadata.models.ExGalleryMetadata
import exh.metadata.models.NHentaiMetadata
import exh.metadata.models.PervEdenGalleryMetadata
import exh.metadata.models.SearchableGalleryMetadata
import io.paperdb.Paper
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.RealmResults
import rx.Observable
import kotlin.reflect.KClass
class MetadataHelper {
fun Realm.ehMetaQueryFromUrl(url: String,
exh: Boolean,
meta: RealmQuery<ExGalleryMetadata>? = null) =
ehMetadataQuery(
ExGalleryMetadata.galleryId(url),
ExGalleryMetadata.galleryToken(url),
exh,
meta
)
fun writeGallery(galleryMetadata: SearchableGalleryMetadata, source: Long)
= (if(isExSource(source) || isEhSource(source)) exGalleryBook()
else if(isPervEdenSource(source)) pervEdenGalleryBook()
else if(isNhentaiSource(source)) nhentaiGalleryBook()
else null)?.write(galleryMetadata.galleryUniqueIdentifier(), galleryMetadata)!!
fun Realm.ehMetadataQuery(gId: String,
gToken: String,
exh: Boolean,
meta: RealmQuery<ExGalleryMetadata>? = null)
= (meta ?: where(ExGalleryMetadata::class.java))
.equalTo(ExGalleryMetadata::gId.name, gId)
.equalTo(ExGalleryMetadata::gToken.name, gToken)
.equalTo(ExGalleryMetadata::exh.name, exh)
fun fetchEhMetadata(url: String, exh: Boolean): ExGalleryMetadata?
= ExGalleryMetadata().let {
it.url = url
it.exh = exh
return exGalleryBook().read<ExGalleryMetadata>(it.galleryUniqueIdentifier())
fun Realm.loadEh(gId: String, gToken: String, exh: Boolean): ExGalleryMetadata?
= ehMetadataQuery(gId, gToken, exh)
.findFirst()
fun Realm.loadEhAsync(gId: String, gToken: String, exh: Boolean): Observable<ExGalleryMetadata?>
= ehMetadataQuery(gId, gToken, exh)
.findFirstAsync()
.asObservable()
private fun pervEdenSourceToLang(source: Long)
= when (source) {
PERV_EDEN_EN_SOURCE_ID -> "en"
PERV_EDEN_IT_SOURCE_ID -> "it"
else -> throw IllegalArgumentException()
}
fun Realm.pervEdenMetaQueryFromUrl(url: String,
source: Long,
meta: RealmQuery<PervEdenGalleryMetadata>?) =
pervEdenMetadataQuery(
PervEdenGalleryMetadata.pvIdFromUrl(url),
source,
meta
)
fun Realm.pervEdenMetadataQuery(pvId: String,
source: Long,
meta: RealmQuery<PervEdenGalleryMetadata>? = null)
= (meta ?: where(PervEdenGalleryMetadata::class.java))
.equalTo(PervEdenGalleryMetadata::lang.name, pervEdenSourceToLang(source))
.equalTo(PervEdenGalleryMetadata::pvId.name, pvId)
fun Realm.loadPervEden(pvId: String, source: Long): PervEdenGalleryMetadata?
= pervEdenMetadataQuery(pvId, source)
.findFirst()
fun Realm.loadPervEdenAsync(pvId: String, source: Long): Observable<PervEdenGalleryMetadata?>
= pervEdenMetadataQuery(pvId, source)
.findFirstAsync()
.asObservable()
fun Realm.nhentaiMetaQueryFromUrl(url: String,
meta: RealmQuery<NHentaiMetadata>?) =
nhentaiMetadataQuery(
NHentaiMetadata.nhIdFromUrl(url),
meta
)
fun Realm.nhentaiMetadataQuery(nhId: Long,
meta: RealmQuery<NHentaiMetadata>? = null)
= (meta ?: where(NHentaiMetadata::class.java))
.equalTo(NHentaiMetadata::nhId.name, nhId)
fun Realm.loadNhentai(nhId: Long): NHentaiMetadata?
= nhentaiMetadataQuery(nhId)
.findFirst()
fun Realm.loadNhentaiAsync(nhId: Long): Observable<NHentaiMetadata?>
= nhentaiMetadataQuery(nhId)
.findFirstAsync()
.asObservable()
fun Realm.loadAllMetadata(): Map<KClass<out SearchableGalleryMetadata>, RealmResults<out SearchableGalleryMetadata>> =
mapOf(
Pair(ExGalleryMetadata::class, where(ExGalleryMetadata::class.java).findAll()),
Pair(NHentaiMetadata::class, where(NHentaiMetadata::class.java).findAll()),
Pair(PervEdenGalleryMetadata::class, where(PervEdenGalleryMetadata::class.java).findAll())
)
fun Realm.queryMetadataFromManga(manga: Manga,
meta: RealmQuery<out SearchableGalleryMetadata>? = null): RealmQuery<out SearchableGalleryMetadata> =
when(manga.source) {
EH_SOURCE_ID -> ehMetaQueryFromUrl(manga.url, false, meta as? RealmQuery<ExGalleryMetadata>)
EXH_SOURCE_ID -> ehMetaQueryFromUrl(manga.url, true, meta as? RealmQuery<ExGalleryMetadata>)
PERV_EDEN_EN_SOURCE_ID,
PERV_EDEN_IT_SOURCE_ID ->
pervEdenMetaQueryFromUrl(manga.url, manga.source, meta as? RealmQuery<PervEdenGalleryMetadata>)
NHENTAI_SOURCE_ID -> nhentaiMetaQueryFromUrl(manga.url, meta as? RealmQuery<NHentaiMetadata>)
else -> throw IllegalArgumentException("Unknown source type!")
}
fun fetchPervEdenMetadata(url: String, source: Long): PervEdenGalleryMetadata?
= PervEdenGalleryMetadata().let {
it.url = url
if(source == PERV_EDEN_EN_SOURCE_ID)
it.lang = "en"
else if(source == PERV_EDEN_IT_SOURCE_ID)
it.lang = "it"
else throw IllegalArgumentException("Invalid source id!")
return pervEdenGalleryBook().read<PervEdenGalleryMetadata>(it.galleryUniqueIdentifier())
}
fun fetchNhentaiMetadata(url: String) = NHentaiMetadata().let {
it.url = url
nhentaiGalleryBook().read<NHentaiMetadata>(it.galleryUniqueIdentifier())
}
fun fetchMetadata(url: String, source: Long): SearchableGalleryMetadata? {
if(isExSource(source) || isEhSource(source)) {
return fetchEhMetadata(url, isExSource(source))
} else if(isPervEdenSource(source)) {
return fetchPervEdenMetadata(url, source)
} else if(isNhentaiSource(source)) {
return fetchNhentaiMetadata(url)
} else {
return null
}
}
fun getAllGalleries() = exGalleryBook().allKeys.map {
exGalleryBook().read<ExGalleryMetadata>(it)
}
fun exGalleryBook() = Paper.book("gallery-ex")!!
fun pervEdenGalleryBook() = Paper.book("gallery-perveden")!!
fun nhentaiGalleryBook() = Paper.book("gallery-nhentai")!!
}

View File

@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.source.online.all.EHentaiMetadata
import eu.kanade.tachiyomi.source.online.all.PervEden
import exh.metadata.models.*
import exh.plusAssign
@ -51,12 +50,12 @@ fun ExGalleryMetadata.copyTo(manga: SManga) {
titleObj?.let { manga.title = it }
//Set artist (if we can find one)
tags[EH_ARTIST_NAMESPACE]?.let {
if(it.isNotEmpty()) manga.artist = it.joinToString(transform = Tag::name)
tags.filter { it.namespace == EH_ARTIST_NAMESPACE }.let {
if(it.isNotEmpty()) manga.artist = it.joinToString(transform = { it.name!! })
}
//Set author (if we can find one)
tags[EH_AUTHOR_NAMESPACE]?.let {
if(it.isNotEmpty()) manga.author = it.joinToString(transform = Tag::name)
tags.filter { it.namespace == EH_AUTHOR_NAMESPACE }.let {
if(it.isNotEmpty()) manga.author = it.joinToString(transform = { it.name!! })
}
//Set genre
genre?.let { manga.genre = it }
@ -159,12 +158,12 @@ fun NHentaiMetadata.copyTo(manga: SManga) {
manga.title = englishTitle ?: japaneseTitle ?: shortTitle!!
//Set artist (if we can find one)
tags[NHENTAI_ARTIST_NAMESPACE]?.let {
if(it.isNotEmpty()) manga.artist = it.joinToString(transform = Tag::name)
tags.filter { it.namespace == NHENTAI_ARTIST_NAMESPACE }.let {
if(it.isNotEmpty()) manga.artist = it.joinToString(transform = { it.name!! })
}
tags[NHENTAI_CATEGORIES_NAMESPACE]?.let {
if(it.isNotEmpty()) manga.genre = it.joinToString(transform = Tag::name)
tags.filter { it.namespace == NHENTAI_CATEGORIES_NAMESPACE }.let {
if(it.isNotEmpty()) manga.genre = it.joinToString(transform = { it.name!! })
}
//Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes
@ -209,7 +208,9 @@ fun SearchableGalleryMetadata.genericCopyTo(manga: SManga): Boolean {
private fun buildTagsDescription(metadata: SearchableGalleryMetadata)
= StringBuilder("Tags:\n").apply {
//BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags'
metadata.tags.entries.forEach { namespace, tags ->
metadata.tags.groupBy {
it.namespace
}.entries.forEach { namespace, tags ->
if (tags.isNotEmpty()) {
val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" })
this += "$namespace: $joinedTags\n"

View File

@ -1,22 +1,43 @@
package exh.metadata.models
import android.net.Uri
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.Index
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
import java.util.*
/**
* Gallery metadata storage model
*/
class ExGalleryMetadata : SearchableGalleryMetadata() {
@RealmClass
open class ExGalleryMetadata : RealmObject(), SearchableGalleryMetadata {
@PrimaryKey
override var uuid: String = UUID.randomUUID().toString()
var url: String? = null
@Index
var gId: String? = null
@Index
var gToken: String? = null
@Index
var exh: Boolean? = null
var thumbnailUrl: String? = null
@Index
var title: String? = null
@Index
var altTitle: String? = null
@Index
override var uploader: String? = null
var genre: String? = null
var datePosted: Long? = null
@ -30,22 +51,26 @@ class ExGalleryMetadata : SearchableGalleryMetadata() {
var ratingCount: Int? = null
var averageRating: Double? = null
override var tags: RealmList<Tag> = RealmList()
override fun getTitles() = listOf(title, altTitle).filterNotNull()
private fun splitGalleryUrl()
= url?.let {
Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank)
}
@Ignore
override val titleFields = listOf(
ExGalleryMetadata::title.name,
ExGalleryMetadata::altTitle.name
)
fun galleryId() = splitGalleryUrl()?.let { it[it.size - 2] }
companion object {
private fun splitGalleryUrl(url: String)
= url.let {
Uri.parse(it).pathSegments
.filterNot(String::isNullOrBlank)
}
fun galleryToken() =
splitGalleryUrl()?.last()
fun galleryId(url: String) = splitGalleryUrl(url).let { it[it.size - 2] }
override fun galleryUniqueIdentifier() = exh?.let { exh ->
url?.let {
//Fuck, this should be EXH and EH but it's too late to change it now...
"${if(exh) "EXH" else "EX"}-${galleryId()}-${galleryToken()}"
}
fun galleryToken(url: String) =
splitGalleryUrl(url).last()
}
}

View File

@ -1,40 +1,64 @@
package exh.metadata.models
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.Index
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
import java.util.*
/**
* NHentai metadata
*/
class NHentaiMetadata : SearchableGalleryMetadata() {
@RealmClass
open class NHentaiMetadata : RealmObject(), SearchableGalleryMetadata {
@PrimaryKey
override var uuid: String = UUID.randomUUID().toString()
var id: Long? = null
var nhId: Long? = null
var url get() = id?.let { "$BASE_URL/g/$it" }
var url get() = nhId?.let { "$BASE_URL/g/$it" }
set(a) {
a?.let {
id = a.split("/").last { it.isNotBlank() }.toLong()
nhId = nhIdFromUrl(a)
}
}
@Index
override var uploader: String? = null
var uploadDate: Long? = null
var favoritesCount: Long? = null
var mediaId: String? = null
@Index
var japaneseTitle: String? = null
@Index
var englishTitle: String? = null
@Index
var shortTitle: String? = null
var coverImageType: String? = null
var pageImageTypes: MutableList<String> = mutableListOf()
var pageImageTypes: RealmList<PageImageType> = RealmList()
var thumbnailImageType: String? = null
var scanlator: String? = null
override fun galleryUniqueIdentifier(): String? = "NHENTAI-$id"
override var tags: RealmList<Tag> = RealmList()
override fun getTitles() = listOf(japaneseTitle, englishTitle, shortTitle).filterNotNull()
@Ignore
override val titleFields = listOf(
NHentaiMetadata::japaneseTitle.name,
NHentaiMetadata::englishTitle.name,
NHentaiMetadata::shortTitle.name
)
companion object {
val BASE_URL = "https://nhentai.net"
@ -44,5 +68,27 @@ class NHentaiMetadata : SearchableGalleryMetadata() {
"j" -> "jpg"
else -> null
}
fun nhIdFromUrl(url: String)
= url.split("/").last { it.isNotBlank() }.toLong()
}
}
@RealmClass
open class PageImageType(var type: String? = null): RealmObject() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as PageImageType
if (type != other.type) return false
return true
}
override fun hashCode() = type?.hashCode() ?: 0
override fun toString() = "PageImageType(type=$type)"
}

View File

@ -1,14 +1,33 @@
package exh.metadata.models
import android.net.Uri
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.Index
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
import java.util.*
@RealmClass
open class PervEdenGalleryMetadata : RealmObject(), SearchableGalleryMetadata {
@PrimaryKey
override var uuid: String = UUID.randomUUID().toString()
@Index
var pvId: String? = null
class PervEdenGalleryMetadata : SearchableGalleryMetadata() {
var url: String? = null
var thumbnailUrl: String? = null
@Index
var title: String? = null
var altTitles: MutableList<String> = mutableListOf()
var altTitles: RealmList<PervEdenTitle> = RealmList()
@Index
override var uploader: String? = null
@Index
var artist: String? = null
var type: String? = null
@ -19,14 +38,48 @@ class PervEdenGalleryMetadata : SearchableGalleryMetadata() {
var lang: String? = null
override fun getTitles() = listOf(title).plus(altTitles).filterNotNull()
override var tags: RealmList<Tag> = RealmList()
private fun splitGalleryUrl()
= url?.let {
Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank)
}
override fun getTitles() = listOf(title).plus(altTitles.map {
it.title
}).filterNotNull()
override fun galleryUniqueIdentifier() = splitGalleryUrl()?.let {
"PERVEDEN-${lang?.toUpperCase()}-${it.last()}"
@Ignore
override val titleFields = listOf(
//TODO Somehow include altTitles
PervEdenGalleryMetadata::title.name
)
companion object {
private fun splitGalleryUrl(url: String)
= url.let {
Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank)
}
fun pvIdFromUrl(url: String) = splitGalleryUrl(url).last()
}
}
@RealmClass
open class PervEdenTitle(var metadata: PervEdenGalleryMetadata? = null,
@Index var title: String? = null): RealmObject() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as PervEdenTitle
if (metadata != other.metadata) return false
if (title != other.title) return false
return true
}
override fun hashCode(): Int {
var result = metadata?.hashCode() ?: 0
result = 31 * result + (title?.hashCode() ?: 0)
return result
}
override fun toString() = "PervEdenTitle(metadata=$metadata, title=$title)"
}

View File

@ -1,18 +1,24 @@
package exh.metadata.models
import io.realm.RealmList
import io.realm.RealmModel
import io.realm.annotations.Index
import java.util.ArrayList
import java.util.HashMap
import kotlin.reflect.KCallable
/**
* A gallery that can be searched using the EH search engine
*/
abstract class SearchableGalleryMetadata {
var uploader: String? = null
interface SearchableGalleryMetadata: RealmModel {
var uuid: String
var uploader: String?
//Being specific about which classes are used in generics to make deserialization easier
val tags: HashMap<String, ArrayList<Tag>> = HashMap()
var tags: RealmList<Tag>
abstract fun galleryUniqueIdentifier(): String?
fun getTitles(): List<String>
abstract fun getTitles(): List<String>
val titleFields: List<String>
}

View File

@ -1,7 +1,36 @@
package exh.metadata.models
import io.realm.RealmObject
import io.realm.annotations.Index
import io.realm.annotations.RealmClass
/**
* Simple tag model
*/
data class Tag(var name: String, var light: Boolean)
@RealmClass
open class Tag(@Index var namespace: String? = null,
@Index var name: String? = null,
var light: Boolean? = null): RealmObject() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Tag
if (namespace != other.namespace) return false
if (name != other.name) return false
if (light != other.light) return false
return true
}
override fun hashCode(): Int {
var result = namespace?.hashCode() ?: 0
result = 31 * result + (name?.hashCode() ?: 0)
result = 31 * result + (light?.hashCode() ?: 0)
return result
}
override fun toString() = "Tag(namespace=$namespace, name=$name, light=$light)"
}

View File

@ -2,74 +2,94 @@ package exh.search
import exh.metadata.models.SearchableGalleryMetadata
import exh.metadata.models.Tag
import exh.util.beginLog
import io.realm.Case
import io.realm.RealmResults
class SearchEngine {
private val queryCache = mutableMapOf<String, List<QueryComponent>>()
fun matches(metadata: SearchableGalleryMetadata, query: List<QueryComponent>): Boolean {
fun filterResults(metadata: RealmResults<out SearchableGalleryMetadata>, query: List<QueryComponent>):
RealmResults<out SearchableGalleryMetadata> {
val first = metadata.firstOrNull() ?: return metadata
val rQuery = metadata.where()//.beginLog(SearchableGalleryMetadata::class.java)
var queryEmpty = true
fun matchTagList(tags: Sequence<Tag>,
component: Text): Boolean {
//Match tags
val tagMatcher = if(!component.exact)
component.asLenientRegex()
fun matchTagList(namespace: String?,
component: Text?,
excluded: Boolean) {
if(excluded)
rQuery.not()
else if (queryEmpty)
queryEmpty = false
else
component.asRegex()
//Match beginning of tag
if (tags.find {
tagMatcher.testExact(it.name)
} != null) {
if(component.excluded) return false
} else {
//No tag matched for this component
return false
}
return true
}
rQuery.or()
val cachedTitles = metadata.getTitles().map(String::toLowerCase)
rQuery.beginGroup()
//Match namespace if specified
namespace?.let {
rQuery.equalTo("${SearchableGalleryMetadata::tags.name}.${Tag::namespace.name}",
it,
Case.INSENSITIVE)
}
//Match tag name if specified
component?.let {
rQuery.beginGroup()
val q = if (!it.exact)
it.asLenientTagQueries()
else
listOf(it.asQuery())
q.forEachIndexed { index, s ->
if(index > 0)
rQuery.or()
rQuery.like("${SearchableGalleryMetadata::tags.name}.${Tag::name.name}", s, Case.INSENSITIVE)
}
rQuery.endGroup()
}
rQuery.endGroup()
}
for(component in query) {
if(component is Text) {
if(component.excluded)
rQuery.not()
rQuery.beginGroup()
//Match title
if (cachedTitles.find { component.asRegex().test(it) } != null) {
continue
}
first.titleFields
.forEachIndexed { index, s ->
queryEmpty = false
if(index > 0)
rQuery.or()
rQuery.like(s, component.asLenientTitleQuery(), Case.INSENSITIVE)
}
//Match tags
if(!matchTagList(metadata.tags.entries.asSequence().flatMap { it.value.asSequence() },
component)) return false
matchTagList(null, component, false) //We already deal with exclusions here
rQuery.endGroup()
} else if(component is Namespace) {
if(component.namespace == "uploader") {
queryEmpty = false
//Match uploader
if(!component.tag?.rawTextOnly().equals(metadata.uploader,
ignoreCase = true)) {
return false
}
rQuery.equalTo(SearchableGalleryMetadata::uploader.name,
component.tag!!.rawTextOnly(),
Case.INSENSITIVE)
} else {
if(component.tag!!.components.size > 0) {
//Match namespace
val ns = metadata.tags.entries.asSequence().filter {
it.key == component.namespace
}.flatMap { it.value.asSequence() }
//Match tags
if (!matchTagList(ns, component.tag!!))
return false
//Match namespace + tags
matchTagList(component.namespace, component.tag!!, component.tag!!.excluded)
} else {
//Perform namespace search
val hasNs = metadata.tags.entries.find {
it.key == component.namespace
} != null
if(hasNs && component.excluded)
return false
else if(!hasNs && !component.excluded)
return false
matchTagList(component.namespace, null, component.excluded)
}
}
}
}
return true
return rQuery.findAll()
}
fun parseQuery(query: String) = queryCache.getOrPut(query, {

View File

@ -1,36 +1,51 @@
package exh.search
import exh.anyChar
import ru.lanwen.verbalregex.VerbalExpression
import exh.plusAssign
class Text: QueryComponent() {
val components = mutableListOf<TextComponent>()
private var regex: VerbalExpression? = null
private var lenientRegex: VerbalExpression? = null
private var query: String? = null
private var lenientTitleQuery: String? = null
private var lenientTagQueries: List<String>? = null
private var rawText: String? = null
fun asRegex(): VerbalExpression {
if(regex == null) {
regex = baseBuilder().build()
fun asQuery(): String {
if(query == null) {
query = rBaseBuilder().toString()
}
return regex!!
return query!!
}
fun asLenientRegex(): VerbalExpression {
if(lenientRegex == null) {
lenientRegex = baseBuilder().anything().build()
fun asLenientTitleQuery(): String {
if(lenientTitleQuery == null) {
lenientTitleQuery = StringBuilder("*").append(rBaseBuilder()).append("*").toString()
}
return lenientRegex!!
return lenientTitleQuery!!
}
fun baseBuilder(): VerbalExpression.Builder {
val builder = VerbalExpression.regex()
fun asLenientTagQueries(): List<String> {
if(lenientTagQueries == null) {
lenientTagQueries = listOf(
//Match beginning of tag
rBaseBuilder().append("*").toString(),
//Tag word matcher (that matches multiple words)
//Can't make it match a single word in Realm :(
StringBuilder(" ").append(rBaseBuilder()).append(" ").toString(),
StringBuilder(" ").append(rBaseBuilder()).toString(),
rBaseBuilder().append(" ").toString()
)
}
return lenientTagQueries!!
}
fun rBaseBuilder(): StringBuilder {
val builder = StringBuilder()
for(component in components) {
when(component) {
is StringTextComponent -> builder.then(component.value)
is SingleWildcard -> builder.anyChar()
is MultiWildcard -> builder.anything()
is StringTextComponent -> builder += component.value
is SingleWildcard -> builder += "?"
is MultiWildcard -> builder += "*"
}
}
return builder

View File

@ -9,20 +9,18 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.all.EHentai
import exh.isExSource
import exh.isLewdSource
import exh.metadata.MetadataHelper
import exh.metadata.copyTo
import exh.metadata.genericCopyTo
import exh.metadata.queryMetadataFromManga
import exh.util.defRealm
import exh.util.realmTrans
import timber.log.Timber
import uy.kohesive.injekt.injectLazy
import kotlin.concurrent.thread
class MetadataFetchDialog {
val metadataHelper by lazy { MetadataHelper() }
val db: DatabaseHelper by injectLazy()
val sourceManager: SourceManager by injectLazy()
@ -42,43 +40,45 @@ class MetadataFetchDialog {
.show()
thread {
db.deleteMangasNotInLibrary().executeAsBlocking()
defRealm { realm ->
db.deleteMangasNotInLibrary().executeAsBlocking()
val libraryMangas = db.getLibraryMangas()
.executeAsBlocking()
.filter {
isLewdSource(it.source)
&& metadataHelper.fetchMetadata(it.url, it.source) == null
}
val libraryMangas = db.getLibraryMangas()
.executeAsBlocking()
.filter {
isLewdSource(it.source)
&& realm.queryMetadataFromManga(it).findFirst() == null
}
context.runOnUiThread {
progressDialog.maxProgress = libraryMangas.size
}
//Actual metadata fetch code
libraryMangas.forEachIndexed { i, manga ->
context.runOnUiThread {
progressDialog.setContent("Processing: ${manga.title}")
progressDialog.setProgress(i + 1)
progressDialog.maxProgress = libraryMangas.size
}
try {
val source = sourceManager.get(manga.source)
source?.let {
manga.copyFrom(it.fetchMangaDetails(manga).toBlocking().first())
metadataHelper.fetchMetadata(manga.url, manga.source)?.genericCopyTo(manga)
//Actual metadata fetch code
libraryMangas.forEachIndexed { i, manga ->
context.runOnUiThread {
progressDialog.setContent("Processing: ${manga.title}")
progressDialog.setProgress(i + 1)
}
try {
val source = sourceManager.get(manga.source)
source?.let {
manga.copyFrom(it.fetchMangaDetails(manga).toBlocking().first())
realm.queryMetadataFromManga(manga).findFirst()?.genericCopyTo(manga)
}
} catch (t: Throwable) {
Timber.e(t, "Could not migrate manga!")
}
} catch(t: Throwable) {
Timber.e(t, "Could not migrate manga!")
}
}
context.runOnUiThread {
progressDialog.dismiss()
context.runOnUiThread {
progressDialog.dismiss()
//Enable orientation changes again
context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
//Enable orientation changes again
context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
displayMigrationComplete(context)
displayMigrationComplete(context)
}
}
}
}
@ -106,7 +106,7 @@ class MetadataFetchDialog {
.cancelable(false)
.canceledOnTouchOutside(false)
.dismissListener {
preferenceHelper.migrateLibraryAsked().set(true)
preferenceHelper.migrateLibraryAsked2().set(true)
}.show()
}
}

View File

@ -6,7 +6,8 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import exh.isExSource
import exh.isLewdSource
import exh.metadata.MetadataHelper
import exh.metadata.ehMetaQueryFromUrl
import exh.util.realmTrans
import uy.kohesive.injekt.injectLazy
class UrlMigrator {
@ -14,8 +15,6 @@ class UrlMigrator {
private val prefs: PreferencesHelper by injectLazy()
private val metadataHelper: MetadataHelper by lazy { MetadataHelper() }
fun perform() {
db.inTransaction {
val dbMangas = db.getMangas()
@ -39,33 +38,34 @@ class UrlMigrator {
//Sort possible dups so we can use binary search on it
possibleDups.sortBy { it.url }
badMangas.forEach { manga ->
//Build fixed URL
val urlWithSlash = "/" + manga.url
//Fix metadata if required
val metadata = metadataHelper.fetchEhMetadata(manga.url, isExSource(manga.source))
metadata?.url?.let {
if(it.startsWith("g/")) { //Check if metadata URL has no slash
metadata.url = urlWithSlash //Fix it
metadataHelper.writeGallery(metadata, manga.source) //Write new metadata to disk
realmTrans { realm ->
badMangas.forEach { manga ->
//Build fixed URL
val urlWithSlash = "/" + manga.url
//Fix metadata if required
val metadata = realm.ehMetaQueryFromUrl(manga.url, isExSource(manga.source)).findFirst()
metadata?.url?.let {
if (it.startsWith("g/")) { //Check if metadata URL has no slash
metadata.url = urlWithSlash //Fix it
}
}
}
//If we have a dup (with the fixed url), use the dup instead
val possibleDup = possibleDups.binarySearchBy(urlWithSlash, selector = { it.url })
if(possibleDup >= 0) {
//Make sure it is favorited if we are
if(manga.favorite) {
val dup = possibleDups[possibleDup]
dup.favorite = true
db.insertManga(dup).executeAsBlocking() //Update DB with changes
//If we have a dup (with the fixed url), use the dup instead
val possibleDup = possibleDups.binarySearchBy(urlWithSlash, selector = { it.url })
if (possibleDup >= 0) {
//Make sure it is favorited if we are
if (manga.favorite) {
val dup = possibleDups[possibleDup]
dup.favorite = true
db.insertManga(dup).executeAsBlocking() //Update DB with changes
}
//Delete ourself (but the dup is still there)
db.deleteManga(manga).executeAsBlocking()
return@forEach
}
//Delete ourself (but the dup is still there)
db.deleteManga(manga).executeAsBlocking()
return@forEach
//No dup, correct URL and reinsert ourselves
manga.url = urlWithSlash
db.insertManga(manga).executeAsBlocking()
}
//No dup, correct URL and reinsert ourselves
manga.url = urlWithSlash
db.insertManga(manga).executeAsBlocking()
}
}
}

View File

@ -0,0 +1,550 @@
package exh.util
import io.realm.*
import java.util.*
/**
* 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}"
} ?: ""))
}
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}"
} ?: ""))
}
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}"
} ?: ""))
}
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}"
} ?: ""))
}
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}"
} ?: ""))
}
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}"
} ?: ""))
}
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}"
} ?: ""))
}
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 distinct(fieldName: String): RealmResults<E> {
return query.distinct(fieldName)
}
fun distinctAsync(fieldName: String): RealmResults<E> {
return query.distinctAsync(fieldName)
}
fun distinct(firstFieldName: String, vararg remainingFieldNames: String): RealmResults<E> {
return query.distinct(firstFieldName, *remainingFieldNames)
}
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 findAllSorted(fieldName: String, sortOrder: Sort): RealmResults<E> {
return query.findAllSorted(fieldName, sortOrder)
}
fun findAllSortedAsync(fieldName: String, sortOrder: Sort): RealmResults<E> {
return query.findAllSortedAsync(fieldName, sortOrder)
}
fun findAllSorted(fieldName: String): RealmResults<E> {
return query.findAllSorted(fieldName)
}
fun findAllSortedAsync(fieldName: String): RealmResults<E> {
return query.findAllSortedAsync(fieldName)
}
fun findAllSorted(fieldNames: Array<String>, sortOrders: Array<Sort>): RealmResults<E> {
return query.findAllSorted(fieldNames, sortOrders)
}
fun findAllSortedAsync(fieldNames: Array<String>, sortOrders: Array<Sort>): RealmResults<E> {
return query.findAllSortedAsync(fieldNames, sortOrders)
}
fun findAllSorted(fieldName1: String, sortOrder1: Sort, fieldName2: String, sortOrder2: Sort): RealmResults<E> {
return query.findAllSorted(fieldName1, sortOrder1, fieldName2, sortOrder2)
}
fun findAllSortedAsync(fieldName1: String, sortOrder1: Sort, fieldName2: String, sortOrder2: Sort): RealmResults<E> {
return query.findAllSortedAsync(fieldName1, sortOrder1, fieldName2, sortOrder2)
}
fun findFirst(): E {
return query.findFirst()
}
fun findFirstAsync(): E {
return query.findFirstAsync()
}
}

View File

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