Cleanup of olf auto migration

(cherry picked from commit d64754e3e09e92b2e65c181b0b5e84b531490662)
This commit is contained in:
jobobby04
2020-04-17 00:09:26 -04:00
committed by Jobobby04
parent a6f0e7f9b9
commit 62df1263b1
13 changed files with 179 additions and 1147 deletions
@@ -1,16 +0,0 @@
package eu.kanade.tachiyomi.ui.migration
class MigrationStatus {
companion object {
val NOT_INITIALIZED = -1
val COMPLETED = 0
// Migration process
val NOTIFY_USER = 1
val OPEN_BACKUP_MENU = 2
val PERFORM_BACKUP = 3
val FINALIZE_MIGRATION = 4
val MAX_MIGRATION_STEPS = 2
}
}
@@ -104,9 +104,9 @@ class MigrationBottomSheetDialog(
private fun setFlags() {
var flags = 0
if (mig_chapters.isChecked) flags = flags or MigrationFlags.CHAPTERS
if (mig_categories.isChecked) flags = flags or MigrationFlags.CATEGORIES
if (mig_categories.isChecked) flags = flags or MigrationFlags.TRACK
if(mig_chapters.isChecked) flags = flags or MigrationFlags.CHAPTERS
if(mig_categories.isChecked) flags = flags or MigrationFlags.CATEGORIES
if(mig_tracking.isChecked) flags = flags or MigrationFlags.TRACK
preferences.migrateFlags().set(flags)
}
@@ -1,19 +0,0 @@
package eu.kanade.tachiyomi.ui.migration.manga.process
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import androidx.viewpager.widget.ViewPager
class DeactivatableViewPager : ViewPager {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
override fun onTouchEvent(event: MotionEvent): Boolean {
return !isEnabled || super.onTouchEvent(event)
}
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
return isEnabled && super.onInterceptTouchEvent(event)
}
}
@@ -1,300 +0,0 @@
package eu.kanade.tachiyomi.ui.migration.manga.process
import android.view.View
import android.view.ViewGroup
import androidx.viewpager.widget.PagerAdapter
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.gson.Gson
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.util.view.visible
import exh.MERGED_SOURCE_ID
import java.text.DateFormat
import java.text.DecimalFormat
import java.util.Date
import kotlin.coroutines.CoroutineContext
import kotlinx.android.synthetic.main.migration_manga_card.view.loading_group
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_artist
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_author
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_chapters
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_cover
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_full_title
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_last_chapter
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_last_update
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_source
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_source_label
import kotlinx.android.synthetic.main.migration_manga_card.view.manga_status
import kotlinx.android.synthetic.main.migration_manga_card.view.search_progress
import kotlinx.android.synthetic.main.migration_manga_card.view.search_status
import kotlinx.android.synthetic.main.migration_process_item.view.accept_migration
import kotlinx.android.synthetic.main.migration_process_item.view.migrating_frame
import kotlinx.android.synthetic.main.migration_process_item.view.migration_manga_card_from
import kotlinx.android.synthetic.main.migration_process_item.view.migration_manga_card_to
import kotlinx.android.synthetic.main.migration_process_item.view.skip_migration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.injectLazy
class MigrationProcedureAdapter(
val controller: MigrationProcedureController,
val migratingManga: List<MigratingManga>,
override val coroutineContext: CoroutineContext
) : PagerAdapter(), CoroutineScope {
private val db: DatabaseHelper by injectLazy()
private val gson: Gson by injectLazy()
private val sourceManager: SourceManager by injectLazy()
override fun isViewFromObject(p0: View, p1: Any): Boolean {
return p0 == p1
}
override fun getCount() = migratingManga.size
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val item = migratingManga[position]
val view = container.inflate(R.layout.migration_process_item)
container.addView(view)
view.skip_migration.setOnClickListener {
// controller.nextMigration()
}
val viewTag = ViewTag(coroutineContext)
view.tag = viewTag
view.setupView(viewTag, item)
view.accept_migration.setOnClickListener {
viewTag.launch(Dispatchers.Main) {
view.migrating_frame.visible()
try {
withContext(Dispatchers.Default) {
performMigration(item)
}
controller.nextMigration()
} catch (e: Exception) {
controller.migrationFailure()
}
view.migrating_frame.gone()
}
}
return view
}
suspend fun performMigration(manga: MigratingManga) {
if (!manga.searchResult.initialized) {
return
}
val toMangaObj = db.getManga(manga.searchResult.get() ?: return).executeAsBlocking() ?: return
withContext(Dispatchers.IO) {
migrateMangaInternal(
manga.manga() ?: return@withContext,
toMangaObj,
false
)
}
}
private fun migrateMangaInternal(
prevManga: Manga,
manga: Manga,
replace: Boolean
) {
val config = controller.config ?: return
// db.inTransaction {
// Update chapters read
/* if (MigrationFlags.hasChapters(controller.config.migrationFlags)) {
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
val maxChapterRead = prevMangaChapters.filter { it.read }
.maxBy { it.chapter_number }?.chapter_number
if (maxChapterRead != null) {
val dbChapters = db.getChapters(manga).executeAsBlocking()
for (chapter in dbChapters) {
if (chapter.isRecognizedNumber && chapter.chapter_number <= maxChapterRead) {
chapter.read = true
}
}
db.insertChapters(dbChapters).executeAsBlocking()
}
}
// Update categories
if (MigrationFlags.hasCategories(controller.config.migrationFlags)) {
val categories = db.getCategoriesForManga(prevManga).executeAsBlocking()
val mangaCategories = categories.map { MangaCategory.create(manga, it) }
db.setMangaCategories(mangaCategories, listOf(manga))
}
// Update track
if (MigrationFlags.hasTracks(controller.config.migrationFlags)) {
val tracks = db.getTracks(prevManga).executeAsBlocking()
for (track in tracks) {
track.id = null
track.manga_id = manga.id!!
}
db.insertTracks(tracks).executeAsBlocking()
}
// Update favorite status
if (replace) {
prevManga.favorite = false
db.updateMangaFavorite(prevManga).executeAsBlocking()
}
manga.favorite = true
db.updateMangaFavorite(manga).executeAsBlocking()
// SearchPresenter#networkToLocalManga may have updated the manga title, so ensure db gets updated title
db.updateMangaTitle(manga).executeAsBlocking()
//}*/
}
fun View.setupView(tag: ViewTag, migratingManga: MigratingManga) {
tag.launch {
val manga = migratingManga.manga()
val source = migratingManga.mangaSource()
if (manga != null) {
withContext(Dispatchers.Main) {
migration_manga_card_from.loading_group.gone()
migration_manga_card_from.attachManga(tag, manga, source)
migration_manga_card_from.setOnClickListener {
controller.router.pushController(MangaController(manga, true).withFadeTransaction())
}
}
tag.launch {
migratingManga.progress.asFlow().collect { (max, progress) ->
withContext(Dispatchers.Main) {
migration_manga_card_to.search_progress.let { progressBar ->
progressBar.max = max
progressBar.progress = progress
}
}
}
}
val searchResult = migratingManga.searchResult.get()?.let {
db.getManga(it).executeAsBlocking()
}
val resultSource = searchResult?.source?.let {
sourceManager.get(it)
}
withContext(Dispatchers.Main) {
if (searchResult != null && resultSource != null) {
migration_manga_card_to.loading_group.gone()
migration_manga_card_to.attachManga(tag, searchResult, resultSource)
migration_manga_card_to.setOnClickListener {
controller.router.pushController(MangaController(searchResult, true).withFadeTransaction())
}
accept_migration.isEnabled = true
accept_migration.alpha = 1.0f
} else {
migration_manga_card_to.search_progress.gone()
migration_manga_card_to.search_status.text = "Found no manga"
}
}
}
}
}
suspend fun View.attachManga(tag: ViewTag, manga: Manga, source: Source) {
// TODO Duplicated in MangaInfoController
GlideApp.with(context)
.load(manga)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(manga_cover)
manga_full_title.text = if (manga.title.isBlank()) {
context.getString(R.string.unknown)
} else {
manga.title
}
manga_artist.text = if (manga.artist.isNullOrBlank()) {
context.getString(R.string.unknown)
} else {
manga.artist
}
manga_author.text = if (manga.author.isNullOrBlank()) {
context.getString(R.string.unknown)
} else {
manga.author
}
manga_source.text = if (source.id == MERGED_SOURCE_ID) {
MergedSource.MangaConfig.readFromUrl(gson, manga.url).children.map {
sourceManager.getOrStub(it.source).toString()
}.distinct().joinToString()
} else {
source.toString()
}
if (source.id == MERGED_SOURCE_ID) {
manga_source_label.text = "Sources"
} else {
manga_source_label.setText(R.string.manga_info_source_label)
}
manga_status.setText(when (manga.status) {
SManga.ONGOING -> R.string.ongoing
SManga.COMPLETED -> R.string.completed
SManga.LICENSED -> R.string.licensed
else -> R.string.unknown
})
val mangaChapters = db.getChapters(manga).executeAsBlocking()
manga_chapters.text = mangaChapters.size.toString()
val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f
val lastUpdate = Date(mangaChapters.maxBy { it.date_upload }?.date_upload ?: 0)
if (latestChapter > 0f) {
manga_last_chapter.text = DecimalFormat("#.#").format(latestChapter)
} else {
manga_last_chapter.setText(R.string.unknown)
}
if (lastUpdate.time != 0L) {
manga_last_update.text = DateFormat.getDateInstance(DateFormat.SHORT).format(lastUpdate)
} else {
manga_last_update.setText(R.string.unknown)
}
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
val objectAsView = `object` as View
container.removeView(objectAsView)
(objectAsView.tag as? ViewTag)?.destroy()
}
class ViewTag(parent: CoroutineContext) : CoroutineScope {
/**
* The context of this scope.
* Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
* Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
*
* By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
*/
override val coroutineContext = parent + Job() + Dispatchers.Default
fun destroy() {
cancel()
}
}
}
@@ -1,239 +0,0 @@
package eu.kanade.tachiyomi.ui.migration.manga.process
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.smartsearch.SmartSearchEngine
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.util.system.toast
import kotlin.coroutines.CoroutineContext
import kotlinx.android.synthetic.main.migration_process.pager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import uy.kohesive.injekt.injectLazy
// TODO Will probably implode if activity is fully destroyed
class MigrationProcedureController(bundle: Bundle? = null) : BaseController(bundle), CoroutineScope {
private var titleText = "Migrate manga"
private var adapter: MigrationProcedureAdapter? = null
override val coroutineContext: CoroutineContext = Job() + Dispatchers.Default
val config: MigrationProcedureConfig? = args.getParcelable(CONFIG_EXTRA)
private val db: DatabaseHelper by injectLazy()
private val sourceManager: SourceManager by injectLazy()
private val smartSearchEngine = SmartSearchEngine(coroutineContext, config?.extraSearchParams)
private var migrationsJob: Job? = null
private var migratingManga: List<MigratingManga>? = null
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
return inflater.inflate(R.layout.migration_process, container, false)
}
override fun getTitle(): String {
return titleText
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
setTitle()
val config = this.config ?: return
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
val newMigratingManga = migratingManga ?: run {
val new = config.mangaIds.map {
MigratingManga(db, sourceManager, it, coroutineContext)
}
migratingManga = new
new
}
adapter = MigrationProcedureAdapter(this, newMigratingManga, coroutineContext)
pager.adapter = adapter
pager.isEnabled = false
if (migrationsJob == null) {
migrationsJob = launch {
runMigrations(newMigratingManga)
}
}
pager.post {
// pager.currentItem doesn't appear to be valid if we don't do this in a post
updateTitle()
}
}
fun updateTitle() {
titleText = "Migrate manga (${pager.currentItem + 1}/${adapter?.count ?: 0})"
setTitle()
}
fun nextMigration() {
adapter?.let { adapter ->
if (pager.currentItem >= adapter.count - 1) {
applicationContext?.toast("All migrations complete!")
router.popCurrentController()
} else {
adapter.migratingManga[pager.currentItem].migrationJob.cancel()
pager.setCurrentItem(pager.currentItem + 1, true)
launch(Dispatchers.Main) {
updateTitle()
}
}
}
}
fun migrationFailure() {
activity?.let {
MaterialDialog.Builder(it)
.title("Migration failure")
.content("An unknown error occured while migrating this manga!")
.positiveText("Ok")
.show()
}
}
suspend fun runMigrations(mangas: List<MigratingManga>) {
/* val sources = config?.targetSourceIds?.mapNotNull { sourceManager.get(it) as?
CatalogueSource } ?: return
for (manga in mangas) {
if (!manga.searchResult.initialized && manga.migrationJob.isActive) {
val mangaObj = manga.manga()
if (mangaObj == null) {
manga.searchResult.initialize(null)
continue
}
val mangaSource = manga.mangaSource()
val result = try {
CoroutineScope(manga.migrationJob).async {
val validSources = sources.filter {
it.id != mangaSource.id
}
if (config.useSourceWithMostChapters) {
val sourceSemaphore = Semaphore(3)
val processedSources = AtomicInteger()
validSources.map { source ->
async {
sourceSemaphore.withPermit {
try {
val searchResult = if (config.enableLenientSearch) {
smartSearchEngine.smartSearch(source, mangaObj.title)
} else {
smartSearchEngine.normalSearch(source, mangaObj.title)
}
if (searchResult != null) {
val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id)
val chapters = source.fetchChapterList(localManga).toSingle().await(Schedulers.io())
withContext(Dispatchers.IO) {
syncChaptersWithSource(db, chapters, localManga, source)
}
manga.progress.send(validSources.size to processedSources.incrementAndGet())
localManga to chapters.size
} else {
null
}
} catch (e: CancellationException) {
// Ignore cancellations
throw e
} catch (e: Exception) {
null
}
}
}
}.mapNotNull { it.await() }.maxBy { it.second }?.first
} else {
validSources.forEachIndexed { index, source ->
val searchResult = try {
val searchResult = if (config.enableLenientSearch) {
smartSearchEngine.smartSearch(source, mangaObj.title)
} else {
smartSearchEngine.normalSearch(source, mangaObj.title)
}
if (searchResult != null) {
val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id)
val chapters = source.fetchChapterList(localManga).toSingle().await(Schedulers.io())
withContext(Dispatchers.IO) {
syncChaptersWithSource(db, chapters, localManga, source)
}
localManga
} else null
} catch (e: CancellationException) {
// Ignore cancellations
throw e
} catch (e: Exception) {
null
}
manga.progress.send(validSources.size to (index + 1))
if (searchResult != null) return@async searchResult
}
null
}
}.await()
} catch (e: CancellationException) {
// Ignore canceled migrations
continue
}
if (result != null && result.thumbnail_url == null) {
try {
val newManga = sourceManager.getOrStub(result.source)
.fetchMangaDetails(result)
.toSingle()
.await()
result.copyFrom(newManga)
db.insertManga(result).executeAsBlocking()
} catch (e: CancellationException) {
// Ignore cancellations
throw e
} catch (e: Exception) {
}
}
manga.searchResult.initialize(result?.id)
}
}*/
}
override fun onDestroy() {
super.onDestroy()
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
companion object {
const val CONFIG_EXTRA = "config_extra"
fun create(config: MigrationProcedureConfig): MigrationProcedureController {
return MigrationProcedureController(Bundle().apply {
putParcelable(CONFIG_EXTRA, config)
})
}
}
}
@@ -19,8 +19,8 @@ import eu.kanade.tachiyomi.util.view.invisible
import eu.kanade.tachiyomi.util.view.setVectorCompat
import eu.kanade.tachiyomi.util.view.visible
import java.text.DecimalFormat
import kotlinx.android.synthetic.main.migration_new_manga_card.view.*
import kotlinx.android.synthetic.main.migration_new_process_item.*
import kotlinx.android.synthetic.main.migration_manga_card.view.*
import kotlinx.android.synthetic.main.migration_process_item.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.injectLazy
@@ -122,6 +122,7 @@ class MigrationProcessHolder(
manga_chapters.text = ""
manga_chapters.gone()
manga_last_chapter_label.text = ""
migration_manga_card_to.setOnClickListener(null)
}
private fun View.attachManga(manga: Manga, source: Source) {
@@ -12,7 +12,7 @@ class MigrationProcessItem(val manga: MigratingManga) :
var holder: MigrationProcessHolder? = null
override fun getLayoutRes(): Int {
return R.layout.migration_new_process_item
return R.layout.migration_process_item
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): MigrationProcessHolder {