Compare commits

..

3 commits

Author SHA1 Message Date
3ddbe78fe8
nice and clean now
I don't know if this works
2024-08-07 13:37:42 +02:00
5cf1466c54
refactor the api 2024-08-07 12:55:17 +02:00
ebddf95465
merge callbacks 2024-08-07 11:34:42 +02:00
24 changed files with 328 additions and 330 deletions

View file

@ -44,17 +44,16 @@ class DashboardActivity : ComponentActivity() {
val dashboardViewModel = DashboardViewModel(application)
if (intent.getBooleanExtra("direct", false).not()) {
dashboardViewModel.refresh(this)
dashboardViewModel.refresh()
}
dashboardViewModel.checkTermux(this)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
dashboardViewModel.refreshError.collect {
it.forEach { errorMsg ->
Toast.makeText(baseContext, errorMsg, Toast.LENGTH_SHORT).show()
}
}
Toast.makeText(baseContext, it, Toast.LENGTH_SHORT).show()
} // TODO any better way?
}
}
@ -113,9 +112,9 @@ fun MyNavigationBar(items: List<Screen>, navController: NavHostController) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
// re-selecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
// Restore state when re-selecting a previously selected item
restoreState = true
}

View file

@ -1,5 +1,5 @@
package eu.m724.vastapp.activity.dashboard
data class DashboardUiState(
val refreshing: Int = 0
val refreshing: Boolean = false
)

View file

