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.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
} }
} }

View file

@ -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) }
}

View file

@ -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)