mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-04 11:24:35 -05:00
Feature/updater provide more info about update (#657)
* Provide last global update timestamp * Provide skipped mangas in update status * Extract update status logic into function * Rename update "statusMap" to "mangaStatusMap" * Provide info about categories in update status
This commit is contained in:
@@ -36,6 +36,22 @@ class CategoryDataLoader : KotlinDataLoader<Int, CategoryType> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CategoryForIdsDataLoader : KotlinDataLoader<List<Int>, CategoryNodeList> {
|
||||||
|
override val dataLoaderName = "CategoryForIdsDataLoader"
|
||||||
|
override fun getDataLoader(): DataLoader<List<Int>, CategoryNodeList> = DataLoaderFactory.newDataLoader { categoryIds ->
|
||||||
|
future {
|
||||||
|
transaction {
|
||||||
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
|
val ids = categoryIds.flatten().distinct()
|
||||||
|
val categories = CategoryTable.select { CategoryTable.id inList ids }.map { CategoryType(it) }
|
||||||
|
categoryIds.map { categoryIds ->
|
||||||
|
categories.filter { it.id in categoryIds }.toNodeList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class CategoriesForMangaDataLoader : KotlinDataLoader<Int, CategoryNodeList> {
|
class CategoriesForMangaDataLoader : KotlinDataLoader<Int, CategoryNodeList> {
|
||||||
override val dataLoaderName = "CategoriesForMangaDataLoader"
|
override val dataLoaderName = "CategoriesForMangaDataLoader"
|
||||||
override fun getDataLoader(): DataLoader<Int, CategoryNodeList> = DataLoaderFactory.newDataLoader<Int, CategoryNodeList> { ids ->
|
override fun getDataLoader(): DataLoader<Int, CategoryNodeList> = DataLoaderFactory.newDataLoader<Int, CategoryNodeList> { ids ->
|
||||||
|
|||||||
@@ -4,21 +4,18 @@ import org.kodein.di.DI
|
|||||||
import org.kodein.di.conf.global
|
import org.kodein.di.conf.global
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
import suwayomi.tachidesk.graphql.types.UpdateStatus
|
import suwayomi.tachidesk.graphql.types.UpdateStatus
|
||||||
import suwayomi.tachidesk.graphql.types.UpdateStatusType
|
|
||||||
import suwayomi.tachidesk.manga.impl.update.IUpdater
|
import suwayomi.tachidesk.manga.impl.update.IUpdater
|
||||||
import suwayomi.tachidesk.manga.impl.update.JobStatus
|
|
||||||
|
|
||||||
class UpdateQuery {
|
class UpdateQuery {
|
||||||
private val updater by DI.global.instance<IUpdater>()
|
private val updater by DI.global.instance<IUpdater>()
|
||||||
|
|
||||||
fun updateStatus(): UpdateStatus {
|
fun updateStatus(): UpdateStatus {
|
||||||
val status = updater.status.value
|
return UpdateStatus(updater.status.value)
|
||||||
return UpdateStatus(
|
}
|
||||||
isRunning = status.running,
|
|
||||||
pendingJobs = UpdateStatusType(status.statusMap[JobStatus.PENDING]?.map { it.id }.orEmpty()),
|
data class LastUpdateTimestampPayload(val timestamp: Long)
|
||||||
runningJobs = UpdateStatusType(status.statusMap[JobStatus.RUNNING]?.map { it.id }.orEmpty()),
|
|
||||||
completeJobs = UpdateStatusType(status.statusMap[JobStatus.COMPLETE]?.map { it.id }.orEmpty()),
|
fun lastUpdateTimestamp(): LastUpdateTimestampPayload {
|
||||||
failedJobs = UpdateStatusType(status.statusMap[JobStatus.FAILED]?.map { it.id }.orEmpty())
|
return LastUpdateTimestampPayload(updater.getLastUpdateTimestamp())
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ package suwayomi.tachidesk.graphql.server
|
|||||||
import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistryFactory
|
import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistryFactory
|
||||||
import suwayomi.tachidesk.graphql.dataLoaders.CategoriesForMangaDataLoader
|
import suwayomi.tachidesk.graphql.dataLoaders.CategoriesForMangaDataLoader
|
||||||
import suwayomi.tachidesk.graphql.dataLoaders.CategoryDataLoader
|
import suwayomi.tachidesk.graphql.dataLoaders.CategoryDataLoader
|
||||||
|
import suwayomi.tachidesk.graphql.dataLoaders.CategoryForIdsDataLoader
|
||||||
import suwayomi.tachidesk.graphql.dataLoaders.CategoryMetaDataLoader
|
import suwayomi.tachidesk.graphql.dataLoaders.CategoryMetaDataLoader
|
||||||
import suwayomi.tachidesk.graphql.dataLoaders.ChapterDataLoader
|
import suwayomi.tachidesk.graphql.dataLoaders.ChapterDataLoader
|
||||||
import suwayomi.tachidesk.graphql.dataLoaders.ChapterMetaDataLoader
|
import suwayomi.tachidesk.graphql.dataLoaders.ChapterMetaDataLoader
|
||||||
@@ -39,6 +40,7 @@ class TachideskDataLoaderRegistryFactory {
|
|||||||
MangaForSourceDataLoader(),
|
MangaForSourceDataLoader(),
|
||||||
MangaForIdsDataLoader(),
|
MangaForIdsDataLoader(),
|
||||||
CategoryDataLoader(),
|
CategoryDataLoader(),
|
||||||
|
CategoryForIdsDataLoader(),
|
||||||
CategoryMetaDataLoader(),
|
CategoryMetaDataLoader(),
|
||||||
CategoriesForMangaDataLoader(),
|
CategoriesForMangaDataLoader(),
|
||||||
SourceDataLoader(),
|
SourceDataLoader(),
|
||||||
|
|||||||
@@ -3,26 +3,42 @@ package suwayomi.tachidesk.graphql.types
|
|||||||
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
|
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
|
||||||
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
||||||
import graphql.schema.DataFetchingEnvironment
|
import graphql.schema.DataFetchingEnvironment
|
||||||
|
import suwayomi.tachidesk.manga.impl.update.CategoryUpdateStatus
|
||||||
import suwayomi.tachidesk.manga.impl.update.JobStatus
|
import suwayomi.tachidesk.manga.impl.update.JobStatus
|
||||||
import suwayomi.tachidesk.manga.impl.update.UpdateStatus
|
import suwayomi.tachidesk.manga.impl.update.UpdateStatus
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
class UpdateStatus(
|
class UpdateStatus(
|
||||||
val isRunning: Boolean,
|
val isRunning: Boolean,
|
||||||
|
val skippedCategories: UpdateStatusCategoryType,
|
||||||
|
val updatingCategories: UpdateStatusCategoryType,
|
||||||
val pendingJobs: UpdateStatusType,
|
val pendingJobs: UpdateStatusType,
|
||||||
val runningJobs: UpdateStatusType,
|
val runningJobs: UpdateStatusType,
|
||||||
val completeJobs: UpdateStatusType,
|
val completeJobs: UpdateStatusType,
|
||||||
val failedJobs: UpdateStatusType
|
val failedJobs: UpdateStatusType,
|
||||||
|
val skippedJobs: UpdateStatusType
|
||||||
) {
|
) {
|
||||||
constructor(status: UpdateStatus) : this(
|
constructor(status: UpdateStatus) : this(
|
||||||
isRunning = status.running,
|
isRunning = status.running,
|
||||||
pendingJobs = UpdateStatusType(status.statusMap[JobStatus.PENDING]?.map { it.id }.orEmpty()),
|
skippedCategories = UpdateStatusCategoryType(status.categoryStatusMap[CategoryUpdateStatus.SKIPPED]?.map { it.id }.orEmpty()),
|
||||||
runningJobs = UpdateStatusType(status.statusMap[JobStatus.RUNNING]?.map { it.id }.orEmpty()),
|
updatingCategories = UpdateStatusCategoryType(status.categoryStatusMap[CategoryUpdateStatus.UPDATING]?.map { it.id }.orEmpty()),
|
||||||
completeJobs = UpdateStatusType(status.statusMap[JobStatus.COMPLETE]?.map { it.id }.orEmpty()),
|
pendingJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.PENDING]?.map { it.id }.orEmpty()),
|
||||||
failedJobs = UpdateStatusType(status.statusMap[JobStatus.FAILED]?.map { it.id }.orEmpty())
|
runningJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.RUNNING]?.map { it.id }.orEmpty()),
|
||||||
|
completeJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.COMPLETE]?.map { it.id }.orEmpty()),
|
||||||
|
failedJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.FAILED]?.map { it.id }.orEmpty()),
|
||||||
|
skippedJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.SKIPPED]?.map { it.id }.orEmpty())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UpdateStatusCategoryType(
|
||||||
|
@get:GraphQLIgnore
|
||||||
|
val categoryIds: List<Int>
|
||||||
|
) {
|
||||||
|
fun categories(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<CategoryNodeList> {
|
||||||
|
return dataFetchingEnvironment.getValueFromDataLoader("CategoryForIdsDataLoader", categoryIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class UpdateStatusType(
|
class UpdateStatusType(
|
||||||
@get:GraphQLIgnore
|
@get:GraphQLIgnore
|
||||||
val mangaIds: List<Int>
|
val mangaIds: List<Int>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import kotlinx.coroutines.flow.StateFlow
|
|||||||
import suwayomi.tachidesk.manga.model.dataclass.CategoryDataClass
|
import suwayomi.tachidesk.manga.model.dataclass.CategoryDataClass
|
||||||
|
|
||||||
interface IUpdater {
|
interface IUpdater {
|
||||||
|
fun getLastUpdateTimestamp(): Long
|
||||||
fun addCategoriesToUpdateQueue(categories: List<CategoryDataClass>, clear: Boolean?, forceAll: Boolean)
|
fun addCategoriesToUpdateQueue(categories: List<CategoryDataClass>, clear: Boolean?, forceAll: Boolean)
|
||||||
val status: StateFlow<UpdateStatus>
|
val status: StateFlow<UpdateStatus>
|
||||||
fun reset()
|
fun reset()
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ enum class JobStatus {
|
|||||||
PENDING,
|
PENDING,
|
||||||
RUNNING,
|
RUNNING,
|
||||||
COMPLETE,
|
COMPLETE,
|
||||||
FAILED
|
FAILED,
|
||||||
|
SKIPPED
|
||||||
}
|
}
|
||||||
|
|
||||||
data class UpdateJob(
|
data class UpdateJob(
|
||||||
|
|||||||
@@ -1,22 +1,27 @@
|
|||||||
package suwayomi.tachidesk.manga.impl.update
|
package suwayomi.tachidesk.manga.impl.update
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import mu.KotlinLogging
|
import suwayomi.tachidesk.manga.model.dataclass.CategoryDataClass
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
||||||
|
|
||||||
val logger = KotlinLogging.logger {}
|
enum class CategoryUpdateStatus {
|
||||||
|
UPDATING, SKIPPED
|
||||||
|
}
|
||||||
|
|
||||||
data class UpdateStatus(
|
data class UpdateStatus(
|
||||||
val statusMap: Map<JobStatus, List<MangaDataClass>> = emptyMap(),
|
val categoryStatusMap: Map<CategoryUpdateStatus, List<CategoryDataClass>> = emptyMap(),
|
||||||
|
val mangaStatusMap: Map<JobStatus, List<MangaDataClass>> = emptyMap(),
|
||||||
val running: Boolean = false,
|
val running: Boolean = false,
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
val numberOfJobs: Int = 0
|
val numberOfJobs: Int = 0
|
||||||
) {
|
) {
|
||||||
|
|
||||||
constructor(jobs: List<UpdateJob>, running: Boolean) : this(
|
constructor(categories: Map<CategoryUpdateStatus, List<CategoryDataClass>>, jobs: List<UpdateJob>, skippedMangas: List<MangaDataClass>, running: Boolean) : this(
|
||||||
statusMap = jobs.groupBy { it.status }
|
categories,
|
||||||
|
mangaStatusMap = jobs.groupBy { it.status }
|
||||||
.mapValues { entry ->
|
.mapValues { entry ->
|
||||||
entry.value.map { it.manga }
|
entry.value.map { it.manga }
|
||||||
},
|
}.plus(Pair(JobStatus.SKIPPED, skippedMangas)),
|
||||||
running = running,
|
running = running,
|
||||||
numberOfJobs = jobs.size
|
numberOfJobs = jobs.size
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,9 +18,6 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import kotlinx.coroutines.sync.withPermit
|
import kotlinx.coroutines.sync.withPermit
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import org.kodein.di.DI
|
|
||||||
import org.kodein.di.conf.global
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import suwayomi.tachidesk.manga.impl.Category
|
import suwayomi.tachidesk.manga.impl.Category
|
||||||
import suwayomi.tachidesk.manga.impl.CategoryManga
|
import suwayomi.tachidesk.manga.impl.CategoryManga
|
||||||
import suwayomi.tachidesk.manga.impl.Chapter
|
import suwayomi.tachidesk.manga.impl.Chapter
|
||||||
@@ -49,6 +46,7 @@ class Updater : IUpdater {
|
|||||||
private var maxSourcesInParallel = 20 // max permits, necessary to be set to be able to release up to 20 permits
|
private var maxSourcesInParallel = 20 // max permits, necessary to be set to be able to release up to 20 permits
|
||||||
private val semaphore = Semaphore(maxSourcesInParallel)
|
private val semaphore = Semaphore(maxSourcesInParallel)
|
||||||
|
|
||||||
|
private val lastUpdateKey = "lastUpdateKey"
|
||||||
private val lastAutomatedUpdateKey = "lastAutomatedUpdateKey"
|
private val lastAutomatedUpdateKey = "lastAutomatedUpdateKey"
|
||||||
private val preferences = Preferences.userNodeForPackage(Updater::class.java)
|
private val preferences = Preferences.userNodeForPackage(Updater::class.java)
|
||||||
|
|
||||||
@@ -76,6 +74,10 @@ class Updater : IUpdater {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLastUpdateTimestamp(): Long {
|
||||||
|
return preferences.getLong(lastUpdateKey, 0)
|
||||||
|
}
|
||||||
|
|
||||||
private fun autoUpdateTask() {
|
private fun autoUpdateTask() {
|
||||||
val lastAutomatedUpdate = preferences.getLong(lastAutomatedUpdateKey, 0)
|
val lastAutomatedUpdate = preferences.getLong(lastAutomatedUpdateKey, 0)
|
||||||
preferences.putLong(lastAutomatedUpdateKey, System.currentTimeMillis())
|
preferences.putLong(lastAutomatedUpdateKey, System.currentTimeMillis())
|
||||||
@@ -109,6 +111,15 @@ class Updater : IUpdater {
|
|||||||
HAScheduler.schedule(::autoUpdateTask, updateInterval, timeToNextExecution, "global-update")
|
HAScheduler.schedule(::autoUpdateTask, updateInterval, timeToNextExecution, "global-update")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the status and sustains the "skippedMangas"
|
||||||
|
*/
|
||||||
|
private fun updateStatus(jobs: List<UpdateJob>, running: Boolean, categories: Map<CategoryUpdateStatus, List<CategoryDataClass>>? = null, skippedMangas: List<MangaDataClass>? = null) {
|
||||||
|
val updateStatusCategories = categories ?: _status.value.categoryStatusMap
|
||||||
|
val tmpSkippedMangas = skippedMangas ?: _status.value.mangaStatusMap[JobStatus.SKIPPED] ?: emptyList()
|
||||||
|
_status.update { UpdateStatus(updateStatusCategories, jobs, tmpSkippedMangas, running) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun getOrCreateUpdateChannelFor(source: String): Channel<UpdateJob> {
|
private fun getOrCreateUpdateChannelFor(source: String): Channel<UpdateJob> {
|
||||||
return updateChannels.getOrPut(source) {
|
return updateChannels.getOrPut(source) {
|
||||||
logger.debug { "getOrCreateUpdateChannelFor: created channel for $source - channels: ${updateChannels.size + 1}" }
|
logger.debug { "getOrCreateUpdateChannelFor: created channel for $source - channels: ${updateChannels.size + 1}" }
|
||||||
@@ -121,7 +132,7 @@ class Updater : IUpdater {
|
|||||||
channel.consumeAsFlow()
|
channel.consumeAsFlow()
|
||||||
.onEach { job ->
|
.onEach { job ->
|
||||||
semaphore.withPermit {
|
semaphore.withPermit {
|
||||||
_status.value = UpdateStatus(
|
updateStatus(
|
||||||
process(job),
|
process(job),
|
||||||
tracker.any { (_, job) ->
|
tracker.any { (_, job) ->
|
||||||
job.status == JobStatus.PENDING || job.status == JobStatus.RUNNING
|
job.status == JobStatus.PENDING || job.status == JobStatus.RUNNING
|
||||||
@@ -136,7 +147,7 @@ class Updater : IUpdater {
|
|||||||
|
|
||||||
private suspend fun process(job: UpdateJob): List<UpdateJob> {
|
private suspend fun process(job: UpdateJob): List<UpdateJob> {
|
||||||
tracker[job.manga.id] = job.copy(status = JobStatus.RUNNING)
|
tracker[job.manga.id] = job.copy(status = JobStatus.RUNNING)
|
||||||
_status.update { UpdateStatus(tracker.values.toList(), true) }
|
updateStatus(tracker.values.toList(), true)
|
||||||
tracker[job.manga.id] = try {
|
tracker[job.manga.id] = try {
|
||||||
logger.info { "Updating \"${job.manga.title}\" (source: ${job.manga.sourceId})" }
|
logger.info { "Updating \"${job.manga.title}\" (source: ${job.manga.sourceId})" }
|
||||||
Chapter.getChapterList(job.manga.id, true)
|
Chapter.getChapterList(job.manga.id, true)
|
||||||
@@ -150,9 +161,10 @@ class Updater : IUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun addCategoriesToUpdateQueue(categories: List<CategoryDataClass>, clear: Boolean?, forceAll: Boolean) {
|
override fun addCategoriesToUpdateQueue(categories: List<CategoryDataClass>, clear: Boolean?, forceAll: Boolean) {
|
||||||
val updater by DI.global.instance<IUpdater>()
|
preferences.putLong(lastUpdateKey, System.currentTimeMillis())
|
||||||
|
|
||||||
if (clear == true) {
|
if (clear == true) {
|
||||||
updater.reset()
|
reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
val includeInUpdateStatusToCategoryMap = categories.groupBy { it.includeInUpdate }
|
val includeInUpdateStatusToCategoryMap = categories.groupBy { it.includeInUpdate }
|
||||||
@@ -164,6 +176,11 @@ class Updater : IUpdater {
|
|||||||
} else {
|
} else {
|
||||||
includedCategories.ifEmpty { unsetCategories }
|
includedCategories.ifEmpty { unsetCategories }
|
||||||
}
|
}
|
||||||
|
val skippedCategories = categories.subtract(categoriesToUpdate.toSet()).toList()
|
||||||
|
val updateStatusCategories = mapOf(
|
||||||
|
Pair(CategoryUpdateStatus.UPDATING, categoriesToUpdate),
|
||||||
|
Pair(CategoryUpdateStatus.SKIPPED, skippedCategories)
|
||||||
|
)
|
||||||
|
|
||||||
logger.debug { "Updating categories: '${categoriesToUpdate.joinToString("', '") { it.name }}'" }
|
logger.debug { "Updating categories: '${categoriesToUpdate.joinToString("', '") { it.name }}'" }
|
||||||
|
|
||||||
@@ -179,10 +196,12 @@ class Updater : IUpdater {
|
|||||||
.filter { if (serverConfig.excludeCompleted.value) { it.status != MangaStatus.COMPLETED.name } else true }
|
.filter { if (serverConfig.excludeCompleted.value) { it.status != MangaStatus.COMPLETED.name } else true }
|
||||||
.filter { forceAll || !excludedCategories.any { category -> mangasToCategoriesMap[it.id]?.contains(category) == true } }
|
.filter { forceAll || !excludedCategories.any { category -> mangasToCategoriesMap[it.id]?.contains(category) == true } }
|
||||||
.toList()
|
.toList()
|
||||||
|
val skippedMangas = categoriesToUpdateMangas.subtract(mangasToUpdate.toSet()).toList()
|
||||||
|
|
||||||
// In case no manga gets updated and no update job was running before, the client would never receive an info about its update request
|
// In case no manga gets updated and no update job was running before, the client would never receive an info about its update request
|
||||||
|
updateStatus(emptyList(), mangasToUpdate.isNotEmpty(), updateStatusCategories, skippedMangas)
|
||||||
|
|
||||||
if (mangasToUpdate.isEmpty()) {
|
if (mangasToUpdate.isEmpty()) {
|
||||||
UpdaterSocket.notifyAllClients(UpdateStatus())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,10 +211,10 @@ class Updater : IUpdater {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addMangasToQueue(mangas: List<MangaDataClass>) {
|
private fun addMangasToQueue(mangasToUpdate: List<MangaDataClass>) {
|
||||||
mangas.forEach { tracker[it.id] = UpdateJob(it) }
|
mangasToUpdate.forEach { tracker[it.id] = UpdateJob(it) }
|
||||||
_status.update { UpdateStatus(tracker.values.toList(), mangas.isNotEmpty()) }
|
updateStatus(tracker.values.toList(), mangasToUpdate.isNotEmpty())
|
||||||
mangas.forEach { addMangaToQueue(it) }
|
mangasToUpdate.forEach { addMangaToQueue(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addMangaToQueue(manga: MangaDataClass) {
|
private fun addMangaToQueue(manga: MangaDataClass) {
|
||||||
@@ -208,7 +227,7 @@ class Updater : IUpdater {
|
|||||||
override fun reset() {
|
override fun reset() {
|
||||||
scope.coroutineContext.cancelChildren()
|
scope.coroutineContext.cancelChildren()
|
||||||
tracker.clear()
|
tracker.clear()
|
||||||
_status.update { UpdateStatus() }
|
updateStatus(emptyList(), false)
|
||||||
updateChannels.forEach { (_, channel) -> channel.cancel() }
|
updateChannels.forEach { (_, channel) -> channel.cancel() }
|
||||||
updateChannels.clear()
|
updateChannels.clear()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ enum class WebUIChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class WebUIFlavor(
|
enum class WebUIFlavor(
|
||||||
val uiName: String, val repoUrl: String,
|
val uiName: String,
|
||||||
|
val repoUrl: String,
|
||||||
val versionMappingUrl: String,
|
val versionMappingUrl: String,
|
||||||
val latestReleaseInfoUrl: String,
|
val latestReleaseInfoUrl: String,
|
||||||
val baseFileName: String
|
val baseFileName: String
|
||||||
|
|||||||
Reference in New Issue
Block a user