Skip to content

Commit b5526a7

Browse files
authored
Merge pull request #678 from PhilKes/fix/backup-zip-file-creation
Improve backup docs and logging
2 parents c2610d4 + d401ef7 commit b5526a7

File tree

14 files changed

+313
-144
lines changed

14 files changed

+313
-144
lines changed

app/src/main/java/com/philkes/notallyx/NotallyXApplication.kt

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ import com.philkes.notallyx.presentation.viewmodel.preference.Theme
2121
import com.philkes.notallyx.presentation.widget.WidgetProvider
2222
import com.philkes.notallyx.utils.backup.AUTO_BACKUP_WORK_NAME
2323
import com.philkes.notallyx.utils.backup.autoBackupOnSave
24+
import com.philkes.notallyx.utils.backup.autoBackupOnSaveFileExists
2425
import com.philkes.notallyx.utils.backup.cancelAutoBackup
2526
import com.philkes.notallyx.utils.backup.containsNonCancelled
27+
import com.philkes.notallyx.utils.backup.createBackup
2628
import com.philkes.notallyx.utils.backup.deleteModifiedNoteBackup
2729
import com.philkes.notallyx.utils.backup.isEqualTo
2830
import com.philkes.notallyx.utils.backup.modifiedNoteBackupExists
@@ -31,6 +33,7 @@ import com.philkes.notallyx.utils.backup.updateAutoBackup
3133
import com.philkes.notallyx.utils.observeOnce
3234
import com.philkes.notallyx.utils.security.UnlockReceiver
3335
import java.util.concurrent.TimeUnit
36+
import kotlinx.coroutines.CoroutineScope
3437
import kotlinx.coroutines.Dispatchers
3538
import kotlinx.coroutines.MainScope
3639
import kotlinx.coroutines.launch
@@ -79,7 +82,9 @@ class NotallyXApplication : Application(), Application.ActivityLifecycleCallback
7982
backupFolderBefore,
8083
backupFolder,
8184
preferences.periodicBackups.value.periodInDays.toLong(),
85+
execute = true,
8286
)
87+
checkUpdateAutoBackupOnSave(backupFolderBefore, backupFolder)
8388
}
8489
preferences.periodicBackups.observeForever { value ->
8590
val backupFolder = preferences.backupsFolder.value
@@ -117,14 +122,12 @@ class NotallyXApplication : Application(), Application.ActivityLifecycleCallback
117122
previousBackupPassword != backupPassword)
118123
) {
119124
deleteModifiedNoteBackup(backupPath)
120-
MainScope().launch {
121-
withContext(Dispatchers.IO) {
122-
autoBackupOnSave(
123-
backupPath,
124-
savedNote = null,
125-
password = backupPassword,
126-
)
127-
}
125+
runOnIODispatcher {
126+
autoBackupOnSave(
127+
backupPath,
128+
savedNote = null,
129+
password = backupPassword,
130+
)
128131
}
129132
}
130133
}
@@ -136,6 +139,7 @@ class NotallyXApplication : Application(), Application.ActivityLifecycleCallback
136139
backupFolderBefore: String?,
137140
backupFolder: String,
138141
periodInDays: Long,
142+
execute: Boolean = false,
139143
) {
140144
val workManager = getWorkManagerSafe() ?: return
141145
workManager.getWorkInfosForUniqueWorkLiveData(AUTO_BACKUP_WORK_NAME).observeOnce { workInfos
@@ -150,10 +154,30 @@ class NotallyXApplication : Application(), Application.ActivityLifecycleCallback
150154
(backupFolderBefore != null && backupFolderBefore != backupFolder)
151155
) {
152156
workManager.scheduleAutoBackup(this, periodInDays)
157+
if (execute) {
158+
runOnIODispatcher { createBackup() }
159+
}
153160
} else if (
154161
workInfos.first().periodicityInfo?.isEqualTo(periodInDays, TimeUnit.DAYS) == false
155162
) {
156163
workManager.updateAutoBackup(workInfos, periodInDays)
164+
if (execute) {
165+
runOnIODispatcher { createBackup() }
166+
}
167+
}
168+
}
169+
}
170+
171+
private fun checkUpdateAutoBackupOnSave(backupFolderBefore: String?, backupFolder: String) {
172+
if (preferences.backupOnSave.value) {
173+
if (backupFolderBefore == null && !autoBackupOnSaveFileExists(backupFolder)) {
174+
runOnIODispatcher {
175+
autoBackupOnSave(backupFolder, preferences.backupPassword.value, null)
176+
}
177+
}
178+
} else if (backupFolderBefore != backupFolder) {
179+
runOnIODispatcher {
180+
autoBackupOnSave(backupFolder, preferences.backupPassword.value, null)
157181
}
158182
}
159183
}
@@ -167,10 +191,8 @@ class NotallyXApplication : Application(), Application.ActivityLifecycleCallback
167191
}
168192
}
169193

