aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/notify/PollJob.kt
blob: 0998d00345d7fd8bd83cea602b3db1f4aa2226f3 (plain) (blame)
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)
                    }
                }
            }
        }
    }
}