Skip to content

Commit

Permalink
Implement new Backend.save() method that can be re-tried
Browse files Browse the repository at this point in the history
  • Loading branch information
grote committed Jan 3, 2025
1 parent 73da56a commit bd58d57
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ 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

public suspend fun list(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import org.calyxos.seedvault.core.backends.webdav.WebDavBackend
import org.calyxos.seedvault.core.backends.webdav.WebDavConfig

public class BackendFactory {
// TODO: implement a backend wrapper that handles retries for transient errors

public fun createSafBackend(context: Context, config: SafProperties): Backend =
SafBackend(context, config)

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

package org.calyxos.seedvault.core.backends

import java.io.OutputStream

/**
* Used to save data with [Backend]s.
*/
public interface BackendSaver {
/**
* The number of bytes that will be saved or `null` if unknown.
*/
public val size: Long

/**
* The SHA256 hash (in lower-case hex string representation) the bytes to be saved have,
* or `null` if it isn't known.
*/
public val sha256: String?

/**
* Called by the backend when it wants to save the data to the provided [outputStream].
* Can be called more than once, in case the backend encountered an error saving.
*
* @return the number of bytes saved. Should be equal to [size].
*/
public fun save(outputStream: OutputStream): Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package org.calyxos.seedvault.core.backends

import androidx.annotation.VisibleForTesting
import org.calyxos.seedvault.core.toHexString
import java.io.OutputStream
import kotlin.random.Random
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
Expand All @@ -15,6 +16,7 @@ import kotlin.test.assertNotNull
import kotlin.test.assertTrue

@VisibleForTesting
@Suppress("BlockingMethodInNonBlockingContext")
public abstract class BackendTest {

public abstract val backend: Backend
Expand All @@ -26,13 +28,8 @@ public abstract class BackendTest {
val now = System.currentTimeMillis()
val bytes1 = Random.nextBytes(1337)
val bytes2 = Random.nextBytes(1337 * 8)
backend.save(LegacyAppBackupFile.Metadata(now)).use {
it.write(bytes1)
}

backend.save(FileBackupFileType.Snapshot(androidId, now)).use {
it.write(bytes2)
}
backend.save(LegacyAppBackupFile.Metadata(now), getSaver(bytes1))
backend.save(FileBackupFileType.Snapshot(androidId, now), getSaver(bytes2))

var metadata: LegacyAppBackupFile.Metadata? = null
var fileSnapshot: FileBackupFileType.Snapshot? = null
Expand All @@ -58,9 +55,7 @@ public abstract class BackendTest {
val blobName = Random.nextBytes(32).toHexString()
var blob: FileBackupFileType.Blob? = null
val bytes3 = Random.nextBytes(1337 * 16)
backend.save(FileBackupFileType.Blob(androidId, blobName)).use {
it.write(bytes3)
}
backend.save(FileBackupFileType.Blob(androidId, blobName), getSaver(bytes3))
backend.list(
null,
FileBackupFileType.Snapshot::class,
Expand Down Expand Up @@ -91,9 +86,7 @@ public abstract class BackendTest {

val bytes4 = Random.nextBytes(1337)
val bytes5 = Random.nextBytes(1337 * 8)
backend.save(AppBackupFileType.Snapshot(repoId, snapshotId)).use {
it.write(bytes4)
}
backend.save(AppBackupFileType.Snapshot(repoId, snapshotId), getSaver(bytes4))

var appSnapshot: AppBackupFileType.Snapshot? = null
backend.list(
Expand All @@ -108,9 +101,7 @@ public abstract class BackendTest {
assertNotNull(appSnapshot)
assertContentEquals(bytes4, backend.load(appSnapshot as FileHandle).readAllBytes())

backend.save(AppBackupFileType.Blob(repoId, blobId)).use {
it.write(bytes5)
}
backend.save(AppBackupFileType.Blob(repoId, blobId), getSaver(bytes5))

var blobHandle: AppBackupFileType.Blob? = null
backend.list(
Expand Down Expand Up @@ -151,9 +142,7 @@ public abstract class BackendTest {

backend.remove(blob)
try {
backend.save(blob).use {
it.write(bytes)
}
backend.save(blob, getSaver(bytes))
assertContentEquals(bytes, backend.load(blob as FileHandle).readAllBytes())
} finally {
backend.remove(blob)
Expand All @@ -170,13 +159,21 @@ public abstract class BackendTest {
val blob = AppBackupFileType.Blob(repoId, Random.nextBytes(32).toHexString())
val bytes = Random.nextBytes(2342)
try {
backend.save(blob).use {
it.write(bytes)
}
backend.save(blob, getSaver(bytes))
assertContentEquals(bytes, backend.load(blob as FileHandle).readAllBytes())
} finally {
backend.remove(blob)
}
}

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 @@ -18,6 +18,7 @@ import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging
import org.calyxos.seedvault.core.backends.AppBackupFileType
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.BackendSaver
import org.calyxos.seedvault.core.backends.Constants.DIRECTORY_ROOT
import org.calyxos.seedvault.core.backends.Constants.FILE_BACKUP_METADATA
import org.calyxos.seedvault.core.backends.Constants.appSnapshotRegex
Expand Down Expand Up @@ -86,12 +87,19 @@ 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)
return file.getOutputStream(context.contentResolver).use { saver.save(it) }
}

override suspend fun load(handle: FileHandle): InputStream {
log.debugLog { "load($handle)" }
val file = cache.getOrCreateFile(handle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import okhttp3.RequestBody
import okio.BufferedSink
import org.calyxos.seedvault.core.backends.AppBackupFileType
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.BackendSaver
import org.calyxos.seedvault.core.backends.Constants.DIRECTORY_ROOT
import org.calyxos.seedvault.core.backends.Constants.FILE_BACKUP_METADATA
import org.calyxos.seedvault.core.backends.Constants.appSnapshotRegex
Expand Down Expand Up @@ -121,6 +122,7 @@ 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)
Expand Down Expand Up @@ -153,6 +155,27 @@ public class WebDavBackend(
return pipedOutputStream
}

override suspend fun save(handle: FileHandle, saver: BackendSaver): Long {
val location = handle.toHttpUrl()
val davCollection = DavCollection(okHttpClient, location)
davCollection.ensureFoldersExist(log, folders)

val body = object : RequestBody() {
override fun isOneShot(): Boolean = true
override fun contentType() = "application/octet-stream".toMediaType()
override fun contentLength(): Long = saver.size
override fun writeTo(sink: BufferedSink) {
saver.save(sink.outputStream())
}
}
return suspendCoroutine { cont ->
davCollection.put(body) { response ->
log.debugLog { "save($location) = $response" }
cont.resume(saver.size)
}
}
}

override suspend fun load(handle: FileHandle): InputStream {
val location = handle.toHttpUrl()
val davCollection = DavCollection(okHttpClient, location)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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.BackendSaver
import org.calyxos.seedvault.core.backends.FileHandle
import org.calyxos.seedvault.core.backends.FileInfo
import org.calyxos.seedvault.core.backends.TopLevelFolder
Expand Down Expand Up @@ -47,6 +48,11 @@ class TestSafBackend(
return delegate.save(handle)
}

override suspend fun save(handle: FileHandle, saver: BackendSaver): Long {
if (getLocationUri() == null) return 0
return delegate.save(handle, saver)
}

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

0 comments on commit bd58d57

Please sign in to comment.