1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
package org.meshbay.client.notify
import android.app.job.JobParameters
import android.app.job.JobService
import android.content.Context
import android.util.Log
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import org.meshbay.client.bridge.Bridge
import org.meshbay.client.hub.HubClient
import java.util.concurrent.TimeUnit
/**
* One fetch of what is new (`POST /v1/push/poll`), page running or not.
*
* Authenticated by the row's poll secret, never a session: what this keeps for
* running in the background reads notification lines and nothing else. It goes
* to the hub the application is signed in to, and only there.
*/
class PollJob : JobService() {
@Volatile private var worker: Thread? = null
override fun onStartJob(params: JobParameters): Boolean {
worker = Thread {
try { fetch() } catch (e: Exception) { Log.w(Bridge.TAG, "poll failed: ${e.javaClass.simpleName}") }
jobFinished(params, false)
}.apply { start() }
return true
}
override fun onStopJob(params: JobParameters): Boolean {
worker?.interrupt()
return false
}
private fun fetch() {
val state = PushState(getSharedPreferences(PushState.PREFS, Context.MODE_PRIVATE))
val target = state.pollTarget() ?: return
val base = HubClient(getSharedPreferences("shell", Context.MODE_PRIVATE)).base
val url = base.toHttpUrlOrNull()?.newBuilder()?.encodedPath("/v1/push/poll")?.build() ?: return
val body = JSONObject().put("id", target.id).put("secret", target.secret).put("since", target.since)
.toString().toRequestBody("application/json".toMediaType())
val http = OkHttpClient.Builder().callTimeout(30, TimeUnit.SECONDS).followRedirects(false).build()
http.newCall(Request.Builder().url(url).post(body).build()).execute().use { r ->
when {
// The row is gone — signed out elsewhere, or deleted: the page
// registers again when it next runs, if push is still on.
r.code == 404 -> state.forgetSubscription()
r.isSuccessful -> {
val list = JSONObject(r.body.string()).optJSONArray("notifications") ?: return
for (i in 0 until list.length()) {
val raw = list.optJSONObject(i)?.toString()?.toByteArray() ?: continue
val shown = Notifier.parse(raw) ?: continue
Notifier.deliver(this, state, shown)
state.advance(shown.createdAt)
}
}
}
}
}
}
|