Skip to content

Commit

Permalink
Use new Backend directly in storage lib
Browse files Browse the repository at this point in the history
  • Loading branch information
grote committed Aug 27, 2024
1 parent 77580d1 commit ca02cea
Show file tree
Hide file tree
Showing 24 changed files with 446 additions and 406 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ import org.calyxos.backup.storage.api.StorageBackup
import org.koin.dsl.module

val storageModule = module {
single { StorageBackup(get(), { get<StoragePluginManager>().filesPlugin }, get<KeyManager>()) }
single { StorageBackup(get(), { get<StoragePluginManager>().backend }, get<KeyManager>()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import android.os.StrictMode
import android.os.StrictMode.VmPolicy
import android.util.Log
import de.grobox.storagebackuptester.crypto.KeyManager
import de.grobox.storagebackuptester.plugin.TestSafStoragePlugin
import de.grobox.storagebackuptester.plugin.TestSafBackend
import de.grobox.storagebackuptester.settings.SettingsManager
import org.calyxos.backup.storage.api.StorageBackup
import org.calyxos.backup.storage.ui.restore.FileSelectionManager
Expand All @@ -19,7 +19,7 @@ class App : Application() {

val settingsManager: SettingsManager by lazy { SettingsManager(applicationContext) }
val storageBackup: StorageBackup by lazy {
val plugin = TestSafStoragePlugin(this) { settingsManager.getBackupLocation() }
val plugin = TestSafBackend(this) { settingsManager.getBackupLocation() }
StorageBackup(this, { plugin }, KeyManager)
}
val fileSelectionManager: FileSelectionManager get() = FileSelectionManager()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* SPDX-FileCopyrightText: 2021 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/

package de.grobox.storagebackuptester.plugin

import android.content.Context
import android.net.Uri
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.FileHandle
import org.calyxos.seedvault.core.backends.FileInfo
import org.calyxos.seedvault.core.backends.TopLevelFolder
import org.calyxos.seedvault.core.backends.saf.SafBackend
import org.calyxos.seedvault.core.backends.saf.SafConfig
import java.io.InputStream
import java.io.OutputStream
import kotlin.reflect.KClass

class TestSafBackend(
private val appContext: Context,
private val getLocationUri: () -> Uri?,
) : Backend {

private val safConfig
get() = SafConfig(
config = getLocationUri() ?: error("no uri"),
name = "foo",
isUsb = false,
requiresNetwork = false,
rootId = "bar",
)
private val delegate: SafBackend get() = SafBackend(appContext, safConfig)

private val nullStream = object : OutputStream() {
override fun write(b: Int) {
// oops
}
}

override suspend fun test(): Boolean = delegate.test()

override suspend fun getFreeSpace(): Long? = delegate.getFreeSpace()

override suspend fun save(handle: FileHandle): OutputStream {
if (getLocationUri() == null) return nullStream
return delegate.save(handle)
}

override suspend fun load(handle: FileHandle): InputStream {
return delegate.load(handle)
}

override suspend fun list(
topLevelFolder: TopLevelFolder?,
vararg fileTypes: KClass<out FileHandle>,
callback: (FileInfo) -> Unit,
) = delegate.list(topLevelFolder, *fileTypes, callback = callback)

override suspend fun remove(handle: FileHandle) = delegate.remove(handle)

override suspend fun rename(from: TopLevelFolder, to: TopLevelFolder) {
delegate.rename(from, to)
}

override suspend fun removeAll() = delegate.removeAll()

override val providerPackageName: String? get() = delegate.providerPackageName

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* SPDX-FileCopyrightText: 2024 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/

package org.calyxos.backup.storage

import com.google.protobuf.InvalidProtocolBufferException
import org.calyxos.backup.storage.api.StoredSnapshot
import org.calyxos.backup.storage.backup.BackupSnapshot
import org.calyxos.backup.storage.crypto.StreamCrypto
import org.calyxos.backup.storage.restore.readVersion
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.FileBackupFileType
import org.calyxos.seedvault.core.backends.TopLevelFolder
import java.io.IOException
import java.security.GeneralSecurityException

internal class SnapshotRetriever(
private val storagePlugin: () -> Backend,
private val streamCrypto: StreamCrypto = StreamCrypto,
) {

@Throws(
IOException::class,
GeneralSecurityException::class,
InvalidProtocolBufferException::class,
)
suspend fun getSnapshot(streamKey: ByteArray, storedSnapshot: StoredSnapshot): BackupSnapshot {
return storagePlugin().load(storedSnapshot.snapshotHandle).use { inputStream ->
val version = inputStream.readVersion()
val timestamp = storedSnapshot.timestamp
val ad = streamCrypto.getAssociatedDataForSnapshot(timestamp, version.toByte())
streamCrypto.newDecryptingStream(streamKey, inputStream, ad).use { decryptedStream ->
BackupSnapshot.parseFrom(decryptedStream)
}
}
}

}

@Throws(IOException::class)
internal suspend fun Backend.getCurrentBackupSnapshots(androidId: String): List<StoredSnapshot> {
val topLevelFolder = TopLevelFolder("$androidId.sv")
val snapshots = ArrayList<StoredSnapshot>()
list(topLevelFolder, FileBackupFileType.Snapshot::class) { fileInfo ->
val handle = fileInfo.fileHandle as FileBackupFileType.Snapshot
val folderName = handle.topLevelFolder.name
val timestamp = handle.time
val storedSnapshot = StoredSnapshot(folderName, timestamp)
snapshots.add(storedSnapshot)
}
return snapshots
}

@Throws(IOException::class)
internal suspend fun Backend.getBackupSnapshotsForRestore(): List<StoredSnapshot> {
val snapshots = ArrayList<StoredSnapshot>()
list(null, FileBackupFileType.Snapshot::class) { fileInfo ->
val handle = fileInfo.fileHandle as FileBackupFileType.Snapshot
val folderName = handle.topLevelFolder.name
val timestamp = handle.time
val storedSnapshot = StoredSnapshot(folderName, timestamp)
snapshots.add(storedSnapshot)
}
return snapshots
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
package org.calyxos.backup.storage.api

import org.calyxos.backup.storage.backup.BackupSnapshot
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.FileBackupFileType

public data class SnapshotItem(
public val storedSnapshot: StoredSnapshot,
Expand All @@ -21,7 +23,7 @@ public sealed class SnapshotResult {

public data class StoredSnapshot(
/**
* The unique ID of the current device/user combination chosen by the [StoragePlugin].
* The unique ID of the current device/user combination chosen by the [Backend].
* It may include an '.sv' extension.
*/
public val userId: String,
Expand All @@ -31,6 +33,11 @@ public data class StoredSnapshot(
public val timestamp: Long,
) {
public val androidId: String = userId.substringBefore(".sv")
public val snapshotHandle: FileBackupFileType.Snapshot
get() = FileBackupFileType.Snapshot(
androidId = androidId,
time = timestamp,
)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,28 @@

package org.calyxos.backup.storage.api

import android.annotation.SuppressLint
import android.content.Context
import android.net.Uri
import android.provider.DocumentsContract.isTreeUri
import android.provider.MediaStore
import android.provider.Settings
import android.provider.Settings.Secure.ANDROID_ID
import android.util.Log
import androidx.annotation.WorkerThread
import androidx.room.Room
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
import org.calyxos.backup.storage.SnapshotRetriever
import org.calyxos.backup.storage.backup.Backup
import org.calyxos.backup.storage.backup.BackupSnapshot
import org.calyxos.backup.storage.backup.ChunksCacheRepopulater
import org.calyxos.backup.storage.db.Db
import org.calyxos.backup.storage.getCurrentBackupSnapshots
import org.calyxos.backup.storage.getDocumentPath
import org.calyxos.backup.storage.getMediaType
import org.calyxos.backup.storage.plugin.SnapshotRetriever
import org.calyxos.backup.storage.prune.Pruner
import org.calyxos.backup.storage.prune.RetentionManager
import org.calyxos.backup.storage.restore.FileRestore
Expand All @@ -31,6 +35,8 @@ import org.calyxos.backup.storage.scanner.DocumentScanner
import org.calyxos.backup.storage.scanner.FileScanner
import org.calyxos.backup.storage.scanner.MediaScanner
import org.calyxos.backup.storage.toStoredUri
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.FileBackupFileType
import org.calyxos.seedvault.core.crypto.KeyManager
import java.io.IOException
import java.util.concurrent.atomic.AtomicBoolean
Expand All @@ -39,7 +45,7 @@ private const val TAG = "StorageBackup"

public class StorageBackup(
private val context: Context,
private val pluginGetter: () -> StoragePlugin,
private val pluginGetter: () -> Backend,
private val keyManager: KeyManager,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
) {
Expand All @@ -50,21 +56,37 @@ public class StorageBackup(
}
private val uriStore by lazy { db.getUriStore() }

@SuppressLint("HardwareIds")
private val androidId = Settings.Secure.getString(context.contentResolver, ANDROID_ID)

private val mediaScanner by lazy { MediaScanner(context) }
private val snapshotRetriever = SnapshotRetriever(pluginGetter)
private val chunksCacheRepopulater = ChunksCacheRepopulater(db, pluginGetter, snapshotRetriever)
private val chunksCacheRepopulater = ChunksCacheRepopulater(
db = db,
storagePlugin = pluginGetter,
androidId = androidId,
snapshotRetriever = snapshotRetriever,
)
private val backup by lazy {
val documentScanner = DocumentScanner(context)
val fileScanner = FileScanner(uriStore, mediaScanner, documentScanner)
Backup(context, db, fileScanner, pluginGetter, keyManager, chunksCacheRepopulater)
Backup(
context = context,
db = db,
fileScanner = fileScanner,
backendGetter = pluginGetter,
androidId = androidId,
keyManager = keyManager,
cacheRepopulater = chunksCacheRepopulater
)
}
private val restore by lazy {
val fileRestore = FileRestore(context, mediaScanner)
Restore(context, pluginGetter, keyManager, snapshotRetriever, fileRestore)
}
private val retention = RetentionManager(context)
private val pruner by lazy {
Pruner(db, retention, pluginGetter, keyManager, snapshotRetriever)
Pruner(db, retention, pluginGetter, androidId, keyManager, snapshotRetriever)
}

private val backupRunning = AtomicBoolean(false)
Expand Down Expand Up @@ -113,7 +135,6 @@ public class StorageBackup(
* (see [deleteAllSnapshots]) as well as clears local cache (see [clearCache]).
*/
public suspend fun init() {
pluginGetter().init()
deleteAllSnapshots()
clearCache()
}
Expand All @@ -123,13 +144,14 @@ public class StorageBackup(
* (potentially encrypted with an old key) laying around.
* Using a storage location with existing data is not supported.
* Using the same root folder for storage on different devices or user profiles is fine though
* as the [StoragePlugin] should isolate storage per [StoredSnapshot.userId].
* as the [Backend] should isolate storage per [StoredSnapshot.userId].
*/
public suspend fun deleteAllSnapshots(): Unit = withContext(dispatcher) {
try {
pluginGetter().getCurrentBackupSnapshots().forEach {
pluginGetter().getCurrentBackupSnapshots(androidId).forEach {
val handle = FileBackupFileType.Snapshot(androidId, it.timestamp)
try {
pluginGetter().deleteBackupSnapshot(it)
pluginGetter().remove(handle)
} catch (e: IOException) {
Log.e(TAG, "Error deleting snapshot $it", e)
}
Expand Down
Loading

0 comments on commit ca02cea

Please sign in to comment.