Last part of GQL conversion

This commit is contained in:
Syer10
2025-10-14 15:43:59 -04:00
parent 5b7228c687
commit 0245ef25fe
56 changed files with 343 additions and 691 deletions

View File

@@ -23,18 +23,13 @@ import ca.gosyer.jui.core.lang.throwIfCancellation
import ca.gosyer.jui.core.prefs.getAsFlow import ca.gosyer.jui.core.prefs.getAsFlow
import ca.gosyer.jui.domain.base.WebsocketService.Actions import ca.gosyer.jui.domain.base.WebsocketService.Actions
import ca.gosyer.jui.domain.base.WebsocketService.Status import ca.gosyer.jui.domain.base.WebsocketService.Status
import ca.gosyer.jui.domain.library.model.JobStatus import ca.gosyer.jui.domain.library.model.MangaUpdate
import ca.gosyer.jui.domain.library.model.UpdateStatus
import ca.gosyer.jui.domain.library.service.LibraryUpdateService import ca.gosyer.jui.domain.library.service.LibraryUpdateService
import ca.gosyer.jui.domain.library.service.LibraryUpdateService.Companion.status import ca.gosyer.jui.domain.library.service.LibraryUpdateService.Companion.status
import ca.gosyer.jui.i18n.MR import ca.gosyer.jui.i18n.MR
import com.diamondedge.logging.logging import com.diamondedge.logging.logging
import dev.icerock.moko.resources.desc.desc import dev.icerock.moko.resources.desc.desc
import dev.icerock.moko.resources.format import dev.icerock.moko.resources.format
import io.ktor.client.plugins.websocket.ws
import io.ktor.http.URLProtocol
import io.ktor.websocket.Frame
import io.ktor.websocket.readText
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -43,13 +38,9 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.job import kotlinx.coroutines.job
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@@ -154,31 +145,12 @@ class AndroidLibraryService : Service() {
throw CancellationException() throw CancellationException()
} }
runCatching { runCatching {
client.ws( appComponent.libraryUpdateService
host = serverUrl.host, .getSubscription()
port = serverUrl.port, .onEach {
path = serverUrl.encodedPath + "/api/v1/update", onReceived()
request = { }
if (serverUrl.protocol == URLProtocol.HTTPS) { .collect()
url.protocol = URLProtocol.WSS
}
},
) {
errorConnectionCount = 0
status.value = Status.RUNNING
send(Frame.Text("STATUS"))
incoming.receiveAsFlow()
.filterIsInstance<Frame.Text>()
.map { json.decodeFromString<UpdateStatus>(it.readText()) }
.distinctUntilChanged()
.drop(1)
.mapLatest(::onReceived)
.catch {
log.warn(it) { "Error running library update" }
}
.collect()
}
}.throwIfCancellation().isFailure.let { }.throwIfCancellation().isFailure.let {
status.value = Status.STARTING status.value = Status.STARTING
if (it) errorConnectionCount++ if (it) errorConnectionCount++
@@ -193,20 +165,16 @@ class AndroidLibraryService : Service() {
.launchIn(ioScope) .launchIn(ioScope)
} }
private fun onReceived(status: UpdateStatus) { private fun onReceived() {
LibraryUpdateService.updateStatus.value = status val status = LibraryUpdateService.updateStatus.value
val complete = status.mangaStatusMap[JobStatus.COMPLETE]?.size ?: 0 val total = status.jobsInfo.totalJobs
val failed = status.mangaStatusMap[JobStatus.FAILED]?.size ?: 0 val current = status.jobsInfo.finishedJobs
val running = status.mangaStatusMap[JobStatus.RUNNING]?.size ?: 0
val pending = status.mangaStatusMap[JobStatus.PENDING]?.size ?: 0
val skipped = status.mangaStatusMap[JobStatus.SKIPPED]?.size ?: 0
val total = complete + failed + running + pending + skipped
val current = complete + failed + skipped
if (current != total) { if (current != total) {
val notification = with(progressNotificationBuilder) { val notification = with(progressNotificationBuilder) {
val updatingText = status.mangaStatusMap[JobStatus.RUNNING] val updatingText = status.mangaUpdates
?.joinToString("\n") { it.title.chop(40) } .filter { it.status == MangaUpdate.Status.RUNNING }
.joinToString("\n") { it.manga.title.chop(40) }
setContentTitle( setContentTitle(
MR.strings.notification_updating MR.strings.notification_updating
.format(current, total) .format(current, total)

View File

@@ -57,10 +57,14 @@ mutation DeleteCategory($categoryId: Int!) {
} }
} }
query GetCategoryManga($categoryId: [Int!]!) { query GetCategoryManga($categoryId: Int!) {
mangas(orderBy: ID, condition: {categoryIds: $categoryId}) { category(id: $categoryId) {
nodes { id
...MangaFragment mangas {
nodes {
...LibraryMangaFragment
}
totalCount
} }
} }
} }

View File

@@ -3,3 +3,43 @@ mutation SetMangaInLibrary($id: Int!, $inLibrary: Boolean!) {
clientMutationId clientMutationId
} }
} }
query LibraryUpdateStatus {
libraryUpdateStatus {
categoryUpdates {
...CategoryUpdateFragment
}
jobsInfo {
...UpdaterJobsInfoFragment
}
mangaUpdates {
...MangaUpdateFragment
}
}
}
subscription LibraryUpdateStatusChanged($maxUpdates: Int = 30) {
libraryUpdateStatusChanged(input: {maxUpdates: $maxUpdates}) {
initial {
categoryUpdates {
...CategoryUpdateFragment
}
jobsInfo {
...UpdaterJobsInfoFragment
}
mangaUpdates {
...MangaUpdateFragment
}
}
categoryUpdates {
...CategoryUpdateFragment
}
mangaUpdates {
...MangaUpdateFragment
}
jobsInfo {
...UpdaterJobsInfoFragment
}
omittedUpdates
}
}

View File

@@ -0,0 +1,23 @@
fragment CategoryUpdateFragment on CategoryUpdateType {
category {
id
name
}
status
}
fragment UpdaterJobsInfoFragment on UpdaterJobsInfoType {
finishedJobs
isRunning
skippedCategoriesCount
skippedMangasCount
totalJobs
}
fragment MangaUpdateFragment on MangaUpdateType {
manga {
id
title
}
status
}

View File

@@ -20,7 +20,6 @@ import ca.gosyer.jui.data.graphql.SetCategoryMetaMutation
import ca.gosyer.jui.data.graphql.fragment.CategoryFragment import ca.gosyer.jui.data.graphql.fragment.CategoryFragment
import ca.gosyer.jui.data.manga.MangaRepositoryImpl.Companion.toManga import ca.gosyer.jui.data.manga.MangaRepositoryImpl.Companion.toManga
import ca.gosyer.jui.domain.category.model.Category import ca.gosyer.jui.domain.category.model.Category
import ca.gosyer.jui.domain.category.model.CategoryMeta
import ca.gosyer.jui.domain.category.service.CategoryRepository import ca.gosyer.jui.domain.category.service.CategoryRepository
import ca.gosyer.jui.domain.manga.model.Manga import ca.gosyer.jui.domain.manga.model.Manga
import ca.gosyer.jui.domain.server.Http import ca.gosyer.jui.domain.server.Http
@@ -133,12 +132,12 @@ class CategoryRepositoryImpl(
override fun getMangaFromCategory(categoryId: Long): Flow<List<Manga>> = override fun getMangaFromCategory(categoryId: Long): Flow<List<Manga>> =
apolloClient.query( apolloClient.query(
GetCategoryMangaQuery(listOf(categoryId.toInt())), GetCategoryMangaQuery(categoryId.toInt()),
) )
.toFlow() .toFlow()
.map { .map {
val data = it.dataAssertNoErrors val data = it.dataAssertNoErrors
data.mangas.nodes.map { it.mangaFragment.toManga() } data.category.mangas.nodes.map { it.libraryMangaFragment.toManga() }
} }
override fun updateCategoryMeta( override fun updateCategoryMeta(
@@ -162,7 +161,7 @@ class CategoryRepositoryImpl(
order = order, order = order,
name = name, name = name,
default = default, default = default,
meta = CategoryMeta(), meta = Category.CategoryMeta(),
) )
} }
} }

View File

@@ -15,7 +15,6 @@ import ca.gosyer.jui.data.graphql.fragment.ChapterWithMangaFragment
import ca.gosyer.jui.data.graphql.type.UpdateChapterPatchInput import ca.gosyer.jui.data.graphql.type.UpdateChapterPatchInput
import ca.gosyer.jui.data.manga.MangaRepositoryImpl.Companion.toManga import ca.gosyer.jui.data.manga.MangaRepositoryImpl.Companion.toManga
import ca.gosyer.jui.domain.chapter.model.Chapter import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.model.ChapterMeta
import ca.gosyer.jui.domain.chapter.service.ChapterRepository import ca.gosyer.jui.domain.chapter.service.ChapterRepository
import ca.gosyer.jui.domain.server.Http import ca.gosyer.jui.domain.server.Http
import ca.gosyer.jui.domain.updates.model.MangaAndChapter import ca.gosyer.jui.domain.updates.model.MangaAndChapter
@@ -190,7 +189,7 @@ class ChapterRepositoryImpl(
pageCount = pageCount, pageCount = pageCount,
lastReadAt = lastPageRead, lastReadAt = lastPageRead,
downloaded = isDownloaded, downloaded = isDownloaded,
meta = ChapterMeta( meta = Chapter.ChapterMeta(
juiPageOffset = meta.find { it.key == "juiPageOffset" }?.value?.toIntOrNull() ?: 0, juiPageOffset = meta.find { it.key == "juiPageOffset" }?.value?.toIntOrNull() ?: 0,
), ),
) )

