mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-03 23:58:55 +01:00 
			
		
		
		
	Finish auto-migration feature
This commit is contained in:
		@@ -165,6 +165,16 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
 | 
			
		||||
 | 
			
		||||
        subscriptions += controller.selectionRelay
 | 
			
		||||
                .subscribe { onSelectionChanged(it) }
 | 
			
		||||
 | 
			
		||||
        subscriptions += controller.selectAllRelay
 | 
			
		||||
                .subscribe {
 | 
			
		||||
                    if (it == category.id) {
 | 
			
		||||
                        adapter.currentItems.forEach { item ->
 | 
			
		||||
                            controller.setSelection(item.manga, true)
 | 
			
		||||
                        }
 | 
			
		||||
                        controller.invalidateActionMode()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun onRecycle() {
 | 
			
		||||
 
 | 
			
		||||
@@ -99,6 +99,8 @@ class LibraryController(
 | 
			
		||||
     */
 | 
			
		||||
    val libraryMangaRelay: BehaviorRelay<LibraryMangaEvent> = BehaviorRelay.create()
 | 
			
		||||
 | 
			
		||||
    val selectAllRelay: PublishRelay<Int> = PublishRelay.create()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Number of manga per row in grid mode.
 | 
			
		||||
     */
 | 
			
		||||
@@ -436,6 +438,9 @@ class LibraryController(
 | 
			
		||||
            }
 | 
			
		||||
            R.id.action_move_to_category -> showChangeMangaCategoriesDialog()
 | 
			
		||||
            R.id.action_delete -> showDeleteMangaDialog()
 | 
			
		||||
            R.id.action_select_all -> {
 | 
			
		||||
                selectAllRelay.call(activeCategory)
 | 
			
		||||
            }
 | 
			
		||||
            R.id.action_auto_source_migration -> {
 | 
			
		||||
                router.pushController(MigrationDesignController.create(
 | 
			
		||||
                        selectedMangas.mapNotNull { it.id }
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,8 @@ import rx.schedulers.Schedulers
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import kotlin.coroutines.CoroutineContext
 | 
			
		||||
 | 
			
		||||
class SmartSearchEngine(parentContext: CoroutineContext): CoroutineScope {
 | 
			
		||||
class SmartSearchEngine(parentContext: CoroutineContext,
 | 
			
		||||
                        val extraSearchParams: String? = null): CoroutineScope {
 | 
			
		||||
    override val coroutineContext: CoroutineContext = parentContext + Job() + Dispatchers.Default
 | 
			
		||||
 | 
			
		||||
    private val db: DatabaseHelper by injectLazy()
 | 
			
		||||
@@ -26,7 +27,11 @@ class SmartSearchEngine(parentContext: CoroutineContext): CoroutineScope {
 | 
			
		||||
        val eligibleManga = supervisorScope {
 | 
			
		||||
            queries.map { query ->
 | 
			
		||||
                async(Dispatchers.Default) {
 | 
			
		||||
                    val searchResults = source.fetchSearchManga(1, query, FilterList()).toSingle().await(Schedulers.io())
 | 
			
		||||
                    val builtQuery = if(extraSearchParams != null) {
 | 
			
		||||
                        "$query ${extraSearchParams.trim()}"
 | 
			
		||||
                    } else title
 | 
			
		||||
 | 
			
		||||
                    val searchResults = source.fetchSearchManga(1, builtQuery, FilterList()).toSingle().await(Schedulers.io())
 | 
			
		||||
 | 
			
		||||
                    searchResults.mangas.map {
 | 
			
		||||
                        val cleanedMangaTitle = cleanSmartSearchTitle(it.title)
 | 
			
		||||
@@ -44,7 +49,10 @@ class SmartSearchEngine(parentContext: CoroutineContext): CoroutineScope {
 | 
			
		||||
 | 
			
		||||
    suspend fun normalSearch(source: CatalogueSource, title: String): SManga? {
 | 
			
		||||
        val eligibleManga = supervisorScope {
 | 
			
		||||
            val searchResults = source.fetchSearchManga(1, title, FilterList()).toSingle().await(Schedulers.io())
 | 
			
		||||
            val searchQuery = if(extraSearchParams != null) {
 | 
			
		||||
                "$title ${extraSearchParams.trim()}"
 | 
			
		||||
            } else title
 | 
			
		||||
            val searchResults = source.fetchSearchManga(1, searchQuery, FilterList()).toSingle().await(Schedulers.io())
 | 
			
		||||
 | 
			
		||||
            searchResults.mangas.map {
 | 
			
		||||
                val normalizedDistance = NormalizedLevenshtein().similarity(title, it.title)
 | 
			
		||||
@@ -80,7 +88,7 @@ class SmartSearchEngine(parentContext: CoroutineContext): CoroutineScope {
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        return searchQueries.map {
 | 
			
		||||
            it.joinToString().trim()
 | 
			
		||||
            it.joinToString(" ").trim()
 | 
			
		||||
        }.distinct()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -167,8 +175,8 @@ class SmartSearchEngine(parentContext: CoroutineContext): CoroutineScope {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val MIN_SMART_ELIGIBLE_THRESHOLD = 0.7
 | 
			
		||||
        const val MIN_NORMAL_ELIGIBLE_THRESHOLD = 0.5
 | 
			
		||||
        const val MIN_SMART_ELIGIBLE_THRESHOLD = 0.4
 | 
			
		||||
        const val MIN_NORMAL_ELIGIBLE_THRESHOLD = 0.4
 | 
			
		||||
 | 
			
		||||
        private val titleRegex = Regex("[^a-zA-Z0-9- ]")
 | 
			
		||||
        private val consecutiveSpacesRegex = Regex(" +")
 | 
			
		||||
 
 | 
			
		||||
@@ -11,13 +11,14 @@ import eu.kanade.tachiyomi.source.SourceManager
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.HttpSource
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
 | 
			
		||||
import eu.kanade.tachiyomi.ui.migration.MigrationFlags
 | 
			
		||||
import eu.kanade.tachiyomi.util.gone
 | 
			
		||||
import eu.kanade.tachiyomi.util.visible
 | 
			
		||||
import exh.ui.base.BaseExhController
 | 
			
		||||
import exh.ui.migration.manga.process.MigrationProcedureConfig
 | 
			
		||||
import exh.ui.migration.manga.process.MigrationProcedureController
 | 
			
		||||
import kotlinx.android.synthetic.main.eh_migration_design.*
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
 | 
			
		||||
// TODO Handle config changes
 | 
			
		||||
// TODO Select all in library
 | 
			
		||||
class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bundle), FlexibleAdapter.OnItemClickListener {
 | 
			
		||||
    private val sourceManager: SourceManager by injectLazy()
 | 
			
		||||
@@ -29,6 +30,8 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund
 | 
			
		||||
 | 
			
		||||
    private val config: LongArray = args.getLongArray(MANGA_IDS_EXTRA) ?: LongArray(0)
 | 
			
		||||
 | 
			
		||||
    private var showingOptions = false
 | 
			
		||||
 | 
			
		||||
    override fun getTitle() = "Select target sources"
 | 
			
		||||
 | 
			
		||||
    override fun onViewCreated(view: View) {
 | 
			
		||||
@@ -53,13 +56,33 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund
 | 
			
		||||
            use_smart_search.toggle()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        copy_manga_desc.setOnClickListener {
 | 
			
		||||
            copy_manga.toggle()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        extra_search_param_desc.setOnClickListener {
 | 
			
		||||
            extra_search_param.toggle()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        prioritize_chapter_count.setOnCheckedChangeListener { _, b ->
 | 
			
		||||
            updatePrioritizeChapterCount(b)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        extra_search_param.setOnCheckedChangeListener { _, b ->
 | 
			
		||||
            updateOptionsState()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        updatePrioritizeChapterCount(prioritize_chapter_count.isChecked)
 | 
			
		||||
 | 
			
		||||
        updateOptionsState()
 | 
			
		||||
 | 
			
		||||
        begin_migration_btn.setOnClickListener {
 | 
			
		||||
            if(!showingOptions) {
 | 
			
		||||
                showingOptions = true
 | 
			
		||||
                updateOptionsState()
 | 
			
		||||
                return@setOnClickListener
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var flags = 0
 | 
			
		||||
            if(mig_chapters.isChecked) flags = flags or MigrationFlags.CHAPTERS
 | 
			
		||||
            if(mig_categories.isChecked) flags = flags or MigrationFlags.CATEGORIES
 | 
			
		||||
@@ -73,12 +96,41 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund
 | 
			
		||||
                            }.map { it.source.id },
 | 
			
		||||
                            useSourceWithMostChapters = prioritize_chapter_count.isChecked,
 | 
			
		||||
                            enableLenientSearch = use_smart_search.isChecked,
 | 
			
		||||
                            migrationFlags = flags
 | 
			
		||||
                            migrationFlags = flags,
 | 
			
		||||
                            copy = copy_manga.isChecked,
 | 
			
		||||
                            extraSearchParams = if(extra_search_param.isChecked && extra_search_param_text.text.isNotBlank()) {
 | 
			
		||||
                                extra_search_param_text.text.toString()
 | 
			
		||||
                            } else null
 | 
			
		||||
                    )
 | 
			
		||||
            ).withFadeTransaction())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun updateOptionsState() {
 | 
			
		||||
        if (showingOptions) {
 | 
			
		||||
            begin_migration_btn.text = "Begin migration"
 | 
			
		||||
            options_group.visible()
 | 
			
		||||
            if(extra_search_param.isChecked) {
 | 
			
		||||
                extra_search_param_text.visible()
 | 
			
		||||
            } else {
 | 
			
		||||
                extra_search_param_text.gone()
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            begin_migration_btn.text = "Next step"
 | 
			
		||||
            options_group.gone()
 | 
			
		||||
            extra_search_param_text.gone()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun handleBack(): Boolean {
 | 
			
		||||
        if(showingOptions) {
 | 
			
		||||
            showingOptions = false
 | 
			
		||||
            updateOptionsState()
 | 
			
		||||
            return true
 | 
			
		||||
        }
 | 
			
		||||
        return super.handleBack()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onSaveInstanceState(outState: Bundle) {
 | 
			
		||||
        super.onSaveInstanceState(outState)
 | 
			
		||||
        adapter?.onSaveInstanceState(outState)
 | 
			
		||||
@@ -92,9 +144,9 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund
 | 
			
		||||
 | 
			
		||||
    private fun updatePrioritizeChapterCount(migrationMode: Boolean) {
 | 
			
		||||
        migration_mode.text = if(migrationMode) {
 | 
			
		||||
            "Use the source with the most chapters and use the above list to break ties (slow with many sources or smart search)"
 | 
			
		||||
            "Currently using the source with the most chapters and the above list to break ties (slow with many sources or smart search)"
 | 
			
		||||
        } else {
 | 
			
		||||
            "Use the first source in the list that has the manga"
 | 
			
		||||
            "Currently using the first source in the list that has the manga"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,11 +7,7 @@ import eu.kanade.tachiyomi.source.SourceManager
 | 
			
		||||
import exh.util.DeferredField
 | 
			
		||||
import exh.util.await
 | 
			
		||||
import kotlinx.coroutines.*
 | 
			
		||||
import kotlinx.coroutines.channels.BroadcastChannel
 | 
			
		||||
import kotlinx.coroutines.channels.Channel
 | 
			
		||||
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
 | 
			
		||||
import kotlinx.coroutines.channels.ReceiveChannel
 | 
			
		||||
import kotlinx.coroutines.flow.Flow
 | 
			
		||||
import kotlin.coroutines.CoroutineContext
 | 
			
		||||
 | 
			
		||||
class MigratingManga(private val db: DatabaseHelper,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,28 +4,22 @@ import android.support.v4.view.PagerAdapter
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
 | 
			
		||||
import com.elvishew.xlog.XLog
 | 
			
		||||
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.database.models.MangaCategory
 | 
			
		||||
import eu.kanade.tachiyomi.data.glide.GlideApp
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
 | 
			
		||||
import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import eu.kanade.tachiyomi.source.SourceManager
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SChapter
 | 
			
		||||
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.ui.manga.info.MangaInfoController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.migration.MigrationFlags
 | 
			
		||||
import eu.kanade.tachiyomi.util.gone
 | 
			
		||||
import eu.kanade.tachiyomi.util.inflate
 | 
			
		||||
import eu.kanade.tachiyomi.util.syncChaptersWithSource
 | 
			
		||||
import eu.kanade.tachiyomi.util.visible
 | 
			
		||||
import eu.kanade.tachiyomi.util.*
 | 
			
		||||
import exh.MERGED_SOURCE_ID
 | 
			
		||||
import exh.debug.DebugFunctions.sourceManager
 | 
			
		||||
import exh.util.await
 | 
			
		||||
import kotlinx.android.synthetic.main.eh_manga_card.view.*
 | 
			
		||||
import kotlinx.android.synthetic.main.eh_migration_process_item.view.*
 | 
			
		||||
@@ -33,6 +27,9 @@ import kotlinx.coroutines.*
 | 
			
		||||
import kotlinx.coroutines.flow.asFlow
 | 
			
		||||
import kotlinx.coroutines.flow.collect
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import java.text.DateFormat
 | 
			
		||||
import java.text.DecimalFormat
 | 
			
		||||
import java.util.*
 | 
			
		||||
import kotlin.coroutines.CoroutineContext
 | 
			
		||||
 | 
			
		||||
class MigrationProcedureAdapter(val controller: MigrationProcedureController,
 | 
			
		||||
@@ -42,6 +39,8 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
 | 
			
		||||
    private val gson: Gson by injectLazy()
 | 
			
		||||
    private val sourceManager: SourceManager by injectLazy()
 | 
			
		||||
 | 
			
		||||
    private val logger = XLog.tag(this::class.simpleName)
 | 
			
		||||
 | 
			
		||||
    override fun isViewFromObject(p0: View, p1: Any): Boolean {
 | 
			
		||||
        return p0 == p1
 | 
			
		||||
    }
 | 
			
		||||
@@ -57,34 +56,51 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
 | 
			
		||||
            controller.nextMigration()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        view.accept_migration.setOnClickListener {
 | 
			
		||||
            view.migrating_frame.visible()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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) {
 | 
			
		||||
                    logger.e("Migration failure!", e)
 | 
			
		||||
                    controller.migrationFailure()
 | 
			
		||||
                }
 | 
			
		||||
                view.migrating_frame.gone()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return view
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun performMigration() {
 | 
			
		||||
    suspend fun performMigration(manga: MigratingManga) {
 | 
			
		||||
        if(!manga.searchResult.initialized) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val toMangaObj = db.getManga(manga.searchResult.get() ?: return).await() ?: return
 | 
			
		||||
 | 
			
		||||
        withContext(Dispatchers.IO) {
 | 
			
		||||
            migrateMangaInternal(
 | 
			
		||||
                    manga.manga() ?: return@withContext,
 | 
			
		||||
                    toMangaObj,
 | 
			
		||||
                    !controller.config.copy
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private fun migrateMangaInternal(source: Source,
 | 
			
		||||
                                     sourceChapters: List<SChapter>,
 | 
			
		||||
                                     prevManga: Manga,
 | 
			
		||||
 | 
			
		||||
    private fun migrateMangaInternal(prevManga: Manga,
 | 
			
		||||
                                     manga: Manga,
 | 
			
		||||
                                     replace: Boolean) {
 | 
			
		||||
        db.inTransaction {
 | 
			
		||||
            // Update chapters read
 | 
			
		||||
            if (migrateChapters) {
 | 
			
		||||
                try {
 | 
			
		||||
                    syncChaptersWithSource(db, sourceChapters, manga, source)
 | 
			
		||||
                } catch (e: Exception) {
 | 
			
		||||
                    // Worst case, chapters won't be synced
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            if (MigrationFlags.hasChapters(controller.config.migrationFlags)) {
 | 
			
		||||
                val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
 | 
			
		||||
                val maxChapterRead = prevMangaChapters.filter { it.read }
 | 
			
		||||
                        .maxBy { it.chapter_number }?.chapter_number
 | 
			
		||||
@@ -99,13 +115,13 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            // Update categories
 | 
			
		||||
            if (migrateCategories) {
 | 
			
		||||
            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 (migrateTracks) {
 | 
			
		||||
            if (MigrationFlags.hasTracks(controller.config.migrationFlags)) {
 | 
			
		||||
                val tracks = db.getTracks(prevManga).executeAsBlocking()
 | 
			
		||||
                for (track in tracks) {
 | 
			
		||||
                    track.id = null
 | 
			
		||||
@@ -174,7 +190,7 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun View.attachManga(tag: ViewTag, manga: Manga, source: Source) {
 | 
			
		||||
    suspend fun View.attachManga(tag: ViewTag, manga: Manga, source: Source) {
 | 
			
		||||
        // TODO Duplicated in MangaInfoController
 | 
			
		||||
 | 
			
		||||
        GlideApp.with(context)
 | 
			
		||||
@@ -221,6 +237,23 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController,
 | 
			
		||||
            SManga.LICENSED -> R.string.licensed
 | 
			
		||||
            else -> R.string.unknown
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        val mangaChapters = db.getChapters(manga).await()
 | 
			
		||||
        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(count)
 | 
			
		||||
        } 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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,5 +9,7 @@ data class MigrationProcedureConfig(
 | 
			
		||||
        val targetSourceIds: List<Long>,
 | 
			
		||||
        val useSourceWithMostChapters: Boolean,
 | 
			
		||||
        val enableLenientSearch: Boolean,
 | 
			
		||||
        val migrationFlags: Int
 | 
			
		||||
        val migrationFlags: Int,
 | 
			
		||||
        val copy: Boolean,
 | 
			
		||||
        val extraSearchParams: String?
 | 
			
		||||
): Parcelable
 | 
			
		||||
@@ -4,12 +4,13 @@ import android.content.pm.ActivityInfo
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.View
 | 
			
		||||
import com.afollestad.materialdialogs.MaterialDialog
 | 
			
		||||
import com.elvishew.xlog.XLog
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.source.CatalogueSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.SourceManager
 | 
			
		||||
import eu.kanade.tachiyomi.util.syncChaptersWithSource
 | 
			
		||||
import eu.kanade.tachiyomi.util.toast
 | 
			
		||||
import exh.smartsearch.SmartSearchEngine
 | 
			
		||||
import exh.ui.base.BaseExhController
 | 
			
		||||
@@ -22,6 +23,7 @@ import rx.schedulers.Schedulers
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import java.util.concurrent.atomic.AtomicInteger
 | 
			
		||||
 | 
			
		||||
// TODO Will probably implode if activity is fully destroyed
 | 
			
		||||
class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(bundle), CoroutineScope {
 | 
			
		||||
    override val layoutId = R.layout.eh_migration_process
 | 
			
		||||
 | 
			
		||||
@@ -29,12 +31,12 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
 | 
			
		||||
 | 
			
		||||
    private var adapter: MigrationProcedureAdapter? = null
 | 
			
		||||
 | 
			
		||||
    private val config: MigrationProcedureConfig = args.getParcelable(CONFIG_EXTRA)
 | 
			
		||||
    val config: MigrationProcedureConfig = args.getParcelable(CONFIG_EXTRA)
 | 
			
		||||
 | 
			
		||||
    private val db: DatabaseHelper by injectLazy()
 | 
			
		||||
    private val sourceManager: SourceManager by injectLazy()
 | 
			
		||||
 | 
			
		||||
    private val smartSearchEngine = SmartSearchEngine(coroutineContext)
 | 
			
		||||
    private val smartSearchEngine = SmartSearchEngine(coroutineContext, config.extraSearchParams)
 | 
			
		||||
 | 
			
		||||
    private val logger = XLog.tag("MigrationProcedureController")
 | 
			
		||||
 | 
			
		||||
@@ -74,11 +76,15 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        updateTitle()
 | 
			
		||||
        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() {
 | 
			
		||||
@@ -88,14 +94,23 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
 | 
			
		||||
                router.popCurrentController()
 | 
			
		||||
            } else {
 | 
			
		||||
                pager.setCurrentItem(pager.currentItem + 1, true)
 | 
			
		||||
                updateTitle()
 | 
			
		||||
                launch(Dispatchers.Main) {
 | 
			
		||||
                    setTitle()
 | 
			
		||||
                    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 }
 | 
			
		||||
 | 
			
		||||
@@ -123,21 +138,22 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
 | 
			
		||||
                               async {
 | 
			
		||||
                                   sourceSemaphore.withPermit {
 | 
			
		||||
                                       try {
 | 
			
		||||
                                           supervisorScope {
 | 
			
		||||
                                               val searchResult = if (config.enableLenientSearch) {
 | 
			
		||||
                                                   smartSearchEngine.smartSearch(source, mangaObj.title)
 | 
			
		||||
                                               } else {
 | 
			
		||||
                                                   smartSearchEngine.normalSearch(source, mangaObj.title)
 | 
			
		||||
                                               }
 | 
			
		||||
                                           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())
 | 
			
		||||
                                                   manga.progress.send(validSources.size to processedSources.incrementAndGet())
 | 
			
		||||
                                                   localManga to chapters.size
 | 
			
		||||
                                               } else {
 | 
			
		||||
                                                   null
 | 
			
		||||
                                           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: Exception) {
 | 
			
		||||
                                           logger.e("Failed to search in source: ${source.id}!", e)
 | 
			
		||||
@@ -149,17 +165,20 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
 | 
			
		||||
                        } else {
 | 
			
		||||
                            validSources.forEachIndexed { index, source ->
 | 
			
		||||
                                val searchResult = try {
 | 
			
		||||
                                    supervisorScope {
 | 
			
		||||
                                        val searchResult = if (config.enableLenientSearch) {
 | 
			
		||||
                                            smartSearchEngine.smartSearch(source, mangaObj.title)
 | 
			
		||||
                                        } else {
 | 
			
		||||
                                            smartSearchEngine.normalSearch(source, mangaObj.title)
 | 
			
		||||
                                        }
 | 
			
		||||
 | 
			
		||||
                                        if (searchResult != null) {
 | 
			
		||||
                                            smartSearchEngine.networkToLocalManga(searchResult, source.id)
 | 
			
		||||
                                        } else null
 | 
			
		||||
                                    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: Exception) {
 | 
			
		||||
                                    logger.e("Failed to search in source: ${source.id}!", e)
 | 
			
		||||
                                    null
 | 
			
		||||
@@ -180,15 +199,13 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b
 | 
			
		||||
 | 
			
		||||
                if(result != null && result.thumbnail_url == null) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        supervisorScope {
 | 
			
		||||
                            val newManga = sourceManager.getOrStub(result.source)
 | 
			
		||||
                                    .fetchMangaDetails(result)
 | 
			
		||||
                                    .toSingle()
 | 
			
		||||
                                    .await()
 | 
			
		||||
                            result.copyFrom(newManga)
 | 
			
		||||
                        val newManga = sourceManager.getOrStub(result.source)
 | 
			
		||||
                                .fetchMangaDetails(result)
 | 
			
		||||
                                .toSingle()
 | 
			
		||||
                                .await()
 | 
			
		||||
                        result.copyFrom(newManga)
 | 
			
		||||
 | 
			
		||||
                            db.insertManga(result).await()
 | 
			
		||||
                        }
 | 
			
		||||
                        db.insertManga(result).await()
 | 
			
		||||
                    } catch(e: Exception) {
 | 
			
		||||
                        logger.e("Could not load search manga details", e)
 | 
			
		||||
                    }
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,6 @@ class DeferredField<T> {
 | 
			
		||||
        mutex.withLock {}
 | 
			
		||||
 | 
			
		||||
        // Field is initialized, return value
 | 
			
		||||
        return content!!
 | 
			
		||||
        return content as T
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user