Skip to content

Commit

Permalink
WIP Files Backup Check UI
Browse files Browse the repository at this point in the history
  • Loading branch information
grote committed Nov 14, 2024
1 parent 98f1e8d commit 927253f
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import com.stevesoltys.seedvault.worker.AppBackupWorker
import com.stevesoltys.seedvault.worker.AppBackupWorker.Companion.UNIQUE_WORK_NAME
import com.stevesoltys.seedvault.worker.AppCheckerWorker
import com.stevesoltys.seedvault.worker.BackupRequester.Companion.requestFilesAndAppBackup
import com.stevesoltys.seedvault.worker.FileCheckerWorker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
Expand Down Expand Up @@ -90,6 +91,8 @@ internal class SettingsViewModel(

private val mBackupSize = MutableLiveData<Long>()
val backupSize: LiveData<Long> = mBackupSize
private val mFilesBackupSize = MutableLiveData<Long>()
val filesBackupSize: LiveData<Long> = mFilesBackupSize

internal val lastBackupTime = settingsManager.lastBackupTime
internal val appBackupWorkInfo =
Expand Down Expand Up @@ -332,10 +335,20 @@ internal class SettingsViewModel(
}
}

fun loadFileBackupSize() {
viewModelScope.launch(Dispatchers.IO) {
mFilesBackupSize.postValue(storageBackup.getBackupSize())
}
}

fun checkAppBackups(percent: Int) {
AppCheckerWorker.scheduleNow(app, percent)
}

fun checkFileBackups(percent: Int) {
FileCheckerWorker.scheduleNow(app, percent)
}

