fix(sync): carry ack recovery over transports
This commit is contained in:
parent
0750a47cef
commit
41d0f1ff96
9 changed files with 67 additions and 9 deletions
|
|
@ -41,6 +41,17 @@ private const val MAX_BT_FILE_BYTES = 64L * 1024L * 1024L
|
|||
private const val MAX_BT_FILES = 128
|
||||
private const val AUTO_REQUEST_COOLDOWN_MS = 5L * 60L * 1000L
|
||||
|
||||
internal val BLUETOOTH_ANDROID_PULL_COORDINATION =
|
||||
listOf(
|
||||
"android-peer-v1.json",
|
||||
FULL_GENERATION_REQUEST_NAME,
|
||||
LEGACY_SYNC_REQUEST_NAME,
|
||||
"android-generation-v1.json",
|
||||
"android-consumption-ack-v1.json",
|
||||
"android-archive-acknowledgements-v1.json",
|
||||
"android-generation-error-v1.json",
|
||||
)
|
||||
|
||||
internal data class BondedBluetoothDesktop(
|
||||
val name: String,
|
||||
val address: String,
|
||||
|
|
@ -248,14 +259,7 @@ private class BluetoothExchangeArchive(
|
|||
val totals = longArrayOf(0L, 0L)
|
||||
try {
|
||||
ZipOutputStream(BufferedOutputStream(FileOutputStream(output))).use { zip ->
|
||||
listOf(
|
||||
"android-peer-v1.json",
|
||||
FULL_GENERATION_REQUEST_NAME,
|
||||
LEGACY_SYNC_REQUEST_NAME,
|
||||
"android-generation-v1.json",
|
||||
"android-consumption-ack-v1.json",
|
||||
"android-generation-error-v1.json",
|
||||
).forEach { name ->
|
||||
BLUETOOTH_ANDROID_PULL_COORDINATION.forEach { name ->
|
||||
addFile(zip, File(exchangeDirectory, name), name, seen, totals)
|
||||
}
|
||||
val reference = File(exchangeDirectory, "android-generation-v1.json")
|
||||
|
|
|
|||
|
|
@ -195,7 +195,8 @@ internal class DriveDocumentTransport(private val context: Context, treeUri: Uri
|
|||
localRoot.mkdirs()
|
||||
val coordination = listOf(
|
||||
"android-peer-v1.json", "android-generation-v1.json", "android-consumption-ack-v1.json",
|
||||
"android-generation-error-v1.json", "request-v1.json",
|
||||
"android-archive-acknowledgements-v1.json", "android-generation-error-v1.json",
|
||||
"request-v1.json",
|
||||
"desktop-archive-acknowledgements-v1.json", "desktop-consumption-ack-v1.json",
|
||||
"desktop-generation-v1.json",
|
||||
)
|
||||
|
|
@ -261,6 +262,13 @@ internal class DriveDocumentTransport(private val context: Context, treeUri: Uri
|
|||
upload("android-generation-v1.json", reference)
|
||||
}
|
||||
|
||||
fun pushRecoveryAcknowledgements(localRoot: File) {
|
||||
upload(
|
||||
"android-archive-acknowledgements-v1.json",
|
||||
File(localRoot, "android-archive-acknowledgements-v1.json"),
|
||||
)
|
||||
}
|
||||
|
||||
fun pushAcknowledgement(localRoot: File) {
|
||||
upload(
|
||||
"android-consumption-ack-v1.json",
|
||||
|
|
@ -318,6 +326,10 @@ class DriveSyncWorker(context: Context, parameters: WorkerParameters) :
|
|||
local,
|
||||
Duration.ofMinutes(10),
|
||||
afterPeerPublication = { transport.pushPeer(local) },
|
||||
afterRecoveryAcknowledgements = {
|
||||
stage = "recovery-acknowledgement"
|
||||
transport.pushRecoveryAcknowledgements(local)
|
||||
},
|
||||
afterPublication = {
|
||||
stage = "generation-publication"
|
||||
transport.pushGeneration(local)
|
||||
|
|
|
|||
|
|
@ -404,6 +404,7 @@ internal class SyncGenerationCoordinator(
|
|||
directory: File,
|
||||
timeout: Duration = Duration.ofMinutes(5),
|
||||
afterPeerPublication: (() -> Unit)? = null,
|
||||
afterRecoveryAcknowledgements: (() -> Unit)? = null,
|
||||
afterPublication: (() -> Unit)? = null,
|
||||
pollTransport: (() -> Unit)? = null,
|
||||
afterAcknowledgement: (() -> Unit)? = null,
|
||||
|
|
@ -509,6 +510,7 @@ internal class SyncGenerationCoordinator(
|
|||
)
|
||||
publish(recoveryPath, recoveryEnvelope)
|
||||
visibility.confirm(listOf(recoveryPath))
|
||||
afterRecoveryAcknowledgements?.invoke()
|
||||
phase(
|
||||
runId,
|
||||
"android_archive_ack_published",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package com.labfytools.trainlog.data
|
||||
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class BluetoothSyncTransportContractTest {
|
||||
@Test
|
||||
fun androidPullArchiveCarriesGenerationAckRecoveryEvidence() {
|
||||
assertTrue(
|
||||
BLUETOOTH_ANDROID_PULL_COORDINATION.contains(
|
||||
"android-archive-acknowledgements-v1.json"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -116,6 +116,8 @@ class DriveTransportTest(unittest.TestCase):
|
|||
source = Path(raw) / "source"
|
||||
source.mkdir()
|
||||
self.generation(source)
|
||||
recovery = source / "android-archive-acknowledgements-v1.json"
|
||||
recovery.write_text('{"format":"trainlog-sync-archive-acknowledgements","version":1}')
|
||||
remote = MemoryDrive()
|
||||
drive.push(remote, source)
|
||||
destination = Path(raw) / "destination"
|
||||
|
|
@ -124,6 +126,10 @@ class DriveTransportTest(unittest.TestCase):
|
|||
(destination / "desktop-objects/generations/gen_one/history.json").read_bytes(),
|
||||
b"{}\n",
|
||||
)
|
||||
self.assertEqual(
|
||||
(destination / "android-archive-acknowledgements-v1.json").read_bytes(),
|
||||
recovery.read_bytes(),
|
||||
)
|
||||
|
||||
remote.downloads.clear()
|
||||
drive.pull(remote, destination)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ COORDINATION = (
|
|||
"android-peer-v1.json",
|
||||
"android-generation-v1.json",
|
||||
"android-consumption-ack-v1.json",
|
||||
"android-archive-acknowledgements-v1.json",
|
||||
"android-generation-error-v1.json",
|
||||
"request-v1.json",
|
||||
"desktop-archive-acknowledgements-v1.json",
|
||||
|
|
|
|||
|
|
@ -242,6 +242,7 @@ def mirror_current_exchange(transport_root: Path, remote: str, timeout: int) ->
|
|||
"android-peer-v1.json",
|
||||
"android-generation-v1.json",
|
||||
"android-consumption-ack-v1.json",
|
||||
"android-archive-acknowledgements-v1.json",
|
||||
"android-generation-error-v1.json",
|
||||
"request-v1.json",
|
||||
"desktop-archive-acknowledgements-v1.json",
|
||||
|
|
|
|||
|
|
@ -622,6 +622,14 @@ TrainlogStatus trainlog_generation_mtp_pull(const TrainlogGenerationMtpIo *io,
|
|||
status = pull_named_file(
|
||||
io, &selection, selection.root, "android-consumption-ack-v1.json", local, false);
|
||||
}
|
||||
if (status == TRAINLOG_STATUS_OK) {
|
||||
status = pull_named_file(io,
|
||||
&selection,
|
||||
selection.root,
|
||||
"android-archive-acknowledgements-v1.json",
|
||||
local,
|
||||
false);
|
||||
}
|
||||
if (status == TRAINLOG_STATUS_OK) {
|
||||
status = pull_named_file(
|
||||
io, &selection, selection.root, "android-generation-error-v1.json", local, false);
|
||||
|
|
|
|||
|
|
@ -250,6 +250,12 @@ static int test_pull_and_manifest_last(void) {
|
|||
"trainlog-sync-full-generation-request-v1.json",
|
||||
"{\"format\":\"trainlog-sync-request\",\"version\":1,\"request_id\":\"sr_11111111-"
|
||||
"1111-4111-8111-111111111111\",\"requested_at\":\"2026-09-20T12:00:00Z\"}");
|
||||
add(15,
|
||||
2,
|
||||
false,
|
||||
"android-archive-acknowledgements-v1.json",
|
||||
"{\"format\":\"trainlog-sync-archive-acknowledgements\",\"version\":1,"
|
||||
"\"acknowledgements\":[]}");
|
||||
add(5, 2, true, "android-objects", NULL);
|
||||
add(6, 5, true, "generations", NULL);
|
||||
add(7, 6, true, "gen_11111111-1111-4111-8111-111111111111", NULL);
|
||||
|
|
@ -272,6 +278,9 @@ static int test_pull_and_manifest_last(void) {
|
|||
CHECK(access(path, R_OK) == 0);
|
||||
CHECK(trainlog_test_join_path(path, sizeof(path), root, "trainlog-sync-request-v1.json"));
|
||||
CHECK(access(path, R_OK) == 0);
|
||||
CHECK(trainlog_test_join_path(
|
||||
path, sizeof(path), root, "android-archive-acknowledgements-v1.json"));
|
||||
CHECK(access(path, R_OK) == 0);
|
||||
CHECK(trainlog_test_join_path(path, sizeof(path), root, "out"));
|
||||
CHECK(mkdir(path, 0700) == 0);
|
||||
char gen[512];
|
||||
|
|
|
|||
Loading…
Reference in a new issue