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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
package com.pitchedapps.frost.services
import android.app.job.JobParameters
import android.app.job.JobService
import android.content.Context
import android.support.v4.app.NotificationManagerCompat
import ca.allanwang.kau.kotlin.firstOrNull
import ca.allanwang.kau.utils.string
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
import com.pitchedapps.frost.facebook.requests.httpClient
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.ReleasePrefs
import com.pitchedapps.frost.utils.frostEvent
import okhttp3.Request
import org.jetbrains.anko.doAsync
import org.joda.time.DateTime
import java.util.concurrent.Future
/**
* Created by Allan Wang on 07/04/18.
*/
data class FrostRelease(val versionName: String,
val timestamp: Long,
val apk: FrostApkRelease? = null,
val category: String = "")
data class FrostApkRelease(val size: Long,
val name: String,
val url: String,
val timestamp: Long,
val downloadCount: Long = -1)
object UpdateManager {
internal fun getLatestGithubRelease(): FrostRelease? {
try {
val data = getGithubReleaseJsonV3() ?: return null
return parseGithubReleaseV3(data)
} catch (e: Exception) {
L.e(e) {
"Failed to get github release"
}
return null
}
}
private fun JsonNode.asMillis(): Long = DateTime(asText()).millis
private fun getGithubReleaseJsonV3(): JsonNode? {
val mapper = ObjectMapper()
val response = httpClient.newCall(Request.Builder()
.url("https://api.github.com/repos/AllanWang/Frost-for-Facebook/releases/latest")
.get().build()).execute().body()?.string() ?: return null
return mapper.readTree(response)
}
private fun parseGithubReleaseV3(data: JsonNode): FrostRelease? {
val versionName = data.get("tag_name").asText()
if (versionName.isEmpty()) return null
val release = FrostRelease(
versionName = versionName,
timestamp = data.get("created_at").asMillis(),
category = "Github")
val assets = data.get("assets")
if (!assets.isArray) return release
val apkRelease = assets.elements().firstOrNull {
it.get("content_type").asText().contains("android")
} ?: return release
val apk = FrostApkRelease(size = apkRelease.get("size").asLong(),
name = apkRelease.get("name").asText(),
url = apkRelease.get("browser_download_url").asText(),
timestamp = apkRelease.get("updated_at").asMillis(),
downloadCount = apkRelease.get("download_count").asLong())
return release.copy(apk = apk)
}
}
class UpdateService : JobService() {
private var future: Future<Unit>? = null
private val startTime = System.currentTimeMillis()
override fun onStopJob(params: JobParameters?): Boolean {
val time = System.currentTimeMillis() - startTime
L.d { "Update service has finished abruptly in $time ms" }
frostEvent("UpdateTime",
"Type" to "Service force stop",
"Duration" to time)
future?.cancel(true)
future = null
return false
}
fun finish(params: JobParameters?) {
val time = System.currentTimeMillis() - startTime
L.i { "Update service has finished in $time ms" }
frostEvent("UpdateTime",
"Type" to "Service",
"Duration" to time)
jobFinished(params, false)
future?.cancel(true)
future = null
}
override fun onStartJob(params: JobParameters?): Boolean {
// L.i { "Fetching update" }
// future = doAsync {
// fetch()
// finish(params)
// }
// return true
return false
}
private fun fetch() {
val release = UpdateManager.getLatestGithubRelease() ?: return
val timestamp = release.apk?.timestamp ?: return
if (ReleasePrefs.lastTimeStamp >= timestamp) return
ReleasePrefs.lastTimeStamp = timestamp
if (BuildConfig.VERSION_NAME.contains(release.apk.name)) return
updateNotification(release)
}
private fun updateNotification(release: FrostRelease) {
val notifBuilder = frostNotification(NOTIF_CHANNEL_UPDATES)
.setFrostAlert(true, Prefs.notificationRingtone)
.setContentTitle(string(R.string.frost_name))
.setContentText(string(R.string.update_notif_message))
NotificationManagerCompat.from(this).notify(release.versionName.hashCode(), notifBuilder.build())
}
}
const val UPDATE_PERIODIC_JOB = 7
fun Context.scheduleUpdater(enable: Boolean): Boolean =
scheduleJob<UpdateService>(UPDATE_PERIODIC_JOB, if (enable) 1440 else -1)
const val UPDATE_JOB_NOW = 6
fun Context.fetchUpdates(): Boolean =
fetchJob<UpdateService>(UPDATE_JOB_NOW)
|