@@ -4,8 +4,8 @@ import android.content.Context
44import android.graphics.Bitmap
55import android.graphics.BitmapFactory
66import android.media.ThumbnailUtils
7+ import com.google.common.util.concurrent.ThreadFactoryBuilder
78import com.tomclaw.cache.DiskLruCache
8- import okhttp3.internal.closeQuietly
99import org.cryptomator.cryptolib.api.Cryptor
1010import org.cryptomator.cryptolib.common.DecryptingReadableByteChannel
1111import org.cryptomator.cryptolib.common.EncryptingWritableByteChannel
@@ -37,6 +37,7 @@ import org.cryptomator.util.file.MimeType
3737import org.cryptomator.util.file.MimeTypeMap
3838import org.cryptomator.util.file.MimeTypes
3939import java.io.ByteArrayOutputStream
40+ import java.io.Closeable
4041import java.io.File
4142import java.io.FileInputStream
4243import java.io.FileOutputStream
@@ -51,7 +52,10 @@ import java.util.function.Supplier
5152import timber.log.Timber
5253import java.io.PipedInputStream
5354import 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
5761abstract 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