170-
companion object {
171-
private fun isTestRunner(): Boolean {
172-
return Build.FINGERPRINT.equals("robolectric", ignoreCase = true)
173-
}
194+
private fun <T> runOnIODispatcher(block: suspend CoroutineScope.() -> T) {
195+
MainScope().launch { withContext(Dispatchers.IO, block) }
174196
}
175197

176198
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
@@ -188,4 +210,10 @@ class NotallyXApplication : Application(), Application.ActivityLifecycleCallback
188210
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
189211

190212
override fun onActivityDestroyed(activity: Activity) {}
213+
214+
companion object {
215+
private fun isTestRunner(): Boolean {
216+
return Build.FINGERPRINT.equals("robolectric", ignoreCase = true)
217+
}
218+
}
191219
}

app/src/main/java/com/philkes/notallyx/presentation/UiExtensions.kt

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -841,20 +841,78 @@ fun Fragment.showDialog(
841841
messageResId: Int,
842842
positiveButtonTextResId: Int,
843843
onPositiveButtonClickListener: DialogInterface.OnClickListener,
844+
neutralButtonTextResId: Int? = null,
845+
onNeutralButtonClickListener: DialogInterface.OnClickListener? = null,
846+
fullSize: Boolean = false,
844847
) =
845848
requireContext()
846-
.showDialog(messageResId, positiveButtonTextResId, onPositiveButtonClickListener)
849+
.showDialog(
850+
messageResId,
851+
positiveButtonTextResId,
852+
onPositiveButtonClickListener,
853+
neutralButtonTextResId,
854+
onNeutralButtonClickListener,
855+
fullSize,
856+
)
857+
858+
fun Fragment.showDialog(
859+
message: String,
860+
positiveButtonTextResId: Int,
861+
onPositiveButtonClickListener: DialogInterface.OnClickListener,
862+
neutralButtonTextResId: Int? = null,
863+
onNeutralButtonClickListener: DialogInterface.OnClickListener? = null,
864+
fullSize: Boolean = false,
865+
) =
866+
requireContext()
867+
.showDialog(
868+
message,
869+
positiveButtonTextResId,
870+
onPositiveButtonClickListener,
871+
neutralButtonTextResId,
872+
onNeutralButtonClickListener,
873+
fullSize,
874+
)
847875

