Release v6.1.3

Make search engine synchronous
Offload some search engine tasks to background threads
Minor search engine speedups
This commit is contained in:
NerdNumber9 2017-08-26 03:40:59 -04:00
parent bcc2ec1668
commit 84121ff901
14 changed files with 262 additions and 83 deletions

View File

@ -44,8 +44,8 @@ android {
minSdkVersion 16
targetSdkVersion 25
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
versionCode 6102
versionName "v6.1.2-EH"
versionCode 6103
versionName "v6.1.3-EH"
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""

View File

@ -62,7 +62,8 @@ open class App : Application() {
Realm.init(this)
val config = RealmConfiguration.Builder()
.name("gallery-metadata.realm")
.schemaVersion(1)
.schemaVersion(2)
.deleteRealmIfMigrationNeeded()
.build()
Realm.setDefaultConfiguration(config)

View File

@ -3,16 +3,16 @@ package eu.kanade.tachiyomi.ui.library
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.data.database.models.Manga
import exh.*
import exh.metadata.loadAllMetadata
import exh.metadata.models.ExGalleryMetadata
import exh.metadata.models.NHentaiMetadata
import exh.metadata.models.PervEdenGalleryMetadata
import exh.metadata.models.SearchableGalleryMetadata
import exh.metadata.queryMetadataFromManga
import exh.metadata.syncMangaIds
import exh.search.SearchEngine
import exh.util.defRealm
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import io.realm.RealmResults
import timber.log.Timber
import kotlin.concurrent.thread
/**
@ -20,18 +20,17 @@ import kotlin.concurrent.thread
*
* @param view the fragment containing this adapter.
*/
class LibraryCategoryAdapter(view: LibraryCategoryView) :
class LibraryCategoryAdapter(val view: LibraryCategoryView) :
FlexibleAdapter<LibraryItem>(null, view, true) {
// --> EH
private val searchEngine = SearchEngine()
// <-- EH
/**
* The list of manga in this category.
*/
private var mangas: List<LibraryItem> = emptyList()
// --> EH
private val searchEngine = SearchEngine()
// <-- EH
/**
* Sets a list of manga in the adapter.
*
@ -41,6 +40,17 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
// A copy of manga always unfiltered.
mangas = list.toList()
// Sync manga IDs in background (EH)
thread {
//Wait 1s to reduce UI stutter during animations
Thread.sleep(1000)
defRealm {
it.syncMangaIds(list.map {
it.manga
})
}
}
performFilter()
}
@ -54,43 +64,94 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
}
fun performFilter() {
Observable.fromCallable {
defRealm { realm ->
//TODO Wrap in try catch
if(searchText.isNotBlank()) {
try {
val parsedQuery = searchEngine.parseQuery(searchText)
val metadata = realm.loadAllMetadata().map {
Pair(it.key, searchEngine.filterResults(it.value, parsedQuery))
}
mangas.filter { manga ->
val metadata = view.meta!!.map {
val meta: RealmResults<out SearchableGalleryMetadata> = if (it.value.isNotEmpty())
searchEngine.filterResults(it.value.where(),
parsedQuery,
it.value.first().titleFields)
.findAllSorted(SearchableGalleryMetadata::mangaId.name)
else
it.value
Pair(it.key, meta)
}.toMap()
// --> Possible data set compare algorithm?
// var curUnfilteredMetaIndex = 0
// var curFilteredMetaIndex = 0
// val res = mangas.sortedBy { it.manga.id }.filter { manga ->
val res = mangas.filter { manga ->
// --> EH
try {
if (isLewdSource(manga.manga.source)) {
val hasMeta
= realm.queryMetadataFromManga(manga.manga).count() > 0
if(hasMeta)
metadata.any {
val unfilteredMeta: RealmResults<out SearchableGalleryMetadata>?
val filteredMeta: RealmResults<out SearchableGalleryMetadata>?
when (manga.manga.source) {
EH_SOURCE_ID,
EXH_SOURCE_ID ->
if (it.first != ExGalleryMetadata::class)
return@any false
EXH_SOURCE_ID -> {
unfilteredMeta = view.meta!![ExGalleryMetadata::class]
filteredMeta = metadata[ExGalleryMetadata::class]
}
PERV_EDEN_IT_SOURCE_ID,
PERV_EDEN_EN_SOURCE_ID ->
if (it.first != PervEdenGalleryMetadata::class)
return@any false
NHENTAI_SOURCE_ID ->
if (it.first != NHentaiMetadata::class)
return@any false
PERV_EDEN_EN_SOURCE_ID -> {
unfilteredMeta = view.meta!![PervEdenGalleryMetadata::class]
filteredMeta = metadata[PervEdenGalleryMetadata::class]
}
return@filter realm.queryMetadataFromManga(manga.manga, it.second.where()).count() > 0
NHENTAI_SOURCE_ID -> {
unfilteredMeta = view.meta!![NHentaiMetadata::class]
filteredMeta = metadata[NHentaiMetadata::class]
}
else -> {
unfilteredMeta = null
filteredMeta = null
}
}
/*
--> Possible data set compare algorithm?
var atUnfilteredMeta = unfilteredMeta?.getOrNull(curUnfilteredMetaIndex)
while(atUnfilteredMeta?.mangaId != null
&& atUnfilteredMeta.mangaId!! < manga.manga.id!!) {
curUnfilteredMetaIndex++
atUnfilteredMeta = unfilteredMeta?.getOrNull(curUnfilteredMetaIndex)
}
if(atUnfilteredMeta?.mangaId == manga.manga.id) {
var atFilteredMeta = filteredMeta?.getOrNull(curFilteredMetaIndex)
while(atFilteredMeta?.mangaId != null
&& atFilteredMeta.mangaId!! < manga.manga.id!!) {
curFilteredMetaIndex++
atFilteredMeta = filteredMeta?.getOrNull(curFilteredMetaIndex)
}
return@filter atFilteredMeta?.mangaId == manga.manga.id
}*/
val hasMeta = unfilteredMeta
?.where()
?.equalTo(SearchableGalleryMetadata::mangaId.name, manga.manga.id)
?.count() ?: 0 > 0
if (hasMeta)
return@filter filteredMeta!!.where()
.equalTo(SearchableGalleryMetadata::mangaId.name, manga.manga.id)
.count() > 0
}
} catch(e: Exception) {
Timber.w(e, "Could not filter manga!", manga.manga)
}
manga.filter(searchText)
// <-- EH
}
updateDataSet(res)
} catch(e: Exception) {
Timber.w(e, "Could not filter mangas!")
updateDataSet(mangas)
}
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
updateDataSet(it)
} else {
updateDataSet(mangas)
}
}

