Loading app/src/main/java/io/heckel/ntfy/db/Database.kt +3 −0 Original line number Diff line number Diff line Loading @@ -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) Loading app/src/main/java/io/heckel/ntfy/db/Repository.kt +4 −0 Original line number Diff line number Diff line Loading @@ -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() } Loading app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt +4 −1 Original line number Diff line number Diff line Loading @@ -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) Loading app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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 } Loading app/src/main/java/io/heckel/ntfy/work/DeleteWorker.kt +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. Loading @@ -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 Loading @@ -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 Loading
app/src/main/java/io/heckel/ntfy/db/Database.kt +3 −0 Original line number Diff line number Diff line Loading @@ -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) Loading
app/src/main/java/io/heckel/ntfy/db/Repository.kt +4 −0 Original line number Diff line number Diff line Loading @@ -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() } Loading
app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt +4 −1 Original line number Diff line number Diff line Loading @@ -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) Loading
app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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 } Loading
app/src/main/java/io/heckel/ntfy/work/DeleteWorker.kt +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. Loading @@ -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 Loading @@ -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