Delete attachments for expired notifications regularly
This commit is contained in:
parent
a77d1a0f3a
commit
c55693f9cf
6 changed files with 71 additions and 19 deletions
|
@ -290,6 +290,9 @@ interface NotificationDao {
|
||||||
@Query("SELECT id FROM notification WHERE subscriptionId = :subscriptionId") // Includes deleted
|
@Query("SELECT id FROM notification WHERE subscriptionId = :subscriptionId") // Includes deleted
|
||||||
fun listIds(subscriptionId: Long): List<String>
|
fun listIds(subscriptionId: Long): List<String>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM notification WHERE deleted = 1 AND attachment_contentUri <> ''")
|
||||||
|
fun listDeletedWithAttachments(): List<Notification>
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
fun add(notification: Notification)
|
fun add(notification: Notification)
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,10 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
|
||||||
return notificationDao.list()
|
return notificationDao.list()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getDeletedNotificationsWithAttachments(): List<Notification> {
|
||||||
|
return notificationDao.listDeletedWithAttachments()
|
||||||
|
}
|
||||||
|
|
||||||
fun getNotificationsLiveData(subscriptionId: Long): LiveData<List<Notification>> {
|
fun getNotificationsLiveData(subscriptionId: Long): LiveData<List<Notification>> {
|
||||||
return notificationDao.listFlow(subscriptionId).asLiveData()
|
return notificationDao.listFlow(subscriptionId).asLiveData()
|
||||||
}
|
}
|
||||||
|
|
|
@ -355,7 +355,10 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo
|
||||||
val resolver = context.applicationContext.contentResolver
|
val resolver = context.applicationContext.contentResolver
|
||||||
val deleted = resolver.delete(contentUri, null, null) > 0
|
val deleted = resolver.delete(contentUri, null, null) > 0
|
||||||
if (!deleted) throw Exception("no rows deleted")
|
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)
|
val newNotification = notification.copy(attachment = newAttachment)
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
repository.updateNotification(newNotification)
|
repository.updateNotification(newNotification)
|
||||||
|
|
|
@ -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.
|
// (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!
|
// 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 DELETE_WORKER_INTERVAL_MINUTES = 8 * 60L
|
||||||
const val SERVICE_START_WORKER_INTERVAL_MINUTES = 3 * 60L
|
const val SERVICE_START_WORKER_INTERVAL_MINUTES = 3 * 60L
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
package io.heckel.ntfy.work
|
package io.heckel.ntfy.work
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
import androidx.work.CoroutineWorker
|
import androidx.work.CoroutineWorker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import io.heckel.ntfy.BuildConfig
|
import io.heckel.ntfy.BuildConfig
|
||||||
|
import io.heckel.ntfy.db.PROGRESS_DELETED
|
||||||
import io.heckel.ntfy.db.Repository
|
import io.heckel.ntfy.db.Repository
|
||||||
|
import io.heckel.ntfy.ui.DetailAdapter
|
||||||
import io.heckel.ntfy.util.Log
|
import io.heckel.ntfy.util.Log
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes notifications marked for deletion and attachments for deleted notifications.
|
||||||
|
*/
|
||||||
class DeleteWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
|
class DeleteWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
|
||||||
// IMPORTANT:
|
// IMPORTANT:
|
||||||
// Every time the worker is changed, the periodic work has to be REPLACEd.
|
// Every time the worker is changed, the periodic work has to be REPLACEd.
|
||||||
|
@ -20,27 +26,58 @@ class DeleteWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
Log.d(TAG, "Deleting expired notifications")
|
deleteExpiredAttachments() // Before notifications, so we will also catch manually deleted notifications
|
||||||
val repository = Repository.getInstance(applicationContext)
|
deleteExpiredNotifications()
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark as deleted
|
|
||||||
val markDeletedOlderThanTimestamp = (System.currentTimeMillis()/1000) - deleteAfterSeconds
|
|
||||||
Log.d(TAG, "Marking notifications older than $markDeletedOlderThanTimestamp as deleted")
|
|
||||||
repository.markAsDeletedIfOlderThan(markDeletedOlderThanTimestamp)
|
|
||||||
|
|
||||||
// Hard delete
|
|
||||||
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()
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as deleted
|
||||||
|
val markDeletedOlderThanTimestamp = (System.currentTimeMillis()/1000) - deleteAfterSeconds
|
||||||
|
Log.d(TAG, "Marking notifications older than $markDeletedOlderThanTimestamp as deleted")
|
||||||
|
repository.markAsDeletedIfOlderThan(markDeletedOlderThanTimestamp)
|
||||||
|
|
||||||
|
// Hard delete
|
||||||
|
val deleteOlderThanTimestamp = (System.currentTimeMillis()/1000) - HARD_DELETE_AFTER_SECONDS
|
||||||
|
Log.d(TAG, "Hard deleting notifications older than $markDeletedOlderThanTimestamp")
|
||||||
|
repository.removeNotificationsIfOlderThan(deleteOlderThanTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val VERSION = BuildConfig.VERSION_CODE
|
const val VERSION = BuildConfig.VERSION_CODE
|
||||||
const val TAG = "NtfyDeleteWorker"
|
const val TAG = "NtfyDeleteWorker"
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
Features:
|
Features:
|
||||||
* Download attachments to cache folder (#181)
|
* Download attachments to cache folder (#181)
|
||||||
|
* Regularly delete attachments for deleted notifications (#142)
|
||||||
|
|
||||||
Bugs:
|
Bugs:
|
||||||
* IllegalStateException: Failed to build unique file (#177, thanks to @Fallenbagel for reporting)
|
* IllegalStateException: Failed to build unique file (#177, thanks to @Fallenbagel for reporting)
|
||||||
|
|
||||||
|
Thanks:
|
||||||
|
* Many thanks to @cmeis, @Fallenbagel, @J117 and @rogeliodh for input on the new attachment logic, and for
|
||||||
|
testing the release
|
||||||
|
|
Loading…
Reference in a new issue