848876
fun Context.showDialog(
849877
messageResId: Int,
850878
positiveButtonTextResId: Int,
851879
onPositiveButtonClickListener: DialogInterface.OnClickListener,
880+
neutralButtonTextResId: Int? = null,
881+
onNeutralButtonClickListener: DialogInterface.OnClickListener? = null,
882+
fullSize: Boolean = false,
852883
) {
853-
MaterialAlertDialogBuilder(this)
854-
.setMessage(messageResId)
855-
.setPositiveButton(positiveButtonTextResId, onPositiveButtonClickListener)
856-
.setCancelButton()
857-
.show()
884+
MaterialAlertDialogBuilder(this).apply {
885+
setMessage(messageResId)
886+
setPositiveButton(positiveButtonTextResId, onPositiveButtonClickListener)
887+
setCancelButton()
888+
neutralButtonTextResId?.let { setNeutralButton(it, onNeutralButtonClickListener) }
889+
if (fullSize) {
890+
showAndFocus(allowFullSize = true)
891+
} else {
892+
show()
893+
}
894+
}
895+
}
896+
897+
fun Context.showDialog(
898+
message: String,
899+
positiveButtonTextResId: Int,
900+
onPositiveButtonClickListener: DialogInterface.OnClickListener,
901+
neutralButtonTextResId: Int? = null,
902+
onNeutralButtonClickListener: DialogInterface.OnClickListener? = null,
903+
fullSize: Boolean = false,
904+
) {
905+
MaterialAlertDialogBuilder(this).apply {
906+
setMessage(message)
907+
setPositiveButton(positiveButtonTextResId, onPositiveButtonClickListener)
908+
setCancelButton()
909+
neutralButtonTextResId?.let { setNeutralButton(it, onNeutralButtonClickListener) }
910+
if (fullSize) {
911+
showAndFocus(allowFullSize = true)
912+
} else {
913+
show()
914+
}
915+
}
858916
}
859917

860918
fun Fragment.showToast(messageResId: Int) = requireContext().showToast(messageResId)

app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/settings/PreferenceBindingExtensions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ fun PreferenceBinding.setupBackupsFolder(
440440
{ "Folder with uri: '$uri' does not exist" },
441441
)
442442
if (folder.exists()) {
443-
val path = uri.toReadablePath()
443+
val path = context.toReadablePath(uri)
444444
Value.text = path
445445
} else Value.setText(R.string.cant_find_folder)
446446

app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/settings/SettingsFragment.kt