View File

@ -16,9 +16,14 @@ import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.util.plusAssign
import eu.kanade.tachiyomi.util.toast
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import exh.metadata.loadAllMetadata
import exh.metadata.models.SearchableGalleryMetadata
import io.realm.Realm
import io.realm.RealmResults
import kotlinx.android.synthetic.main.library_category.view.*
import rx.subscriptions.CompositeSubscription
import uy.kohesive.injekt.injectLazy
import kotlin.reflect.KClass
/**
* Fragment containing the library manga for a certain category.
@ -59,6 +64,14 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
*/
private var subscriptions = CompositeSubscription()
// --> EH
// Cached Realm instance
var realm: Realm? = null
// Cached metadata (auto-updating)
var meta: Map<KClass<out SearchableGalleryMetadata>, RealmResults<out SearchableGalleryMetadata>>? = null
// <-- EH
fun onCreate(controller: LibraryController) {
this.controller = controller
@ -102,6 +115,12 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
fun onBind(category: Category) {
this.category = category
// Cache Realm (EH)
realm?.close()
realm = Realm.getDefaultInstance()?.apply {
meta = loadAllMetadata()
}
adapter.mode = if (controller.selectedMangas.isNotEmpty()) {
FlexibleAdapter.MODE_MULTI
} else {
@ -126,8 +145,22 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
subscriptions.clear()
}
override fun onAttachedToWindow() {
// --> EH
realm?.close()
realm = Realm.getDefaultInstance()?.apply {
meta = loadAllMetadata()
}
// <-- EH
super.onAttachedToWindow()
}
override fun onDetachedFromWindow() {
subscriptions.clear()
// --> EH
meta = null
realm?.close()
// <-- EH
super.onDetachedFromWindow()
}

View File

@ -346,7 +346,7 @@ class LibraryController(
// Debounce search (EH)
searchView.queryTextChanges()
.debounce(200, TimeUnit.MILLISECONDS)
.debounce(350, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy {
query = it.toString()

View File

@ -12,9 +12,23 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import exh.*
import exh.metadata.ehMetaQueryFromUrl
import exh.metadata.models.ExGalleryMetadata
import exh.metadata.models.NHentaiMetadata
import exh.metadata.models.PervEdenGalleryMetadata
import exh.metadata.models.SearchableGalleryMetadata
import exh.metadata.nhentaiMetaQueryFromUrl
import exh.metadata.pervEdenMetaQueryFromUrl
import exh.search.SearchEngine
import exh.util.defRealm
import io.realm.RealmQuery
import kotlinx.android.synthetic.main.catalogue_grid_item.view.*
class LibraryItem(val manga: Manga) : AbstractFlexibleItem<LibraryHolder>(), IFilterable {
// --> EH
private val searchEngine = SearchEngine()
// <-- EH
override fun getLayoutRes(): Int {
return R.layout.catalogue_grid_item
@ -53,6 +67,38 @@ class LibraryItem(val manga: Manga) : AbstractFlexibleItem<LibraryHolder>(), IFi
* @return true if the manga should be included, false otherwise.
*/
override fun filter(constraint: String): Boolean {
defRealm { realm ->
if (isLewdSource(manga.source)) {
val titleFields: List<String>?
var query: RealmQuery<out SearchableGalleryMetadata>?
when (manga.source) {
EH_SOURCE_ID -> {
titleFields = ExGalleryMetadata.TITLE_FIELDS
query = realm.ehMetaQueryFromUrl(manga.url, false)
}
EXH_SOURCE_ID -> {
titleFields = ExGalleryMetadata.TITLE_FIELDS
query = realm.ehMetaQueryFromUrl(manga.url, true)
}
PERV_EDEN_IT_SOURCE_ID,
PERV_EDEN_EN_SOURCE_ID -> {
titleFields = PervEdenGalleryMetadata.TITLE_FIELDS
query = realm.pervEdenMetaQueryFromUrl(manga.url, manga.source)
}
NHENTAI_SOURCE_ID -> {
titleFields = NHentaiMetadata.TITLE_FIELDS
query = realm.nhentaiMetaQueryFromUrl(manga.url)
}
else -> return@defRealm
}
val hasMeta = query!!.count() > 0
if(hasMeta) {
val parsedQuery = searchEngine.parseQuery(constraint)
query = searchEngine.filterResults(query, parsedQuery, titleFields)
return@filter query.count() > 0
}
}
}
return manga.title.contains(constraint, true) ||
(manga.author?.contains(constraint, true) ?: false)
}

View File

@ -31,12 +31,14 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
import eu.kanade.tachiyomi.ui.setting.SettingsMainController
import exh.metadata.loadAllMetadata
import exh.ui.batchadd.BatchAddController
import exh.ui.lock.LockChangeHandler
import exh.ui.lock.LockController
import exh.ui.lock.lockEnabled
import exh.ui.lock.notifyLockSecurity
import exh.ui.migration.MetadataFetchDialog
import exh.util.defRealm
import kotlinx.android.synthetic.main.main_activity.*
import uy.kohesive.injekt.injectLazy
@ -168,9 +170,12 @@ class MainActivity : BaseActivity() {
ChangelogDialogController().showDialog(router)
}
// Migrate metadata to Realm (EH)
if(!preferences.migrateLibraryAsked2().getOrDefault())
MetadataFetchDialog().askMigration(this, false)
// Migrate metadata if empty (EH)
if(!defRealm {
it.loadAllMetadata().any {
it.value.isNotEmpty()
}
}) MetadataFetchDialog().askMigration(this, false)
}
}

View File

@ -11,6 +11,7 @@ import io.realm.Realm
import io.realm.RealmQuery
import io.realm.RealmResults
import rx.Observable
import timber.log.Timber
import kotlin.reflect.KClass
fun Realm.ehMetaQueryFromUrl(url: String,
@ -50,7 +51,7 @@ private fun pervEdenSourceToLang(source: Long)
fun Realm.pervEdenMetaQueryFromUrl(url: String,
source: Long,
meta: RealmQuery<PervEdenGalleryMetadata>?) =
meta: RealmQuery<PervEdenGalleryMetadata>? = null) =
pervEdenMetadataQuery(
PervEdenGalleryMetadata.pvIdFromUrl(url),
source,
@ -74,7 +75,7 @@ fun Realm.loadPervEdenAsync(pvId: String, source: Long): Observable<PervEdenGall
.asObservable()
fun Realm.nhentaiMetaQueryFromUrl(url: String,
meta: RealmQuery<NHentaiMetadata>?) =
meta: RealmQuery<NHentaiMetadata>? = null) =
nhentaiMetadataQuery(
NHentaiMetadata.nhIdFromUrl(url),
meta
@ -95,11 +96,13 @@ fun Realm.loadNhentaiAsync(nhId: Long): Observable<NHentaiMetadata?>
.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())
)
listOf<Pair<KClass<out SearchableGalleryMetadata>, RealmQuery<out SearchableGalleryMetadata>>>(
Pair(ExGalleryMetadata::class, where(ExGalleryMetadata::class.java)),
Pair(NHentaiMetadata::class, where(NHentaiMetadata::class.java)),
Pair(PervEdenGalleryMetadata::class, where(PervEdenGalleryMetadata::class.java))
).map {
Pair(it.first, it.second.findAllSorted(SearchableGalleryMetadata::mangaId.name))
}.toMap()
fun Realm.queryMetadataFromManga(manga: Manga,
meta: RealmQuery<out SearchableGalleryMetadata>? = null): RealmQuery<out SearchableGalleryMetadata> =
@ -112,3 +115,21 @@ fun Realm.queryMetadataFromManga(manga: Manga,
NHENTAI_SOURCE_ID -> nhentaiMetaQueryFromUrl(manga.url, meta as? RealmQuery<NHentaiMetadata>)
else -> throw IllegalArgumentException("Unknown source type!")
}
fun Realm.syncMangaIds(mangas: List<Manga>) {
Timber.d("--> EH: Begin syncing ${mangas.size} manga IDs...")
executeTransaction {
mangas.filter {
isLewdSource(it.source)
}.forEach { manga ->
try {
queryMetadataFromManga(manga).findFirst()?.let { meta ->
meta.mangaId = manga.id
}
} catch(e: Exception) {
Timber.w(e, "Error syncing manga IDs! Ignoring...")
}
}
}
Timber.d("--> EH: Finish syncing ${mangas.size} manga IDs!")
}

View File

@ -56,10 +56,10 @@ open class ExGalleryMetadata : RealmObject(), SearchableGalleryMetadata {
override fun getTitles() = listOf(title, altTitle).filterNotNull()
@Ignore
override val titleFields = listOf(
ExGalleryMetadata::title.name,
ExGalleryMetadata::altTitle.name
)
override val titleFields = TITLE_FIELDS
@Index
override var mangaId: Long? = null
companion object {
private fun splitGalleryUrl(url: String)
@ -72,5 +72,10 @@ open class ExGalleryMetadata : RealmObject(), SearchableGalleryMetadata {
fun galleryToken(url: String) =
splitGalleryUrl(url).last()
val TITLE_FIELDS = listOf(
ExGalleryMetadata::title.name,
ExGalleryMetadata::altTitle.name
)
}
}

View File

@ -53,11 +53,10 @@ open class NHentaiMetadata : RealmObject(), SearchableGalleryMetadata {
override fun getTitles() = listOf(japaneseTitle, englishTitle, shortTitle).filterNotNull()
@Ignore
override val titleFields = listOf(
NHentaiMetadata::japaneseTitle.name,
NHentaiMetadata::englishTitle.name,
NHentaiMetadata::shortTitle.name
)
override val titleFields = TITLE_FIELDS
@Index
override var mangaId: Long? = null
companion object {
val BASE_URL = "https://nhentai.net"
@ -71,6 +70,12 @@ open class NHentaiMetadata : RealmObject(), SearchableGalleryMetadata {
fun nhIdFromUrl(url: String)
= url.split("/").last { it.isNotBlank() }.toLong()
val TITLE_FIELDS = listOf(
NHentaiMetadata::japaneseTitle.name,
NHentaiMetadata::englishTitle.name,
NHentaiMetadata::shortTitle.name
)
}
}

View File

@ -45,10 +45,10 @@ open class PervEdenGalleryMetadata : RealmObject(), SearchableGalleryMetadata {
}).filterNotNull()
@Ignore
override val titleFields = listOf(
//TODO Somehow include altTitles
PervEdenGalleryMetadata::title.name
)
override val titleFields = TITLE_FIELDS
@Index
override var mangaId: Long? = null
companion object {
private fun splitGalleryUrl(url: String)
@ -57,6 +57,11 @@ open class PervEdenGalleryMetadata : RealmObject(), SearchableGalleryMetadata {
}
fun pvIdFromUrl(url: String) = splitGalleryUrl(url).last()
val TITLE_FIELDS = listOf(
//TODO Somehow include altTitles
PervEdenGalleryMetadata::title.name
)
}
}

View File

@ -21,4 +21,6 @@ interface SearchableGalleryMetadata: RealmModel {
fun getTitles(): List<String>
val titleFields: List<String>
var mangaId: Long?
}

View File

@ -2,18 +2,17 @@ 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
import io.realm.RealmQuery
class SearchEngine {
private val queryCache = mutableMapOf<String, List<QueryComponent>>()
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)
fun <T : SearchableGalleryMetadata> filterResults(rQuery: RealmQuery<T>,
query: List<QueryComponent>,
titleFields: List<String>):
RealmQuery<T> {
var queryEmpty = true
fun matchTagList(namespace: String?,
@ -59,8 +58,7 @@ class SearchEngine {
rQuery.beginGroup()
//Match title
first.titleFields
.forEachIndexed { index, s ->
titleFields.forEachIndexed { index, s ->
queryEmpty = false
if(index > 0)
rQuery.or()
@ -89,7 +87,7 @@ class SearchEngine {
}
}
}
return rQuery.findAll()
return rQuery
}
fun parseQuery(query: String) = queryCache.getOrPut(query, {

View File

@ -88,7 +88,6 @@ class MetadataFetchDialog {
db.getLibraryMangas().asRxSingle().subscribe {
if(!explicit && it.isEmpty()) {
//Do not open dialog on startup if no manga
preferenceHelper.migrateLibraryAsked2().set(true)
} else {
//Not logged in but have ExHentai galleries
if (!preferenceHelper.enableExhentai().getOrDefault()) {
@ -109,9 +108,7 @@ class MetadataFetchDialog {
.onNegative({ _, _ -> adviseMigrationLater(activity) })
.cancelable(false)
.canceledOnTouchOutside(false)
.dismissListener {
preferenceHelper.migrateLibraryAsked2().set(true)
}.show()
.show()
}
}
}