@ -6,18 +6,17 @@ import android.content.Context
import androidx.activity.ComponentActivity
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.viewModelScope
import eu.m724.vastapp.R
import eu.m724.vastapp.VastApplication
import eu.m724.vastapp.activity.Opener
import eu.m724.vastapp.activity.PermissionChecker
import eu.m724.vastapp.vastai.ApiRoute
import eu.m724.vastapp.vastai.api.InstancesUrlRequestCallback
import eu.m724.vastapp.vastai.api.UserUrlRequestCallback
import eu.m724.vastapp.vastai.data.RentedInstance
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class DashboardViewModel(
@ -25,12 +24,12 @@ class DashboardViewModel(
) : AndroidViewModel(application) { // TODO do something with the user
private val _uiState: MutableStateFlow<DashboardUiState> =
MutableStateFlow(DashboardUiState(0))
MutableStateFlow(DashboardUiState(false))
val uiState: StateFlow<DashboardUiState> =
_uiState.asStateFlow()
private val _refreshError: MutableStateFlow<List<String>> = MutableStateFlow(emptyList())
val refreshError: StateFlow<List<String>> = _refreshError.asStateFlow()
private val _refreshError: MutableStateFlow<String?> = MutableStateFlow(null)
val refreshError: StateFlow<String?> = _refreshError.asStateFlow()
private val _termuxAvailable: MutableStateFlow<Int> = MutableStateFlow(0)
val termuxAvailable: StateFlow<Int> = _termuxAvailable.asStateFlow()
@ -39,43 +38,29 @@ class DashboardViewModel(
private val vastApi = this.application.vastApi
val account = this.application.account!!
fun refresh(activity: ComponentActivity) {
_uiState.value = _uiState.value.copy(refreshing = 2)
_refreshError.value = emptyList()
fun refresh() {
_uiState.update { it.copy(refreshing = true) }
_refreshError.value = null
val userRequest = vastApi.buildRequest(
ApiRoute.SHOW_USER,
UserUrlRequestCallback({ newUser ->
account.updateUser(newUser)
_uiState.update {
it.copy(refreshing = it.refreshing - 1) // TODO I don't like how this looks
}
}, { apiFailure ->
_refreshError.update { it + apiFailure.errorMessage!! }
_uiState.update {
it.copy(refreshing = it.refreshing - 1)
}
})
)
val userDeferred = vastApi.getUser()
val rentedInstancesDeferred = vastApi.getRentedInstances()
val instancesRequest = vastApi.buildRequest(
ApiRoute.GET_INSTANCES,
InstancesUrlRequestCallback({ instances ->
account.updateRentedInstances(instances)
_uiState.update {
it.copy(refreshing = it.refreshing - 1)
viewModelScope.launch {
try {
account.updateUser(userDeferred.await())
account.updateRentedInstances(rentedInstancesDeferred.await())
} catch (e: Exception) {
_refreshError.update {
"Refresh failed: " + e.message.toString()
}
}
_uiState.update {
it.copy(refreshing = false)
}
}
}, { apiFailure ->
_refreshError.update { it + apiFailure.errorMessage!! }
_uiState.update {
it.copy(refreshing = it.refreshing - 1)
}
})
) // TODO move all that refreshing to some shared place
userRequest.start()
instancesRequest.start()
fun checkTermux(activity: ComponentActivity) {
val context = activity.applicationContext
_termuxAvailable.value =
@ -88,8 +73,6 @@ class DashboardViewModel(
} else -1 // not available because permission denied
}
} else -1 // not available
// TODO I don't like this function especially the last line. I think it should be moved to application
}
@SuppressLint("SdCardPath")

View file

@ -32,7 +32,7 @@ class LoadingActivity : ComponentActivity() {
.putExtra("direct", true)
} else {
Intent(this, LoginActivity::class.java)
.putExtra("error", result.error!!)
.putExtra("error", result.error)
}
this.startActivity(intent)

View file

@ -2,11 +2,10 @@ package eu.m724.vastapp.activity.dashboard.loading
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import eu.m724.vastapp.VastApplication
import eu.m724.vastapp.vastai.Account
import eu.m724.vastapp.vastai.ApiRoute
import eu.m724.vastapp.vastai.api.InstancesUrlRequestCallback
import eu.m724.vastapp.vastai.api.UserUrlRequestCallback
import kotlinx.coroutines.launch
class LoadingViewModel(
application: Application,
@ -15,40 +14,30 @@ class LoadingViewModel(
private val application = application as VastApplication
fun init() {
val vastApi = application.vastApi
val apiKey = application.loadKey()
if (apiKey != null) {
vastApi.apiKey = apiKey
val request = vastApi.buildRequest(
ApiRoute.SHOW_USER,
UserUrlRequestCallback({ user ->
application.account = Account(user)
loadInstances()
}, { apiFailure ->
onEnded(LoadingResult(false, apiFailure.errorMessage))
})
)
request.start()
}
if (apiKey == null) {
onEnded(LoadingResult(false, null))
return
}
private fun loadInstances() {
val vastApi = application.vastApi
val instancesRequest = vastApi.buildRequest(
ApiRoute.GET_INSTANCES,
InstancesUrlRequestCallback({ instances ->
application.account!!.updateRentedInstances(instances)
onEnded(LoadingResult(true, null))
}, { apiFailure ->
// TODO I don't know what to do yet
onEnded(LoadingResult(true, null))
})
)
vastApi.setApiKey(apiKey)
val userDeferred = vastApi.getUser()
instancesRequest.start()
viewModelScope.launch {
try {
val user = userDeferred.await()
application.account = Account(user)
// TODO should we do this were, or is it better to say you're logged in and handle the error from dashboard
val rentedInstances = vastApi.getRentedInstances().await()
application.account!!.updateRentedInstances(rentedInstances)
onEnded(LoadingResult(true, null))
} catch (e: Exception) {
onEnded(LoadingResult(false, e.message.toString()))
}
}
}
}

View file

@ -1,6 +1,5 @@
package eu.m724.vastapp.activity.dashboard.screen
import androidx.activity.ComponentActivity
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
@ -24,9 +23,7 @@ import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -47,14 +44,13 @@ fun DashboardScreen(dashboardViewModel: DashboardViewModel) {
val user by dashboardViewModel.account.user.collectAsState()
val rentedInstances by dashboardViewModel.account.rentedInstances.collectAsState()
val remainingTime by dashboardViewModel.account.remainingTime.collectAsState()
val isRefreshing by remember(uiState) { derivedStateOf { uiState.refreshing > 0 } }
val scrollState = rememberScrollState()
PullToRefreshBox(
isRefreshing = isRefreshing,
isRefreshing = uiState.refreshing,
state = rememberPullToRefreshState(),
onRefresh = { dashboardViewModel.refresh(context as ComponentActivity) }
onRefresh = { dashboardViewModel.refresh() }
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,

View file

@ -4,13 +4,13 @@ import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import eu.m724.vastapp.VastApplication
import eu.m724.vastapp.vastai.ApiRoute
import eu.m724.vastapp.vastai.api.UserUrlRequestCallback
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.chromium.net.CronetEngine
import java.util.concurrent.Executor
@ -30,29 +30,23 @@ class LoginViewModel(
private val _apiKey = MutableStateFlow<String>("")
var apiKey: StateFlow<String> = _apiKey.asStateFlow()
private val _fullscreenLoading = MutableStateFlow<Boolean>(false)
var fullscreenLoading: StateFlow<Boolean> = _fullscreenLoading.asStateFlow()
private val application = getApplication<VastApplication>()
fun tryLogin() {
val vastApi = application.vastApi
vastApi.apiKey = apiKey.value
val userDeferred = application.vastApi.getUser()
val request = vastApi.buildRequest(
ApiRoute.SHOW_USER,
UserUrlRequestCallback({ user ->
viewModelScope.launch {
try {
val user = userDeferred.await()
application.submitKey(apiKey.value) // TODO toggle for this
_uiState.value = LoginUiState.Success(user)
}, { apiFailure ->
_uiState.value = LoginUiState.Idle
_fullscreenLoading.value = false
_error.postValue(apiFailure.errorMessage)
})
)
_uiState.update { LoginUiState.Success(user) }
} catch (e: Exception) {
_uiState.update { LoginUiState.Idle }
_error.postValue(e.toString())
}
}
_uiState.value = LoginUiState.Loading
request.start()
_uiState.update { LoginUiState.Loading }
}
fun onApiKeyChange(apiKey: String) {

View file

@ -1,6 +0,0 @@
package eu.m724.vastapp.vastai
data class ApiFailure(
/** user friendly error message */
val errorMessage: String?,
)

View file

@ -1,8 +0,0 @@
package eu.m724.vastapp.vastai
enum class ApiRoute(val path: String, val method: String) {
SHOW_USER("/users/current", "GET"),
GET_INSTANCES("/instances", "GET"),
INSTANCES_COUNT("/instances/count", "GET"),
MACHINES_MAINTENANCES("/machines/maintenances", "GET")
}

View file

@ -0,0 +1,55 @@
package eu.m724.vastapp.vastai
import eu.m724.vastapp.BuildConfig
import org.chromium.net.CronetEngine
import org.chromium.net.UploadDataProvider
import org.chromium.net.UrlRequest
import java.util.concurrent.Executor
class RequestMaker(
private var apiKey: String,
private val cronetEngine: CronetEngine,
private val executor: Executor
) {
fun setApiKey(apiKey: String) {
this.apiKey = apiKey
}
/**
* build an api request
* don't forget to call .start() on the returned [UrlRequest]
*
* @param endpoint the endpoint path starting with a slash like /users/current
* @param callback any callback for example [UserUrlRequestCallback]
* @param method request method, default GET
* @param headers additional request headers
* @param uploadDataProvider [UploadDataProvider] if request sends data
* @return an [UrlRequest] you must .start() yourself
*/
fun buildRequest(
endpoint: String,
callback: UrlRequest.Callback,
method: String = "GET",
headers: Map<String, String>? = null,
uploadDataProvider: UploadDataProvider? = null
): UrlRequest {
var requestBuilder = cronetEngine.newUrlRequestBuilder(
BuildConfig.VASIAI_API_ENDPOINT + endpoint,
callback,
executor
).addHeader("Authorization", "Bearer $apiKey")
requestBuilder = requestBuilder.setHttpMethod(method)
headers?.forEach { e ->
requestBuilder = requestBuilder.addHeader(e.key, e.value)
}
if (uploadDataProvider != null) {
requestBuilder = requestBuilder.setUploadDataProvider(uploadDataProvider, executor)
}
return requestBuilder.build()
}
}

View file

@ -1,57 +1,115 @@
package eu.m724.vastapp.vastai
import eu.m724.vastapp.BuildConfig
import eu.m724.vastapp.vastai.cronet.InstancesUrlRequestCallback
import eu.m724.vastapp.vastai.cronet.JsonUrlRequestCallback
import eu.m724.vastapp.vastai.cronet.UserUrlRequestCallback
import eu.m724.vastapp.vastai.cronet.upload.StringUploadDataProvider
import eu.m724.vastapp.vastai.data.RentedInstance
import eu.m724.vastapp.vastai.data.User
import eu.m724.vastapp.vastai.exceptions.ApiException
import kotlinx.coroutines.CompletableDeferred
import org.chromium.net.CronetEngine
import org.chromium.net.UploadDataProvider
import org.chromium.net.UrlRequest
import java.util.concurrent.Executor
class VastApi(
var apiKey: String, // TODO make private?
private val cronetEngine: CronetEngine,
private val executor: Executor
apiKey: String, // TODO make private?
cronetEngine: CronetEngine,
executor: Executor
) {
/**
* build an api request
* don't forget to call .start() on the returned [UrlRequest]
*
* @param endpoint the endpoint path starting with a slash like /users/current
* @param callback any callback for example [UserUrlRequestCallback]
* @param method request method, default GET
* @param uploadDataProvider [UploadDataProvider] if request sends data
* @return an [UrlRequest] you must .start() yourself
*/
fun buildRequest(
endpoint: String,
callback: UrlRequest.Callback,
method: String = "GET",
uploadDataProvider: UploadDataProvider?
): UrlRequest {
var requestBuilder = cronetEngine.newUrlRequestBuilder(
BuildConfig.VASIAI_API_ENDPOINT + endpoint,
callback,
executor
).addHeader("Authorization", "Bearer $apiKey")
private val requestMaker = RequestMaker(apiKey, cronetEngine, executor)
requestBuilder = requestBuilder.setHttpMethod(method)
if (uploadDataProvider != null) {
requestBuilder = requestBuilder.setUploadDataProvider(uploadDataProvider, executor)
fun setApiKey(apiKey: String) {
requestMaker.setApiKey(apiKey)
}
return requestBuilder.build()
fun getUser(): CompletableDeferred<User> {
val deferred = CompletableDeferred<User>()
val request = requestMaker.buildRequest(
"/users/current",
UserUrlRequestCallback(
onSuccess = { deferred.complete(it) },
onFailure = { deferred.completeExceptionally(it) }
)
)
request.start()
return deferred
}
/**
* build an api request
* don't forget to call .start() on the returned [UrlRequest]
*
* @param apiRoute the api route
* @param callback any callback for example [UserUrlRequestCallback]
* @param uploadDataProvider [UploadDataProvider] if request sends data
* @return an [UrlRequest] you must .start() yourself
*/
fun buildRequest(apiRoute: ApiRoute, callback: UrlRequest.Callback, uploadDataProvider: UploadDataProvider? = null): UrlRequest {
return buildRequest(apiRoute.path, callback, apiRoute.method, uploadDataProvider)
fun getRentedInstances(): CompletableDeferred<List<RentedInstance>> {
val deferred = CompletableDeferred<List<RentedInstance>>()
val request = requestMaker.buildRequest(
"/instances",
InstancesUrlRequestCallback(
onSuccess = { deferred.complete(it) },
onFailure = { deferred.completeExceptionally(it) }
)
)
request.start()
return deferred
} // TODO maybe we could make a function that handles all that build stuff and just takes a type and path
fun deleteInstance(rentalId: Int): CompletableDeferred<Unit> {
val deferred = CompletableDeferred<Unit>()
val request = requestMaker.buildRequest(
"/instances/$rentalId",
JsonUrlRequestCallback(
onSuccess = {
if (it.getBoolean("success"))
deferred.complete(Unit)
else
deferred.completeExceptionally(ApiException("Failed to delete: $it"))
},
onFailure = { deferred.completeExceptionally(it) }
),
"DELETE"
)
request.start()
return deferred
}
fun startInstance(rentalId: Int): CompletableDeferred<Unit> {
val deferred = CompletableDeferred<Unit>()
val request = requestMaker.buildRequest(
"/instances/$rentalId",
JsonUrlRequestCallback(
onSuccess = {
deferred.complete(Unit)
},
onFailure = { deferred.completeExceptionally(it) }
),
"PUT",
mapOf(Pair("Content-Type", "application/json")),
StringUploadDataProvider("{\"state\": \"running\"}")
)
request.start()
return deferred
}
fun stopInstance(rentalId: Int): CompletableDeferred<Unit> { // TODO this too make one function that does all things
val deferred = CompletableDeferred<Unit>()
val request = requestMaker.buildRequest(
"/instances/$rentalId",
JsonUrlRequestCallback(
onSuccess = {
deferred.complete(Unit)
},
onFailure = { deferred.completeExceptionally(it) }
),
"PUT",
mapOf(Pair("Content-Type", "application/json")),
StringUploadDataProvider("{\"state\": \"stopped\"}")
)
request.start()
return deferred
}
}

View file

@ -1,63 +0,0 @@
package eu.m724.vastapp.vastai.api
import eu.m724.vastapp.vastai.ApiFailure
import eu.m724.vastapp.vastai.data.RentedInstance
import org.chromium.net.CronetException
import org.chromium.net.UrlRequest
import org.chromium.net.UrlResponseInfo
import org.json.JSONObject
import java.nio.ByteBuffer
import java.nio.charset.CodingErrorAction
class InstancesUrlRequestCallback(
val onSuccess: (List<RentedInstance>) -> Unit,
val onFailure: (ApiFailure) -> Unit
) : UrlRequest.Callback() {
private val stringResponse = StringBuilder()
override fun onRedirectReceived(
request: UrlRequest?,
info: UrlResponseInfo?,
newLocationUrl: String?
) {
request?.followRedirect()
}
override fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo?) {
request?.read(ByteBuffer.allocateDirect(102400))
}
override fun onReadCompleted(
request: UrlRequest?,
info: UrlResponseInfo?,
byteBuffer: ByteBuffer?
) {
byteBuffer?.clear()
request?.read(byteBuffer)
stringResponse.append(Charsets.UTF_8.newDecoder().onUnmappableCharacter(CodingErrorAction.IGNORE).decode(byteBuffer))
}
override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) {
println(stringResponse) // TODO don't do that
if (info?.httpStatusCode == 200) {
val jsonResponse = JSONObject(stringResponse.toString())
val instances = ArrayList<RentedInstance>()
val instancesJson = jsonResponse.getJSONArray("instances")
for (i in 0..<instancesJson.length()) {
instances.add(RentedInstance.fromJson(instancesJson.getJSONObject(i)))
}
onSuccess(instances) // TODO handle json errors
} else {
onFailure(ApiFailure("${info?.httpStatusCode} ${info?.httpStatusText}"))
println("API error: $stringResponse")
}
}
override fun onFailed(request: UrlRequest?, info: UrlResponseInfo?, error: CronetException?) {
onFailure(ApiFailure("Network error: ${error?.message ?: "Unknown"}"))
}
}

View file

@ -1,16 +0,0 @@
package eu.m724.vastapp.vastai.api
import eu.m724.vastapp.vastai.ApiFailure
import org.json.JSONObject
class JsonUrlRequestCallback(
onSuccess: (JSONObject) -> Unit,
onFailure: (ApiFailure) -> Unit
) : StringUrlRequestCallback({ stringResponse ->
try {
val jsonResponse = JSONObject(stringResponse)
onSuccess(jsonResponse)
} catch (e: Exception) {
onFailure(ApiFailure(e.message))
}
}, onFailure)

View file

@ -1,72 +0,0 @@
package eu.m724.vastapp.vastai.api
import eu.m724.vastapp.vastai.ApiFailure
import eu.m724.vastapp.vastai.data.User
import org.chromium.net.CronetException
import org.chromium.net.UrlRequest
import org.chromium.net.UrlResponseInfo
import org.json.JSONObject
import java.nio.ByteBuffer
import java.nio.charset.CodingErrorAction
class UserUrlRequestCallback(
val onSuccess: (User) -> Unit,
val onFailure: (ApiFailure) -> Unit
) : UrlRequest.Callback() {
private val stringResponse = StringBuilder()
override fun onRedirectReceived(
request: UrlRequest?,
info: UrlResponseInfo?,
newLocationUrl: String?
) {
request?.followRedirect()
}
override fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo?) {
request?.read(ByteBuffer.allocateDirect(102400))
}
override fun onReadCompleted(
request: UrlRequest?,
info: UrlResponseInfo?,
byteBuffer: ByteBuffer?
) {
byteBuffer?.clear()
request?.read(byteBuffer)
stringResponse.append(Charsets.UTF_8.newDecoder().onUnmappableCharacter(CodingErrorAction.IGNORE).decode(byteBuffer))
}
override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) {
println(stringResponse) // TODO don't do that
if (info?.httpStatusCode == 200) {
try {
val jsonResponse = JSONObject(stringResponse.toString())
onSuccess(
User(
id = jsonResponse.getString("id"),
username = jsonResponse.getString("username"),
email = jsonResponse.getString("email"),
apiKey = jsonResponse.getString("api_key"),
credit = jsonResponse.getDouble("credit"),
balanceThreshold = jsonResponse.getDouble("balance_threshold"),
balanceThresholdEnabled = jsonResponse.getBoolean("balance_threshold_enabled"),
)
)
} catch (e: Exception) {
onFailure(ApiFailure(e.message))
println("API response error: $stringResponse")
}
} else {
onFailure(ApiFailure("${info?.httpStatusCode} ${info?.httpStatusText}"))
println("API error: $stringResponse")
}
}
override fun onFailed(request: UrlRequest?, info: UrlResponseInfo?, error: CronetException?) {
onFailure(ApiFailure("Network error: ${error?.message ?: "Unknown"}"))
}
}

View file

@ -0,0 +1,18 @@
package eu.m724.vastapp.vastai.cronet
import eu.m724.vastapp.vastai.exceptions.ApiException
import eu.m724.vastapp.vastai.data.RentedInstance
class InstancesUrlRequestCallback(
onSuccess: (List<RentedInstance>) -> Unit,
onFailure: (ApiException) -> Unit
) : JsonUrlRequestCallback({ json ->
val instances = ArrayList<RentedInstance>()
val instancesJson = json.getJSONArray("instances")
for (i in 0..<instancesJson.length()) {
instances.add(RentedInstance.fromJson(instancesJson.getJSONObject(i)))
}
onSuccess(instances)
}, onFailure)

View file

@ -0,0 +1,12 @@
package eu.m724.vastapp.vastai.cronet
import eu.m724.vastapp.vastai.exceptions.ApiException
import org.json.JSONObject
open class JsonUrlRequestCallback(
onSuccess: (JSONObject) -> Unit,
onFailure: (ApiException) -> Unit
) : StringUrlRequestCallback({ stringResponse ->
val jsonResponse = JSONObject(stringResponse) // errors are handled in the super class
onSuccess(jsonResponse)
}, onFailure)

View file

@ -1,6 +1,9 @@
package eu.m724.vastapp.vastai.api
package eu.m724.vastapp.vastai.cronet
import eu.m724.vastapp.vastai.ApiFailure
import eu.m724.vastapp.vastai.exceptions.ApiException
import eu.m724.vastapp.vastai.exceptions.ClientException
import eu.m724.vastapp.vastai.exceptions.ServerError
import eu.m724.vastapp.vastai.exceptions.UnauthorizedException
import org.chromium.net.CronetException
import org.chromium.net.UrlRequest
import org.chromium.net.UrlResponseInfo
@ -9,7 +12,7 @@ import java.nio.charset.CodingErrorAction
open class StringUrlRequestCallback(
val onSuccess: (String) -> Unit,
val onFailure: (ApiFailure) -> Unit
val onFailure: (ApiException) -> Unit
) : UrlRequest.Callback() {
protected val stringResponse = StringBuilder()
@ -37,15 +40,27 @@ open class StringUrlRequestCallback(
}
override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) {
if (info?.httpStatusCode == 200) {
onSuccess(stringResponse.toString())
if (info != null) {
val body = stringResponse.toString()
val statusCode = info.httpStatusCode
if (statusCode == 200) {
try {
onSuccess(body)
} catch (e: Exception) { // TODO maybe do it differently
onFailure(ClientException(body))
}
} else if (statusCode >= 500) {
onFailure(ServerError(statusCode, body))
} else if (statusCode == 403) {
onFailure(UnauthorizedException(body))
} else {
onFailure(ApiFailure("${info?.httpStatusCode} ${info?.httpStatusText}"))
println("API error: ${stringResponse.toString()}")
onFailure(ClientException(body))
}
}
}
override fun onFailed(request: UrlRequest?, info: UrlResponseInfo?, error: CronetException?) {
onFailure(ApiFailure("Network error: ${error?.message ?: "Unknown"}"))
onFailure(ApiException(error?.message, error))
}
}

View file

@ -0,0 +1,21 @@
package eu.m724.vastapp.vastai.cronet
import eu.m724.vastapp.vastai.exceptions.ApiException
import eu.m724.vastapp.vastai.data.User
class UserUrlRequestCallback(
onSuccess: (User) -> Unit,
onFailure: (ApiException) -> Unit
): JsonUrlRequestCallback({ json ->
onSuccess(
User( // TODO move that to a static function in User
id = json.getString("id"),
username = json.getString("username"),
email = json.getString("email"),
apiKey = json.getString("api_key"),
credit = json.getDouble("credit"),
balanceThreshold = json.getDouble("balance_threshold"),
balanceThresholdEnabled = json.getBoolean("balance_threshold_enabled"),
)
)
}, onFailure)

View file

@ -1,4 +1,4 @@
package eu.m724.vastapp.vastai.api.upload
package eu.m724.vastapp.vastai.cronet.upload
import org.json.JSONObject

View file

@ -1,4 +1,4 @@
package eu.m724.vastapp.vastai.api.upload
package eu.m724.vastapp.vastai.cronet.upload
import org.chromium.net.UploadDataProvider
import org.chromium.net.UploadDataSink

View file

@ -0,0 +1,6 @@
package eu.m724.vastapp.vastai.exceptions
open class ApiException(
override val message: String? = null,
override val cause: Throwable? = null
): Exception()

View file

@ -0,0 +1,6 @@
package eu.m724.vastapp.vastai.exceptions
open class ClientException(
message: String? = null,
cause: Throwable? = null
) : ApiException(message, cause)

View file

@ -0,0 +1,6 @@
package eu.m724.vastapp.vastai.exceptions
open class ServerError(
val statusCode: Int,
message: String?,
): ApiException(message, null)

View file

@ -0,0 +1,5 @@
package eu.m724.vastapp.vastai.exceptions
class UnauthorizedException(
message: String? = null
) : ClientException(message, null)