Merge branch 'main-upstream' into ci-use-built-in-gradle-cache
This commit is contained in:
@@ -746,7 +746,7 @@ android {
|
|||||||
minSdk = 26 // Android 8.0 (Oreo)
|
minSdk = 26 // Android 8.0 (Oreo)
|
||||||
targetSdk = 36 // Android 15
|
targetSdk = 36 // Android 15
|
||||||
versionCode = 435
|
versionCode = 435
|
||||||
versionName = "1.06.3"
|
versionName = "1.07.0"
|
||||||
|
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
useSupportLibrary = true
|
useSupportLibrary = true
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ description: Integration guide for using the Quartz Nostr KMP library in externa
|
|||||||
|
|
||||||
Reference for integrating `com.vitorpamplona.quartz:quartz` into external Nostr KMP projects.
|
Reference for integrating `com.vitorpamplona.quartz:quartz` into external Nostr KMP projects.
|
||||||
|
|
||||||
**Published artifact**: `com.vitorpamplona.quartz:quartz:1.06.3` (Maven Central)
|
**Published artifact**: `com.vitorpamplona.quartz:quartz:1.07.0` (Maven Central)
|
||||||
**Targets**: JVM 21+, Android (minSdk 21+), iOS (XCFramework `quartz-kmpKit`)
|
**Targets**: JVM 21+, Android (minSdk 21+), iOS (XCFramework `quartz-kmpKit`)
|
||||||
**License**: MIT
|
**License**: MIT
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ Reference for integrating `com.vitorpamplona.quartz:quartz` into external Nostr
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[versions]
|
[versions]
|
||||||
quartz = "1.06.3"
|
quartz = "1.07.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
quartz = { module = "com.vitorpamplona.quartz:quartz", version.ref = "quartz" }
|
quartz = { module = "com.vitorpamplona.quartz:quartz", version.ref = "quartz" }
|
||||||
@@ -41,7 +41,7 @@ kotlin {
|
|||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("com.vitorpamplona.quartz:quartz:1.06.3")
|
implementation("com.vitorpamplona.quartz:quartz:1.07.0")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
## Current version
|
## Current version
|
||||||
|
|
||||||
```
|
```
|
||||||
com.vitorpamplona.quartz:quartz:1.06.3
|
com.vitorpamplona.quartz:quartz:1.07.0
|
||||||
```
|
```
|
||||||
|
|
||||||
Check latest: https://central.sonatype.com/artifact/com.vitorpamplona.quartz/quartz
|
Check latest: https://central.sonatype.com/artifact/com.vitorpamplona.quartz/quartz
|
||||||
@@ -16,7 +16,7 @@ Check latest: https://central.sonatype.com/artifact/com.vitorpamplona.quartz/qua
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[versions]
|
[versions]
|
||||||
quartz = "1.06.3"
|
quartz = "1.07.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
quartz = { module = "com.vitorpamplona.quartz:quartz", version.ref = "quartz" }
|
quartz = { module = "com.vitorpamplona.quartz:quartz", version.ref = "quartz" }
|
||||||
@@ -55,7 +55,7 @@ kotlin {
|
|||||||
```kotlin
|
```kotlin
|
||||||
// build.gradle.kts (app module)
|
// build.gradle.kts (app module)
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("com.vitorpamplona.quartz:quartz:1.06.3")
|
implementation("com.vitorpamplona.quartz:quartz:1.07.0")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("com.vitorpamplona.quartz:quartz:1.06.3")
|
implementation("com.vitorpamplona.quartz:quartz:1.07.0")
|
||||||
// JNA needed for libsodium (NIP-44) on JVM
|
// JNA needed for libsodium (NIP-44) on JVM
|
||||||
implementation("net.java.dev.jna:jna:5.18.1")
|
implementation("net.java.dev.jna:jna:5.18.1")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ The Quartz library was successfully converted from Android-only to full KMP supp
|
|||||||
## Current artifact
|
## Current artifact
|
||||||
|
|
||||||
```
|
```
|
||||||
com.vitorpamplona.quartz:quartz:1.06.3
|
com.vitorpamplona.quartz:quartz:1.07.0
|
||||||
```
|
```
|
||||||
|
|
||||||
See `.claude/skills/quartz-integration/SKILL.md` for full integration guide.
|
See `.claude/skills/quartz-integration/SKILL.md` for full integration guide.
|
||||||
+176
-3
@@ -1,5 +1,178 @@
|
|||||||
|
<a id="v1.07.0"></a>
|
||||||
|
# [Release v1.07.0: Vanish, Relay Management, Pin Notes](https://github.com/vitorpamplona/amethyst/releases/tag/v1.07.0) - 2026-03-30
|
||||||
|
|
||||||
|
Amethyst:
|
||||||
|
- Adds support to Pin Notes
|
||||||
|
- Adds support to Polls feed screen
|
||||||
|
- Adds support for Requests to Vanish
|
||||||
|
- Adds support for Relay management (NIP-86) from Amethyst
|
||||||
|
- Adds support for Relay monitor assessments (NIP-66) in the Relay Info screen.
|
||||||
|
- Adds support for Relay member information (NIP-43) in the Relay Info screen
|
||||||
|
- Adds support for WebBookmarks
|
||||||
|
- Adds support for Zap Goals
|
||||||
|
|
||||||
|
Quartz:
|
||||||
|
- Rewords the NostrClient API for simpler commands.
|
||||||
|
- Adds NIP-15: Nostr Marketplace protocol — product listings, stalls, merchant events (#2020)
|
||||||
|
- Adds NIP-24: Birthday field support added to UserMetadata (#1979)
|
||||||
|
- Adds NIP-29: Relay-based groups — group events, member management, moderation (#2021)
|
||||||
|
- Adds NIP-32: Labeling protocol — LabelEvent and tag parsing (#1975)
|
||||||
|
- Adds NIP-43: Relay access metadata and membership management with UI screens (#2010)
|
||||||
|
- Adds NIP-60: Cashu wallet and spending history event support (#2009)
|
||||||
|
- Adds NIP-61: Nutzaps protocol support (#2008)
|
||||||
|
- Adds NIP-62: Request to Vanish feature — data deletion with relay compliance testing (#1958)
|
||||||
|
- Adds NIP-66: Relay monitor discovery events — UI, filter assembler, subscription composable (#1968)
|
||||||
|
- Adds NIP-69: P2P Order Events (kind 38383) (#2019)
|
||||||
|
- Adds NIP-75: Zap Goals support with creation and rendering (#1965)
|
||||||
|
- Adds NIP-77: Negentropy set reconciliation protocol support (#1955)
|
||||||
|
- Adds NIP-7D: Thread events (kind 11) rendering (#2016)
|
||||||
|
- Adds NIP-85: Trusted Assertions — assertions for events and addressables (#1981)
|
||||||
|
- Adds NIP-86: Relay management UI and client implementation (#1954)
|
||||||
|
- Adds NIP-87: Ecash mint discoverability — Cashu and Fedimint event types (#2015)
|
||||||
|
- Adds NIP-89: Compliance fixes, PlatformLinkTag parsing, app handler extensions (#2030)
|
||||||
|
- Adds NIP-90: All DVM kind event classes from data-vending-machines spec, restructured packages (#2023, #2025)
|
||||||
|
- Adds NIP-A4: Add k tag to zap requests and enforce e tag prohibition (#1978)
|
||||||
|
- Adds NIP-BE: Bluetooth Low Energy mesh networking / Nostr BLE Communications Protocol (#2022)
|
||||||
|
- Adds NIP-C7: Chat messages (kind 9) implementation (#2018)
|
||||||
|
- Adds NIP-5A: Static website event rendering (#2017)
|
||||||
|
- Adds NIP-51, kind 10001: Pinned notes feature (#1956)
|
||||||
|
|
||||||
|
Improvements and Fixes:
|
||||||
|
- Show toast instead of dialog on media download success
|
||||||
|
- Dynamically adjust preferred Blossom server when list changes
|
||||||
|
- Add relay discovery to node master rendering list
|
||||||
|
- Add scroll to settings page
|
||||||
|
- Solves crashing when multiple relays with the same url are included in the resulting list.
|
||||||
|
- Keep screen on during PiP playback and survive screen lock (#1999)
|
||||||
|
- Desktop feed loading — missing events, broken profile navigation (#2027)
|
||||||
|
- Use getOrCreateNote for reply linking to fix flaky thread test (#2027)
|
||||||
|
- Route ReadsScreen following-mode events through cache (#2027)
|
||||||
|
- Pin ElectrumX server certs for Samsung One UI 7 / Android 16 compatibility (#1937)
|
||||||
|
- Duplicate keys in relay management lazy column — sort pubkeys
|
||||||
|
- URL detector — fixes localhost:3030 strings, Japanese character URLs
|
||||||
|
- Web Bookmarks floating action button shape (circle) and open graph previews.
|
||||||
|
- NIP-86 requests now send Accept and Content-Type headers
|
||||||
|
- On DMs, activates decryption for all filetypes that match decryption url with the cipher info, not only binaries
|
||||||
|
- Adds a try/finally to subscriptions to make sure they close even in crashes.
|
||||||
|
- Protects against crashes when the signer sends an unverifiable payload back to Amethyst
|
||||||
|
|
||||||
|
Desktop:
|
||||||
|
- Cache-centric architecture for desktop feeds (#1905)
|
||||||
|
- Render reposts and quoted notes in feed (#2027)
|
||||||
|
|
||||||
|
Performance
|
||||||
|
- Faster startup procedures with less loading on the main thread.
|
||||||
|
- 20x Faster Rfc3986Normalizer and way less objects being created.
|
||||||
|
- Url Detector without using regex
|
||||||
|
- Parallelize preference file access at startup for faster launch
|
||||||
|
- Remove internal runBlocking calls
|
||||||
|
- Lazy loading the memory trimming service
|
||||||
|
- Speeding up DrawerContent rendering
|
||||||
|
- Eagerly delete intermediate temp files in upload pipeline
|
||||||
|
|
||||||
|
Refactoring:
|
||||||
|
- Simplify NostrClient API for beginner-friendliness (#1986)
|
||||||
|
- Simplify relay API with AutoCloseable and serve() helper (#2031)
|
||||||
|
- URL detector performance and readability improvements (#2013)
|
||||||
|
- Restructure NIP-90 DVMs to match NIP-88 Polls pattern (#2023)
|
||||||
|
- Reorganize NIP-BE into subpackages
|
||||||
|
- Move public messages and trusted assertions out of experimental
|
||||||
|
|
||||||
|
Migrations & Deprecation Fixes
|
||||||
|
|
||||||
|
- Migrate `LocalClipboardManager` to `LocalClipboard` (#1995)
|
||||||
|
- Migrate `LocalAutofillTree` to semantics-based Autofill API (#1996)
|
||||||
|
- Migrate `TabRow`/`ScrollableTabRow` to Material 3 Secondary variants (#1994)
|
||||||
|
- Replace `ContextCompat.startActivity` with `Context.startActivity` (#1993)
|
||||||
|
- Suppress deprecation and unchecked cast warnings in Quartz internal code (#1985)
|
||||||
|
|
||||||
|
Platform & Build
|
||||||
|
|
||||||
|
- Add linuxX64 target and restructure native source sets
|
||||||
|
- Add explicit permissions to GitHub Actions workflows
|
||||||
|
- Stop Gradle daemons after build steps
|
||||||
|
- Increase Android CI build timeout to 45 minutes
|
||||||
|
- Update video compression library to latest
|
||||||
|
- Update dependencies, add local maven repo for easier library development
|
||||||
|
- Remove libsodium files
|
||||||
|
- Git hooks installation in worktrees (.git file vs directory)
|
||||||
|
- Fixes lack of proxy access to download VLC files in claude's web environment.
|
||||||
|
- Log level filtering — LogLevel enum, PlatformLog, lambda overloads for 194+ Log.d() calls (#2005)
|
||||||
|
- Parallelize BaseDBTest forEachDB using coroutines
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
|
||||||
|
- Add CLIENT.md guide for building Nostr clients with Quartz
|
||||||
|
- Add RELAY.md guide for building relays with Ktor, NostrServer, SQLite
|
||||||
|
|
||||||
|
Contributors
|
||||||
|
|
||||||
|
- @npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z — Primary development
|
||||||
|
- @npub1e2yuky03caw4ke3zy68lg0fz3r4gkt94hx4fjmlelacyljgyk79svn3eef — Log level filtering, video compression, toast downloads, CI fixes, git worktree fix
|
||||||
|
- @npub12cfje6nl2nuxplcqfvhg7ljt89fmpj0n0fd24zxsukja5qm9wmtqd7y76c — Desktop cache architecture, desktop bugfixes
|
||||||
|
- @npub1w4uswmv6lu9yel005l3qgheysmr7tk9uvwluddznju3nuxalevvs2d0jr5 — CI workflow permissions
|
||||||
|
- @npub1a3tx8wcrt789skl6gg7rqwj4wey0j53eesr4z6asd4h4jwrd62jq0wkq4k — Remove libsodium files
|
||||||
|
- **mstrofnone** — ElectrumX cert pinning for Samsung One UI 7
|
||||||
|
|
||||||
|
Translations
|
||||||
|
|
||||||
|
- Czech, German, Swedish, and Portuguese by @npub1e2yuky03caw4ke3zy68lg0fz3r4gkt94hx4fjmlelacyljgyk79svn3eef
|
||||||
|
- Hungarian by @npub1dnvslq0vvrs8d603suykc4harv94yglcxwna9sl2xu8grt2afm3qgfh0tp
|
||||||
|
- French by @npub106efcyntxc5qwl3w8krrhyt626m59ya2nk9f40px5s968u5xdwhsjsr8fz
|
||||||
|
- Polish by @npub16gjyljum0ksrrm28zzvejydgxwfm7xse98zwc4hlgq8epxeuggushqwyrm
|
||||||
|
- Hindi by @npub1ww6huwu3xye6r05n3qkjeq62wds5pq0jswhl7uc59lchc0n0ns4sdtw5e6
|
||||||
|
- Slovenian by @npub1qqqqqqz7nhdqz3uuwmzlflxt46lyu7zkuqhcapddhgz66c4ddynswreecw
|
||||||
|
- Bengali by @npub13qtw3yu0uc9r4yj5x0rhgy8nj5q0uyeq0pavkgt9ly69uuzxgkfqwvx23t
|
||||||
|
- Spanish by @npub1luhyzgce7qtcs6r6v00ryjxza8av8u4dzh3avg0zks38tjktnmxspxq903
|
||||||
|
- Chinese by hypnotichemionus4 and @npub1gd8e0xfkylc7v8c5a6hkpj4gelwwcy99jt90lqjseqjj2t253s2s6ch58h
|
||||||
|
- Russian by Anton Zhao
|
||||||
|
|
||||||
|
<a id="v1.06.3"></a>
|
||||||
|
# [Release v1.06.3: Anon Posting](https://github.com/vitorpamplona/amethyst/releases/tag/v1.06.3) - 2026-03-24
|
||||||
|
|
||||||
|
Improvements to Anon Posting and Reply navigation on DMs
|
||||||
|
|
||||||
|
<a id="v1.06.2"></a>
|
||||||
|
# [Release v1.06.2: Multi-choice Polls](https://github.com/vitorpamplona/amethyst/releases/tag/v1.06.2) - 2026-03-24
|
||||||
|
|
||||||
|
- Add poll type selector for single and multiple choice polls
|
||||||
|
- Add drag-to-seek support on the video progress indicator
|
||||||
|
- Hide video controls on playback start
|
||||||
|
- Recording indicator bar stops recording when tapped, not just the small stop icon.
|
||||||
|
- Fixes non http uris in the references tag
|
||||||
|
- Makes sure quote tags only happen once in events.
|
||||||
|
- Removes the clickable NIP-05 URL in the @ tagging of users
|
||||||
|
- Reduces the need to compute naddr to index articles in bookmarks
|
||||||
|
- Checks if p tags are user pubkeys when loading drafts.
|
||||||
|
- Scroll to the replied message when clicking reply preview in chat
|
||||||
|
- Add an anonymous reply with a throwaway keypair
|
||||||
|
- Fixes pool rendering when one label is large and the other is small
|
||||||
|
- Use uri-reference-kmp in commonMain. Remove platform-specific implementations.
|
||||||
|
- Avoids crashing the app in URLs with japanese chars
|
||||||
|
- Sorts followers into a set to avoid LazyColumn key conflicts.
|
||||||
|
- Clickable relay rows on the profile page
|
||||||
|
- Move cache lookups from NavHost route lambdas into screen composables
|
||||||
|
- Improvements to the status of attestations. Validity first, then Processing status.
|
||||||
|
- Fixes Tor Manager flow value and improves Tor binding lifecycle.
|
||||||
|
- Simplifies the report feed in the user profile
|
||||||
|
- Clears some inconsistencies in translated strings
|
||||||
|
- Fixes client tags
|
||||||
|
- Fixes routes for AppDefinition events.
|
||||||
|
|
||||||
|
<a id="v1.06.1"></a>
|
||||||
|
# [Release v1.06.1: Fixes](https://github.com/vitorpamplona/amethyst/releases/tag/v1.06.1) - 2026-03-23
|
||||||
|
|
||||||
|
- Improvements to the rendering of Polls.
|
||||||
|
- Solves some of the crashes of the concurrent modification exception
|
||||||
|
- Fixes URL parsers with Japanese chars
|
||||||
|
- Fixes Wallet import from Primal on Poco phones
|
||||||
|
- Improves the wording of the Last Seen
|
||||||
|
- Fixes for "Cannot disable reuse from root if it was caused by other groups"
|
||||||
|
- Fixes comparator to avoid Comparison method violates its general contract!
|
||||||
|
- Improves zap-store settings
|
||||||
|
|
||||||
<a id="v1.06.0"></a>
|
<a id="v1.06.0"></a>
|
||||||
# [Release v1.06.0: Polls, Relay Feeds, Wallets and much more](https://github.com/vitorpamplona/amethyst/releases/tag/v1.06.0) - 2025-03-21
|
# [Release v1.06.0: Polls, Relay Feeds, Wallets and much more](https://github.com/vitorpamplona/amethyst/releases/tag/v1.06.0) - 2026-03-21
|
||||||
|
|
||||||
Polls:
|
Polls:
|
||||||
- Adds support for creating and rendering NIP-85
|
- Adds support for creating and rendering NIP-85
|
||||||
@@ -271,13 +444,13 @@ Updated translations:
|
|||||||
- Russian by Anton Zhao
|
- Russian by Anton Zhao
|
||||||
|
|
||||||
<a id="v1.05.1"></a>
|
<a id="v1.05.1"></a>
|
||||||
# [Release v1.05.1: BugFixes](https://github.com/vitorpamplona/amethyst/releases/tag/v1.05.0) - 2025-01-08
|
# [Release v1.05.1: BugFixes](https://github.com/vitorpamplona/amethyst/releases/tag/v1.05.0) - 2026-01-08
|
||||||
|
|
||||||
- Fixed mixed DMs between logged in users.
|
- Fixed mixed DMs between logged in users.
|
||||||
- Fixed draft screen click to edit post.
|
- Fixed draft screen click to edit post.
|
||||||
|
|
||||||
<a id="v1.05.0"></a>
|
<a id="v1.05.0"></a>
|
||||||
# [Release v1.05.0: Bookmark Lists and WoT Scores](https://github.com/vitorpamplona/amethyst/releases/tag/v1.05.0) - 2025-01-08
|
# [Release v1.05.0: Bookmark Lists and WoT Scores](https://github.com/vitorpamplona/amethyst/releases/tag/v1.05.0) - 2026-01-08
|
||||||
|
|
||||||
#Amethyst v1.05.0: Bookmark Lists, Voice Notes, and WoT Scores
|
#Amethyst v1.05.0: Bookmark Lists, Voice Notes, and WoT Scores
|
||||||
|
|
||||||
|
|||||||
@@ -54,9 +54,9 @@ android {
|
|||||||
applicationId = "com.vitorpamplona.amethyst"
|
applicationId = "com.vitorpamplona.amethyst"
|
||||||
minSdk = libs.versions.android.minSdk.get().toInteger()
|
minSdk = libs.versions.android.minSdk.get().toInteger()
|
||||||
targetSdk = libs.versions.android.targetSdk.get().toInteger()
|
targetSdk = libs.versions.android.targetSdk.get().toInteger()
|
||||||
versionCode = 435
|
versionCode = 436
|
||||||
versionName = generateVersionName("1.06.3")
|
versionName = generateVersionName("1.07.0")
|
||||||
buildConfigField "String", "RELEASE_NOTES_ID", "\"0b6af7660b44215b0edf9c39a1c9c0b4aafba7aba1ae28665ffcecb1a9717195\""
|
buildConfigField "String", "RELEASE_NOTES_ID", "\"12cd4bce977ed53502cf121ecba89a190ab02685333c8f230bac35b04f920eeb\""
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ kotlin {
|
|||||||
// SQLite KMP driver for event store
|
// SQLite KMP driver for event store
|
||||||
api(libs.androidx.sqlite)
|
api(libs.androidx.sqlite)
|
||||||
implementation(libs.androidx.sqlite.bundled)
|
implementation(libs.androidx.sqlite.bundled)
|
||||||
|
|
||||||
// Negentropy set reconciliation (NIP-77)
|
// Negentropy set reconciliation (NIP-77)
|
||||||
api(libs.negentropy.kmp)
|
api(libs.negentropy.kmp)
|
||||||
}
|
}
|
||||||
@@ -315,7 +315,7 @@ mavenPublishing {
|
|||||||
coordinates(
|
coordinates(
|
||||||
groupId = "com.vitorpamplona.quartz",
|
groupId = "com.vitorpamplona.quartz",
|
||||||
artifactId = "quartz",
|
artifactId = "quartz",
|
||||||
version = "1.06.3",
|
version = "1.07.0",
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configure publishing to Maven Central
|
// Configure publishing to Maven Central
|
||||||
|
|||||||
+21
-15
@@ -20,37 +20,43 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.quartz.nipBEBle.protocol
|
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
|
* Accumulates incoming BLE chunks for a single message and reassembles
|
||||||
* them once all chunks have arrived.
|
* 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 {
|
class BleChunkAssembler {
|
||||||
private val lock = Any()
|
private val receivedChunks = AtomicReference(emptyList<ByteArray>())
|
||||||
private val receivedChunks = mutableListOf<ByteArray>()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a received chunk. If this completes the message, returns the
|
* Adds a received chunk. If this completes the message, returns the
|
||||||
* reassembled JSON string. Otherwise returns null.
|
* reassembled JSON string. Otherwise returns null.
|
||||||
*/
|
*/
|
||||||
fun addChunk(chunk: ByteArray): String? =
|
fun addChunk(chunk: ByteArray): String? {
|
||||||
synchronized(lock) {
|
while (true) {
|
||||||
receivedChunks.add(chunk)
|
val current = receivedChunks.load()
|
||||||
if (BleMessageChunker.isComplete(receivedChunks)) {
|
val updated = current + chunk
|
||||||
val message = BleMessageChunker.joinChunks(receivedChunks.toTypedArray())
|
if (BleMessageChunker.isComplete(updated)) {
|
||||||
receivedChunks.clear()
|
if (receivedChunks.compareAndSet(current, emptyList())) {
|
||||||
message
|
return BleMessageChunker.joinChunks(updated.toTypedArray())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
null
|
if (receivedChunks.compareAndSet(current, updated)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Discards any partially received chunks.
|
* Discards any partially received chunks.
|
||||||
*/
|
*/
|
||||||
fun reset() =
|
fun reset() {
|
||||||
synchronized(lock) {
|
receivedChunks.store(emptyList())
|
||||||
receivedChunks.clear()
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+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.protocol.BleMessageChunker
|
||||||
import com.vitorpamplona.quartz.nipBEBle.transport.BleTransport
|
import com.vitorpamplona.quartz.nipBEBle.transport.BleTransport
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
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.
|
* 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))
|
* client.sendIfConnected(EventCmd(myEvent))
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
@OptIn(ExperimentalAtomicApi::class, ExperimentalCoroutinesApi::class)
|
||||||
class BleNostrClient(
|
class BleNostrClient(
|
||||||
val peer: BlePeer,
|
val peer: BlePeer,
|
||||||
val transport: BleTransport,
|
val transport: BleTransport,
|
||||||
@@ -64,8 +69,8 @@ class BleNostrClient(
|
|||||||
|
|
||||||
private var connected = false
|
private var connected = false
|
||||||
private val assembler = BleChunkAssembler()
|
private val assembler = BleChunkAssembler()
|
||||||
private val sendQueue = ArrayDeque<Array<ByteArray>>()
|
private val sendQueue = Channel<Array<ByteArray>>(Channel.UNLIMITED)
|
||||||
private var isSending = false
|
private val isSending = AtomicBoolean(false)
|
||||||
|
|
||||||
override fun isConnected(): Boolean = connected
|
override fun isConnected(): Boolean = connected
|
||||||
|
|
||||||
@@ -96,12 +101,9 @@ class BleNostrClient(
|
|||||||
val json = OptimizedJsonMapper.toJson(cmd)
|
val json = OptimizedJsonMapper.toJson(cmd)
|
||||||
val chunks = BleMessageChunker.splitIntoChunks(json, chunkSize)
|
val chunks = BleMessageChunker.splitIntoChunks(json, chunkSize)
|
||||||
|
|
||||||
synchronized(sendQueue) {
|
sendQueue.trySend(chunks)
|
||||||
sendQueue.addLast(chunks)
|
if (!isSending.exchange(true)) {
|
||||||
if (!isSending) {
|
sendNextMessage()
|
||||||
isSending = true
|
|
||||||
sendNextMessage()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
listener.onSent(this, json, cmd, true)
|
listener.onSent(this, json, cmd, true)
|
||||||
@@ -110,10 +112,7 @@ class BleNostrClient(
|
|||||||
override fun disconnect() {
|
override fun disconnect() {
|
||||||
connected = false
|
connected = false
|
||||||
assembler.reset()
|
assembler.reset()
|
||||||
synchronized(sendQueue) {
|
drainQueue()
|
||||||
sendQueue.clear()
|
|
||||||
isSending = false
|
|
||||||
}
|
|
||||||
transport.disconnectFromPeer(peer)
|
transport.disconnectFromPeer(peer)
|
||||||
listener.onDisconnected(this)
|
listener.onDisconnected(this)
|
||||||
}
|
}
|
||||||
@@ -132,10 +131,7 @@ class BleNostrClient(
|
|||||||
fun onDisconnected() {
|
fun onDisconnected() {
|
||||||
connected = false
|
connected = false
|
||||||
assembler.reset()
|
assembler.reset()
|
||||||
synchronized(sendQueue) {
|
drainQueue()
|
||||||
sendQueue.clear()
|
|
||||||
isSending = false
|
|
||||||
}
|
|
||||||
listener.onDisconnected(this)
|
listener.onDisconnected(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,21 +161,28 @@ class BleNostrClient(
|
|||||||
private var currentChunkIndex = 0
|
private var currentChunkIndex = 0
|
||||||
|
|
||||||
private fun sendNextMessage() {
|
private fun sendNextMessage() {
|
||||||
val chunks =
|
val result = sendQueue.tryReceive()
|
||||||
synchronized(sendQueue) {
|
if (result.isFailure) {
|
||||||
sendQueue.removeFirstOrNull()
|
isSending.store(false)
|
||||||
}
|
// Double-check: an item may have been added after tryReceive
|
||||||
if (chunks == null) {
|
// but before we cleared the flag.
|
||||||
synchronized(sendQueue) {
|
if (sendQueue.isEmpty) return
|
||||||
isSending = false
|
if (!isSending.exchange(true)) {
|
||||||
|
sendNextMessage()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
currentChunks = chunks
|
currentChunks = result.getOrNull()
|
||||||
currentChunkIndex = 0
|
currentChunkIndex = 0
|
||||||
sendNextChunk()
|
sendNextChunk()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun drainQueue() {
|
||||||
|
while (sendQueue.tryReceive().isSuccess) {}
|
||||||
|
currentChunks = null
|
||||||
|
isSending.store(false)
|
||||||
|
}
|
||||||
|
|
||||||
private fun sendNextChunk() {
|
private fun sendNextChunk() {
|
||||||
val chunks = currentChunks ?: return
|
val chunks = currentChunks ?: return
|
||||||
if (currentChunkIndex >= chunks.size) {
|
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.protocol.BleMessageChunker
|
||||||
import com.vitorpamplona.quartz.nipBEBle.transport.BleTransport
|
import com.vitorpamplona.quartz.nipBEBle.transport.BleTransport
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
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.
|
* 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))
|
* server.sendMessage(EventMessage(subId, event))
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
@OptIn(ExperimentalAtomicApi::class, ExperimentalCoroutinesApi::class)
|
||||||
class BleNostrServer(
|
class BleNostrServer(
|
||||||
val peer: BlePeer,
|
val peer: BlePeer,
|
||||||
val transport: BleTransport,
|
val transport: BleTransport,
|
||||||
@@ -60,8 +65,8 @@ class BleNostrServer(
|
|||||||
) {
|
) {
|
||||||
private var connected = false
|
private var connected = false
|
||||||
private val assembler = BleChunkAssembler()
|
private val assembler = BleChunkAssembler()
|
||||||
private val sendQueue = ArrayDeque<Array<ByteArray>>()
|
private val sendQueue = Channel<Array<ByteArray>>(Channel.UNLIMITED)
|
||||||
private var isSending = false
|
private val isSending = AtomicBoolean(false)
|
||||||
|
|
||||||
fun isConnected(): Boolean = connected
|
fun isConnected(): Boolean = connected
|
||||||
|
|
||||||
@@ -79,10 +84,7 @@ class BleNostrServer(
|
|||||||
fun onClientDisconnected() {
|
fun onClientDisconnected() {
|
||||||
connected = false
|
connected = false
|
||||||
assembler.reset()
|
assembler.reset()
|
||||||
synchronized(sendQueue) {
|
drainQueue()
|
||||||
sendQueue.clear()
|
|
||||||
isSending = false
|
|
||||||
}
|
|
||||||
listener.onClientDisconnected(this)
|
listener.onClientDisconnected(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,12 +112,9 @@ class BleNostrServer(
|
|||||||
val json = OptimizedJsonMapper.toJson(msg)
|
val json = OptimizedJsonMapper.toJson(msg)
|
||||||
val chunks = BleMessageChunker.splitIntoChunks(json, chunkSize)
|
val chunks = BleMessageChunker.splitIntoChunks(json, chunkSize)
|
||||||
|
|
||||||
synchronized(sendQueue) {
|
sendQueue.trySend(chunks)
|
||||||
sendQueue.addLast(chunks)
|
if (!isSending.exchange(true)) {
|
||||||
if (!isSending) {
|
sendNextMessage()
|
||||||
isSending = true
|
|
||||||
sendNextMessage()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,21 +138,28 @@ class BleNostrServer(
|
|||||||
private var currentChunkIndex = 0
|
private var currentChunkIndex = 0
|
||||||
|
|
||||||
private fun sendNextMessage() {
|
private fun sendNextMessage() {
|
||||||
val chunks =
|
val result = sendQueue.tryReceive()
|
||||||
synchronized(sendQueue) {
|
if (result.isFailure) {
|
||||||
sendQueue.removeFirstOrNull()
|
isSending.store(false)
|
||||||
}
|
// Double-check: an item may have been added after tryReceive
|
||||||
if (chunks == null) {
|
// but before we cleared the flag.
|
||||||
synchronized(sendQueue) {
|
if (sendQueue.isEmpty) return
|
||||||
isSending = false
|
if (!isSending.exchange(true)) {
|
||||||
|
sendNextMessage()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
currentChunks = chunks
|
currentChunks = result.getOrNull()
|
||||||
currentChunkIndex = 0
|
currentChunkIndex = 0
|
||||||
sendNextChunk()
|
sendNextChunk()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun drainQueue() {
|
||||||
|
while (sendQueue.tryReceive().isSuccess) {}
|
||||||
|
currentChunks = null
|
||||||
|
isSending.store(false)
|
||||||
|
}
|
||||||
|
|
||||||
private fun sendNextChunk() {
|
private fun sendNextChunk() {
|
||||||
val chunks = currentChunks ?: return
|
val chunks = currentChunks ?: return
|
||||||
if (currentChunkIndex >= chunks.size) {
|
if (currentChunkIndex >= chunks.size) {
|
||||||
|
|||||||
Reference in New Issue
Block a user