Commit 29195dab authored by Jonas L.'s avatar Jonas L.

Refactor image downloading

parent 55c30347
......@@ -40,6 +40,7 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.30.1'
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
implementation 'com.google.android.material:material:1.0.0'
......
package de.determapp.android.content.download
import android.content.Context
import android.util.Log
import androidx.core.os.CancellationSignal
import androidx.lifecycle.MutableLiveData
import de.determapp.android.BuildConfig
import de.determapp.android.Http
import de.determapp.android.content.ContentStorage
import de.determapp.android.content.projectdata.Image
import de.determapp.android.content.projectdata.Project
import de.determapp.android.ui.viewer.ProjectSpec
import de.determapp.android.util.*
import kotlinx.coroutines.experimental.*
import okhttp3.HttpUrl
import okhttp3.Request
import okio.Okio
import java.io.File
import java.io.IOException
import java.util.*
import java.util.concurrent.CountDownLatch
private const val NUM_OF_THREADS = 4
private const val NUM_OF_PARALLEL_DOWNLOADS = 4
private const val LOG_TAG = "ProjectImageDownloader"
val imageDownloadingNotifications = MutableLiveData<Void>()
fun updateProjectImages(context: Context, baseUrl: String, project: Project, resolution: Int?, projectSpec: ProjectSpec, cancellationSignal: CancellationSignal, progressListener: ProgressListener?) {
......@@ -39,14 +42,6 @@ fun updateProjectImages(context: Context, baseUrl: String, project: Project, res
// download new files
if (newFiles.isNotEmpty()) {
val numOfThreads = Math.min(NUM_OF_THREADS, newFiles.size)
val countDownLatch = CountDownLatch(numOfThreads)
var ownException: Throwable? = null
val ownCancellationSignal = CancellationSignal()
cancellationSignal.setOnCancelListener {
ownCancellationSignal.cancel()
ownException = InterruptedException()
}
val processedFiles = Collections.synchronizedList(ArrayList<Image>())
val completeFileSize = calculateSize(project.image.values, resolution)
......@@ -57,97 +52,74 @@ fun updateProjectImages(context: Context, baseUrl: String, project: Project, res
// do a report
runOnUiThread(Runnable {
imageDownloadingNotifications.value = null
})
if (ownCancellationSignal.isCanceled) {
// ignore it
return
}
if (cancellationSignal.isCanceled) {
// ignore it
return@Runnable
}
val processedFileSize = calculateSize(processedFiles, resolution)
val processedFileSize = calculateSize(processedFiles, resolution)
callProgressListener(
Progress(((originalProcessedFileSize + processedFileSize) * 1000L / completeFileSize).toInt(), 1000),
progressListener
)
callProgressListener(
Progress(((originalProcessedFileSize + processedFileSize) * 1000L / completeFileSize).toInt(), 1000),
progressListener
)
})
}
for (i in 1..numOfThreads) {
Async.network.submit {
try {
while (true) {
if (ownCancellationSignal.isCanceled) {
// canceled
break
}
// FIXME: this would not limit the concurrency correctly when downloads are non blocking
runBlocking (context = newFixedThreadPoolContext(NUM_OF_PARALLEL_DOWNLOADS, "image downloading")) {
cancellationSignal.setOnCancelListener {
this.coroutineContext.cancel()
}
var processedFileTemp: Image? = null
newFiles.forEach {
processedFile ->
synchronized(newFiles) {
if (newFiles.isNotEmpty()) {
processedFileTemp = newFiles.removeAt(0)
}
}
async {
val requestedFile = processedFile.getByResolution(resolution)
if (processedFileTemp == null) {
// done
break
}
val processedFile = processedFileTemp!!
val requestedFile = processedFile.getByResolution(resolution)
if (BuildConfig.DEBUG) {
Log.d(LOG_TAG, "start download of ${requestedFile.filename}")
}
// start request
val response = Http.uncachedClient.newCall(
Request.Builder()
.url(HttpUrl.parse(baseUrl)!!.resolve("./image/" + requestedFile.filename)!!)
.build()
).execute()
val tempFileName = ContentStorage.generateId()
val tempFile = File(imageDirectory, tempFileName)
Http.uncachedClient.newCall(
Request.Builder()
.url(HttpUrl.parse(baseUrl)!!.resolve("./image/" + requestedFile.filename)!!)
.build()
).execute().use { response ->
if (!response.isSuccessful) {
throw IOException("request failed")
}
val tempFileName = ContentStorage.generateId()
val tempFile = File(imageDirectory, tempFileName)
// write response to temp file
try {
val tempFileSink = Okio.buffer(Okio.sink(tempFile));
try {
tempFileSink.writeAll(response.body()!!.source())
} finally {
tempFileSink.close()
}
} finally {
response.body()!!.source().close()
Okio.buffer(Okio.sink(tempFile)).use { tempFileSink ->
tempFileSink.writeAll(response.body()!!.source())
}
}
// validate the temp file
ImageValidator.assertImageValid(requestedFile, tempFile)
// validate the temp file
ImageValidator.assertImageValid(requestedFile, tempFile)
// rename to final name
tempFile.renameTo(File(imageDirectory, requestedFile.filename))
// rename to final name
tempFile.renameTo(File(imageDirectory, requestedFile.filename))
// update the progress
processedFiles.add(processedFile)
updateProgress()
// update the progress
processedFiles.add(processedFile)
updateProgress()
if (BuildConfig.DEBUG) {
Log.d(LOG_TAG, "finished download of ${requestedFile.filename}")
}
} catch (ex: Throwable) {
ownException = ex
ownCancellationSignal.cancel()
} finally {
countDownLatch.countDown()
}
}
}
countDownLatch.await()
val currentOwnException = ownException
if (currentOwnException != null) {
throw currentOwnException
if (cancellationSignal.isCanceled) {
throw InterruptedException()
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment