Skip to content

Commit

Permalink
Store size of app backups in metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
grote committed Jan 16, 2024
1 parent 0319d73 commit c362da8
Show file tree
Hide file tree
Showing 13 changed files with 106 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ data class PackageMetadata(
internal var time: Long = 0L,
internal var state: PackageState = UNKNOWN_ERROR,
internal var backupType: BackupType? = null,
internal var size: Long? = null,
internal val system: Boolean = false,
internal val version: Long? = null,
internal val installer: String? = null,
Expand All @@ -97,6 +98,7 @@ enum class BackupType { KV, FULL }
internal const val JSON_PACKAGE_TIME = "time"
internal const val JSON_PACKAGE_BACKUP_TYPE = "backupType"
internal const val JSON_PACKAGE_STATE = "state"
internal const val JSON_PACKAGE_SIZE = "size"
internal const val JSON_PACKAGE_SYSTEM = "system"
internal const val JSON_PACKAGE_VERSION = "version"
internal const val JSON_PACKAGE_INSTALLER = "installer"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ internal class MetadataManager(
fun onPackageBackedUp(
packageInfo: PackageInfo,
type: BackupType,
size: Long?,
metadataOutputStream: OutputStream,
) {
val packageName = packageInfo.packageName
Expand All @@ -143,12 +144,15 @@ internal class MetadataManager(
metadata.packageMetadataMap[packageName]!!.time = now
metadata.packageMetadataMap[packageName]!!.state = APK_AND_DATA
metadata.packageMetadataMap[packageName]!!.backupType = type
// don't override a previous K/V size, if there were no K/V changes
if (size != null) metadata.packageMetadataMap[packageName]!!.size = size
} else {
metadata.packageMetadataMap[packageName] = PackageMetadata(
time = now,
state = APK_AND_DATA,
backupType = type,
system = packageInfo.isSystemApp()
size = size,
system = packageInfo.isSystemApp(),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
// because when only backing up the APK for example, there's no type
else -> null
}
val pSize = p.optLong(JSON_PACKAGE_SIZE, -1L)
val pSystem = p.optBoolean(JSON_PACKAGE_SYSTEM, false)
val pVersion = p.optLong(JSON_PACKAGE_VERSION, 0L)
val pInstaller = p.optString(JSON_PACKAGE_INSTALLER)
Expand All @@ -136,6 +137,7 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
time = p.getLong(JSON_PACKAGE_TIME),
state = pState,
backupType = pBackupType,
size = if (pSize < 0L) null else pSize,
system = pSystem,
version = if (pVersion == 0L) null else pVersion,
installer = if (pInstaller == "") null else pInstaller,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ internal class MetadataWriterImpl(private val crypto: Crypto) : MetadataWriter {
if (packageMetadata.backupType != null) {
put(JSON_PACKAGE_BACKUP_TYPE, packageMetadata.backupType!!.name)
}
if (packageMetadata.size != null) {
put(JSON_PACKAGE_SIZE, packageMetadata.size)
}
if (packageMetadata.system) {
put(JSON_PACKAGE_SYSTEM, packageMetadata.system)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,14 +363,15 @@ internal class BackupCoordinator(
// getCurrentPackage() not-null because we have state, call before finishing
val packageInfo = kv.getCurrentPackage()!!
val packageName = packageInfo.packageName
val size = kv.getCurrentSize()
// tell K/V backup to finish
var result = kv.finishBackup()
if (result == TRANSPORT_OK) {
val isPmBackup = packageName == MAGIC_PACKAGE_MANAGER
// call onPackageBackedUp for @pm@ only if we can do backups right now
if (!isPmBackup || settingsManager.canDoBackupNow()) {
try {
onPackageBackedUp(packageInfo, BackupType.KV)
onPackageBackedUp(packageInfo, BackupType.KV, size)
} catch (e: Exception) {
Log.e(TAG, "Error calling onPackageBackedUp for $packageName", e)
result = TRANSPORT_PACKAGE_REJECTED
Expand All @@ -396,10 +397,11 @@ internal class BackupCoordinator(
// getCurrentPackage() not-null because we have state
val packageInfo = full.getCurrentPackage()!!
val packageName = packageInfo.packageName
val size = full.getCurrentSize()
// tell full backup to finish
var result = full.finishBackup()
try {
onPackageBackedUp(packageInfo, BackupType.FULL)
onPackageBackedUp(packageInfo, BackupType.FULL, size)
} catch (e: Exception) {
Log.e(TAG, "Error calling onPackageBackedUp for $packageName", e)
result = TRANSPORT_PACKAGE_REJECTED
Expand Down Expand Up @@ -470,9 +472,9 @@ internal class BackupCoordinator(
}
}

private suspend fun onPackageBackedUp(packageInfo: PackageInfo, type: BackupType) {
private suspend fun onPackageBackedUp(packageInfo: PackageInfo, type: BackupType, size: Long?) {
plugin.getMetadataOutputStream().use {
metadataManager.onPackageBackedUp(packageInfo, type, it)
metadataManager.onPackageBackedUp(packageInfo, type, size, it)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ internal class FullBackup(

fun getCurrentPackage() = state?.packageInfo

fun getCurrentSize() = state?.size

fun getQuota(): Long {
return if (settingsManager.isQuotaUnlimited()) Long.MAX_VALUE else DEFAULT_QUOTA_FULL_BACKUP
}
Expand Down Expand Up @@ -190,7 +192,7 @@ internal class FullBackup(
}

fun finishBackup(): Int {
Log.i(TAG, "Finish full backup of ${state!!.packageName}.")
Log.i(TAG, "Finish full backup of ${state!!.packageName}. Wrote ${state!!.size} bytes")
return clearState()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ internal class KVBackup(

fun getCurrentPackage() = state?.packageInfo

fun getCurrentSize() = getCurrentPackage()?.let {
dbManager.getDbSize(it.packageName)
}

fun getQuota(): Long = if (settingsManager.isQuotaUnlimited()) {
Long.MAX_VALUE
} else {
Expand Down Expand Up @@ -252,7 +256,7 @@ internal class KVBackup(
}
}
}
Log.d(TAG, "Uploaded db file for $packageName")
Log.d(TAG, "Uploaded db file for $packageName.")
}

private class KVOperation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ interface KvDbManager {
* Use only for backup.
*/
fun existsDb(packageName: String): Boolean

/**
* Returns the current size of the DB in bytes or null, if no DB exists.
*/
fun getDbSize(packageName: String): Long?
fun deleteDb(packageName: String, isRestore: Boolean = false): Boolean
}

Expand Down Expand Up @@ -59,6 +64,11 @@ class KvDbManagerImpl(private val context: Context) : KvDbManager {
return getDbFile(packageName).isFile
}

override fun getDbSize(packageName: String): Long? {
val file = getDbFile(packageName)
return if (file.isFile) file.length() else null
}

override fun deleteDb(packageName: String, isRestore: Boolean): Boolean {
return getDbFile(packageName, isRestore).delete()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,17 +249,23 @@ class MetadataManagerTest {
time = time,
packageMetadataMap = PackageMetadataMap() // otherwise this isn't copied, but referenced
)
val size = Random.nextLong()
val packageMetadata = PackageMetadata(time)
updatedMetadata.packageMetadataMap[packageName] = packageMetadata

expectReadFromCache()
every { clock.time() } returns time
expectModifyMetadata(initialMetadata)

manager.onPackageBackedUp(packageInfo, BackupType.FULL, storageOutputStream)
manager.onPackageBackedUp(packageInfo, BackupType.FULL, size, storageOutputStream)

assertEquals(
packageMetadata.copy(state = APK_AND_DATA, backupType = BackupType.FULL, system = true),
packageMetadata.copy(
state = APK_AND_DATA,
backupType = BackupType.FULL,
size = size,
system = true,
),
manager.getPackageMetadata(packageName)
)
assertEquals(time, manager.getLastBackupTime())
Expand All @@ -270,6 +276,7 @@ class MetadataManagerTest {
cacheOutputStream.close()
}
}

@Test
fun `test onPackageBackedUp() with D2D enabled`() {
expectReadFromCache()
Expand All @@ -278,7 +285,7 @@ class MetadataManagerTest {

every { settingsManager.d2dBackupsEnabled() } returns true

manager.onPackageBackedUp(packageInfo, BackupType.FULL, storageOutputStream)
manager.onPackageBackedUp(packageInfo, BackupType.FULL, 0L, storageOutputStream)
assertTrue(initialMetadata.d2dBackup)

verify {
Expand All @@ -290,19 +297,20 @@ class MetadataManagerTest {
@Test
fun `test onPackageBackedUp() fails to write to storage`() {
val updateTime = time + 1
val size = Random.nextLong()
val updatedMetadata = initialMetadata.copy(
time = updateTime,
packageMetadataMap = PackageMetadataMap() // otherwise this isn't copied, but referenced
)
updatedMetadata.packageMetadataMap[packageName] =
PackageMetadata(updateTime, APK_AND_DATA, BackupType.KV)
PackageMetadata(updateTime, APK_AND_DATA, BackupType.KV, size)

expectReadFromCache()
every { clock.time() } returns updateTime
every { metadataWriter.write(updatedMetadata, storageOutputStream) } throws IOException()

try {
manager.onPackageBackedUp(packageInfo, BackupType.KV, storageOutputStream)
manager.onPackageBackedUp(packageInfo, BackupType.KV, size, storageOutputStream)
fail()
} catch (e: IOException) {
// expected
Expand Down Expand Up @@ -335,7 +343,7 @@ class MetadataManagerTest {
every { clock.time() } returns time
expectModifyMetadata(updatedMetadata)

manager.onPackageBackedUp(packageInfo, BackupType.FULL, storageOutputStream)
manager.onPackageBackedUp(packageInfo, BackupType.FULL, 0L, storageOutputStream)

assertEquals(time, manager.getLastBackupTime())
assertEquals(PackageMetadata(time), manager.getPackageMetadata(cachedPackageName))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
import kotlin.random.Random
import kotlin.random.nextLong

@TestInstance(PER_CLASS)
internal class MetadataWriterDecoderTest {
Expand Down Expand Up @@ -81,34 +82,37 @@ internal class MetadataWriterDecoderTest {
time = Random.nextLong(),
state = QUOTA_EXCEEDED,
backupType = BackupType.FULL,
size = Random.nextLong(0..Long.MAX_VALUE),
system = Random.nextBoolean(),
version = Random.nextLong(),
installer = getRandomString(),
sha256 = getRandomString(),
signatures = listOf(getRandomString())
signatures = listOf(getRandomString()),
)
)
put(
getRandomString(), PackageMetadata(
time = Random.nextLong(),
state = NO_DATA,
backupType = BackupType.KV,
size = null,
system = Random.nextBoolean(),
version = Random.nextLong(),
installer = getRandomString(),
sha256 = getRandomString(),
signatures = listOf(getRandomString(), getRandomString())
signatures = listOf(getRandomString(), getRandomString()),
)
)
put(
getRandomString(), PackageMetadata(
time = 0L,
state = NOT_ALLOWED,
size = 0,
system = Random.nextBoolean(),
version = Random.nextLong(),
installer = getRandomString(),
sha256 = getRandomString(),
signatures = listOf(getRandomString(), getRandomString())
signatures = listOf(getRandomString(), getRandomString()),
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,12 @@ internal class CoordinatorIntegrationTest : TransportTest() {
metadataManager.onApkBackedUp(packageInfo, packageMetadata, metadataOutputStream)
} just Runs
every {
metadataManager.onPackageBackedUp(packageInfo, BackupType.KV, metadataOutputStream)
metadataManager.onPackageBackedUp(
packageInfo = packageInfo,
type = BackupType.KV,
size = more((appData.size + appData2.size).toLong()), // more because DB overhead
metadataOutputStream = metadataOutputStream,
)
} just Runs

// start K/V backup
Expand Down Expand Up @@ -216,7 +221,12 @@ internal class CoordinatorIntegrationTest : TransportTest() {
backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA)
} returns metadataOutputStream
every {
metadataManager.onPackageBackedUp(packageInfo, BackupType.KV, metadataOutputStream)
metadataManager.onPackageBackedUp(
packageInfo = packageInfo,
type = BackupType.KV,
size = more(size.toLong()), // more than $size, because DB overhead
metadataOutputStream = metadataOutputStream,
)
} just Runs

// start K/V backup
Expand Down Expand Up @@ -289,7 +299,12 @@ internal class CoordinatorIntegrationTest : TransportTest() {
)
} just Runs
every {
metadataManager.onPackageBackedUp(packageInfo, BackupType.FULL, metadataOutputStream)
metadataManager.onPackageBackedUp(
packageInfo = packageInfo,
type = BackupType.FULL,
size = appData.size.toLong(),
metadataOutputStream = metadataOutputStream,
)
} just Runs

// perform backup to output stream
Expand Down
Loading

0 comments on commit c362da8

Please sign in to comment.