It Builds!

This commit is contained in:
Jobobby04
2020-05-03 18:34:46 -04:00
parent e9ff202851
commit bef0a44447
71 changed files with 1416 additions and 1095 deletions
+22 -146
View File
@@ -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)
}
}
+4
View File
@@ -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), "")
}