Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c7edb50e authored by Hunter Kehoe's avatar Hunter Kehoe
Browse files

cache notification icons and delete after 24 hours

parent a2ae6e4c
Loading
Loading
Loading
Loading
+22 −8
Original line number Diff line number Diff line
@@ -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
    }
}
+8 −0
Original line number Diff line number Diff line
@@ -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) }
}
 No newline at end of file
+21 −0
Original line number Diff line number Diff line
@@ -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)