Android: always refresh seed DB from assets when size differs

Old logic: copy only if file doesn't exist. App updates with
newer seed DB were ignored because the old file was already there.
Now compares file size with asset size and overwrites on mismatch.
This commit is contained in:
4aiman 2026-05-21 01:10:15 +03:00
commit c5bf6da79e

View file

@ -0,0 +1,151 @@
package dev.baldav.app
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.FileObserver
import android.util.Log
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import java.io.File
class MainActivity : TauriActivity() {
private var commandObserver: FileObserver? = null
private var pendingSaveContent: String? = null
private val saveFileLauncher = registerForActivityResult(
ActivityResultContracts.CreateDocument("application/gpx+xml")
) { uri: Uri? ->
if (uri != null && pendingSaveContent != null) {
try {
contentResolver.openOutputStream(uri)?.use { it.write(pendingSaveContent!!.toByteArray()) }
Log.d("BalDav", "File saved to $uri")
} catch (e: Exception) {
Log.e("BalDav", "Failed to save file", e)
}
}
pendingSaveContent = null
}
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
// Rust app_data_dir() on Android = app's root data dir (filesDir.parentFile)
val appDataDir = filesDir.parentFile ?: filesDir
Log.d("BalDav", "App data dir: ${appDataDir.absolutePath}")
// Create command file if not exists (FileObserver needs it)
val cmdFile = File(appDataDir, "gps_command.txt")
if (!cmdFile.exists()) cmdFile.createNewFile()
setupCommandWatcher(appDataDir)
processGpsCommand(appDataDir)
Log.d("BalDav", "Command watcher started on ${appDataDir.absolutePath}")
// Copy seed DB from assets to filesDir (Rust can't read Android assets directly).
// Always overwrite — APK updates may contain a newer seed DB.
try {
val seedDest = File(filesDir, "products_seed.db")
val assetSize = assets.open("frontend/data/products_seed.db").use { it.available().toLong() }
if (!seedDest.exists() || seedDest.length() != assetSize) {
assets.open("frontend/data/products_seed.db").use { input ->
seedDest.outputStream().use { output ->
input.copyTo(output)
}
}
Log.d("BalDav", "Seed DB copied to ${seedDest.absolutePath} (${seedDest.length()} bytes)")
}
} catch (e: Exception) {
Log.e("BalDav", "Failed to copy seed DB", e)
}
val decorView = window.decorView
ViewCompat.setOnApplyWindowInsetsListener(decorView) { view, insets ->
val statusBar = insets.getInsets(WindowInsetsCompat.Type.statusBars())
val navBar = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
val density = resources.displayMetrics.density
val top = (statusBar.top / density).toInt()
val bottom = (navBar.bottom / density).toInt()
val left = (navBar.left / density).toInt()
val right = (navBar.right / density).toInt()
try {
val insetsFile = File(filesDir, "safe_insets.json")
insetsFile.writeText("""{"top":$top,"bottom":$bottom,"left":$left,"right":$right}""")
Log.d("BalDav", "Safe insets: top=$top bottom=$bottom left=$left right=$right")
} catch (e: Exception) {
Log.e("BalDav", "Failed to write safe insets", e)
}
ViewCompat.onApplyWindowInsets(view, insets)
}
}
private fun setupCommandWatcher(appDataDir: File) {
commandObserver = object : FileObserver(appDataDir.absolutePath, CREATE or CLOSE_WRITE or MODIFY or MOVED_TO) {
override fun onEvent(event: Int, path: String?) {
if (path == "gps_command.txt") {
Log.d("BalDav", "Command file event: $event")
runOnUiThread { processGpsCommand(appDataDir) }
}
}
}
commandObserver?.startWatching()
Log.d("BalDav", "FileObserver started on ${appDataDir.absolutePath}")
}
private fun processGpsCommand(appDataDir: File) {
val cmdFile = File(appDataDir, "gps_command.txt")
if (!cmdFile.exists()) return
val cmd = cmdFile.readText().trim()
if (cmd.isEmpty()) return
cmdFile.writeText("") // Clear but don't delete (FileObserver needs the file)
Log.d("BalDav", "Processing GPS command: $cmd")
when {
cmd.startsWith("start") -> {
val parts = cmd.split(":")
val minSpeed = parts.getOrNull(1)?.toFloatOrNull() ?: 0.42f
val maxAcc = parts.getOrNull(2)?.toFloatOrNull() ?: 20f
val minDist = parts.getOrNull(3)?.toFloatOrNull() ?: 1f
Log.d("BalDav", "Starting GPS service: speed=$minSpeed acc=$maxAcc dist=$minDist")
val intent = Intent(this, GpsRecorderService::class.java).apply {
putExtra(GpsRecorderService.EXTRA_MIN_SPEED, minSpeed)
putExtra(GpsRecorderService.EXTRA_MAX_ACC, maxAcc)
putExtra(GpsRecorderService.EXTRA_MIN_DIST, minDist)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
}
cmd == "stop" -> {
Log.d("BalDav", "Stopping GPS service")
val intent = Intent(this, GpsRecorderService::class.java)
intent.action = GpsRecorderService.ACTION_STOP
startService(intent)
}
cmd.startsWith("save:") -> {
// save:filename.gpx — read content from save_content.txt, show SAF picker
val filename = cmd.substringAfter("save:")
val contentFile = File(appDataDir, "save_content.txt")
if (contentFile.exists()) {
pendingSaveContent = contentFile.readText()
contentFile.delete()
Log.d("BalDav", "Opening save dialog for $filename (${pendingSaveContent?.length} bytes)")
saveFileLauncher.launch(filename)
}
}
}
}
override fun onDestroy() {
commandObserver?.stopWatching()
super.onDestroy()
}
}