Fix Small Preview CrossFade not work (#1816)
* Fix Small Preview CrossFade not work * Abstract * Update MergeInterceptor.kt --------- Co-authored-by: Dude so hot <djohn@fbi.gov>
This commit is contained in:
1 parent
c1b701f1f9
commit
3b9cfefd5f
5 files changed
+78
-35
No files matched your search
@@ -10,7 +10,7 @@ import io.ktor.client.HttpClient
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.withContext
|
||||
import moe.tarsin.coroutines.NamedSemaphore
|
||||
import moe.tarsin.coroutines.withPermit
|
||||
import moe.tarsin.coroutines.withLock
|
||||
|
||||
class LimitConcurrencyNetworkClient(val impl: NetworkClient) : NetworkClient {
|
||||
val semaphores = NamedSemaphore<String>(permits = 8)
|
||||
@@ -18,9 +18,9 @@ class LimitConcurrencyNetworkClient(val impl: NetworkClient) : NetworkClient {
|
||||
val url = req.url
|
||||
return when {
|
||||
// Ex thumb server may not have h2 multiplexing support
|
||||
URL_PREFIX_THUMB_EX in url -> semaphores.withPermit(URL_PREFIX_THUMB_EX) { withContext(NonCancellable) { impl.executeRequest(req, f) } }
|
||||
URL_PREFIX_THUMB_EX in url -> semaphores.withLock(URL_PREFIX_THUMB_EX) { withContext(NonCancellable) { impl.executeRequest(req, f) } }
|
||||
// H@H server may not have h2 multiplexing support
|
||||
URL_SIGNATURE_THUMB_NORMAL in url -> semaphores.withPermit(url.substringBefore(URL_SIGNATURE_THUMB_NORMAL)) { withContext(NonCancellable) { impl.executeRequest(req, f) } }
|
||||
URL_SIGNATURE_THUMB_NORMAL in url -> semaphores.withLock(url.substringBefore(URL_SIGNATURE_THUMB_NORMAL)) { withContext(NonCancellable) { impl.executeRequest(req, f) } }
|
||||
// H2 multiplexing enabled
|
||||
else -> impl.executeRequest(req, f)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.hippo.ehviewer.coil
|
||||
|
||||
import coil3.decode.DataSource
|
||||
import coil3.intercept.Interceptor
|
||||
import coil3.request.ImageResult
|
||||
import coil3.request.SuccessResult
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.isNormalPreviewKey
|
||||
import moe.tarsin.coroutines.NamedMutex
|
||||
import moe.tarsin.coroutines.withLock
|
||||
import moe.tarsin.coroutines.withLockNeedSuspend
|
||||
|
||||
object MergeInterceptor : Interceptor {
|
||||
private val mutex = NamedMutex<String>()
|
||||
@@ -14,7 +16,11 @@ object MergeInterceptor : Interceptor {
|
||||
val req = chain.request
|
||||
val key = req.memoryCacheKey?.takeIf { it.isNormalPreviewKey || Settings.preloadThumbAggressively }
|
||||
return if (key != null) {
|
||||
mutex.withLock(key) { chain.proceed() }
|
||||
val (result, suspended) = mutex.withLockNeedSuspend(key) { chain.proceed() }
|
||||
when (result) {
|
||||
is SuccessResult if (suspended) -> result.copy(dataSource = DataSource.MEMORY)
|
||||
else -> result
|
||||
}
|
||||
} else {
|
||||
chain.proceed()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package moe.tarsin.coroutines
|
||||
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
interface LockPool<Lock, K> {
|
||||
fun acquire(key: K): Lock
|
||||
fun release(key: K, lock: Lock)
|
||||
suspend fun Lock.lock()
|
||||
fun Lock.tryLock(): Boolean
|
||||
fun Lock.unlock()
|
||||
}
|
||||
|
||||
suspend inline fun <Lock, K, R> LockPool<Lock, K>.withLock(key: K, action: () -> R): R {
|
||||
contract {
|
||||
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
val lock = acquire(key)
|
||||
return try {
|
||||
lock.lock()
|
||||
return try {
|
||||
action()
|
||||
} finally {
|
||||
lock.unlock()
|
||||
}
|
||||
} finally {
|
||||
release(key, lock)
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun <Lock, K, R> LockPool<Lock, K>.withLockNeedSuspend(key: K, action: () -> R): Pair<R, Boolean> {
|
||||
contract {
|
||||
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
val lock = acquire(key)
|
||||
return try {
|
||||
val mustSuspend = !lock.tryLock()
|
||||
if (mustSuspend) lock.lock()
|
||||
return try {
|
||||
action() to mustSuspend
|
||||
} finally {
|
||||
lock.unlock()
|
||||
}
|
||||
} finally {
|
||||
release(key, lock)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import androidx.collection.MutableScatterMap
|
||||
import androidx.collection.mutableScatterMapOf
|
||||
import io.ktor.utils.io.pool.DefaultPool
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
class MutexTracker(mutex: Mutex = Mutex(), private var count: Int = 0) : Mutex by mutex {
|
||||
operator fun inc() = apply { count++ }
|
||||
@@ -21,20 +20,16 @@ object MutexPool : DefaultPool<MutexTracker>(capacity = 32) {
|
||||
}
|
||||
}
|
||||
|
||||
@JvmInline
|
||||
value class NamedMutex<K>(val active: MutableScatterMap<K, MutexTracker> = mutableScatterMapOf())
|
||||
|
||||
suspend inline fun <K, R> NamedMutex<K>.withLock(key: K, owner: Any? = null, action: () -> R): R {
|
||||
val mutex = synchronized(active) { active.getOrPut(key) { MutexPool.borrow() }.inc() }
|
||||
return try {
|
||||
mutex.withLock(owner, action)
|
||||
} finally {
|
||||
synchronized(active) {
|
||||
mutex.dec()
|
||||
if (mutex.isFree) {
|
||||
active.remove(key)
|
||||
MutexPool.recycle(mutex)
|
||||
}
|
||||
class NamedMutex<K>(val active: MutableScatterMap<K, MutexTracker> = mutableScatterMapOf()) : LockPool<MutexTracker, K> {
|
||||
override fun acquire(key: K) = synchronized(active) { active.getOrPut(key) { MutexPool.borrow() }.inc() }
|
||||
override fun release(key: K, lock: MutexTracker) = synchronized(active) {
|
||||
lock.dec()
|
||||
if (lock.isFree) {
|
||||
active.remove(key)
|
||||
MutexPool.recycle(lock)
|
||||
}
|
||||
}
|
||||
override suspend fun MutexTracker.lock() = lock()
|
||||
override fun MutexTracker.tryLock() = tryLock()
|
||||
override fun MutexTracker.unlock() = unlock()
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package moe.tarsin.coroutines
|
||||
import androidx.collection.mutableScatterMapOf
|
||||
import io.ktor.utils.io.pool.DefaultPool
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
|
||||
class SemaphoreTracker(semaphore: Semaphore, private var count: Int = 0) : Semaphore by semaphore {
|
||||
operator fun inc() = apply { count++ }
|
||||
@@ -20,22 +19,18 @@ class SemaphorePool(val permits: Int) : DefaultPool<SemaphoreTracker>(capacity =
|
||||
}
|
||||
}
|
||||
|
||||
class NamedSemaphore<K>(val permits: Int) {
|
||||
class NamedSemaphore<K>(val permits: Int) : LockPool<SemaphoreTracker, K> {
|
||||
val pool = SemaphorePool(permits = permits)
|
||||
val active = mutableScatterMapOf<K, SemaphoreTracker>()
|
||||
}
|
||||
|
||||
suspend inline fun <K, R> NamedSemaphore<K>.withPermit(key: K, action: () -> R): R {
|
||||
val semaphore = synchronized(active) { active.getOrPut(key) { pool.borrow() }.inc() }
|
||||
return try {
|
||||
semaphore.withPermit(action)
|
||||
} finally {
|
||||
synchronized(active) {
|
||||
semaphore.dec()
|
||||
if (semaphore.isFree) {
|
||||
active.remove(key)
|
||||
pool.recycle(semaphore)
|
||||
}
|
||||
override fun acquire(key: K) = synchronized(active) { active.getOrPut(key) { pool.borrow() }.inc() }
|
||||
override fun release(key: K, lock: SemaphoreTracker) = synchronized(active) {
|
||||
lock.dec()
|
||||
if (lock.isFree) {
|
||||
active.remove(key)
|
||||
pool.recycle(lock)
|
||||
}
|
||||
}
|
||||
override suspend fun SemaphoreTracker.lock() = acquire()
|
||||
override fun SemaphoreTracker.tryLock() = tryAcquire()
|
||||
override fun SemaphoreTracker.unlock() = release()
|
||||
}
|
||||
Reference in new issue
Block a user