cache notification icons and delete after 24 hours

This commit is contained in:
Hunter Kehoe 2022-08-27 12:11:27 -06:00
parent a2ae6e4c21
commit c7edb50ebc
3 changed files with 51 additions and 8 deletions

View file

@ -14,7 +14,7 @@ import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application
import io.heckel.ntfy.db.*
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.util.ensureSafeNewFile
import io.heckel.ntfy.util.stringToHash
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
@ -44,7 +44,17 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
subscription = repository.getSubscription(notification.subscriptionId) ?: return Result.failure()
icon = notification.icon ?: return Result.failure()
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) {
failed(e)
}
@ -56,7 +66,7 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
maybeDeleteFile()
}
private fun downloadIcon() {
private fun downloadIcon(iconFile: File) {
Log.d(TAG, "Downloading icon from ${icon.url}")
try {
@ -74,7 +84,7 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
return
}
val resolver = applicationContext.contentResolver
val uri = createUri(notification)
val uri = createIconUri(iconFile)
this.uri = uri // Required for cleanup in onStopped()
Log.d(TAG, "Starting download to content URI: $uri")
@ -137,13 +147,17 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
return size > maxAutoDownloadSize
}
private fun createUri(notification: Notification): Uri {
private fun createIconFile(icon: Icon): File {
val iconDir = File(context.cacheDir, ICON_CACHE_DIR)
if (!iconDir.exists() && !iconDir.mkdirs()) {
throw Exception("Cannot create cache directory for icons: $iconDir")
}
val file = ensureSafeNewFile(iconDir, notification.id)
return FileProvider.getUriForFile(context, FILE_PROVIDER_AUTHORITY, file)
val hash = stringToHash(icon.url)
return File(iconDir, hash)
}
private fun createIconUri(iconFile: File): Uri {
return FileProvider.getUriForFile(context, FILE_PROVIDER_AUTHORITY, iconFile)
}
companion object {
@ -152,7 +166,7 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
const val MAX_ICON_DOWNLOAD_SIZE = 300000
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
}
}

View file

@ -38,6 +38,7 @@ import okhttp3.RequestBody
import okio.BufferedSink
import okio.source
import java.io.*
import java.security.MessageDigest
import java.security.SecureRandom
import java.text.DateFormat
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)
.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) }
}

View file

@ -7,11 +7,14 @@ import androidx.work.WorkerParameters
import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.db.ATTACHMENT_PROGRESS_DELETED
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.msg.DownloadIconWorker
import io.heckel.ntfy.ui.DetailAdapter
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.util.topicShortUrl
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.util.*
/**
* 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
deleteExpiredAttachments() // Before notifications, so we will also catch manually deleted notifications
deleteExpiredNotifications()
cleanIconCache()
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() {
Log.d(TAG, "Deleting expired notifications")
val repository = Repository.getInstance(applicationContext)