Merge pull request #353 from gotify/concurrent-image-access
fix: image loading when using markdown img and bigImageUrl
This commit is contained in:
@@ -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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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?,
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user