Skip to content

Commit

Permalink
fix: image loading when using markdown img and bigImageUrl
Browse files Browse the repository at this point in the history
When receiving an image with the same image in the markdown body and in
the extras client::notification.bigImageUrl, then there is a clash on
the file system.

One request succeeds and the other fails with the following error. This
commit ensures that there is only one cail image loader instance, so
that there shouldn't be file system race conditions.

    WebSocket(1): received message {"id":845,"appid":21,...}
    Failed - http://192.168.178.2:8000/1.jpg?v=1718369188 - java.lang.IllegalStateException: closed
    java.lang.IllegalStateException: closed
        at okio.RealBufferedSource.rangeEquals(RealBufferedSource.kt:466)
        at okio.RealBufferedSource.rangeEquals(RealBufferedSource.kt:130)
        at coil.decode.SvgDecodeUtils.isSvg(DecodeUtils.kt:19)
        at coil.decode.SvgDecoder$Factory.isApplicable(SvgDecoder.kt:104)
        at coil.decode.SvgDecoder$Factory.create(SvgDecoder.kt:99)
        at coil.ComponentRegistry.newDecoder(ComponentRegistry.kt:100)
        at coil.intercept.EngineInterceptor.decode(EngineInterceptor.kt:197)
        at coil.intercept.EngineInterceptor.access$decode(EngineInterceptor.kt:42)
        at coil.intercept.EngineInterceptor$execute$executeResult$1.invokeSuspend(EngineInterceptor.kt:131)
        at coil.intercept.EngineInterceptor$execute$executeResult$1.invoke(EngineInterceptor.kt)
        at coil.intercept.EngineInterceptor$execute$executeResult$1.invoke(EngineInterceptor.kt)
        at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:78)
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:167)
        at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
        at coil.intercept.EngineInterceptor.execute(EngineInterceptor.kt:130)
        at coil.intercept.EngineInterceptor.access$execute(EngineInterceptor.kt:42)
        at coil.intercept.EngineInterceptor$execute$1.invokeSuspend(EngineInterceptor.kt)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
        at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:115)
        at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:103)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
    Successful (NETWORK) - http://192.168.178.2:8000/1.jpg?v=1718369188
  • Loading branch information
jmattheis committed Jun 14, 2024
1 parent b9b767f commit 0e7f675
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,56 +16,72 @@ import java.io.IOException
import okhttp3.OkHttpClient
import org.tinylog.kotlin.Logger

