Skip to content

Commit

Permalink
Speedup access to shared storage
Browse files Browse the repository at this point in the history
  • Loading branch information
qnga committed Oct 25, 2024
1 parent edd9991 commit 5f0aa70
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,21 @@ import android.net.Uri
import android.provider.MediaStore
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.readium.r2.shared.InternalReadiumApi
import org.readium.r2.shared.extensions.*
import org.readium.r2.shared.extensions.coerceFirstNonNegative
import org.readium.r2.shared.extensions.queryProjection
import org.readium.r2.shared.extensions.readFully
import org.readium.r2.shared.extensions.requireLengthFitInt
import org.readium.r2.shared.util.AbsoluteUrl
import org.readium.r2.shared.util.DebugError
import org.readium.r2.shared.util.Try
import org.readium.r2.shared.util.data.ReadError
import org.readium.r2.shared.util.flatMap
import org.readium.r2.shared.util.getOrElse
import org.readium.r2.shared.util.io.CountingInputStream
import org.readium.r2.shared.util.mediatype.MediaType
import org.readium.r2.shared.util.resource.Resource
import org.readium.r2.shared.util.resource.filename
Expand All @@ -45,9 +49,12 @@ public class ContentResource(

private lateinit var _properties: Try<Resource.Properties, ReadError>

private var stream: CountingInputStream? = null

override val sourceUrl: AbsoluteUrl? = uri.toUrl() as? AbsoluteUrl

override fun close() {
stream?.close()
}

override suspend fun properties(): Try<Resource.Properties, ReadError> {
Expand Down Expand Up @@ -95,22 +102,12 @@ public class ContentResource(
}

private suspend fun readFully(): Try<ByteArray, ReadError> =
withStream { it.readFully() }
withStream(fromIndex = 0) { it.readFully() }

private suspend fun readRange(range: LongRange): Try<ByteArray, ReadError> =
withStream {
withStream(fromIndex = range.first) {
withContext(Dispatchers.IO) {
var skipped: Long = 0

while (skipped != range.first) {
skipped += it.skip(range.first - skipped)
if (skipped == 0L) {
throw IOException("Could not skip InputStream to read ranges from $uri.")
}
}

val length = range.last - range.first + 1
it.read(length)
it.readRange(range)
}
}

Expand All @@ -134,16 +131,37 @@ public class ContentResource(
return _length
}

private suspend fun <T> withStream(block: suspend (InputStream) -> T): Try<T, ReadError> {
private suspend fun <T> withStream(
fromIndex: Long,
block: suspend (CountingInputStream) -> T
): Try<T, ReadError> {
val stream = stream(fromIndex)
.getOrElse { return Try.failure(it) }

return Try.catching {
val stream = contentResolver.openInputStream(uri)
block(stream)
}
}

private fun stream(fromIndex: Long): Try<CountingInputStream, ReadError> {
// Reuse the current stream if it didn't exceed the requested index.
stream
?.takeIf { it.count <= fromIndex }
?.let { return Try.success(it) }

stream?.close()

val contentStream =
contentResolver.openInputStream(uri)
?: return Try.failure(
ReadError.Access(
ContentResolverError.NotAvailable()
)
)
stream.use { block(stream) }
}

stream = CountingInputStream(contentStream)

return Try.success(stream!!)
}

private inline fun <T> Try.Companion.catching(closure: () -> T): Try<T, ReadError> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,16 @@ public class CountingInputStream(
return ByteArray(0)
}

skip(range.first - count)
val toSkip = range.first - count
var skipped: Long = 0

while (skipped != toSkip) {
skipped += skip(toSkip - skipped)
if (skipped == 0L) {
throw IOException("Could not skip InputStream to read ranges.")
}
}

val length = range.last - range.first + 1
return read(length)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,13 @@ internal class StreamingZipArchiveProvider {
val datasourceChannel = ReadableChannelAdapter(readable, wrapError)
val channel = wrapBaseChannel(datasourceChannel)
val zipFile = ZipFile(channel, true)
StreamingZipContainer(zipFile, sourceUrl)
val sourceScheme = (readable as? Resource)?.sourceUrl?.scheme
val cacheEntryMaxSize =
when {
sourceScheme?.isContent ?: false -> 5242880
else -> 0
}
StreamingZipContainer(zipFile, sourceUrl, cacheEntryMaxSize)
}

internal suspend fun openFile(file: File): Container<Resource> = withContext(Dispatchers.IO) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,18 @@ import org.readium.r2.shared.util.zip.compress.archivers.zip.ZipFile

internal class StreamingZipContainer(
private val zipFile: ZipFile,
override val sourceUrl: AbsoluteUrl?
override val sourceUrl: AbsoluteUrl?,
private val cacheEntryMaxSize: Int = 0
) : Container<Resource> {

private inner class Entry(
private val url: Url,
private val entry: ZipArchiveEntry
) : Resource {

private var cache: ByteArray? =
null

override val sourceUrl: AbsoluteUrl? get() = null

override suspend fun properties(): ReadTry<Resource.Properties> =
Expand Down Expand Up @@ -102,8 +106,25 @@ internal class StreamingZipContainer(
it.readFully()
}

private fun readRange(range: LongRange): ByteArray =
stream(range.first).readRange(range)
private suspend fun readRange(range: LongRange): ByteArray =
when {
cache != null -> {
// If the entry is cached, its size fit into an Int.
val rangeSize = (range.last - range.first + 1).toInt()
cache!!.copyInto(
ByteArray(rangeSize),
startIndex = range.first.toInt(),
endIndex = range.last.toInt() + 1
)
}

entry.size in 0 until cacheEntryMaxSize -> {
cache = readFully()
readRange(range)
}
else ->
stream(range.first).readRange(range)
}

/**
* Reading an entry in chunks (e.g. from the HTTP server) can be really slow if the entry
Expand Down

0 comments on commit 5f0aa70

Please sign in to comment.