feat: Improve JSON import error handling and logging
This commit introduces several improvements to the JSON import functionality: - Enhanced error handling for both manual and automatic JSON imports in and . - Implemented graceful fallback to unencrypted parsing if decryption fails. - Added specific error handling for and generic during JSON parsing. - Provided user-friendly Toast messages for import failures. - Improved error logging in for when encrypted data is too short. - Added missing string resources for import error messages and their German translations. - Ensured correct placement of resource to avoid duplicates and created missing . - Added debug logging to show raw JSON strings before parsing for better diagnostics. These changes make the import process more robust and provide clearer feedback to the user in case of corrupted or malformed import files.
This commit is contained in:
@@ -100,6 +100,7 @@ import de.lxtools.noteshop.ui.theme.NoteshopTheme
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import android.content.Intent
|
||||
import kotlinx.serialization.SerializationException
|
||||
import android.content.Context
|
||||
import android.content.Intent.ACTION_SEND
|
||||
import android.net.Uri
|
||||
@@ -641,7 +642,15 @@ fun AppShell(
|
||||
}
|
||||
|
||||
if (jsonString.isNotBlank()) {
|
||||
notesViewModel.importNotesFromJson(jsonString)
|
||||
Log.d("AppShell", "Notes JSON string: $jsonString")
|
||||
try {
|
||||
} catch (e: kotlinx.serialization.SerializationException) {
|
||||
Log.e("AppShell", "Error parsing notes JSON: ${e.message}")
|
||||
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_invalid_format, e.message), android.widget.Toast.LENGTH_LONG).show()
|
||||
} catch (e: Exception) {
|
||||
Log.e("AppShell", "Error importing notes: ${e.message}")
|
||||
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_generic, e.message), android.widget.Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -664,7 +673,16 @@ fun AppShell(
|
||||
}
|
||||
|
||||
if (jsonString.isNotBlank()) {
|
||||
shoppingListsViewModel.importShoppingListsFromJson(jsonString)
|
||||
Log.d("AppShell", "Shopping Lists JSON string: $jsonString")
|
||||
try {
|
||||
shoppingListsViewModel.importShoppingListsFromJson(jsonString)
|
||||
} catch (e: kotlinx.serialization.SerializationException) {
|
||||
Log.e("AppShell", "Error parsing shopping lists JSON: ${e.message}")
|
||||
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_invalid_format, e.message), android.widget.Toast.LENGTH_LONG).show()
|
||||
} catch (e: Exception) {
|
||||
Log.e("AppShell", "Error importing shopping lists: ${e.message}")
|
||||
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_generic, e.message), android.widget.Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -687,7 +705,16 @@ fun AppShell(
|
||||
}
|
||||
|
||||
if (jsonString.isNotBlank()) {
|
||||
recipesViewModel.importRecipesFromJson(jsonString)
|
||||
Log.d("AppShell", "Recipes JSON string: $jsonString")
|
||||
try {
|
||||
recipesViewModel.importRecipesFromJson(jsonString)
|
||||
} catch (e: kotlinx.serialization.SerializationException) {
|
||||
Log.e("AppShell", "Error parsing recipes JSON: ${e.message}")
|
||||
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_invalid_format, e.message), android.widget.Toast.LENGTH_LONG).show()
|
||||
} catch (e: Exception) {
|
||||
Log.e("AppShell", "Error importing recipes: ${e.message}")
|
||||
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_generic, e.message), android.widget.Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,9 @@ class FileEncryptor {
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec)
|
||||
|
||||
return cipher.doFinal(encryptedData)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e("FileEncryptor", "Error decrypting data: Invalid encrypted data format (too short for IV).", e)
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
Log.e("FileEncryptor", "Error decrypting data", e)
|
||||
throw e
|
||||
|
||||
@@ -22,6 +22,8 @@ import androidx.compose.ui.res.stringResource
|
||||
import de.lxtools.noteshop.security.FileEncryptor
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.lxtools.noteshop.R
|
||||
import android.widget.Toast
|
||||
import kotlinx.serialization.SerializationException
|
||||
import de.lxtools.noteshop.ui.notes.NotesViewModel
|
||||
import de.lxtools.noteshop.ui.shopping.ShoppingListsViewModel
|
||||
import de.lxtools.noteshop.ui.recipes.RecipesViewModel
|
||||
@@ -87,12 +89,36 @@ fun JsonImportExportDialog(
|
||||
uri?.let {
|
||||
scope.launch {
|
||||
context.contentResolver.openInputStream(it)?.use { inputStream ->
|
||||
secretKey?.let { key ->
|
||||
val encryptedContent = inputStream.readBytes()
|
||||
val decryptedBytes = fileEncryptor.decrypt(encryptedContent, key)
|
||||
val jsonString = decryptedBytes.toString(Charsets.UTF_8)
|
||||
notesViewModel.importNotesFromJson(jsonString)
|
||||
android.widget.Toast.makeText(context, R.string.json_import_successful, android.widget.Toast.LENGTH_SHORT).show()
|
||||
val fileContent = inputStream.readBytes()
|
||||
var jsonString: String? = null
|
||||
|
||||
if (secretKey != null) {
|
||||
try {
|
||||
val decryptedBytes = fileEncryptor.decrypt(fileContent, secretKey!!)
|
||||
jsonString = decryptedBytes.toString(Charsets.UTF_8)
|
||||
} catch (e: javax.crypto.AEADBadTagException) {
|
||||
android.util.Log.w("JsonImportExportDialog", "Decryption failed, trying as unencrypted: ${e.message}")
|
||||
// Fallback to unencrypted if decryption fails
|
||||
jsonString = fileContent.toString(Charsets.UTF_8)
|
||||
}
|
||||
} else {
|
||||
// If no secret key, assume unencrypted
|
||||
jsonString = fileContent.toString(Charsets.UTF_8)
|
||||
}
|
||||
|
||||
if (jsonString != null) {
|
||||
try {
|
||||
notesViewModel.importNotesFromJson(jsonString)
|
||||
android.widget.Toast.makeText(context, R.string.json_import_successful, android.widget.Toast.LENGTH_SHORT).show()
|
||||
} catch (e: kotlinx.serialization.SerializationException) {
|
||||
android.util.Log.e("JsonImportExportDialog", "Error parsing notes JSON: ${e.message}")
|
||||
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_invalid_format, e.message), android.widget.Toast.LENGTH_LONG).show()
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("JsonImportExportDialog", "Error importing notes: ${e.message}")
|
||||
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_generic, e.message), android.widget.Toast.LENGTH_LONG).show()
|
||||
}
|
||||
} else {
|
||||
android.widget.Toast.makeText(context, R.string.json_import_failed_no_data, android.widget.Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,12 +173,36 @@ fun JsonImportExportDialog(
|
||||
uri?.let {
|
||||
scope.launch {
|
||||
context.contentResolver.openInputStream(it)?.use { inputStream ->
|
||||
secretKey?.let { key ->
|
||||
val encryptedContent = inputStream.readBytes()
|
||||
val decryptedBytes = fileEncryptor.decrypt(encryptedContent, key)
|
||||
val jsonString = decryptedBytes.toString(Charsets.UTF_8)
|
||||
shoppingListsViewModel.importShoppingListsFromJson(jsonString)
|
||||
android.widget.Toast.makeText(context, R.string.json_import_successful, android.widget.Toast.LENGTH_SHORT).show()
|
||||
val fileContent = inputStream.readBytes()
|
||||
var jsonString: String? = null
|
||||
|
||||
if (secretKey != null) {
|
||||
try {
|
||||
val decryptedBytes = fileEncryptor.decrypt(fileContent, secretKey!!)
|
||||
jsonString = decryptedBytes.toString(Charsets.UTF_8)
|
||||
} catch (e: javax.crypto.AEADBadTagException) {
|
||||
android.util.Log.w("JsonImportExportDialog", "Decryption failed, trying as unencrypted: ${e.message}")
|
||||
// Fallback to unencrypted if decryption fails
|
||||
jsonString = fileContent.toString(Charsets.UTF_8)
|
||||
}
|
||||
} else {
|
||||
// If no secret key, assume unencrypted
|
||||
jsonString = fileContent.toString(Charsets.UTF_8)
|
||||
}
|
||||
|
||||
if (jsonString != null) {
|
||||
try {
|
||||
shoppingListsViewModel.importShoppingListsFromJson(jsonString)
|
||||
android.widget.Toast.makeText(context, R.string.json_import_successful, android.widget.Toast.LENGTH_SHORT).show()
|
||||
} catch (e: kotlinx.serialization.SerializationException) {
|
||||
android.util.Log.e("JsonImportExportDialog", "Error parsing shopping lists JSON: ${e.message}")
|
||||
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_invalid_format, e.message), android.widget.Toast.LENGTH_LONG).show()
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("JsonImportExportDialog", "Error importing shopping lists: ${e.message}")
|
||||
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_generic, e.message), android.widget.Toast.LENGTH_LONG).show()
|
||||
}
|
||||
} else {
|
||||
android.widget.Toast.makeText(context, R.string.json_import_failed_no_data, android.widget.Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,12 +237,36 @@ fun JsonImportExportDialog(
|
||||
uri?.let {
|
||||
scope.launch {
|
||||
context.contentResolver.openInputStream(it)?.use { inputStream ->
|
||||
secretKey?.let { key ->
|
||||
val encryptedContent = inputStream.readBytes()
|
||||
val decryptedBytes = fileEncryptor.decrypt(encryptedContent, key)
|
||||
val jsonString = decryptedBytes.toString(Charsets.UTF_8)
|
||||
recipesViewModel.importRecipesFromJson(jsonString)
|
||||
android.widget.Toast.makeText(context, R.string.json_import_successful, android.widget.Toast.LENGTH_SHORT).show()
|
||||
val fileContent = inputStream.readBytes()
|
||||
var jsonString: String? = null
|
||||
|
||||
if (secretKey != null) {
|
||||
try {
|
||||
val decryptedBytes = fileEncryptor.decrypt(fileContent, secretKey!!)
|
||||
jsonString = decryptedBytes.toString(Charsets.UTF_8)
|
||||
} catch (e: javax.crypto.AEADBadTagException) {
|
||||
android.util.Log.w("JsonImportExportDialog", "Decryption failed, trying as unencrypted: ${e.message}")
|
||||
// Fallback to unencrypted if decryption fails
|
||||
jsonString = fileContent.toString(Charsets.UTF_8)
|
||||
}
|
||||
} else {
|
||||
// If no secret key, assume unencrypted
|
||||
jsonString = fileContent.toString(Charsets.UTF_8)
|
||||
}
|
||||
|
||||
if (jsonString != null) {
|
||||
try {
|
||||
recipesViewModel.importRecipesFromJson(jsonString)
|
||||
android.widget.Toast.makeText(context, R.string.json_import_successful, android.widget.Toast.LENGTH_SHORT).show()
|
||||
} catch (e: kotlinx.serialization.SerializationException) {
|
||||
android.util.Log.e("JsonImportExportDialog", "Error parsing recipes JSON: ${e.message}")
|
||||
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_invalid_format, e.message), android.widget.Toast.LENGTH_LONG).show()
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("JsonImportExportDialog", "Error importing recipes: ${e.message}")
|
||||
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_generic, e.message), android.widget.Toast.LENGTH_LONG).show()
|
||||
}
|
||||
} else {
|
||||
android.widget.Toast.makeText(context, R.string.json_import_failed_no_data, android.widget.Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
app/src/main/res/values-de/salt.xml
Normal file
3
app/src/main/res/values-de/salt.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="encryption_salt" translatable="false">/y2B/g/y2B/g/y2B/g==</string>
|
||||
</resources>
|
||||
@@ -315,4 +315,7 @@
|
||||
<string name="tour_page_3_title">Sicher & Privat</string>
|
||||
<string name="tour_page_3_text">Deine Daten gehören dir. Verschlüssele deine Daten für maximale Privatsphäre.</string>
|
||||
<string name="show_guided_tour">Geführte Tour anzeigen</string>
|
||||
<string name="json_import_failed_invalid_format">JSON-Import fehlgeschlagen: Ungültiges Format. %1$s</string>
|
||||
<string name="json_import_failed_generic">JSON-Import fehlgeschlagen: %1$s</string>
|
||||
<string name="json_import_failed_no_data">JSON-Import fehlgeschlagen: Keine Daten zum Importieren.</string>
|
||||
</resources>
|
||||
@@ -315,4 +315,7 @@
|
||||
<string name="tour_page_3_title">Secure & Private</string>
|
||||
<string name="tour_page_3_text">Your data is yours. Encrypt your data for maximum privacy.</string>
|
||||
<string name="show_guided_tour">Show Guided Tour</string>
|
||||
<string name="json_import_failed_invalid_format">JSON import failed: Invalid format. %1$s</string>
|
||||
<string name="json_import_failed_generic">JSON import failed: %1$s</string>
|
||||
<string name="json_import_failed_no_data">JSON import failed: No data to import.</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user