In Ktor how do I get the sessionId within a get() {}? - ktor

install(Sessions) {
header<MySession>("MY_SESSION", SessionStorageMemory())
}
get("/session/increment") {
val session = call.sessions.get<MySession>() ?: throw AuthorizationException()
call.sessions.set(session.copy(count = session.count + 1))
// insert code here to get sessionId ?
call.respondText("Counter is ${session.count}. Refresh to increment.")
}
I've been trying to get it out of the attributes but it seems the framework has made all those data structures private to prevent me from getting the sessionId and have no working solution yet.
val attributeKey = call.attributes.allKeys.map{
val x = it as AttributeKey<Any>
call.attributes.get(x)
}
// AttributeKey<SessionData>("SessionKey")
SessionData is private so I can't get access to data structure that holds sessionId

Related

onSensorChanged function is called in an other app but not in mine (Kotlin)

i want to implement a step counter in my app, so i search how to make that and i found lot of differents implementations.
I notably found an app on GitHub which works. I have tried to implement this code in my app and in an other "test" app but any of them works and i don't no why.
The problem is caused by the onSensorChanged function of my STEP_COUNTER which is not called.
I have search in all the files of the app and i don't found the problem.
If somebody have a solution...
(I'm french so sorry if it's badly written)
the code i use:
private var sensorManager: SensorManager? = null
// Creating a variable which will give the running status
// and initially given the boolean value as false
private var running = false
// Creating a variable which will counts total steps
// and it has been given the value of 0 float
private var totalSteps = 0f
// Creating a variable which counts previous total
// steps and it has also been given the value of 0 float
private var previousTotalSteps = 0f
//in the onCreate
loadData()
resetSteps()
// Adding a context of SENSOR_SERVICE as Sensor Manager
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
override fun onResume() {
super.onResume()
mainHandler.post(pingRunnable)
binding.map.onResume()
running = true
// Returns the number of steps taken by the user since the last reboot while activated
// This sensor requires permission android.permission.ACTIVITY_RECOGNITION.
// So don't forget to add the following permission in AndroidManifest.xml present in manifest folder of the app.
val stepSensor = sensorManager?.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
if (stepSensor == null) {
// This will give a toast message to the user if there is no sensor in the device
Toast.makeText(this, "No sensor detected on this device", Toast.LENGTH_SHORT).show()
} else {
// Rate suitable for the user interface
sensorManager?.registerListener(this, stepSensor, SensorManager.SENSOR_DELAY_UI)
}
}
override fun onSensorChanged(event: SensorEvent?) {
// Calling the TextView that we made in activity_main.xml
// by the id given to that TextView
var tvStepsTaken = findViewById<TextView>(R.id.step)
if (running) {
totalSteps = event!!.values[0]
// Current steps are calculated by taking the difference of total steps
// and previous steps
val currentSteps = totalSteps.toInt() - previousTotalSteps.toInt()
// It will show the current steps to the user
tvStepsTaken.text = ("$currentSteps")
}
}
private fun resetSteps() {
var resetButton = findViewById<Button>(R.id.reset)
resetButton.setOnClickListener {
// This will give a toast message if the user want to reset the steps
previousTotalSteps = totalSteps
// When the user will click long tap on the screen,
// the steps will be reset to 0
testFragment?.binding?.step?.text = 0.toString()
// This will save the data
saveData()
true
}
}
private fun saveData() {
// Shared Preferences will allow us to save
// and retrieve data in the form of key,value pair.
// In this function we will save data
val sharedPreferences = getSharedPreferences("myPrefs", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.putFloat("key1", previousTotalSteps)
editor.apply()
}
private fun loadData() {
// In this function we will retrieve data
val sharedPreferences = getSharedPreferences("myPrefs", Context.MODE_PRIVATE)
val savedNumber = sharedPreferences.getFloat("key1", 0f)
// Log.d is used for debugging purposes
Log.d("MainActivity", "$savedNumber")
previousTotalSteps = savedNumber
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// We do not have to write anything in this function for this app
}

Getting ID token from google sign-in

I am trying to use http4k's built in oauth module to implement Google sign-in in my backend app.
Here is what I have so far:
val googleClientId = "<GoogleClientID>"
val googleClientSecret = "<GoogleClientSecret>"
// this is a test implementation of the OAuthPersistence interface, which should be
// implemented by application developers
val oAuthPersistence = InsecureCookieBasedOAuthPersistence("Google")
// pre-defined configuration exist for common OAuth providers
val oauthProvider = OAuthProvider.google(
JavaHttpClient(),
Credentials(googleClientId, googleClientSecret),
Uri.of("http://localhost:9000/oauth/callback"),
oAuthPersistence
)
val app: HttpHandler = routes(
"/oauth" bind routes(
"/" bind GET to oauthProvider.authFilter.then {
val user = contextFn(it)
Response(OK).body("authenticated!")
},
"/callback" bind GET to oauthProvider.callback
)
app.asServer(SunHttp(9000)).start()
This lets me go to http://localhost:9000/oauth and I can sign-in to my google account. Cool!
However, after the redirect, I go to the following function contextFn, which looks like this atm:
val transport = NetHttpTransport()
val jsonFactory = GsonFactory.getDefaultInstance()
val verifier = GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(listOf(googleClientId))
.build()
fun contextFn(request: Request): Principal {
// TODO: get the id token somehow, but the request header only contains the following in cookie:
// - GoogleCsrf
// - GoogleAccessToken
// - GoogleOriginalUri
val idTokenString = ""
val idToken: GoogleIdToken = verifier.verify(idTokenString)
val payload: GoogleIdToken.Payload = idToken.payload
// Print user identifier
val userId: String = payload.subject
println("User ID: $userId")
// Get profile information from payload
val email: String = payload.email
val emailVerified: Boolean = payload.emailVerified
val name = payload["name"]
return GoogleUser(email)
}
How can i get the id token? Currently I am getting the access token from google.
Can you try to add this scope(openid)?
I am not sure listOf or addScope support in http4k
But it missed openid scope.
val oauthProvider = OAuthProvider.google(
JavaHttpClient(),
Credentials(googleClientId, googleClientSecret),
Uri.of("http://localhost:9000/oauth/callback"),
listOf("openidScope"),
oAuthPersistence
)
oauthProvider.addScope('openid');

Google Cloud Functions - Realtime Database Trigger - how to deserialize data JSON to POJO?

As described on the Google Cloud Functions docs, it is possible to trigger a Function based on Firebase Realtime Database events (write/create/update/delete).
The following docs sample explains how to get the delta snapshot.
public class FirebaseRtdb implements RawBackgroundFunction {
private static final Logger logger = Logger.getLogger(FirebaseRtdb.class.getName());
// Use GSON (https://github.com/google/gson) to parse JSON content.
private static final Gson gson = new Gson();
#Override
public void accept(String json, Context context) {
logger.info("Function triggered by change to: " + context.resource());
JsonObject body = gson.fromJson(json, JsonObject.class);
boolean isAdmin = false;
if (body != null && body.has("auth")) {
JsonObject authObj = body.getAsJsonObject("auth");
isAdmin = authObj.has("admin") && authObj.get("admin").getAsBoolean();
}
logger.info("Admin?: " + isAdmin);
if (body != null && body.has("delta")) {
logger.info("Delta:");
logger.info(body.get("delta").toString());
}
}
}
The sample works perfectly but the question is: How can I deserialize this delta to a POJO?
I tried:
val mObject = gson.fromJson(body.get("delta").toString(), MyCustomObject::class.java)
But I am getting:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT
As far as I know it is because MyObject class has a List<T> field, and Firebase Database always convert Lists to Maps with integer keys.
I preferably do not want to change every List<T> to Map<Int,T>, because I have a lot of classes :(
Thanks in advance!
So, here is what I ended up doing (maybe not the best solution!):
1) Create a custom Json Deserializer for Firebase-coming lists:
class ListFirebaseDeserializer<T> : JsonDeserializer<ArrayList<T>> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): ArrayList<T> {
val result = ArrayList<T>()
val typeOfElement = (typeOfT as ParameterizedType).actualTypeArguments[0]
json?.let {
json.asJsonObject.entrySet().forEach {
entry->
result.add(Gson().fromJson(entry.value, typeOfElement))
}
}
return result
}
}
This takes the lists that Firebase turned into maps and convert it back to actual lists.
2) Annotate every list in my POJO with #JsonAdapter(ListFirebaseDeserializer::class), for instance:
class MyCustomObject {
#JsonAdapter(ListFirebaseDeserializer::class)
var myPaymentList = ArrayList<Payment>()
}
It could be a pain if you already have lots of lists to annotate, but it is better than having to use maps instead.
Hope it helps!

