ref: dd2ee96d47a98c46ac7ca5a5631e01b8b40a4bea
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 |
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 class TimetableDownloader : IntentService("TimetableDownloader") { 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 (!isNetworkAvailable(this)) { sendResult("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) throw Exception("Failed to connect") 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 currentLastModified = prefs.getString("timetableLastModified", "1979-10-12T00:00") if (lastModified <= currentLastModified && !intent.getBooleanExtra("force", false)) { sendResult("up-to-date") return } Log.i("Downloader", "timetable is newer ($lastModified > $currentLastModified)") notify(0) val xzDbUrl = URL("https://adamsprogs.ml/w/_media/programmes/bimba/timetable.db.xz") httpCon = xzDbUrl.openConnection() as HttpURLConnection if (httpCon.responseCode != HttpURLConnection.HTTP_OK) throw Exception("Failed to connect") 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(File("timetable.db")) val prefsEditor = prefs.edit() prefsEditor.putString("timetableLastModified", lastModified) prefsEditor.apply() sendResult("downloaded") } else { Log.i("Downloader", "downloaded but is wrong") sendResult("validity failed") } cancelNotification() } } private fun sendResult(result: String) { val broadcastIntent = Intent() broadcastIntent.action = "ml.adamsprogs.bimba.timetableDownloaded" broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT) broadcastIntent.putExtra("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 } } } |