Skip to content

Commit 4d0e715

Browse files
committed
Add thumbanil generator thread pool executor and subsample image stream
1 parent 6daefd7 commit 4d0e715

File tree

1 file changed

+66
-32
lines changed

1 file changed

+66
-32
lines changed

data/src/main/java/org/cryptomator/data/cloud/crypto/CryptoImplDecorator.kt

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import android.content.Context
44
import android.graphics.Bitmap
55
import android.graphics.BitmapFactory
66
import android.media.ThumbnailUtils
7+
import com.google.common.util.concurrent.ThreadFactoryBuilder
78
import com.tomclaw.cache.DiskLruCache
8-
import okhttp3.internal.closeQuietly
99
import org.cryptomator.cryptolib.api.Cryptor
1010
import org.cryptomator.cryptolib.common.DecryptingReadableByteChannel
1111
import org.cryptomator.cryptolib.common.EncryptingWritableByteChannel
@@ -37,6 +37,7 @@ import org.cryptomator.util.file.MimeType
3737
import org.cryptomator.util.file.MimeTypeMap
3838
import org.cryptomator.util.file.MimeTypes
3939
import java.io.ByteArrayOutputStream
40+
import java.io.Closeable
4041
import java.io.File
4142
import java.io.FileInputStream
4243
import java.io.FileOutputStream
@@ -51,7 +52,10 @@ import java.util.function.Supplier
5152
import timber.log.Timber
5253
import java.io.PipedInputStream
5354
import java.io.PipedOutputStream
54-
import kotlin.concurrent.thread
55+
import java.util.concurrent.ExecutorService
56+
import java.util.concurrent.Executors
57+
import java.util.concurrent.Future
58+
import kotlin.math.ceil
5559

5660

