From a92a54e15fb08323c75b478ba55f2bc401954ca3 Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Tue, 1 Oct 2024 09:29:04 +0500 Subject: [PATCH 1/4] move all closeable resources management to CloseableSequence reduce nesting in ZippedApkSplits factories more natural syntax for adding a resource to managed CloseableSequence resources --- .../ackpine/splits/CloseableSequence.kt | 21 ++++-- .../ackpine/splits/ZippedApkSplits.kt | 71 ++++++++----------- 2 files changed, 45 insertions(+), 47 deletions(-) diff --git a/ackpine-splits/src/main/kotlin/ru/solrudev/ackpine/splits/CloseableSequence.kt b/ackpine-splits/src/main/kotlin/ru/solrudev/ackpine/splits/CloseableSequence.kt index ef8370945..f1bbccba3 100644 --- a/ackpine-splits/src/main/kotlin/ru/solrudev/ackpine/splits/CloseableSequence.kt +++ b/ackpine-splits/src/main/kotlin/ru/solrudev/ackpine/splits/CloseableSequence.kt @@ -17,6 +17,8 @@ package ru.solrudev.ackpine.splits import androidx.annotation.RestrictTo +import java.util.Collections +import java.util.concurrent.ConcurrentHashMap import kotlin.coroutines.RestrictsSuspension /** @@ -35,7 +37,7 @@ internal fun closeableSequence(block: suspend CloseableSequenceScope.() - /** * The scope for yielding values of a [CloseableSequence], provides [yield] and [yieldAll] suspension functions and - * [addCloseableResource] function to manage [AutoCloseable] resources. + * [use] function to manage [AutoCloseable] resources. * * @see closeableSequence */ @@ -49,9 +51,9 @@ internal interface CloseableSequenceScope { val isClosed: Boolean /** - * Adds a [resource] to a set of [AutoCloseable] resources managed by the [CloseableSequence]. + * Adds a resource to a set of [AutoCloseable] resources managed by the [CloseableSequence]. */ - fun addCloseableResource(resource: AutoCloseable) + fun T.use(): T /** * Yields a value to the [Iterator] being built and suspends until the next value is requested. @@ -81,7 +83,9 @@ private class CloseableSequenceImpl( @Volatile private lateinit var scope: SequenceScope - private val resources = mutableSetOf() + private val resources = Collections.newSetFromMap( + ConcurrentHashMap() + ) override fun iterator(): Iterator { if (isConsumed) { @@ -90,7 +94,9 @@ private class CloseableSequenceImpl( isConsumed = true return iterator { scope = this - block() + use { + block() + } } } @@ -102,8 +108,9 @@ private class CloseableSequenceImpl( resources.clear() } - override fun addCloseableResource(resource: AutoCloseable) { - resources += resource + override fun T.use(): T { + resources += this + return this } override suspend fun yield(value: T) = scope.yield(value) diff --git a/ackpine-splits/src/main/kotlin/ru/solrudev/ackpine/splits/ZippedApkSplits.kt b/ackpine-splits/src/main/kotlin/ru/solrudev/ackpine/splits/ZippedApkSplits.kt index 390dc5896..15c40b9ef 100644 --- a/ackpine-splits/src/main/kotlin/ru/solrudev/ackpine/splits/ZippedApkSplits.kt +++ b/ackpine-splits/src/main/kotlin/ru/solrudev/ackpine/splits/ZippedApkSplits.kt @@ -41,18 +41,17 @@ public object ZippedApkSplits { */ @JvmStatic public fun getApksForFile(file: File): Sequence = closeableSequence { - ZipFile(file).use { zipFile -> - addCloseableResource(zipFile) - zipFile.entries() - .asSequence() - .filterNot { isClosed } - .mapNotNull { zipEntry -> - zipFile.getInputStream(zipEntry).use { entryStream -> - Apk.fromZipEntry(file.absolutePath, zipEntry, entryStream) - } + val zipFile = ZipFile(file).use() + zipFile.entries() + .asSequence() + .filterNot { isClosed } + .mapNotNull { zipEntry -> + zipFile.getInputStream(zipEntry).use { entryStream -> + // java.util.zip.ZipFile closes all entry streams when closed, no need to apply .use() + Apk.fromZipEntry(file.absolutePath, zipEntry, entryStream) } - .forEach { yield(it) } - } + } + .forEach { yield(it) } } /** @@ -89,38 +88,30 @@ public object ZippedApkSplits { @RequiresApi(Build.VERSION_CODES.O) private suspend inline fun CloseableSequenceScope.yieldAllUsingFileChannel(context: Context, uri: Uri) { - context.contentResolver.openFileDescriptor(uri, "r").use { fd -> - fd ?: throw NullPointerException("ParcelFileDescriptor was null: $uri") - addCloseableResource(fd) - FileInputStream(fd.fileDescriptor).use { fileInputStream -> - addCloseableResource(fileInputStream) - org.apache.commons.compress.archivers.zip.ZipFile.builder() - .setSeekableByteChannel(fileInputStream.channel) - .get() - .use { zipFile -> - addCloseableResource(zipFile) - zipFile.entries - .asSequence() - .filterNot { isClosed } - .mapNotNull { zipEntry -> - zipFile.getInputStream(zipEntry).use { entryStream -> - addCloseableResource(entryStream) - Apk.fromZipEntry(uri.toString(), zipEntry, entryStream) - } - } - .forEach { yield(it) } - } + val fd = context.contentResolver.openFileDescriptor(uri, "r")?.use() + ?: throw NullPointerException("ParcelFileDescriptor was null: $uri") + val fileInputStream = FileInputStream(fd.fileDescriptor).use() + val zipFile = org.apache.commons.compress.archivers.zip.ZipFile.builder() + .setSeekableByteChannel(fileInputStream.channel) + .get() + .use() + zipFile.entries + .asSequence() + .filterNot { isClosed } + .mapNotNull { zipEntry -> + zipFile.getInputStream(zipEntry).use { entryStream -> + entryStream.use() + Apk.fromZipEntry(uri.toString(), zipEntry, entryStream) + } } - } + .forEach { yield(it) } } private suspend inline fun CloseableSequenceScope.yieldAllUsingZipInputStream(context: Context, uri: Uri) { - ZipInputStream(context.contentResolver.openInputStream(uri)).use { zipStream -> - addCloseableResource(zipStream) - zipStream.entries() - .filterNot { isClosed } - .mapNotNull { zipEntry -> Apk.fromZipEntry(uri.toString(), zipEntry, zipStream) } - .forEach { yield(it) } - } + val zipStream = ZipInputStream(context.contentResolver.openInputStream(uri)).use() + zipStream.entries() + .filterNot { isClosed } + .mapNotNull { zipEntry -> Apk.fromZipEntry(uri.toString(), zipEntry, zipStream) } + .forEach { yield(it) } } } \ No newline at end of file From 8e9a5bb51bf79155455e7d1081cf1131f2971f92 Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Tue, 1 Oct 2024 09:54:30 +0500 Subject: [PATCH 2/4] support closing resources when delegating from ZippedApkSplits.getApksForUri to ZippedApkSplits.getApksForFile --- .../ackpine/splits/ZippedApkSplits.kt | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/ackpine-splits/src/main/kotlin/ru/solrudev/ackpine/splits/ZippedApkSplits.kt b/ackpine-splits/src/main/kotlin/ru/solrudev/ackpine/splits/ZippedApkSplits.kt index 15c40b9ef..2efc6f9ff 100644 --- a/ackpine-splits/src/main/kotlin/ru/solrudev/ackpine/splits/ZippedApkSplits.kt +++ b/ackpine-splits/src/main/kotlin/ru/solrudev/ackpine/splits/ZippedApkSplits.kt @@ -41,17 +41,7 @@ public object ZippedApkSplits { */ @JvmStatic public fun getApksForFile(file: File): Sequence = closeableSequence { - val zipFile = ZipFile(file).use() - zipFile.entries() - .asSequence() - .filterNot { isClosed } - .mapNotNull { zipEntry -> - zipFile.getInputStream(zipEntry).use { entryStream -> - // java.util.zip.ZipFile closes all entry streams when closed, no need to apply .use() - Apk.fromZipEntry(file.absolutePath, zipEntry, entryStream) - } - } - .forEach { yield(it) } + yieldAllUsingFile(file) } /** @@ -70,7 +60,7 @@ public object ZippedApkSplits { return closeableSequence { val file = uri.toFile(applicationContext) if (file.canRead()) { - yieldAll(getApksForFile(file)) + yieldAllUsingFile(file) return@closeableSequence } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -86,6 +76,21 @@ public object ZippedApkSplits { } } + @Suppress("BlockingMethodInNonBlockingContext") + private suspend inline fun CloseableSequenceScope.yieldAllUsingFile(file: File) { + val zipFile = ZipFile(file).use() + zipFile.entries() + .asSequence() + .filterNot { isClosed } + .mapNotNull { zipEntry -> + zipFile.getInputStream(zipEntry).use { entryStream -> + // java.util.zip.ZipFile closes all entry streams when closed, no need to apply .use() + Apk.fromZipEntry(file.absolutePath, zipEntry, entryStream) + } + } + .forEach { yield(it) } + } + @RequiresApi(Build.VERSION_CODES.O) private suspend inline fun CloseableSequenceScope.yieldAllUsingFileChannel(context: Context, uri: Uri) { val fd = context.contentResolver.openFileDescriptor(uri, "r")?.use() From 5168626e3e2a32db72e36c2d5c988cb9caf8fe0d Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Tue, 1 Oct 2024 09:57:21 +0500 Subject: [PATCH 3/4] increment version --- README.md | 2 +- docs/index.md | 2 +- version.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 442760235..938122e05 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Ackpine depends on Jetpack libraries, so it's necessary to declare the `google() ```kotlin dependencies { - val ackpineVersion = "0.7.2" + val ackpineVersion = "0.7.3" implementation("ru.solrudev.ackpine:ackpine-core:$ackpineVersion") // optional - Kotlin extensions and Coroutines support diff --git a/docs/index.md b/docs/index.md index 0162431e1..dee45179b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,7 +27,7 @@ Ackpine depends on Jetpack libraries, so it's necessary to declare the `google() ```kotlin dependencies { - val ackpineVersion = "0.7.2" + val ackpineVersion = "0.7.3" implementation("ru.solrudev.ackpine:ackpine-core:$ackpineVersion") // optional - Kotlin extensions and Coroutines support diff --git a/version.properties b/version.properties index 2ceda036c..1e58ebe00 100644 --- a/version.properties +++ b/version.properties @@ -1,5 +1,5 @@ MAJOR_VERSION=0 MINOR_VERSION=7 -PATCH_VERSION=2 +PATCH_VERSION=3 SUFFIX= SNAPSHOT=false \ No newline at end of file From c5bc3b1cb925dce626e7af02e437a938c0f396b6 Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Tue, 1 Oct 2024 10:04:47 +0500 Subject: [PATCH 4/4] add version 0.7.3 to changelog --- docs/changelog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index a8021d8fb..d2aa4e691 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,6 +1,13 @@ Change Log ========== +Version 0.7.3 (2024-10-01) +-------------------------- + +### Bug fixes and improvements + +- Fix resources not closing when throwing if `throwOnInvalidSplitPackage()` is applied and `ZippedApkSplits.getApksForUri()` delegates to `ZippedApkSplits.getApksForFile()`. + Version 0.7.2 (2024-09-30) --------------------------