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)
5 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
Kerf - Android 157.026
Kiln - Android 157.026
Wedge - Android 156.026
Whittle - Linux 155.026
Grit - Firefox/Chrome 154.026

Menu
Calendar
Project Tin (024/029)
Miter
RSS Feed
User Avatar
@vgmlr
=SUM(parts)
0.00293
257,189 (+443)