aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt163
1 files changed, 163 insertions, 0 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
new file mode 100644
index 00000000..35f69bca
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
@@ -0,0 +1,163 @@
+package com.pitchedapps.frost.utils
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.support.v4.app.NotificationCompat
+import android.support.v4.app.NotificationManagerCompat
+import android.support.v4.content.FileProvider
+import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE
+import ca.allanwang.kau.permissions.kauRequestPermissions
+import ca.allanwang.kau.utils.copyFromInputStream
+import ca.allanwang.kau.utils.string
+import com.pitchedapps.frost.BuildConfig
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.services.frostConfig
+import com.pitchedapps.frost.services.frostNotification
+import com.pitchedapps.frost.services.getNotificationPendingCancelIntent
+import okhttp3.MediaType
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.ResponseBody
+import okio.*
+import org.jetbrains.anko.AnkoAsyncContext
+import org.jetbrains.anko.doAsync
+import java.io.File
+import java.io.IOException
+import java.lang.ref.WeakReference
+
+/**
+ * Created by Allan Wang on 2017-08-04.
+ *
+ * With reference to the <a href="https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/Progress.java">OkHttp3 sample</a>
+ */
+fun Context.frostDownload(url: String) {
+ L.d("Received download request", "Download $url")
+ val type = if (url.contains("video")) DownloadType.VIDEO
+ else return L.d("Download request does not match any type")
+ kauRequestPermissions(PERMISSION_WRITE_EXTERNAL_STORAGE) {
+ granted, _ ->
+ if (granted) doAsync { frostDownloadImpl(url, type) }
+ }
+}
+
+private const val MAX_PROGRESS = 1000
+//private val DOWNLOAD_GROUP: String? = null
+private const val DOWNLOAD_GROUP = "frost_downloads"
+
+private enum class DownloadType(val downloadingRes: Int, val downloadedRes: Int) {
+ VIDEO(R.string.downloading_video, R.string.downloaded_video),
+ FILE(R.string.downloading_file, R.string.downloaded_file);
+
+ fun getPendingIntent(context: Context, file: File): PendingIntent {
+ val uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file)
+ val type = context.contentResolver.getType(uri)
+ L.d("DownloadType: retrieved pending intent - $uri $type")
+ val intent = Intent(Intent.ACTION_VIEW, uri)
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ .setDataAndType(uri, type)
+ return PendingIntent.getActivity(context, 0, intent, 0)
+ }
+}
+
+private fun AnkoAsyncContext<Context>.frostDownloadImpl(url: String, type: DownloadType) {
+ L.d("Starting download request")
+ val notifId = Math.abs(url.hashCode() + System.currentTimeMillis().toInt())
+ var notifBuilderAttempt: NotificationCompat.Builder? = null
+ weakRef.get()?.apply {
+ notifBuilderAttempt = frostNotification
+ .setContentTitle(string(type.downloadingRes))
+ .setCategory(Notification.CATEGORY_PROGRESS)
+ .setWhen(System.currentTimeMillis())
+ .setProgress(MAX_PROGRESS, 0, false)
+ .setOngoing(true)
+ .addAction(R.drawable.ic_action_cancel, string(R.string.kau_cancel), getNotificationPendingCancelIntent(DOWNLOAD_GROUP, notifId))
+ .setGroup(DOWNLOAD_GROUP)
+ }
+ val notifBuilder = notifBuilderAttempt ?: return
+ notifBuilder.show(weakRef, notifId)
+
+ val request: Request = Request.Builder()
+ .url(url)
+ .tag(url)
+ .build()
+
+ var client: OkHttpClient? = null
+ client = OkHttpClient.Builder()
+ .addNetworkInterceptor {
+ chain ->
+ val original = chain.proceed(chain.request())
+ return@addNetworkInterceptor original.newBuilder().body(ProgressResponseBody(original.body()!!) {
+ bytesRead, contentLength, done ->
+ //cancel request if context reference is now invalid
+ if (weakRef.get() == null) {
+ client?.cancel(url)
+ }
+ val ctx = weakRef.get() ?: return@ProgressResponseBody client?.cancel(url) ?: Unit
+ val percentage = bytesRead.toFloat() / contentLength.toFloat() * MAX_PROGRESS
+ L.v("Download request progress: $percentage")
+ notifBuilder.setProgress(MAX_PROGRESS, percentage.toInt(), false)
+ if (done) {
+ notifBuilder.setFinished(ctx, type)
+ L.d("Download request finished")
+ }
+ notifBuilder.show(weakRef, notifId)
+ }).build()
+ }
+ .build()
+ client.newCall(request).execute().use {
+ response ->
+ if (!response.isSuccessful) throw IOException("Unexpected code $response")
+ val stream = response.body()?.byteStream()
+ if (stream != null) {
+ val destination = createMediaFile(".mp4")
+ destination.copyFromInputStream(stream)
+ weakRef.get()?.apply {
+ notifBuilder.setContentIntent(type.getPendingIntent(this, destination))
+ notifBuilder.show(weakRef, notifId)
+ }
+ }
+ }
+}
+
+private fun NotificationCompat.Builder.setFinished(context: Context, type: DownloadType)
+ = setContentTitle(context.string(type.downloadedRes))
+ .setProgress(0, 0, false).setOngoing(false).setAutoCancel(true)
+ .apply { mActions.clear() }
+
+private fun OkHttpClient.cancel(url: String) {
+ val call = dispatcher().runningCalls().firstOrNull { it.request().tag() == url }
+ if (call != null && !call.isCanceled) call.cancel()
+}
+
+private fun NotificationCompat.Builder.show(weakRef: WeakReference<Context>, notifId: Int) {
+ val c = weakRef.get() ?: return
+ NotificationManagerCompat.from(c).notify(DOWNLOAD_GROUP, notifId, build().frostConfig())
+}
+
+private class ProgressResponseBody(
+ val responseBody: ResponseBody,
+ val listener: (bytesRead: Long, contentLength: Long, done: Boolean) -> Unit) : ResponseBody() {
+
+ private val bufferedSource: BufferedSource by lazy { Okio.buffer(source(responseBody.source())) }
+
+ override fun contentLength(): Long = responseBody.contentLength()
+
+ override fun contentType(): MediaType? = responseBody.contentType()
+
+ override fun source(): BufferedSource = bufferedSource
+
+ private fun source(source: Source): Source = object : ForwardingSource(source) {
+
+ private var totalBytesRead = 0L
+
+ override fun read(sink: Buffer?, byteCount: Long): Long {
+ val bytesRead = super.read(sink, byteCount)
+ // read() returns the number of bytes read, or -1 if this source is exhausted.
+ totalBytesRead += if (bytesRead != -1L) bytesRead else 0
+ listener(totalBytesRead, responseBody.contentLength(), bytesRead == -1L)
+ return bytesRead
+ }
+ }
+} \ No newline at end of file