Private
Public Access
1
0

Much better error/badconfig handling

This commit is contained in:
2024-03-23 19:01:20 -07:00
parent 611ad15997
commit f266e04895
7 changed files with 101 additions and 45 deletions

View File

@@ -5,6 +5,7 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import net.buzzert.kordophone.backend.db.CachedChatDatabase
import net.buzzert.kordophone.backend.server.APIClientFactory
import net.buzzert.kordophone.backend.server.Authentication
import net.buzzert.kordophone.backend.server.ChatRepository
import net.buzzert.kordophone.backend.server.RetrofitAPIClient
@@ -19,18 +20,10 @@ object AppModule {
@Provides
fun provideChatRepository(configRepository: ServerConfigRepository): ChatRepository {
val serverConfig = configRepository.serverConfig.value
val authentication = serverConfig.authentication?.let {
Authentication(it.username, it.password)
}
// TODO: This is really bad error handling...
val baseURL: URL = try { URL(serverConfig.serverName) }
catch (e: java.net.MalformedURLException) { URL("http://localhost") }
val client = RetrofitAPIClient(
baseURL = baseURL,
authentication = authentication ?: Authentication("", "")
)
val server = serverConfig.serverName
val authentication = serverConfig.authentication?.toBackendAuthentication()
val client = APIClientFactory.createClient(server, authentication)
val database = CachedChatDatabase.liveDatabase()
return ChatRepository(client, database)

View File

@@ -20,6 +20,8 @@ import net.buzzert.kordophone.backend.server.ChatRepository
import net.buzzert.kordophonedroid.ui.messagelist.MVM_LOG
import javax.inject.Inject
const val AVM_LOG: String = "AttachmentViewModel"
data class AttachmentFetchData(
val guid: String,
val preview: Boolean = false
@@ -72,7 +74,7 @@ private class AttachmentFetcher(
val data: AttachmentFetchData
): Fetcher {
override suspend fun fetch(): FetchResult {
Log.d(MVM_LOG, "Loading attachment ${data.guid} from network")
Log.d(AVM_LOG, "Loading attachment ${data.guid} from network")
val source = repository.fetchAttachmentDataSource(data.guid, data.preview)
return SourceResult(
source = ImageSource(source, context),

View File

@@ -10,10 +10,13 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.buzzert.kordophone.backend.model.Conversation
import net.buzzert.kordophone.backend.server.APIClientFactory
import net.buzzert.kordophone.backend.server.Authentication
import net.buzzert.kordophone.backend.server.ChatRepository
import net.buzzert.kordophone.backend.server.RetrofitAPIClient
import net.buzzert.kordophonedroid.ui.shared.ServerConfigRepository
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import java.net.URL
import javax.inject.Inject
@@ -37,21 +40,11 @@ class ConversationListViewModel @Inject constructor(
serverConfigRepository.serverConfig.collect { config ->
Log.d(CL_VM_LOG, "Got settings change.")
// Check settings
try {
val baseURL = URL(config.serverName)
val authentication = config.authentication?.let { serverAuth ->
Authentication(serverAuth.username, serverAuth.password)
} ?: throw Error("No authentication data.")
// Make new APIClient
val apiClient = RetrofitAPIClient(baseURL, authentication)
chatRepository.updateAPIClient(apiClient)
} catch (e: Error) {
Log.e(CL_VM_LOG, "Error re-creating API client for settings change: $e")
} catch (e: java.net.MalformedURLException) {
Log.e(CL_VM_LOG, "Malformed server URL")
}
// Make new APIClient
val baseURL = config.serverName
val authentication = config.authentication?.toBackendAuthentication()
val apiClient = APIClientFactory.createClient(baseURL, authentication)
chatRepository.updateAPIClient(apiClient)
// Perform db synchronization
withContext(Dispatchers.IO) {

View File

@@ -8,6 +8,7 @@ import androidx.security.crypto.MasterKey
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import net.buzzert.kordophone.backend.server.Authentication
import java.lang.reflect.Constructor
import javax.inject.Inject
import javax.inject.Singleton
@@ -84,6 +85,10 @@ data class ServerAuthentication(
apply()
}
}
fun toBackendAuthentication(): Authentication {
return Authentication(username, password)
}
}
@Singleton

View File

@@ -1,12 +1,16 @@
package net.buzzert.kordophone.backend.server
import kotlinx.coroutines.runBlocking
import net.buzzert.kordophone.backend.model.Conversation
import net.buzzert.kordophone.backend.model.GUID
import net.buzzert.kordophone.backend.model.Message
import okhttp3.Authenticator
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody
import okhttp3.Route
import okhttp3.WebSocket
import okhttp3.WebSocketListener
@@ -71,18 +75,71 @@ class TokenAuthenticator(
apiInterface.authenticate(request).body()
}
if (token == null) {
// Auth failure.
// TODO: How to bubble this up?
return null
when (token) {
null -> {
// Auth failure.
// TODO: How to bubble this up?
return null
}
// Update token store
else -> {
tokenStore.authenticationToken = token.serializedToken
return response.request().newBuilder()
.header("Authorization", "Bearer ${token.serializedToken}")
.build()
}
}
}
}
class APIClientFactory {
companion object {
fun createClient(serverString: String?, authentication: Authentication?): APIClient {
if (serverString == null || authentication == null) {
return InvalidConfigurationAPIClient()
}
// Try to parse server string
val serverURL = HttpUrl.parse(serverString) ?: return InvalidConfigurationAPIClient()
return RetrofitAPIClient(serverURL.url(), authentication)
}
}
}
// TODO: Is this a dumb idea?
class InvalidConfigurationAPIClient: APIClient {
private class InvalidConfigurationAPIInterface: APIInterface {
private fun throwError(): Nothing {
throw Error("Invalid Configuration.")
}
// Update token store
tokenStore.authenticationToken = token.serializedToken
override suspend fun getVersion(): ResponseBody = throwError()
override suspend fun getConversations(): retrofit2.Response<List<Conversation>> = throwError()
override suspend fun sendMessage(request: SendMessageRequest): retrofit2.Response<SendMessageResponse> = throwError()
override suspend fun markConversation(conversationGUID: String): retrofit2.Response<Void> = throwError()
override suspend fun fetchAttachment(guid: String, preview: Boolean): ResponseBody = throwError()
override suspend fun authenticate(request: AuthenticationRequest): retrofit2.Response<AuthenticationResponse> = throwError()
override suspend fun getMessages(
conversationGUID: String,
limit: Int?,
beforeMessageGUID: GUID?,
afterMessageGUID: GUID?
): retrofit2.Response<List<Message>> = throwError()
}
return response.request().newBuilder()
.header("Authorization", "Bearer ${token.serializedToken}")
.build()
override fun getAPIInterface(): APIInterface {
return InvalidConfigurationAPIInterface()
}
override fun getWebSocketClient(
serverPath: String,
queryParams: Map<String, String>?,
listener: WebSocketListener
): WebSocket {
throw Error("Invalid configuration.")
}
}

View File

@@ -41,9 +41,9 @@ class ChatRepository(
open val title: String = "Error"
open val description: String = "Generic Error"
data class ConnectionError(val exception: java.lang.Exception): Error() {
data class ConnectionError(val message: String?): Error() {
override val title: String = "Connection Error"
override val description: String = exception.message ?: "???"
override val description: String = message ?: "???"
}
}
@@ -172,7 +172,9 @@ class ChatRepository(
synchronizeConversation(conversation)
}
} catch (e: java.lang.Exception) {
_errorEncounteredChannel.emit(Error.ConnectionError(e))
_errorEncounteredChannel.emit(Error.ConnectionError(e.message))
} catch (e: java.lang.Error) {
_errorEncounteredChannel.emit(Error.ConnectionError(e.message))
}
suspend fun synchronizeConversation(conversation: Conversation, limit: Int = 15) = try {
@@ -181,7 +183,7 @@ class ChatRepository(
val messages = fetchMessages(conversation, limit = limit)
database.writeMessages(messages, conversation)
} catch (e: java.lang.Exception) {
_errorEncounteredChannel.emit(Error.ConnectionError(e))
_errorEncounteredChannel.emit(Error.ConnectionError(e.message))
}
suspend fun markConversationAsRead(conversation: Conversation) = try {

View File

@@ -41,11 +41,15 @@ class UpdateMonitor(private val client: APIClient) : WebSocketListener() {
fun beginMonitoringUpdates() {
Log.d(UPMON_LOG, "Opening websocket connection")
this.webSocket = client.getWebSocketClient(
serverPath = "updates",
queryParams = mapOf("seq" to messageSeq.toString()),
listener = this
)
try {
this.webSocket = client.getWebSocketClient(
serverPath = "updates",
queryParams = mapOf("seq" to messageSeq.toString()),
listener = this
)
} catch (e: Error) {
Log.e(UPMON_LOG, "Error getting websocket client: ${e.message}")
}
}
fun stopMonitoringForUpdates() {