Integrate Ktorfit

This commit is contained in:
Syer10
2022-09-22 22:30:24 -04:00
parent 0057de2e25
commit 7a7e92a7b4
61 changed files with 798 additions and 1455 deletions

View File

@@ -32,6 +32,7 @@ kotlin {
languageSettings {
optIn("kotlin.RequiresOptIn")
optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
optIn("de.jensklingenberg.ktorfit.internal.InternalKtorfitApi")
}
}
val commonMain by getting {
@@ -48,6 +49,7 @@ kotlin {
api(libs.ktor.auth)
api(libs.ktor.logging)
api(libs.ktor.websockets)
api(libs.ktorfit.lib)
api(libs.okio)
api(libs.dateTime)
api(projects.core)
@@ -113,6 +115,9 @@ kotlin {
dependencies {
add("kspDesktop", libs.kotlinInject.compiler)
add("kspAndroid", libs.kotlinInject.compiler)
add("kspCommonMainMetadata", libs.ktorfit.ksp)
add("kspDesktop", libs.ktorfit.ksp)
add("kspAndroid", libs.ktorfit.ksp)
}
buildkonfig {

View File

@@ -4,14 +4,9 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.server.model.requests
package ca.gosyer.jui.domain
annotation class Get
import de.jensklingenberg.ktorfit.Ktorfit
import de.jensklingenberg.ktorfit.create
annotation class Post
annotation class Delete
annotation class Patch
annotation class WS
inline fun <reified T> Ktorfit.createIt(): T = create()

View File

@@ -21,7 +21,7 @@ class ImportBackupFile @Inject constructor(private val backupRepository: BackupR
.singleOrNull()
fun asFlow(file: Path, block: HttpRequestBuilder.() -> Unit = {}) =
backupRepository.importBackupFile(file, block)
backupRepository.importBackupFile(BackupRepository.buildBackupFormData(file), block)
companion object {
private val log = logging()

View File

@@ -21,7 +21,7 @@ class ValidateBackupFile @Inject constructor(private val backupRepository: Backu
.singleOrNull()
fun asFlow(file: Path, block: HttpRequestBuilder.() -> Unit = {}) =
backupRepository.validateBackupFile(file, block)
backupRepository.validateBackupFile(BackupRepository.buildBackupFormData(file), block)
companion object {
private val log = logging()

View File

@@ -6,14 +6,55 @@
package ca.gosyer.jui.domain.backup.service
import ca.gosyer.jui.core.io.SYSTEM
import ca.gosyer.jui.domain.backup.model.BackupValidationResult
import de.jensklingenberg.ktorfit.http.Multipart
import de.jensklingenberg.ktorfit.http.POST
import de.jensklingenberg.ktorfit.http.Part
import de.jensklingenberg.ktorfit.http.ReqBuilder
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.forms.formData
import io.ktor.client.statement.HttpResponse
import io.ktor.http.ContentType
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
import io.ktor.http.content.PartData
import kotlinx.coroutines.flow.Flow
import okio.FileSystem
import okio.Path
import okio.buffer
interface BackupRepository {
fun importBackupFile(file: Path, block: HttpRequestBuilder.() -> Unit = {}): Flow<HttpResponse>
fun validateBackupFile(file: Path, block: HttpRequestBuilder.() -> Unit = {}): Flow<BackupValidationResult>
fun exportBackupFile(block: HttpRequestBuilder.() -> Unit = {}): Flow<HttpResponse>
@Multipart
@POST("api/v1/backup/import/file")
fun importBackupFile(
@Part("") formData: List<PartData>,
@ReqBuilder block: HttpRequestBuilder.() -> Unit = {}
): Flow<HttpResponse>
@Multipart
@POST("api/v1/backup/validate/file")
fun validateBackupFile(
@Part("") formData: List<PartData>,
@ReqBuilder block: HttpRequestBuilder.() -> Unit = {}
): Flow<BackupValidationResult>
@POST("api/v1/backup/export/file")
fun exportBackupFile(
@ReqBuilder block: HttpRequestBuilder.() -> Unit = {}
): Flow<HttpResponse>
companion object {
fun buildBackupFormData(file: Path) = formData {
append(
"backup.proto.gz",
FileSystem.SYSTEM.source(file).buffer().readByteArray(),
Headers.build {
append(HttpHeaders.ContentType, ContentType.MultiPart.FormData.toString())
append(HttpHeaders.ContentDisposition, "filename=backup.proto.gz")
}
)
}
}
}

View File

@@ -8,6 +8,7 @@ package ca.gosyer.jui.domain.category.interactor
import ca.gosyer.jui.domain.category.service.CategoryRepository
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.singleOrNull
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
@@ -18,7 +19,12 @@ class GetCategories @Inject constructor(private val categoryRepository: Category
.catch { log.warn(it) { "Failed to get categories" } }
.singleOrNull()
fun asFlow(dropDefault: Boolean = false) = categoryRepository.getCategories(dropDefault)
fun asFlow(dropDefault: Boolean = false) = categoryRepository.getCategories()
.map { categories ->
if (dropDefault) {
categories.filterNot { it.name.equals("default", true) }
} else categories
}
companion object {
private val log = logging()

View File

@@ -15,28 +15,24 @@ import org.lighthousegames.logging.logging
class ModifyCategory @Inject constructor(private val categoryRepository: CategoryRepository) {
suspend fun await(categoryId: Long, name: String? = null, isLanding: Boolean? = null) = asFlow(
suspend fun await(categoryId: Long, name: String) = asFlow(
categoryId = categoryId,
name = name,
isLanding = isLanding
).catch { log.warn(it) { "Failed to modify category $categoryId with options: name=$name,isLanding=$isLanding" } }.collect()
name = name
).catch { log.warn(it) { "Failed to modify category $categoryId with options: name=$name" } }.collect()
suspend fun await(category: Category, name: String? = null, isLanding: Boolean? = null) = asFlow(
suspend fun await(category: Category, name: String? = null) = asFlow(
category = category,
name = name,
isLanding = isLanding
).catch { log.warn(it) { "Failed to modify category ${category.name} with options: name=$name,isLanding=$isLanding" } }.collect()
name = name
).catch { log.warn(it) { "Failed to modify category ${category.name} with options: name=$name" } }.collect()
fun asFlow(categoryId: Long, name: String? = null, isLanding: Boolean? = null) = categoryRepository.modifyCategory(
fun asFlow(categoryId: Long, name: String) = categoryRepository.modifyCategory(
categoryId = categoryId,
name = name,
isLanding = isLanding
name = name
)
fun asFlow(category: Category, name: String? = null, isLanding: Boolean? = null) = categoryRepository.modifyCategory(
fun asFlow(category: Category, name: String? = null) = categoryRepository.modifyCategory(
categoryId = category.id,
name = name,
isLanding = isLanding
name = name ?: category.name
)
companion object {

View File

@@ -8,17 +8,64 @@ package ca.gosyer.jui.domain.category.service
import ca.gosyer.jui.domain.category.model.Category
import ca.gosyer.jui.domain.manga.model.Manga
import de.jensklingenberg.ktorfit.http.DELETE
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 de.jensklingenberg.ktorfit.http.POST
import de.jensklingenberg.ktorfit.http.Path
import io.ktor.client.statement.HttpResponse
import kotlinx.coroutines.flow.Flow
interface CategoryRepository {
fun getMangaCategories(mangaId: Long): Flow<List<Category>>
fun addMangaToCategory(mangaId: Long, categoryId: Long): Flow<HttpResponse>
fun removeMangaFromCategory(mangaId: Long, categoryId: Long): Flow<HttpResponse>
fun getCategories(dropDefault: Boolean = false): Flow<List<Category>>
fun createCategory(name: String): Flow<HttpResponse>
fun modifyCategory(categoryId: Long, name: String? = null, isLanding: Boolean? = null): Flow<HttpResponse>
fun reorderCategory(to: Int, from: Int): Flow<HttpResponse>
fun deleteCategory(categoryId: Long): Flow<HttpResponse>
fun getMangaFromCategory(categoryId: Long): Flow<List<Manga>>
@GET("api/v1/manga/{mangaId}/category/")
fun getMangaCategories(
@Path("mangaId") mangaId: Long
): Flow<List<Category>>
@GET("api/v1/manga/{mangaId}/category/{categoryId}")
fun addMangaToCategory(
@Path("mangaId") mangaId: Long,
@Path("categoryId") categoryId: Long
): Flow<HttpResponse>
@DELETE("api/v1/manga/{mangaId}/category/{categoryId}")
fun removeMangaFromCategory(
@Path("mangaId") mangaId: Long,
@Path("categoryId") categoryId: Long
): Flow<HttpResponse>
@GET("api/v1/category/")
fun getCategories(): Flow<List<Category>>
@FormUrlEncoded
@POST("api/v1/category/")
fun createCategory(
@Field("name") name: String
): Flow<HttpResponse>
@FormUrlEncoded
@PATCH("api/v1/category/{categoryId}")
fun modifyCategory(
@Path("categoryId") categoryId: Long,
@Field("name") name: String
): Flow<HttpResponse>
@FormUrlEncoded
@PATCH("api/v1/category/reorder")
fun reorderCategory(
@Field("to") to: Int,
@Field("from") from: Int
): Flow<HttpResponse>
@DELETE("api/v1/category/{categoryId}")
fun deleteCategory(
@Path("categoryId") categoryId: Long
): Flow<HttpResponse>
@GET("api/v1/category/{categoryId}")
fun getMangaFromCategory(
@Path("categoryId") categoryId: Long
): Flow<List<Manga>>
}

View File

@@ -0,0 +1,74 @@
/*
* 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.interactor
import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.service.ChapterRepository
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 UpdateChapterBookmarked @Inject constructor(private val chapterRepository: ChapterRepository) {
suspend fun await(
mangaId: Long,
index: Int,
bookmarked: Boolean,
) = asFlow(mangaId, index, bookmarked)
.catch { log.warn(it) { "Failed to update chapter bookmark for chapter $index of $mangaId" } }
.collect()
suspend fun await(
manga: Manga,
index: Int,
bookmarked: Boolean,
) = asFlow(manga, index, bookmarked)
.catch { log.warn(it) { "Failed to update chapter bookmark for chapter $index of ${manga.title}(${manga.id})" } }
.collect()
suspend fun await(
chapter: Chapter,
bookmarked: Boolean,
) = asFlow(chapter, bookmarked)
.catch { log.warn(it) { "Failed to update chapter bookmark for chapter ${chapter.index} of ${chapter.mangaId}" } }
.collect()
fun asFlow(
mangaId: Long,
index: Int,
bookmarked: Boolean,
) = chapterRepository.updateChapterBookmarked(
mangaId = mangaId,
chapterIndex = index,
bookmarked = bookmarked,
)
fun asFlow(
manga: Manga,
index: Int,
bookmarked: Boolean,
) = chapterRepository.updateChapterBookmarked(
mangaId = manga.id,
chapterIndex = index,
bookmarked = bookmarked,
)
fun asFlow(
chapter: Chapter,
bookmarked: Boolean,
) = chapterRepository.updateChapterBookmarked(
mangaId = chapter.mangaId,
chapterIndex = chapter.index,
bookmarked = bookmarked,
)
companion object {
private val log = logging()
}
}

View File

@@ -1,101 +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.interactor
import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.service.ChapterRepository
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 UpdateChapterFlags @Inject constructor(private val chapterRepository: ChapterRepository) {
suspend fun await(
mangaId: Long,
index: Int,
read: Boolean? = null,
bookmarked: Boolean? = null,
lastPageRead: Int? = null,
markPreviousRead: Boolean? = null
) = asFlow(mangaId, index, read, bookmarked, lastPageRead, markPreviousRead)
.catch { log.warn(it) { "Failed to update chapter flags for chapter $index of $mangaId" } }
.collect()
suspend fun await(
manga: Manga,
index: Int,
read: Boolean? = null,
bookmarked: Boolean? = null,
lastPageRead: Int? = null,
markPreviousRead: Boolean? = null
) = asFlow(manga, index, read, bookmarked, lastPageRead, markPreviousRead)
.catch { log.warn(it) { "Failed to update chapter flags for chapter $index of ${manga.title}(${manga.id})" } }
.collect()
suspend fun await(
chapter: Chapter,
read: Boolean? = null,
bookmarked: Boolean? = null,
lastPageRead: Int? = null,
markPreviousRead: Boolean? = null
) = asFlow(chapter, read, bookmarked, lastPageRead, markPreviousRead)
.catch { log.warn(it) { "Failed to update chapter flags for chapter ${chapter.index} of ${chapter.mangaId}" } }
.collect()
fun asFlow(
mangaId: Long,
index: Int,
read: Boolean? = null,
bookmarked: Boolean? = null,
lastPageRead: Int? = null,
markPreviousRead: Boolean? = null
) = chapterRepository.updateChapter(
mangaId = mangaId,
chapterIndex = index,
read = read,
bookmarked = bookmarked,
lastPageRead = lastPageRead,
markPreviousRead = markPreviousRead
)
fun asFlow(
manga: Manga,
index: Int,
read: Boolean? = null,
bookmarked: Boolean? = null,
lastPageRead: Int? = null,
markPreviousRead: Boolean? = null
) = chapterRepository.updateChapter(
mangaId = manga.id,
chapterIndex = index,
read = read,
bookmarked = bookmarked,
lastPageRead = lastPageRead,
markPreviousRead = markPreviousRead
)
fun asFlow(
chapter: Chapter,
read: Boolean? = null,
bookmarked: Boolean? = null,
lastPageRead: Int? = null,
markPreviousRead: Boolean? = null
) = chapterRepository.updateChapter(
mangaId = chapter.mangaId,
chapterIndex = chapter.index,
read = read,
bookmarked = bookmarked,
lastPageRead = lastPageRead,
markPreviousRead = markPreviousRead
)
companion object {
private val log = logging()
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.interactor
import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.service.ChapterRepository
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 UpdateChapterLastPageRead @Inject constructor(private val chapterRepository: ChapterRepository) {
suspend fun await(
mangaId: Long,
index: Int,
lastPageRead: Int,
) = asFlow(mangaId, index, lastPageRead)
.catch { log.warn(it) { "Failed to update chapter last page read for chapter $index of $mangaId" } }
.collect()
suspend fun await(
manga: Manga,
index: Int,
lastPageRead: Int,
) = asFlow(manga, index, lastPageRead)
.catch { log.warn(it) { "Failed to update chapter last page read for chapter $index of ${manga.title}(${manga.id})" } }
.collect()
suspend fun await(
chapter: Chapter,
lastPageRead: Int,
) = asFlow(chapter, lastPageRead)
.catch { log.warn(it) { "Failed to update chapter last page read for chapter ${chapter.index} of ${chapter.mangaId}" } }
.collect()
fun asFlow(
mangaId: Long,
index: Int,
lastPageRead: Int,
) = chapterRepository.updateChapterLastPageRead(
mangaId = mangaId,
chapterIndex = index,
lastPageRead = lastPageRead,
)
fun asFlow(
manga: Manga,
index: Int,
lastPageRead: Int,
) = chapterRepository.updateChapterLastPageRead(
mangaId = manga.id,
chapterIndex = index,
lastPageRead = lastPageRead,
)
fun asFlow(
chapter: Chapter,
lastPageRead: Int,
) = chapterRepository.updateChapterLastPageRead(
mangaId = chapter.mangaId,
chapterIndex = chapter.index,
lastPageRead = lastPageRead,
)
companion object {
private val log = logging()
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.interactor
import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.service.ChapterRepository
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 UpdateChapterMarkPreviousRead @Inject constructor(private val chapterRepository: ChapterRepository) {
suspend fun await(
mangaId: Long,
index: Int
) = asFlow(mangaId, index)
.catch { log.warn(it) { "Failed to update chapter read status for chapter $index of $mangaId" } }
.collect()
suspend fun await(
manga: Manga,
index: Int
) = asFlow(manga, index)
.catch { log.warn(it) { "Failed to update chapter read status for chapter $index of ${manga.title}(${manga.id})" } }
.collect()
suspend fun await(
chapter: Chapter
) = asFlow(chapter)
.catch { log.warn(it) { "Failed to update chapter read status for chapter ${chapter.index} of ${chapter.mangaId}" } }
.collect()
fun asFlow(
mangaId: Long,
index: Int,
) = chapterRepository.updateChapterMarkPrevRead(
mangaId = mangaId,
chapterIndex = index
)
fun asFlow(
manga: Manga,
index: Int,
) = chapterRepository.updateChapterMarkPrevRead(
mangaId = manga.id,
chapterIndex = index,
)
fun asFlow(
chapter: Chapter,
) = chapterRepository.updateChapterMarkPrevRead(
mangaId = chapter.mangaId,
chapterIndex = chapter.index,
)
companion object {
private val log = logging()
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.interactor
import ca.gosyer.jui.domain.chapter.model.Chapter
import ca.gosyer.jui.domain.chapter.service.ChapterRepository
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 UpdateChapterRead @Inject constructor(private val chapterRepository: ChapterRepository) {
suspend fun await(
mangaId: Long,
index: Int,
read: Boolean,
) = asFlow(mangaId, index, read)
.catch { log.warn(it) { "Failed to update chapter read status for chapter $index of $mangaId" } }
.collect()
suspend fun await(
manga: Manga,
index: Int,
read: Boolean,
) = asFlow(manga, index, read)
.catch { log.warn(it) { "Failed to update chapter read status for chapter $index of ${manga.title}(${manga.id})" } }
.collect()
suspend fun await(
chapter: Chapter,
read: Boolean,
) = asFlow(chapter, read)
.catch { log.warn(it) { "Failed to update chapter read status for chapter ${chapter.index} of ${chapter.mangaId}" } }
.collect()
fun asFlow(
mangaId: Long,
index: Int,
read: Boolean,
) = chapterRepository.updateChapterRead(
mangaId = mangaId,
chapterIndex = index,
read = read,
)
fun asFlow(
manga: Manga,
index: Int,
read: Boolean,
) = chapterRepository.updateChapterRead(
mangaId = manga.id,
chapterIndex = index,
read = read,
)
fun asFlow(
chapter: Chapter,
read: Boolean,
) = chapterRepository.updateChapterRead(
mangaId = chapter.mangaId,
chapterIndex = chapter.index,
read = read,
)
companion object {
private val log = logging()
}
}

View File

@@ -7,31 +7,109 @@
package ca.gosyer.jui.domain.chapter.service
import ca.gosyer.jui.domain.chapter.model.Chapter
import de.jensklingenberg.ktorfit.http.DELETE
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 de.jensklingenberg.ktorfit.http.Path
import de.jensklingenberg.ktorfit.http.Query
import de.jensklingenberg.ktorfit.http.ReqBuilder
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.statement.HttpResponse
import kotlinx.coroutines.flow.Flow
interface ChapterRepository {
fun getChapters(mangaId: Long, refresh: Boolean = false): Flow<List<Chapter>>
fun getChapter(mangaId: Long, chapterIndex: Int): Flow<Chapter>
@GET("api/v1/manga/{mangaId}/chapters")
fun getChapters(
@Path("mangaId") mangaId: Long,
@Query("onlineFetch") refresh: Boolean = false
): Flow<List<Chapter>>
@GET("api/v1/manga/{mangaId}/chapter/{chapterIndex}")
fun getChapter(
@Path("mangaId") mangaId: Long,
@Path("chapterIndex") chapterIndex: Int
): Flow<Chapter>
/* TODO add once ktorfit supports nullable paremters
@FormUrlEncoded
@PATCH("/api/v1/manga/{mangaId}/chapter/{chapterIndex}")
fun updateChapter(
mangaId: Long,
chapterIndex: Int,
read: Boolean? = null,
bookmarked: Boolean? = null,
lastPageRead: Int? = null,
markPreviousRead: Boolean? = null
@Path("mangaId") mangaId: Long,
@Path("chapterIndex") chapterIndex: Int,
@Field("read") read: Boolean? = null,
@Field("bookmarked") bookmarked: Boolean? = null,
@Field("lastPageRead") lastPageRead: Int? = null,
@Field("markPrevRead") markPreviousRead: Boolean? = null
): Flow<HttpResponse>*/
//todo remove following updateChapter functions once ktorfit supports nullable parameters
@FormUrlEncoded
@PATCH("api/v1/manga/{mangaId}/chapter/{chapterIndex}")
fun updateChapterRead(
@Path("mangaId") mangaId: Long,
@Path("chapterIndex") chapterIndex: Int,
@Field("read") read: Boolean,
): Flow<HttpResponse>
@FormUrlEncoded
@PATCH("api/v1/manga/{mangaId}/chapter/{chapterIndex}")
fun updateChapterBookmarked(
@Path("mangaId") mangaId: Long,
@Path("chapterIndex") chapterIndex: Int,
@Field("bookmarked") bookmarked: Boolean,
): Flow<HttpResponse>
@FormUrlEncoded
@PATCH("api/v1/manga/{mangaId}/chapter/{chapterIndex}")
fun updateChapterLastPageRead(
@Path("mangaId") mangaId: Long,
@Path("chapterIndex") chapterIndex: Int,
@Field("lastPageRead") lastPageRead: Int,
): Flow<HttpResponse>
@FormUrlEncoded
@PATCH("api/v1/manga/{mangaId}/chapter/{chapterIndex}")
fun updateChapterMarkPrevRead(
@Path("mangaId") mangaId: Long,
@Path("chapterIndex") chapterIndex: Int,
@Field("markPrevRead") markPreviousRead: Boolean = true
): Flow<HttpResponse>
@GET("api/v1/manga/{mangaId}/chapter/{chapterIndex}/page/{pageNum}")
fun getPage(
mangaId: Long,
chapterIndex: Int,
pageNum: Int,
block: HttpRequestBuilder.() -> Unit
@Path("mangaId") mangaId: Long,
@Path("chapterIndex") chapterIndex: Int,
@Path("pageNum") pageNum: Int,
@ReqBuilder block: HttpRequestBuilder.() -> Unit
): Flow<HttpResponse>
fun deleteChapterDownload(mangaId: Long, chapterIndex: Int): Flow<HttpResponse>
fun queueChapterDownload(mangaId: Long, chapterIndex: Int): Flow<HttpResponse>
fun stopChapterDownload(mangaId: Long, chapterIndex: Int): Flow<HttpResponse>
fun updateChapterMeta(mangaId: Long, chapterIndex: Int, key: String, value: String): Flow<HttpResponse>
@DELETE("api/v1/manga/{mangaId}/chapter/{chapterIndex}")
fun deleteChapterDownload(
@Path("mangaId") mangaId: Long,
@Path("chapterIndex") chapterIndex: Int
): Flow<HttpResponse>
@GET("api/v1/download/{mangaId}/chapter/{chapterIndex}")
fun queueChapterDownload(
@Path("mangaId") mangaId: Long,
@Path("chapterIndex") chapterIndex: Int
): Flow<HttpResponse>
@DELETE("api/v1/download/{mangaId}/chapter/{chapterIndex}")
fun stopChapterDownload(
@Path("mangaId") mangaId: Long,
@Path("chapterIndex") chapterIndex: Int
): Flow<HttpResponse>
@FormUrlEncoded
@PATCH("api/v1/manga/{mangaId}/chapter/{chapterIndex}/meta")
fun updateChapterMeta(
@Path("mangaId") mangaId: Long,
@Path("chapterIndex") chapterIndex: Int,
@Field("key") key: String,
@Field("value") value: String
): Flow<HttpResponse>
}

View File

@@ -6,11 +6,17 @@
package ca.gosyer.jui.domain.download.service
import de.jensklingenberg.ktorfit.http.GET
import io.ktor.client.statement.HttpResponse
import kotlinx.coroutines.flow.Flow
interface DownloadRepository {
@GET("api/v1/downloads/start")
fun startDownloading(): Flow<HttpResponse>
@GET("api/v1/downloads/stop")
fun stopDownloading(): Flow<HttpResponse>
@GET("api/v1/downloads/clear")
fun clearDownloadQueue(): Flow<HttpResponse>
}

View File

@@ -11,7 +11,6 @@ import ca.gosyer.jui.domain.download.model.DownloadChapter
import ca.gosyer.jui.domain.download.model.DownloadStatus
import ca.gosyer.jui.domain.download.model.DownloaderStatus
import ca.gosyer.jui.domain.server.Http
import ca.gosyer.jui.domain.server.model.requests.downloadsQuery
import ca.gosyer.jui.domain.server.service.ServerPreferences
import io.ktor.websocket.Frame
import io.ktor.websocket.readText
@@ -28,7 +27,7 @@ class DownloadService @Inject constructor(
get() = status
override val query: String
get() = downloadsQuery()
get() = "/api/v1/downloads"
override suspend fun onReceived(frame: Frame.Text) {
val status = json.decodeFromString<DownloadStatus>(frame.readText())

View File

@@ -19,7 +19,7 @@ class InstallExtension @Inject constructor(private val extensionRepository: Exte
.catch { log.warn(it) { "Failed to install extension ${extension.apkName}" } }
.collect()
fun asFlow(extension: Extension) = extensionRepository.installExtension(extension)
fun asFlow(extension: Extension) = extensionRepository.installExtension(extension.pkgName)
companion object {
private val log = logging()

View File

@@ -19,7 +19,7 @@ class UninstallExtension @Inject constructor(private val extensionRepository: Ex
.catch { log.warn(it) { "Failed to uninstall extension ${extension.apkName}" } }
.collect()
fun asFlow(extension: Extension) = extensionRepository.uninstallExtension(extension)
fun asFlow(extension: Extension) = extensionRepository.uninstallExtension(extension.pkgName)
companion object {
private val log = logging()

View File

@@ -19,7 +19,7 @@ class UpdateExtension @Inject constructor(private val extensionRepository: Exten
.catch { log.warn(it) { "Failed to update extension ${extension.apkName}" } }
.collect()
fun asFlow(extension: Extension) = extensionRepository.updateExtension(extension)
fun asFlow(extension: Extension) = extensionRepository.updateExtension(extension.pkgName)
companion object {
private val log = logging()

View File

@@ -7,15 +7,36 @@
package ca.gosyer.jui.domain.extension.service
import ca.gosyer.jui.domain.extension.model.Extension
import de.jensklingenberg.ktorfit.http.GET
import de.jensklingenberg.ktorfit.http.Path
import de.jensklingenberg.ktorfit.http.ReqBuilder
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.statement.HttpResponse
import io.ktor.utils.io.ByteReadChannel
import kotlinx.coroutines.flow.Flow
interface ExtensionRepository {
@GET("api/v1/extension/list")
fun getExtensionList(): Flow<List<Extension>>
fun installExtension(extension: Extension): Flow<HttpResponse>
fun updateExtension(extension: Extension): Flow<HttpResponse>
fun uninstallExtension(extension: Extension): Flow<HttpResponse>
fun getApkIcon(extension: Extension, block: HttpRequestBuilder.() -> Unit): Flow<ByteReadChannel>
@GET("api/v1/extension/install/{pkgName}")
fun installExtension(
@Path("pkgName") pkgName: String
): Flow<HttpResponse>
@GET("api/v1/extension/update/{pkgName}")
fun updateExtension(
@Path("pkgName") pkgName: String
): Flow<HttpResponse>
@GET("api/v1/extension/uninstall/{pkgName}")
fun uninstallExtension(
@Path("pkgName") pkgName: String
): Flow<HttpResponse>
@GET("api/v1/extension/icon/{apkName}")
fun getApkIcon(
@Path("apkName") apkName: String,
@ReqBuilder block: HttpRequestBuilder.() -> Unit
): Flow<ByteReadChannel>
}

View File

@@ -6,10 +6,21 @@
package ca.gosyer.jui.domain.library.service
import de.jensklingenberg.ktorfit.http.DELETE
import de.jensklingenberg.ktorfit.http.GET
import de.jensklingenberg.ktorfit.http.Path
import io.ktor.client.statement.HttpResponse
import kotlinx.coroutines.flow.Flow
interface LibraryRepository {
fun addMangaToLibrary(mangaId: Long): Flow<HttpResponse>
fun removeMangaFromLibrary(mangaId: Long): Flow<HttpResponse>
@GET("api/v1/manga/{mangaId}/library")
fun addMangaToLibrary(
@Path("mangaId") mangaId: Long
): Flow<HttpResponse>
@DELETE("api/v1/manga/{mangaId}/library")
fun removeMangaFromLibrary(
@Path("mangaId") mangaId: Long
): Flow<HttpResponse>
}

View File

@@ -9,7 +9,6 @@ package ca.gosyer.jui.domain.library.service
import ca.gosyer.jui.domain.base.WebsocketService
import ca.gosyer.jui.domain.library.model.UpdateStatus
import ca.gosyer.jui.domain.server.Http
import ca.gosyer.jui.domain.server.model.requests.updatesQuery
import ca.gosyer.jui.domain.server.service.ServerPreferences
import io.ktor.websocket.Frame
import io.ktor.websocket.readText
@@ -26,7 +25,7 @@ class LibraryUpdateService @Inject constructor(
override val _status: MutableStateFlow<Status> = MutableStateFlow(Status.STARTING)
override val query: String
get() = updatesQuery()
get() = "/api/v1/update"
override suspend fun onReceived(frame: Frame.Text) {
val status = json.decodeFromString<UpdateStatus>(frame.readText())

View File

@@ -7,13 +7,36 @@
package ca.gosyer.jui.domain.manga.service
import ca.gosyer.jui.domain.manga.model.Manga
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 de.jensklingenberg.ktorfit.http.Path
import de.jensklingenberg.ktorfit.http.Query
import de.jensklingenberg.ktorfit.http.ReqBuilder
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.statement.HttpResponse
import io.ktor.utils.io.ByteReadChannel
import kotlinx.coroutines.flow.Flow
interface MangaRepository {
fun getManga(mangaId: Long, refresh: Boolean = false): Flow<Manga>
fun getMangaThumbnail(mangaId: Long, block: HttpRequestBuilder.() -> Unit): Flow<ByteReadChannel>
fun updateMangaMeta(mangaId: Long, key: String, value: String): Flow<HttpResponse>
@GET("api/v1/manga/{mangaId}/")
fun getManga(
@Path("mangaId") mangaId: Long,
@Query("onlineFetch") refresh: Boolean = false
): Flow<Manga>
@GET("api/v1/manga/{mangaId}/thumbnail")
fun getMangaThumbnail(
@Path("mangaId") mangaId: Long,
@ReqBuilder block: HttpRequestBuilder.() -> Unit
): Flow<ByteReadChannel>
@PATCH("api/v1/manga/{mangaId}/meta")
@FormUrlEncoded
fun updateMangaMeta(
@Path("mangaId") mangaId: Long,
@Field("key") key: String,
@Field("value") value: String
): Flow<HttpResponse>
}

View File

@@ -1,27 +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.server.model.requests
@Post
fun backupImportRequest() =
"/api/v1/backup/import"
@Post
fun backupFileImportRequest() =
"/api/v1/backup/import/file"
@Post
fun backupExportRequest() =
"/api/v1/backup/export"
@Post
fun backupFileExportRequest() =
"/api/v1/backup/export/file"
@Post
fun validateBackupFileRequest() =
"/api/v1/backup/validate/file"

View File

@@ -1,46 +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.server.model.requests
@Get
fun getMangaCategoriesQuery(mangaId: Long) =
"/api/v1/manga/$mangaId/category/"
@Get
fun addMangaToCategoryQuery(mangaId: Long, categoryId: Long) =
"/api/v1/manga/$mangaId/category/$categoryId"
@Delete
fun removeMangaFromCategoryRequest(mangaId: Long, categoryId: Long) =
"/api/v1/manga/$mangaId/category/$categoryId"
@Get
fun getCategoriesQuery() =
"/api/v1/category/"
/**
* Post a formbody with the param {name} for creation of a category
*/
@Post
fun createCategoryRequest() =
"/api/v1/category/"
@Patch
fun categoryModifyRequest(categoryId: Long) =
"/api/v1/category/$categoryId"
@Patch
fun categoryReorderRequest() =
"/api/v1/category/reorder"
@Delete
fun categoryDeleteRequest(categoryId: Long) =
"/api/v1/category/$categoryId"
@Get
fun getMangaInCategoryQuery(categoryId: Long) =
"/api/v1/category/$categoryId"

View File

@@ -1,39 +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.server.model.requests
@Get
fun getMangaChaptersQuery(mangaId: Long) =
"/api/v1/manga/$mangaId/chapters"
@Get
fun getChapterQuery(mangaId: Long, chapterIndex: Int) =
"/api/v1/manga/$mangaId/chapter/$chapterIndex"
@Patch
fun updateChapterRequest(mangaId: Long, chapterIndex: Int) =
"/api/v1/manga/$mangaId/chapter/$chapterIndex"
@Get
fun getPageQuery(mangaId: Long, chapterIndex: Int, index: Int) =
"/api/v1/manga/$mangaId/chapter/$chapterIndex/page/$index"
@Delete
fun deleteDownloadedChapterRequest(mangaId: Long, chapterIndex: Int) =
"/api/v1/manga/$mangaId/chapter/$chapterIndex"
@Get
fun queueDownloadChapterRequest(mangaId: Long, chapterIndex: Int) =
"/api/v1/download/$mangaId/chapter/$chapterIndex"
@Delete
fun stopDownloadingChapterRequest(mangaId: Long, chapterIndex: Int) =
"/api/v1/download/$mangaId/chapter/$chapterIndex"
@Patch
fun updateChapterMetaRequest(mangaId: Long, chapterIndex: Int) =
"/api/v1/manga/$mangaId/chapter/$chapterIndex/meta"

View File

@@ -1,23 +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.server.model.requests
@WS
fun downloadsQuery() =
"/api/v1/downloads"
@Get
fun downloadsStartRequest() =
"/api/v1/downloads/start"
@Get
fun downloadsStopRequest() =
"/api/v1/downloads/stop"
@Get
fun downloadsClearRequest() =
"/api/v1/downloads/clear"

View File

@@ -1,27 +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.server.model.requests
@Get
fun extensionListQuery() =
"/api/v1/extension/list"
@Get
fun apkInstallQuery(pkgName: String) =
"/api/v1/extension/install/$pkgName"
@Get
fun apkUpdateQuery(pkgName: String) =
"/api/v1/extension/update/$pkgName"
@Get
fun apkUninstallQuery(pkgName: String) =
"/api/v1/extension/uninstall/$pkgName"
@Get
fun apkIconQuery(apkName: String) =
"/api/v1/extension/icon/$apkName"

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.server.model.requests
@Get
fun addMangaToLibraryQuery(mangaId: Long) =
"/api/v1/manga/$mangaId/library"
@Delete
fun removeMangaFromLibraryRequest(mangaId: Long) =
"/api/v1/manga/$mangaId/library"

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.server.model.requests
@Get
fun mangaQuery(mangaId: Long) =
"/api/v1/manga/$mangaId/"
@Get
fun mangaThumbnailQuery(mangaId: Long) =
"/api/v1/manga/$mangaId/thumbnail"
@Post
fun updateMangaMetaRequest(mangaId: Long) =
"/api/v1/manga/$mangaId/meta"

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.server.model.requests
@Get
fun aboutQuery() =
"/api/v1/settings/about"
@Get
fun checkUpdateQuery() =
"/api/v1/settings/check-update"

View File

@@ -1,47 +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.server.model.requests
@Get
fun sourceListQuery() =
"/api/v1/source/list"
@Get
fun sourceInfoQuery(sourceId: Long) =
"/api/v1/source/$sourceId"
@Get
fun sourcePopularQuery(sourceId: Long, pageNum: Int) =
"/api/v1/source/$sourceId/popular/$pageNum"
@Get
fun sourceLatestQuery(sourceId: Long, pageNum: Int) =
"/api/v1/source/$sourceId/latest/$pageNum"
@Get
fun globalSearchQuery() =
"/api/v1/source/all/search"
@Get
fun sourceSearchQuery(sourceId: Long) =
"/api/v1/source/$sourceId/search"
@Get
fun getFilterListQuery(sourceId: Long) =
"/api/v1/source/$sourceId/filters"
@Post
fun setFilterRequest(sourceId: Long) =
"/api/v1/source/$sourceId/filters"
@Get
fun getSourceSettingsQuery(sourceId: Long) =
"/api/v1/source/$sourceId/preferences"
@Post
fun updateSourceSettingQuery(sourceId: Long) =
"/api/v1/source/$sourceId/preferences"

View File

@@ -1,23 +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.server.model.requests
@Get
fun recentUpdatesQuery(pageNum: Int) =
"/api/v1/update/recentChapters/$pageNum"
@Post
fun fetchUpdatesRequest() =
"/api/v1/update/fetch"
@Get
fun updatesSummaryQuery() =
"/api/v1/update/summary"
@WS
fun updatesQuery() =
"/api/v1/update"

View File

@@ -7,10 +7,15 @@
package ca.gosyer.jui.domain.settings.service
import ca.gosyer.jui.domain.settings.model.About
import de.jensklingenberg.ktorfit.http.GET
import de.jensklingenberg.ktorfit.http.POST
import io.ktor.client.statement.HttpResponse
import kotlinx.coroutines.flow.Flow
interface SettingsRepository {
@GET("api/v1/settings/about")
fun aboutServer(): Flow<About>
@POST("api/v1/settings/check-update")
fun checkUpdate(): Flow<HttpResponse>
}

View File

@@ -12,20 +12,62 @@ 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.sourcepreference.SourcePreference
import ca.gosyer.jui.domain.source.model.sourcepreference.SourcePreferenceChange
import de.jensklingenberg.ktorfit.http.Body
import de.jensklingenberg.ktorfit.http.GET
import de.jensklingenberg.ktorfit.http.POST
import de.jensklingenberg.ktorfit.http.Path
import de.jensklingenberg.ktorfit.http.Query
import io.ktor.client.statement.HttpResponse
import kotlinx.coroutines.flow.Flow
interface SourceRepository {
@GET("api/v1/source/list")
fun getSourceList(): Flow<List<Source>>
fun getSourceInfo(sourceId: Long): Flow<Source>
fun getPopularManga(sourceId: Long, pageNum: Int): Flow<MangaPage>
fun getLatestManga(sourceId: Long, pageNum: Int): Flow<MangaPage>
fun getSearchResults(sourceId: Long, searchTerm: String, pageNum: Int): Flow<MangaPage>
fun getFilterList(sourceId: Long, reset: Boolean = false): Flow<List<SourceFilter>>
fun setFilter(sourceId: Long, sourceFilter: SourceFilterChange): Flow<HttpResponse>
fun setFilter(sourceId: Long, position: Int, value: Any): Flow<HttpResponse>
fun setFilter(sourceId: Long, parentPosition: Int, childPosition: Int, value: Any): Flow<HttpResponse>
fun getSourceSettings(sourceId: Long): Flow<List<SourcePreference>>
fun setSourceSetting(sourceId: Long, sourcePreference: SourcePreferenceChange): Flow<HttpResponse>
fun setSourceSetting(sourceId: Long, position: Int, value: Any): Flow<HttpResponse>
@GET("api/v1/source/{sourceId}")
fun getSourceInfo(
@Path("sourceId") sourceId: Long
): Flow<Source>
@GET("api/v1/source/{sourceId}/popular/{pageNum}")
fun getPopularManga(
@Path("sourceId") sourceId: Long,
@Path("pageNum") pageNum: Int
): Flow<MangaPage>
@GET("api/v1/source/{sourceId}/latest/{pageNum}")
fun getLatestManga(
@Path("sourceId") sourceId: Long,
@Path("pageNum") pageNum: Int
): Flow<MangaPage>
@GET("api/v1/source/{sourceId}/search")
fun getSearchResults(
@Path("sourceId") sourceId: Long,
@Query("searchTerm") searchTerm: String?,
@Query("pageNum") pageNum: Int
): Flow<MangaPage>
@GET("api/v1/source/{sourceId}/filters")
fun getFilterList(
@Path("sourceId") sourceId: Long,
@Query("reset") reset: Boolean = false
): Flow<List<SourceFilter>>
@POST("api/v1/source/{sourceId}/filters")
fun setFilter(
@Path("sourceId") sourceId: Long,
@Body sourceFilter: SourceFilterChange
): Flow<HttpResponse>
@GET("api/v1/source/{sourceId}/preferences")
fun getSourceSettings(
@Path("sourceId") sourceId: Long
): Flow<List<SourcePreference>>
@POST("api/v1/source/{sourceId}/preferences")
fun setSourceSetting(
@Path("sourceId") sourceId: Long,
@Body sourcePreference: SourcePreferenceChange
): Flow<HttpResponse>
}

View File

@@ -7,11 +7,26 @@
package ca.gosyer.jui.domain.updates.service
import ca.gosyer.jui.domain.updates.model.Updates
import de.jensklingenberg.ktorfit.http.Field
import de.jensklingenberg.ktorfit.http.FormUrlEncoded
import de.jensklingenberg.ktorfit.http.GET
import de.jensklingenberg.ktorfit.http.POST
import de.jensklingenberg.ktorfit.http.Path
import io.ktor.client.statement.HttpResponse
import kotlinx.coroutines.flow.Flow
interface UpdatesRepository {
fun getRecentUpdates(pageNum: Int): Flow<Updates>
@GET("api/v1/update/recentChapters/{pageNum}/")
fun getRecentUpdates(
@Path("pageNum") pageNum: Int
): Flow<Updates>
@POST("api/v1/update/fetch/")
fun updateLibrary(): Flow<HttpResponse>
fun updateCategory(categoryId: Long): Flow<HttpResponse>
@POST("api/v1/update/fetch/")
@FormUrlEncoded
fun updateCategory(
@Field("category") categoryId: Long
): Flow<HttpResponse>
}