Added Upload feature. Database will be uploaded to Hetzner Server. Database already encrypted with AES256. Database will be deleted from the app after uploading database.

This commit is contained in:
oxidiert
2025-08-08 12:01:30 +02:00
parent 2cf9faed38
commit fbe548c8d8
7 changed files with 139 additions and 22 deletions

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -53,4 +53,13 @@ dependencies {
implementation("androidx.room:room-ktx:$room_version") implementation("androidx.room:room-ktx:$room_version")
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
// SQLCipher
implementation ("net.zetetic:android-database-sqlcipher:4.5.3@aar")
implementation ("androidx.sqlite:sqlite:2.1.0")
implementation ("androidx.sqlite:sqlite-framework:2.1.0")
// Server Upload
implementation("com.squareup.okhttp3:okhttp:4.12.0")
} }

View File

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name=".MyApp" android:name=".MyApp"
@ -12,6 +14,7 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Test1" android:theme="@style/Theme.Test1"
android:networkSecurityConfig="@xml/network_security_config"
tools:targetApi="31"> tools:targetApi="31">
<provider <provider

View File

@ -0,0 +1,88 @@
package com.dano.test1
import android.content.Context
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.*
import java.io.File
import java.io.IOException
import kotlin.system.exitProcess
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.asRequestBody
object DatabaseUploader {
private const val DB_NAME = "questionnaire_database"
private const val ENCRYPTED_FILE_NAME = "exported_encrypted_database.db"
private const val SERVER_UPLOAD_URL = "http://49.13.157.44/upload.php"
private val client = OkHttpClient()
fun uploadEncryptedDatabase(context: Context) {
CoroutineScope(Dispatchers.IO).launch {
try {
Log.d("UPLOAD", "Upload gestartet")
val dbFile = context.getDatabasePath(DB_NAME)
Log.d("UPLOAD", "Pfad zur DB: ${dbFile.absolutePath}, existiert: ${dbFile.exists()}")
if (!dbFile.exists()) {
Log.e("UPLOAD", "Datenbankdatei existiert nicht.")
return@launch
}
val exportFile = File(context.cacheDir, ENCRYPTED_FILE_NAME)
dbFile.copyTo(exportFile, overwrite = true)
Log.d("UPLOAD", "Datei kopiert: ${exportFile.absolutePath}")
uploadFile(context, exportFile, dbFile)
} catch (e: Exception) {
Log.e("UPLOAD", "Fehler beim Hochladen der DB", e)
}
}
}
private fun uploadFile(context: Context, file: File, originalDbFile: File) {
val requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart(
"file", file.name,
file.asRequestBody("application/octet-stream".toMediaTypeOrNull())
)
.build()
val request = Request.Builder()
.url(SERVER_UPLOAD_URL)
.post(requestBody)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.e("UPLOAD", "Upload fehlgeschlagen: ${e.message}")
}
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
Log.d("UPLOAD", "Upload erfolgreich: ${response.message}")
// Lokale DB-Datei löschen
if (originalDbFile.delete()) {
Log.d("UPLOAD", "Lokale Datenbank erfolgreich gelöscht.")
// App schließen
Log.d("UPLOAD", "App wird beendet.")
exitProcess(0)
} else {
Log.e("UPLOAD", "Löschen der lokalen Datenbank fehlgeschlagen.")
}
} else {
Log.e("UPLOAD", "Upload fehlgeschlagen: ${response.code} ${response.message}")
}
}
})
}
}

View File

