mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 00:25:19 +00:00
Reuse cached mention regex in parseMentions (#812)
This commit is contained in:
@@ -33,13 +33,32 @@ final class GeoRelayDirectory {
|
||||
|
||||
/// Returns up to `count` relay URLs (wss://) closest to the given coordinate.
|
||||
func closestRelays(toLat lat: Double, lon: Double, count: Int = 5) -> [String] {
|
||||
guard !entries.isEmpty else { return [] }
|
||||
let sorted = entries
|
||||
.sorted { a, b in
|
||||
haversineKm(lat, lon, a.lat, a.lon) < haversineKm(lat, lon, b.lat, b.lon)
|
||||
guard !entries.isEmpty, count > 0 else { return [] }
|
||||
|
||||
if entries.count <= count {
|
||||
return entries
|
||||
.sorted { a, b in
|
||||
haversineKm(lat, lon, a.lat, a.lon) < haversineKm(lat, lon, b.lat, b.lon)
|
||||
}
|
||||
.map { "wss://\($0.host)" }
|
||||
}
|
||||
|
||||
var best: [(entry: Entry, distance: Double)] = []
|
||||
best.reserveCapacity(count)
|
||||
|
||||
for entry in entries {
|
||||
let distance = haversineKm(lat, lon, entry.lat, entry.lon)
|
||||
if best.count < count {
|
||||
let idx = best.firstIndex { $0.distance > distance } ?? best.count
|
||||
best.insert((entry, distance), at: idx)
|
||||
} else if let worstDistance = best.last?.distance, distance < worstDistance {
|
||||
let idx = best.firstIndex { $0.distance > distance } ?? best.count
|
||||
best.insert((entry, distance), at: idx)
|
||||
best.removeLast()
|
||||
}
|
||||
.prefix(count)
|
||||
return sorted.map { "wss://\($0.host)" }
|
||||
}
|
||||
|
||||
return best.map { "wss://\($0.entry.host)" }
|
||||
}
|
||||
|
||||
// MARK: - Remote Fetch
|
||||
|
||||
@@ -197,20 +197,37 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
// Persistent recent content map (LRU) to speed near-duplicate checks
|
||||
private var contentLRUMap: [String: Date] = [:]
|
||||
private var contentLRUOrder: [String] = []
|
||||
private var contentLRUHead = 0
|
||||
private let contentLRUCap = TransportConfig.contentLRUCap
|
||||
private func recordContentKey(_ key: String, timestamp: Date) {
|
||||
if contentLRUMap[key] == nil { contentLRUOrder.append(key) }
|
||||
contentLRUMap[key] = timestamp
|
||||
if contentLRUOrder.count > contentLRUCap {
|
||||
let overflow = contentLRUOrder.count - contentLRUCap
|
||||
for _ in 0..<overflow {
|
||||
if let victim = contentLRUOrder.first {
|
||||
contentLRUOrder.removeFirst()
|
||||
contentLRUMap.removeValue(forKey: victim)
|
||||
}
|
||||
}
|
||||
trimContentLRUIfNeeded()
|
||||
}
|
||||
|
||||
private func trimContentLRUIfNeeded() {
|
||||
let activeCount = contentLRUOrder.count - contentLRUHead
|
||||
guard activeCount > contentLRUCap else { return }
|
||||
|
||||
let overflow = activeCount - contentLRUCap
|
||||
for _ in 0..<overflow {
|
||||
guard let victim = popOldestContentKey() else { break }
|
||||
contentLRUMap.removeValue(forKey: victim)
|
||||
}
|
||||
}
|
||||
|
||||
private func popOldestContentKey() -> String? {
|
||||
guard contentLRUHead < contentLRUOrder.count else { return nil }
|
||||
let victim = contentLRUOrder[contentLRUHead]
|
||||
contentLRUHead += 1
|
||||
|
||||
// Periodically compact the backing storage to avoid unbounded growth.
|
||||
if contentLRUHead >= 32 && contentLRUHead * 2 >= contentLRUOrder.count {
|
||||
contentLRUOrder.removeFirst(contentLRUHead)
|
||||
contentLRUHead = 0
|
||||
}
|
||||
return victim
|
||||
}
|
||||
// MARK: - Published Properties
|
||||
|
||||
@Published var messages: [BitchatMessage] = []
|
||||
@@ -348,6 +365,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
// PeerManager replaced by UnifiedPeerService
|
||||
private var processedNostrEvents = Set<String>() // Simple deduplication
|
||||
private var processedNostrEventOrder: [String] = []
|
||||
private var processedNostrEventHead = 0
|
||||
private let maxProcessedNostrEvents = TransportConfig.uiProcessedNostrEventsCap
|
||||
private let userDefaults = UserDefaults.standard
|
||||
private let keychain: KeychainManagerProtocol
|
||||
@@ -2168,16 +2186,31 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
private func recordProcessedEvent(_ id: String) {
|
||||
processedNostrEvents.insert(id)
|
||||
processedNostrEventOrder.append(id)
|
||||
if processedNostrEventOrder.count > maxProcessedNostrEvents {
|
||||
let overflow = processedNostrEventOrder.count - maxProcessedNostrEvents
|
||||
for _ in 0..<overflow {
|
||||
if let old = processedNostrEventOrder.first {
|
||||
processedNostrEventOrder.removeFirst()
|
||||
processedNostrEvents.remove(old)
|
||||
}
|
||||
}
|
||||
trimProcessedNostrEventsIfNeeded()
|
||||
}
|
||||
|
||||
private func trimProcessedNostrEventsIfNeeded() {
|
||||
let activeCount = processedNostrEventOrder.count - processedNostrEventHead
|
||||
guard activeCount > maxProcessedNostrEvents else { return }
|
||||
|
||||
let overflow = activeCount - maxProcessedNostrEvents
|
||||
for _ in 0..<overflow {
|
||||
guard let old = popOldestProcessedEvent() else { break }
|
||||
processedNostrEvents.remove(old)
|
||||
}
|
||||
}
|
||||
|
||||
private func popOldestProcessedEvent() -> String? {
|
||||
guard processedNostrEventHead < processedNostrEventOrder.count else { return nil }
|
||||
let value = processedNostrEventOrder[processedNostrEventHead]
|
||||
processedNostrEventHead += 1
|
||||
|
||||
if processedNostrEventHead >= 32 && processedNostrEventHead * 2 >= processedNostrEventOrder.count {
|
||||
processedNostrEventOrder.removeFirst(processedNostrEventHead)
|
||||
processedNostrEventHead = 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
/// Sends an encrypted private message to a specific peer.
|
||||
/// - Parameters:
|
||||
@@ -4826,11 +4859,10 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
|
||||
private func parseMentions(from content: String) -> [String] {
|
||||
// Allow optional disambiguation suffix '#abcd' for duplicate nicknames
|
||||
let pattern = "@([\\p{L}0-9_]+(?:#[a-fA-F0-9]{4})?)"
|
||||
let regex = try? NSRegularExpression(pattern: pattern, options: [])
|
||||
let regex = Regexes.mention
|
||||
let nsContent = content as NSString
|
||||
let nsLen = nsContent.length
|
||||
let matches = regex?.matches(in: content, options: [], range: NSRange(location: 0, length: nsLen)) ?? []
|
||||
let matches = regex.matches(in: content, options: [], range: NSRange(location: 0, length: nsLen))
|
||||
|
||||
var mentions: [String] = []
|
||||
let peerNicknames = meshService.getPeerNicknames()
|
||||
|
||||
Reference in New Issue
Block a user