Lines changed: 69 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import android.view.ViewGroup
1818
import androidx.activity.result.ActivityResultLauncher
1919
import androidx.activity.result.contract.ActivityResultContracts
2020
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
21+
import androidx.core.net.toUri
2122
import androidx.core.view.isVisible
2223
import androidx.fragment.app.Fragment
2324
import androidx.fragment.app.activityViewModels
@@ -622,35 +623,47 @@ class SettingsFragment : Fragment() {
622623
private fun NotallyXPreferences.setupSettings(binding: FragmentSettingsBinding) {
623624
binding.apply {
624625
ImportSettings.setOnClickListener {
625-
showDialog(R.string.import_settings_message, R.string.import_action) { _, _ ->
626-
val intent =
627-
Intent(Intent.ACTION_OPEN_DOCUMENT)
628-
.apply {
629-
type = MIME_TYPE_JSON
630-
addCategory(Intent.CATEGORY_OPENABLE)
631-
putExtra(Intent.EXTRA_TITLE, "NotallyX_Settings.json")
632-
}
633-
.wrapWithChooser(requireContext())
634-
importSettingsActivityResultLauncher.launch(intent)
635-
}
626+
showDialog(
627+
R.string.import_settings_message,
628+
R.string.import_action,
629+
{ _, _ ->
630+
val intent =
631+
Intent(Intent.ACTION_OPEN_DOCUMENT)
632+
.apply {
633+
type = MIME_TYPE_JSON
634+
addCategory(Intent.CATEGORY_OPENABLE)
635+
putExtra(Intent.EXTRA_TITLE, "NotallyX_Settings.json")
636+
}
637+
.wrapWithChooser(requireContext())
638+
importSettingsActivityResultLauncher.launch(intent)
639+
},
640+
)
636641
}
637642
ExportSettings.setOnClickListener {
638-
showDialog(R.string.export_settings_message, R.string.export) { _, _ ->
639-
val intent =
640-
Intent(Intent.ACTION_CREATE_DOCUMENT)
641-
.apply {
642-
type = MIME_TYPE_JSON
643-
addCategory(Intent.CATEGORY_OPENABLE)
644-
putExtra(Intent.EXTRA_TITLE, "NotallyX_Settings.json")
645-
}
646-
.wrapWithChooser(requireContext())
647-
exportSettingsActivityResultLauncher.launch(intent)
648-
}
643+
showDialog(
644+
R.string.export_settings_message,
645+
R.string.export,
646+
{ _, _ ->
647+
val intent =
648+
Intent(Intent.ACTION_CREATE_DOCUMENT)
649+
.apply {
650+
type = MIME_TYPE_JSON
651+
addCategory(Intent.CATEGORY_OPENABLE)
652+
putExtra(Intent.EXTRA_TITLE, "NotallyX_Settings.json")
653+
}
654+
.wrapWithChooser(requireContext())
655+
exportSettingsActivityResultLauncher.launch(intent)
656+
},
657+
)
649658
}
650659
ResetSettings.setOnClickListener {
651-
showDialog(R.string.reset_settings_message, R.string.reset_settings) { _, _ ->
652-
model.resetPreferences { _ -> showToast(R.string.reset_settings_success) }
653-
}
660+
showDialog(
661+
R.string.reset_settings_message,
662+
R.string.reset_settings,
663+
{ _, _ ->
664+
model.resetPreferences { _ -> showToast(R.string.reset_settings_success) }
665+
},
666+
)
654667
}
655668
dataInPublicFolder.observe(viewLifecycleOwner) { value ->
656669
binding.DataInPublicFolder.setup(
@@ -803,10 +816,17 @@ class SettingsFragment : Fragment() {
803816
}
804817

805818
private fun displayChooseBackupFolderDialog() {
806-
showDialog(R.string.auto_backups_folder_hint, R.string.choose_folder) { _, _ ->
807-
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).wrapWithChooser(requireContext())
808-
chooseBackupFolderActivityResultLauncher.launch(intent)
809-
}
819+
showDialog(
820+
getString(R.string.auto_backups_folder_hint, getString(R.string.documentation)),
821+
R.string.choose_folder,
822+
{ _, _ ->
823+
val intent = Intent(ACTION_OPEN_DOCUMENT_TREE).wrapWithChooser(requireContext())
824+
chooseBackupFolderActivityResultLauncher.launch(intent)
825+
},
826+
R.string.documentation,
827+
{ _, _ -> openDocsLink("features/backups#using-cloud-storagewebdav") },
828+
fullSize = true,
829+
)
810830
}
811831

812832
private fun showEnableBiometricLock() {
@@ -845,25 +865,33 @@ class SettingsFragment : Fragment() {
845865
}
846866

847867
private fun showBiometricsNotSetupDialog() {
848-
showDialog(R.string.biometrics_not_setup, R.string.tap_to_set_up) { _, _ ->
849-
val intent =
850-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
851-
Intent(Settings.ACTION_BIOMETRIC_ENROLL)
852-
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
853-
Intent(Settings.ACTION_FINGERPRINT_ENROLL)
854-
} else {
855-
Intent(Settings.ACTION_SECURITY_SETTINGS)
856-
}
857-
setupLockActivityResultLauncher.launch(intent)
858-
}
868+
showDialog(
869+
R.string.biometrics_not_setup,
870+
R.string.tap_to_set_up,
871+
{ _, _ ->
872+
val intent =
873+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
874+
Intent(Settings.ACTION_BIOMETRIC_ENROLL)
875+
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
876+
Intent(Settings.ACTION_FINGERPRINT_ENROLL)
877+
} else {
878+
Intent(Settings.ACTION_SECURITY_SETTINGS)
879+
}
880+
setupLockActivityResultLauncher.launch(intent)
881+
},
882+
)
859883
}
860884

861885
private fun openLink(link: String) {
862-
val uri = Uri.parse(link)
886+
val uri = link.toUri()
863887
val intent = Intent(Intent.ACTION_VIEW, uri).wrapWithChooser(requireContext())
864888
startActivity(intent)
865889
}
866890

891+
private fun openDocsLink(docPath: String) {
892+
openLink("https://philkes.github.io/NotallyX/docs/$docPath")
893+
}
894+
867895
private fun askForUriPermissions(uri: Uri) {
868896
chooseBackupFolderActivityResultLauncher.launch(
869897
Intent(ACTION_OPEN_DOCUMENT_TREE).apply {

0 commit comments

Comments
 (0)