cache notification icons and delete after 24 hours
This commit is contained in:
parent
a2ae6e4c21
commit
c7edb50ebc
3 changed files with 51 additions and 8 deletions
|
@ -14,7 +14,7 @@ import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.app.Application
|
import io.heckel.ntfy.app.Application
|
||||||
import io.heckel.ntfy.db.*
|
import io.heckel.ntfy.db.*
|
||||||
import io.heckel.ntfy.util.Log
|
import io.heckel.ntfy.util.Log
|
||||||
import io.heckel.ntfy.util.ensureSafeNewFile
|
import io.heckel.ntfy.util.stringToHash
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
@ -44,7 +44,17 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
|
||||||
subscription = repository.getSubscription(notification.subscriptionId) ?: return Result.failure()
|
subscription = repository.getSubscription(notification.subscriptionId) ?: return Result.failure()
|
||||||
icon = notification.icon ?: return Result.failure()
|
icon = notification.icon ?: return Result.failure()
|
||||||
try {
|
try {
|
||||||
downloadIcon()
|
val iconFile = createIconFile(icon)
|
||||||
|
if (!iconFile.exists()) {
|
||||||
|
downloadIcon(iconFile)
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Loading icon from cache: ${icon.url}")
|
||||||
|
val iconUri = createIconUri(iconFile)
|
||||||
|
this.uri = iconUri // Required for cleanup in onStopped()
|
||||||
|
save(icon.copy(
|
||||||
|
contentUri = iconUri.toString()
|
||||||
|
))
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
failed(e)
|
failed(e)
|
||||||
}
|
}
|
||||||
|
@ -56,7 +66,7 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
|
||||||
maybeDeleteFile()
|
maybeDeleteFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun downloadIcon() {
|
private fun downloadIcon(iconFile: File) {
|
||||||
Log.d(TAG, "Downloading icon from ${icon.url}")
|
Log.d(TAG, "Downloading icon from ${icon.url}")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -74,7 +84,7 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val resolver = applicationContext.contentResolver
|
val resolver = applicationContext.contentResolver
|
||||||
val uri = createUri(notification)
|
val uri = createIconUri(iconFile)
|
||||||
this.uri = uri // Required for cleanup in onStopped()
|
this.uri = uri // Required for cleanup in onStopped()
|
||||||
|
|
||||||
Log.d(TAG, "Starting download to content URI: $uri")
|
Log.d(TAG, "Starting download to content URI: $uri")
|
||||||
|
@ -137,13 +147,17 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
|
||||||
return size > maxAutoDownloadSize
|
return size > maxAutoDownloadSize
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createUri(notification: Notification): Uri {
|
private fun createIconFile(icon: Icon): File {
|
||||||
val iconDir = File(context.cacheDir, ICON_CACHE_DIR)
|
val iconDir = File(context.cacheDir, ICON_CACHE_DIR)
|
||||||
if (!iconDir.exists() && !iconDir.mkdirs()) {
|
if (!iconDir.exists() && !iconDir.mkdirs()) {
|
||||||
throw Exception("Cannot create cache directory for icons: $iconDir")
|
throw Exception("Cannot create cache directory for icons: $iconDir")
|
||||||
}
|
}
|
||||||
val file = ensureSafeNewFile(iconDir, notification.id)
|
val hash = stringToHash(icon.url)
|
||||||
return FileProvider.getUriForFile(context, FILE_PROVIDER_AUTHORITY, file)
|
return File(iconDir, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createIconUri(iconFile: File): Uri {
|
||||||
|
return FileProvider.getUriForFile(context, FILE_PROVIDER_AUTHORITY, iconFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -152,7 +166,7 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
|
||||||
const val MAX_ICON_DOWNLOAD_SIZE = 300000
|
const val MAX_ICON_DOWNLOAD_SIZE = 300000
|
||||||
|
|
||||||
private const val TAG = "NtfyIconDownload"
|
private const val TAG = "NtfyIconDownload"
|
||||||
private const val ICON_CACHE_DIR = "icons"
|
const val ICON_CACHE_DIR = "icons"
|
||||||
private const val BUFFER_SIZE = 8 * 1024
|
private const val BUFFER_SIZE = 8 * 1024
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import okhttp3.RequestBody
|
||||||
import okio.BufferedSink
|
import okio.BufferedSink
|
||||||
import okio.source
|
import okio.source
|
||||||
import java.io.*
|
import java.io.*
|
||||||
|
import java.security.MessageDigest
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.StringCharacterIterator
|
import java.text.StringCharacterIterator
|
||||||
|
@ -469,3 +470,10 @@ fun copyToClipboard(context: Context, notification: Notification) {
|
||||||
.makeText(context, context.getString(R.string.detail_copied_to_clipboard_message), Toast.LENGTH_LONG)
|
.makeText(context, context.getString(R.string.detail_copied_to_clipboard_message), Toast.LENGTH_LONG)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun stringToHash(s: String): String {
|
||||||
|
val bytes = s.toByteArray();
|
||||||
|
val md = MessageDigest.getInstance("SHA-256")
|
||||||
|
val digest = md.digest(bytes)
|
||||||
|
return digest.fold("") { str, it -> str + "%02x".format(it) }
|
||||||
|
}
|
|
@ -7,11 +7,14 @@ import androidx.work.WorkerParameters
|
||||||
import io.heckel.ntfy.BuildConfig
|
import io.heckel.ntfy.BuildConfig
|
||||||
import io.heckel.ntfy.db.ATTACHMENT_PROGRESS_DELETED
|
import io.heckel.ntfy.db.ATTACHMENT_PROGRESS_DELETED
|
||||||
import io.heckel.ntfy.db.Repository
|
import io.heckel.ntfy.db.Repository
|
||||||
|
import io.heckel.ntfy.msg.DownloadIconWorker
|
||||||
import io.heckel.ntfy.ui.DetailAdapter
|
import io.heckel.ntfy.ui.DetailAdapter
|
||||||
import io.heckel.ntfy.util.Log
|
import io.heckel.ntfy.util.Log
|
||||||
import io.heckel.ntfy.util.topicShortUrl
|
import io.heckel.ntfy.util.topicShortUrl
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes notifications marked for deletion and attachments for deleted notifications.
|
* Deletes notifications marked for deletion and attachments for deleted notifications.
|
||||||
|
@ -30,6 +33,7 @@ class DeleteWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx
|
||||||
deleteExpiredIcons() // Before notifications, so we will also catch manually deleted notifications
|
deleteExpiredIcons() // Before notifications, so we will also catch manually deleted notifications
|
||||||
deleteExpiredAttachments() // Before notifications, so we will also catch manually deleted notifications
|
deleteExpiredAttachments() // Before notifications, so we will also catch manually deleted notifications
|
||||||
deleteExpiredNotifications()
|
deleteExpiredNotifications()
|
||||||
|
cleanIconCache()
|
||||||
return@withContext Result.success()
|
return@withContext Result.success()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,6 +89,23 @@ class DeleteWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun cleanIconCache() {
|
||||||
|
Log.d(DeleteWorker.TAG, "Cleaning icons older than 24 hours from cache")
|
||||||
|
val iconDir = File(applicationContext.cacheDir, DownloadIconWorker.ICON_CACHE_DIR)
|
||||||
|
if (iconDir.exists()) {
|
||||||
|
for (f: File in iconDir.listFiles()) {
|
||||||
|
var lastModified = f.lastModified()
|
||||||
|
var today = Date()
|
||||||
|
|
||||||
|
var diffInHours = ((today.time - lastModified) / (1000 * 60 * 60))
|
||||||
|
if (diffInHours > 24) {
|
||||||
|
Log.d(DeleteWorker.TAG, "Deleting cached icon: ${f.name}")
|
||||||
|
f.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun deleteExpiredNotifications() {
|
private suspend fun deleteExpiredNotifications() {
|
||||||
Log.d(TAG, "Deleting expired notifications")
|
Log.d(TAG, "Deleting expired notifications")
|
||||||
val repository = Repository.getInstance(applicationContext)
|
val repository = Repository.getInstance(applicationContext)
|
||||||
|
|
Loading…
Reference in a new issue