mirror of
https://github.com/mihonapp/mihon.git
synced 2025-06-28 20:17:51 +02:00
Migrate to realm for metadata
This commit is contained in:
@ -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
|
||||
|
@ -1,5 +0,0 @@
|
||||
package exh
|
||||
|
||||
import ru.lanwen.verbalregex.VerbalExpression
|
||||
|
||||
fun VerbalExpression.Builder.anyChar() = add(".")!!
|
@ -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")!!
|
||||
}
|
@ -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"
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)"
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
|
@ -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>
|
||||
}
|
@ -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)"
|
||||
}
|
||||
|
@ -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, {
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
550
app/src/main/java/exh/util/LoggingRealmQuery.kt
Normal file
550
app/src/main/java/exh/util/LoggingRealmQuery.kt
Normal 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()
|
||||
}
|
||||
}
|
39
app/src/main/java/exh/util/RealmUtil.kt
Normal file
39
app/src/main/java/exh/util/RealmUtil.kt
Normal 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())
|
Reference in New Issue
Block a user