diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index 112a2cfe..a58e85c1 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -6,9 +6,9 @@ object Config { // Tachidesk-Server version const val tachideskVersion = "v0.6.5" // Match this to the Tachidesk-Server commit count - const val serverCode = 1143 + const val serverCode = 1148 const val preview = true - const val previewCommit = "2ac5c1362c0c5bb8f39d1049d6f72328102dd182" + const val previewCommit = "2195c3df765c3e1e435595d9edbec8ad3590bf46" val desktopJvmTarget = JavaVersion.VERSION_17 val androidJvmTarget = JavaVersion.VERSION_11 diff --git a/data/src/commonMain/kotlin/ca/gosyer/jui/data/DataComponent.kt b/data/src/commonMain/kotlin/ca/gosyer/jui/data/DataComponent.kt index c9f6d545..cdd1183f 100644 --- a/data/src/commonMain/kotlin/ca/gosyer/jui/data/DataComponent.kt +++ b/data/src/commonMain/kotlin/ca/gosyer/jui/data/DataComponent.kt @@ -13,6 +13,7 @@ import ca.gosyer.jui.domain.chapter.service.ChapterRepository import ca.gosyer.jui.domain.createIt import ca.gosyer.jui.domain.download.service.DownloadRepository import ca.gosyer.jui.domain.extension.service.ExtensionRepository +import ca.gosyer.jui.domain.global.service.GlobalRepository import ca.gosyer.jui.domain.library.service.LibraryRepository import ca.gosyer.jui.domain.manga.service.MangaRepository import ca.gosyer.jui.domain.server.Http @@ -48,6 +49,9 @@ interface DataComponent { @Provides fun extensionRepository(ktorfit: Ktorfit) = ktorfit.createIt() + @Provides + fun globalRepository(ktorfit: Ktorfit) = ktorfit.createIt() + @Provides fun libraryRepository(ktorfit: Ktorfit) = ktorfit.createIt() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/UpdateCategoryMeta.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/UpdateCategoryMeta.kt new file mode 100644 index 00000000..734ba6dd --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/UpdateCategoryMeta.kt @@ -0,0 +1,47 @@ +/* + * 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.category.interactor + +import ca.gosyer.jui.domain.category.model.Category +import ca.gosyer.jui.domain.category.service.CategoryRepository +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flow +import me.tatarka.inject.annotations.Inject +import org.lighthousegames.logging.logging + +class UpdateCategoryMeta @Inject constructor(private val categoryRepository: CategoryRepository) { + + suspend fun await( + category: Category, + example: Int = category.meta.example, + onError: suspend (Throwable) -> Unit = {} + ) = asFlow(category, example) + .catch { + onError(it) + log.warn(it) { "Failed to update ${category.name}(${category.id}) meta" } + } + .collect() + + fun asFlow( + category: Category, + example: Int = category.meta.example, + ) = flow { + if (example != category.meta.example) { + categoryRepository.updateCategoryMeta( + category.id, + "example", + example.toString() + ).collect() + } + emit(Unit) + } + + companion object { + private val log = logging() + } +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/model/Category.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/model/Category.kt index b251f55b..f65bb915 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/model/Category.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/model/Category.kt @@ -15,5 +15,12 @@ data class Category( val id: Long, val order: Int, val name: String, - val default: Boolean + val default: Boolean, + val meta: CategoryMeta +) + +@Serializable +@Immutable +data class CategoryMeta( + val example: Int = 0 ) diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/service/CategoryRepository.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/service/CategoryRepository.kt index 1791282d..3ec39848 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/service/CategoryRepository.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/service/CategoryRepository.kt @@ -68,4 +68,12 @@ interface CategoryRepository { fun getMangaFromCategory( @Path("categoryId") categoryId: Long ): Flow> + + @FormUrlEncoded + @PATCH("api/v1/category/{categoryId}/meta") + fun updateCategoryMeta( + @Path("categoryId") categoryId: Long, + @Field("key") key: String, + @Field("value") value: String + ): Flow } diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/ReorderChapterDownload.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/ReorderChapterDownload.kt new file mode 100644 index 00000000..2850c1e7 --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/ReorderChapterDownload.kt @@ -0,0 +1,49 @@ +/* + * 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.interactor + +import ca.gosyer.jui.domain.chapter.model.Chapter +import ca.gosyer.jui.domain.download.service.DownloadRepository +import ca.gosyer.jui.domain.manga.model.Manga +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect +import me.tatarka.inject.annotations.Inject +import org.lighthousegames.logging.logging + +class ReorderChapterDownload @Inject constructor(private val downloadRepository: DownloadRepository) { + + suspend fun await(mangaId: Long, index: Int, to: Int, onError: suspend (Throwable) -> Unit = {}) = asFlow(mangaId, index, to) + .catch { + onError(it) + log.warn(it) { "Failed to reorder chapter download for $index of $mangaId to $to" } + } + .collect() + + suspend fun await(manga: Manga, index: Int, to: Int, onError: suspend (Throwable) -> Unit = {}) = asFlow(manga, index, to) + .catch { + onError(it) + log.warn(it) { "Failed to reorder chapter download for $index of ${manga.title}(${manga.id}) to $to" } + } + .collect() + + suspend fun await(chapter: Chapter, to: Int, onError: suspend (Throwable) -> Unit = {}) = asFlow(chapter, to) + .catch { + onError(it) + log.warn(it) { "Failed to reorder chapter download for ${chapter.index} of ${chapter.mangaId} to $to" } + } + .collect() + + fun asFlow(mangaId: Long, index: Int, to: Int) = downloadRepository.reorderChapterDownload(mangaId, index, to) + + fun asFlow(manga: Manga, index: Int, to: Int) = downloadRepository.reorderChapterDownload(manga.id, index, to) + + fun asFlow(chapter: Chapter, to: Int) = downloadRepository.reorderChapterDownload(chapter.mangaId, chapter.index, to) + + companion object { + private val log = logging() + } +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/service/DownloadRepository.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/service/DownloadRepository.kt index b35b1b40..b12a3806 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/service/DownloadRepository.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/service/DownloadRepository.kt @@ -11,6 +11,7 @@ import de.jensklingenberg.ktorfit.http.Body import de.jensklingenberg.ktorfit.http.DELETE import de.jensklingenberg.ktorfit.http.GET import de.jensklingenberg.ktorfit.http.Headers +import de.jensklingenberg.ktorfit.http.PATCH import de.jensklingenberg.ktorfit.http.POST import de.jensklingenberg.ktorfit.http.Path import io.ktor.client.statement.HttpResponse @@ -38,6 +39,13 @@ interface DownloadRepository { @Path("chapterIndex") chapterIndex: Int ): Flow + @PATCH("api/v1/download/{mangaId}/chapter/{chapterIndex}/reorder/{to}") + fun reorderChapterDownload( + @Path("mangaId") mangaId: Long, + @Path("chapterIndex") chapterIndex: Int, + @Path("to") to: Int + ): Flow + @POST("api/v1/download/batch") @Headers("Content-Type: application/json") fun batchDownload( diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/interactor/GetGlobalMeta.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/interactor/GetGlobalMeta.kt new file mode 100644 index 00000000..8b679d2c --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/interactor/GetGlobalMeta.kt @@ -0,0 +1,29 @@ +/* + * 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.global.interactor + +import ca.gosyer.jui.domain.global.service.GlobalRepository +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.singleOrNull +import me.tatarka.inject.annotations.Inject +import org.lighthousegames.logging.logging + +class GetGlobalMeta @Inject constructor(private val globalRepository: GlobalRepository) { + + suspend fun await(onError: suspend (Throwable) -> Unit = {}) = asFlow() + .catch { + onError(it) + log.warn(it) { "Failed to get global meta" } + } + .singleOrNull() + + fun asFlow() = globalRepository.getGlobalMeta() + + companion object { + private val log = logging() + } +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/interactor/UpdateGlobalMeta.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/interactor/UpdateGlobalMeta.kt new file mode 100644 index 00000000..7d5cac2b --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/interactor/UpdateGlobalMeta.kt @@ -0,0 +1,46 @@ +/* + * 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.global.interactor + +import ca.gosyer.jui.domain.global.model.GlobalMeta +import ca.gosyer.jui.domain.global.service.GlobalRepository +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flow +import me.tatarka.inject.annotations.Inject +import org.lighthousegames.logging.logging + +class UpdateGlobalMeta @Inject constructor(private val globalRepository: GlobalRepository) { + + suspend fun await( + globalMeta: GlobalMeta, + example: Int = globalMeta.example, + onError: suspend (Throwable) -> Unit = {} + ) = asFlow(globalMeta, example) + .catch { + onError(it) + log.warn(it) { "Failed to update global meta" } + } + .collect() + + fun asFlow( + globalMeta: GlobalMeta, + example: Int = globalMeta.example, + ) = flow { + if (example != globalMeta.example) { + globalRepository.updateGlobalMeta( + "example", + example.toString() + ).collect() + } + emit(Unit) + } + + companion object { + private val log = logging() + } +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/model/GlobalMeta.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/model/GlobalMeta.kt new file mode 100644 index 00000000..d54f165b --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/model/GlobalMeta.kt @@ -0,0 +1,16 @@ +/* + * 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.global.model + +import androidx.compose.runtime.Immutable +import kotlinx.serialization.Serializable + +@Serializable +@Immutable +data class GlobalMeta( + val example: Int = 0 +) \ No newline at end of file diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/service/GlobalRepository.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/service/GlobalRepository.kt new file mode 100644 index 00000000..5df24ef4 --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/service/GlobalRepository.kt @@ -0,0 +1,27 @@ +/* + * 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.global.service + +import ca.gosyer.jui.domain.global.model.GlobalMeta +import de.jensklingenberg.ktorfit.http.Field +import de.jensklingenberg.ktorfit.http.FormUrlEncoded +import de.jensklingenberg.ktorfit.http.GET +import de.jensklingenberg.ktorfit.http.PATCH +import io.ktor.client.statement.HttpResponse +import kotlinx.coroutines.flow.Flow + +interface GlobalRepository { + @GET("api/v1/meta") + fun getGlobalMeta(): Flow + + @FormUrlEncoded + @PATCH("api/v1/meta") + fun updateGlobalMeta( + @Field("key") key: String, + @Field("value") value: String + ): Flow +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetQuickSearchManga.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetQuickSearchManga.kt new file mode 100644 index 00000000..646adc5a --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetQuickSearchManga.kt @@ -0,0 +1,55 @@ +/* + * 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.interactor + +import ca.gosyer.jui.domain.source.model.Source +import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilterChange +import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilterData +import ca.gosyer.jui.domain.source.service.SourceRepository +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.singleOrNull +import me.tatarka.inject.annotations.Inject +import org.lighthousegames.logging.logging + +class GetQuickSearchManga @Inject constructor(private val sourceRepository: SourceRepository) { + + suspend fun await(source: Source, searchTerm: String?, page: Int, filters: List?, onError: suspend (Throwable) -> Unit = {}) = asFlow(source.id, searchTerm, page, filters) + .catch { + onError(it) + log.warn(it) { "Failed to get quick search results from ${source.displayName} on page $page with query '$searchTerm'" } + } + .singleOrNull() + + suspend fun await(sourceId: Long, searchTerm: String?, page: Int, filters: List?, onError: suspend (Throwable) -> Unit = {}) = asFlow(sourceId, searchTerm, page, filters) + .catch { + onError(it) + log.warn(it) { "Failed to get quick search results from $sourceId on page $page with query '$searchTerm'" } + } + .singleOrNull() + + fun asFlow(source: Source, searchTerm: String?, page: Int, filters: List?) = sourceRepository.getQuickSearchResults( + source.id, + page, + SourceFilterData( + searchTerm?.ifBlank { null }, + filters?.ifEmpty { null } + ) + ) + + fun asFlow(sourceId: Long, searchTerm: String?, page: Int, filters: List?) = sourceRepository.getQuickSearchResults( + sourceId, + page, + SourceFilterData( + searchTerm?.ifBlank { null }, + filters?.ifEmpty { null } + ) + ) + + companion object { + private val log = logging() + } +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilterData.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilterData.kt new file mode 100644 index 00000000..37cc39ca --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilterData.kt @@ -0,0 +1,15 @@ +/* + * 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? +) \ No newline at end of file diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/service/SourceRepository.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/service/SourceRepository.kt index 1fb82808..bd10bd04 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/service/SourceRepository.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/service/SourceRepository.kt @@ -10,6 +10,7 @@ import ca.gosyer.jui.domain.source.model.MangaPage import ca.gosyer.jui.domain.source.model.Source import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilter import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilterChange +import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilterData import ca.gosyer.jui.domain.source.model.sourcepreference.SourcePreference import ca.gosyer.jui.domain.source.model.sourcepreference.SourcePreferenceChange import de.jensklingenberg.ktorfit.http.Body @@ -62,6 +63,21 @@ interface SourceRepository { @Body sourceFilter: SourceFilterChange ): Flow + @POST("api/v1/source/{sourceId}/filters") + @Headers("Content-Type: application/json") + fun setFilters( + @Path("sourceId") sourceId: Long, + @Body sourceFilters: List + ): Flow + + @POST("api/v1/source/{sourceId}/quick-search") + @Headers("Content-Type: application/json") + fun getQuickSearchResults( + @Path("sourceId") sourceId: Long, + @Query("pageNum") pageNum: Int, + @Body filterData: SourceFilterData + ): Flow + @GET("api/v1/source/{sourceId}/preferences") fun getSourceSettings( @Path("sourceId") sourceId: Long