Fix external backup crashes

master
Koitharu 1 year ago
parent 1f0180d601
commit bb6f7b1e9f
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -2,16 +2,18 @@ package org.koitharu.kotatsu.core.backup
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.annotation.CheckResult
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import okio.IOException
import okio.buffer import okio.buffer
import okio.sink import okio.sink
import okio.source import okio.source
import org.jetbrains.annotations.Blocking import org.jetbrains.annotations.Blocking
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
@ -21,7 +23,7 @@ class ExternalBackupStorage @Inject constructor(
) { ) {
suspend fun list(): List<BackupFile> = runInterruptible(Dispatchers.IO) { suspend fun list(): List<BackupFile> = runInterruptible(Dispatchers.IO) {
getRoot().listFiles().mapNotNull { getRootOrThrow().listFiles().mapNotNull {
if (it.isFile && it.canRead()) { if (it.isFile && it.canRead()) {
BackupFile( BackupFile(
uri = it.uri, uri = it.uri,
@ -35,8 +37,14 @@ class ExternalBackupStorage @Inject constructor(
}.sortedDescending() }.sortedDescending()
} }
suspend fun listOrNull() = runCatchingCancellable {
list()
}.onFailure { e ->
e.printStackTraceDebug()
}.getOrNull()
suspend fun put(file: File): Uri = runInterruptible(Dispatchers.IO) { suspend fun put(file: File): Uri = runInterruptible(Dispatchers.IO) {
val out = checkNotNull(getRoot().createFile("application/zip", file.nameWithoutExtension)) { val out = checkNotNull(getRootOrThrow().createFile("application/zip", file.nameWithoutExtension)) {
"Cannot create target backup file" "Cannot create target backup file"
} }
checkNotNull(context.contentResolver.openOutputStream(out.uri, "wt")).sink().use { sink -> checkNotNull(context.contentResolver.openOutputStream(out.uri, "wt")).sink().use { sink ->
@ -47,25 +55,30 @@ class ExternalBackupStorage @Inject constructor(
out.uri out.uri
} }
@CheckResult
suspend fun delete(victim: BackupFile) = runInterruptible(Dispatchers.IO) { suspend fun delete(victim: BackupFile) = runInterruptible(Dispatchers.IO) {
val df = checkNotNull(DocumentFile.fromSingleUri(context, victim.uri)) { val df = DocumentFile.fromSingleUri(context, victim.uri)
"${victim.uri} cannot be resolved to the DocumentFile" df != null && df.delete()
}
if (!df.delete()) {
throw IOException("Cannot delete ${df.uri}")
}
} }
suspend fun getLastBackupDate() = list().maxByOrNull { it.dateTime }?.dateTime suspend fun getLastBackupDate() = listOrNull()?.maxOfOrNull { it.dateTime }
suspend fun trim(maxCount: Int) { suspend fun trim(maxCount: Int): Boolean {
list().drop(maxCount).forEach { val list = listOrNull()
delete(it) if (list == null || list.size <= maxCount) {
return false
}
var result = false
for (i in maxCount until list.size) {
if (delete(list[i])) {
result = true
}
} }
return result
} }
@Blocking @Blocking
private fun getRoot(): DocumentFile { private fun getRootOrThrow(): DocumentFile {
val uri = checkNotNull(settings.periodicalBackupDirectory) { val uri = checkNotNull(settings.periodicalBackupDirectory) {
"Backup directory is not specified" "Backup directory is not specified"
} }

Loading…
Cancel
Save