internal class CoilHandler(private val context: Context, private val settings: Settings) {
private val imageLoader = makeImageLoader()

private fun makeImageLoader(): ImageLoader {
val builder = OkHttpClient.Builder()
CertUtils.applySslSettings(builder, settings.sslSettings())
return ImageLoader.Builder(context)
.okHttpClient(builder.build())
.diskCache {
DiskCache.Builder()
.directory(context.cacheDir.resolve("coil-cache"))
.build()
}
.components {
add(SvgDecoder.Factory())
}
.build()
}
object CoilInstance {
private var holder: Pair<SSLSettings, ImageLoader>? = null

@Throws(IOException::class)
fun getImageFromUrl(url: String?): Bitmap {
fun getImageFromUrl(context: Context, url: String?): Bitmap {
val request = ImageRequest.Builder(context)
.data(url)
.build()
return (imageLoader.executeBlocking(request).drawable as BitmapDrawable).bitmap
return (get(context).executeBlocking(request).drawable as BitmapDrawable).bitmap
}

fun getIcon(app: Application?): Bitmap {
fun getIcon(context: Context, app: Application?): Bitmap {
if (app == null) {
return BitmapFactory.decodeResource(context.resources, R.drawable.gotify)
}
val baseUrl = Settings(context).url
try {
return getImageFromUrl(
Utils.resolveAbsoluteUrl("${settings.url}/", app.image)
context,
Utils.resolveAbsoluteUrl("$baseUrl/", app.image)
)
} catch (e: IOException) {
Logger.error(e, "Could not load image for notification")
}
return BitmapFactory.decodeResource(context.resources, R.drawable.gotify)
}

fun get() = imageLoader

@OptIn(ExperimentalCoilApi::class)
fun evict() {
fun evict(context: Context) {
try {
imageLoader.diskCache?.clear()
imageLoader.memoryCache?.clear()
get(context).apply {
diskCache?.clear()
memoryCache?.clear()
}
} catch (e: IOException) {
Logger.error(e, "Problem evicting Coil cache")
}
}

@Synchronized
fun get(context: Context): ImageLoader {
val newSettings = Settings(context).sslSettings()
val copy = holder
if (copy != null && copy.first == newSettings) {
return copy.second
}
return makeImageLoader(context, newSettings).also { holder = it }.second
}

private fun makeImageLoader(
context: Context,
sslSettings: SSLSettings
): Pair<SSLSettings, ImageLoader> {
val builder = OkHttpClient.Builder()
CertUtils.applySslSettings(builder, sslSettings)
val loader = ImageLoader.Builder(context)
.okHttpClient(builder.build())
.diskCache {
DiskCache.Builder()
.directory(context.cacheDir.resolve("coil-cache"))
.build()
}
.components {
add(SvgDecoder.Factory())
}
.build()
return sslSettings to loader
}
}
2 changes: 1 addition & 1 deletion app/src/main/kotlin/com/github/gotify/SSLSettings.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.github.gotify

internal class SSLSettings(
internal data class SSLSettings(
val validateSSL: Boolean,
val caCertPath: String?,
val clientCertPath: String?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil.request.ImageRequest
import com.github.gotify.BuildConfig
import com.github.gotify.CoilInstance
import com.github.gotify.MissedMessageUtil
import com.github.gotify.R
import com.github.gotify.Utils
Expand Down Expand Up @@ -102,7 +103,7 @@ internal class MessagesActivity :
listMessageAdapter = ListMessageAdapter(
this,
viewModel.settings,
viewModel.coilHandler.get()
CoilInstance.get(this)
) { message ->
scheduleDeletion(message)
}
Expand Down Expand Up @@ -169,13 +170,13 @@ internal class MessagesActivity :
}

private fun refreshAll() {
viewModel.coilHandler.evict()
CoilInstance.evict(this)
startActivity(Intent(this, InitializationActivity::class.java))
finish()
}

private fun onRefresh() {
viewModel.coilHandler.evict()
CoilInstance.evict(this)
viewModel.messages.clear()
launchCoroutine {
loadMore(viewModel.appId).forEachIndexed { index, message ->
Expand Down Expand Up @@ -211,9 +212,7 @@ internal class MessagesActivity :
.size(100, 100)
.target(t)
.build()
viewModel.coilHandler
.get()
.enqueue(request)
CoilInstance.get(this).enqueue(request)
}
selectAppInMenu(selectedItem)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.github.gotify.messages
import android.app.Activity
import androidx.lifecycle.ViewModel
import coil.target.Target
import com.github.gotify.CoilHandler
import com.github.gotify.Settings
import com.github.gotify.api.ClientFactory
import com.github.gotify.client.api.MessageApi
Expand All @@ -13,7 +12,6 @@ import com.github.gotify.messages.provider.MessageState

internal class MessagesModel(parentView: Activity) : ViewModel() {
val settings = Settings(parentView)
val coilHandler = CoilHandler(parentView, settings)
val client = ClientFactory.clientToken(settings)
val appsHolder = ApplicationHolder(parentView, client)
val messages = MessageFacade(client.createService(MessageApi::class.java), appsHolder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.github.gotify.BuildConfig
import com.github.gotify.CoilHandler
import com.github.gotify.CoilInstance
import com.github.gotify.MarkwonFactory
import com.github.gotify.MissedMessageUtil
import com.github.gotify.NotificationSupport
Expand Down Expand Up @@ -62,7 +62,6 @@ internal class WebSocketService : Service() {
private val lastReceivedMessage = AtomicLong(NOT_LOADED)
private lateinit var missingMessageUtil: MissedMessageUtil

private lateinit var coilHandler: CoilHandler
private lateinit var markwon: Markwon

override fun onCreate() {
Expand All @@ -71,8 +70,7 @@ internal class WebSocketService : Service() {
val client = ClientFactory.clientToken(settings)
missingMessageUtil = MissedMessageUtil(client.createService(MessageApi::class.java))
Logger.info("Create ${javaClass.simpleName}")
coilHandler = CoilHandler(this, settings)
markwon = MarkwonFactory.createForNotification(this, coilHandler.get())
markwon = MarkwonFactory.createForNotification(this, CoilInstance.get(this))
}

override fun onDestroy() {
Expand Down Expand Up @@ -377,7 +375,7 @@ internal class WebSocketService : Service() {
.setDefaults(Notification.DEFAULT_ALL)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_gotify)
.setLargeIcon(coilHandler.getIcon(appIdToApp[appId]))
.setLargeIcon(CoilInstance.getIcon(this, appIdToApp[appId]))
.setTicker("${getString(R.string.app_name)} - $title")
.setGroup(NotificationSupport.Group.MESSAGES)
.setContentTitle(title)
Expand Down Expand Up @@ -406,7 +404,7 @@ internal class WebSocketService : Service() {
try {
b.setStyle(
NotificationCompat.BigPictureStyle()
.bigPicture(coilHandler.getImageFromUrl(notificationImageUrl))
.bigPicture(CoilInstance.getImageFromUrl(this, notificationImageUrl))
)
} catch (e: Exception) {
Logger.error(e, "Error loading bigImageUrl")
Expand Down

0 comments on commit 0e7f675

Please sign in to comment.