Much better error/badconfig handling
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user