mirror of
https://github.com/immich-app/immich.git
synced 2026-02-15 13:28:24 +03:00
refactor, fix capacity handling
This commit is contained in:
@@ -30,20 +30,20 @@ class NativeByteBuffer(initialCapacity: Int) {
|
||||
var capacity = initialCapacity
|
||||
var offset = 0
|
||||
|
||||
fun ensureHeadroom(needed: Int = INITIAL_BUFFER_SIZE) {
|
||||
if (offset + needed > capacity) {
|
||||
capacity = (capacity * 2).coerceAtLeast(offset + needed)
|
||||
inline fun ensureHeadroom() {
|
||||
if (offset == capacity) {
|
||||
capacity *= 2
|
||||
pointer = NativeBuffer.realloc(pointer, capacity)
|
||||
}
|
||||
}
|
||||
|
||||
fun wrapRemaining() = NativeBuffer.wrap(pointer + offset, capacity - offset)
|
||||
inline fun wrapRemaining() = NativeBuffer.wrap(pointer + offset, capacity - offset)
|
||||
|
||||
fun advance(bytesRead: Int) {
|
||||
inline fun advance(bytesRead: Int) {
|
||||
offset += bytesRead
|
||||
}
|
||||
|
||||
fun free() {
|
||||
inline fun free() {
|
||||
if (pointer != 0L) {
|
||||
NativeBuffer.free(pointer)
|
||||
pointer = 0L
|
||||
|
||||
@@ -59,7 +59,7 @@ private open class LocalImagesPigeonCodec : StandardMessageCodec() {
|
||||
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface LocalImageApi {
|
||||
fun requestImage(assetId: String, requestId: Long, width: Long, height: Long, isVideo: Boolean, callback: (Result<Map<String, Long>>) -> Unit)
|
||||
fun requestImage(assetId: String, requestId: Long, width: Long, height: Long, isVideo: Boolean, callback: (Result<Map<String, Long>?>) -> Unit)
|
||||
fun cancelRequest(requestId: Long)
|
||||
fun getThumbhash(thumbhash: String, callback: (Result<Map<String, Long>>) -> Unit)
|
||||
|
||||
@@ -82,7 +82,7 @@ interface LocalImageApi {
|
||||
val widthArg = args[2] as Long
|
||||
val heightArg = args[3] as Long
|
||||
val isVideoArg = args[4] as Boolean
|
||||
api.requestImage(assetIdArg, requestIdArg, widthArg, heightArg, isVideoArg) { result: Result<Map<String, Long>> ->
|
||||
api.requestImage(assetIdArg, requestIdArg, widthArg, heightArg, isVideoArg) { result: Result<Map<String, Long>?> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(LocalImagesPigeonUtils.wrapError(error))
|
||||
|
||||
@@ -27,7 +27,7 @@ import java.util.concurrent.Future
|
||||
data class Request(
|
||||
val taskFuture: Future<*>,
|
||||
val cancellationSignal: CancellationSignal,
|
||||
val callback: (Result<Map<String, Long>>) -> Unit
|
||||
val callback: (Result<Map<String, Long>?>) -> Unit
|
||||
)
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
@@ -71,7 +71,7 @@ class LocalImagesImpl(context: Context) : LocalImageApi {
|
||||
private val requestMap = ConcurrentHashMap<Long, Request>()
|
||||
|
||||
companion object {
|
||||
val CANCELLED = Result.success<Map<String, Long>>(mapOf())
|
||||
val CANCELLED = Result.success<Map<String, Long>?>(null)
|
||||
val OPTIONS = BitmapFactory.Options().apply { inPreferredConfig = Bitmap.Config.ARGB_8888 }
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ class LocalImagesImpl(context: Context) : LocalImageApi {
|
||||
width: Long,
|
||||
height: Long,
|
||||
isVideo: Boolean,
|
||||
callback: (Result<Map<String, Long>>) -> Unit
|
||||
callback: (Result<Map<String, Long>?>) -> Unit
|
||||
) {
|
||||
val signal = CancellationSignal()
|
||||
val task = threadPool.submit {
|
||||
@@ -138,7 +138,7 @@ class LocalImagesImpl(context: Context) : LocalImageApi {
|
||||
width: Long,
|
||||
height: Long,
|
||||
isVideo: Boolean,
|
||||
callback: (Result<Map<String, Long>>) -> Unit,
|
||||
callback: (Result<Map<String, Long>?>) -> Unit,
|
||||
signal: CancellationSignal
|
||||
) {
|
||||
signal.throwIfCanceled()
|
||||
|
||||
@@ -47,7 +47,7 @@ private open class RemoteImagesPigeonCodec : StandardMessageCodec() {
|
||||
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface RemoteImageApi {
|
||||
fun requestImage(url: String, headers: Map<String, String>, requestId: Long, callback: (Result<Map<String, Long>>) -> Unit)
|
||||
fun requestImage(url: String, headers: Map<String, String>, requestId: Long, callback: (Result<Map<String, Long>?>) -> Unit)
|
||||
fun cancelRequest(requestId: Long)
|
||||
|
||||
companion object {
|
||||
@@ -67,7 +67,7 @@ interface RemoteImageApi {
|
||||
val urlArg = args[0] as String
|
||||
val headersArg = args[1] as Map<String, String>
|
||||
val requestIdArg = args[2] as Long
|
||||
api.requestImage(urlArg, headersArg, requestIdArg) { result: Result<Map<String, Long>> ->
|
||||
api.requestImage(urlArg, headersArg, requestIdArg) { result: Result<Map<String, Long>?> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(RemoteImagesPigeonUtils.wrapError(error))
|
||||
|
||||
@@ -46,14 +46,14 @@ class RemoteImagesImpl(context: Context) : RemoteImageApi {
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CANCELLED = Result.success<Map<String, Long>>(emptyMap())
|
||||
val CANCELLED = Result.success<Map<String, Long>?>(null)
|
||||
}
|
||||
|
||||
override fun requestImage(
|
||||
url: String,
|
||||
headers: Map<String, String>,
|
||||
requestId: Long,
|
||||
callback: (Result<Map<String, Long>>) -> Unit
|
||||
callback: (Result<Map<String, Long>?>) -> Unit
|
||||
) {
|
||||
val signal = CancellationSignal()
|
||||
requestMap[requestId] = RemoteRequest(signal)
|
||||
@@ -167,7 +167,7 @@ private class CronetImageFetcher(context: Context, cacheDir: File) : ImageFetche
|
||||
.enableBrotli(true)
|
||||
.setStoragePath(storageDir.absolutePath)
|
||||
.setUserAgent(USER_AGENT)
|
||||
.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK, CACHE_SIZE_BYTES)
|
||||
// .enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK, CACHE_SIZE_BYTES)
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ private class CronetImageFetcher(context: Context, cacheDir: File) : ImageFetche
|
||||
val requestBuilder = engine.newUrlRequestBuilder(url, callback, executor)
|
||||
headers.forEach { (key, value) -> requestBuilder.addHeader(key, value) }
|
||||
val request = requestBuilder.build()
|
||||
signal.setOnCancelListener { request.cancel() }
|
||||
signal.setOnCancelListener(request::cancel)
|
||||
request.start()
|
||||
}
|
||||
|
||||
@@ -222,6 +222,7 @@ private class CronetImageFetcher(context: Context, cacheDir: File) : ImageFetche
|
||||
private val onFailure: (Exception) -> Unit,
|
||||
private val onComplete: () -> Unit,
|
||||
) : UrlRequest.Callback() {
|
||||
private var contentLength: Int = 0
|
||||
private var buffer: NativeByteBuffer? = null
|
||||
private var httpError: IOException? = null
|
||||
|
||||
@@ -235,9 +236,9 @@ private class CronetImageFetcher(context: Context, cacheDir: File) : ImageFetche
|
||||
return request.cancel()
|
||||
}
|
||||
|
||||
// Pre-size from Content-Length when available, otherwise use reasonable default
|
||||
val capacity = info.allHeaders["content-length"]?.firstOrNull()?.toIntOrNull()
|
||||
?.takeIf { it > 0 } ?: INITIAL_BUFFER_SIZE
|
||||
contentLength = info.allHeaders["content-length"]?.firstOrNull()?.toIntOrNull() ?: 0
|
||||
// Cronet wants the buffer to always have free space, so increment by 1
|
||||
val capacity = if (contentLength > 0) contentLength + 1 else INITIAL_BUFFER_SIZE
|
||||
buffer = NativeByteBuffer(capacity)
|
||||
request.read(buffer!!.wrapRemaining())
|
||||
}
|
||||
@@ -248,7 +249,7 @@ private class CronetImageFetcher(context: Context, cacheDir: File) : ImageFetche
|
||||
byteBuffer: ByteBuffer
|
||||
) {
|
||||
buffer!!.apply {
|
||||
advance(byteBuffer.remaining())
|
||||
advance(byteBuffer.position())
|
||||
ensureHeadroom()
|
||||
}
|
||||
request.read(buffer!!.wrapRemaining())
|
||||
@@ -303,7 +304,7 @@ private class OkHttpImageFetcher private constructor(
|
||||
}
|
||||
.dispatcher(Dispatcher().apply { maxRequestsPerHost = MAX_REQUESTS_PER_HOST })
|
||||
.connectionPool(connectionPool)
|
||||
.cache(Cache(File(dir, "thumbnails"), CACHE_SIZE_BYTES))
|
||||
// .cache(Cache(File(dir, "thumbnails"), CACHE_SIZE_BYTES))
|
||||
|
||||
if (sslSocketFactory != null && trustManager != null) {
|
||||
builder.sslSocketFactory(sslSocketFactory, trustManager)
|
||||
@@ -345,8 +346,7 @@ private class OkHttpImageFetcher private constructor(
|
||||
) {
|
||||
synchronized(stateLock) {
|
||||
if (draining) {
|
||||
onFailure(IllegalStateException("Client is draining"))
|
||||
return
|
||||
return onFailure(IllegalStateException("Client is draining"))
|
||||
}
|
||||
activeCount++
|
||||
}
|
||||
@@ -354,7 +354,7 @@ private class OkHttpImageFetcher private constructor(
|
||||
val requestBuilder = Request.Builder().url(url)
|
||||
headers.forEach { (key, value) -> requestBuilder.addHeader(key, value) }
|
||||
val call = client.newCall(requestBuilder.build())
|
||||
signal.setOnCancelListener { call.cancel() }
|
||||
signal.setOnCancelListener(call::cancel)
|
||||
|
||||
call.enqueue(object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
@@ -363,45 +363,38 @@ private class OkHttpImageFetcher private constructor(
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
try {
|
||||
response.use {
|
||||
if (call.isCanceled()) {
|
||||
return onFailure(OperationCanceledException())
|
||||
}
|
||||
response.use {
|
||||
if (!response.isSuccessful) {
|
||||
return onFailure(IOException("HTTP ${response.code}: ${response.message}")).also { onComplete() }
|
||||
}
|
||||
|
||||
if (!response.isSuccessful) {
|
||||
return onFailure(IOException("HTTP ${response.code}: ${response.message}"))
|
||||
}
|
||||
val body = response.body
|
||||
?: return onFailure(IOException("Empty response body")).also { onComplete() }
|
||||
|
||||
val body = response.body ?: return onFailure(IOException("Empty response body"))
|
||||
|
||||
val contentLength = body.contentLength()
|
||||
val capacity = if (contentLength > 0 && contentLength <= Int.MAX_VALUE) {
|
||||
contentLength.toInt()
|
||||
} else {
|
||||
INITIAL_BUFFER_SIZE
|
||||
}
|
||||
val buffer = NativeByteBuffer(capacity)
|
||||
if (call.isCanceled()) {
|
||||
onFailure(OperationCanceledException())
|
||||
return onComplete()
|
||||
}
|
||||
|
||||
val contentLength = body.contentLength().toInt()
|
||||
val capacity = if (contentLength > 0) contentLength + 1 else INITIAL_BUFFER_SIZE
|
||||
val buffer = NativeByteBuffer(capacity)
|
||||
body.source().use { source ->
|
||||
try {
|
||||
body.source().use { source ->
|
||||
while (true) {
|
||||
signal.throwIfCanceled()
|
||||
buffer.ensureHeadroom()
|
||||
val bytesRead = source.read(buffer.wrapRemaining())
|
||||
if (bytesRead == -1) break
|
||||
buffer.advance(bytesRead)
|
||||
}
|
||||
while (true) {
|
||||
if (call.isCanceled()) throw OperationCanceledException()
|
||||
val bytesRead = source.read(buffer.wrapRemaining())
|
||||
if (bytesRead == -1) break
|
||||
buffer.ensureHeadroom()
|
||||
buffer.advance(bytesRead)
|
||||
}
|
||||
|
||||
onSuccess(buffer)
|
||||
} catch (e: Exception) {
|
||||
buffer.free()
|
||||
onFailure(e)
|
||||
}
|
||||
onComplete()
|
||||
}
|
||||
} finally {
|
||||
onComplete()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user