Skip to content

Commit

Permalink
Remove old Backend.save() method and hacky WebDAV pipe code with it
Browse files Browse the repository at this point in the history
  • Loading branch information
grote committed Jan 7, 2025
1 parent 255f1db commit cac71a5
Show file tree
Hide file tree
Showing 7 changed files with 31 additions and 151 deletions.
63 changes: 29 additions & 34 deletions app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ import com.stevesoltys.seedvault.backend.LegacyStoragePlugin
import com.stevesoltys.seedvault.backend.saf.DocumentsProviderLegacyPlugin
import com.stevesoltys.seedvault.backend.saf.DocumentsStorage
import com.stevesoltys.seedvault.settings.SettingsManager
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.calyxos.seedvault.core.backends.BackendSaver
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
import org.calyxos.seedvault.core.backends.saf.SafBackend
import org.junit.After
Expand All @@ -28,20 +27,23 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.io.OutputStream

private const val root = ".SeedvaultPluginTest"

@RunWith(AndroidJUnit4::class)
@MediumTest
class PluginTest : KoinComponent {

private val context = InstrumentationRegistry.getInstrumentation().targetContext
private val settingsManager: SettingsManager by inject()
private val mockedSettingsManager: SettingsManager = mockk()
private val storage = DocumentsStorage(
appContext = context,
safStorage = settingsManager.getSafProperties() ?: error("No SAF storage"),
root = root,
)

private val backend = SafBackend(context, storage.safStorage)
private val backend = SafBackend(context, storage.safStorage, root)

@Suppress("Deprecation")
private val legacyStoragePlugin: LegacyStoragePlugin = DocumentsProviderLegacyPlugin(context) {
Expand All @@ -54,9 +56,6 @@ class PluginTest : KoinComponent {

@Before
fun setup() = runBlocking {
every {
mockedSettingsManager.getSafProperties()
} returns settingsManager.getSafProperties()
backend.removeAll()
}

Expand Down Expand Up @@ -93,34 +92,26 @@ class PluginTest : KoinComponent {
// no backups available initially
assertEquals(0, backend.getAvailableBackupFileHandles().toList().size)

// prepare returned tokens requested when initializing device
every { mockedSettingsManager.token } returnsMany listOf(token, token + 1, token + 1)

// write metadata (needed for backup to be recognized)
backend.save(LegacyAppBackupFile.Metadata(token))
.writeAndClose(getRandomByteArray())
backend.save(LegacyAppBackupFile.Metadata(token), getSaver(getRandomByteArray()))

// one backup available now
assertEquals(1, backend.getAvailableBackupFileHandles().toList().size)

// initializing again (with another restore set) does add a restore set
backend.save(LegacyAppBackupFile.Metadata(token + 1))
.writeAndClose(getRandomByteArray())
backend.save(LegacyAppBackupFile.Metadata(token + 1), getSaver(getRandomByteArray()))
assertEquals(2, backend.getAvailableBackupFileHandles().toList().size)

// initializing again (without new restore set) doesn't change number of restore sets
backend.save(LegacyAppBackupFile.Metadata(token + 1))
.writeAndClose(getRandomByteArray())
backend.save(LegacyAppBackupFile.Metadata(token + 1), getSaver(getRandomByteArray()))
assertEquals(2, backend.getAvailableBackupFileHandles().toList().size)
}

@Test
fun testMetadataWriteRead() = runBlocking(Dispatchers.IO) {
every { mockedSettingsManager.token } returns token

// write metadata
val metadata = getRandomByteArray()
backend.save(LegacyAppBackupFile.Metadata(token)).writeAndClose(metadata)
backend.save(LegacyAppBackupFile.Metadata(token), getSaver(metadata))

// get available backups, expect only one with our token and no error
var availableBackups = backend.getAvailableBackupFileHandles().toList()
Expand All @@ -132,7 +123,7 @@ class PluginTest : KoinComponent {
assertReadEquals(metadata, backend.load(backupHandle))

// initializing again (without changing storage) keeps restore set with same token
backend.save(LegacyAppBackupFile.Metadata(token)).writeAndClose(metadata)
backend.save(LegacyAppBackupFile.Metadata(token), getSaver(metadata))
availableBackups = backend.getAvailableBackupFileHandles().toList()
assertEquals(1, availableBackups.size)
backupHandle = availableBackups[0] as LegacyAppBackupFile.Metadata
Expand All @@ -144,13 +135,12 @@ class PluginTest : KoinComponent {

@Test
fun v0testApkWriteRead() = runBlocking {
// initialize storage with given token
initStorage(token)

// write random bytes as APK
val apk1 = getRandomByteArray(1337 * 1024)
backend.save(LegacyAppBackupFile.Blob(token, "${packageInfo.packageName}.apk"))
.writeAndClose(apk1)
backend.save(
LegacyAppBackupFile.Blob(token, "${packageInfo.packageName}.apk"),
getSaver(apk1)
)

// assert that read APK bytes match what was written
assertReadEquals(
Expand All @@ -162,8 +152,10 @@ class PluginTest : KoinComponent {
val suffix2 = getRandomBase64(23)
val apk2 = getRandomByteArray(23 * 1024 * 1024)

backend.save(LegacyAppBackupFile.Blob(token, "${packageInfo2.packageName}$suffix2.apk"))
.writeAndClose(apk2)
backend.save(
LegacyAppBackupFile.Blob(token, "${packageInfo2.packageName}$suffix2.apk"),
getSaver(apk2)
)

// assert that read APK bytes match what was written
assertReadEquals(
Expand All @@ -174,22 +166,19 @@ class PluginTest : KoinComponent {

@Test
fun testBackupRestore() = runBlocking {
// initialize storage with given token
initStorage(token)

val name1 = getRandomBase64()
val name2 = getRandomBase64()

// write full backup data
val data = getRandomByteArray(5 * 1024 * 1024)
backend.save(LegacyAppBackupFile.Blob(token, name1)).writeAndClose(data)
backend.save(LegacyAppBackupFile.Blob(token, name1), getSaver(data))

// restore data matches backed up data
assertReadEquals(data, backend.load(LegacyAppBackupFile.Blob(token, name1)))

// write and check data for second package
val data2 = getRandomByteArray(5 * 1024 * 1024)
backend.save(LegacyAppBackupFile.Blob(token, name2)).writeAndClose(data2)
backend.save(LegacyAppBackupFile.Blob(token, name2), getSaver(data2))
assertReadEquals(data2, backend.load(LegacyAppBackupFile.Blob(token, name2)))

// remove data of first package again and ensure that no more data is found
Expand All @@ -199,8 +188,14 @@ class PluginTest : KoinComponent {
backend.remove(LegacyAppBackupFile.Blob(token, name2))
}

private fun initStorage(token: Long) = runBlocking {
every { mockedSettingsManager.token } returns token
private fun getSaver(bytes: ByteArray) = object : BackendSaver {
override val size: Long = bytes.size.toLong()
override val sha256: String? = null

override fun save(outputStream: OutputStream): Long {
outputStream.write(bytes)
return size
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ private val TAG = DocumentsStorage::class.java.simpleName
internal class DocumentsStorage(
private val appContext: Context,
internal val safStorage: SafProperties,
private val root: String = DIRECTORY_ROOT,
) {

/**
Expand All @@ -55,7 +56,7 @@ internal class DocumentsStorage(
if (field == null) {
val parent = safStorage.getDocumentFile(context)
field = try {
parent.createOrGetDirectory(context, DIRECTORY_ROOT)
parent.createOrGetDirectory(context, root)
} catch (e: IOException) {
Log.e(TAG, "Error creating root backup dir.", e)
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package org.calyxos.seedvault.core.backends

import androidx.annotation.VisibleForTesting
import java.io.InputStream
import java.io.OutputStream
import kotlin.reflect.KClass

public interface Backend {
Expand All @@ -25,9 +24,6 @@ public interface Backend {
*/
public suspend fun getFreeSpace(): Long?

@Deprecated(message = "use save(FileHandle, BackendSaver) instead")
public suspend fun save(handle: FileHandle): OutputStream

public suspend fun save(handle: FileHandle, saver: BackendSaver): Long

public suspend fun load(handle: FileHandle): InputStream
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
import org.calyxos.seedvault.core.backends.TopLevelFolder
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import kotlin.reflect.KClass

internal const val AUTHORITY_STORAGE = "com.android.externalstorage.documents"
Expand Down Expand Up @@ -87,13 +86,6 @@ public class SafBackend(
} else bytesAvailable
}

@Deprecated("use save(FileHandle, BackendSaver) instead")
override suspend fun save(handle: FileHandle): OutputStream {
log.debugLog { "save($handle)" }
val file = cache.getOrCreateFile(handle)
return file.getOutputStream(context.contentResolver)
}

override suspend fun save(handle: FileHandle, saver: BackendSaver): Long {
log.debugLog { "save($handle)" }
val file = cache.getOrCreateFile(handle)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ import at.bitfire.dav4jvm.exception.NotFoundException
import at.bitfire.dav4jvm.property.webdav.QuotaAvailableBytes
import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import okhttp3.ConnectionSpec
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
Expand Down Expand Up @@ -45,16 +40,13 @@ import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
import org.calyxos.seedvault.core.backends.TopLevelFolder
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.io.PipedInputStream
import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlin.reflect.KClass

private const val DEBUG_LOG = true

@OptIn(DelicateCoroutinesApi::class)
public class WebDavBackend(
webDavConfig: WebDavConfig,
root: String = DIRECTORY_ROOT,
Expand Down Expand Up @@ -122,39 +114,6 @@ public class WebDavBackend(
return availableBytes
}

@Deprecated("use save(FileHandle, BackendSaver) instead")
override suspend fun save(handle: FileHandle): OutputStream {
val location = handle.toHttpUrl()
val davCollection = DavCollection(okHttpClient, location)
davCollection.ensureFoldersExist(log, folders)

val pipedInputStream = PipedInputStream()
val pipedOutputStream = PipedCloseActionOutputStream(pipedInputStream)

val body = object : RequestBody() {
override fun isOneShot(): Boolean = true
override fun contentType() = "application/octet-stream".toMediaType()
override fun writeTo(sink: BufferedSink) {
pipedInputStream.use { inputStream ->
sink.outputStream().use { outputStream ->
inputStream.copyTo(outputStream)
}
}
}
}
val deferred = GlobalScope.async(Dispatchers.IO) {
davCollection.put(body) { response ->
log.debugLog { "save($location) = $response" }
}
}
pipedOutputStream.doOnClose {
runBlocking { // blocking i/o wait
deferred.await()
}
}
return pipedOutputStream
}

override suspend fun save(handle: FileHandle, saver: BackendSaver): Long {
val location = handle.toHttpUrl()
val davCollection = DavCollection(okHttpClient, location)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,6 @@ class TestSafBackend(

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 save(handle: FileHandle, saver: BackendSaver): Long {
if (getLocationUri() == null) return 0
return delegate.save(handle, saver)
Expand Down

0 comments on commit cac71a5

Please sign in to comment.