Pith - wedge_android
wedge_android/app/src/main/java/com/vgmlr/wedge/WedgeBackup.kt [4.1 kb]
Modified: 00:00:27 153 026 (19 Aug 026)
11 Days Ago
package com.vgmlr.wedge

import android.content.Context
import java.io.File
import android.net.Uri
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import kotlinx.coroutines.flow.first
import java.util.concurrent.TimeUnit

object WedgeBackup {
    private const val DIR = "backups"
    private const val FILE = "wedge.bk"
    private const val MIME = "application/octet-stream"
    const val WORK_NAME = "WedgeBackupJob"

    suspend fun run(context: Context) {
        val db = AppDatabase.getInstance(context)
        val prefs = PreferenceManager(context)
        val note = db.noteDao().getNoteSync()
        var content = note?.content ?: ""

        val phrase = prefs.backupPhrase.first()
        if (prefs.backupEncrypt.first() && phrase.isNotEmpty() && !WedgeSecurity.isEncrypted(content)) {
            content = WedgeSecurity.encrypt(content, phrase)
        }

        val location = prefs.backupLocation.first()
        if (location.isNotEmpty()) {
            writeToTree(context, location.toUri(), content)
        } else {
            val folder = File(context.getExternalFilesDir(null), DIR)
            if (!folder.exists()) folder.mkdirs()
            File(folder, FILE).writeText(content)
        }
        prefs.setBackupStamp(System.currentTimeMillis())
    }

    suspend fun restore(context: Context): Boolean {
        val prefs = PreferenceManager(context)
        var content = read(context, prefs.backupLocation.first()) ?: return false
        if (WedgeSecurity.isEncrypted(content)) {
            val phrase = prefs.backupPhrase.first()
            if (phrase.isEmpty()) return false
            content = WedgeSecurity.decrypt(content, phrase) ?: return false
        }
        AppDatabase.getInstance(context).noteDao().save(NoteEntity(id = 1, content = content))
        NoteWidgetProvider.triggerUpdate(context)
        return true
    }

    fun schedule(context: Context, minutes: Int) {
        if (minutes <= WedgeConfig.BACKUP_INTERVAL_NONE) {
            WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME)
            return
        }
        val constraints = Constraints.Builder().setRequiresStorageNotLow(true).build()
        val request = PeriodicWorkRequestBuilder<WedgeBackupWorker>(
            minutes.toLong().coerceAtLeast(15L), TimeUnit.MINUTES
        ).setConstraints(constraints).build()
        WorkManager.getInstance(context).enqueueUniquePeriodicWork(
            WORK_NAME, ExistingPeriodicWorkPolicy.UPDATE, request
        )
    }

    private fun resolveBackupFile(dir: DocumentFile, create: Boolean): DocumentFile? {
        val matches = dir.listFiles().filter { f ->
            val n = f.name
            f.isFile && n != null &&
                    (n == FILE || n.startsWith("$FILE.") || n.startsWith("$FILE ("))
        }.sortedByDescending { it.lastModified() }

        matches.drop(1).forEach { it.delete() }

        return matches.firstOrNull() ?: if (create) dir.createFile(MIME, FILE) else null
    }

    private fun writeToTree(context: Context, tree: Uri, content: String) {
        val dir = DocumentFile.fromTreeUri(context, tree) ?: return
        val file = resolveBackupFile(dir, create = true) ?: return
        context.contentResolver.openOutputStream(file.uri, "wt")?.use {
            it.write(content.toByteArray(Charsets.UTF_8))
        }
    }

    private fun read(context: Context, location: String): String? = try {
        if (location.isNotEmpty()) {
            val dir = DocumentFile.fromTreeUri(context, location.toUri())
            val file = dir?.let { resolveBackupFile(it, create = false) }
            file?.let {
                context.contentResolver.openInputStream(it.uri)
                    ?.bufferedReader()?.use { r -> r.readText() }
            }
        } else {
            val f = File(File(context.getExternalFilesDir(null), DIR), FILE)
            if (f.exists()) f.readText() else null
        }
    } catch (_: Exception) {
        null
    }
}
Updates
Stave - Android 158.026
Kerf - Android 157.026
Kiln - Android 157.026
Wedge - Android 156.026
Whittle - Linux 155.026

Menu
Calendar
Project Tin (024/029)
Miter
RSS Feed
User Avatar
@vgmlr
=SUM(parts)
0.0021
261,147 (+1,561)