ref: 85f4ee806ced6e075630cfb0cb52cead4c4a0f25
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 |
package ml.adamsprogs.bimba import android.app.IntentService 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.util.Log import android.app.NotificationManager 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" } lateinit var notificationManager: NotificationManager 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 } Log.i("Downloader", "Got metadata") 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 } Log.i("Downloader", "timetable is newer ($lastModified > $currentLastModified)") 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 } Log.i("Downloader", "connected to db") val xzIn = XZInputStream(httpCon.inputStream) val file = File(this.filesDir, "new_timetable.db") if (copyInputStreamToFile(xzIn, file, checksum)) { Log.i("Downloader", "downloaded") 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 { Log.i("Downloader", "downloaded but is wrong") 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) { 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()) } 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..digest.size - 1) { hex += Integer.toString((digest[i] and 0xff.toByte()) + 0x100, 16).padStart(3, '0').substring(1) } Log.i("Downloader", "checksum is $checksum, and hex is $hex") return checksum == hex } } } |