Remove repeat requests when failures happen

This commit is contained in:
Syer10
2021-07-14 22:30:05 -04:00
parent 9b2ab4554a
commit 57457c7117
10 changed files with 64 additions and 138 deletions

View File

@@ -17,6 +17,8 @@ import ca.gosyer.util.lang.withIOContext
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitFormWithBinaryData
import io.ktor.client.request.get
import io.ktor.client.request.post
import io.ktor.client.statement.HttpResponse
import io.ktor.http.ContentType
import io.ktor.http.Headers
@@ -47,7 +49,7 @@ class BackupInteractionHandler @Inject constructor(
}
suspend fun importBackup(backup: Backup, block: HttpRequestBuilder.() -> Unit = {}) = withIOContext {
client.postRepeat<HttpResponse>(
client.post<HttpResponse>(
serverUrl + backupImportRequest()
) {
contentType(ContentType.Application.Json)
@@ -57,14 +59,14 @@ class BackupInteractionHandler @Inject constructor(
}
suspend fun exportBackupFile(block: HttpRequestBuilder.() -> Unit = {}) = withIOContext {
client.getRepeat<HttpResponse>(
client.get<HttpResponse>(
serverUrl + backupFileExportRequest(),
block
)
}
suspend fun exportBackup(block: HttpRequestBuilder.() -> Unit = {}) = withIOContext {
client.getRepeat<Backup>(
client.get<Backup>(
serverUrl + backupExportRequest(),
block
)

View File

@@ -6,17 +6,8 @@
package ca.gosyer.data.server.interactions
import androidx.compose.ui.graphics.ImageBitmap
import ca.gosyer.data.server.Http
import ca.gosyer.data.server.ServerPreferences
import ca.gosyer.util.lang.throwIfCancellation
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.delete
import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.get
import io.ktor.client.request.patch
import io.ktor.client.request.post
import io.ktor.http.Parameters
open class BaseInteractionHandler(
protected val client: Http,
@@ -24,72 +15,4 @@ open class BaseInteractionHandler(
) {
private val _serverUrl = serverPreferences.serverUrl()
val serverUrl get() = _serverUrl.get()
protected inline fun <T> repeat(block: () -> T): T {
var attempt = 1
var lastException: Exception
do {
try {
return block()
} catch (e: Exception) {
e.throwIfCancellation()
lastException = e
}
attempt++
} while (attempt <= 3)
throw lastException
}
protected suspend inline fun <reified T> Http.getRepeat(
urlString: String,
noinline block: HttpRequestBuilder.() -> Unit = {}
): T {
return repeat {
get(urlString, block)
}
}
protected suspend inline fun <reified T> Http.deleteRepeat(
urlString: String,
noinline block: HttpRequestBuilder.() -> Unit = {}
): T {
return repeat {
delete(urlString, block)
}
}
protected suspend inline fun <reified T> Http.patchRepeat(
urlString: String,
noinline block: HttpRequestBuilder.() -> Unit = {}
): T {
return repeat {
patch(urlString, block)
}
}
protected suspend inline fun <reified T> Http.postRepeat(
urlString: String,
noinline block: HttpRequestBuilder.() -> Unit = {}
): T {
return repeat {
post(urlString, block)
}
}
protected suspend inline fun <reified T> Http.submitFormRepeat(
urlString: String,
formParameters: Parameters = Parameters.Empty,
encodeInQuery: Boolean = false,
block: HttpRequestBuilder.() -> Unit = {}
): T {
return repeat {
submitForm(urlString, formParameters, encodeInQuery, block)
}
}
suspend fun imageFromUrl(client: Http, imageUrl: String, block: HttpRequestBuilder.() -> Unit): ImageBitmap {
return repeat {
ca.gosyer.util.compose.imageFromUrl(client, imageUrl, block)
}
}
}

View File

@@ -20,6 +20,9 @@ import ca.gosyer.data.server.requests.getMangaCategoriesQuery
import ca.gosyer.data.server.requests.getMangaInCategoryQuery
import ca.gosyer.data.server.requests.removeMangaFromCategoryRequest
import ca.gosyer.util.lang.withIOContext
import io.ktor.client.request.delete
import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpMethod
import io.ktor.http.Parameters
@@ -31,7 +34,7 @@ class CategoryInteractionHandler @Inject constructor(
) : BaseInteractionHandler(client, serverPreferences) {
suspend fun getMangaCategories(mangaId: Long) = withIOContext {
client.getRepeat<List<Category>>(
client.get<List<Category>>(
serverUrl + getMangaCategoriesQuery(mangaId)
)
}
@@ -39,7 +42,7 @@ class CategoryInteractionHandler @Inject constructor(
suspend fun getMangaCategories(manga: Manga) = getMangaCategories(manga.id)
suspend fun addMangaToCategory(mangaId: Long, categoryId: Long) = withIOContext {
client.getRepeat<HttpResponse>(
client.get<HttpResponse>(
serverUrl + addMangaToCategoryQuery(mangaId, categoryId)
)
}
@@ -48,7 +51,7 @@ class CategoryInteractionHandler @Inject constructor(
suspend fun addMangaToCategory(mangaId: Long, category: Category) = addMangaToCategory(mangaId, category.id)
suspend fun removeMangaFromCategory(mangaId: Long, categoryId: Long) = withIOContext {
client.deleteRepeat<HttpResponse>(
client.delete<HttpResponse>(
serverUrl + removeMangaFromCategoryRequest(mangaId, categoryId)
)
}
@@ -57,13 +60,13 @@ class CategoryInteractionHandler @Inject constructor(
suspend fun removeMangaFromCategory(mangaId: Long, category: Category) = removeMangaFromCategory(mangaId, category.id)
suspend fun getCategories() = withIOContext {
client.getRepeat<List<Category>>(
client.get<List<Category>>(
serverUrl + getCategoriesQuery()
)
}
suspend fun createCategory(name: String) = withIOContext {
client.submitFormRepeat<HttpResponse>(
client.submitForm<HttpResponse>(
serverUrl + createCategoryRequest(),
formParameters = Parameters.build {
append("name", name)
@@ -72,7 +75,7 @@ class CategoryInteractionHandler @Inject constructor(
}
suspend fun modifyCategory(categoryId: Long, name: String? = null, isLanding: Boolean? = null) = withIOContext {
client.submitFormRepeat<HttpResponse>(
client.submitForm<HttpResponse>(
serverUrl + categoryModifyRequest(categoryId),
formParameters = Parameters.build {
if (name != null) {
@@ -89,7 +92,7 @@ class CategoryInteractionHandler @Inject constructor(
suspend fun modifyCategory(category: Category, name: String? = null, isLanding: Boolean? = null) = modifyCategory(category.id, name, isLanding)
suspend fun reorderCategory(categoryId: Long, to: Int, from: Int) = withIOContext {
client.submitFormRepeat<HttpResponse>(
client.submitForm<HttpResponse>(
serverUrl + categoryReorderRequest(categoryId),
formParameters = Parameters.build {
append("to", to.toString())
@@ -102,14 +105,14 @@ class CategoryInteractionHandler @Inject constructor(
suspend fun reorderCategory(category: Category, to: Int, from: Int) = reorderCategory(category.id, to, from)
suspend fun deleteCategory(categoryId: Long) = withIOContext {
client.deleteRepeat<HttpResponse>(
client.delete<HttpResponse>(
serverUrl + categoryDeleteRequest(categoryId)
)
}
suspend fun deleteCategory(category: Category) = deleteCategory(category.id)
suspend fun getMangaFromCategory(categoryId: Long) = withIOContext {
client.getRepeat<List<Manga>>(
client.get<List<Manga>>(
serverUrl + getMangaInCategoryQuery(categoryId)
)
}

View File

@@ -17,8 +17,12 @@ import ca.gosyer.data.server.requests.getPageQuery
import ca.gosyer.data.server.requests.queueDownloadChapterRequest
import ca.gosyer.data.server.requests.updateChapterMetaRequest
import ca.gosyer.data.server.requests.updateChapterRequest
import ca.gosyer.util.compose.imageFromUrl
import ca.gosyer.util.lang.withIOContext
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.delete
import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpMethod
@@ -31,7 +35,7 @@ class ChapterInteractionHandler @Inject constructor(
) : BaseInteractionHandler(client, serverPreferences) {
suspend fun getChapters(mangaId: Long, refresh: Boolean = false) = withIOContext {
client.getRepeat<List<Chapter>>(
client.get<List<Chapter>>(
serverUrl + getMangaChaptersQuery(mangaId)
) {
url {
@@ -45,7 +49,7 @@ class ChapterInteractionHandler @Inject constructor(
suspend fun getChapters(manga: Manga, refresh: Boolean = false) = getChapters(manga.id, refresh)
suspend fun getChapter(mangaId: Long, chapterIndex: Int) = withIOContext {
client.getRepeat<Chapter>(
client.get<Chapter>(
serverUrl + getChapterQuery(mangaId, chapterIndex)
)
}
@@ -64,7 +68,7 @@ class ChapterInteractionHandler @Inject constructor(
lastPageRead: Int? = null,
markPreviousRead: Boolean? = null
) = withIOContext {
client.submitFormRepeat<HttpResponse>(
client.submitForm<HttpResponse>(
serverUrl + updateChapterRequest(mangaId, chapterIndex),
formParameters = Parameters.build {
if (read != null) {
@@ -132,7 +136,7 @@ class ChapterInteractionHandler @Inject constructor(
suspend fun getPage(manga: Manga, chapter: Chapter, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = getPage(manga.id, chapter.index, pageNum, block)
suspend fun queueChapterDownload(mangaId: Long, chapterIndex: Int) = withIOContext {
client.getRepeat<HttpResponse>(
client.get<HttpResponse>(
serverUrl + queueDownloadChapterRequest(mangaId, chapterIndex)
)
}
@@ -144,7 +148,7 @@ class ChapterInteractionHandler @Inject constructor(
suspend fun queueChapterDownload(manga: Manga, chapter: Chapter) = queueChapterDownload(manga.id, chapter.index)
suspend fun deleteChapterDownload(mangaId: Long, chapterIndex: Int) = withIOContext {
client.deleteRepeat<HttpResponse>(
client.delete<HttpResponse>(
serverUrl + deleteDownloadChapterRequest(mangaId, chapterIndex)
)
}
@@ -156,7 +160,7 @@ class ChapterInteractionHandler @Inject constructor(
suspend fun deleteChapterDownload(manga: Manga, chapter: Chapter) = deleteChapterDownload(manga.id, chapter.index)
suspend fun updateChapterMeta(mangaId: Long, chapterIndex: Int, key: String, value: String) = withIOContext {
client.submitFormRepeat<HttpResponse>(
client.submitForm<HttpResponse>(
serverUrl + updateChapterMetaRequest(mangaId, chapterIndex),
formParameters = Parameters.build {
append("key", key)

View File

@@ -12,6 +12,7 @@ import ca.gosyer.data.server.requests.downloadsClearRequest
import ca.gosyer.data.server.requests.downloadsStartRequest
import ca.gosyer.data.server.requests.downloadsStopRequest
import ca.gosyer.util.lang.withIOContext
import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse
import javax.inject.Inject
@@ -21,19 +22,19 @@ class DownloadInteractionHandler @Inject constructor(
) : BaseInteractionHandler(client, serverPreferences) {
suspend fun startDownloading() = withIOContext {
client.getRepeat<HttpResponse>(
client.get<HttpResponse>(
serverUrl + downloadsStartRequest()
)
}
suspend fun stopDownloading() = withIOContext {
client.getRepeat<HttpResponse>(
client.get<HttpResponse>(
serverUrl + downloadsStopRequest()
)
}
suspend fun clearDownloadQueue() = withIOContext {
client.getRepeat<HttpResponse>(
client.get<HttpResponse>(
serverUrl + downloadsClearRequest()
)
}

View File

@@ -14,8 +14,10 @@ import ca.gosyer.data.server.requests.apkInstallQuery
import ca.gosyer.data.server.requests.apkUninstallQuery
import ca.gosyer.data.server.requests.apkUpdateQuery
import ca.gosyer.data.server.requests.extensionListQuery
import ca.gosyer.util.compose.imageFromUrl
import ca.gosyer.util.lang.withIOContext
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse
import javax.inject.Inject
@@ -25,25 +27,25 @@ class ExtensionInteractionHandler @Inject constructor(
) : BaseInteractionHandler(client, serverPreferences) {
suspend fun getExtensionList() = withIOContext {
client.getRepeat<List<Extension>>(
client.get<List<Extension>>(
serverUrl + extensionListQuery()
)
}
suspend fun installExtension(extension: Extension) = withIOContext {
client.getRepeat<HttpResponse>(
client.get<HttpResponse>(
serverUrl + apkInstallQuery(extension.pkgName)
)
}
suspend fun updateExtension(extension: Extension) = withIOContext {
client.getRepeat<HttpResponse>(
client.get<HttpResponse>(
serverUrl + apkUpdateQuery(extension.pkgName)
)
}
suspend fun uninstallExtension(extension: Extension) = withIOContext {
client.getRepeat<HttpResponse>(
client.get<HttpResponse>(
serverUrl + apkUninstallQuery(extension.pkgName)
)
}

View File

@@ -13,6 +13,8 @@ import ca.gosyer.data.server.requests.addMangaToLibraryQuery
import ca.gosyer.data.server.requests.getLibraryQuery
import ca.gosyer.data.server.requests.removeMangaFromLibraryRequest
import ca.gosyer.util.lang.withIOContext
import io.ktor.client.request.delete
import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse
import javax.inject.Inject
@@ -22,13 +24,13 @@ class LibraryInteractionHandler @Inject constructor(
) : BaseInteractionHandler(client, serverPreferences) {
suspend fun getLibraryManga() = withIOContext {
client.getRepeat<List<Manga>>(
client.get<List<Manga>>(
serverUrl + getLibraryQuery()
)
}
suspend fun addMangaToLibrary(mangaId: Long) = withIOContext {
client.getRepeat<HttpResponse>(
client.get<HttpResponse>(
serverUrl + addMangaToLibraryQuery(mangaId)
)
}
@@ -36,7 +38,7 @@ class LibraryInteractionHandler @Inject constructor(
suspend fun addMangaToLibrary(manga: Manga) = addMangaToLibrary(manga.id)
suspend fun removeMangaFromLibrary(mangaId: Long) = withIOContext {
client.deleteRepeat<HttpResponse>(
client.delete<HttpResponse>(
serverUrl + removeMangaFromLibraryRequest(mangaId)
)
}

View File

@@ -12,8 +12,11 @@ import ca.gosyer.data.server.ServerPreferences
import ca.gosyer.data.server.requests.mangaQuery
import ca.gosyer.data.server.requests.mangaThumbnailQuery
import ca.gosyer.data.server.requests.updateMangaMetaRequest
import ca.gosyer.util.compose.imageFromUrl
import ca.gosyer.util.lang.withIOContext
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpMethod
@@ -26,7 +29,7 @@ class MangaInteractionHandler @Inject constructor(
) : BaseInteractionHandler(client, serverPreferences) {
suspend fun getManga(mangaId: Long, refresh: Boolean = false) = withIOContext {
client.getRepeat<Manga>(
client.get<Manga>(
serverUrl + mangaQuery(mangaId)
) {
url {
@@ -48,7 +51,7 @@ class MangaInteractionHandler @Inject constructor(
}
suspend fun updateMangaMeta(mangaId: Long, key: String, value: String) = withIOContext {
client.submitFormRepeat<HttpResponse>(
client.submitForm<HttpResponse>(
serverUrl + updateMangaMetaRequest(mangaId),
formParameters = Parameters.build {
append("key", key)

View File

@@ -18,6 +18,7 @@ import ca.gosyer.data.server.requests.sourceListQuery
import ca.gosyer.data.server.requests.sourcePopularQuery
import ca.gosyer.data.server.requests.sourceSearchQuery
import ca.gosyer.util.lang.withIOContext
import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse
import javax.inject.Inject
@@ -27,13 +28,13 @@ class SourceInteractionHandler @Inject constructor(
) : BaseInteractionHandler(client, serverPreferences) {
suspend fun getSourceList() = withIOContext {
client.getRepeat<List<Source>>(
client.get<List<Source>>(
serverUrl + sourceListQuery()
)
}
suspend fun getSourceInfo(sourceId: Long) = withIOContext {
client.getRepeat<Source>(
client.get<Source>(
serverUrl + sourceInfoQuery(sourceId)
)
}
@@ -41,7 +42,7 @@ class SourceInteractionHandler @Inject constructor(
suspend fun getSourceInfo(source: Source) = getSourceInfo(source.id)
suspend fun getPopularManga(sourceId: Long, pageNum: Int) = withIOContext {
client.getRepeat<MangaPage>(
client.get<MangaPage>(
serverUrl + sourcePopularQuery(sourceId, pageNum)
)
}
@@ -52,7 +53,7 @@ class SourceInteractionHandler @Inject constructor(
)
suspend fun getLatestManga(sourceId: Long, pageNum: Int) = withIOContext {
client.getRepeat<MangaPage>(
client.get<MangaPage>(
serverUrl + sourceLatestQuery(sourceId, pageNum)
)
}
@@ -64,13 +65,13 @@ class SourceInteractionHandler @Inject constructor(
// TODO: 2021-03-14
suspend fun getGlobalSearchResults(searchTerm: String) = withIOContext {
client.getRepeat<HttpResponse>(
client.get<HttpResponse>(
serverUrl + globalSearchQuery(searchTerm)
)
}
suspend fun getSearchResults(sourceId: Long, searchTerm: String, pageNum: Int) = withIOContext {
client.getRepeat<MangaPage>(
client.get<MangaPage>(
serverUrl + sourceSearchQuery(sourceId, searchTerm, pageNum)
)
}
@@ -83,7 +84,7 @@ class SourceInteractionHandler @Inject constructor(
// TODO: 2021-03-14
suspend fun getFilterList(sourceId: Long) = withIOContext {
client.getRepeat<HttpResponse>(
client.get<HttpResponse>(
serverUrl + getFilterListQuery(sourceId)
)
}

View File

@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@@ -23,14 +22,15 @@ import androidx.compose.ui.layout.ContentScale
import ca.gosyer.common.di.AppScope
import ca.gosyer.data.server.Http
import ca.gosyer.util.compose.imageFromUrl
import ca.gosyer.util.lang.throwIfCancellation
import ca.gosyer.util.system.kLogger
import io.ktor.client.features.onDownload
import io.ktor.client.request.HttpRequestBuilder
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
val logger = kLogger {}
@OptIn(DelicateCoroutinesApi::class)
@Composable
fun KtorImage(
@@ -42,22 +42,22 @@ fun KtorImage(
contentScale: ContentScale = ContentScale.Fit,
alpha: Float = DefaultAlpha,
colorFilter: ColorFilter? = null,
retries: Int = 3,
client: Http = remember { AppScope.getInstance() }
) {
BoxWithConstraints {
val drawable: MutableState<ImageBitmap?> = remember { mutableStateOf(null) }
val loading: MutableState<Boolean> = remember { mutableStateOf(true) }
val progress: MutableState<Float> = remember { mutableStateOf(0.0F) }
val error: MutableState<String?> = remember { mutableStateOf(null) }
val drawable = remember { mutableStateOf<ImageBitmap?>(null) }
val loading = remember { mutableStateOf(true) }
val progress = remember { mutableStateOf(0.0F) }
val error = remember { mutableStateOf<String?>(null) }
DisposableEffect(imageUrl) {
val handler = CoroutineExceptionHandler { _, throwable ->
logger.error(throwable) { "Error loading image $imageUrl" }
loading.value = false
error.value = throwable.message
}
val job = GlobalScope.launch(handler) {
if (drawable.value == null) {
drawable.value = getImage(client, imageUrl, retries) {
drawable.value = imageFromUrl(client, imageUrl) {
onDownload { bytesSentTotal, contentLength ->
progress.value = (bytesSentTotal.toFloat() / contentLength).coerceAtMost(1.0F)
}
@@ -88,18 +88,3 @@ fun KtorImage(
}
}
}
private suspend fun getImage(client: Http, imageUrl: String, retries: Int = 3, block: HttpRequestBuilder.() -> Unit): ImageBitmap {
var attempt = 1
var lastException: Exception
do {
try {
return imageFromUrl(client, imageUrl, block)
} catch (e: Exception) {
e.throwIfCancellation()
lastException = e
}
attempt++
} while (attempt <= retries)
throw lastException
}