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

Commit c55693f9 authored by Philipp Heckel's avatar Philipp Heckel
Browse files

Delete attachments for expired notifications regularly

parent a77d1a0f
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -290,6 +290,9 @@ interface NotificationDao {
    @Query("SELECT id FROM notification WHERE subscriptionId = :subscriptionId") // Includes deleted
    fun listIds(subscriptionId: Long): List<String>

    @Query("SELECT * FROM notification WHERE deleted = 1 AND attachment_contentUri <> ''")
    fun listDeletedWithAttachments(): List<Notification>

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun add(notification: Notification)

+4 −0
Original line number Diff line number Diff line
@@ -88,6 +88,10 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
        return notificationDao.list()
    }

    fun getDeletedNotificationsWithAttachments(): List<Notification> {
        return notificationDao.listDeletedWithAttachments()
    }

    fun getNotificationsLiveData(subscriptionId: Long): LiveData<List<Notification>> {
        return notificationDao.listFlow(subscriptionId).asLiveData()
    }
+4 −1
Original line number Diff line number Diff line
@@ -355,7 +355,10 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo
                val resolver = context.applicationContext.contentResolver
                val deleted = resolver.delete(contentUri, null, null) > 0
                if (!deleted) throw Exception("no rows deleted")
                val newAttachment = attachment.copy(progress = PROGRESS_DELETED)
                val newAttachment = attachment.copy(
                    contentUri = null,
                    progress = PROGRESS_DELETED
                )
                val newNotification = notification.copy(attachment = newAttachment)
                GlobalScope.launch(Dispatchers.IO) {
                    repository.updateNotification(newNotification)
+1 −1
Original line number Diff line number Diff line
@@ -663,7 +663,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
        // (same as the JobScheduler API), but in practice 15 doesn't work. Using 16 here.
        // Thanks to varunon9 (https://gist.github.com/varunon9/f2beec0a743c96708eb0ef971a9ff9cd) for this!

        const val POLL_WORKER_INTERVAL_MINUTES = 2 * 60L
        const val POLL_WORKER_INTERVAL_MINUTES = 60L
        const val DELETE_WORKER_INTERVAL_MINUTES = 8 * 60L
        const val SERVICE_START_WORKER_INTERVAL_MINUTES = 3 * 60L
    }
+53 −16
Original line number Diff line number Diff line
package io.heckel.ntfy.work

import android.content.Context
import android.net.Uri
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.db.PROGRESS_DELETED
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.ui.DetailAdapter
import io.heckel.ntfy.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

/**
 * Deletes notifications marked for deletion and attachments for deleted notifications.
 */
class DeleteWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
    // IMPORTANT:
    //   Every time the worker is changed, the periodic work has to be REPLACEd.
@@ -20,12 +26,45 @@ class DeleteWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx

    override suspend fun doWork(): Result {
        return withContext(Dispatchers.IO) {
            deleteExpiredAttachments() // Before notifications, so we will also catch manually deleted notifications
            deleteExpiredNotifications()
            return@withContext Result.success()
        }
    }

    private fun deleteExpiredAttachments() {
        Log.d(TAG, "Deleting attachments for deleted notifications")
        val resolver = applicationContext.contentResolver
        val repository = Repository.getInstance(applicationContext)
        val notifications = repository.getDeletedNotificationsWithAttachments()
        notifications.forEach { notification ->
            try {
                val attachment = notification.attachment ?: return
                val contentUri = Uri.parse(attachment.contentUri ?: return)
                Log.d(TAG, "Deleting attachment for notification ${notification.id}: ${attachment.contentUri} (${attachment.name})")
                val deleted = resolver.delete(contentUri, null, null) > 0
                if (!deleted) {
                    Log.w(TAG, "Unable to delete attachment for notification ${notification.id}")
                }
                val newAttachment = attachment.copy(
                    contentUri = null,
                    progress = PROGRESS_DELETED
                )
                val newNotification = notification.copy(attachment = newAttachment)
                repository.updateNotification(newNotification)
            } catch (e: Exception) {
                Log.w(DetailAdapter.TAG, "Failed to delete attachment for notification: ${e.message}", e)
            }
        }
    }

    private fun deleteExpiredNotifications() {
        Log.d(TAG, "Deleting expired notifications")
        val repository = Repository.getInstance(applicationContext)
        val deleteAfterSeconds = repository.getAutoDeleteSeconds()
        if (deleteAfterSeconds == Repository.AUTO_DELETE_NEVER) {
            Log.d(TAG, "Not deleting any notifications; global setting set to NEVER")
                return@withContext Result.success()
            return
        }

        // Mark as deleted
@@ -37,8 +76,6 @@ class DeleteWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx
        val deleteOlderThanTimestamp = (System.currentTimeMillis()/1000) - HARD_DELETE_AFTER_SECONDS
        Log.d(TAG, "Hard deleting notifications older than $markDeletedOlderThanTimestamp")
        repository.removeNotificationsIfOlderThan(deleteOlderThanTimestamp)
            return@withContext Result.success()
        }
    }

    companion object {
Loading