Merge pull request #353 from gotify/concurrent-image-access

fix: image loading when using markdown img and bigImageUrl
This commit is contained in:
Jannis Mattheis
2024-06-18 19:44:57 +02:00
committed by GitHub
5 changed files with 65 additions and 54 deletions

View File

@@ -16,13 +16,62 @@ import java.io.IOException
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.tinylog.kotlin.Logger import org.tinylog.kotlin.Logger
internal class CoilHandler(private val context: Context, private val settings: Settings) { object CoilInstance {
private val imageLoader = makeImageLoader() private var holder: Pair<SSLSettings, ImageLoader>? = null
private fun makeImageLoader(): ImageLoader { @Throws(IOException::class)
fun getImageFromUrl(context: Context, url: String?): Bitmap {
val request = ImageRequest.Builder(context)
.data(url)
.build()
return (get(context).executeBlocking(request).drawable as BitmapDrawable).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(
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)
}
@OptIn(ExperimentalCoilApi::class)
fun evict(context: Context) {
try {
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() val builder = OkHttpClient.Builder()
CertUtils.applySslSettings(builder, settings.sslSettings()) CertUtils.applySslSettings(builder, sslSettings)
return ImageLoader.Builder(context) val loader = ImageLoader.Builder(context)
.okHttpClient(builder.build()) .okHttpClient(builder.build())
.diskCache { .diskCache {
DiskCache.Builder() DiskCache.Builder()
@@ -33,39 +82,6 @@ internal class CoilHandler(private val context: Context, private val settings: S
add(SvgDecoder.Factory()) add(SvgDecoder.Factory())
} }
.build() .build()
} return sslSettings to loader
@Throws(IOException::class)
fun getImageFromUrl(url: String?): Bitmap {
val request = ImageRequest.Builder(context)
.data(url)
.build()
return (imageLoader.executeBlocking(request).drawable as BitmapDrawable).bitmap
}
fun getIcon(app: Application?): Bitmap {
if (app == null) {
return BitmapFactory.decodeResource(context.resources, R.drawable.gotify)
}
try {
return getImageFromUrl(
Utils.resolveAbsoluteUrl("${settings.url}/", 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() {
try {
imageLoader.diskCache?.clear()
imageLoader.memoryCache?.clear()
} catch (e: IOException) {
Logger.error(e, "Problem evicting Coil cache")
}
} }
} }

View File

@@ -1,6 +1,6 @@
package com.github.gotify package com.github.gotify
internal class SSLSettings( internal data class SSLSettings(
val validateSSL: Boolean, val validateSSL: Boolean,
val caCertPath: String?, val caCertPath: String?,
val clientCertPath: String?, val clientCertPath: String?,

View File

@@ -31,6 +31,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import coil.request.ImageRequest import coil.request.ImageRequest
import com.github.gotify.BuildConfig import com.github.gotify.BuildConfig
import com.github.gotify.CoilInstance
import com.github.gotify.MissedMessageUtil import com.github.gotify.MissedMessageUtil
import com.github.gotify.R import com.github.gotify.R
import com.github.gotify.Utils import com.github.gotify.Utils
@@ -102,7 +103,7 @@ internal class MessagesActivity :
listMessageAdapter = ListMessageAdapter( listMessageAdapter = ListMessageAdapter(
this, this,
viewModel.settings, viewModel.settings,
viewModel.coilHandler.get() CoilInstance.get(this)
) { message -> ) { message ->
scheduleDeletion(message) scheduleDeletion(message)
} }
@@ -169,13 +170,13 @@ internal class MessagesActivity :
} }
private fun refreshAll() { private fun refreshAll() {
viewModel.coilHandler.evict() CoilInstance.evict(this)
startActivity(Intent(this, InitializationActivity::class.java)) startActivity(Intent(this, InitializationActivity::class.java))
finish() finish()
} }
private fun onRefresh() { private fun onRefresh() {
viewModel.coilHandler.evict() CoilInstance.evict(this)
viewModel.messages.clear() viewModel.messages.clear()
launchCoroutine { launchCoroutine {
loadMore(viewModel.appId).forEachIndexed { index, message -> loadMore(viewModel.appId).forEachIndexed { index, message ->
@@ -211,9 +212,7 @@ internal class MessagesActivity :
.size(100, 100) .size(100, 100)
.target(t) .target(t)
.build() .build()
viewModel.coilHandler CoilInstance.get(this).enqueue(request)
.get()
.enqueue(request)
} }
selectAppInMenu(selectedItem) selectAppInMenu(selectedItem)
} }

View File

@@ -3,7 +3,6 @@ package com.github.gotify.messages
import android.app.Activity import android.app.Activity
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import coil.target.Target import coil.target.Target
import com.github.gotify.CoilHandler
import com.github.gotify.Settings import com.github.gotify.Settings
import com.github.gotify.api.ClientFactory import com.github.gotify.api.ClientFactory
import com.github.gotify.client.api.MessageApi import com.github.gotify.client.api.MessageApi
@@ -13,7 +12,6 @@ import com.github.gotify.messages.provider.MessageState
internal class MessagesModel(parentView: Activity) : ViewModel() { internal class MessagesModel(parentView: Activity) : ViewModel() {
val settings = Settings(parentView) val settings = Settings(parentView)
val coilHandler = CoilHandler(parentView, settings)
val client = ClientFactory.clientToken(settings) val client = ClientFactory.clientToken(settings)
val appsHolder = ApplicationHolder(parentView, client) val appsHolder = ApplicationHolder(parentView, client)
val messages = MessageFacade(client.createService(MessageApi::class.java), appsHolder) val messages = MessageFacade(client.createService(MessageApi::class.java), appsHolder)

View File

@@ -17,7 +17,7 @@ import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.github.gotify.BuildConfig import com.github.gotify.BuildConfig
import com.github.gotify.CoilHandler import com.github.gotify.CoilInstance
import com.github.gotify.MarkwonFactory import com.github.gotify.MarkwonFactory
import com.github.gotify.MissedMessageUtil import com.github.gotify.MissedMessageUtil
import com.github.gotify.NotificationSupport import com.github.gotify.NotificationSupport
@@ -62,7 +62,6 @@ internal class WebSocketService : Service() {
private val lastReceivedMessage = AtomicLong(NOT_LOADED) private val lastReceivedMessage = AtomicLong(NOT_LOADED)
private lateinit var missingMessageUtil: MissedMessageUtil private lateinit var missingMessageUtil: MissedMessageUtil
private lateinit var coilHandler: CoilHandler
private lateinit var markwon: Markwon private lateinit var markwon: Markwon
override fun onCreate() { override fun onCreate() {
@@ -71,8 +70,7 @@ internal class WebSocketService : Service() {
val client = ClientFactory.clientToken(settings) val client = ClientFactory.clientToken(settings)
missingMessageUtil = MissedMessageUtil(client.createService(MessageApi::class.java)) missingMessageUtil = MissedMessageUtil(client.createService(MessageApi::class.java))
Logger.info("Create ${javaClass.simpleName}") Logger.info("Create ${javaClass.simpleName}")
coilHandler = CoilHandler(this, settings) markwon = MarkwonFactory.createForNotification(this, CoilInstance.get(this))
markwon = MarkwonFactory.createForNotification(this, coilHandler.get())
} }
override fun onDestroy() { override fun onDestroy() {
@@ -377,7 +375,7 @@ internal class WebSocketService : Service() {
.setDefaults(Notification.DEFAULT_ALL) .setDefaults(Notification.DEFAULT_ALL)
.setWhen(System.currentTimeMillis()) .setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_gotify) .setSmallIcon(R.drawable.ic_gotify)
.setLargeIcon(coilHandler.getIcon(appIdToApp[appId])) .setLargeIcon(CoilInstance.getIcon(this, appIdToApp[appId]))
.setTicker("${getString(R.string.app_name)} - $title") .setTicker("${getString(R.string.app_name)} - $title")
.setGroup(NotificationSupport.Group.MESSAGES) .setGroup(NotificationSupport.Group.MESSAGES)
.setContentTitle(title) .setContentTitle(title)
@@ -406,7 +404,7 @@ internal class WebSocketService : Service() {
try { try {
b.setStyle( b.setStyle(
NotificationCompat.BigPictureStyle() NotificationCompat.BigPictureStyle()
.bigPicture(coilHandler.getImageFromUrl(notificationImageUrl)) .bigPicture(CoilInstance.getImageFromUrl(this, notificationImageUrl))
) )
} catch (e: Exception) { } catch (e: Exception) {
Logger.error(e, "Error loading bigImageUrl") Logger.error(e, "Error loading bigImageUrl")