View File

@@ -1,7 +1,19 @@
package ca.gosyer.jui.data.library package ca.gosyer.jui.data.library
import ca.gosyer.jui.data.ApolloAppClient import ca.gosyer.jui.data.ApolloAppClient
import ca.gosyer.jui.data.graphql.LibraryUpdateStatusChangedSubscription
import ca.gosyer.jui.data.graphql.LibraryUpdateStatusQuery
import ca.gosyer.jui.data.graphql.SetMangaInLibraryMutation import ca.gosyer.jui.data.graphql.SetMangaInLibraryMutation
import ca.gosyer.jui.data.graphql.fragment.CategoryUpdateFragment
import ca.gosyer.jui.data.graphql.fragment.MangaUpdateFragment
import ca.gosyer.jui.data.graphql.fragment.UpdaterJobsInfoFragment
import ca.gosyer.jui.data.graphql.type.CategoryJobStatus
import ca.gosyer.jui.data.graphql.type.MangaJobStatus
import ca.gosyer.jui.domain.library.model.CategoryUpdate
import ca.gosyer.jui.domain.library.model.MangaUpdate
import ca.gosyer.jui.domain.library.model.UpdateStatus
import ca.gosyer.jui.domain.library.model.UpdaterJobsInfo
import ca.gosyer.jui.domain.library.model.UpdaterUpdates
import ca.gosyer.jui.domain.library.service.LibraryRepository import ca.gosyer.jui.domain.library.service.LibraryRepository
import ca.gosyer.jui.domain.server.Http import ca.gosyer.jui.domain.server.Http
import com.apollographql.apollo.ApolloClient import com.apollographql.apollo.ApolloClient
@@ -33,4 +45,92 @@ class LibraryRepositoryImpl(
override fun addMangaToLibrary(mangaId: Long): Flow<Unit> = setMangaInLibrary(mangaId, true) override fun addMangaToLibrary(mangaId: Long): Flow<Unit> = setMangaInLibrary(mangaId, true)
override fun removeMangaFromLibrary(mangaId: Long): Flow<Unit> = setMangaInLibrary(mangaId, false) override fun removeMangaFromLibrary(mangaId: Long): Flow<Unit> = setMangaInLibrary(mangaId, false)
override fun libraryUpdateSubscription(): Flow<UpdaterUpdates> =
apolloClient.subscription(
LibraryUpdateStatusChangedSubscription(),
)
.toFlow()
.map {
val data = it.dataAssertNoErrors.libraryUpdateStatusChanged
UpdaterUpdates(
data.initial?.let {
UpdateStatus(
it.categoryUpdates.map {
it.categoryUpdateFragment.toCategoryUpdate()
},
it.mangaUpdates.map {
it.mangaUpdateFragment.toMangaUpdate()
},
it.jobsInfo.updaterJobsInfoFragment.toUpdaterJobsInfo(),
)
},
data.categoryUpdates.map {
it.categoryUpdateFragment.toCategoryUpdate()
},
data.mangaUpdates.map {
it.mangaUpdateFragment.toMangaUpdate()
},
data.jobsInfo.updaterJobsInfoFragment.toUpdaterJobsInfo(),
data.omittedUpdates,
)
}
override fun libraryUpdateStatus(): Flow<UpdateStatus> =
apolloClient.query(
LibraryUpdateStatusQuery(),
)
.toFlow()
.map {
val data = it.dataAssertNoErrors.libraryUpdateStatus
UpdateStatus(
data.categoryUpdates.map {
it.categoryUpdateFragment.toCategoryUpdate()
},
data.mangaUpdates.map {
it.mangaUpdateFragment.toMangaUpdate()
},
data.jobsInfo.updaterJobsInfoFragment.toUpdaterJobsInfo(),
)
}
companion object {
fun UpdaterJobsInfoFragment.toUpdaterJobsInfo() =
UpdaterJobsInfo(
finishedJobs,
isRunning,
skippedCategoriesCount,
skippedMangasCount,
totalJobs,
)
fun CategoryUpdateFragment.toCategoryUpdate() =
CategoryUpdate(
CategoryUpdate.UpdateCategory(
category.id.toLong(),
category.name,
),
when (status) {
CategoryJobStatus.UPDATING -> CategoryUpdate.Status.UPDATING
CategoryJobStatus.SKIPPED -> CategoryUpdate.Status.SKIPPED
CategoryJobStatus.UNKNOWN__ -> CategoryUpdate.Status.UPDATING
},
)
fun MangaUpdateFragment.toMangaUpdate() =
MangaUpdate(
MangaUpdate.UpdateManga(
manga.id.toLong(),
manga.title,
),
when (status) {
MangaJobStatus.PENDING -> MangaUpdate.Status.PENDING
MangaJobStatus.RUNNING -> MangaUpdate.Status.RUNNING
MangaJobStatus.COMPLETE -> MangaUpdate.Status.COMPLETE
MangaJobStatus.FAILED -> MangaUpdate.Status.FAILED
MangaJobStatus.SKIPPED -> MangaUpdate.Status.SKIPPED
MangaJobStatus.UNKNOWN__ -> MangaUpdate.Status.FAILED
},
)
}
} }

