changes to save scheduled message attachment data in app local dir so external delete of attachment file doesn't result in it being unable to be found when sending message

This commit is contained in:
gavine99
2025-02-20 20:41:49 +11:00
parent 1eebb9cc8e
commit 2706a558b7
17 changed files with 551 additions and 309 deletions
@@ -1,41 +0,0 @@
/*
* Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com>
*
* This file is part of QKSMS.
*
* QKSMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QKSMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QKSMS. If not, see <http://www.gnu.org/licenses/>.
*/
package dev.octoshrimpy.quik.extensions
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import androidx.core.net.toFile
fun Uri.resourceExists(context: Context): Boolean {
var retVal: Boolean? = null
try {
when (this.scheme) {
ContentResolver.SCHEME_CONTENT -> {
retVal = context.contentResolver.query(
this, null, null, null, null
)?.use { it.moveToFirst() }
}
ContentResolver.SCHEME_FILE -> retVal = this.toFile().exists()
}
} catch (e: Exception) { /* nothing */
}
return retVal ?: false
}
@@ -0,0 +1,85 @@
package com.moez.QKSMS.manager
import android.content.Context
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.moez.QKSMS.manager.MediaRecorderManager.AUDIO_FILE_PREFIX
import dev.octoshrimpy.quik.repository.ScheduledMessageRepositoryImpl
import java.io.File
import java.util.concurrent.TimeUnit
class HousekeepingWorkManager(appContext: Context, workerParams: WorkerParameters)
: Worker(appContext, workerParams) {
companion object {
private val WORKER_TAG: String = HousekeepingWorkManager::class.java.simpleName
fun register(context: Context) {
// don't check return value because, well, we can't do much about a failure
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
WORKER_TAG,
ExistingPeriodicWorkPolicy.KEEP,
PeriodicWorkRequest.Builder(
HousekeepingWorkManager::class.java,
24,
TimeUnit.HOURS
)
.setConstraints(
Constraints.Builder()
// idle device constraint kinda guarantees quik won't be in use
// as files are deleted (primarily for deleting audio recordings)
.setRequiresDeviceIdle(true)
// good citizens don't use up low batteries
.setRequiresBatteryNotLow(true)
.build()
)
.addTag(WORKER_TAG)
.build()
)
}
fun cancel(context: Context) {
WorkManager.getInstance(context).cancelUniqueWork(WORKER_TAG)
}
}
override fun doWork(): Result {
removeOrphanedScheduledMessageAttachmentFiles()
removeOrphanedComposeAudioRecording()
return Result.success()
}
private fun removeOrphanedScheduledMessageAttachmentFiles() {
// get list of all scheduled message ids
val scheduledMessageIds = ScheduledMessageRepositoryImpl().getAllScheduledMessageIdsSnapshot()
// remove orphaned scheduled message dirs in files dir
File(applicationContext.filesDir,"")
// get dirs that match prefix 'scheduled-'
.listFiles { entry -> entry.isDirectory && entry.name.startsWith("scheduled-") }
// filter out any dirs that have an associated scheduled message in db
?.filterNot {
scheduledMessageIds.contains(it.name.substringAfter('-').toLong())
}
// recursively delete orphan dir
?.forEach { it.deleteRecursively() }
}
private fun removeOrphanedComposeAudioRecording() {
// find recording files in cache dir
applicationContext.cacheDir.listFiles { entry ->
entry.isFile &&
entry.name.startsWith(AUDIO_FILE_PREFIX) &&
entry.name.endsWith(MediaRecorderManager.AUDIO_FILE_SUFFIX)
}
// delete recording file
?.forEach { it.delete() }
}
}
@@ -21,9 +21,14 @@ class ScheduledMessageRepositoryImpl @Inject constructor() : ScheduledMessageRep
sendAsGroup: Boolean,
body: String,
attachments: List<String>
) {
): ScheduledMessage {
Realm.getDefaultInstance().use { realm ->
val id = (realm.where(ScheduledMessage::class.java).max("id")?.toLong() ?: -1) + 1
val id = (realm
.where(ScheduledMessage::class.java)
.max("id")
?.toLong() ?: -1
) + 1
val recipientsRealmList = RealmList(*recipients.toTypedArray())
val attachmentsRealmList = RealmList(*attachments.toTypedArray())
@@ -31,6 +36,14 @@ class ScheduledMessageRepositoryImpl @Inject constructor() : ScheduledMessageRep
attachmentsRealmList)
realm.executeTransaction { realm.insertOrUpdate(message) }
return message
}
}
override fun updateScheduledMessage(scheduledMessage: ScheduledMessage) {
Realm.getDefaultInstance().use { realm ->
realm.executeTransaction { realm.insertOrUpdate(scheduledMessage) }
}
}
@@ -73,6 +86,17 @@ class ScheduledMessageRepositoryImpl @Inject constructor() : ScheduledMessageRep
ids.forEach { deleteScheduledMessage(it) }
}
override fun getAllScheduledMessageIdsSnapshot(): List<Long> {
Realm.getDefaultInstance().use { realm ->
return realm
.where(ScheduledMessage::class.java)
.sort("date")
.findAll()
.createSnapshot()
.map { it.id }
}
}
// Ensure to clear disposables when the repository is no longer needed
fun clearDisposables() {
disposables.clear()