Add Komga as an unattended track service (#5049)

* fix: prevent crash if TrackService.getScoreList() is empty

* disabled track score button if service doesn't support scoring

* first implementation of the Komga tracking
this doesn't work for read lists

* auto track when adding to library

* handle refresh

* 2-way sync of chapters for unattended tracking services

* Update app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSheet.kt

Co-authored-by: Andreas <andreas.everos@gmail.com>

* group strings together

* support for read lists

* sync read chapters on bind

* only mark local chapters as read during 2-way sync (incoming)

* local progress from read chapters will be sent to remote tracker on bind/refresh
this enables syncing after reading offline

* remove unused variable

* refactor the 2-way sync in a util function

* handle auto add to track for unattended services from the browse source screen when long clicking
this will also sync chapters, as it is possible to have read or marked as read chapters from there

* 2-way sync when library update for TRACKING

* refactor

* better handling of what has been read server side

* refactor: extract function

* fix: localLastRead could be -1 when all chapters are read

* refactor to rethrow exception so it can be shown in toast

* extract strings

* replace komga logo

Co-authored-by: Andreas <andreas.everos@gmail.com>
This commit is contained in:
Gauthier
2021-05-23 00:07:58 +08:00
committed by GitHub
parent dbe8931cf0
commit d6b3b0baf7
18 changed files with 481 additions and 7 deletions

View File

@@ -9,6 +9,9 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.UnattendedTrackService
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.Filter
@@ -30,6 +33,7 @@ import eu.kanade.tachiyomi.ui.browse.source.filter.TextSectionItem
import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateItem
import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateSectionItem
import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.removeCovers
@@ -102,6 +106,8 @@ open class BrowseSourcePresenter(
*/
private var pageSubscription: Subscription? = null
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } }
init {
query = searchQuery ?: ""
}
@@ -260,11 +266,36 @@ open class BrowseSourcePresenter(
manga.removeCovers(coverCache)
} else {
ChapterSettingsHelper.applySettingDefaults(manga)
if (prefs.autoAddTrack()) {
autoAddTrack(manga)
}
}
db.insertManga(manga).executeAsBlocking()
}
private fun autoAddTrack(manga: Manga) {
loggedServices
.filterIsInstance<UnattendedTrackService>()
.filter { it.accept(source) }
.forEach { service ->
launchIO {
try {
service.match(manga)?.let { track ->
track.manga_id = manga.id!!
(service as TrackService).bind(track)
db.insertTrack(track).executeAsBlocking()
syncChaptersWithTrackServiceTwoWay(db, db.getChapters(manga).executeAsBlocking(), track, service as TrackService)
}
} catch (e: Exception) {
Timber.w(e, "Could not match manga: ${manga.title} with service $service")
}
}
}
}
/**
* Set the filter states for the current source.
*

View File

@@ -37,6 +37,8 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.UnattendedTrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.databinding.MangaControllerBinding
import eu.kanade.tachiyomi.source.LocalSource
@@ -72,6 +74,7 @@ import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
import eu.kanade.tachiyomi.util.hasCustomCover
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.toast
@@ -507,6 +510,24 @@ class MangaController :
.showDialog(router)
}
}
if (source != null && preferences.autoAddTrack()) {
presenter.trackList
.map { it.service }
.filterIsInstance<UnattendedTrackService>()
.filter { it.accept(source!!) }
.forEach { service ->
launchIO {
try {
service.match(manga)?.let { track ->
presenter.registerTracking(track, service as TrackService)
}
} catch (e: Exception) {
Timber.w(e, "Could not match manga: ${manga.title} with service $service")
}
}
}
}
}
/**

View File

@@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.UnattendedTrackService
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.toSChapter
@@ -26,6 +27,7 @@ import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay
import eu.kanade.tachiyomi.util.isLocal
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
@@ -709,6 +711,10 @@ class MangaPresenter(
async {
val track = it.service.refresh(it.track!!)
db.insertTrack(track).executeAsBlocking()
if (it.service is UnattendedTrackService) {
syncChaptersWithTrackServiceTwoWay(db, chapters, track, it.service)
}
}
}
.awaitAll()
@@ -740,6 +746,10 @@ class MangaPresenter(
try {
service.bind(item)
db.insertTrack(item).executeAsBlocking()
if (service is UnattendedTrackService) {
syncChaptersWithTrackServiceTwoWay(db, chapters, item, service)
}
} catch (e: Throwable) {
withUIContext { view?.applicationContext?.toast(e.message) }
}

View File

@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.manga.track
import android.annotation.SuppressLint
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R.string
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.TrackItemBinding
import uy.kohesive.injekt.injectLazy
@@ -49,6 +50,12 @@ class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter)
if (track.total_chapters > 0) track.total_chapters else "-"
binding.trackStatus.text = item.service.getStatus(track.status)
binding.trackScore.text = if (track.score == 0f) "-" else item.service.displayScore(track)
if (item.service.getScoreList().isEmpty()) {
with(binding.trackScore) {
text = context.getString(string.score_unsupported)
isEnabled = false
}
}
if (item.service.supportsReadingDates) {
binding.trackStartDate.text =

View File

@@ -5,16 +5,25 @@ import android.view.LayoutInflater
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.bottomsheet.BottomSheetBehavior
import eu.kanade.tachiyomi.R.string
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.track.UnattendedTrackService
import eu.kanade.tachiyomi.databinding.TrackControllerBinding
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class TrackSheet(
val controller: MangaController,
val manga: Manga
val manga: Manga,
private val sourceManager: SourceManager = Injekt.get()
) : BaseBottomSheetDialog(controller.activity!!),
TrackAdapter.OnClickListener,
SetTrackStatusDialog.Listener,
@@ -69,7 +78,31 @@ class TrackSheet(
override fun onSetClick(position: Int) {
val item = adapter.getItem(position) ?: return
TrackSearchDialog(controller, item.service).showDialog(controller.router, TAG_SEARCH_CONTROLLER)
if (item.service is UnattendedTrackService) {
if (item.track != null) {
controller.presenter.unregisterTracking(item.service)
return
}
if (!item.service.accept(sourceManager.getOrStub(manga.source))) {
controller.presenter.view?.applicationContext?.toast(string.source_unsupported)
return
}
launchIO {
try {
item.service.match(manga)?.let { track ->
controller.presenter.registerTracking(track, item.service)
}
?: withUIContext { controller.presenter.view?.applicationContext?.toast(string.error_no_match) }
} catch (e: Exception) {
withUIContext { controller.presenter.view?.applicationContext?.toast(string.error_no_match) }
}
}
} else {
TrackSearchDialog(controller, item.service).showDialog(controller.router, TAG_SEARCH_CONTROLLER)
}
}
override fun onTitleLongClick(position: Int) {
@@ -94,7 +127,7 @@ class TrackSheet(
override fun onScoreClick(position: Int) {
val item = adapter.getItem(position) ?: return
if (item.track == null) return
if (item.track == null || item.service.getScoreList().isEmpty()) return
SetTrackScoreDialog(controller, this, item).showDialog(controller.router)
}

View File

@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.setting
import android.app.Activity
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.NoLoginTrackService
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.anilist.AnilistApi
@@ -38,6 +39,11 @@ class SettingsTrackingController :
titleRes = R.string.pref_auto_update_manga_sync
defaultValue = true
}
switchPreference {
key = Keys.autoAddTrack
titleRes = R.string.pref_auto_add_track
defaultValue = true
}
preferenceCategory {
titleRes = R.string.services
@@ -58,6 +64,10 @@ class SettingsTrackingController :
trackPreference(trackManager.bangumi) {
activity?.openInBrowser(BangumiApi.authUrl(), trackManager.bangumi.getLogoColor())
}
trackPreference(trackManager.komga) {
trackManager.komga.loginNoop()
updatePreference(trackManager.komga.id)
}
}
preferenceCategory {
infoPreference(R.string.tracking_info)
@@ -76,9 +86,14 @@ class SettingsTrackingController :
{
onClick {
if (service.isLogged) {
val dialog = TrackLogoutDialog(service)
dialog.targetController = this@SettingsTrackingController
dialog.showDialog(router)
if (service is NoLoginTrackService) {
service.logout()
updatePreference(service.id)
} else {
val dialog = TrackLogoutDialog(service)
dialog.targetController = this@SettingsTrackingController
dialog.showDialog(router)
}
} else {
login()
}