mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-03-29 11:11:44 +01:00
Makes lists either immutable or puts them into a synchronized block for thread safety
This commit is contained in:
parent
2037b35af6
commit
0667a822f1
@ -26,6 +26,7 @@ Amethyst brings the best social network to your Android phone. Just insert your
|
||||
- [ ] Profile Edit
|
||||
- [ ] Relay Edit
|
||||
- [ ] Account Creation / Backup Guidance
|
||||
- [ ] Message Sent feedback
|
||||
|
||||
# Development Overview
|
||||
|
||||
|
@ -67,9 +67,12 @@ class User(val pubkey: ByteArray) {
|
||||
}
|
||||
|
||||
fun updateFollows(newFollows: List<User>, updateAt: Long) {
|
||||
val toBeAdded = newFollows - follows
|
||||
val toBeRemoved = follows - newFollows
|
||||
|
||||
val toBeAdded = synchronized(follows) {
|
||||
newFollows - follows
|
||||
}
|
||||
val toBeRemoved = synchronized(follows) {
|
||||
follows - newFollows
|
||||
}
|
||||
toBeAdded.forEach {
|
||||
follow(it)
|
||||
}
|
||||
@ -89,6 +92,12 @@ class User(val pubkey: ByteArray) {
|
||||
live.refresh()
|
||||
}
|
||||
|
||||
fun isFollowing(user: User): Boolean {
|
||||
return synchronized(follows) {
|
||||
follows.contains(user)
|
||||
}
|
||||
}
|
||||
|
||||
// Observers line up here.
|
||||
val live: UserLiveData = UserLiveData(this)
|
||||
|
||||
|
@ -57,10 +57,15 @@ object NostrAccountDataSource: NostrDataSource<Note>("AccountData") {
|
||||
|
||||
override fun feed(): List<Note> {
|
||||
val user = account.userProfile()
|
||||
val follows = user.follows.map { it.pubkeyHex }.plus(user.pubkeyHex).toSet()
|
||||
|
||||
val follows = user.follows
|
||||
val followKeys = synchronized(follows) {
|
||||
follows.map { it.pubkeyHex }
|
||||
}
|
||||
val allowSet = followKeys.plus(user.pubkeyHex).toSet()
|
||||
|
||||
return LocalCache.notes.values
|
||||
.filter { (it.event is TextNoteEvent || it.event is RepostEvent) && it.author?.pubkeyHex in follows }
|
||||
.filter { (it.event is TextNoteEvent || it.event is RepostEvent) && it.author?.pubkeyHex in allowSet }
|
||||
.sortedBy { it.event!!.createdAt }
|
||||
.reversed()
|
||||
}
|
||||
|
@ -50,14 +50,14 @@ object NostrChatroomListDataSource: NostrDataSource<Note>("MailBoxFeed") {
|
||||
val messagingWith = messages.keys().toList()
|
||||
|
||||
val privateMessages = messagingWith.mapNotNull {
|
||||
messages[it]?.sortedBy { it.event?.createdAt }?.last { it.event != null }
|
||||
messages[it]?.sortedBy { it.event?.createdAt }?.lastOrNull { it.event != null }
|
||||
}
|
||||
|
||||
val publicChannels = account.followingChannels().map {
|
||||
it.notes.values.sortedBy { it.event?.createdAt }.last { it.event != null }
|
||||
it.notes.values.sortedBy { it.event?.createdAt }.lastOrNull { it.event != null }
|
||||
}
|
||||
|
||||
return (privateMessages + publicChannels).sortedBy { it.event?.createdAt }.reversed()
|
||||
return (privateMessages + publicChannels).filterNotNull().sortedBy { it.event?.createdAt }.reversed()
|
||||
}
|
||||
|
||||
override fun updateChannelFilters() {
|
||||
|
@ -29,17 +29,21 @@ object NostrHomeDataSource: NostrDataSource<Note>("HomeFeed") {
|
||||
}
|
||||
|
||||
fun createFollowAccountsFilter(): JsonFilter? {
|
||||
val follows = listOf(account.userProfile().pubkeyHex.substring(0, 6)).plus(
|
||||
account.userProfile().follows?.map {
|
||||
it.pubkey.toHex().substring(0, 6)
|
||||
} ?: emptyList()
|
||||
)
|
||||
val follows = account.userProfile().follows ?: emptySet()
|
||||
|
||||
if (follows.isEmpty()) return null
|
||||
val followKeys = synchronized(follows) {
|
||||
follows.map {
|
||||
it.pubkey.toHex().substring(0, 6)
|
||||
}
|
||||
}
|
||||
|
||||
val followSet = followKeys.plus(account.userProfile().pubkeyHex.substring(0, 6))
|
||||
|
||||
if (followSet.isEmpty()) return null
|
||||
|
||||
return JsonFilter(
|
||||
kinds = listOf(TextNoteEvent.kind, RepostEvent.kind),
|
||||
authors = follows,
|
||||
authors = followSet,
|
||||
since = System.currentTimeMillis() / 1000 - (60 * 60 * 24 * 1), // 24 hours
|
||||
)
|
||||
}
|
||||
@ -64,10 +68,16 @@ object NostrHomeDataSource: NostrDataSource<Note>("HomeFeed") {
|
||||
|
||||
override fun feed(): List<Note> {
|
||||
val user = account.userProfile()
|
||||
val follows = user.follows.map { it.pubkeyHex }.plus(user.pubkeyHex).toSet()
|
||||
|
||||
val follows = user.follows
|
||||
val followKeys = synchronized(follows) {
|
||||
follows.map { it.pubkeyHex }
|
||||
}
|
||||
|
||||
val allowSet = followKeys.plus(user.pubkeyHex).toSet()
|
||||
|
||||
return LocalCache.notes.values
|
||||
.filter { (it.event is TextNoteEvent || it.event is RepostEvent) && it.author?.pubkeyHex in follows }
|
||||
.filter { (it.event is TextNoteEvent || it.event is RepostEvent) && it.author?.pubkeyHex in allowSet }
|
||||
.sortedBy { it.event!!.createdAt }
|
||||
.reversed()
|
||||
}
|
||||
|
@ -32,10 +32,12 @@ object NostrNotificationDataSource: NostrDataSource<Note>("GlobalFeed") {
|
||||
}
|
||||
|
||||
override fun feed(): List<Note> {
|
||||
return account.userProfile().taggedPosts
|
||||
.filter { it.event != null }
|
||||
.sortedBy { it.event!!.createdAt }
|
||||
.reversed()
|
||||
val set = account.userProfile().taggedPosts
|
||||
val filtered = synchronized(set) {
|
||||
set.filter { it.event != null }
|
||||
}
|
||||
|
||||
return filtered.sortedBy { it.event!!.createdAt }.reversed()
|
||||
}
|
||||
|
||||
override fun updateChannelFilters() {
|
||||
|
@ -9,9 +9,9 @@ import nostr.postr.JsonFilter
|
||||
import nostr.postr.events.TextNoteEvent
|
||||
|
||||
object NostrSingleEventDataSource: NostrDataSource<Note>("SingleEventFeed") {
|
||||
val eventsToWatch = Collections.synchronizedList(mutableListOf<String>())
|
||||
private var eventsToWatch = listOf<String>()
|
||||
|
||||
fun createRepliesAndReactionsFilter(): JsonFilter? {
|
||||
private fun createRepliesAndReactionsFilter(): JsonFilter? {
|
||||
val reactionsToWatch = eventsToWatch.map { it.substring(0, 8) }
|
||||
|
||||
if (reactionsToWatch.isEmpty()) {
|
||||
@ -65,12 +65,12 @@ object NostrSingleEventDataSource: NostrDataSource<Note>("SingleEventFeed") {
|
||||
}
|
||||
|
||||
fun add(eventId: String) {
|
||||
eventsToWatch.add(eventId)
|
||||
eventsToWatch = eventsToWatch.plus(eventId)
|
||||
resetFilters()
|
||||
}
|
||||
|
||||
fun remove(eventId: String) {
|
||||
eventsToWatch.remove(eventId)
|
||||
eventsToWatch = eventsToWatch.minus(eventId)
|
||||
resetFilters()
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ import nostr.postr.JsonFilter
|
||||
import nostr.postr.events.MetadataEvent
|
||||
|
||||
object NostrSingleUserDataSource: NostrDataSource<Note>("SingleUserFeed") {
|
||||
val usersToWatch = Collections.synchronizedList(mutableListOf<String>())
|
||||
var usersToWatch = listOf<String>()
|
||||
|
||||
fun createUserFilter(): JsonFilter? {
|
||||
if (usersToWatch.isEmpty()) return null
|
||||
@ -31,12 +31,12 @@ object NostrSingleUserDataSource: NostrDataSource<Note>("SingleUserFeed") {
|
||||
}
|
||||
|
||||
fun add(userId: String) {
|
||||
usersToWatch.add(userId)
|
||||
usersToWatch = usersToWatch.plus(userId)
|
||||
resetFilters()
|
||||
}
|
||||
|
||||
fun remove(userId: String) {
|
||||
usersToWatch.remove(userId)
|
||||
usersToWatch = usersToWatch.minus(userId)
|
||||
resetFilters()
|
||||
}
|
||||
}
|
@ -35,7 +35,11 @@ object NostrUserProfileDataSource: NostrDataSource<Note>("UserProfileFeed") {
|
||||
val notesChannel = requestNewChannel()
|
||||
|
||||
override fun feed(): List<Note> {
|
||||
return user?.notes?.sortedBy { it.event?.createdAt }?.reversed() ?: emptyList()
|
||||
val notes = user?.notes ?: return emptyList()
|
||||
val sortedNotes = synchronized(notes) {
|
||||
notes.sortedBy { it.event?.createdAt }
|
||||
}
|
||||
return sortedNotes.reversed()
|
||||
}
|
||||
|
||||
override fun updateChannelFilters() {
|
||||
|
@ -22,7 +22,11 @@ object NostrUserProfileFollowersDataSource: NostrDataSource<User>("UserProfileFo
|
||||
val followerChannel = requestNewChannel()
|
||||
|
||||
override fun feed(): List<User> {
|
||||
return user?.followers?.toList() ?: emptyList()
|
||||
val followers = user?.followers ?: emptyList()
|
||||
|
||||
return synchronized(followers) {
|
||||
followers.toList()
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateChannelFilters() {
|
||||
|
@ -21,7 +21,7 @@ class ChannelMuteUserEvent (
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val kind = 43
|
||||
const val kind = 44
|
||||
|
||||
fun create(reason: String, usersToMute: List<String>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelMuteUserEvent {
|
||||
val content = reason
|
||||
|
@ -23,9 +23,9 @@ object Client: RelayPool.Listener {
|
||||
* something.
|
||||
**/
|
||||
var lenient: Boolean = false
|
||||
private val listeners = Collections.synchronizedSet(HashSet<Listener>())
|
||||
internal var relays = Constants.defaultRelays
|
||||
internal val subscriptions = ConcurrentHashMap<String, MutableList<JsonFilter>>()
|
||||
private var listeners = setOf<Listener>()
|
||||
private var relays = Constants.defaultRelays
|
||||
private val subscriptions = mutableMapOf<String, List<JsonFilter>>()
|
||||
|
||||
fun connect(
|
||||
relays: Array<Relay> = Constants.defaultRelays
|
||||
@ -35,17 +35,9 @@ object Client: RelayPool.Listener {
|
||||
this.relays = relays
|
||||
}
|
||||
|
||||
fun requestAndWatch(
|
||||
subscriptionId: String = UUID.randomUUID().toString().substring(0..10),
|
||||
filters: MutableList<JsonFilter> = mutableListOf(JsonFilter())
|
||||
) {
|
||||
subscriptions[subscriptionId] = filters
|
||||
RelayPool.requestAndWatch()
|
||||
}
|
||||
|
||||
fun sendFilter(
|
||||
subscriptionId: String = UUID.randomUUID().toString().substring(0..10),
|
||||
filters: MutableList<JsonFilter> = mutableListOf(JsonFilter())
|
||||
filters: List<JsonFilter> = listOf(JsonFilter())
|
||||
) {
|
||||
subscriptions[subscriptionId] = filters
|
||||
RelayPool.sendFilter(subscriptionId)
|
||||
@ -53,10 +45,10 @@ object Client: RelayPool.Listener {
|
||||
|
||||
fun sendFilterOnlyIfDisconnected(
|
||||
subscriptionId: String = UUID.randomUUID().toString().substring(0..10),
|
||||
filters: MutableList<JsonFilter> = mutableListOf(JsonFilter())
|
||||
filters: List<JsonFilter> = listOf(JsonFilter())
|
||||
) {
|
||||
subscriptions[subscriptionId] = filters
|
||||
RelayPool.sendFilterOnlyIfDisconnected(subscriptionId)
|
||||
RelayPool.sendFilterOnlyIfDisconnected()
|
||||
}
|
||||
|
||||
fun send(signedEvent: Event) {
|
||||
@ -90,13 +82,22 @@ object Client: RelayPool.Listener {
|
||||
}
|
||||
|
||||
fun subscribe(listener: Listener) {
|
||||
listeners.add(listener)
|
||||
listeners = listeners.plus(listener)
|
||||
}
|
||||
|
||||
fun unsubscribe(listener: Listener): Boolean {
|
||||
return listeners.remove(listener)
|
||||
fun unsubscribe(listener: Listener) {
|
||||
listeners = listeners.minus(listener)
|
||||
}
|
||||
|
||||
fun allSubscriptions(): List<String> {
|
||||
return synchronized(subscriptions) {
|
||||
subscriptions.keys.toList()
|
||||
}
|
||||
}
|
||||
|
||||
fun getSubscriptionFilters(subId: String): List<JsonFilter> {
|
||||
return subscriptions[subId] ?: emptyList()
|
||||
}
|
||||
|
||||
abstract class Listener {
|
||||
/**
|
||||
|
@ -2,7 +2,6 @@ package com.vitorpamplona.amethyst.service.relays
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import java.util.Collections
|
||||
import nostr.postr.JsonFilter
|
||||
import nostr.postr.events.Event
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
@ -16,27 +15,29 @@ class Relay(
|
||||
var write: Boolean = true
|
||||
) {
|
||||
private val httpClient = OkHttpClient()
|
||||
private val listeners = Collections.synchronizedSet(HashSet<Listener>())
|
||||
private var listeners = setOf<Listener>()
|
||||
private var socket: WebSocket? = null
|
||||
|
||||
fun register(listener: Listener) {
|
||||
listeners.add(listener)
|
||||
listeners = listeners.plus(listener)
|
||||
}
|
||||
|
||||
fun unregister(listener: Listener) {
|
||||
listeners = listeners.minus(listener)
|
||||
}
|
||||
|
||||
fun isConnected(): Boolean {
|
||||
return socket != null
|
||||
}
|
||||
|
||||
fun unregister(listener: Listener) = listeners.remove(listener)
|
||||
|
||||
fun requestAndWatch(reconnectTs: Long? = null) {
|
||||
fun requestAndWatch() {
|
||||
val request = Request.Builder().url(url).build()
|
||||
val listener = object : WebSocketListener() {
|
||||
|
||||
override fun onOpen(webSocket: WebSocket, response: Response) {
|
||||
// Sends everything.
|
||||
Client.subscriptions.forEach {
|
||||
sendFilter(requestId = it.key, reconnectTs = reconnectTs)
|
||||
Client.allSubscriptions().forEach {
|
||||
sendFilter(requestId = it)
|
||||
}
|
||||
listeners.forEach { it.onRelayStateChange(this@Relay, Type.CONNECT) }
|
||||
}
|
||||
@ -101,28 +102,22 @@ class Relay(
|
||||
socket?.close(1000, "Normal close")
|
||||
}
|
||||
|
||||
fun sendFilter(requestId: String, reconnectTs: Long? = null) {
|
||||
fun sendFilter(requestId: String) {
|
||||
if (socket == null) {
|
||||
requestAndWatch(reconnectTs)
|
||||
requestAndWatch()
|
||||
} else {
|
||||
val filters = if (reconnectTs != null) {
|
||||
Client.subscriptions[requestId]?.let {
|
||||
it.map { filter ->
|
||||
JsonFilter(filter.ids, filter.authors, filter.kinds, filter.tags, since = reconnectTs)
|
||||
}
|
||||
} ?: error("No filter(s) found.")
|
||||
} else {
|
||||
Client.subscriptions[requestId] ?: error("No filter(s) found.")
|
||||
val filters = Client.getSubscriptionFilters(requestId)
|
||||
if (filters.isNotEmpty()) {
|
||||
val request = """["REQ","$requestId",${filters.joinToString(",") { it.toJson() }}]"""
|
||||
//println("FILTERSSENT " + """["REQ","$requestId",${filters.joinToString(",") { it.toJson() }}]""")
|
||||
socket!!.send(request)
|
||||
}
|
||||
val request = """["REQ","$requestId",${filters.joinToString(",") { it.toJson() }}]"""
|
||||
//println("FILTERSSENT " + """["REQ","$requestId",${filters.joinToString(",") { it.toJson() }}]""")
|
||||
socket!!.send(request)
|
||||
}
|
||||
}
|
||||
|
||||
fun sendFilterOnlyIfDisconnected(requestId: String, reconnectTs: Long? = null) {
|
||||
fun sendFilterOnlyIfDisconnected() {
|
||||
if (socket == null) {
|
||||
requestAndWatch(reconnectTs)
|
||||
requestAndWatch()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,8 @@ import nostr.postr.events.Event
|
||||
* RelayPool manages the connection to multiple Relays and lets consumers deal with simple events.
|
||||
*/
|
||||
object RelayPool: Relay.Listener {
|
||||
private val relays = Collections.synchronizedList(ArrayList<Relay>())
|
||||
private val listeners = Collections.synchronizedSet(HashSet<Listener>())
|
||||
private var relays = listOf<Relay>()
|
||||
private var listeners = setOf<Listener>()
|
||||
|
||||
fun availableRelays(): Int {
|
||||
return relays.size
|
||||
@ -29,7 +29,8 @@ object RelayPool: Relay.Listener {
|
||||
}
|
||||
|
||||
fun unloadRelays() {
|
||||
relays.toList().forEach { removeRelay(it) }
|
||||
relays.forEach { it.unregister(this) }
|
||||
relays = listOf()
|
||||
}
|
||||
|
||||
fun requestAndWatch() {
|
||||
@ -40,8 +41,8 @@ object RelayPool: Relay.Listener {
|
||||
relays.forEach { it.sendFilter(subscriptionId) }
|
||||
}
|
||||
|
||||
fun sendFilterOnlyIfDisconnected(subscriptionId: String) {
|
||||
relays.forEach { it.sendFilterOnlyIfDisconnected(subscriptionId) }
|
||||
fun sendFilterOnlyIfDisconnected() {
|
||||
relays.forEach { it.sendFilterOnlyIfDisconnected() }
|
||||
}
|
||||
|
||||
fun send(signedEvent: Event) {
|
||||
@ -61,19 +62,17 @@ object RelayPool: Relay.Listener {
|
||||
relays += relay
|
||||
}
|
||||
|
||||
fun removeRelay(relay: Relay): Boolean {
|
||||
fun removeRelay(relay: Relay) {
|
||||
relay.unregister(this)
|
||||
return relays.remove(relay)
|
||||
relays = relays.minus(relay)
|
||||
}
|
||||
|
||||
fun getRelays(): List<Relay> = relays
|
||||
|
||||
fun register(listener: Listener) {
|
||||
listeners.add(listener)
|
||||
listeners = listeners.plus(listener)
|
||||
}
|
||||
|
||||
fun unregister(listener: Listener): Boolean {
|
||||
return listeners.remove(listener)
|
||||
fun unregister(listener: Listener) {
|
||||
listeners = listeners.minus(listener)
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
|
@ -71,7 +71,7 @@ fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel)
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
Client.subscriptions.map { "${it.key} ${it.value.joinToString { it.toJson() }}" }.forEach {
|
||||
Client.allSubscriptions().map { "${it} ${Client.getSubscriptionFilters(it).joinToString { it.toJson() }}" }.forEach {
|
||||
Log.d("CURRENT FILTERS", it)
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ fun UserCompose(baseUser: User, accountViewModel: AccountViewModel, navControlle
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||
if (accountState?.account?.userProfile()?.follows?.contains(user) == true) {
|
||||
if (accountState?.account?.userProfile()?.isFollowing(user) == true) {
|
||||
UnfollowButton { accountState?.account?.unfollow(user) }
|
||||
} else {
|
||||
FollowButton { accountState?.account?.follow(user) }
|
||||
|
@ -143,7 +143,7 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
|
||||
if (accountUser == user) {
|
||||
EditButton()
|
||||
} else {
|
||||
if (accountUser.follows?.contains(user) == true) {
|
||||
if (accountUser.isFollowing(user) == true) {
|
||||
UnfollowButton { account.unfollow(user) }
|
||||
} else {
|
||||
FollowButton { account.follow(user) }
|
||||
|
Loading…
x
Reference in New Issue
Block a user