5761
abstract class CryptoImplDecorator(
@@ -72,6 +76,11 @@ abstract class CryptoImplDecorator(
7276

7377
private val mimeTypes = MimeTypes(MimeTypeMap())
7478

79+
private val thumbnailExecutorService: ExecutorService by lazy {
80+
val threadFactory = ThreadFactoryBuilder().setNameFormat("thumbnail-generation-thread-%d").build()
81+
Executors.newFixedThreadPool(3, threadFactory)
82+
}
83+
7584
protected fun getLruCacheFor(type : CloudType): DiskLruCache? {
7685
return getOrCreateLruCache(getCacheTypeFromCloudType(type), sharedPreferencesHandler.lruCacheSize())
7786
}
@@ -363,30 +372,17 @@ abstract class CryptoImplDecorator(
363372
val diskCache = cryptoFile.cloudFile.cloud?.type()?.let { getLruCacheFor(it) }
364373
val cacheKey = generateCacheKey(ciphertextFile)
365374
val genThumbnail = isGenerateThumbnailsEnabled(diskCache, cryptoFile.name)
366-
var thumbnailBitmap : Bitmap? = null
367375

368376
val thumbnailWriter = PipedOutputStream()
369377
val thumbnailReader = PipedInputStream(thumbnailWriter)
370378

371379
try {
372-
// cloudContentRepository.read(file, encryptedTmpFile, encryptedData, ...)
373-
// file appena letto dalla rete, portato in cache ancora cifrato!
374380
val encryptedTmpFile = readToTmpFile(cryptoFile, ciphertextFile, progressAware)
375381

376-
// TODO: reusable thread?
377-
// A thread pool is a managed collection of threads that runs tasks in parallel from a queue.
378-
// https://developer.android.com/develop/background-work/background-tasks/asynchronous/java-threads
379-
val t = thread(start = false, name = "S.AN-DRO") { // Simply A New Data Readable Output
380-
try {
381-
val bitmap = BitmapFactory.decodeStream(thumbnailReader) // wait for the full image
382-
thumbnailBitmap = ThumbnailUtils.extractThumbnail(bitmap, 100, 100)
383-
thumbnailReader.closeQuietly()
384-
} catch (e : Exception) {
385-
Timber.e("Bitmap generation crashed")
386-
}
382+
if (genThumbnail) {
383+
startThumbnailGeneratorThread(diskCache, cacheKey, thumbnailReader)
387384
}
388385

389-
t.start()
390386
progressAware.onProgress(Progress.started(DownloadState.decryption(cryptoFile)))
391387
try {
392388
Channels.newChannel(FileInputStream(encryptedTmpFile)).use { readableByteChannel ->
@@ -398,7 +394,9 @@ abstract class CryptoImplDecorator(
398394
while (decryptingReadableByteChannel.read(buff).also { read = it } > 0) {
399395
buff.flip()
400396
data.write(buff.array(), 0, buff.remaining())
401-
thumbnailWriter.write(buff.array(), 0, buff.remaining())
397+
if (genThumbnail) {
398+
thumbnailWriter.write(buff.array(), 0, buff.remaining())
399+
}
402400

403401
decrypted += read.toLong()
404402

@@ -412,21 +410,64 @@ abstract class CryptoImplDecorator(
412410
}
413411
}
414412
thumbnailWriter.flush()
413+
closeQuietly(thumbnailWriter)
415414
}
416415
} finally {
417416
encryptedTmpFile.delete()
418-
thumbnailWriter.closeQuietly()
419417
progressAware.onProgress(Progress.completed(DownloadState.decryption(cryptoFile)))
420418
}
421-
t.join() // wait the thread
422-
thumbnailReader.closeQuietly()
419+
420+
closeQuietly(thumbnailReader)
423421
} catch (e: IOException) {
424422
throw FatalBackendException(e)
425423
}
424+
}
426425

427-
// store it in cloud-related LRU cache
428-
if(genThumbnail && thumbnailBitmap != null) {
429-
generateAndStoreThumbnail(diskCache, cacheKey, thumbnailBitmap!!)
426+
private fun closeQuietly(closeable : Closeable) {
427+
try {
428+
closeable.close();
429+
} catch (e : IOException) {
430+
// ignore
431+
}
432+
}
433+
private fun startThumbnailGeneratorThread(diskCache: DiskLruCache?, cacheKey: String, thumbnailReader: PipedInputStream) : Future<*> {
434+
return thumbnailExecutorService.submit {
435+
try {
436+
val options = BitmapFactory.Options()
437+
val thumbnailBitmap : Bitmap?
438+
// options.inJustDecodeBounds = true
439+
// read properties of the image: outWidth, outHeight (no bitmap allocation!)
440+
// BitmapFactory.decodeStream(thumbnailReaderTee, null, options)
441+
// options.inJustDecodeBounds = false
442+
// options.outWidth; options.outHeight
443+
options.inSampleSize = 4 // pixel number reduced by a factor of 1/16
444+
// options.inSampleSize = 8 // pixel number reduced by a factor of 1/64
445+
446+
// obtain a subsampled version of the image
447+
val bitmap = BitmapFactory.decodeStream(thumbnailReader, null, options)
448+
449+
val thumbnailWidth = 100
450+
val thumbnailHeight = 100
451+
// var aspectRatio = 1f
452+
// bitmap?.let {
453+
// if (it.height != 0) {
454+
// aspectRatio = it.width.toFloat() / it.height
455+
// }
456+
// }
457+
// val thumbnailHeight = ceil(1 / aspectRatio * thumbnailWidth).toInt()
458+
459+
// generate thumbnail preserving aspect ratio
460+
thumbnailBitmap = ThumbnailUtils.extractThumbnail(bitmap, thumbnailWidth, thumbnailHeight)
461+
462+
// store it in cloud-related LRU cache
463+
if(thumbnailBitmap != null) {
464+
storeThumbnail(diskCache, cacheKey, thumbnailBitmap)
465+
}
466+
467+
closeQuietly(thumbnailReader)
468+
} catch (e: Exception) {
469+
Timber.e("Bitmap generation crashed")
470+
}
430471
}
431472
}
432473

@@ -450,14 +491,7 @@ abstract class CryptoImplDecorator(
450491
isImageMediaType(fileName)
451492
}
452493

453-
private fun generateAndStoreThumbnail(cache: DiskLruCache?, cacheKey: String, thumbnailBitmap: Bitmap){
454-
// // generate the Bitmap (in memory)
455-
// val bitmap : Bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
456-
// ThumbnailUtils.createImageThumbnail(thumbnailTmp, Size(100, 100), null)
457-
// } else {
458-
// ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(thumbnailTmp.path), 100, 100)
459-
// }
460-
494+
private fun storeThumbnail(cache: DiskLruCache?, cacheKey: String, thumbnailBitmap: Bitmap){
461495
// write the thumbnail in a file (on disk)
462496
val thumbnailFile : File = File.createTempFile(UUID.randomUUID().toString(), ".thumbnail", internalCache)
463497
thumbnailBitmap.compress(Bitmap.CompressFormat.JPEG, 100, thumbnailFile.outputStream())

0 commit comments

Comments
 (0)