ref: b7294b4ca96bb74b5e88d9dd14a0df8c05c8d3a7
app/src/main/java/ml/adamsprogs/bimba/TimetableDownloader.kt
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 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
package ml.adamsprogs.bimba import android.annotation.TargetApi import android.app.IntentService import android.app.Notification import android.content.Context import android.content.Intent import android.support.v4.app.NotificationCompat import java.net.HttpURLConnection import java.net.URL import org.tukaani.xz.XZInputStream import java.io.* import java.security.MessageDigest import kotlin.experimental.and import android.app.NotificationManager import android.os.Build import ml.adamsprogs.bimba.models.Timetable class TimetableDownloader : IntentService("TimetableDownloader") { companion object { val ACTION_DOWNLOADED = "ml.adamsprogs.bimba.timetableDownloaded" val EXTRA_FORCE = "force" val EXTRA_RESULT = "result" val RESULT_NO_CONNECTIVITY = "no connectivity" val RESULT_VERSION_MISMATCH = "version mismatch" val RESULT_UP_TO_DATE = "up-to-date" val RESULT_DOWNLOADED = "downloaded" val RESULT_VALIDITY_FAILED = "validity failed" } private lateinit var notificationManager: NotificationManager private var size: Int = 0 override fun onHandleIntent(intent: Intent?) { if (intent != null) { notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val prefs = this.getSharedPreferences("ml.adamsprogs.bimba.prefs", Context.MODE_PRIVATE)!! if (!NetworkStateReceiver.isNetworkAvailable(this)) { sendResult(RESULT_NO_CONNECTIVITY) return } val metadataUrl = URL("https://adamsprogs.ml/w/_media/programmes/bimba/timetable.db.meta") var httpCon = metadataUrl.openConnection() as HttpURLConnection if (httpCon.responseCode != HttpURLConnection.HTTP_OK) { sendResult(RESULT_NO_CONNECTIVITY) return } val reader = BufferedReader(InputStreamReader(httpCon.inputStream)) val lastModified = reader.readLine() val checksum = reader.readLine() size = Integer.parseInt(reader.readLine()) / 1024 val dbVersion = reader.readLine() if (Integer.parseInt(dbVersion.split(".")[0]) > Timetable.version) { sendResult(RESULT_VERSION_MISMATCH) return } val dbFilename = reader.readLine() val currentLastModified = prefs.getString("timetableLastModified", "19791012") if (lastModified <= currentLastModified && !intent.getBooleanExtra(EXTRA_FORCE, false)) { sendResult(RESULT_UP_TO_DATE) return } notify(0) val xzDbUrl = URL("https://adamsprogs.ml/w/_media/programmes/bimba/$dbFilename") httpCon = xzDbUrl.openConnection() as HttpURLConnection if (httpCon.responseCode != HttpURLConnection.HTTP_OK) { sendResult(RESULT_NO_CONNECTIVITY) return } val xzIn = XZInputStream(httpCon.inputStream) val file = File(this.filesDir, "new_timetable.db") if (copyInputStreamToFile(xzIn, file, checksum)) { val oldFile = File(this.filesDir, "timetable.db") oldFile.delete() file.renameTo(oldFile) val prefsEditor = prefs.edit() prefsEditor.putString("timetableLastModified", lastModified) prefsEditor.apply() sendResult(RESULT_DOWNLOADED) } else { sendResult(RESULT_VALIDITY_FAILED) } cancelNotification() } } private fun sendResult(result: String) { val broadcastIntent = Intent() broadcastIntent.action = ACTION_DOWNLOADED broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT) broadcastIntent.putExtra(EXTRA_RESULT, result) sendBroadcast(broadcastIntent) } private fun notify(progress: Int) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) notifyCompat(progress) else notifyStandard(progress) } @Suppress("DEPRECATION") private fun notifyCompat(progress: Int) { val builder = NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_download) .setContentTitle(getString(R.string.timetable_downloading)) .setContentText("$progress KiB/$size KiB") .setCategory(NotificationCompat.CATEGORY_PROGRESS) .setOngoing(true) .setProgress(size, progress, false) notificationManager.notify(42, builder.build()) } @TargetApi(Build.VERSION_CODES.O) private fun notifyStandard(progress: Int) { NotificationChannels.makeChannel(NotificationChannels.CHANNEL_UPDATES, "Updates", notificationManager) val builder = Notification.Builder(this, NotificationChannels.CHANNEL_UPDATES) .setSmallIcon(R.drawable.ic_download) .setContentTitle(getString(R.string.timetable_downloading)) .setContentText("$progress KiB/$size KiB") .setCategory(Notification.CATEGORY_PROGRESS) .setOngoing(true) .setProgress(size, progress, false) notificationManager.notify(42, builder.build()) } private fun cancelNotification() { notificationManager.cancel(42) } private fun copyInputStreamToFile(ins: InputStream, file: File, checksum: String): Boolean { val md = MessageDigest.getInstance("SHA-512") var hex = "" try { val out = FileOutputStream(file) val buf = ByteArray(5 * 1024) var lenSum = 0.0f var len = 42 while (len > 0) { len = ins.read(buf) if (len <= 0) break md.update(buf, 0, len) out.write(buf, 0, len) lenSum += len.toFloat() / 1024.0f notify(lenSum.toInt()) } out.close() } catch (e: Exception) { e.printStackTrace() } finally { ins.close() val digest = md.digest() for (i in 0 until digest.size) { hex += Integer.toString((digest[i] and 0xff.toByte()) + 0x100, 16).padStart(3, '0').substring(1) } return checksum == hex } } } |