Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store and show the size of app backups #605

Merged
merged 2 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -38,6 +38,7 @@ data class AppStatus(
val icon: Drawable,
val name: String,
val time: Long,
val size: Long?,
val status: AppBackupState,
val isSpecial: Boolean = false,
) : AppListItem()
Expand Down Expand Up @@ -87,6 +88,7 @@ internal class AppListRetriever(
icon = getIcon(packageName),
name = context.getString(stringId),
time = metadata?.time ?: 0,
size = metadata?.size,
status = status,
isSpecial = true
)
Expand All @@ -111,6 +113,7 @@ internal class AppListRetriever(
icon = getIcon(it.packageName),
name = getAppName(context, it.packageName).toString(),
time = time,
size = metadata?.size,
status = status
)
}.sortedBy { it.name.lowercase(locale) }
Expand All @@ -125,6 +128,7 @@ internal class AppListRetriever(
icon = getIcon(it.packageName),
name = getAppName(context, it.packageName).toString(),
time = 0,
size = null,
status = FAILED_NOT_ALLOWED
)
}.sortedBy { it.name.lowercase(locale) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.stevesoltys.seedvault.settings
import android.content.Intent
import android.net.Uri
import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
import android.text.format.Formatter.formatShortFileSize
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
Expand Down Expand Up @@ -116,7 +117,12 @@ internal class AppStatusAdapter(private val toggleListener: AppStatusToggleListe
setState(item.status, false)
}
if (item.status == SUCCEEDED) {
appInfo.text = item.time.toRelativeTime(context)
appInfo.text = if (item.size == null) {
item.time.toRelativeTime(context)
} else {
item.time.toRelativeTime(context).toString() +
" (${formatShortFileSize(v.context, item.size)})"
}
appInfo.visibility = VISIBLE
}
switchView.visibility = INVISIBLE
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
Loading