View File

@@ -100,7 +100,7 @@ suspend fun main() {
.filter { it == ServerResult.STARTED || it == ServerResult.UNUSED } .filter { it == ServerResult.STARTED || it == ServerResult.UNUSED }
.onEach { .onEach {
appComponent.downloadService.getSubscription().launchIn(GlobalScope) appComponent.downloadService.getSubscription().launchIn(GlobalScope)
appComponent.libraryUpdateService.init() appComponent.libraryUpdateService.getSubscription().launchIn(GlobalScope)
} }
.launchIn(GlobalScope) .launchIn(GlobalScope)

View File

@@ -113,11 +113,6 @@ interface SharedDomainComponent : CoreComponent {
val serverHostPreferencesFactory: ServerHostPreferences val serverHostPreferencesFactory: ServerHostPreferences
get() = ServerHostPreferences(preferenceFactory.create("host")) get() = ServerHostPreferences(preferenceFactory.create("host"))
@get:AppScope
@get:Provides
val libraryUpdateServiceFactory: LibraryUpdateService
get() = LibraryUpdateService(serverPreferences, http)
@get:AppScope @get:AppScope
@get:Provides @get:Provides
val serverListenersFactory: ServerListeners val serverListenersFactory: ServerListeners

View File

@@ -6,9 +6,6 @@
package ca.gosyer.jui.domain.backup.model package ca.gosyer.jui.domain.backup.model
import kotlinx.serialization.Serializable
@Serializable
data class BackupValidationResult( data class BackupValidationResult(
val missingSources: List<String>, val missingSources: List<String>,
val missingTrackers: List<String>, val missingTrackers: List<String>,

View File

@@ -6,8 +6,6 @@
package ca.gosyer.jui.domain.backup.model package ca.gosyer.jui.domain.backup.model
import kotlinx.serialization.Serializable
enum class RestoreState { enum class RestoreState {
IDLE, IDLE,
SUCCESS, SUCCESS,
@@ -19,7 +17,6 @@ enum class RestoreState {
UNKNOWN, UNKNOWN,
} }
@Serializable
data class RestoreStatus( data class RestoreStatus(
val state: RestoreState, val state: RestoreState,
val completed: Int, val completed: Int,

View File

@@ -6,6 +6,7 @@
package ca.gosyer.jui.domain.category.interactor package ca.gosyer.jui.domain.category.interactor
import ca.gosyer.jui.domain.ServerListeners
import ca.gosyer.jui.domain.category.service.CategoryRepository import ca.gosyer.jui.domain.category.service.CategoryRepository
import com.diamondedge.logging.logging import com.diamondedge.logging.logging
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
@@ -16,6 +17,7 @@ import me.tatarka.inject.annotations.Inject
@Inject @Inject
class GetCategories( class GetCategories(
private val categoryRepository: CategoryRepository, private val categoryRepository: CategoryRepository,
private val serverListeners: ServerListeners,
) { ) {
suspend fun await( suspend fun await(
dropDefault: Boolean = false, dropDefault: Boolean = false,
@@ -27,15 +29,16 @@ class GetCategories(
} }
.singleOrNull() .singleOrNull()
fun asFlow(dropDefault: Boolean = false) = fun asFlow(dropDefault: Boolean = false) = serverListeners.combineCategoryManga(
categoryRepository.getCategories() categoryRepository.getCategories()
.map { categories -> ).map { categories ->
if (dropDefault) { if (dropDefault) {
categories.filterNot { it.name.equals("default", true) } categories.filterNot { it.name.equals("default", true) }
} else { } else {
categories categories
} }
} }
companion object { companion object {
private val log = logging() private val log = logging()

View File

@@ -7,9 +7,7 @@
package ca.gosyer.jui.domain.category.model package ca.gosyer.jui.domain.category.model
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import kotlinx.serialization.Serializable
@Serializable
@Immutable @Immutable
data class Category( data class Category(
val id: Long, val id: Long,
@@ -17,10 +15,9 @@ data class Category(
val name: String, val name: String,
val default: Boolean, val default: Boolean,
val meta: CategoryMeta, val meta: CategoryMeta,
) ) {
@Immutable
@Serializable data class CategoryMeta(
@Immutable val example: Int = 0,
data class CategoryMeta( )
val example: Int = 0, }
)

View File

@@ -7,9 +7,7 @@
package ca.gosyer.jui.domain.chapter.model package ca.gosyer.jui.domain.chapter.model
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import kotlinx.serialization.Serializable
@Serializable
@Immutable @Immutable
data class Chapter( data class Chapter(
val id: Long, val id: Long,
@@ -29,10 +27,10 @@ data class Chapter(
val lastReadAt: Int?, val lastReadAt: Int?,
val downloaded: Boolean, val downloaded: Boolean,
val meta: ChapterMeta, val meta: ChapterMeta,
) ) {
@Immutable
data class ChapterMeta(
val juiPageOffset: Int = 0,
)
}
@Serializable
@Immutable
data class ChapterMeta(
val juiPageOffset: Int = 0,
)

View File

@@ -1,15 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.chapter.model
import kotlinx.serialization.Serializable
@Serializable
data class ChapterBatchEditInput(
val chapterIds: List<Long>? = null,
val change: ChapterChange?,
)

View File

@@ -1,17 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.chapter.model
import kotlinx.serialization.Serializable
@Serializable
data class ChapterChange(
val isRead: Boolean? = null,
val isBookmarked: Boolean? = null,
val lastPageRead: Int? = null,
val delete: Boolean? = null,
)

View File

@@ -1,16 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.chapter.model
import kotlinx.serialization.Serializable
@Serializable
data class MangaChapterBatchEditInput(
val chapterIds: List<Long>? = null,
val chapterIndexes: List<Int>? = null,
val change: ChapterChange?,
)

View File

@@ -1,14 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.download.model
import kotlinx.serialization.Serializable
@Serializable
data class DownloadEnqueue(
val chapterIds: List<Long>,
)

View File

@@ -7,9 +7,7 @@
package ca.gosyer.jui.domain.extension.model package ca.gosyer.jui.domain.extension.model
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import kotlinx.serialization.Serializable
@Serializable
@Immutable @Immutable
data class Extension( data class Extension(
val name: String, val name: String,

View File

@@ -0,0 +1,17 @@
package ca.gosyer.jui.domain.library.model
data class CategoryUpdate(
val category: UpdateCategory,
val status: Status,
) {
enum class Status {
UPDATING,
SKIPPED
}
data class UpdateCategory(
val id: Long,
val name: String,
)
}

View File

@@ -1,17 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.library.model
import androidx.compose.runtime.Stable
import kotlinx.serialization.Serializable
@Serializable
@Stable
enum class CategoryUpdateStatus {
UPDATING,
SKIPPED,
}

View File

@@ -1,20 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.library.model
import androidx.compose.runtime.Stable
import kotlinx.serialization.Serializable
@Serializable
@Stable
enum class JobStatus {
PENDING,
RUNNING,
COMPLETE,
FAILED,
SKIPPED,
}

View File

@@ -0,0 +1,19 @@
package ca.gosyer.jui.domain.library.model
data class MangaUpdate(
val manga: UpdateManga,
val status: Status,
) {
enum class Status {
PENDING,
RUNNING,
COMPLETE,
FAILED,
SKIPPED
}
data class UpdateManga(
val id: Long,
val title: String,
)
}

View File

@@ -6,15 +6,8 @@
package ca.gosyer.jui.domain.library.model package ca.gosyer.jui.domain.library.model
import androidx.compose.runtime.Immutable
import ca.gosyer.jui.domain.category.model.Category
import ca.gosyer.jui.domain.manga.model.Manga
import kotlinx.serialization.Serializable
@Serializable
@Immutable
data class UpdateStatus( data class UpdateStatus(
val categoryStatusMap: Map<CategoryUpdateStatus, List<Category>> = emptyMap(), val categoryUpdates: List<CategoryUpdate>,
val mangaStatusMap: Map<JobStatus, List<Manga>> = emptyMap(), val mangaUpdates: List<MangaUpdate>,
val running: Boolean, val jobsInfo: UpdaterJobsInfo,
) )

View File

@@ -0,0 +1,9 @@
package ca.gosyer.jui.domain.library.model
data class UpdaterJobsInfo(
val finishedJobs: Int,
val isRunning: Boolean,
val skippedCategoriesCount: Int,
val skippedMangasCount: Int,
val totalJobs: Int,
)

View File

@@ -0,0 +1,9 @@
package ca.gosyer.jui.domain.library.model
data class UpdaterUpdates(
val initial: UpdateStatus?,
val categoryUpdates: List<CategoryUpdate>,
val mangaUpdates: List<MangaUpdate>,
val jobsInfo: UpdaterJobsInfo,
val omittedUpdates: Boolean,
)

View File

@@ -6,10 +6,17 @@
package ca.gosyer.jui.domain.library.service package ca.gosyer.jui.domain.library.service
import ca.gosyer.jui.domain.library.model.UpdateStatus
import ca.gosyer.jui.domain.library.model.UpdaterUpdates
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface LibraryRepository { interface LibraryRepository {
fun addMangaToLibrary(mangaId: Long): Flow<Unit> fun addMangaToLibrary(mangaId: Long): Flow<Unit>
fun removeMangaFromLibrary(mangaId: Long): Flow<Unit> fun removeMangaFromLibrary(mangaId: Long): Flow<Unit>
fun libraryUpdateSubscription(): Flow<UpdaterUpdates>
fun libraryUpdateStatus(): Flow<UpdateStatus>
} }

View File

@@ -8,33 +8,73 @@ package ca.gosyer.jui.domain.library.service
import ca.gosyer.jui.domain.base.WebsocketService import ca.gosyer.jui.domain.base.WebsocketService
import ca.gosyer.jui.domain.library.model.UpdateStatus import ca.gosyer.jui.domain.library.model.UpdateStatus
import ca.gosyer.jui.domain.server.Http import ca.gosyer.jui.domain.library.model.UpdaterJobsInfo
import ca.gosyer.jui.domain.server.service.ServerPreferences
import com.diamondedge.logging.logging import com.diamondedge.logging.logging
import io.ktor.websocket.Frame
import io.ktor.websocket.readText
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.update
import me.tatarka.inject.annotations.Inject import me.tatarka.inject.annotations.Inject
@Inject @Inject
class LibraryUpdateService( class LibraryUpdateService(
serverPreferences: ServerPreferences, private val libraryUpdateRepository: LibraryRepository,
client: Http, ) {
) : WebsocketService(serverPreferences, client) { fun getSubscription() = libraryUpdateRepository.libraryUpdateSubscription()
override val status: MutableStateFlow<Status> .onStart {
get() = LibraryUpdateService.status log.info { "Starting library update status subscription" }
status.value = WebsocketService.Status.STARTING
}
.catch { error ->
log.error(error) { "Error in library update status subscription" }
status.value = WebsocketService.Status.STOPPED
}
.map { updates ->
status.value = WebsocketService.Status.RUNNING
if (updates.omittedUpdates) {
log.info { "Omitted updates detected, fetching fresh library update status" }
fetchLibraryUpdateStatus()
return@map
}
if (updates.initial != null) {
updateStatus.value = updates.initial
}
updates.jobsInfo.let { jobsInfo ->
updateStatus.update {
it.copy(
jobsInfo = jobsInfo,
categoryUpdates = updates.categoryUpdates,
mangaUpdates = updates.mangaUpdates
)
}
}
}
override val query: String private suspend fun fetchLibraryUpdateStatus() {
get() = "/api/v1/update" val status = libraryUpdateRepository.libraryUpdateStatus().firstOrNull()
if (status != null) {
override suspend fun onReceived(frame: Frame.Text) { updateStatus.value = status
updateStatus.value = json.decodeFromString<UpdateStatus>(frame.readText()) }
} }
companion object { companion object {
private val log = logging() private val log = logging()
val status = MutableStateFlow(Status.STARTING) val status = MutableStateFlow(WebsocketService.Status.STARTING)
val updateStatus = MutableStateFlow(UpdateStatus(emptyMap(), emptyMap(), false)) val updateStatus = MutableStateFlow(
UpdateStatus(
emptyList(),
emptyList(),
UpdaterJobsInfo(
0,
false,
0,
0,
0,
)
)
)
} }
} }

View File

@@ -11,10 +11,8 @@ import androidx.compose.runtime.Stable
import ca.gosyer.jui.domain.source.model.Source import ca.gosyer.jui.domain.source.model.Source
import ca.gosyer.jui.i18n.MR import ca.gosyer.jui.i18n.MR
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
@Serializable
@Immutable @Immutable
data class Manga( data class Manga(
val id: Long, val id: Long,
@@ -46,7 +44,6 @@ data class Manga(
val chaptersAge: Long?, val chaptersAge: Long?,
) )
@Serializable
@Immutable @Immutable
data class MangaMeta( data class MangaMeta(
val juiReaderMode: String = DEFAULT_READER_MODE, val juiReaderMode: String = DEFAULT_READER_MODE,
@@ -56,7 +53,6 @@ data class MangaMeta(
} }
} }
@Serializable
@Stable @Stable
enum class MangaStatus( enum class MangaStatus(
@Transient val res: StringResource, @Transient val res: StringResource,
@@ -70,7 +66,6 @@ enum class MangaStatus(
ON_HIATUS(MR.strings.status_on_hiatus), ON_HIATUS(MR.strings.status_on_hiatus),
} }
@Serializable
@Stable @Stable
enum class UpdateStrategy { enum class UpdateStrategy {
ALWAYS_UPDATE, ALWAYS_UPDATE,

View File

@@ -8,9 +8,7 @@ package ca.gosyer.jui.domain.settings.model
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import kotlinx.serialization.Serializable
@Serializable
@Immutable @Immutable
data class About( data class About(
val name: String, val name: String,
@@ -21,7 +19,6 @@ data class About(
val discord: String, val discord: String,
) )
@Serializable
@Stable @Stable
enum class AboutBuildType { enum class AboutBuildType {
Preview, Preview,

View File

@@ -8,9 +8,7 @@ package ca.gosyer.jui.domain.source.model
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import ca.gosyer.jui.domain.manga.model.Manga import ca.gosyer.jui.domain.manga.model.Manga
import kotlinx.serialization.Serializable
@Serializable
@Stable @Stable
data class MangaPage( data class MangaPage(
val mangaList: List<Manga>, val mangaList: List<Manga>,

View File

@@ -7,10 +7,8 @@
package ca.gosyer.jui.domain.source.model package ca.gosyer.jui.domain.source.model
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import kotlinx.serialization.Serializable
import ca.gosyer.jui.core.io.Serializable as JvmSerializable import ca.gosyer.jui.core.io.Serializable as JvmSerializable
@Serializable
@Stable @Stable
data class Source( data class Source(
val id: Long, val id: Long,

View File

@@ -1,22 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcefilters
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("CheckBox")
data class CheckBoxFilterOld(
override val filter: CheckBoxProps,
) : SourceFilterOld() {
@Serializable
data class CheckBoxProps(
override val name: String,
override val state: Boolean,
) : Props<Boolean>
}

View File

@@ -1,22 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcefilters
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("Group")
data class GroupFilterOld(
override val filter: GroupProps,
) : SourceFilterOld() {
@Serializable
data class GroupProps(
override val name: String,
override val state: List<SourceFilterOld>,
) : Props<List<SourceFilterOld>>
}

View File

@@ -1,24 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcefilters
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
@Serializable
@SerialName("Header")
data class HeaderFilterOld(
override val filter: HeaderProps,
) : SourceFilterOld() {
@Serializable
data class HeaderProps(
override val name: String,
@Transient
override val state: Int = 0,
) : Props<Int>
}

View File

@@ -1,25 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcefilters
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
@Serializable
@SerialName("Select")
data class SelectFilterOld(
override val filter: SelectProps,
) : SourceFilterOld() {
@Serializable
data class SelectProps(
override val name: String,
override val state: Int,
val values: List<JsonElement>,
val displayValues: List<String>? = null,
) : Props<Int>
}

View File

@@ -1,24 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcefilters
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
@Serializable
@SerialName("Separator")
data class SeparatorFilterOld(
override val filter: SeparatorProps,
) : SourceFilterOld() {
@Serializable
data class SeparatorProps(
override val name: String,
@Transient
override val state: Int = 0,
) : Props<Int>
}

View File

@@ -1,29 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcefilters
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("Sort")
data class SortFilterOld(
override val filter: SortProps,
) : SourceFilterOld() {
@Serializable
data class SortProps(
override val name: String,
override val state: Selection?,
val values: List<String>,
) : Props<Selection?>
@Serializable
data class Selection(
val index: Int,
val ascending: Boolean,
)
}

View File

@@ -6,23 +6,6 @@
package ca.gosyer.jui.domain.source.model.sourcefilters package ca.gosyer.jui.domain.source.model.sourcefilters
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@Serializable
data class SourceFilterChangeOld(
val position: Int,
val state: String,
) {
constructor(position: Int, state: Any) : this(
position,
if (state is SortFilterOld.Selection) {
Json.encodeToString(state)
} else {
state.toString()
},
)
}
sealed interface SourceFilter { sealed interface SourceFilter {
val position: Int val position: Int

View File

@@ -1,15 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcefilters
import kotlinx.serialization.Serializable
@Serializable
data class SourceFilterData(
val searchTerm: String?,
val filter: List<SourceFilterChangeOld>?,
)

View File

@@ -1,19 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcefilters
import kotlinx.serialization.Serializable
@Serializable
sealed class SourceFilterOld {
abstract val filter: Props<*>
}
interface Props<T> {
val name: String
val state: T
}

View File

@@ -1,22 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcefilters
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("Text")
data class TextFilterOld(
override val filter: TextProps,
) : SourceFilterOld() {
@Serializable
data class TextProps(
override val name: String,
override val state: String,
) : Props<String>
}

View File

@@ -1,22 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcefilters
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("TriState")
data class TriStateFilterOld(
override val filter: TriStateProps,
) : SourceFilterOld() {
@Serializable
data class TriStateProps(
override val name: String,
override val state: Int,
) : Props<Int>
}

View File

@@ -1,18 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcepreference
import androidx.compose.runtime.Immutable
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("CheckBoxPreference")
@Immutable
data class CheckBoxPreferenceOld(
override val props: TwoStateProps,
) : SourcePreferenceOld()

View File

@@ -1,32 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcepreference
import androidx.compose.runtime.Immutable
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("EditTextPreference")
@Immutable
data class EditTextPreferenceOld(
override val props: EditTextProps,
) : SourcePreferenceOld() {
@Serializable
@Immutable
data class EditTextProps(
override val key: String,
override val title: String?,
override val summary: String?,
override val currentValue: String?,
override val defaultValue: String?,
override val defaultValueType: String,
val dialogTitle: String?,
val dialogMessage: String?,
val text: String?,
) : Props<String?>
}

View File

@@ -1,31 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcepreference
import androidx.compose.runtime.Immutable
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("ListPreference")
@Immutable
data class ListPreferenceOld(
override val props: ListProps,
) : SourcePreferenceOld() {
@Serializable
@Immutable
data class ListProps(
override val key: String,
override val title: String,
override val summary: String?,
override val currentValue: String?,
override val defaultValue: String?,
override val defaultValueType: String,
val entries: List<String>,
val entryValues: List<String>,
) : Props<String?>
}

View File

@@ -1,33 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcepreference
import androidx.compose.runtime.Immutable
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("MultiSelectListPreference")
@Immutable
data class MultiSelectListPreferenceOld(
override val props: MultiSelectListProps,
) : SourcePreferenceOld() {
@Serializable
@Immutable
data class MultiSelectListProps(
override val key: String,
override val title: String,
override val summary: String?,
override val currentValue: List<String>?,
override val defaultValue: List<String>?,
override val defaultValueType: String,
val dialogTitle: String?,
val dialogMessage: String?,
val entries: List<String>,
val entryValues: List<String>,
) : Props<List<String>?>
}

View File

@@ -6,25 +6,6 @@
package ca.gosyer.jui.domain.source.model.sourcepreference package ca.gosyer.jui.domain.source.model.sourcepreference
import androidx.compose.runtime.Immutable
import kotlinx.serialization.Serializable
@Serializable
@Immutable
sealed class SourcePreferenceOld {
abstract val props: Props<*>
}
@Immutable
interface Props<T> {
val key: String
val title: String?
val summary: String?
val currentValue: T
val defaultValue: T
val defaultValueType: String
}
sealed interface SourcePreference { sealed interface SourcePreference {
val position: Int val position: Int
} }

View File

@@ -1,26 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcepreference
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@Serializable
data class SourcePreferenceChange(
val position: Int,
val value: String,
) {
constructor(position: Int, value: Any) : this(
position,
if (value is List<*>) {
@Suppress("UNCHECKED_CAST")
Json.encodeToString(value as List<String>)
} else {
value.toString()
},
)
}

View File

@@ -1,18 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcepreference
import androidx.compose.runtime.Immutable
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("SwitchPreferenceCompat")
@Immutable
data class SwitchPreferenceOld(
override val props: TwoStateProps,
) : SourcePreferenceOld()

View File

@@ -1,21 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.model.sourcepreference
import androidx.compose.runtime.Immutable
import kotlinx.serialization.Serializable
@Serializable
@Immutable
data class TwoStateProps(
override val key: String,
override val title: String?,
override val summary: String?,
override val currentValue: Boolean?,
override val defaultValue: Boolean?,
override val defaultValueType: String,
) : Props<Boolean?>

View File

@@ -9,9 +9,7 @@ package ca.gosyer.jui.domain.updates.model
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import ca.gosyer.jui.domain.chapter.model.Chapter import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.manga.model.Manga import ca.gosyer.jui.domain.manga.model.Manga
import kotlinx.serialization.Serializable
@Serializable
@Immutable @Immutable
data class MangaAndChapter( data class MangaAndChapter(
val manga: Manga, val manga: Manga,

View File

@@ -7,9 +7,7 @@
package ca.gosyer.jui.domain.updates.model package ca.gosyer.jui.domain.updates.model
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import kotlinx.serialization.Serializable
@Serializable
@Immutable @Immutable
data class Updates( data class Updates(
val page: List<MangaAndChapter>, val page: List<MangaAndChapter>,

View File

@@ -22,7 +22,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import ca.gosyer.jui.domain.base.WebsocketService import ca.gosyer.jui.domain.base.WebsocketService
import ca.gosyer.jui.domain.library.model.JobStatus
import ca.gosyer.jui.i18n.MR import ca.gosyer.jui.i18n.MR
import ca.gosyer.jui.ui.base.LocalViewModels import ca.gosyer.jui.ui.base.LocalViewModels
import ca.gosyer.jui.uicore.resources.stringResource import ca.gosyer.jui.uicore.resources.stringResource
@@ -37,27 +36,18 @@ fun LibraryUpdatesExtraInfo() {
val serviceStatus by vm.serviceStatus.collectAsState() val serviceStatus by vm.serviceStatus.collectAsState()
val updateStatus by vm.updateStatus.collectAsState() val updateStatus by vm.updateStatus.collectAsState()
fun Map<JobStatus, List<*>>.getSize(jobStatus: JobStatus): Int = get(jobStatus)?.size ?: 0
val current = remember(updateStatus) { val current = remember(updateStatus) {
updateStatus.mangaStatusMap.run { updateStatus.jobsInfo.finishedJobs
getSize(JobStatus.COMPLETE) + getSize(JobStatus.FAILED) + getSize(JobStatus.SKIPPED)
}
} }
val total = remember(updateStatus) { val total = remember(updateStatus) {
updateStatus.mangaStatusMap.run { updateStatus.jobsInfo.totalJobs
getSize(JobStatus.COMPLETE) +
getSize(JobStatus.FAILED) +
getSize(JobStatus.PENDING) +
getSize(JobStatus.RUNNING) +
getSize(JobStatus.SKIPPED)
}
} }
val text = when (serviceStatus) { val text = when (serviceStatus) {
WebsocketService.Status.STARTING -> stringResource(MR.strings.downloads_loading) WebsocketService.Status.STARTING -> stringResource(MR.strings.downloads_loading)
WebsocketService.Status.RUNNING -> { WebsocketService.Status.RUNNING -> {
if (updateStatus.running) { if (updateStatus.jobsInfo.isRunning) {
stringResource(MR.strings.notification_updating, current, total) stringResource(MR.strings.notification_updating, current, total)
} else { } else {
null null

View File

@@ -9,11 +9,13 @@ package ca.gosyer.jui.ui.main.components
import ca.gosyer.jui.domain.base.WebsocketService import ca.gosyer.jui.domain.base.WebsocketService
import ca.gosyer.jui.domain.library.service.LibraryUpdateService import ca.gosyer.jui.domain.library.service.LibraryUpdateService
import ca.gosyer.jui.uicore.vm.ContextWrapper import ca.gosyer.jui.uicore.vm.ContextWrapper
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.launchIn
internal actual fun startLibraryUpdatesService( internal actual fun startLibraryUpdatesService(
contextWrapper: ContextWrapper, contextWrapper: ContextWrapper,
libraryUpdatesService: LibraryUpdateService, libraryUpdatesService: LibraryUpdateService,
actions: WebsocketService.Actions, actions: WebsocketService.Actions,
) { ) {
libraryUpdatesService.init() libraryUpdatesService.getSubscription().launchIn(GlobalScope)
} }

View File

@@ -9,11 +9,13 @@ package ca.gosyer.jui.ui.main.components
import ca.gosyer.jui.domain.base.WebsocketService import ca.gosyer.jui.domain.base.WebsocketService
import ca.gosyer.jui.domain.library.service.LibraryUpdateService import ca.gosyer.jui.domain.library.service.LibraryUpdateService
import ca.gosyer.jui.uicore.vm.ContextWrapper import ca.gosyer.jui.uicore.vm.ContextWrapper
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.launchIn
internal actual fun startLibraryUpdatesService( internal actual fun startLibraryUpdatesService(
contextWrapper: ContextWrapper, contextWrapper: ContextWrapper,
libraryUpdatesService: LibraryUpdateService, libraryUpdatesService: LibraryUpdateService,
actions: WebsocketService.Actions, actions: WebsocketService.Actions,
) { ) {
libraryUpdatesService.init() libraryUpdatesService.getSubscription().launchIn(GlobalScope)
} }