Loading app/src/main/java/io/heckel/ntfy/msg/DownloadIconWorker.kt +22 −8 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) } Loading @@ -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 { Loading @@ -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") Loading Loading @@ -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 { Loading @@ -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 } } app/src/main/java/io/heckel/ntfy/util/Util.kt +8 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) } } No newline at end of file app/src/main/java/io/heckel/ntfy/work/DeleteWorker.kt +21 −0 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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() } } Loading Loading @@ -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) Loading Loading
app/src/main/java/io/heckel/ntfy/msg/DownloadIconWorker.kt +22 −8 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) } Loading @@ -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 { Loading @@ -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") Loading Loading @@ -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 { Loading @@ -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 } }
app/src/main/java/io/heckel/ntfy/util/Util.kt +8 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) } } No newline at end of file
app/src/main/java/io/heckel/ntfy/work/DeleteWorker.kt +21 −0 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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() } } Loading Loading @@ -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) Loading