From 8529e82adce185f394e677580c99f1e11698312c Mon Sep 17 00:00:00 2001 From: mihanentalpo Date: Thu, 21 Aug 2025 17:32:00 +0700 Subject: [PATCH] Automatic backup: change save method to SAF --- .../uhabits/database/PublicBackupWorker.kt | 12 +++---- .../org/isoron/uhabits/utils/DatabaseUtils.kt | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/database/PublicBackupWorker.kt b/uhabits-android/src/main/java/org/isoron/uhabits/database/PublicBackupWorker.kt index d17ed1957..b7fe470d1 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/database/PublicBackupWorker.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/database/PublicBackupWorker.kt @@ -2,13 +2,11 @@ package org.isoron.uhabits.database import android.content.Context import android.net.Uri +import android.util.Log import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import org.isoron.uhabits.HabitsApplication import org.isoron.uhabits.utils.DatabaseUtils -import org.isoron.uhabits.utils.UriUtils -import java.io.File -import java.io.IOException class PublicBackupWorker( appContext: Context, @@ -19,13 +17,13 @@ class PublicBackupWorker( val app = applicationContext as HabitsApplication val prefs = app.component.preferences val uriString = prefs.publicBackupUri ?: return Result.failure() - val path = UriUtils.getPathFromTreeUri(applicationContext, Uri.parse(uriString)) - ?: return Result.failure() + val folderUri = Uri.parse(uriString) return try { val addDate = prefs.isPublicBackupAddDateEnabled - DatabaseUtils.saveDatabaseCopy(applicationContext, File(path), addDate) + DatabaseUtils.saveDatabaseCopy(applicationContext, folderUri, addDate) Result.success() - } catch (e: IOException) { + } catch (e: Exception) { + Log.e("PublicBackupWorker", "backup failed", e) Result.retry() } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.kt b/uhabits-android/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.kt index 7ba9ca76a..d52ccae91 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.kt @@ -20,7 +20,9 @@ package org.isoron.uhabits.utils import android.content.Context import android.database.sqlite.SQLiteDatabase +import android.net.Uri import android.util.Log +import androidx.documentfile.provider.DocumentFile import org.isoron.uhabits.HabitsApplication.Companion.isTestMode import org.isoron.uhabits.HabitsDatabaseOpener import org.isoron.uhabits.core.DATABASE_FILENAME @@ -73,6 +75,35 @@ object DatabaseUtils { return dbCopy.absolutePath } + @JvmStatic + @Throws(IOException::class) + fun saveDatabaseCopy( + context: Context, + folderUri: Uri, + add_date: Boolean = true + ): String { + val dateFormat: SimpleDateFormat = getBackupDateFormat() + val filename = if (add_date) { + val date = dateFormat.format(getLocalTime()) + "Loop Habits Backup $date.db" + } else { + "Loop Habits Backup.db" + } + + val folder = DocumentFile.fromTreeUri(context, folderUri) + ?: throw IOException("Invalid folder uri") + val document = folder.createFile("application/octet-stream", filename) + ?: throw IOException("Failed to create backup file") + + context.contentResolver.openOutputStream(document.uri).use { outStream -> + val out = outStream ?: throw IOException("Cannot open output stream") + getDatabaseFile(context).inputStream().use { inStream -> + inStream.copyTo(out) + } + } + return document.uri.toString() + } + fun openDatabase(): SQLiteDatabase { checkNotNull(opener) return opener!!.writableDatabase