From 42dd87f0c4b7940d35ee273e292d9d084b2c22ec Mon Sep 17 00:00:00 2001 From: Nacho Date: Sat, 18 Jan 2025 00:45:16 +0100 Subject: [PATCH] Data export working During export the database will be closed, otherwise the WAL will ruin the export. data will be saved as a .vbhelper file, then the app will be closed to avoid making any changes to the DB For import, we check if the extension is .vbhelper, otherwise we can corrupt the database, IMPORTANT. Next we delete everything related to RoomDB and we swap it with the new files. Finally the app will be closed to ensure RoomDB is running on the new DB. Finally, I still have to reintegrate the importApk functionality with the NewSettingsScreenController and get rid of the old one. --- .../github/nacabaro/vbhelper/MainActivity.kt | 23 ++- .../vbhelper/navigation/AppNavigation.kt | 15 +- .../NewSettingsScreenController.kt | 8 + .../NewSettingsScreenControllerImpl.kt | 152 ++++++++++++++++++ .../{ => settingsScreen}/SettingsScreen.kt | 12 +- .../SettingsScreenController.kt | 2 +- 6 files changed, 198 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenController.kt create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenControllerImpl.kt rename app/src/main/java/com/github/nacabaro/vbhelper/screens/{ => settingsScreen}/SettingsScreen.kt (83%) rename app/src/main/java/com/github/nacabaro/vbhelper/screens/{ => settingsScreen}/SettingsScreenController.kt (98%) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt b/app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt index 033f574..deae7b2 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt @@ -21,12 +21,12 @@ import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.domain.characters.Card import com.github.nacabaro.vbhelper.domain.Sprites import com.github.nacabaro.vbhelper.domain.characters.Character -import com.github.nacabaro.vbhelper.domain.characters.Dex import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl -import com.github.nacabaro.vbhelper.screens.SettingsScreenController +import com.github.nacabaro.vbhelper.screens.settingsScreen.NewSettingsScreenControllerImpl +import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenController import com.github.nacabaro.vbhelper.source.ApkSecretsImporter import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme import com.github.nacabaro.vbhelper.utils.DeviceType @@ -64,14 +64,15 @@ class MainActivity : ComponentActivity() { this::handleReceivedNfcCharacter, this, this::registerActivityLifecycleListener, - this::unregisterActivityLifecycleListener) - + this::unregisterActivityLifecycleListener + ) + val newSettingsScreenController = NewSettingsScreenControllerImpl(this) super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { VBHelperTheme { - MainApplication(settingsScreenController, scanScreenController) + MainApplication(settingsScreenController, scanScreenController, newSettingsScreenController) } } Log.i("MainActivity", "Activity onCreated") @@ -190,10 +191,18 @@ class MainActivity : ComponentActivity() { } @Composable - private fun MainApplication(settingsScreenController: SettingsScreenController, scanScreenController: ScanScreenControllerImpl) { + private fun MainApplication( + settingsScreenController: SettingsScreenController, + scanScreenController: ScanScreenControllerImpl, + newSettingsScreenController: NewSettingsScreenControllerImpl + ) { AppNavigation( - applicationNavigationHandlers = AppNavigationHandlers(settingsScreenController, scanScreenController), + applicationNavigationHandlers = AppNavigationHandlers( + settingsScreenController, + scanScreenController, + newSettingsScreenController + ), onClickImportCard = { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt b/app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt index 6f5fb75..b0138b5 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt @@ -1,6 +1,5 @@ package com.github.nacabaro.vbhelper.navigation -import android.util.Log import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable @@ -15,12 +14,17 @@ import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreen import com.github.nacabaro.vbhelper.screens.ItemsScreen import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreen import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl -import com.github.nacabaro.vbhelper.screens.SettingsScreen -import com.github.nacabaro.vbhelper.screens.SettingsScreenController +import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreen +import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenController import com.github.nacabaro.vbhelper.screens.SpriteViewer import com.github.nacabaro.vbhelper.screens.StorageScreen +import com.github.nacabaro.vbhelper.screens.settingsScreen.NewSettingsScreenControllerImpl -data class AppNavigationHandlers(val settingsScreenController: SettingsScreenController, val scanScreenController: ScanScreenControllerImpl) +data class AppNavigationHandlers( + val settingsScreenController: SettingsScreenController, + val scanScreenController: ScanScreenControllerImpl, + val newSettingsScreenController: NewSettingsScreenControllerImpl +) @Composable fun AppNavigation( @@ -72,7 +76,8 @@ fun AppNavigation( SettingsScreen( navController = navController, settingsScreenController = applicationNavigationHandlers.settingsScreenController, - onClickImportCard = onClickImportCard + newSettingsScreenController = applicationNavigationHandlers.newSettingsScreenController, + onClickImportCard = onClickImportCard, ) } composable(NavigationItems.Viewer.route) { diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenController.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenController.kt new file mode 100644 index 0000000..612406b --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenController.kt @@ -0,0 +1,8 @@ +package com.github.nacabaro.vbhelper.screens.settingsScreen + +import android.net.Uri + +interface NewSettingsScreenController { + fun onClickOpenDirectory() + fun onClickImportDatabase() +} \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenControllerImpl.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenControllerImpl.kt new file mode 100644 index 0000000..53e8515 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/NewSettingsScreenControllerImpl.kt @@ -0,0 +1,152 @@ +package com.github.nacabaro.vbhelper.screens.settingsScreen + +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.launch +import android.net.Uri +import android.provider.OpenableColumns +import android.util.Log +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import com.github.nacabaro.vbhelper.di.VBHelper +import kotlinx.coroutines.Dispatchers +import java.io.File +import java.io.InputStream +import java.io.OutputStream + + +class NewSettingsScreenControllerImpl( + private val context: ComponentActivity, +): NewSettingsScreenController { + private val filePickerLauncher: ActivityResultLauncher + private val filePickerOpenerLauncher: ActivityResultLauncher> + + init { + filePickerLauncher = context.registerForActivityResult( + ActivityResultContracts.CreateDocument("application/octet-stream") + ) { uri -> + if (uri != null) { + exportDatabase("internalDb", uri) + } else { + context.runOnUiThread { + Toast.makeText(context, "No destination selected", Toast.LENGTH_SHORT) + .show() + } + } + } + + filePickerOpenerLauncher = context.registerForActivityResult( + ActivityResultContracts.OpenDocument() + ) { uri -> + if (uri != null) { + importDatabase("internalDb", uri) + } else { + context.runOnUiThread { + Toast.makeText(context, "No source selected", Toast.LENGTH_SHORT).show() + } + } + } + } + + override fun onClickOpenDirectory() { + filePickerLauncher.launch("My application data.vbhelper") + } + + override fun onClickImportDatabase() { + filePickerOpenerLauncher.launch(arrayOf("application/octet-stream")) + } + + private fun exportDatabase(roomDbName: String, destinationUri: Uri) { + context.lifecycleScope.launch(Dispatchers.IO) { + try { + val application = context.applicationContext as VBHelper + val dbFile = File(context.getDatabasePath(roomDbName).absolutePath) + if (!dbFile.exists()) { + throw IllegalStateException("Database file does not exist!") + } + + application.container.db.close() + + context.contentResolver.openOutputStream(destinationUri)?.use { outputStream -> + dbFile.inputStream().use { inputStream -> + copyFile(inputStream, outputStream) + } + } ?: throw IllegalArgumentException("Unable to open destination Uri for writing") + + context.runOnUiThread { + Toast.makeText(context, "Database exported successfully!", Toast.LENGTH_SHORT).show() + Toast.makeText(context, "Closing application to avoid changes.", Toast.LENGTH_LONG).show() + context.finishAffinity() + } + } catch (e: Exception) { + Log.e("ScanScreenController", "Error exporting database $e") + context.runOnUiThread { + Toast.makeText(context, "Error exporting database: ${e.message}", Toast.LENGTH_LONG).show() + } + } + } + } + + private fun importDatabase(roomDbName: String, sourceUri: Uri) { + context.lifecycleScope.launch(Dispatchers.IO) { + try { + if (!getFileNameFromUri(sourceUri)!!.endsWith(".vbhelper")) { + context.runOnUiThread { + Toast.makeText(context, "Invalid file format", Toast.LENGTH_SHORT).show() + } + return@launch + } + + val dbPath = context.getDatabasePath(roomDbName) + val shmFile = File(dbPath.parent, "$roomDbName-shm") + val walFile = File(dbPath.parent, "$roomDbName-wal") + + // Delete existing database files + if (dbPath.exists()) dbPath.delete() + if (shmFile.exists()) shmFile.delete() + if (walFile.exists()) walFile.delete() + + val dbFile = File(dbPath.absolutePath) + + context.contentResolver.openInputStream(sourceUri)?.use { inputStream -> + dbFile.outputStream().use { outputStream -> + copyFile(inputStream, outputStream) + } + } ?: throw IllegalArgumentException("Unable to open source Uri for reading") + + context.runOnUiThread { + Toast.makeText(context, "Database imported successfully!", Toast.LENGTH_SHORT).show() + Toast.makeText(context, "Reopen the app to finish import process!", Toast.LENGTH_LONG).show() + context.finishAffinity() + } + } catch (e: Exception) { + Log.e("ScanScreenController", "Error importing database $e") + context.runOnUiThread { + Toast.makeText(context, "Error importing database: ${e.message}", Toast.LENGTH_LONG).show() + } + } + } + } + + private fun getFileNameFromUri(uri: Uri): String? { + var fileName: String? = null + val cursor = context.contentResolver.query(uri, null, null, null, null) + cursor?.use { + if (it.moveToFirst()) { + val nameIndex = it.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME) + fileName = it.getString(nameIndex) + } + } + return fileName + } + + private fun copyFile(inputStream: InputStream, outputStream: OutputStream) { + val buffer = ByteArray(1024) + var bytesRead: Int + while (inputStream.read(buffer).also { bytesRead = it } != -1) { + outputStream.write(buffer, 0, bytesRead) + } + outputStream.flush() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/SettingsScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreen.kt similarity index 83% rename from app/src/main/java/com/github/nacabaro/vbhelper/screens/SettingsScreen.kt rename to app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreen.kt index 104cded..42b6e8d 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/SettingsScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreen.kt @@ -1,4 +1,4 @@ -package com.github.nacabaro.vbhelper.screens +package com.github.nacabaro.vbhelper.screens.settingsScreen import android.net.Uri import androidx.activity.ComponentActivity @@ -22,11 +22,14 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.github.nacabaro.vbhelper.components.TopBanner +import com.github.nacabaro.vbhelper.navigation.AppNavigation +import com.github.nacabaro.vbhelper.navigation.NavigationItems @Composable fun SettingsScreen( navController: NavController, settingsScreenController: SettingsScreenController, + newSettingsScreenController: NewSettingsScreenControllerImpl, onClickImportCard: () -> Unit ) { Scaffold ( @@ -51,6 +54,13 @@ fun SettingsScreen( SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") { settingsScreenController.apkFilePickLauncher.launch(arrayOf("*/*")) } + SettingsSection("Data management") + SettingsEntry(title = "Export data", description = "Export application database") { + newSettingsScreenController.onClickOpenDirectory() + } + SettingsEntry(title = "Import data", description = "Import application database") { + newSettingsScreenController.onClickImportDatabase() + } SettingsSection("DiM/BEm management") SettingsEntry(title = "Import DiM card", description = "Import DiM/BEm card file", onClick = onClickImportCard) SettingsEntry(title = "Rename DiM/BEm", description = "Set card name") { } diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/SettingsScreenController.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreenController.kt similarity index 98% rename from app/src/main/java/com/github/nacabaro/vbhelper/screens/SettingsScreenController.kt rename to app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreenController.kt index 4afb3d1..6f1f41a 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/SettingsScreenController.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreenController.kt @@ -1,4 +1,4 @@ -package com.github.nacabaro.vbhelper.screens +package com.github.nacabaro.vbhelper.screens.settingsScreen import android.net.Uri import android.widget.Toast