Support adb shell bmgr backupnow

We don't get notified about the start nor the end of such a backup run, so we need hacks to do initialization and finalization.
This commit is contained in:
Torsten Grote 2024-09-25 14:12:09 -03:00
parent e602bbe2ab
commit 8d949e2d64
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
5 changed files with 48 additions and 4 deletions

View file

@ -38,9 +38,13 @@ internal class AppBackupManager(
/** /**
* A temporary [SnapshotCreator] that has a lifetime only valid during the backup run. * A temporary [SnapshotCreator] that has a lifetime only valid during the backup run.
*/ */
@Volatile
var snapshotCreator: SnapshotCreator? = null var snapshotCreator: SnapshotCreator? = null
private set private set
@Volatile
private var startedViaAdb = false
/** /**
* Call this method before doing any kind of backup work. * Call this method before doing any kind of backup work.
* It will * It will
@ -113,6 +117,29 @@ internal class AppBackupManager(
} }
} }
/**
* When doing backups with `adb shell bmgr backupnow`,
* we don't get a chance to do our initialization in [beforeBackup],
* so we use this opportunity to do it now.
*/
suspend fun ensureBackupPrepared() = if (snapshotCreator == null) {
log.warn { "Backup not prepared. If not started via `adb shell bmgr` that's a bug" }
startedViaAdb = true
beforeBackup()
} else Unit
/**
* We don't get notified when backups ran from `adb shell bmgr backupnow` end,
* so [afterBackupFinished] will not run, so we need to find a place
*/
suspend fun finalizeBackupIfNeeded() {
if (startedViaAdb) {
log.warn { "Backup not finalized. If not started via `adb shell bmgr` that's a bug" }
startedViaAdb = false
afterBackupFinished(true) // is there a way to know if success or not?
}
}
/** /**
* Returns true if the repo identified by [repoId] can be transferred to this device. * Returns true if the repo identified by [repoId] can be transferred to this device.
* This is the case when it isn't the same as the current repoId and the version is latest. * This is the case when it isn't the same as the current repoId and the version is latest.

View file

@ -127,8 +127,8 @@ class ConfigurableBackupTransport internal constructor(private val context: Cont
return backupCoordinator.isAppEligibleForBackup(targetPackage, isFullBackup) return backupCoordinator.isAppEligibleForBackup(targetPackage, isFullBackup)
} }
override fun getBackupQuota(packageName: String, isFullBackup: Boolean): Long { override fun getBackupQuota(packageName: String, isFullBackup: Boolean): Long = runBlocking {
return backupCoordinator.getBackupQuota(packageName, isFullBackup) backupCoordinator.getBackupQuota(packageName, isFullBackup)
} }
override fun clearBackupData(packageInfo: PackageInfo): Int = runBlocking { override fun clearBackupData(packageInfo: PackageInfo): Int = runBlocking {

View file

@ -12,9 +12,11 @@ import android.os.IBinder
import android.util.Log import android.util.Log
import com.stevesoltys.seedvault.crypto.KeyManager import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.permitDiskReads import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.repo.AppBackupManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.runBlocking
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
@ -35,6 +37,7 @@ class ConfigurableBackupTransportService : Service(), KoinComponent {
private val keyManager: KeyManager by inject() private val keyManager: KeyManager by inject()
private val backupManager: IBackupManager by inject() private val backupManager: IBackupManager by inject()
private val appBackupManager: AppBackupManager by inject()
private val notificationManager: BackupNotificationManager by inject() private val notificationManager: BackupNotificationManager by inject()
override fun onCreate() { override fun onCreate() {
@ -62,6 +65,11 @@ class ConfigurableBackupTransportService : Service(), KoinComponent {
notificationManager.onServiceDestroyed() notificationManager.onServiceDestroyed()
transport = null transport = null
mIsRunning.value = false mIsRunning.value = false
runBlocking {
// This is a hack for `adb shell bmgr backupnow`. Better would be a foreground service,
// but since this isn't a typical use-case we don't bother for now.
appBackupManager.finalizeBackupIfNeeded()
}
Log.d(TAG, "Service destroyed.") Log.d(TAG, "Service destroyed.")
} }

View file

@ -126,9 +126,14 @@ internal class BackupCoordinator(
* otherwise for key-value backup. * otherwise for key-value backup.
* @return Current limit on backup size in bytes. * @return Current limit on backup size in bytes.
*/ */
fun getBackupQuota(packageName: String, isFullBackup: Boolean): Long { suspend fun getBackupQuota(packageName: String, isFullBackup: Boolean): Long {
// report back quota
Log.i(TAG, "Get backup quota for $packageName. Is full backup: $isFullBackup.") Log.i(TAG, "Get backup quota for $packageName. Is full backup: $isFullBackup.")
if (!isFullBackup) {
// hack for `adb shell bmgr backupnow`
// which starts with a K/V backup calling this method, so we hook in here
appBackupManager.ensureBackupPrepared()
}
val quota = settingsManager.quota val quota = settingsManager.quota
Log.i(TAG, "Reported quota of $quota bytes.") Log.i(TAG, "Reported quota of $quota bytes.")
return quota return quota

View file

@ -85,6 +85,10 @@ internal class BackupCoordinatorTest : BackupTest() {
val quota = Random.nextLong() val quota = Random.nextLong()
every { settingsManager.quota } returns quota every { settingsManager.quota } returns quota
if (!isFullBackup) { // hack for `adb shell bmgr` which starts with a K/V backup
coEvery { appBackupManager.ensureBackupPrepared() } just Runs
}
assertEquals(quota, backup.getBackupQuota(packageInfo.packageName, isFullBackup)) assertEquals(quota, backup.getBackupQuota(packageInfo.packageName, isFullBackup))
} }