From 060bb425da7ce15c1c5eab5b642450d37ef7cb01 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 15 Oct 2024 15:52:40 -0300 Subject: [PATCH] UI for checking app backups --- .../java/com/stevesoltys/seedvault/App.kt | 1 + .../com/stevesoltys/seedvault/repo/Checker.kt | 39 +++++++ .../stevesoltys/seedvault/repo/RepoModule.kt | 1 + .../seedvault/settings/AppCheckFragment.kt | 87 +++++++++++++++ .../seedvault/settings/SettingsViewModel.kt | 11 ++ app/src/main/res/drawable/ic_cloud_search.xml | 10 ++ .../main/res/layout/fragment_app_check.xml | 104 ++++++++++++++++++ app/src/main/res/values/strings.xml | 8 ++ app/src/main/res/xml/settings.xml | 7 ++ 9 files changed, 268 insertions(+) create mode 100644 app/src/main/java/com/stevesoltys/seedvault/repo/Checker.kt create mode 100644 app/src/main/java/com/stevesoltys/seedvault/settings/AppCheckFragment.kt create mode 100644 app/src/main/res/drawable/ic_cloud_search.xml create mode 100644 app/src/main/res/layout/fragment_app_check.xml diff --git a/app/src/main/java/com/stevesoltys/seedvault/App.kt b/app/src/main/java/com/stevesoltys/seedvault/App.kt index eac57f83..02b31b0c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/App.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/App.kt @@ -90,6 +90,7 @@ open class App : Application() { storageBackup = get(), backupManager = get(), backupStateManager = get(), + checker = get(), ) } viewModel { diff --git a/app/src/main/java/com/stevesoltys/seedvault/repo/Checker.kt b/app/src/main/java/com/stevesoltys/seedvault/repo/Checker.kt new file mode 100644 index 00000000..ffcdf627 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/repo/Checker.kt @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2024 The Calyx Institute + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.repo + +import androidx.annotation.WorkerThread +import com.stevesoltys.seedvault.backend.BackendManager +import com.stevesoltys.seedvault.crypto.Crypto +import org.calyxos.seedvault.core.backends.AppBackupFileType +import org.calyxos.seedvault.core.backends.TopLevelFolder + +@WorkerThread +internal class Checker( + private val crypto: Crypto, + private val backendManager: BackendManager, + private val snapshotManager: SnapshotManager, +) { + + suspend fun getBackupSize(): Long { + // get all snapshots + val folder = TopLevelFolder(crypto.repoId) + val handles = mutableListOf() + backendManager.backend.list(folder, AppBackupFileType.Snapshot::class) { fileInfo -> + handles.add(fileInfo.fileHandle as AppBackupFileType.Snapshot) + } + val snapshots = snapshotManager.onSnapshotsLoaded(handles) + + // get total disk space used by snapshots + val sizeMap = mutableMapOf() + snapshots.forEach { snapshot -> + // add sizes to a map first, so we don't double count + snapshot.blobsMap.forEach { (chunkId, blob) -> sizeMap[chunkId] = blob.length } + } + return sizeMap.values.sumOf { it.toLong() } + } + +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/repo/RepoModule.kt b/app/src/main/java/com/stevesoltys/seedvault/repo/RepoModule.kt index 8aa72064..bf1dca9f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/repo/RepoModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/repo/RepoModule.kt @@ -21,4 +21,5 @@ val repoModule = module { } factory { SnapshotCreatorFactory(androidContext(), get(), get(), get()) } factory { Pruner(get(), get(), get()) } + single { Checker(get(), get(), get()) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/AppCheckFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/AppCheckFragment.kt new file mode 100644 index 00000000..076e8a3f --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/AppCheckFragment.kt @@ -0,0 +1,87 @@ +/* + * SPDX-FileCopyrightText: 2024 The Calyx Institute + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.settings + +import android.os.Bundle +import android.text.format.Formatter.formatShortFileSize +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import android.widget.Button +import android.widget.ScrollView +import android.widget.TextView +import androidx.fragment.app.Fragment +import com.google.android.material.slider.LabelFormatter.LABEL_VISIBLE +import com.google.android.material.slider.Slider +import com.stevesoltys.seedvault.R +import org.koin.androidx.viewmodel.ext.android.activityViewModel + +private const val WARN_PERCENT = 25 +private const val WARN_BYTES = 1024 * 1024 * 1024 // 1 GB + +class AppCheckFragment : Fragment() { + + private val viewModel: SettingsViewModel by activityViewModel() + private lateinit var sliderLabel: TextView + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + val v = inflater.inflate(R.layout.fragment_app_check, container, false) as ScrollView + + val slider = v.requireViewById(R.id.slider) + sliderLabel = v.requireViewById(R.id.sliderLabel) + + // label not scrolling will be fixed in material-components 1.12.0 (next update) + slider.setLabelFormatter { value -> + viewModel.backupSize.value?.let { + formatShortFileSize(context, (it * value / 100).toLong()) + } ?: "${value.toInt()}%" + } + slider.addOnChangeListener { _, value, _ -> + onSliderChanged(value) + } + + viewModel.backupSize.observe(viewLifecycleOwner) { + slider.labelBehavior = LABEL_VISIBLE + slider.invalidate() + onSliderChanged(slider.value) + // we can stop observing as the loaded size won't change again + viewModel.backupSize.removeObservers(viewLifecycleOwner) + } + + v.requireViewById