diff --git a/app/src/main/java/io/heckel/ntfy/db/Database.kt b/app/src/main/java/io/heckel/ntfy/db/Database.kt index e267a45..3ab4ca5 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -380,8 +380,14 @@ interface NotificationDao { @Query("SELECT * FROM notification WHERE deleted = 1 AND attachment_contentUri <> ''") fun listDeletedWithAttachments(): List - @Query("SELECT * FROM notification WHERE deleted = 1 AND icon_contentUri <> ''") - fun listDeletedWithIcons(): List + @Query("SELECT DISTINCT icon_contentUri FROM notification WHERE deleted != 1 AND icon_contentUri <> ''") + fun listActiveIconUris(): List + + @Query("SELECT DISTINCT icon_contentUri FROM notification WHERE deleted = 1 AND icon_contentUri <> ''") + fun listDeletedIconUris(): List + + @Query("UPDATE notification SET icon_contentUri = null WHERE icon_contentUri = :uri") + fun clearIconUri(uri: String) @Insert(onConflict = OnConflictStrategy.IGNORE) fun add(notification: Notification) diff --git a/app/src/main/java/io/heckel/ntfy/db/Repository.kt b/app/src/main/java/io/heckel/ntfy/db/Repository.kt index ed45597..a39f617 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -92,8 +92,16 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas return notificationDao.listDeletedWithAttachments() } - fun getDeletedNotificationsWithIcons(): List { - return notificationDao.listDeletedWithIcons() + fun getActiveIconUris(): Set { + return notificationDao.listActiveIconUris().toSet() + } + + fun getDeletedIconUris(): Set { + return notificationDao.listDeletedIconUris().toSet() + } + + fun clearIconUri(uri: String) { + notificationDao.clearIconUri(uri) } fun getNotificationsLiveData(subscriptionId: Long): LiveData> { diff --git a/app/src/main/java/io/heckel/ntfy/msg/DownloadIconWorker.kt b/app/src/main/java/io/heckel/ntfy/msg/DownloadIconWorker.kt index aa74157..9242743 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/DownloadIconWorker.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/DownloadIconWorker.kt @@ -14,11 +14,12 @@ 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.stringToHash +import io.heckel.ntfy.util.sha256 import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import java.io.File +import java.util.Date import java.util.concurrent.TimeUnit class DownloadIconWorker(private val context: Context, params: WorkerParameters) : Worker(context, params) { @@ -45,7 +46,8 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters) icon = notification.icon ?: return Result.failure() try { val iconFile = createIconFile(icon) - if (!iconFile.exists()) { + val yesterdayTimestamp = Date().time - 1000*60*60*24 // now Unix timestamp - 24 hours + if (!iconFile.exists() || iconFile.lastModified() < yesterdayTimestamp) { downloadIcon(iconFile) } else { Log.d(TAG, "Loading icon from cache: ${icon.url}") @@ -152,7 +154,7 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters) if (!iconDir.exists() && !iconDir.mkdirs()) { throw Exception("Cannot create cache directory for icons: $iconDir") } - val hash = stringToHash(icon.url) + val hash = icon.url.sha256() return File(iconDir, hash) } diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt index 877a3be..60b2e3a 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt @@ -50,11 +50,7 @@ class NotificationParser { ) } } else null - val icon: Icon? = if (message.icon != null) { - Icon( - url = message.icon - ) - } else null + val icon: Icon? = if (message.icon != null) Icon(url = message.icon) else null val notification = Notification( id = message.id, subscriptionId = subscriptionId, diff --git a/app/src/main/java/io/heckel/ntfy/util/Util.kt b/app/src/main/java/io/heckel/ntfy/util/Util.kt index 59d1df3..a3702e1 100644 --- a/app/src/main/java/io/heckel/ntfy/util/Util.kt +++ b/app/src/main/java/io/heckel/ntfy/util/Util.kt @@ -471,9 +471,8 @@ fun copyToClipboard(context: Context, notification: Notification) { .show() } -fun stringToHash(s: String): String { - val bytes = s.toByteArray(); +fun String.sha256(): String { val md = MessageDigest.getInstance("SHA-256") - val digest = md.digest(bytes) + val digest = md.digest(this.toByteArray()) return digest.fold("") { str, it -> str + "%02x".format(it) } } \ No newline at end of file diff --git a/app/src/main/java/io/heckel/ntfy/work/DeleteWorker.kt b/app/src/main/java/io/heckel/ntfy/work/DeleteWorker.kt index ddeee83..7d611cb 100644 --- a/app/src/main/java/io/heckel/ntfy/work/DeleteWorker.kt +++ b/app/src/main/java/io/heckel/ntfy/work/DeleteWorker.kt @@ -33,7 +33,6 @@ 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() } } @@ -68,40 +67,19 @@ class DeleteWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx Log.d(TAG, "Deleting icons for deleted notifications") val resolver = applicationContext.contentResolver val repository = Repository.getInstance(applicationContext) - val notifications = repository.getDeletedNotificationsWithIcons() - notifications.forEach { notification -> + val activeIconUris = repository.getActiveIconUris() + val expiredIconUris = repository.getDeletedIconUris() + val urisToDelete = expiredIconUris.minus(activeIconUris) + urisToDelete.forEach { uri -> try { - val icon = notification.icon ?: return - val contentUri = Uri.parse(icon.contentUri ?: return) - Log.d(TAG, "Deleting icon for notification ${notification.id}: ${icon.contentUri} (${icon.url})") - val deleted = resolver.delete(contentUri, null, null) > 0 + val deleted = resolver.delete(Uri.parse(uri), null, null) > 0 if (!deleted) { - Log.w(TAG, "Unable to delete icon for notification ${notification.id}") + Log.w(TAG, "Unable to delete icon at $uri") } - val newIcon = icon.copy( - contentUri = null, - ) - val newNotification = notification.copy(icon = newIcon) - repository.updateNotification(newNotification) + + repository.clearIconUri(uri) } catch (e: Exception) { - Log.w(TAG, "Failed to delete icon for notification: ${e.message}", e) - } - } - } - - 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() - } + Log.w(TAG, "Failed to delete icon: ${e.message}", e) } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2e46612..466cd40 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -165,7 +165,6 @@ download failed download failed, link expired download failed, link expires %1$s - Could not download icon: %1$s Notifications on