diff --git a/app/src/main/kotlin/com/sanmer/mrepo/provider/repo/RepoProvider.kt b/app/src/main/kotlin/com/sanmer/mrepo/provider/repo/RepoProvider.kt index 845d7f45..36884640 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/provider/repo/RepoProvider.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/provider/repo/RepoProvider.kt @@ -5,7 +5,7 @@ import com.sanmer.mrepo.data.CloudManager import com.sanmer.mrepo.data.RepoManger import com.sanmer.mrepo.data.database.entity.Repo import com.sanmer.mrepo.data.json.OnlineModule -import com.sanmer.mrepo.data.json.Update +import com.sanmer.mrepo.utils.expansion.runRequest import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -16,7 +16,7 @@ object RepoProvider { private val coroutineScope = CoroutineScope(Dispatchers.IO) @Synchronized - fun getRepoAll() = coroutineScope.launch(Dispatchers.IO) { + fun getRepoAll() = coroutineScope.launch { if (Status.Cloud.isLoading) { Timber.w("getRepo is already loading!") return@launch @@ -40,90 +40,54 @@ object RepoProvider { } } - suspend fun getRepo(repo: Repo) = if (repo.enable) { - getRepo(repo.url).onSuccess { + suspend fun getRepo(repo: Repo) = withContext(Dispatchers.IO) { + if (!repo.enable) { + return@withContext Result.failure(RuntimeException("${repo.name} is disabled!")) + } + + runRequest { + val api = RepoService.create(repo.url) + return@runRequest api.getModules().execute() + }.onSuccess { CloudManager.updateById( id = repo.id, value = it.copy(repoId = repo.id) ) - RepoManger.update(repo.copy( - name = it.name, - size = it.modules.size, - timestamp = it.timestamp - )) + RepoManger.update( + repo.copy( + name = it.name, + size = it.modules.size, + timestamp = it.timestamp + ) + ) }.onFailure { Timber.e("getRepo: ${it.message}") } - } else { - Result.failure(RuntimeException("${repo.name} is disabled!")) } - private suspend fun getRepo(repoUrl: String) = withContext(Dispatchers.IO) { - try { - val api = RepoService.create(repoUrl) - val response = api.getModules().execute() - - if (response.isSuccessful) { - val data = response.body() - return@withContext if (data != null) { - Result.success(data) - }else { - Result.failure(NullPointerException("The data is null!")) - } - } else { - val errorBody = response.errorBody() - val error = errorBody?.string() - - return@withContext Result.failure(RuntimeException(error)) - } - } catch (e: Exception) { - return@withContext Result.failure(e) + suspend fun getUpdate(module: OnlineModule) = withContext(Dispatchers.IO) { + if (module.repoId.isEmpty()) { + return@withContext Result.failure(NoSuchElementException("The repoId is empty!")) } - } - suspend fun getUpdate(module: OnlineModule): Result> { - if (module.repoId.isEmpty()) { - return Result.failure(NoSuchElementException("The repoId is empty!")) - } else { - val result = module.repoId.map { id -> - val repo = RepoManger.getById(id)!! - getUpdate( - repo = repo, - id = module.id - ).onFailure { - Timber.d("getUpdate: ${it.message}") - } - } + val result = module.repoId.map { id -> + val repo = RepoManger.getById(id)!! - return if (result.all { it.isFailure }) { - Result.failure(result.first().exceptionOrNull()!!) - } else { - Result.success(result.map { it.getOrNull() }) + runRequest { + val api = RepoService.create(repo.url) + api.getUpdate(module.id).execute() + }.onSuccess { + return@map Result.success(it.copy(repoId = repo.id)) + }.onFailure { + Timber.d("getUpdate: ${it.message}") } } - } - - private suspend fun getUpdate(repo: Repo, id: String) = withContext(Dispatchers.IO) { - try { - val api = RepoService.create(repo.url) - val response = api.getUpdate(id).execute() - - if (response.isSuccessful) { - val data = response.body() - return@withContext if (data != null) { - Result.success(data.copy(repoId = repo.id)) - }else { - Result.failure(NullPointerException("The data is null!")) - } - } else { - val errorBody = response.errorBody() - val error = errorBody?.string() - return@withContext Result.failure(RuntimeException(error)) - } - } catch (e: Exception) { - return@withContext Result.failure(e) + return@withContext if (result.all { it.isFailure }) { + Result.failure(result.first().exceptionOrNull()!!) + } else { + Result.success(result.map { it.getOrNull() }) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/sanmer/mrepo/provider/repo/RepoService.kt b/app/src/main/kotlin/com/sanmer/mrepo/provider/repo/RepoService.kt index c15dcec0..51910729 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/provider/repo/RepoService.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/provider/repo/RepoService.kt @@ -6,6 +6,7 @@ import com.sanmer.mrepo.data.json.Update import retrofit2.Call import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory +import retrofit2.create import retrofit2.http.GET import retrofit2.http.Path import timber.log.Timber @@ -25,7 +26,7 @@ interface RepoService { .baseUrl(repoUrl) .addConverterFactory(MoshiConverterFactory.create()) .build() - .create(RepoService::class.java) + .create() } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/sanmer/mrepo/provider/spdx/LicenseProvider.kt b/app/src/main/kotlin/com/sanmer/mrepo/provider/spdx/LicenseProvider.kt index 853e3e98..577fe065 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/provider/spdx/LicenseProvider.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/provider/spdx/LicenseProvider.kt @@ -1,5 +1,6 @@ package com.sanmer.mrepo.provider.spdx +import com.sanmer.mrepo.utils.expansion.runRequest import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -7,21 +8,10 @@ object LicenseProvider { private val api by lazy { SpdxService.create() } suspend fun getLicense(id: String) = withContext(Dispatchers.IO) { - try { - val response = api.getLicense(id).execute() - - if (response.isSuccessful) { - val data = response.body() - return@withContext if (data != null) { - Result.success(data) - }else { - Result.failure(NullPointerException("The data is null!")) - } - } else { - return@withContext Result.failure(RuntimeException("The specified key does not exist!")) - } - } catch (e: Exception) { - return@withContext Result.failure(e) + runRequest { + api.getLicense(id).execute() + }.onFailure { + return@withContext Result.failure(RuntimeException("The specified key does not exist!")) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/sanmer/mrepo/provider/spdx/SpdxService.kt b/app/src/main/kotlin/com/sanmer/mrepo/provider/spdx/SpdxService.kt index 544058c4..281ed024 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/provider/spdx/SpdxService.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/provider/spdx/SpdxService.kt @@ -5,6 +5,7 @@ import com.sanmer.mrepo.data.json.License import retrofit2.Call import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory +import retrofit2.create import retrofit2.http.GET import retrofit2.http.Path @@ -18,7 +19,7 @@ interface SpdxService { .baseUrl(Const.SPDX_URL) .addConverterFactory(MoshiConverterFactory.create()) .build() - .create(SpdxService::class.java) + .create() } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/sanmer/mrepo/service/DownloadService.kt b/app/src/main/kotlin/com/sanmer/mrepo/service/DownloadService.kt index f1e41938..7a2ef734 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/service/DownloadService.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/service/DownloadService.kt @@ -18,13 +18,14 @@ import com.sanmer.mrepo.utils.NotificationUtils import com.sanmer.mrepo.utils.expansion.parcelable import com.sanmer.mrepo.utils.expansion.toFile import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber class DownloadService : LifecycleService() { + val context by lazy { this } + private data class DownloadItem( val id: Int = System.currentTimeMillis().toInt(), val value: Module, @@ -78,7 +79,7 @@ class DownloadService : LifecycleService() { private fun downloader( item: DownloadItem - ) { + ) = lifecycleScope.launch { val module = item.value val notificationId = item.id val notificationIdFinish = notificationId + 1 @@ -87,7 +88,7 @@ class DownloadService : LifecycleService() { Timber.d("download to ${path.absolutePath}") val notification = NotificationUtils - .buildNotification(this, Const.CHANNEL_ID_DOWNLOAD) + .buildNotification(context, Const.CHANNEL_ID_DOWNLOAD) .setContentTitle(module.name) .setContentIntent(NotificationUtils.getActivity(MainActivity::class)) .setProgress(0, 0 , false) @@ -121,7 +122,7 @@ class DownloadService : LifecycleService() { ) if (item.install) { - InstallActivity.start(context = this, path = path) + InstallActivity.start(context = context, path = path) } } @@ -138,18 +139,22 @@ class DownloadService : LifecycleService() { HttpUtils.downloader( url = module.url, - path = path, + out = path, onProgress = { broadcast(it, module) progressFlow.value = (it * 100).toInt() - }, - onSucceeded = succeeded, - onFailed = failed, - onFinished = { - list.remove(item) - stopIt() } - ) + ).onSuccess { + succeeded() + + list.remove(item) + stopIt() + }.onFailure { + failed(it.message) + + list.remove(item) + stopIt() + } } private fun setForeground() { diff --git a/app/src/main/kotlin/com/sanmer/mrepo/utils/HttpUtils.kt b/app/src/main/kotlin/com/sanmer/mrepo/utils/HttpUtils.kt index c43b1e77..568523a6 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/utils/HttpUtils.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/utils/HttpUtils.kt @@ -1,87 +1,71 @@ package com.sanmer.mrepo.utils +import com.sanmer.mrepo.utils.expansion.runRequest import com.sanmer.mrepo.utils.MediaStoreUtils.newOutputStream +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import okhttp3.* import java.io.File -import java.io.IOException object HttpUtils { - private fun request( - url: String, callback: Callback - ) { - val client = OkHttpClient() - val request = Request.Builder() - .url(url) - .build() - client.newCall(request).enqueue(callback) - } - - fun request( + suspend fun request( url: String, - onSucceeded: (ResponseBody) -> Unit = {}, - onFailed: (String?) -> Unit = {}, - onFinished: () -> Unit = {}, - ) = request(url = url, callback = object : Callback { - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful) { - response.body()?.let(onSucceeded) - } else { - onFailed(response.body()?.string()) - } - onFinished() + get: (ResponseBody) -> T + ) = withContext(Dispatchers.IO) { + runRequest(get) { + val client = OkHttpClient() + val request = Request.Builder() + .url(url) + .build() + client.newCall(request).execute() } - override fun onFailure(call: Call, e: IOException) { - onFailed(e.message) - onFinished() - } - }) + } - fun downloader( + suspend fun requestString( + url: String + ): Result = request( + url = url, + get = { it.string() } + ) + + suspend fun downloader( url: String, - onProgress: (Float) -> Unit, - onSucceeded: () -> Unit = {}, - onFailed: (String?) -> Unit = {}, - onFinished: () -> Unit = {}, - path: File - ) { - path.parentFile!!.let { + out: File, + onProgress: (Float) -> Unit + ): Result { + out.parentFile!!.let { if (!it.exists()) it.mkdirs() } - val succeeded: (ResponseBody) -> Unit = { body -> - runCatching { - val buffer = ByteArray(2048) - val input = body.byteStream() - - val output = path.newOutputStream() + val get: (ResponseBody) -> File = { + val buffer = ByteArray(2048) + val input = it.byteStream() - val all = body.contentLength() - var finished: Long = 0 - var readying: Int + val output = out.newOutputStream() - while (input.read(buffer).also { readying = it } != -1) { - output?.write(buffer, 0, readying) - finished += readying.toLong() + val all = it.contentLength() + var finished: Long = 0 + var readying: Int - val progress = (finished * 1.0 / all).toFloat() - onProgress(progress) - } + while (input.read(buffer).also { readying = it } != -1) { + output?.write(buffer, 0, readying) + finished += readying.toLong() - output?.flush() - output?.close() - input.close() - onSucceeded() - }.onFailure { - onFailed(it.message) + val progress = (finished * 1.0 / all).toFloat() + onProgress(progress) } + + output?.flush() + output?.close() + input.close() + + out } - request( + return request( url = url, - onSucceeded = succeeded, - onFailed = onFailed, - onFinished = onFinished + get = get ) } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/sanmer/mrepo/utils/expansion/Response.kt b/app/src/main/kotlin/com/sanmer/mrepo/utils/expansion/Response.kt new file mode 100644 index 00000000..db30dbfe --- /dev/null +++ b/app/src/main/kotlin/com/sanmer/mrepo/utils/expansion/Response.kt @@ -0,0 +1,44 @@ +package com.sanmer.mrepo.utils.expansion + +fun runRequest( + run: () -> retrofit2.Response +): Result = try { + val response = run() + if (response.isSuccessful) { + val data = response.body() + if (data != null) { + Result.success(data) + }else { + Result.failure(NullPointerException("The data is null!")) + } + } else { + val errorBody = response.errorBody() + val error = errorBody?.string() + + Result.failure(RuntimeException(error)) + } +} catch (e: Exception) { + Result.failure(e) +} + +fun runRequest( + get: (okhttp3.ResponseBody) -> T, + run: () -> okhttp3.Response +): Result = try { + val response = run() + if (response.isSuccessful) { + val data = response.body() + if (data != null) { + Result.success(get(data)) + } else { + Result.failure(NullPointerException("The data is null!")) + } + } else { + val errorBody = response.body() + val error = errorBody?.string() + + Result.failure(RuntimeException(error)) + } +} catch (e: Exception) { + Result.failure(e) +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/DetailViewModel.kt b/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/DetailViewModel.kt index 45e9db56..589888d8 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/DetailViewModel.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/DetailViewModel.kt @@ -13,6 +13,7 @@ import com.sanmer.mrepo.app.Event import com.sanmer.mrepo.app.Status import com.sanmer.mrepo.data.ModuleManager import com.sanmer.mrepo.data.json.OnlineModule +import com.sanmer.mrepo.data.json.Update import com.sanmer.mrepo.data.json.UpdateItem import com.sanmer.mrepo.data.parcelable.Module import com.sanmer.mrepo.provider.repo.RepoProvider @@ -53,7 +54,7 @@ class DetailViewModel( getModule() } - private fun getModule() = viewModelScope.launch(Dispatchers.Default) { + private fun getModule() = viewModelScope.launch { if (id.isNullOrBlank()) { state.setFailed("The id is null or blank") } else { @@ -70,18 +71,20 @@ class DetailViewModel( } suspend fun getUpdates() { + val update: (Update) -> Unit = { update -> + update.versions.forEach { item -> + val versionCodes = versions.map { it.versionCode } + if (item.versionCode !in versionCodes) { + val new = item.copy(repoId = update.repoId) + versions.update(new) + } + } + } + RepoProvider.getUpdate(module).onSuccess { list -> list.filterNotNull() .sortedByDescending { it.timestamp } - .forEach { update -> - update.versions.forEach { item -> - val versionCodes = versions.map { it.versionCode } - if (item.versionCode !in versionCodes) { - val new = item.copy(repoId = update.repoId) - versions.update(new) - } - } - } + .forEach(update) if (versions.isNotEmpty()) { versions.sortedByDescending { it.versionCode } @@ -95,22 +98,20 @@ class DetailViewModel( } } - fun getChangelog(versionCode: Int) { + fun getChangelog(versionCode: Int) = viewModelScope.launch { val updateItem = versions.find { it.versionCode == versionCode } ?: versions.first() if (updateItem.changelog.isNotBlank()) { - HttpUtils.request( - url = updateItem.changelog, - onSucceeded = { - changelog = it.string() - }, - onFailed = { - changelog = it - Timber.d("getChangelog: $it") - } - ) + HttpUtils.requestString( + url = updateItem.changelog + ).onSuccess { + changelog = it + }.onFailure { + changelog = it.message + Timber.d("getChangelog: ${it.message}") + } } }