fun onLogcatUriReceived(uri: Uri?) = viewModelScope.launch(Dispatchers.IO) {
if (uri == null) {
onLogcatError()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.settings.SettingsViewModel
import org.koin.androidx.viewmodel.ext.android.activityViewModel

private const val WARN_PERCENT = 25
private const val WARN_BYTES = 1024 * 1024 * 1024 // 1 GB
internal const val WARN_PERCENT = 25
internal const val WARN_BYTES = 1024 * 1024 * 1024 // 1 GB

class AppCheckFragment : Fragment() {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: 2024 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/

package com.stevesoltys.seedvault.ui.check

import android.os.Bundle
import android.text.format.Formatter
import android.view.LayoutInflater
import android.view.View
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
import com.google.android.material.slider.Slider
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.settings.SettingsViewModel
import org.koin.androidx.viewmodel.ext.android.activityViewModel

class FileCheckFragment : 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

v.requireViewById<TextView>(R.id.titleView).setText(R.string.settings_file_check_title)
v.requireViewById<TextView>(R.id.descriptionView).setText(R.string.settings_file_check_text)
v.requireViewById<TextView>(R.id.introView).setText(R.string.settings_file_check_text2)

val slider = v.requireViewById<Slider>(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.filesBackupSize.value?.let {
Formatter.formatShortFileSize(context, (it * value / 100).toLong())
} ?: "${value.toInt()}%"
}
slider.addOnChangeListener { _, value, _ ->
onSliderChanged(value)
}

viewModel.filesBackupSize.observe(viewLifecycleOwner) {
if (it != null) {
slider.labelBehavior = LabelFormatter.LABEL_VISIBLE
slider.invalidate()
onSliderChanged(slider.value)
}
// we can stop observing as the loaded size won't change again
viewModel.filesBackupSize.removeObservers(viewLifecycleOwner)
}

v.requireViewById<Button>(R.id.startButton).setOnClickListener {
viewModel.checkFileBackups(slider.value.toInt())
requireActivity().onBackPressedDispatcher.onBackPressed()
}
return v
}

override fun onStart() {
super.onStart()
viewModel.loadFileBackupSize()
}

private fun onSliderChanged(value: Float) {
val size = viewModel.filesBackupSize.value
// when size is unknown, we show warning based on percent
val showWarning = if (size == null) {
value > WARN_PERCENT
} else {
size * value / 100 > WARN_BYTES
}
// only update label visibility when different from before
val newVisibility = if (showWarning) View.VISIBLE else View.GONE
if (sliderLabel.visibility != newVisibility) {
sliderLabel.visibility = newVisibility
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* SPDX-FileCopyrightText: 2024 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/

package com.stevesoltys.seedvault.worker

import android.content.Context
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.util.Log
import androidx.work.BackoffPolicy.EXPONENTIAL
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.ExistingWorkPolicy.REPLACE
import androidx.work.ForegroundInfo
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.stevesoltys.seedvault.BackupStateManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.NOTIFICATION_ID_CHECKING
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.flow.first
import org.calyxos.backup.storage.api.StorageBackup
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.time.Duration

class FileCheckerWorker(
appContext: Context,
workerParams: WorkerParameters,
) : CoroutineWorker(appContext, workerParams), KoinComponent {

companion object {
private val TAG = FileCheckerWorker::class.simpleName
private const val PERCENT = "percent"
internal const val UNIQUE_WORK_NAME = "com.stevesoltys.seedvault.FILE_BACKUP_CHECK"

fun scheduleNow(context: Context, percent: Int) {
check(percent in 0..100) { "Percent $percent out of bounds." }
val data = Data.Builder().putInt(PERCENT, percent).build()
val workRequest = OneTimeWorkRequestBuilder<FileCheckerWorker>()
.setExpedited(RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.setBackoffCriteria(EXPONENTIAL, Duration.ofSeconds(10))
.setInputData(data)
.build()
val workManager = WorkManager.getInstance(context)
Log.i(TAG, "Asking to check $percent% of file backups now...")
workManager.enqueueUniqueWork(UNIQUE_WORK_NAME, REPLACE, workRequest)
}
}

private val log = KotlinLogging.logger {}
private val backupStateManager: BackupStateManager by inject()
private val storageBackup: StorageBackup by inject()
private val nm: BackupNotificationManager by inject()

override suspend fun doWork(): Result {
log.info { "Start worker $this ($id)" }
if (backupStateManager.isBackupRunning.first()) {
Log.i(TAG, "isBackupRunning was true, so retrying later...")
return Result.retry()
}
// try {
// setForeground(createForegroundInfo())
// } catch (e: Exception) {
// log.error(e) { "Error while running setForeground: " }
// }
val percent = inputData.getInt(PERCENT, -1)
check(percent in 0..100) { "Percent $percent out of bounds." }

storageBackup.checkBackups(percent, null)
return Result.success()
}

// TODO
private fun createForegroundInfo() = ForegroundInfo(
NOTIFICATION_ID_CHECKING,
nm.getCheckNotification().build(),
FOREGROUND_SERVICE_TYPE_DATA_SYNC,
)
}
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@
<string name="settings_app_check_text2">Here you can run a one-time check to verify your app backup. Select how much of the app data should be downloaded for the check. The more you select, the longer it will take, yet the more reliable the check will be. This will run in the background and show a notification once the check is done.</string>
<string name="settings_app_check_warning">Warning: Downloading large amounts of data can take a long time.</string>
<string name="settings_app_check_button">Check backup</string>
<string name="settings_file_check_title">Verify files backup integrity</string>
<string name="settings_file_check_text">Each backup process will automatically check the backup integrity. However, to save time, actual file data will not be downloaded and verified.</string>
<string name="settings_file_check_text2">Here you can run a one-time check to verify your files backup. Select how much of the files should be downloaded for the check. The more you select, the longer it will take, yet the more reliable the check will be. This will run in the background and show a notification once the check is done.</string>

<string name="settings_expert_title">Expert settings</string>
<string name="settings_expert_logcat_title">Save app log</string>
Expand Down
9 changes: 8 additions & 1 deletion app/src/main/res/xml/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,20 @@

<androidx.preference.Preference
android:dependency="backup_storage"
app:dependency="backup_storage"
app:fragment="com.stevesoltys.seedvault.ui.files.FileSelectionFragment"
app:icon="@drawable/ic_library_add"
app:key="backup_files"
app:summary="@string/settings_backup_files_summary"
app:title="@string/settings_backup_files_title" />

<androidx.preference.Preference
android:dependency="backup_storage"
app:fragment="com.stevesoltys.seedvault.ui.check.FileCheckFragment"
app:icon="@drawable/ic_cloud_search"
app:key="backup_file_check"
app:summary="@string/settings_backup_app_check_summary"
app:title="@string/settings_backup_app_check_title" />

</PreferenceCategory>

<androidx.preference.Preference
Expand Down

0 comments on commit 927253f

Please sign in to comment.