Merge branch 'main-upstream' into chess-enhancements-and-bug-fixes
This commit is contained in:
+1
-6
@@ -52,12 +52,7 @@ data class IntentResult(
|
||||
result = data.getStringExtra("result"),
|
||||
event = data.getStringExtra("event"),
|
||||
`package` = data.getStringExtra("package"),
|
||||
rejected =
|
||||
if (data.extras?.containsKey("rejected") == true) {
|
||||
data.getBooleanExtra("rejected", false)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
rejected = data.extras?.containsKey("rejected"),
|
||||
)
|
||||
|
||||
fun fromJson(json: String): IntentResult = JsonMapperNip55.fromJsonTo<IntentResult>(json)
|
||||
|
||||
+21
-15
@@ -20,37 +20,43 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nipBEBle.protocol
|
||||
|
||||
import kotlin.concurrent.atomics.AtomicReference
|
||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||
|
||||
/**
|
||||
* Accumulates incoming BLE chunks for a single message and reassembles
|
||||
* them once all chunks have arrived.
|
||||
*
|
||||
* Thread-safe: all access to internal state is synchronized.
|
||||
* Thread-safe: uses a lock-free compare-and-set loop over an immutable list.
|
||||
*/
|
||||
@OptIn(ExperimentalAtomicApi::class)
|
||||
class BleChunkAssembler {
|
||||
private val lock = Any()
|
||||
private val receivedChunks = mutableListOf<ByteArray>()
|
||||
private val receivedChunks = AtomicReference(emptyList<ByteArray>())
|
||||
|
||||
/**
|
||||
* Adds a received chunk. If this completes the message, returns the
|
||||
* reassembled JSON string. Otherwise returns null.
|
||||
*/
|
||||
fun addChunk(chunk: ByteArray): String? =
|
||||
synchronized(lock) {
|
||||
receivedChunks.add(chunk)
|
||||
if (BleMessageChunker.isComplete(receivedChunks)) {
|
||||
val message = BleMessageChunker.joinChunks(receivedChunks.toTypedArray())
|
||||
receivedChunks.clear()
|
||||
message
|
||||
fun addChunk(chunk: ByteArray): String? {
|
||||
while (true) {
|
||||
val current = receivedChunks.load()
|
||||
val updated = current + chunk
|
||||
if (BleMessageChunker.isComplete(updated)) {
|
||||
if (receivedChunks.compareAndSet(current, emptyList())) {
|
||||
return BleMessageChunker.joinChunks(updated.toTypedArray())
|
||||
}
|
||||
} else {
|
||||
null
|
||||
if (receivedChunks.compareAndSet(current, updated)) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards any partially received chunks.
|
||||
*/
|
||||
fun reset() =
|
||||
synchronized(lock) {
|
||||
receivedChunks.clear()
|
||||
}
|
||||
fun reset() {
|
||||
receivedChunks.store(emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
+27
-24
@@ -31,6 +31,10 @@ import com.vitorpamplona.quartz.nipBEBle.protocol.BleChunkAssembler
|
||||
import com.vitorpamplona.quartz.nipBEBle.protocol.BleMessageChunker
|
||||
import com.vitorpamplona.quartz.nipBEBle.transport.BleTransport
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlin.concurrent.atomics.AtomicBoolean
|
||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||
|
||||
/**
|
||||
* A Nostr relay client that communicates over BLE instead of WebSockets.
|
||||
@@ -54,6 +58,7 @@ import com.vitorpamplona.quartz.utils.Log
|
||||
* client.sendIfConnected(EventCmd(myEvent))
|
||||
* ```
|
||||
*/
|
||||
@OptIn(ExperimentalAtomicApi::class, ExperimentalCoroutinesApi::class)
|
||||
class BleNostrClient(
|
||||
val peer: BlePeer,
|
||||
val transport: BleTransport,
|
||||
@@ -64,8 +69,8 @@ class BleNostrClient(
|
||||
|
||||
private var connected = false
|
||||
private val assembler = BleChunkAssembler()
|
||||
private val sendQueue = ArrayDeque<Array<ByteArray>>()
|
||||
private var isSending = false
|
||||
private val sendQueue = Channel<Array<ByteArray>>(Channel.UNLIMITED)
|
||||
private val isSending = AtomicBoolean(false)
|
||||
|
||||
override fun isConnected(): Boolean = connected
|
||||
|
||||
@@ -96,12 +101,9 @@ class BleNostrClient(
|
||||
val json = OptimizedJsonMapper.toJson(cmd)
|
||||
val chunks = BleMessageChunker.splitIntoChunks(json, chunkSize)
|
||||
|
||||
synchronized(sendQueue) {
|
||||
sendQueue.addLast(chunks)
|
||||
if (!isSending) {
|
||||
isSending = true
|
||||
sendNextMessage()
|
||||
}
|
||||
sendQueue.trySend(chunks)
|
||||
if (!isSending.exchange(true)) {
|
||||
sendNextMessage()
|
||||
}
|
||||
|
||||
listener.onSent(this, json, cmd, true)
|
||||
@@ -110,10 +112,7 @@ class BleNostrClient(
|
||||
override fun disconnect() {
|
||||
connected = false
|
||||
assembler.reset()
|
||||
synchronized(sendQueue) {
|
||||
sendQueue.clear()
|
||||
isSending = false
|
||||
}
|
||||
drainQueue()
|
||||
transport.disconnectFromPeer(peer)
|
||||
listener.onDisconnected(this)
|
||||
}
|
||||
@@ -132,10 +131,7 @@ class BleNostrClient(
|
||||
fun onDisconnected() {
|
||||
connected = false
|
||||
assembler.reset()
|
||||
synchronized(sendQueue) {
|
||||
sendQueue.clear()
|
||||
isSending = false
|
||||
}
|
||||
drainQueue()
|
||||
listener.onDisconnected(this)
|
||||
}
|
||||
|
||||
@@ -165,21 +161,28 @@ class BleNostrClient(
|
||||
private var currentChunkIndex = 0
|
||||
|
||||
private fun sendNextMessage() {
|
||||
val chunks =
|
||||
synchronized(sendQueue) {
|
||||
sendQueue.removeFirstOrNull()
|
||||
}
|
||||
if (chunks == null) {
|
||||
synchronized(sendQueue) {
|
||||
isSending = false
|
||||
val result = sendQueue.tryReceive()
|
||||
if (result.isFailure) {
|
||||
isSending.store(false)
|
||||
// Double-check: an item may have been added after tryReceive
|
||||
// but before we cleared the flag.
|
||||
if (sendQueue.isEmpty) return
|
||||
if (!isSending.exchange(true)) {
|
||||
sendNextMessage()
|
||||
}
|
||||
return
|
||||
}
|
||||
currentChunks = chunks
|
||||
currentChunks = result.getOrNull()
|
||||
currentChunkIndex = 0
|
||||
sendNextChunk()
|
||||
}
|
||||
|
||||
private fun drainQueue() {
|
||||
while (sendQueue.tryReceive().isSuccess) {}
|
||||
currentChunks = null
|
||||
isSending.store(false)
|
||||
}
|
||||
|
||||
private fun sendNextChunk() {
|
||||
val chunks = currentChunks ?: return
|
||||
if (currentChunkIndex >= chunks.size) {
|
||||
|
||||
+26
-20
@@ -29,6 +29,10 @@ import com.vitorpamplona.quartz.nipBEBle.protocol.BleChunkAssembler
|
||||
import com.vitorpamplona.quartz.nipBEBle.protocol.BleMessageChunker
|
||||
import com.vitorpamplona.quartz.nipBEBle.transport.BleTransport
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlin.concurrent.atomics.AtomicBoolean
|
||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||
|
||||
/**
|
||||
* Handles the server (relay) side of a BLE Nostr connection.
|
||||
@@ -52,6 +56,7 @@ import com.vitorpamplona.quartz.utils.Log
|
||||
* server.sendMessage(EventMessage(subId, event))
|
||||
* ```
|
||||
*/
|
||||
@OptIn(ExperimentalAtomicApi::class, ExperimentalCoroutinesApi::class)
|
||||
class BleNostrServer(
|
||||
val peer: BlePeer,
|
||||
val transport: BleTransport,
|
||||
@@ -60,8 +65,8 @@ class BleNostrServer(
|
||||
) {
|
||||
private var connected = false
|
||||
private val assembler = BleChunkAssembler()
|
||||
private val sendQueue = ArrayDeque<Array<ByteArray>>()
|
||||
private var isSending = false
|
||||
private val sendQueue = Channel<Array<ByteArray>>(Channel.UNLIMITED)
|
||||
private val isSending = AtomicBoolean(false)
|
||||
|
||||
fun isConnected(): Boolean = connected
|
||||
|
||||
@@ -79,10 +84,7 @@ class BleNostrServer(
|
||||
fun onClientDisconnected() {
|
||||
connected = false
|
||||
assembler.reset()
|
||||
synchronized(sendQueue) {
|
||||
sendQueue.clear()
|
||||
isSending = false
|
||||
}
|
||||
drainQueue()
|
||||
listener.onClientDisconnected(this)
|
||||
}
|
||||
|
||||
@@ -110,12 +112,9 @@ class BleNostrServer(
|
||||
val json = OptimizedJsonMapper.toJson(msg)
|
||||
val chunks = BleMessageChunker.splitIntoChunks(json, chunkSize)
|
||||
|
||||
synchronized(sendQueue) {
|
||||
sendQueue.addLast(chunks)
|
||||
if (!isSending) {
|
||||
isSending = true
|
||||
sendNextMessage()
|
||||
}
|
||||
sendQueue.trySend(chunks)
|
||||
if (!isSending.exchange(true)) {
|
||||
sendNextMessage()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,21 +138,28 @@ class BleNostrServer(
|
||||
private var currentChunkIndex = 0
|
||||
|
||||
private fun sendNextMessage() {
|
||||
val chunks =
|
||||
synchronized(sendQueue) {
|
||||
sendQueue.removeFirstOrNull()
|
||||
}
|
||||
if (chunks == null) {
|
||||
synchronized(sendQueue) {
|
||||
isSending = false
|
||||
val result = sendQueue.tryReceive()
|
||||
if (result.isFailure) {
|
||||
isSending.store(false)
|
||||
// Double-check: an item may have been added after tryReceive
|
||||
// but before we cleared the flag.
|
||||
if (sendQueue.isEmpty) return
|
||||
if (!isSending.exchange(true)) {
|
||||
sendNextMessage()
|
||||
}
|
||||
return
|
||||
}
|
||||
currentChunks = chunks
|
||||
currentChunks = result.getOrNull()
|
||||
currentChunkIndex = 0
|
||||
sendNextChunk()
|
||||
}
|
||||
|
||||
private fun drainQueue() {
|
||||
while (sendQueue.tryReceive().isSuccess) {}
|
||||
currentChunks = null
|
||||
isSending.store(false)
|
||||
}
|
||||
|
||||
private fun sendNextChunk() {
|
||||
val chunks = currentChunks ?: return
|
||||
if (currentChunkIndex >= chunks.size) {
|
||||
|
||||
Reference in New Issue
Block a user