Kotlin Wi-Fi loss/bad signal during FTP transfer

I'm nearly done creating an app that sends txt files, containing scanned data, to an ftp server.
The issue that I'm currently struggling with is: what if my Wi-Fi has terrible signal or no signal at all.
I noticed that my 'isOnline()' check works fine and that if there is no internet, it alerts the user. However a few hours ago I tested the app in the basement and noticed that when the Wi-Fi signal has no bars, it still sends the data but it gets lost somewhere along the way.
Currently the flow of the data is as follow:
user presses 'send'
check internet and if true, continue
clear content list and send
the content to viewmodel
viewmodel checks internet again before
creating the txt files
txt files get sent via FTP code below.
private fun sendTXT(result: String) {
try {
val name = "00_VER${LocalDateTime.now().format(fileNameFormatter)}.txt"
val path = getApplication<Application>().applicationContext.filesDir.path
.toString() + name
val f = File(path)
val isNewFileCreated: Boolean = f.createNewFile()
if (isNewFileCreated) {
f.writeText(result, Charsets.UTF_8)
}
val ftpClient = FTPClient()
ftpClient.addProtocolCommandListener(PrintCommandListener(PrintWriter(System.out)))
ftpClient.connect("xxx.xx.xxx.xx", 21)
val reply: Int = ftpClient.replyCode
if (!FTPReply.isPositiveCompletion(reply)) {
ftpClient.disconnect()
throw IOException("Exception in connecting to FTP Server")
}
if (ftpClient.login("username", "pass")) {
ftpClient.enterLocalPassiveMode()
ftpClient.setFileType(FTP.BINARY_FILE_TYPE)
val inp = FileInputStream(f)
var directory = "/files/input"
ftpClient.changeWorkingDirectory(directory)
val result = ftpClient.storeFile(name, inp)
inp.close()
if (result) {
ftpClient.logout()
ftpClient.disconnect()
f.delete()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
fun isOnline(context: Context): Boolean {
var result = false
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val networkCapabilities = connectivityManager.activeNetwork ?: return false
val actNw =
connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
result = when {
actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
else -> false
}
} else {
connectivityManager.run {
connectivityManager.activeNetworkInfo?.run {
result = when (type) {
ConnectivityManager.TYPE_WIFI -> true
ConnectivityManager.TYPE_MOBILE -> true
ConnectivityManager.TYPE_ETHERNET -> true
else -> false
}
}
}
}
return result
}
I'm stuck at finding a way to make sure the data gets to the server. Is there a more advanced way to check for internet connectivity?
I was considering adding all the scan objects as JSON to sharedpreferences, and if at the end of the day the user notices a scan didn't make it through, they can look up the missing scan and resend it.
However this seems very unconventional and I'm pretty sure there must be a better way to handle things.

Reactor lazy pagination with expand

Based on How to collect paginated API responses using spring boot WebClient?
I created the following crawler class
class GitlabCrawler(private val client: WebClient, private val token: String) {
fun fetchCommits(project: URI): Flux<Commit> {
return fetchCommitsInternal(project).expand { cr: ClientResponse? ->
val nextUrl = getNextUrl(cr)
nextUrl?.let { fetchCommitsInternal(URI.create(it)) }
?: Mono.empty<ClientResponse>()
}.limitRate(1)
.flatMap { cr: ClientResponse? -> cr?.bodyToFlux(Commit::class.java) ?: Flux.empty() }
}
private fun getNextUrl(cr: ClientResponse?):String? {
// TODO replace with proper link parsing
return cr?.headers()?.header(HttpHeaders.LINK)?.firstOrNull()
?.splitToSequence(",")
?.find { it.endsWith("rel=\"next\"") }
?.let { it.substring(it.indexOf('<') + 1, it.lastIndexOf('>')) }
}
private fun fetchCommitsInternal(url: URI): Mono<ClientResponse> {
return client.get()
.uri(url)
.accept(MediaType.APPLICATION_JSON_UTF8)
.header("Private-Token", token)
.exchange()
}
}
data class Commit(
val id: String,
val message: String,
#JsonProperty("parent_ids") val parentIds: List<String>,
#JsonProperty("created_at") val createdAt: String)
I'd like to avoid unnecessary request, but it performs more request than needed to fulfill the request.
gitlabCrawler.fetchCommits(URI.create("https://...")).take(15).collectList().block()
Would only need one request as each page contains 20 entries, but it starts the second page request. It seems to always request one more page than necessary. I tried using limitRate but that doesn't seem to have an effect.
Is there a way to make it lazy, i.e., only request the next page when the current is exhausted?
Are you positive that it actually performs the request? fetchCommitInternal being invoked means that WebFlux "prepared" the request, not necessarily that it was executed (ie. subscribed).
The following simplification of your use case shows the difference:
private static Tuple2<Integer, Flux<Integer>> nextPage(int index, int pageSize) {
System.out.println("prepared a request for page " + index);
return Tuples.of(index, Flux.range((pageSize * (index - 1)) + 1, pageSize));
}
#Test
public void expandLimitedRequest() {
int pageSize = 5;
Flux.just(nextPage(1, pageSize))
.doOnSubscribe(sub -> System.out.println("requested first page"))
.expand(page -> {
int currentPage = page.getT1();
if (currentPage < 3) {
int nextPage = currentPage + 1;
return Flux.just(nextPage(nextPage, pageSize))
.doOnSubscribe(sub -> System.out.println("requested page " + nextPage));
}
return Flux.empty();
})
.doOnNext(System.out::println)
.flatMap(Tuple2::getT2)
.doOnNext(System.out::println)
.take(8)
.blockLast();
}
Prints:
prepared a request for page 1
requested first page
[1,FluxRange]
1
2
3
4
5
prepared a request for page 2
requested page 2
[2,FluxRange]
6
7
8
prepared a request for page 3
As you can see, it prepares the request for page 3 but never execute it (because the take downstream cancels the expand before that).