[WIP] linked client and server system with the UI
This commit is contained in:
parent
c9334c543b
commit
bc8dd0f859
12 changed files with 202 additions and 86 deletions
|
@ -1,4 +1,3 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
<!-- Applications -->
|
||||
|
||||
<!-- NOTE: usesCleartextTraffic is enabled because of the API system using simple HTTP -->
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
|
@ -38,6 +39,7 @@
|
|||
android:supportsRtl="true"
|
||||
tools:ignore="RtlEnabled"
|
||||
android:theme="@style/Theme.Tasksvalider"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
|
|
@ -1,49 +1,100 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task
|
||||
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import java.net.InetAddress
|
||||
import java.net.URL
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
|
||||
|
||||
/**
|
||||
* A client to handle the room connection.
|
||||
* @param address the address of the server
|
||||
* @param port the port of the server
|
||||
* @param baseCookies list of cookies to use (optional)
|
||||
*/
|
||||
class TaskClient(
|
||||
private val address: InetAddress,
|
||||
private val port: Int
|
||||
) : Thread() {
|
||||
private val client = OkHttpClient()
|
||||
private val jsonParser = Gson()
|
||||
private val address: String,
|
||||
private val port: Int,
|
||||
private val baseCookies: List<okhttp3.Cookie> = listOf()
|
||||
) {
|
||||
private val baseUrl = "http://$address:$port"
|
||||
private val client = OkHttpClient().newBuilder().cookieJar(
|
||||
// TODO(Faraphel): should be moved into another object
|
||||
object : okhttp3.CookieJar {
|
||||
private val cookies = baseCookies.toMutableList() ///< list of cookies
|
||||
|
||||
constructor(address: String, port: Int) : this(InetAddress.getByName(address), port)
|
||||
|
||||
override fun run() {
|
||||
Log.i("room-client", "started !")
|
||||
|
||||
// send a request to the server
|
||||
val request = okhttp3.Request.Builder()
|
||||
.url(URL("http://${address.hostAddress}:$port"))
|
||||
.build()
|
||||
|
||||
// get the response
|
||||
val response = client.newCall(request).execute()
|
||||
|
||||
// check if the response is successful
|
||||
if (!response.isSuccessful) {
|
||||
Log.e("room-client", "could not connect to the server")
|
||||
return
|
||||
override fun loadForRequest(url: HttpUrl): List<okhttp3.Cookie> {
|
||||
return this.cookies
|
||||
}
|
||||
override fun saveFromResponse(url: HttpUrl, cookies: List<okhttp3.Cookie>) {
|
||||
this.cookies.addAll(cookies)
|
||||
}
|
||||
}
|
||||
Log.i("room-client", "connected to the server")
|
||||
).build()
|
||||
|
||||
// parse the response
|
||||
val body = response.body.string()
|
||||
val data = jsonParser.fromJson(body, Map::class.java)
|
||||
// TODO(Faraphel): automatically convert content to the correct type ?
|
||||
|
||||
// print the data
|
||||
data.forEach { (key, value) -> Log.d("room-client", "$key: $value") }
|
||||
}
|
||||
/**
|
||||
* Return a basic request to the server
|
||||
* @param endpoint the endpoint of the server
|
||||
*/
|
||||
private fun baseRequestBuilder(endpoint: String): okhttp3.Request.Builder =
|
||||
okhttp3.Request.Builder().url("$baseUrl/$endpoint")
|
||||
|
||||
/**
|
||||
* Run a HEAD request
|
||||
* @param endpoint the endpoint of the server
|
||||
*/
|
||||
fun head(endpoint: String): okhttp3.Request =
|
||||
this.baseRequestBuilder(endpoint).head().build()
|
||||
|
||||
/**
|
||||
* Run a GET request
|
||||
* @param endpoint the endpoint of the server
|
||||
*/
|
||||
fun get(endpoint: String): okhttp3.Response =
|
||||
this.client.newCall(
|
||||
this.baseRequestBuilder(endpoint)
|
||||
.get()
|
||||
.build()
|
||||
).execute()
|
||||
|
||||
/**
|
||||
* Run a POST request
|
||||
* @param endpoint the endpoint of the server
|
||||
* @param content the content of the request
|
||||
* @param type the type of the content
|
||||
*/
|
||||
fun post(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
|
||||
this.client.newCall(
|
||||
this.baseRequestBuilder(endpoint)
|
||||
.post(content.toRequestBody(type.toMediaType()))
|
||||
.build()
|
||||
).execute()
|
||||
|
||||
/**
|
||||
* Run a PATCH request
|
||||
* @param endpoint the endpoint of the server
|
||||
* @param content the content of the request
|
||||
* @param type the type of the content
|
||||
*/
|
||||
fun patch(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
|
||||
this.client.newCall(
|
||||
this.baseRequestBuilder(endpoint)
|
||||
.patch(content.toRequestBody(type.toMediaType()))
|
||||
.build()
|
||||
).execute()
|
||||
|
||||
/**
|
||||
* Run a DELETE request
|
||||
* @param endpoint the endpoint of the server
|
||||
* @param content the content of the request
|
||||
* @param type the type of the content
|
||||
*/
|
||||
fun delete(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
|
||||
this.client.newCall(
|
||||
this.baseRequestBuilder(endpoint)
|
||||
.delete(content.toRequestBody(type.toMediaType()))
|
||||
.build()
|
||||
).execute()
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task
|
||||
|
||||
import com.faraphel.tasks_valider.connectivity.task.api.TaskSessionManagerApi
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskRole
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.database.api.TaskDatabaseApi
|
||||
|
@ -16,11 +18,36 @@ class TaskServer(
|
|||
private val port: Int,
|
||||
private val database: TaskDatabase
|
||||
) : NanoHTTPD(port) {
|
||||
companion object {
|
||||
private val TASK_SESSION_ADMIN = TaskSession( ///< the admin default session
|
||||
role = TaskRole.ADMIN
|
||||
)
|
||||
}
|
||||
|
||||
private val sessionManager = TaskSessionManager() ///< the session manager
|
||||
private val adminSessionId = this.sessionManager.newSessionData(TASK_SESSION_ADMIN) ///< default admin session id
|
||||
|
||||
private val sessionManagerApi = TaskSessionManagerApi(this.sessionManager) ///< the api of the session manager
|
||||
private val databaseApi = TaskDatabaseApi(this.database) ///< the api of the database
|
||||
|
||||
/**
|
||||
* Return a new client that can be used by the admin
|
||||
*/
|
||||
fun getClientAdmin(): TaskClient {
|
||||
// create the session cookie for the admin
|
||||
val cookieSession = okhttp3.Cookie.Builder()
|
||||
.domain("localhost")
|
||||
.name("sessionId")
|
||||
.value(adminSessionId)
|
||||
.build()
|
||||
// create a new client
|
||||
return TaskClient(
|
||||
"localhost",
|
||||
this.port,
|
||||
listOf(cookieSession)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an API request
|
||||
* @param httpSession the http session
|
||||
|
@ -30,7 +57,7 @@ class TaskServer(
|
|||
val taskSession = this.sessionManager.getOrCreateSessionData(httpSession)
|
||||
|
||||
// parse the url
|
||||
val uri: String = httpSession.uri.substring(1) // remove the first slash
|
||||
val uri: String = httpSession.uri.trim('/')
|
||||
val path = uri.split("/").toMutableList()
|
||||
|
||||
// get the type of the request from the uri
|
||||
|
|
|
@ -6,6 +6,7 @@ import androidx.room.PrimaryKey
|
|||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||
|
||||
|
||||
// TODO(Faraphel): should be renamed to TeamEntity
|
||||
@Entity(tableName = "groups")
|
||||
data class GroupEntity (
|
||||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.communication.internet.client
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Button
|
||||
|
@ -19,11 +20,11 @@ import com.faraphel.tasks_valider.ui.screen.task.TaskGroupScreen
|
|||
|
||||
|
||||
@Composable
|
||||
fun CommunicationInternetClientScreen() {
|
||||
fun CommunicationInternetClientScreen(activity: Activity) {
|
||||
val client = remember { mutableStateOf<TaskClient?>(null) }
|
||||
|
||||
if (client.value == null) CommunicationInternetClientContent(client)
|
||||
else TaskGroupScreen()
|
||||
else TaskGroupScreen(activity, client.value!!)
|
||||
}
|
||||
|
||||
|
||||
|
@ -56,7 +57,6 @@ fun CommunicationInternetClientContent(client: MutableState<TaskClient?>) {
|
|||
Button(onClick = {
|
||||
// TODO(Faraphel): check if the server is reachable
|
||||
client.value = TaskClient(serverAddress.value, serverPort.intValue)
|
||||
client.value!!.start()
|
||||
}) {
|
||||
Text("Connect")
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import androidx.compose.material3.Button
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.activity
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
|
@ -19,15 +18,9 @@ fun CommunicationInternetScreen(activity: Activity) {
|
|||
val controller = rememberNavController()
|
||||
|
||||
NavHost(navController = controller, startDestination = "mode") {
|
||||
composable("mode") {
|
||||
CommunicationInternetSelectContent(controller)
|
||||
}
|
||||
composable("client") {
|
||||
CommunicationInternetClientScreen()
|
||||
}
|
||||
composable("server") {
|
||||
CommunicationInternetServerScreen(activity)
|
||||
}
|
||||
composable("mode") { CommunicationInternetSelectContent(controller) }
|
||||
composable("client") { CommunicationInternetClientScreen(activity) }
|
||||
composable("server") { CommunicationInternetServerScreen(activity) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,12 +29,8 @@ fun CommunicationInternetScreen(activity: Activity) {
|
|||
fun CommunicationInternetSelectContent(controller: NavController) {
|
||||
Column {
|
||||
// client mode
|
||||
Button(onClick = { controller.navigate("client") }) {
|
||||
Text("Client")
|
||||
}
|
||||
Button(onClick = { controller.navigate("client") }) { Text("Client") }
|
||||
// server mode
|
||||
Button(onClick = { controller.navigate("server") }) {
|
||||
Text("Server")
|
||||
}
|
||||
Button(onClick = { controller.navigate("server") }) { Text("Server") }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.room.Room
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskClient
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskServer
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT
|
||||
|
@ -25,17 +26,17 @@ import com.faraphel.tasks_valider.ui.screen.task.TaskGroupScreen
|
|||
|
||||
@Composable
|
||||
fun CommunicationInternetServerScreen(activity: Activity) {
|
||||
val server = remember { mutableStateOf<TaskServer?>(null)}
|
||||
val client = remember { mutableStateOf<TaskClient?>(null) }
|
||||
|
||||
// if the server is not created, prompt the user for the server configuration
|
||||
if (server.value == null) CommunicationInternetServerContent(activity, server)
|
||||
if (client.value == null) CommunicationInternetServerContent(activity, client)
|
||||
// else, go to the base tasks screen
|
||||
else TaskGroupScreen()
|
||||
else TaskGroupScreen(activity, client.value!!)
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationInternetServerContent(activity: Activity, server: MutableState<TaskServer?>) {
|
||||
fun CommunicationInternetServerContent(activity: Activity, client: MutableState<TaskClient?>) {
|
||||
val expandedStudentList = remember { mutableStateOf(false) }
|
||||
val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) }
|
||||
|
||||
|
@ -84,8 +85,13 @@ fun CommunicationInternetServerContent(activity: Activity, server: MutableState<
|
|||
|
||||
// Create the server
|
||||
Log.i("room-server", "creating the server")
|
||||
server.value = TaskServer(serverPort.intValue, database)
|
||||
server.value!!.start()
|
||||
Thread { // a thread is used for networking
|
||||
val server = TaskServer(serverPort.intValue, database)
|
||||
server.start()
|
||||
|
||||
// Get the client from the server
|
||||
client.value = server.getClientAdmin()
|
||||
}.start()
|
||||
}) {
|
||||
Text("Create")
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.client
|
||||
|
||||
import android.app.Activity
|
||||
import android.net.wifi.p2p.WifiP2pConfig
|
||||
import android.net.wifi.p2p.WifiP2pDevice
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
@ -8,18 +9,18 @@ import androidx.compose.runtime.MutableState
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
|
||||
import com.faraphel.tasks_valider.ui.screen.task.TaskGroupScreen
|
||||
import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationWifiP2pClientScreen(bwfManager: BwfManager) {
|
||||
fun CommunicationWifiP2pClientScreen(activity: Activity, bwfManager: BwfManager) {
|
||||
val selectedDevice = remember { mutableStateOf<WifiP2pDevice?>(null) }
|
||||
val isConnected = remember { mutableStateOf(false) }
|
||||
|
||||
// if connected, show the task group screen
|
||||
if (isConnected.value) {
|
||||
TaskGroupScreen()
|
||||
// TaskGroupScreen(activity, null)
|
||||
// TODO(Faraphel): finish the connection
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -19,15 +19,9 @@ fun CommunicationWifiP2pScreen(activity: Activity, bwfManager: BwfManager) {
|
|||
val controller = rememberNavController()
|
||||
|
||||
NavHost(navController = controller, startDestination = "mode") {
|
||||
composable("mode") {
|
||||
CommunicationWifiP2pSelectContent(controller)
|
||||
}
|
||||
composable("client") {
|
||||
CommunicationWifiP2pClientScreen(bwfManager)
|
||||
}
|
||||
composable("server") {
|
||||
CommunicationWifiP2pServerScreen(activity, bwfManager)
|
||||
}
|
||||
composable("mode") { CommunicationWifiP2pSelectContent(controller) }
|
||||
composable("client") { CommunicationWifiP2pClientScreen(activity, bwfManager) }
|
||||
composable("server") { CommunicationWifiP2pServerScreen(activity, bwfManager) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,12 +30,8 @@ fun CommunicationWifiP2pScreen(activity: Activity, bwfManager: BwfManager) {
|
|||
fun CommunicationWifiP2pSelectContent(controller: NavController) {
|
||||
Column {
|
||||
// client mode
|
||||
Button(onClick = { controller.navigate("client") }) {
|
||||
Text("Client")
|
||||
}
|
||||
Button(onClick = { controller.navigate("client") }) { Text("Client") }
|
||||
// server mode
|
||||
Button(onClick = { controller.navigate("server") }) {
|
||||
Text("Server")
|
||||
}
|
||||
Button(onClick = { controller.navigate("server") }) { Text("Server") }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.server
|
||||
|
||||
import android.app.Activity
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Button
|
||||
|
@ -16,6 +17,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.room.Room
|
||||
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskClient
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskServer
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT
|
||||
|
@ -25,12 +27,12 @@ import com.faraphel.tasks_valider.ui.screen.task.TaskGroupScreen
|
|||
|
||||
@Composable
|
||||
fun CommunicationWifiP2pServerScreen(activity: Activity, bwfManager: BwfManager) {
|
||||
val server = remember { mutableStateOf<TaskServer?>(null)}
|
||||
val client = remember { mutableStateOf<TaskClient?>(null) }
|
||||
|
||||
// if the server is not created, prompt the user for the server configuration
|
||||
if (server.value == null) CommunicationWifiP2pServerContent(activity, bwfManager, server)
|
||||
if (client.value == null) CommunicationWifiP2pServerContent(activity, bwfManager, client)
|
||||
// else, go to the base tasks screen
|
||||
else TaskGroupScreen()
|
||||
else TaskGroupScreen(activity, client.value!!)
|
||||
}
|
||||
|
||||
|
||||
|
@ -38,7 +40,7 @@ fun CommunicationWifiP2pServerScreen(activity: Activity, bwfManager: BwfManager)
|
|||
fun CommunicationWifiP2pServerContent(
|
||||
activity: Activity,
|
||||
bwfManager: BwfManager,
|
||||
server: MutableState<TaskServer?>
|
||||
client: MutableState<TaskClient?>
|
||||
) {
|
||||
val expandedStudentList = remember { mutableStateOf(false) }
|
||||
val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) }
|
||||
|
@ -90,8 +92,12 @@ fun CommunicationWifiP2pServerContent(
|
|||
|
||||
bwfManager.recreateGroup {
|
||||
// Create the server
|
||||
server.value = TaskServer(serverPort.intValue, database)
|
||||
server.value!!.start()
|
||||
Log.i("room-server", "creating the server")
|
||||
val server = TaskServer(serverPort.intValue, database)
|
||||
server.start()
|
||||
|
||||
// Get the client from the server
|
||||
client.value = server.getClientAdmin()
|
||||
}
|
||||
}) {
|
||||
Text("Create")
|
||||
|
|
|
@ -1,14 +1,58 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.task
|
||||
|
||||
import android.app.Activity
|
||||
import android.widget.Toast
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskClient
|
||||
import com.faraphel.tasks_valider.database.entities.TaskGroupEntity
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
|
||||
|
||||
val jsonParser = Gson()
|
||||
|
||||
|
||||
/**
|
||||
* This screen let the user decide which student team he wants to interact with
|
||||
* @param client an HTTP client that can communicate with the server
|
||||
*/
|
||||
@Composable
|
||||
fun TaskGroupScreen() {
|
||||
// TODO(Faraphel): should handle connexion with the server
|
||||
fun TaskGroupScreen(activity: Activity, client: TaskClient) {
|
||||
val groups = remember { mutableStateOf<List<TaskGroupEntity>?>(null) }
|
||||
|
||||
// title
|
||||
Text(text = "Task Group")
|
||||
|
||||
// if the groups are not yet defined, refresh the list
|
||||
if (groups.value == null) {
|
||||
Thread { refreshGroups(activity, client, groups) }.start()
|
||||
return
|
||||
}
|
||||
|
||||
// if the groups have already been defined, display them
|
||||
for (group in groups.value!!) {
|
||||
Text(text = group.toString())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun refreshGroups(activity: Activity, client: TaskClient, groups: MutableState<List<TaskGroupEntity>?>) {
|
||||
// try to obtain the list of groups
|
||||
val response = client.get("entities/group")
|
||||
|
||||
// in case of error, notify it
|
||||
if (!response.isSuccessful) {
|
||||
Toast.makeText(activity, response.message, Toast.LENGTH_LONG).show()
|
||||
return
|
||||
}
|
||||
|
||||
// parse the list of groups
|
||||
groups.value = jsonParser.fromJson(
|
||||
response.body.toString(),
|
||||
object : TypeToken<List<TaskGroupEntity>>(){}
|
||||
)
|
||||
}
|
Loading…
Reference in a new issue