1+ package de.davis.passwordmanager.backup
2+
3+ import android.content.Context
4+ import android.content.DialogInterface
5+ import android.net.Uri
6+ import android.widget.Toast
7+ import androidx.annotation.IntDef
8+ import com.google.android.material.dialog.MaterialAlertDialogBuilder
9+ import de.davis.passwordmanager.R
10+ import de.davis.passwordmanager.dialog.LoadingDialog
11+ import kotlinx.coroutines.Dispatchers
12+ import kotlinx.coroutines.withContext
13+ import java.io.InputStream
14+ import java.io.OutputStream
15+ import javax.crypto.AEADBadTagException
16+
17+
18+ const val TYPE_EXPORT = 0
19+ const val TYPE_IMPORT = 1
20+
21+ @IntDef(TYPE_EXPORT , TYPE_IMPORT )
22+ annotation class Type
23+
24+ abstract class DataBackup (val context : Context ) {
25+
26+ private lateinit var loadingDialog: LoadingDialog
27+
28+ @Throws(Exception ::class )
29+ internal abstract suspend fun runExport (outputStream : OutputStream ): Result
30+
31+ @Throws(Exception ::class )
32+ internal abstract suspend fun runImport (inputStream : InputStream ): Result
33+
34+ open suspend fun execute (@Type type : Int , uri : Uri , onSyncedHandler : OnSyncedHandler ? = null) {
35+ val resolver = context.contentResolver
36+ loadingDialog = LoadingDialog (context).apply {
37+ setTitle(if (type == TYPE_EXPORT ) R .string.export else R .string.import_str)
38+ setMessage(R .string.wait_text)
39+ }
40+ val alertDialog = withContext(Dispatchers .Main ) { loadingDialog.show() }
41+
42+ try {
43+ withContext(Dispatchers .IO ) {
44+ val result: Result = when (type) {
45+ TYPE_EXPORT -> resolver.openOutputStream(uri)?.use { runExport(it) }!!
46+
47+ TYPE_IMPORT -> resolver.openInputStream(uri)?.use { runImport(it) }!!
48+
49+ else -> Result .Error (" Unexpected error occurred" )
50+ }
51+
52+ handleResult(result, onSyncedHandler)
53+ }
54+ } catch (e: Exception ) {
55+ if (e is NullPointerException ) return
56+ error(e)
57+ } finally {
58+ alertDialog.dismiss()
59+ }
60+ }
61+
62+ internal suspend fun error (exception : Exception ) {
63+ val msg = if (exception is AEADBadTagException )
64+ context.getString(R .string.password_does_not_match)
65+ else
66+ exception.message
67+
68+ withContext(Dispatchers .Main ) {
69+ MaterialAlertDialogBuilder (context).apply {
70+ setTitle(R .string.error_title)
71+ setMessage(msg)
72+ setPositiveButton(R .string.ok) { _: DialogInterface ? , _: Int -> }
73+ }.show()
74+ }
75+
76+ exception.printStackTrace()
77+ }
78+
79+ private suspend fun handleResult (result : Result , onSyncedHandler : OnSyncedHandler ? ) =
80+ withContext(Dispatchers .Main ) {
81+ if (result is Result .Success ) {
82+ Toast .makeText(
83+ context,
84+ if (result.type == TYPE_EXPORT ) R .string.backup_stored else R .string.backup_restored,
85+ Toast .LENGTH_LONG
86+ ).show()
87+ handleSyncHandler(onSyncedHandler, result)
88+ return @withContext
89+ }
90+
91+ MaterialAlertDialogBuilder (context).apply {
92+ setPositiveButton(R .string.ok) { _: DialogInterface ? , _: Int ->
93+ handleSyncHandler(
94+ onSyncedHandler,
95+ result
96+ )
97+ }
98+
99+ if (result is Result .Error ) {
100+ setTitle(R .string.error_title)
101+ setMessage(result.message)
102+ } else if (result is Result .Duplicate ) {
103+ setTitle(R .string.warning)
104+ setMessage(
105+ context.resources.getQuantityString(
106+ R .plurals.item_existed,
107+ result.count,
108+ result.count
109+ )
110+ )
111+ }
112+ }.show()
113+ }
114+
115+ private fun handleSyncHandler (onSyncedHandler : OnSyncedHandler ? , result : Result ) {
116+ onSyncedHandler?.onSynced(result)
117+ }
118+
119+ internal suspend fun notifyUpdate (current : Int , max : Int ) {
120+ withContext(Dispatchers .Main ) {
121+ loadingDialog.updateProgress(current, max)
122+ }
123+ }
124+
125+ interface OnSyncedHandler {
126+ fun onSynced (result : Result ? )
127+ }
128+ }
0 commit comments