Skip to content

Commit

Permalink
Merge pull request #99 from solrudev/develop
Browse files Browse the repository at this point in the history
0.9.3
  • Loading branch information
solrudev authored Dec 25, 2024
2 parents 4bca22f + 03bb720 commit 096c7bc
Show file tree
Hide file tree
Showing 26 changed files with 374 additions and 190 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Ackpine depends on Jetpack libraries, so it's necessary to declare the `google()

```kotlin
dependencies {
val ackpineVersion = "0.9.2"
val ackpineVersion = "0.9.3"
implementation("ru.solrudev.ackpine:ackpine-core:$ackpineVersion")

// optional - Kotlin extensions and Coroutines support
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ public sealed interface ResolvableString : Serializable {
private data object Empty : ResolvableString {
private const val serialVersionUID = 5194188194930148316L
override fun resolve(context: Context): String = ""
private fun readResolve(): Any = Empty
}

private data class Raw(val value: String) : ResolvableString {
Expand Down
4 changes: 4 additions & 0 deletions ackpine-splits/api/ackpine-splits.api
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ public final class ru/solrudev/ackpine/ZippedFileProvider : android/content/Cont
public fun attachInfo (Landroid/content/Context;Landroid/content/pm/ProviderInfo;)V
public fun delete (Landroid/net/Uri;Ljava/lang/String;[Ljava/lang/String;)I
public fun getType (Landroid/net/Uri;)Ljava/lang/String;
public static final fun getUriForZipEntry (Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;
public static final fun getUriForZipEntry (Ljava/io/File;Ljava/lang/String;)Landroid/net/Uri;
public static final fun getUriForZipEntry (Ljava/lang/String;Ljava/lang/String;)Landroid/net/Uri;
public fun insert (Landroid/net/Uri;Landroid/content/ContentValues;)Landroid/net/Uri;
public static final fun isZippedFileProviderUri (Landroid/net/Uri;)Z
Expand All @@ -21,6 +23,8 @@ public final class ru/solrudev/ackpine/ZippedFileProvider : android/content/Cont
}

public final class ru/solrudev/ackpine/ZippedFileProvider$Companion {
public final fun getUriForZipEntry (Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;
public final fun getUriForZipEntry (Ljava/io/File;Ljava/lang/String;)Landroid/net/Uri;
public final fun getUriForZipEntry (Ljava/lang/String;Ljava/lang/String;)Landroid/net/Uri;
public final fun isZippedFileProviderUri (Landroid/net/Uri;)Z
}
Expand Down
133 changes: 27 additions & 106 deletions ackpine-splits/src/main/kotlin/ru/solrudev/ackpine/ZippedFileProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,28 +26,22 @@ import android.content.res.AssetFileDescriptor
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.CancellationSignal
import android.os.ParcelFileDescriptor
import android.provider.OpenableColumns
import android.webkit.MimeTypeMap
import androidx.annotation.RequiresApi
import androidx.core.net.toUri
import ru.solrudev.ackpine.helpers.closeWithException
import ru.solrudev.ackpine.helpers.entries
import ru.solrudev.ackpine.helpers.toFile
import ru.solrudev.ackpine.io.ZipEntryStream
import ru.solrudev.ackpine.plugin.AckpinePlugin
import ru.solrudev.ackpine.plugin.AckpinePluginRegistry
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.concurrent.Executor
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream

/**
* [ContentProvider] which allows to open files inside of ZIP archives.
Expand Down Expand Up @@ -201,71 +195,9 @@ public class ZippedFileProvider : ContentProvider() {

private fun openZipEntryStream(uri: Uri, signal: CancellationSignal?): ZipEntryStream {
val zipFileUri = zipFileUri(uri)
val file = context?.let { context -> zipFileUri.toFile(context, signal) }
if (file?.canRead() == true) {
val zipFile = ZipFile(file)
return try {
val zipEntry = zipFile.getEntry(uri.encodedQuery)
ZipEntryStream(zipFile.getInputStream(zipEntry), zipEntry.size, zipFile)
} catch (throwable: Throwable) {
zipFile.closeWithException(throwable)
throw throwable
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return openZipEntryStreamApi26(zipFileUri, uri.encodedQuery, signal)
}
return openZipEntryStreamUsingZipInputStream(zipFileUri, uri.encodedQuery, signal)
}

@RequiresApi(Build.VERSION_CODES.O)
private fun openZipEntryStreamApi26(
zipFileUri: Uri,
zipEntryName: String?,
signal: CancellationSignal?
): ZipEntryStream {
var fd: ParcelFileDescriptor? = null
var fileInputStream: FileInputStream? = null
val zipFile: org.apache.commons.compress.archivers.zip.ZipFile
try {
fd = context?.contentResolver?.openFileDescriptor(zipFileUri, "r", signal)
?: throw NullPointerException("ParcelFileDescriptor was null: $zipFileUri")
fileInputStream = FileInputStream(fd.fileDescriptor)
zipFile = org.apache.commons.compress.archivers.zip.ZipFile.builder()
.setSeekableByteChannel(fileInputStream.channel)
.get()
} catch (throwable: Throwable) {
fd?.closeWithException(throwable)
fileInputStream?.closeWithException(throwable)
throwable.printStackTrace()
return openZipEntryStreamUsingZipInputStream(zipFileUri, zipEntryName, signal)
}
try {
val zipEntry = zipFile.getEntry(zipEntryName)
return ZipEntryStream(zipFile.getInputStream(zipEntry), zipEntry.size, zipFile, fileInputStream, fd)
} catch (throwable: Throwable) {
fd.closeWithException(throwable)
fileInputStream.closeWithException(throwable)
zipFile.closeWithException(throwable)
throw throwable
}
}

private fun openZipEntryStreamUsingZipInputStream(
zipFileUri: Uri,
zipEntryName: String?,
signal: CancellationSignal?
): ZipEntryStream {
val zipStream = ZipInputStream(context?.contentResolver?.openInputStream(zipFileUri))
val zipEntry = try {
zipStream.entries()
.onEach { signal?.throwIfCanceled() }
.first { it.name == zipEntryName }
} catch (throwable: Throwable) {
zipStream.closeWithException(throwable)
throw throwable
}
return ZipEntryStream(zipStream, zipEntry.size)
val zipEntryName = uri.encodedQuery
return ZipEntryStream.open(zipFileUri, zipEntryName.orEmpty(), context!!, signal)
?: throw IOException("Zip entry $zipEntryName not found at $uri")
}

private fun zipFileUri(uri: Uri): Uri {
Expand Down Expand Up @@ -360,6 +292,28 @@ public class ZippedFileProvider : ContentProvider() {
.encodedQuery(zipEntryName)
.build()
}

/**
* Creates an [Uri] for a ZIP entry.
*
* @param file a ZIP file containing [zip entry][zipEntryName].
* @param zipEntryName name of the ZIP entry inside of the ZIP archive.
*/
@JvmStatic
public fun getUriForZipEntry(file: File, zipEntryName: String): Uri {
return getUriForZipEntry(file.absolutePath, zipEntryName)
}

/**
* Creates an [Uri] for a ZIP entry.
*
* @param uri [Uri] pointing to a ZIP file containing [zip entry][zipEntryName].
* @param zipEntryName name of the ZIP entry inside of the ZIP archive.
*/
@JvmStatic
public fun getUriForZipEntry(uri: Uri, zipEntryName: String): Uri {
return getUriForZipEntry(uri.toString(), zipEntryName)
}
}
}

Expand All @@ -371,37 +325,4 @@ private object ZippedFileProviderPlugin : AckpinePlugin {
override fun setExecutor(executor: Executor) {
this.executor = executor
}
}

private class ZipEntryStream(
private val inputStream: InputStream,
val size: Long,
private vararg var resources: AutoCloseable
) : InputStream() {

override fun read(): Int = inputStream.read()
override fun available(): Int = inputStream.available()
override fun markSupported(): Boolean = inputStream.markSupported()
override fun mark(readlimit: Int) = inputStream.mark(readlimit)
override fun read(b: ByteArray?): Int = inputStream.read(b)
override fun read(b: ByteArray?, off: Int, len: Int): Int = inputStream.read(b, off, len)
override fun reset() = inputStream.reset()
override fun skip(n: Long): Long = inputStream.skip(n)

override fun close() {
for (resource in resources) {
runCatching { resource.close() }
}
resources = emptyArray()
inputStream.close()
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ZipEntryStream) return false
return inputStream == other.inputStream
}

override fun hashCode(): Int = inputStream.hashCode()
override fun toString(): String = inputStream.toString()
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package ru.solrudev.ackpine.helpers
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.CancellationSignal
import android.os.Environment
import android.os.Process
Expand All @@ -28,18 +27,14 @@ import java.io.File
import java.io.FileNotFoundException

@JvmSynthetic
internal fun Uri.toFile(context: Context, signal: CancellationSignal? = null): File {
if (scheme == ContentResolver.SCHEME_FILE) {
return File(requireNotNull(path) { "Uri path is null: $this" })
internal fun Context.getFileFromUri(uri: Uri, signal: CancellationSignal? = null): File {
if (uri.scheme == ContentResolver.SCHEME_FILE) {
return File(requireNotNull(uri.path) { "Uri path is null: $uri" })
}
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
context.contentResolver.openFileDescriptor(this, "r", signal)
} else {
context.contentResolver.openFileDescriptor(this, "r")
}.use { fileDescriptor ->
contentResolver.openFileDescriptor(uri, "r", signal).use { fileDescriptor ->
if (fileDescriptor == null) {
throw NullPointerException("ParcelFileDescriptor was null: $this")
throw NullPointerException("ParcelFileDescriptor was null: $uri")
}
val path = "/proc/${Process.myPid()}/fd/${fileDescriptor.fd}"
val canonicalPath = File(path).canonicalPath.let { canonicalPath ->
Expand All @@ -50,7 +45,7 @@ internal fun Uri.toFile(context: Context, signal: CancellationSignal? = null): F
}
}
if (canonicalPath == path) {
return tryFileFromExternalDocumentUri(context, this) ?: File("")
return tryGetFileFromExternalDocumentUri(this, uri) ?: File("")
}
return File(canonicalPath)
}
Expand All @@ -59,8 +54,8 @@ internal fun Uri.toFile(context: Context, signal: CancellationSignal? = null): F
}
}

private fun tryFileFromExternalDocumentUri(context: Context, uri: Uri): File? {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT || !DocumentsContract.isDocumentUri(context, uri)) {
private fun tryGetFileFromExternalDocumentUri(context: Context, uri: Uri): File? {
if (!DocumentsContract.isDocumentUri(context, uri)) {
return null
}
if (uri.authority != "com.android.externalstorage.documents") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023 Ilya Fomichev
* Copyright (C) 2023-2024 Ilya Fomichev
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,7 +14,7 @@
* limitations under the License.
*/

package ru.solrudev.ackpine.splits.helpers
package ru.solrudev.ackpine.io

import androidx.annotation.RestrictTo
import java.io.InputStream
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2024 Ilya Fomichev
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package ru.solrudev.ackpine.io

import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.nio.ByteBuffer

@JvmSynthetic
internal fun InputStream.toByteBuffer(): ByteBuffer {
val buffer = ByteArrayOutputStream()
copyTo(buffer)
return ByteBuffer.wrap(buffer.toByteArray())
}
Loading

0 comments on commit 096c7bc

Please sign in to comment.