Get rid of device folders, use unix epoch as backup token and store it

This commit is contained in:
Torsten Grote 2019-09-11 15:26:10 -03:00
parent 8b6656a350
commit af43c6154d
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
22 changed files with 160 additions and 103 deletions

View file

@ -106,10 +106,10 @@ dependencies {
implementation 'commons-io:commons-io:2.6'
implementation 'io.github.novacrypto:BIP39:2019.01.27'
implementation 'androidx.core:core-ktx:1.0.2'
implementation 'androidx.preference:preference-ktx:1.0.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.preference:preference-ktx:1.1.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
lintChecks 'com.github.thirdegg:lint-rules:0.0.4-alpha'

View file

@ -4,6 +4,7 @@ import androidx.documentfile.provider.DocumentFile
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import com.stevesoltys.backup.settings.getBackupFolderUri
import com.stevesoltys.backup.settings.getBackupToken
import com.stevesoltys.backup.transport.backup.plugins.DocumentsStorage
import com.stevesoltys.backup.transport.backup.plugins.createOrGetFile
import org.junit.After
@ -20,9 +21,9 @@ private const val filename = "test-file"
class DocumentsStorageTest {
private val context = InstrumentationRegistry.getInstrumentation().targetContext
private val token = getBackupToken(context)
private val folderUri = getBackupFolderUri(context)
private val deviceName = "device name"
private val storage = DocumentsStorage(context, folderUri, deviceName)
private val storage = DocumentsStorage(context, folderUri, token)
private lateinit var file: DocumentFile

View file

@ -3,12 +3,11 @@ package com.stevesoltys.backup.metadata
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import com.stevesoltys.backup.header.VERSION
import com.stevesoltys.backup.transport.DEFAULT_RESTORE_SET_TOKEN
import java.io.InputStream
data class BackupMetadata(
internal val version: Byte = VERSION,
internal val token: Long = DEFAULT_RESTORE_SET_TOKEN,
internal val token: Long,
internal val androidVersion: Int = SDK_INT,
internal val deviceName: String = "${Build.MANUFACTURER} ${Build.MODEL}"
)
@ -22,5 +21,8 @@ class FormatException(cause: Throwable) : Exception(cause)
class EncryptedBackupMetadata private constructor(val token: Long, val inputStream: InputStream?, val error: Boolean) {
constructor(token: Long, inputStream: InputStream) : this(token, inputStream, false)
constructor(token: Long, error: Boolean) : this(token, null, false)
/**
* Indicates that there was an error retrieving the encrypted backup metadata.
*/
constructor(token: Long) : this(token, null, true)
}

View file

@ -3,7 +3,6 @@ package com.stevesoltys.backup.metadata
import androidx.annotation.VisibleForTesting
import com.stevesoltys.backup.Utf8
import com.stevesoltys.backup.crypto.Crypto
import com.stevesoltys.backup.transport.DEFAULT_RESTORE_SET_TOKEN
import org.json.JSONObject
import java.io.IOException
import java.io.OutputStream
@ -11,7 +10,7 @@ import java.io.OutputStream
interface MetadataWriter {
@Throws(IOException::class)
fun write(outputStream: OutputStream, token: Long = DEFAULT_RESTORE_SET_TOKEN)
fun write(outputStream: OutputStream, token: Long)
}

View file

@ -10,9 +10,8 @@ import android.util.Log
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.widget.Toast
import android.widget.Toast.LENGTH_SHORT
import androidx.lifecycle.ViewModelProviders
import androidx.preference.Preference
import androidx.preference.Preference.OnPreferenceChangeListener
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.TwoStatePreference
@ -37,7 +36,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
viewModel = ViewModelProviders.of(requireActivity()).get(SettingsViewModel::class.java)
backup = findPreference("backup") as TwoStatePreference
backup = findPreference("backup")!!
backup.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
val enabled = newValue as Boolean
try {
@ -50,13 +49,13 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
}
val backupLocation = findPreference("backup_location")
val backupLocation = findPreference<Preference>("backup_location")!!
backupLocation.setOnPreferenceClickListener {
viewModel.chooseBackupLocation()
true
}
autoRestore = findPreference("auto_restore") as TwoStatePreference
autoRestore = findPreference("auto_restore")!!
autoRestore.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
val enabled = newValue as Boolean
try {

View file

@ -3,8 +3,10 @@ package com.stevesoltys.backup.settings
import android.content.Context
import android.net.Uri
import android.preference.PreferenceManager.getDefaultSharedPreferences
import java.util.*
private const val PREF_KEY_BACKUP_URI = "backupUri"
private const val PREF_KEY_BACKUP_TOKEN = "backupToken"
private const val PREF_KEY_DEVICE_NAME = "deviceName"
private const val PREF_KEY_BACKUP_PASSWORD = "backupLegacyPassword"
@ -21,6 +23,24 @@ fun getBackupFolderUri(context: Context): Uri? {
return Uri.parse(uriStr)
}
/**
* Generates and returns a new backup token while saving it as well.
* Subsequent calls to [getBackupToken] will return this new token once saved.
*/
fun getAndSaveNewBackupToken(context: Context): Long = Date().time.apply {
getDefaultSharedPreferences(context)
.edit()
.putLong(PREF_KEY_BACKUP_TOKEN, this)
.apply()
}
/**
* Returns the current backup token or 0 if none exists.
*/
fun getBackupToken(context: Context): Long {
return getDefaultSharedPreferences(context).getLong(PREF_KEY_BACKUP_TOKEN, 0L)
}
fun setDeviceName(context: Context, name: String) {
getDefaultSharedPreferences(context)
.edit()

View file

@ -13,7 +13,6 @@ import android.util.Log
import com.stevesoltys.backup.settings.SettingsActivity
val TRANSPORT_ID: String = ConfigurableBackupTransport::class.java.name
const val DEFAULT_RESTORE_SET_TOKEN: Long = 1
private const val TRANSPORT_DIRECTORY_NAME = "com.stevesoltys.backup.transport.ConfigurableBackupTransport"
private val TAG = ConfigurableBackupTransport::class.java.simpleName

View file

@ -9,7 +9,7 @@ import com.stevesoltys.backup.header.HeaderWriterImpl
import com.stevesoltys.backup.metadata.MetadataReaderImpl
import com.stevesoltys.backup.metadata.MetadataWriterImpl
import com.stevesoltys.backup.settings.getBackupFolderUri
import com.stevesoltys.backup.settings.getDeviceName
import com.stevesoltys.backup.settings.getBackupToken
import com.stevesoltys.backup.transport.backup.BackupCoordinator
import com.stevesoltys.backup.transport.backup.FullBackup
import com.stevesoltys.backup.transport.backup.InputFactory
@ -26,7 +26,7 @@ class PluginManager(context: Context) {
// We can think about using an injection framework such as Dagger to simplify this.
private val storage = DocumentsStorage(context, getBackupFolderUri(context), getDeviceName(context)!!)
private val storage = DocumentsStorage(context, getBackupFolderUri(context), getBackupToken(context))
private val headerWriter = HeaderWriterImpl()
private val headerReader = HeaderReaderImpl()
@ -42,7 +42,7 @@ class PluginManager(context: Context) {
private val fullBackup = FullBackup(backupPlugin.fullBackupPlugin, inputFactory, headerWriter, crypto)
private val notificationManager = (context.applicationContext as Backup).notificationManager
internal val backupCoordinator = BackupCoordinator(backupPlugin, kvBackup, fullBackup, metadataWriter, notificationManager)
internal val backupCoordinator = BackupCoordinator(context, backupPlugin, kvBackup, fullBackup, metadataWriter, notificationManager)
private val restorePlugin = DocumentsProviderRestorePlugin(storage)
@ -50,6 +50,6 @@ class PluginManager(context: Context) {
private val kvRestore = KVRestore(restorePlugin.kvRestorePlugin, outputFactory, headerReader, crypto)
private val fullRestore = FullRestore(restorePlugin.fullRestorePlugin, outputFactory, headerReader, crypto)
internal val restoreCoordinator = RestoreCoordinator(restorePlugin, kvRestore, fullRestore, metadataReader)
internal val restoreCoordinator = RestoreCoordinator(context, restorePlugin, kvRestore, fullRestore, metadataReader)
}

View file

@ -2,11 +2,13 @@ package com.stevesoltys.backup.transport.backup
import android.app.backup.BackupTransport.TRANSPORT_ERROR
import android.app.backup.BackupTransport.TRANSPORT_OK
import android.content.Context
import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import android.util.Log
import com.stevesoltys.backup.BackupNotificationManager
import com.stevesoltys.backup.metadata.MetadataWriter
import com.stevesoltys.backup.settings.getBackupToken
import java.io.IOException
private val TAG = BackupCoordinator::class.java.simpleName
@ -16,6 +18,7 @@ private val TAG = BackupCoordinator::class.java.simpleName
* @author Torsten Grote
*/
class BackupCoordinator(
private val context: Context,
private val plugin: BackupPlugin,
private val kv: KVBackup,
private val full: FullBackup,
@ -51,7 +54,7 @@ class BackupCoordinator(
Log.i(TAG, "Initialize Device!")
return try {
plugin.initializeDevice()
writeBackupMetadata()
writeBackupMetadata(getBackupToken(context))
// [finishBackup] will only be called when we return [TRANSPORT_OK] here
// so we remember that we initialized successfully
calledInitialize = true
@ -148,9 +151,9 @@ class BackupCoordinator(
}
@Throws(IOException::class)
private fun writeBackupMetadata() {
private fun writeBackupMetadata(token: Long) {
val outputStream = plugin.getMetadataOutputStream()
metadataWriter.write(outputStream)
metadataWriter.write(outputStream, token)
}
}

View file

@ -25,8 +25,8 @@ class DocumentsProviderBackupPlugin(
storage.rootBackupDir ?: throw IOException()
// create backup folders
val kvDir = storage.defaultKvBackupDir
val fullDir = storage.defaultFullBackupDir
val kvDir = storage.currentKvBackupDir
val fullDir = storage.currentFullBackupDir
// wipe existing data
storage.getSetDir()?.findFile(FILE_BACKUP_METADATA)?.delete()

View file

@ -16,7 +16,7 @@ class DocumentsProviderFullBackup(
@Throws(IOException::class)
override fun getOutputStream(targetPackage: PackageInfo): OutputStream {
val file = storage.defaultFullBackupDir?.createOrGetFile(targetPackage.packageName)
val file = storage.currentFullBackupDir?.createOrGetFile(targetPackage.packageName)
?: throw IOException()
return storage.getOutputStream(file)
}
@ -25,7 +25,7 @@ class DocumentsProviderFullBackup(
override fun removeDataOfPackage(packageInfo: PackageInfo) {
val packageName = packageInfo.packageName
Log.i(TAG, "Deleting $packageName...")
val file = storage.defaultFullBackupDir?.findFile(packageName) ?: return
val file = storage.currentFullBackupDir?.findFile(packageName) ?: return
if (!file.delete()) throw IOException("Failed to delete $packageName")
}

View file

@ -15,7 +15,7 @@ class DocumentsProviderKVBackup(private val storage: DocumentsStorage) : KVBacku
@Throws(IOException::class)
override fun hasDataForPackage(packageInfo: PackageInfo): Boolean {
val packageFile = storage.defaultKvBackupDir?.findFile(packageInfo.packageName)
val packageFile = storage.currentKvBackupDir?.findFile(packageInfo.packageName)
?: return false
return packageFile.listFiles().isNotEmpty()
}
@ -30,7 +30,7 @@ class DocumentsProviderKVBackup(private val storage: DocumentsStorage) : KVBacku
override fun removeDataOfPackage(packageInfo: PackageInfo) {
// we cannot use the cached this.packageFile here,
// because this can be called before [ensureRecordStorageForPackage]
val packageFile = storage.defaultKvBackupDir?.findFile(packageInfo.packageName) ?: return
val packageFile = storage.currentKvBackupDir?.findFile(packageInfo.packageName) ?: return
packageFile.delete()
}

View file

@ -5,7 +5,7 @@ import android.content.pm.PackageInfo
import android.net.Uri
import android.util.Log
import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.backup.transport.DEFAULT_RESTORE_SET_TOKEN
import com.stevesoltys.backup.settings.getAndSaveNewBackupToken
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
@ -13,15 +13,13 @@ import java.io.OutputStream
const val DIRECTORY_FULL_BACKUP = "full"
const val DIRECTORY_KEY_VALUE_BACKUP = "kv"
const val FILE_BACKUP_METADATA = ".backup.metadata"
const val FILE_NO_MEDIA = ".nomedia"
private const val ROOT_DIR_NAME = ".AndroidBackup"
private const val NO_MEDIA = ".nomedia"
private const val MIME_TYPE = "application/octet-stream"
private val TAG = DocumentsStorage::class.java.simpleName
class DocumentsStorage(context: Context, parentFolder: Uri?, deviceName: String) {
private val contentResolver = context.contentResolver
class DocumentsStorage(private val context: Context, parentFolder: Uri?, token: Long) {
internal val rootBackupDir: DocumentFile? by lazy {
val folderUri = parentFolder ?: return@lazy null
@ -30,7 +28,7 @@ class DocumentsStorage(context: Context, parentFolder: Uri?, deviceName: String)
try {
val rootDir = parent.createOrGetDirectory(ROOT_DIR_NAME)
// create .nomedia file to prevent Android's MediaScanner from trying to index the backup
rootDir.createOrGetFile(NO_MEDIA)
rootDir.createOrGetFile(FILE_NO_MEDIA)
rootDir
} catch (e: IOException) {
Log.e(TAG, "Error creating root backup dir.", e)
@ -38,73 +36,71 @@ class DocumentsStorage(context: Context, parentFolder: Uri?, deviceName: String)
}
}
private val deviceDir: DocumentFile? by lazy {
private val currentToken: Long by lazy {
if (token != 0L) token
else getAndSaveNewBackupToken(context).apply {
Log.d(TAG, "Using a fresh backup token: $this")
}
}
private val currentSetDir: DocumentFile? by lazy {
val currentSetName = currentToken.toString()
try {
rootBackupDir?.createOrGetDirectory(deviceName)
rootBackupDir?.createOrGetDirectory(currentSetName)
} catch (e: IOException) {
Log.e(TAG, "Error creating current restore set dir.", e)
null
}
}
private val defaultSetDir: DocumentFile? by lazy {
val currentSetName = DEFAULT_RESTORE_SET_TOKEN.toString()
val currentFullBackupDir: DocumentFile? by lazy {
try {
deviceDir?.createOrGetDirectory(currentSetName)
} catch (e: IOException) {
Log.e(TAG, "Error creating current restore set dir.", e)
null
}
}
val defaultFullBackupDir: DocumentFile? by lazy {
try {
defaultSetDir?.createOrGetDirectory(DIRECTORY_FULL_BACKUP)
currentSetDir?.createOrGetDirectory(DIRECTORY_FULL_BACKUP)
} catch (e: IOException) {
Log.e(TAG, "Error creating full backup dir.", e)
null
}
}
val defaultKvBackupDir: DocumentFile? by lazy {
val currentKvBackupDir: DocumentFile? by lazy {
try {
defaultSetDir?.createOrGetDirectory(DIRECTORY_KEY_VALUE_BACKUP)
currentSetDir?.createOrGetDirectory(DIRECTORY_KEY_VALUE_BACKUP)
} catch (e: IOException) {
Log.e(TAG, "Error creating K/V backup dir.", e)
null
}
}
fun getSetDir(token: Long = DEFAULT_RESTORE_SET_TOKEN): DocumentFile? {
if (token == DEFAULT_RESTORE_SET_TOKEN) return defaultSetDir
return deviceDir?.findFile(token.toString())
fun getSetDir(token: Long = currentToken): DocumentFile? {
if (token == currentToken) return currentSetDir
return rootBackupDir?.findFile(token.toString())
}
fun getKVBackupDir(token: Long = DEFAULT_RESTORE_SET_TOKEN): DocumentFile? {
if (token == DEFAULT_RESTORE_SET_TOKEN) return defaultKvBackupDir ?: throw IOException()
fun getKVBackupDir(token: Long = currentToken): DocumentFile? {
if (token == currentToken) return currentKvBackupDir ?: throw IOException()
return getSetDir(token)?.findFile(DIRECTORY_KEY_VALUE_BACKUP)
}
@Throws(IOException::class)
fun getOrCreateKVBackupDir(token: Long = DEFAULT_RESTORE_SET_TOKEN): DocumentFile {
if (token == DEFAULT_RESTORE_SET_TOKEN) return defaultKvBackupDir ?: throw IOException()
fun getOrCreateKVBackupDir(token: Long = currentToken): DocumentFile {
if (token == currentToken) return currentKvBackupDir ?: throw IOException()
val setDir = getSetDir(token) ?: throw IOException()
return setDir.createOrGetDirectory(DIRECTORY_KEY_VALUE_BACKUP)
}
fun getFullBackupDir(token: Long = DEFAULT_RESTORE_SET_TOKEN): DocumentFile? {
if (token == DEFAULT_RESTORE_SET_TOKEN) return defaultFullBackupDir ?: throw IOException()
fun getFullBackupDir(token: Long = currentToken): DocumentFile? {
if (token == currentToken) return currentFullBackupDir ?: throw IOException()
return getSetDir(token)?.findFile(DIRECTORY_FULL_BACKUP)
}
@Throws(IOException::class)
fun getInputStream(file: DocumentFile): InputStream {
return contentResolver.openInputStream(file.uri) ?: throw IOException()
return context.contentResolver.openInputStream(file.uri) ?: throw IOException()
}
@Throws(IOException::class)
fun getOutputStream(file: DocumentFile): OutputStream {
return contentResolver.openOutputStream(file.uri) ?: throw IOException()
return context.contentResolver.openOutputStream(file.uri) ?: throw IOException()
}
}

View file

@ -5,12 +5,14 @@ import android.app.backup.BackupTransport.TRANSPORT_OK
import android.app.backup.RestoreDescription
import android.app.backup.RestoreDescription.*
import android.app.backup.RestoreSet
import android.content.Context
import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import android.util.Log
import com.stevesoltys.backup.header.UnsupportedVersionException
import com.stevesoltys.backup.metadata.FormatException
import com.stevesoltys.backup.metadata.MetadataReader
import com.stevesoltys.backup.settings.getBackupToken
import libcore.io.IoUtils.closeQuietly
import java.io.IOException
@ -21,6 +23,7 @@ private class RestoreCoordinatorState(
private val TAG = RestoreCoordinator::class.java.simpleName
internal class RestoreCoordinator(
private val context: Context,
private val plugin: RestorePlugin,
private val kv: KVRestore,
private val full: FullRestore,
@ -64,8 +67,15 @@ internal class RestoreCoordinator(
return restoreSets.toTypedArray()
}
/**
* Get the identifying token of the backup set currently being stored from this device.
* This is used in the case of applications wishing to restore their last-known-good data.
*
* @return A token that can be used for restore,
* or 0 if there is no backup set available corresponding to the current device state.
*/
fun getCurrentRestoreSet(): Long {
return plugin.getCurrentRestoreSet()
return getBackupToken(context)
.apply { Log.i(TAG, "Got current restore set token: $this") }
}

View file

@ -16,13 +16,4 @@ interface RestorePlugin {
**/
fun getAvailableBackups(): Sequence<EncryptedBackupMetadata>?
/**
* Get the identifying token of the backup set currently being stored from this device.
* This is used in the case of applications wishing to restore their last-known-good data.
*
* @return A token that can be used for restore,
* or 0 if there is no backup set available corresponding to the current device state.
*/
fun getCurrentRestoreSet(): Long
}

View file

@ -3,9 +3,9 @@ package com.stevesoltys.backup.transport.restore.plugins
import android.util.Log
import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.backup.metadata.EncryptedBackupMetadata
import com.stevesoltys.backup.transport.DEFAULT_RESTORE_SET_TOKEN
import com.stevesoltys.backup.transport.backup.plugins.DocumentsStorage
import com.stevesoltys.backup.transport.backup.plugins.FILE_BACKUP_METADATA
import com.stevesoltys.backup.transport.backup.plugins.FILE_NO_MEDIA
import com.stevesoltys.backup.transport.restore.FullRestorePlugin
import com.stevesoltys.backup.transport.restore.KVRestorePlugin
import com.stevesoltys.backup.transport.restore.RestorePlugin
@ -25,30 +25,41 @@ class DocumentsProviderRestorePlugin(private val storage: DocumentsStorage) : Re
override fun getAvailableBackups(): Sequence<EncryptedBackupMetadata>? {
val rootDir = storage.rootBackupDir ?: return null
val files = ArrayList<DocumentFile>()
for (file in rootDir.listFiles()) {
file.isDirectory || continue
val set = file.findFile(DEFAULT_RESTORE_SET_TOKEN.toString()) ?: continue
val metadata = set.findFile(FILE_BACKUP_METADATA) ?: continue
files.add(metadata)
val files = ArrayList<Pair<Long, DocumentFile>>()
for (set in rootDir.listFiles()) {
if (!set.isDirectory || set.name == null) {
if (set.name != FILE_NO_MEDIA) {
Log.w(TAG, "Found invalid backup set folder: ${set.name}")
}
continue
}
val token = try {
set.name!!.toLong()
} catch (e: NumberFormatException) {
Log.w(TAG, "Found invalid backup set folder: ${set.name}", e)
continue
}
val metadata = set.findFile(FILE_BACKUP_METADATA)
if (metadata == null) {
Log.w(TAG, "Missing metadata file in backup set folder: ${set.name}")
} else {
files.add(Pair(token, metadata))
}
}
val iterator = files.iterator()
return generateSequence {
if (!iterator.hasNext()) return@generateSequence null // end sequence
val metadata = iterator.next()
val token = metadata.parentFile!!.name!!.toLong()
val pair = iterator.next()
val token = pair.first
val metadata = pair.second
try {
val stream = storage.getInputStream(metadata)
EncryptedBackupMetadata(token, stream)
} catch (e: IOException) {
Log.e(TAG, "Error getting InputStream for backup metadata.", e)
EncryptedBackupMetadata(token, true)
EncryptedBackupMetadata(token)
}
}
}
override fun getCurrentRestoreSet(): Long {
return DEFAULT_RESTORE_SET_TOKEN
}
}

View file

@ -1,6 +1,8 @@
package com.stevesoltys.backup.ui
import android.app.Application
import android.app.backup.BackupProgress
import android.app.backup.IBackupObserver
import android.content.Intent
import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
import android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION
@ -13,6 +15,7 @@ import com.stevesoltys.backup.isOnExternalStorage
import com.stevesoltys.backup.settings.getBackupFolderUri
import com.stevesoltys.backup.settings.setBackupFolderUri
import com.stevesoltys.backup.transport.ConfigurableBackupTransportService
import com.stevesoltys.backup.transport.TRANSPORT_ID
private val TAG = BackupViewModel::class.java.simpleName
@ -55,10 +58,11 @@ abstract class BackupViewModel(protected val app: Application) : AndroidViewMode
// stop backup service to be sure the old location will get updated
app.stopService(Intent(app, ConfigurableBackupTransportService::class.java))
// notify the UI that the location has been set
locationWasSet.setEvent(LocationResult(true, initialSetUp))
Log.d(TAG, "New storage location chosen: $folderUri")
// initialize the new location
// TODO don't do this when restoring
Backup.backupManager.initializeTransports(arrayOf(TRANSPORT_ID), InitializationObserver(initialSetUp))
} else {
Log.w(TAG, "Location was rejected: $folderUri")
@ -71,6 +75,27 @@ abstract class BackupViewModel(protected val app: Application) : AndroidViewMode
return true
}
private inner class InitializationObserver(private val initialSetUp: Boolean) : IBackupObserver.Stub() {
override fun onUpdate(currentBackupPackage: String, backupProgress: BackupProgress) {
// noop
}
override fun onResult(target: String, status: Int) {
// noop
}
override fun backupFinished(status: Int) {
if (Log.isLoggable(TAG, Log.INFO)) {
Log.i(TAG, "Initialization finished. Status: $status")
}
if (status == 0) {
// notify the UI that the location has been set
locationWasSet.postEvent(LocationResult(true, initialSetUp))
} else {
// notify the UI that the location was invalid
locationWasSet.postEvent(LocationResult(false, initialSetUp))
}
}
}
}
class LocationResult(val validLocation: Boolean, val initialSetup: Boolean)

View file

@ -43,18 +43,18 @@ internal class CoordinatorIntegrationTest : TransportTest() {
private val fullBackupPlugin = mockk<FullBackupPlugin>()
private val fullBackup = FullBackup(fullBackupPlugin, inputFactory, headerWriter, cryptoImpl)
private val notificationManager = mockk<BackupNotificationManager>()
private val backup = BackupCoordinator(backupPlugin, kvBackup, fullBackup, metadataWriter, notificationManager)
private val backup = BackupCoordinator(context, backupPlugin, kvBackup, fullBackup, metadataWriter, notificationManager)
private val restorePlugin = mockk<RestorePlugin>()
private val kvRestorePlugin = mockk<KVRestorePlugin>()
private val kvRestore = KVRestore(kvRestorePlugin, outputFactory, headerReader, cryptoImpl)
private val fullRestorePlugin = mockk<FullRestorePlugin>()
private val fullRestore = FullRestore(fullRestorePlugin, outputFactory, headerReader, cryptoImpl)
private val restore = RestoreCoordinator(restorePlugin, kvRestore, fullRestore, metadataReader)
private val restore = RestoreCoordinator(context, restorePlugin, kvRestore, fullRestore, metadataReader)
private val backupDataInput = mockk<BackupDataInput>()
private val fileDescriptor = mockk<ParcelFileDescriptor>(relaxed = true)
private val token = DEFAULT_RESTORE_SET_TOKEN
private val token = Random.nextLong()
private val appData = ByteArray(42).apply { Random.nextBytes(this) }
private val appData2 = ByteArray(1337).apply { Random.nextBytes(this) }
private val key = "RestoreKey"

View file

@ -1,5 +1,6 @@
package com.stevesoltys.backup.transport
import android.content.Context
import android.content.pm.PackageInfo
import android.util.Log
import com.stevesoltys.backup.crypto.Crypto
@ -13,6 +14,7 @@ import org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD
abstract class TransportTest {
protected val crypto = mockk<Crypto>()
protected val context = mockk<Context>(relaxed = true)
protected val packageInfo = PackageInfo().apply { packageName = "org.example" }

View file

@ -23,14 +23,14 @@ internal class BackupCoordinatorTest: BackupTest() {
private val metadataWriter = mockk<MetadataWriter>()
private val notificationManager = mockk<BackupNotificationManager>()
private val backup = BackupCoordinator(plugin, kv, full, metadataWriter, notificationManager)
private val backup = BackupCoordinator(context, plugin, kv, full, metadataWriter, notificationManager)
private val metadataOutputStream = mockk<OutputStream>()
@Test
fun `device initialization succeeds and delegates to plugin`() {
every { plugin.initializeDevice() } just Runs
expectWritingMetadata()
expectWritingMetadata(0L)
every { kv.hasState() } returns false
every { full.hasState() } returns false
@ -116,9 +116,9 @@ internal class BackupCoordinatorTest: BackupTest() {
assertEquals(result, backup.finishBackup())
}
private fun expectWritingMetadata() {
private fun expectWritingMetadata(token: Long = this.token) {
every { plugin.getMetadataOutputStream() } returns metadataOutputStream
every { metadataWriter.write(metadataOutputStream) } just Runs
every { metadataWriter.write(metadataOutputStream, token) } just Runs
}
}

View file

@ -1,11 +1,12 @@
package com.stevesoltys.backup.transport.backup
import android.os.ParcelFileDescriptor
import com.stevesoltys.backup.transport.TransportTest
import com.stevesoltys.backup.header.HeaderWriter
import com.stevesoltys.backup.header.VersionHeader
import com.stevesoltys.backup.transport.TransportTest
import io.mockk.mockk
import java.io.OutputStream
import kotlin.random.Random
internal abstract class BackupTest : TransportTest() {
@ -14,6 +15,7 @@ internal abstract class BackupTest : TransportTest() {
protected val data = mockk<ParcelFileDescriptor>()
protected val outputStream = mockk<OutputStream>()
protected val token = Random.nextLong()
protected val header = VersionHeader(packageName = packageInfo.packageName)
protected val quota = 42L

View file

@ -27,7 +27,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
private val full = mockk<FullRestore>()
private val metadataReader = mockk<MetadataReader>()
private val restore = RestoreCoordinator(plugin, kv, full, metadataReader)
private val restore = RestoreCoordinator(context, plugin, kv, full, metadataReader)
private val token = Random.nextLong()
private val inputStream = mockk<InputStream>()
@ -56,11 +56,8 @@ internal class RestoreCoordinatorTest : TransportTest() {
@Test
fun `getCurrentRestoreSet() delegates to plugin`() {
val currentRestoreSet = Random.nextLong()
every { plugin.getCurrentRestoreSet() } returns currentRestoreSet
assertEquals(currentRestoreSet, restore.getCurrentRestoreSet())
// We don't mock the SettingsManager, so the default value is returned here
assertEquals(0L, restore.getCurrentRestoreSet())
}
@Test