mirror of
https://github.com/dzeiocom/OpenHealth.git
synced 2025-04-23 03:12:14 +00:00
feat: add import/export functionality (#152)
This commit is contained in:
parent
85ec96e540
commit
468f30bcca
@ -16,7 +16,6 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
|
||||
<application
|
||||
android:name=".Application"
|
||||
android:allowBackup="true"
|
||||
|
@ -13,6 +13,9 @@ import com.dzeio.openhealth.data.water.Water
|
||||
import com.dzeio.openhealth.data.water.WaterDao
|
||||
import com.dzeio.openhealth.data.weight.Weight
|
||||
import com.dzeio.openhealth.data.weight.WeightDao
|
||||
import com.dzeio.openhealth.utils.ZipFile
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* ROOM SQLite database for the application
|
||||
@ -44,7 +47,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
/**
|
||||
* database name duh
|
||||
*/
|
||||
private const val DATABASE_NAME = "open_health"
|
||||
const val DATABASE_NAME = "open_health"
|
||||
|
||||
// For Singleton instantiation
|
||||
@Volatile
|
||||
@ -66,4 +69,38 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* export the database to a ZIP file and returns the ByteArray of this ZipFile
|
||||
*
|
||||
* @param context the application context
|
||||
* @return the database as a .zip file
|
||||
*
|
||||
* based on https://stackoverflow.com/a/73201175/7335674
|
||||
*/
|
||||
fun exportDatabase(context: Context): ByteArray? {
|
||||
if (instance == null) return null
|
||||
|
||||
val dbFile = context.getDatabasePath(DATABASE_NAME)
|
||||
val dbWalFile = File(dbFile.path + "-whal")
|
||||
val dbShmFile = File(dbFile.path + "-shm")
|
||||
checkpoint()
|
||||
|
||||
try {
|
||||
val output = ZipFile()
|
||||
output.addFile(dbFile.name, dbFile)
|
||||
if (dbWalFile.exists()) output.addFile(dbWalFile.name, dbWalFile)
|
||||
if (dbShmFile.exists()) output.addFile(dbShmFile.name, dbShmFile)
|
||||
return output.toByteArray()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun checkpoint() {
|
||||
val db = this.openHelper.writableDatabase
|
||||
db.query("PRAGMA wal_checkpoint(FULL);", emptyArray())
|
||||
db.query("PRAGMA wal_checkpoint(TRUNCATE);", emptyArray())
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,98 @@
|
||||
package com.dzeio.openhealth.ui.importexport
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import com.dzeio.openhealth.R
|
||||
import com.dzeio.openhealth.core.BaseFragment
|
||||
import com.dzeio.openhealth.data.AppDatabase
|
||||
import com.dzeio.openhealth.databinding.FragmentImportExportBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.io.File
|
||||
import java.util.zip.ZipInputStream
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ImportExportFragment : BaseFragment<ImportExportViewModel, FragmentImportExportBinding>(
|
||||
ImportExportViewModel::class.java
|
||||
) {
|
||||
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentImportExportBinding =
|
||||
FragmentImportExportBinding::inflate
|
||||
|
||||
/**
|
||||
* Response to the import button event
|
||||
*/
|
||||
private val readResult = registerForActivityResult(
|
||||
ActivityResultContracts.OpenDocument()
|
||||
) {
|
||||
// stop if no document was chosen
|
||||
if (it == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
// read the zipfile
|
||||
val stream = requireActivity().contentResolver.openInputStream(it)
|
||||
val zip = ZipInputStream(stream)
|
||||
var entry = zip.nextEntry
|
||||
|
||||
// copy each file to the database directory
|
||||
while (entry != null) {
|
||||
val direction = File(requireContext().getDatabasePath(AppDatabase.DATABASE_NAME).parent, entry.name)
|
||||
Log.d("Pouet", entry.name)
|
||||
|
||||
direction.writeBytes(zip.readBytes())
|
||||
entry = zip.nextEntry
|
||||
}
|
||||
|
||||
// Restart the app
|
||||
val i = requireContext().packageManager.getLaunchIntentForPackage(requireContext().packageName)
|
||||
i!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
requireContext().startActivity(i)
|
||||
Toast.makeText(requireContext(), R.string.import_complete, Toast.LENGTH_LONG).show()
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Response to the export button event
|
||||
*/
|
||||
private val writeResult = registerForActivityResult(
|
||||
ActivityResultContracts.CreateDocument("application/zip")
|
||||
) {
|
||||
|
||||
// stop if no location is given
|
||||
if (it == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
// export the db to a zip
|
||||
val bkp = viewModel.appDatabase.exportDatabase(requireContext())
|
||||
viewModel.appDatabase.checkpoint()
|
||||
|
||||
// write file to location
|
||||
requireActivity().contentResolver.openOutputStream(it)?.apply {
|
||||
write(bkp)
|
||||
close()
|
||||
}
|
||||
|
||||
Toast.makeText(requireContext(), R.string.export_complete, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.imports.setOnClickListener {
|
||||
// ask user the location of the backup file
|
||||
readResult.launch(arrayOf("application/zip"))
|
||||
}
|
||||
|
||||
binding.export.setOnClickListener {
|
||||
// ask user the location to save the backup file
|
||||
writeResult.launch("export.openhealth")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.dzeio.openhealth.ui.importexport
|
||||
|
||||
import com.dzeio.openhealth.core.BaseViewModel
|
||||
import com.dzeio.openhealth.data.AppDatabase
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ImportExportViewModel @Inject internal constructor(
|
||||
val appDatabase: AppDatabase
|
||||
) : BaseViewModel()
|
@ -5,7 +5,6 @@ import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import com.dzeio.openhealth.core.BaseFragment
|
||||
import com.dzeio.openhealth.databinding.FragmentTestsBinding
|
||||
import com.dzeio.openhealth.utils.Bluetooth
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -19,28 +18,8 @@ class TestsFragment :
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
// Bindings
|
||||
// Test code below
|
||||
binding.button.setOnClickListener {
|
||||
Bluetooth(requireContext()).apply {
|
||||
// scanDevices(cb)
|
||||
|
||||
// val device = DeviceMiSmartScale2(requireContext())
|
||||
//
|
||||
// device.status.addObserver {
|
||||
// Log.d("Device", "New device status $it")
|
||||
//
|
||||
// if (it == Device.ConnectionStatus.CONNECTED) {
|
||||
// device.fetchWeights().addObserver {
|
||||
// Log.i(
|
||||
// "FetchStatus",
|
||||
// "${(it.progress.toFloat() / it.progressMax.toFloat() * 100f).roundToInt()}% ${it.progress}/${it.progressMax}"
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// device.connect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
42
app/src/main/java/com/dzeio/openhealth/utils/ZipFile.kt
Normal file
42
app/src/main/java/com/dzeio/openhealth/utils/ZipFile.kt
Normal file
@ -0,0 +1,42 @@
|
||||
package com.dzeio.openhealth.utils
|
||||
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
class ZipFile {
|
||||
|
||||
private val stream = ByteArrayOutputStream()
|
||||
private val output = ZipOutputStream(BufferedOutputStream(stream))
|
||||
|
||||
fun addFile(path: String, item: String) = addFile(path, item.toByteArray())
|
||||
fun addFile(path: String, item: File) {
|
||||
// Read file
|
||||
val data = item.inputStream()
|
||||
val bytes = data.readBytes()
|
||||
data.close()
|
||||
|
||||
return addFile(path, bytes)
|
||||
}
|
||||
|
||||
fun addFile(path: String, item: ByteArray) {
|
||||
val entry = ZipEntry(path)
|
||||
try {
|
||||
output.putNextEntry(entry)
|
||||
|
||||
output.write(item)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
output.closeEntry()
|
||||
}
|
||||
|
||||
fun toByteArray(): ByteArray {
|
||||
output.close()
|
||||
return stream.toByteArray()
|
||||
}
|
||||
}
|
47
app/src/main/res/layout/fragment_import_export.xml
Normal file
47
app/src/main/res/layout/fragment_import_export.xml
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_margin="32dp"
|
||||
app:srcCompat="@drawable/ic_logo_full" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/import_export_data"
|
||||
android:paddingBottom="16dp"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/imports"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="@string/importTxt" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/export"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/export" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
@ -14,6 +14,11 @@
|
||||
android:title="@string/page_settings"
|
||||
android:icon="@drawable/ic_baseline_settings_24"/>
|
||||
|
||||
|
||||
<item
|
||||
android:id="@+id/import_export_fragment"
|
||||
android:title="@string/import_export" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_about"
|
||||
android:title="@string/about" />
|
||||
|
@ -212,4 +212,9 @@
|
||||
tools:layout="@layout/dialog_search"
|
||||
android:name="com.dzeio.openhealth.ui.weight.ScanScalesDialog"
|
||||
android:label="ScanScalesDialog" />
|
||||
<fragment
|
||||
android:id="@+id/import_export_fragment"
|
||||
android:name="com.dzeio.openhealth.ui.importexport.ImportExportFragment"
|
||||
tools:layout="@layout/fragment_import_export"
|
||||
android:label="@string/import_export" />
|
||||
</navigation>
|
||||
|
@ -58,4 +58,10 @@
|
||||
<string name="bluetooth_scale">Balance bluetooth</string>
|
||||
<string name="sync">Synchroniser</string>
|
||||
<string name="searching_scales">Recherche de balances connecté...</string>
|
||||
<string name="import_export_data">Importer & Exporter les données de l\'application.</string>
|
||||
<string name="export">Exporter</string>
|
||||
<string name="importTxt">Importer</string>
|
||||
<string name="import_complete">Import réussi, redémarrage de l\'application</string>
|
||||
<string name="export_complete">Export Réussi!</string>
|
||||
<string name="import_export">Importer/Exporter</string>
|
||||
</resources>
|
||||
|
@ -71,4 +71,10 @@
|
||||
<string name="searching_scales">Searching Scales</string>
|
||||
<string name="sync">Sync</string>
|
||||
<string name="bluetooth_scale">Bluetooth Scale</string>
|
||||
<string name="import_export_data">Import & Export the application datas</string>
|
||||
<string name="export">Export</string>
|
||||
<string name="importTxt">Import</string>
|
||||
<string name="import_complete">Import successful, Restarting the application</string>
|
||||
<string name="export_complete">Export successful!</string>
|
||||
<string name="import_export">Import/Export</string>
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user