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:
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@ -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")
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
|
|||||||
88
app/src/main/java/com/dano/test1/DatabaseUploader.kt
Normal file
88
app/src/main/java/com/dano/test1/DatabaseUploader.kt
Normal 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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -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"
|
||||||
|
|||||||
6
app/src/main/res/xml/network_security_config.xml
Normal file
6
app/src/main/res/xml/network_security_config.xml
Normal 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>
|
||||||
Reference in New Issue
Block a user