It Builds!
This commit is contained in:
@@ -2,29 +2,19 @@ package exh
|
||||
|
||||
import android.content.Context
|
||||
import com.elvishew.xlog.XLog
|
||||
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
|
||||
import eu.kanade.tachiyomi.data.backup.models.DHistory
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaUrlPutResolver
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.data.updater.UpdaterJob
|
||||
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
||||
import eu.kanade.tachiyomi.util.system.jobScheduler
|
||||
import exh.source.BlacklistedSources
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import java.net.URISyntaxException
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
object EXHMigrations {
|
||||
private val db: DatabaseHelper by injectLazy()
|
||||
@@ -42,122 +32,8 @@ object EXHMigrations {
|
||||
val oldVersion = preferences.eh_lastVersionCode().getOrDefault()
|
||||
try {
|
||||
if (oldVersion < BuildConfig.VERSION_CODE) {
|
||||
if (oldVersion < 1) {
|
||||
db.inTransaction {
|
||||
// Migrate HentaiCafe source IDs
|
||||
db.lowLevel().executeSQL(
|
||||
RawQuery.builder()
|
||||
.query(
|
||||
"""
|
||||
UPDATE ${MangaTable.TABLE}
|
||||
SET ${MangaTable.COL_SOURCE} = $HENTAI_CAFE_SOURCE_ID
|
||||
WHERE ${MangaTable.COL_SOURCE} = 6908
|
||||
""".trimIndent()
|
||||
)
|
||||
.affectsTables(MangaTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
|
||||
// Migrate nhentai URLs
|
||||
val nhentaiManga = db.db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_SOURCE} = $NHENTAI_SOURCE_ID")
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
.executeAsBlocking()
|
||||
|
||||
nhentaiManga.forEach {
|
||||
it.url = getUrlWithoutDomain(it.url)
|
||||
}
|
||||
|
||||
db.db.put()
|
||||
.objects(nhentaiManga)
|
||||
// Extremely slow without the resolver :/
|
||||
.withPutResolver(MangaUrlPutResolver())
|
||||
.prepare()
|
||||
.executeAsBlocking()
|
||||
}
|
||||
}
|
||||
|
||||
// Backup database in next release
|
||||
if (oldVersion < 2) {
|
||||
backupDatabase(context, oldVersion)
|
||||
}
|
||||
|
||||
if (oldVersion < 8405) {
|
||||
db.inTransaction {
|
||||
// Migrate HBrowse source IDs
|
||||
db.lowLevel().executeSQL(
|
||||
RawQuery.builder()
|
||||
.query(
|
||||
"""
|
||||
UPDATE ${MangaTable.TABLE}
|
||||
SET ${MangaTable.COL_SOURCE} = $HBROWSE_SOURCE_ID
|
||||
WHERE ${MangaTable.COL_SOURCE} = 1401584337232758222
|
||||
""".trimIndent()
|
||||
)
|
||||
.affectsTables(MangaTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
// Cancel old scheduler jobs with old ids
|
||||
context.jobScheduler.cancelAll()
|
||||
}
|
||||
if (oldVersion < 8408) {
|
||||
db.inTransaction {
|
||||
// Migrate Tsumino source IDs
|
||||
db.lowLevel().executeSQL(
|
||||
RawQuery.builder()
|
||||
.query(
|
||||
"""
|
||||
UPDATE ${MangaTable.TABLE}
|
||||
SET ${MangaTable.COL_SOURCE} = $TSUMINO_SOURCE_ID
|
||||
WHERE ${MangaTable.COL_SOURCE} = 6909
|
||||
""".trimIndent()
|
||||
)
|
||||
.affectsTables(MangaTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
if (oldVersion < 8409) {
|
||||
db.inTransaction {
|
||||
// Migrate tsumino URLs
|
||||
val tsuminoManga = db.db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_SOURCE} = $TSUMINO_SOURCE_ID")
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
.executeAsBlocking()
|
||||
tsuminoManga.forEach {
|
||||
it.url = "/entry/"+it.url.split("/").last()
|
||||
}
|
||||
|
||||
db.db.put()
|
||||
.objects(tsuminoManga)
|
||||
// Extremely slow without the resolver :/
|
||||
.withPutResolver(MangaUrlPutResolver())
|
||||
.prepare()
|
||||
.executeAsBlocking()
|
||||
}
|
||||
}
|
||||
if (oldVersion < 8410) {
|
||||
// Migrate to WorkManager
|
||||
UpdaterJob.setupTask(context)
|
||||
LibraryUpdateJob.setupTask(context)
|
||||
BackupCreatorJob.setupTask(context)
|
||||
ExtensionUpdateJob.setupTask(context)
|
||||
}
|
||||
|
||||
// if (oldVersion < 1) { }
|
||||
// do stuff here when releasing changed crap
|
||||
|
||||
// TODO BE CAREFUL TO NOT FUCK UP MergedSources IF CHANGING URLs
|
||||
|
||||
@@ -165,61 +41,61 @@ object EXHMigrations {
|
||||
|
||||
return true
|
||||
}
|
||||
} catch(e: Exception) {
|
||||
logger.e( "Failed to migrate app from $oldVersion -> ${BuildConfig.VERSION_CODE}!", e)
|
||||
} catch (e: Exception) {
|
||||
logger.e("Failed to migrate app from $oldVersion -> ${BuildConfig.VERSION_CODE}!", e)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun migrateBackupEntry(backupEntry: BackupEntry): Observable<BackupEntry> {
|
||||
fun migrateBackupEntry(backupEntry: BackupEntry): BackupEntry {
|
||||
val (manga, chapters, categories, history, tracks) = backupEntry
|
||||
|
||||
// Migrate HentaiCafe source IDs
|
||||
if(manga.source == 6908L) {
|
||||
if (manga.source == 6908L) {
|
||||
manga.source = HENTAI_CAFE_SOURCE_ID
|
||||
}
|
||||
|
||||
// Migrate Tsumino source IDs
|
||||
if(manga.source == 6909L) {
|
||||
if (manga.source == 6909L) {
|
||||
manga.source = TSUMINO_SOURCE_ID
|
||||
}
|
||||
|
||||
// Migrate nhentai URLs
|
||||
if(manga.source == NHENTAI_SOURCE_ID) {
|
||||
if (manga.source == NHENTAI_SOURCE_ID) {
|
||||
manga.url = getUrlWithoutDomain(manga.url)
|
||||
}
|
||||
|
||||
// Allow importing of nhentai extension backups
|
||||
if(manga.source in BlacklistedSources.NHENTAI_EXT_SOURCES) {
|
||||
if (manga.source in BlacklistedSources.NHENTAI_EXT_SOURCES) {
|
||||
manga.source = NHENTAI_SOURCE_ID
|
||||
}
|
||||
|
||||
// Allow importing of English PervEden extension backups
|
||||
if(manga.source in BlacklistedSources.PERVEDEN_EN_EXT_SOURCES) {
|
||||
if (manga.source in BlacklistedSources.PERVEDEN_EN_EXT_SOURCES) {
|
||||
manga.source = PERV_EDEN_EN_SOURCE_ID
|
||||
}
|
||||
|
||||
// Allow importing of Italian PervEden extension backups
|
||||
if(manga.source in BlacklistedSources.PERVEDEN_IT_EXT_SOURCES) {
|
||||
if (manga.source in BlacklistedSources.PERVEDEN_IT_EXT_SOURCES) {
|
||||
manga.source = PERV_EDEN_IT_SOURCE_ID
|
||||
}
|
||||
|
||||
// Allow importing of EHentai extension backups
|
||||
if(manga.source in BlacklistedSources.EHENTAI_EXT_SOURCES) {
|
||||
if (manga.source in BlacklistedSources.EHENTAI_EXT_SOURCES) {
|
||||
manga.source = EH_SOURCE_ID
|
||||
}
|
||||
|
||||
return Observable.just(backupEntry)
|
||||
return backupEntry
|
||||
}
|
||||
|
||||
private fun backupDatabase(context: Context, oldMigrationVersion: Int) {
|
||||
val backupLocation = File(File(context.filesDir, "exh_db_bck"), "$oldMigrationVersion.bck.db")
|
||||
if(backupLocation.exists()) return // Do not backup same version twice
|
||||
if (backupLocation.exists()) return // Do not backup same version twice
|
||||
|
||||
val dbLocation = context.getDatabasePath(db.lowLevel().sqliteOpenHelper().databaseName)
|
||||
try {
|
||||
dbLocation.copyTo(backupLocation, overwrite = true)
|
||||
} catch(t: Throwable) {
|
||||
} catch (t: Throwable) {
|
||||
XLog.w("Failed to backup database!")
|
||||
}
|
||||
}
|
||||
@@ -242,9 +118,9 @@ object EXHMigrations {
|
||||
}
|
||||
|
||||
data class BackupEntry(
|
||||
val manga: Manga,
|
||||
val chapters: List<Chapter>,
|
||||
val categories: List<String>,
|
||||
val history: List<DHistory>,
|
||||
val tracks: List<Track>
|
||||
)
|
||||
val manga: Manga,
|
||||
val chapters: List<Chapter>,
|
||||
val categories: List<String>,
|
||||
val history: List<DHistory>,
|
||||
val tracks: List<Track>
|
||||
)
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package exh.metadata.sql.models
|
||||
|
||||
data class SearchMetadata(
|
||||
// Manga ID this gallery is linked to
|
||||
// Manga ID this gallery is linked to
|
||||
val mangaId: Long,
|
||||
|
||||
// Gallery uploader
|
||||
// Gallery uploader
|
||||
val uploader: String?,
|
||||
|
||||
// Extra data attached to this metadata, in JSON format
|
||||
// Extra data attached to this metadata, in JSON format
|
||||
val extra: String,
|
||||
|
||||
// Indexed extra data attached to this metadata
|
||||
// Indexed extra data attached to this metadata
|
||||
val indexedExtra: String?,
|
||||
|
||||
// The version of this metadata's extra. Used to track changes to the 'extra' field's schema
|
||||
// The version of this metadata's extra. Used to track changes to the 'extra' field's schema
|
||||
val extraVersion: Int
|
||||
) {
|
||||
// Transient information attached to this piece of metadata, useful for caching
|
||||
|
||||
@@ -9,36 +9,44 @@ import exh.metadata.sql.tables.SearchMetadataTable
|
||||
interface SearchMetadataQueries : DbProvider {
|
||||
|
||||
fun getSearchMetadataForManga(mangaId: Long) = db.get()
|
||||
.`object`(SearchMetadata::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(SearchMetadataTable.TABLE)
|
||||
.where("${SearchMetadataTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(mangaId)
|
||||
.build())
|
||||
.prepare()
|
||||
.`object`(SearchMetadata::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(SearchMetadataTable.TABLE)
|
||||
.where("${SearchMetadataTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(mangaId)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getSearchMetadata() = db.get()
|
||||
.listOfObjects(SearchMetadata::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(SearchMetadataTable.TABLE)
|
||||
.build())
|
||||
.prepare()
|
||||
.listOfObjects(SearchMetadata::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(SearchMetadataTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getSearchMetadataByIndexedExtra(extra: String) = db.get()
|
||||
.listOfObjects(SearchMetadata::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.table(SearchMetadataTable.TABLE)
|
||||
.where("${SearchMetadataTable.COL_INDEXED_EXTRA} = ?")
|
||||
.whereArgs(extra)
|
||||
.build())
|
||||
.prepare()
|
||||
.listOfObjects(SearchMetadata::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(SearchMetadataTable.TABLE)
|
||||
.where("${SearchMetadataTable.COL_INDEXED_EXTRA} = ?")
|
||||
.whereArgs(extra)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun insertSearchMetadata(metadata: SearchMetadata) = db.put().`object`(metadata).prepare()
|
||||
|
||||
fun deleteSearchMetadata(metadata: SearchMetadata) = db.delete().`object`(metadata).prepare()
|
||||
|
||||
fun deleteAllSearchMetadata() = db.delete().byQuery(DeleteQuery.builder()
|
||||
fun deleteAllSearchMetadata() = db.delete().byQuery(
|
||||
DeleteQuery.builder()
|
||||
.table(SearchMetadataTable.TABLE)
|
||||
.build())
|
||||
.prepare()
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
}
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
package exh.ui.migration
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.os.Build
|
||||
import android.text.Html
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import eu.kanade.tachiyomi.R
|
||||
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 exh.EXH_SOURCE_ID
|
||||
import exh.isLewdSource
|
||||
import kotlin.concurrent.thread
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class MetadataFetchDialog {
|
||||
|
||||
val db: DatabaseHelper by injectLazy()
|
||||
|
||||
val sourceManager: SourceManager by injectLazy()
|
||||
|
||||
val preferenceHelper: PreferencesHelper by injectLazy()
|
||||
|
||||
fun show(context: Activity) {
|
||||
// Too lazy to actually deal with orientation changes
|
||||
context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
||||
|
||||
var running = true
|
||||
|
||||
val progressDialog = MaterialDialog.Builder(context)
|
||||
.title("Fetching library metadata")
|
||||
.content("Preparing library")
|
||||
.progress(false, 0, true)
|
||||
.negativeText("Stop")
|
||||
.onNegative { dialog, which ->
|
||||
running = false
|
||||
dialog.dismiss()
|
||||
notifyMigrationStopped(context)
|
||||
}
|
||||
.cancelable(false)
|
||||
.canceledOnTouchOutside(false)
|
||||
.show()
|
||||
|
||||
thread {
|
||||
val libraryMangas = db.getLibraryMangas().executeAsBlocking()
|
||||
.filter { isLewdSource(it.source) }
|
||||
.distinctBy { it.id }
|
||||
|
||||
context.runOnUiThread {
|
||||
progressDialog.maxProgress = libraryMangas.size
|
||||
}
|
||||
|
||||
val mangaWithMissingMetadata = libraryMangas
|
||||
.filterIndexed { index, libraryManga ->
|
||||
if (index % 100 == 0) {
|
||||
context.runOnUiThread {
|
||||
progressDialog.setContent("[Stage 1/2] Scanning for missing metadata...")
|
||||
progressDialog.setProgress(index + 1)
|
||||
}
|
||||
}
|
||||
db.getSearchMetadataForManga(libraryManga.id!!).executeAsBlocking() == null
|
||||
}
|
||||
.toList()
|
||||
|
||||
context.runOnUiThread {
|
||||
progressDialog.maxProgress = mangaWithMissingMetadata.size
|
||||
}
|
||||
|
||||
// Actual metadata fetch code
|
||||
for ((i, manga) in mangaWithMissingMetadata.withIndex()) {
|
||||
if (!running) break
|
||||
context.runOnUiThread {
|
||||
progressDialog.setContent("[Stage 2/2] Processing: ${manga.title}")
|
||||
progressDialog.setProgress(i + 1)
|
||||
}
|
||||
try {
|
||||
val source = sourceManager.get(manga.source)
|
||||
source?.let {
|
||||
manga.copyFrom(it.fetchMangaDetails(manga).toBlocking().first())
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "Could not migrate manga!")
|
||||
}
|
||||
}
|
||||
|
||||
context.runOnUiThread {
|
||||
// Ensure activity still exists before we do anything to the activity
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 || !context.isDestroyed) {
|
||||
progressDialog.dismiss()
|
||||
|
||||
// Enable orientation changes again
|
||||
context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
||||
|
||||
if (running) displayMigrationComplete(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun askMigration(activity: Activity, explicit: Boolean) {
|
||||
var extra = ""
|
||||
db.getLibraryMangas().asRxSingle().subscribe {
|
||||
if (!explicit && it.none { isLewdSource(it.source) }) {
|
||||
// Do not open dialog on startup if no manga
|
||||
// Also do not check again
|
||||
preferenceHelper.migrateLibraryAsked().set(true)
|
||||
} else {
|
||||
// Not logged in but have ExHentai galleries
|
||||
if (!preferenceHelper.enableExhentai().getOrDefault()) {
|
||||
it.find { it.source == EXH_SOURCE_ID }?.let {
|
||||
extra = "<b><font color='red'>If you use ExHentai, please log in first before fetching your library metadata!</font></b><br><br>"
|
||||
}
|
||||
}
|
||||
activity.runOnUiThread {
|
||||
MaterialDialog.Builder(activity)
|
||||
.title("Fetch library metadata")
|
||||
.content(Html.fromHtml("You need to fetch your library's metadata before tag searching in the library will function.<br><br>" +
|
||||
"This process may take a long time depending on your library size and will also use up a significant amount of internet bandwidth but can be stopped and started whenever you wish.<br><br>" +
|
||||
extra +
|
||||
"This process can be done later if required."))
|
||||
.positiveText("Migrate")
|
||||
.negativeText("Later")
|
||||
.onPositive { _, _ -> show(activity) }
|
||||
.onNegative { _, _ -> adviseMigrationLater(activity) }
|
||||
.onAny { _, _ -> preferenceHelper.migrateLibraryAsked().set(true) }
|
||||
.cancelable(false)
|
||||
.canceledOnTouchOutside(false)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun adviseMigrationLater(activity: Activity) {
|
||||
MaterialDialog.Builder(activity)
|
||||
.title("Metadata fetch canceled")
|
||||
.content("Library metadata fetch has been canceled.\n\n" +
|
||||
"You can run this operation later by going to: Settings > Advanced > Migrate library metadata")
|
||||
.positiveText("Ok")
|
||||
.cancelable(true)
|
||||
.canceledOnTouchOutside(true)
|
||||
.show()
|
||||
}
|
||||
|
||||
fun notifyMigrationStopped(activity: Activity) {
|
||||
MaterialDialog.Builder(activity)
|
||||
.title("Metadata fetch stopped")
|
||||
.content("Library metadata fetch has been stopped.\n\n" +
|
||||
"You can continue this operation later by going to: Settings > Advanced > Migrate library metadata")
|
||||
.positiveText("Ok")
|
||||
.cancelable(true)
|
||||
.canceledOnTouchOutside(true)
|
||||
.show()
|
||||
}
|
||||
|
||||
fun displayMigrationComplete(activity: Activity) {
|
||||
MaterialDialog.Builder(activity)
|
||||
.title("Migration complete")
|
||||
.content("${activity.getString(R.string.app_name)} is now ready for use!")
|
||||
.positiveText("Ok")
|
||||
.cancelable(true)
|
||||
.canceledOnTouchOutside(true)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,9 @@ import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||
import eu.kanade.tachiyomi.ui.browse.source.SourceController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.ui.source.SourceController
|
||||
import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import eu.kanade.tachiyomi.ui.source.SourceController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.SourceController
|
||||
import exh.smartsearch.SmartSearchEngine
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package exh.util
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import okhttp3.Call
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
import rx.Producer
|
||||
import rx.Subscription
|
||||
|
||||
fun Call.asObservableWithAsyncStacktrace(): Observable<Pair<Exception, Response>> {
|
||||
// Record stacktrace at creation time for easier debugging
|
||||
// asObservable is involved in a lot of crashes so this is worth the performance hit
|
||||
val asyncStackTrace = Exception("Async stacktrace")
|
||||
|
||||
return Observable.unsafeCreate { subscriber ->
|
||||
// Since Call is a one-shot type, clone it for each new subscriber.
|
||||
val call = clone()
|
||||
|
||||
// Wrap the call in a helper which handles both unsubscription and backpressure.
|
||||
val requestArbiter = object : AtomicBoolean(), Producer, Subscription {
|
||||
val executed = AtomicBoolean(false)
|
||||
|
||||
override fun request(n: Long) {
|
||||
if (n == 0L || !compareAndSet(false, true)) return
|
||||
|
||||
try {
|
||||
val response = call.execute()
|
||||
executed.set(true)
|
||||
if (!subscriber.isUnsubscribed) {
|
||||
subscriber.onNext(asyncStackTrace to response)
|
||||
subscriber.onCompleted()
|
||||
}
|
||||
} catch (error: Throwable) {
|
||||
if (!subscriber.isUnsubscribed) {
|
||||
subscriber.onError(error.withRootCause(asyncStackTrace))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun unsubscribe() {
|
||||
if (!executed.get()) {
|
||||
call.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
override fun isUnsubscribed(): Boolean {
|
||||
return call.isCanceled()
|
||||
}
|
||||
}
|
||||
|
||||
subscriber.add(requestArbiter)
|
||||
subscriber.setProducer(requestArbiter)
|
||||
}
|
||||
}
|
||||
@@ -3,3 +3,7 @@ package exh.util
|
||||
fun List<String>.trimAll() = map { it.trim() }
|
||||
fun List<String>.dropBlank() = filter { it.isNotBlank() }
|
||||
fun List<String>.dropEmpty() = filter { it.isNotEmpty() }
|
||||
|
||||
fun String.removeArticles(): String {
|
||||
return this.replace(Regex("^(an|a|the) ", RegexOption.IGNORE_CASE), "")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user