@ -10,6 +10,7 @@ import kotlinx.coroutines.*
import org.json.JSONArray import org.json.JSONArray
import android.util.Log import android.util.Log
var INTEGRATION_INDEX_POINTS: Int? = null var INTEGRATION_INDEX_POINTS: Int? = null
class HandlerOpeningScreen(private val activity: MainActivity) { class HandlerOpeningScreen(private val activity: MainActivity) {
@ -22,6 +23,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
private lateinit var buttonLoad: Button private lateinit var buttonLoad: Button
private lateinit var saveButton: Button private lateinit var saveButton: Button
private lateinit var editButton: Button private lateinit var editButton: Button
private lateinit var uploadButton: Button
private val dynamicButtons = mutableListOf<Button>() private val dynamicButtons = mutableListOf<Button>()
private val questionnaireFiles = mutableMapOf<Button, String>() private val questionnaireFiles = mutableMapOf<Button, String>()
@ -40,6 +42,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
setupLoadButton() setupLoadButton()
setupSaveButton() setupSaveButton()
setupEditButton() setupEditButton()
setupUploadButton()
if (!editText.text.isNullOrBlank()) { if (!editText.text.isNullOrBlank()) {
buttonLoad.performClick() buttonLoad.performClick()
@ -54,6 +57,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
buttonLoad = activity.findViewById(R.id.loadButton) buttonLoad = activity.findViewById(R.id.loadButton)
saveButton = activity.findViewById(R.id.saveButton) saveButton = activity.findViewById(R.id.saveButton)
editButton = activity.findViewById(R.id.editButton) editButton = activity.findViewById(R.id.editButton)
uploadButton = activity.findViewById(R.id.uploadButton)
val tag = editText.tag as? String ?: "" val tag = editText.tag as? String ?: ""
editText.hint = LanguageManager.getText(languageID, tag) editText.hint = LanguageManager.getText(languageID, tag)
@ -233,7 +237,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
val conditionMet = when (condition.operator) { val conditionMet = when (condition.operator) {
"!=" -> answerValue != condition.value "!=" -> answerValue != condition.value
"==" -> answerValue == condition.value "==" -> answerValue == condition.value
else -> true // fallback: zeige Fragebogen else -> true
} }
if (conditionMet) break if (conditionMet) break
@ -488,26 +492,6 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
return y return y
} }
private fun enableCompletedQuestionnaireButtons(clientCode: String) {
CoroutineScope(Dispatchers.IO).launch {
val completedEntries = MyApp.database.completedQuestionnaireDao().getAllForClient(clientCode)
val completedFiles = completedEntries
.filter { it.isDone }
.map { it.questionnaireId.lowercase() }
withContext(Dispatchers.Main) {
questionnaireFiles.forEach { (button, fileName) ->
val isCompleted = completedFiles.any { fileName.lowercase().contains(it) }
if (isCompleted) {
button.isEnabled = true
button.alpha = 1.0f
}
}
}
}
}
private fun setupSaveButton() { private fun setupSaveButton() {
saveButton.text = LanguageManager.getText(languageID, "save") saveButton.text = LanguageManager.getText(languageID, "save")
saveButton.setOnClickListener { saveButton.setOnClickListener {
@ -560,4 +544,17 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
} }
} }
private fun setupUploadButton() {
uploadButton.text = "Upload"
uploadButton.setOnClickListener {
val clientCode = editText.text.toString().trim()
GlobalValues.LAST_CLIENT_CODE = clientCode
Toast.makeText(activity, "Datenbank wird verschlüsselt...", Toast.LENGTH_SHORT).show()
DatabaseUploader.uploadEncryptedDatabase(activity)
}
}
} }

View File

@ -44,7 +44,6 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView" /> app:layout_constraintTop_toBottomOf="@id/textView" />
<Button <Button
android:id="@+id/loadButton" android:id="@+id/loadButton"
android:layout_width="72dp" android:layout_width="72dp"
@ -54,6 +53,15 @@
app:layout_constraintStart_toStartOf="@id/editText" app:layout_constraintStart_toStartOf="@id/editText"
app:layout_constraintTop_toBottomOf="@id/editText" /> app:layout_constraintTop_toBottomOf="@id/editText" />
<Button
android:id="@+id/uploadButton"
android:layout_width="200dp"
android:layout_height="42dp"
android:layout_marginTop="52dp"
app:layout_constraintEnd_toEndOf="@id/editText"
app:layout_constraintStart_toStartOf="@id/editText"
app:layout_constraintTop_toBottomOf="@id/editText" />
<Button <Button
android:id="@+id/editButton" android:id="@+id/editButton"
android:layout_width="72dp" android:layout_width="72dp"

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">49.13.157.44</domain>
</domain-config>
</network-security-config>