diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt index 01a10265..6c13319c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt @@ -20,7 +20,12 @@ data class BackupMetadata( internal val deviceName: String = "${Build.MANUFACTURER} ${Build.MODEL}", internal var d2dBackup: Boolean = false, internal val packageMetadataMap: PackageMetadataMap = PackageMetadataMap(), -) +) { + val size: Long? + get() = packageMetadataMap.values.sumOf { m -> + (m.size ?: 0L) + (m.splits?.sumOf { it.size ?: 0L } ?: 0L) + } +} internal const val JSON_METADATA = "@meta@" internal const val JSON_METADATA_VERSION = "version" @@ -89,6 +94,7 @@ data class PackageMetadata( data class ApkSplit( val name: String, + val size: Long?, val sha256: String, // There's also a revisionCode, but it doesn't seem to be used just yet ) diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt index 55b29732..ea2d8d17 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt @@ -58,6 +58,8 @@ internal class MetadataManager( return field } + val backupSize: Long? get() = metadata.size + /** * Call this when initializing a new device. * diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt index fe1920b2..1d7bd4ad 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt @@ -169,6 +169,9 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader { val jsonApkSplit = jsonSplits.getJSONObject(i) val apkSplit = ApkSplit( name = jsonApkSplit.getString(JSON_PACKAGE_SPLIT_NAME), + size = jsonApkSplit.optLong(JSON_PACKAGE_SIZE, -1L).let { + if (it < 0L) null else it + }, sha256 = jsonApkSplit.getString(JSON_PACKAGE_SHA256) ) splits.add(apkSplit) diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt index ef9473ba..d1aba586 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt @@ -61,6 +61,7 @@ internal class MetadataWriterImpl(private val crypto: Crypto) : MetadataWriter { put(JSON_PACKAGE_SPLITS, JSONArray().apply { for (split in splits) put(JSONObject().apply { put(JSON_PACKAGE_SPLIT_NAME, split.name) + if (split.size != null) put(JSON_PACKAGE_SIZE, split.size) put(JSON_PACKAGE_SHA256, split.sha256) }) }) diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestorableBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestorableBackup.kt index 2c3abb3c..642533b3 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestorableBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestorableBackup.kt @@ -20,6 +20,9 @@ data class RestorableBackup(val backupMetadata: BackupMetadata) { val time: Long get() = backupMetadata.time + val size: Long? + get() = backupMetadata.size + val deviceName: String get() = backupMetadata.deviceName diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt index f55ccbe3..8e203b0a 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt @@ -3,8 +3,11 @@ package com.stevesoltys.seedvault.restore import android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE import android.text.format.DateUtils.HOUR_IN_MILLIS import android.text.format.DateUtils.getRelativeTimeSpanString +import android.text.format.Formatter 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.TextView import androidx.recyclerview.widget.RecyclerView.Adapter @@ -33,6 +36,7 @@ internal class RestoreSetAdapter( private val titleView = v.requireViewById(R.id.titleView) private val subtitleView = v.requireViewById(R.id.subtitleView) + private val sizeView = v.requireViewById(R.id.sizeView) internal fun bind(item: RestorableBackup) { v.setOnClickListener { listener.onRestorableBackupClicked(item) } @@ -42,6 +46,16 @@ internal class RestoreSetAdapter( val setup = getRelativeTime(item.token) subtitleView.text = v.context.getString(R.string.restore_restore_set_times, lastBackup, setup) + val size = item.size + if (size == null) { + sizeView.visibility = GONE + } else { + sizeView.text = v.context.getString( + R.string.restore_restore_set_size, + Formatter.formatShortFileSize(v.context, size), + ) + sizeView.visibility = VISIBLE + } } private fun getRelativeTime(time: Long): CharSequence { diff --git a/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackup.kt index 991039ad..786975c7 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackup.kt @@ -194,13 +194,16 @@ internal class ApkBackup( // that we exceed the maximum file name length, so we use the hash instead. // The downside is that we need to read the file two times. val messageDigest = MessageDigest.getInstance("SHA-256") - getApkInputStream(sourceDir).use { inputStream -> + val size = getApkInputStream(sourceDir).use { inputStream -> val buffer = ByteArray(DEFAULT_BUFFER_SIZE) + var readCount = 0 var bytes = inputStream.read(buffer) while (bytes >= 0) { + readCount += bytes messageDigest.update(buffer, 0, bytes) bytes = inputStream.read(buffer) } + readCount } val sha256 = messageDigest.digest().encodeBase64() val name = crypto.getNameForApk(metadataManager.salt, packageName, splitName) @@ -210,7 +213,7 @@ internal class ApkBackup( inputStream.copyTo(outputStream) } } - return ApkSplit(splitName, sha256) + return ApkSplit(splitName, size.toLong(), sha256) } } diff --git a/app/src/main/res/layout/list_item_restore_set.xml b/app/src/main/res/layout/list_item_restore_set.xml index 748ec0e6..b430f77e 100644 --- a/app/src/main/res/layout/list_item_restore_set.xml +++ b/app/src/main/res/layout/list_item_restore_set.xml @@ -33,14 +33,25 @@ + tools:text="@string/restore_restore_set_times" /> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5fa45261..8b02e9d8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -195,6 +195,7 @@ Restore from backup Choose a backup to restore Last backup %1$s ยท First %2$s. + Size: %1$s Don\'t restore Skip restoring apps No backups found diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt index 32d3224c..0c3da522 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt @@ -61,9 +61,13 @@ internal class MetadataWriterDecoderTest { version = Random.nextLong(), installer = getRandomString(), splits = listOf( - ApkSplit(getRandomString(), getRandomString()), - ApkSplit(getRandomString(), getRandomString()), - ApkSplit(getRandomString(), getRandomString()) + ApkSplit(getRandomString(), null, getRandomString()), + ApkSplit(getRandomString(), 0L, getRandomString()), + ApkSplit( + name = getRandomString(), + size = Random.nextLong(0, Long.MAX_VALUE), + sha256 = getRandomString(), + ), ), sha256 = getRandomString(), signatures = listOf(getRandomString(), getRandomString()) diff --git a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt index c22c4a38..966e843b 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt @@ -76,7 +76,7 @@ internal class ApkBackupRestoreTest : TransportTest() { installer = getRandomString(), sha256 = "eHx5jjmlvBkQNVuubQzYejay4Q_QICqD47trAF2oNHI", signatures = listOf("AwIB"), - splits = listOf(ApkSplit(splitName, splitSha256)) + splits = listOf(ApkSplit(splitName, Random.nextLong(), splitSha256)) ) private val packageMetadataMap: PackageMetadataMap = hashMapOf(packageName to packageMetadata) private val installerName = packageMetadata.installer diff --git a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt index e6017adc..034038a8 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt @@ -269,8 +269,8 @@ internal class ApkRestoreTest : TransportTest() { val split2Name = getRandomString() packageMetadataMap[packageName] = packageMetadataMap[packageName]!!.copy( splits = listOf( - ApkSplit(split1Name, getRandomBase64()), - ApkSplit(split2Name, getRandomBase64()) + ApkSplit(split1Name, Random.nextLong(), getRandomBase64()), + ApkSplit(split2Name, Random.nextLong(), getRandomBase64()) ) ) @@ -293,7 +293,7 @@ internal class ApkRestoreTest : TransportTest() { // add one APK split to metadata val splitName = getRandomString() packageMetadataMap[packageName] = packageMetadataMap[packageName]!!.copy( - splits = listOf(ApkSplit(splitName, getRandomBase64(23))) + splits = listOf(ApkSplit(splitName, Random.nextLong(), getRandomBase64(23))) ) // cache APK and get icon as well as app name @@ -318,7 +318,7 @@ internal class ApkRestoreTest : TransportTest() { val splitName = getRandomString() val sha256 = getRandomBase64(23) packageMetadataMap[packageName] = packageMetadataMap[packageName]!!.copy( - splits = listOf(ApkSplit(splitName, sha256)) + splits = listOf(ApkSplit(splitName, Random.nextLong(), sha256)) ) // cache APK and get icon as well as app name @@ -343,8 +343,8 @@ internal class ApkRestoreTest : TransportTest() { val split2sha256 = "ZqZ1cVH47lXbEncWx-Pc4L6AdLZOIO2lQuXB5GypxB4" packageMetadataMap[packageName] = packageMetadataMap[packageName]!!.copy( splits = listOf( - ApkSplit(split1Name, split1sha256), - ApkSplit(split2Name, split2sha256) + ApkSplit(split1Name, Random.nextLong(), split1sha256), + ApkSplit(split2Name, Random.nextLong(), split2sha256) ) ) diff --git a/app/src/test/java/com/stevesoltys/seedvault/worker/ApkBackupTest.kt b/app/src/test/java/com/stevesoltys/seedvault/worker/ApkBackupTest.kt index c79f007b..2159ff32 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/worker/ApkBackupTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/worker/ApkBackupTest.kt @@ -208,8 +208,8 @@ internal class ApkBackupTest : BackupTest() { version = packageInfo.longVersionCode, installer = getRandomString(), splits = listOf( - ApkSplit(split1Name, split1Sha256), - ApkSplit(split2Name, split2Sha256) + ApkSplit(split1Name, split1Bytes.size.toLong(), split1Sha256), + ApkSplit(split2Name, split2Bytes.size.toLong(), split2Sha256) ), sha256 = "eHx5jjmlvBkQNVuubQzYejay4Q_QICqD47trAF2oNHI", signatures = packageMetadata.signatures