feat(core, device-initialization): Netzwerk-Discovery verbessert, IP-Binding hinzugefügt und UI optimiert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
+27
-25
@@ -6,10 +6,10 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
* Modell für einen entdeckten Dienst im lokalen Netzwerk.
|
||||
*/
|
||||
data class DiscoveredService(
|
||||
val name: String,
|
||||
val host: String,
|
||||
val port: Int,
|
||||
val metadata: Map<String, String> = emptyMap()
|
||||
val name: String,
|
||||
val host: String,
|
||||
val port: Int,
|
||||
val metadata: Map<String, String> = emptyMap()
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -17,30 +17,32 @@ data class DiscoveredService(
|
||||
* Erlaubt Offline-First Synchronisation im LAN.
|
||||
*/
|
||||
interface NetworkDiscoveryService {
|
||||
/**
|
||||
* Ein StateFlow, der die aktuell entdeckten Dienste enthält.
|
||||
* Ideal für reaktive UIs (Compose).
|
||||
*/
|
||||
val discoveredServices: StateFlow<List<DiscoveredService>>
|
||||
/**
|
||||
* Ein StateFlow, der die aktuell entdeckten Dienste enthält.
|
||||
* Ideal für reaktive UIs (Compose).
|
||||
*/
|
||||
val discoveredServices: StateFlow<List<DiscoveredService>>
|
||||
|
||||
/**
|
||||
* Startet das Scannen nach verfügbaren Diensten im Netzwerk.
|
||||
*/
|
||||
fun startDiscovery()
|
||||
* Startet das Scannen nach verfügbaren Diensten im Netzwerk.
|
||||
* @param preferredIp Optional eine IP-Adresse, an die der Discovery-Dienst gebunden werden soll.
|
||||
*/
|
||||
fun startDiscovery(preferredIp: String? = null)
|
||||
|
||||
/**
|
||||
* Stoppt den Scan-Vorgang.
|
||||
*/
|
||||
fun stopDiscovery()
|
||||
/**
|
||||
* Stoppt den Scan-Vorgang.
|
||||
*/
|
||||
fun stopDiscovery()
|
||||
|
||||
/**
|
||||
* Registriert den eigenen Dienst, damit andere Instanzen ihn finden können.
|
||||
* @param port Der Port, auf dem der lokale WebSocket-Server lauscht.
|
||||
*/
|
||||
fun registerService(port: Int)
|
||||
/**
|
||||
* Registriert den eigenen Dienst, damit andere Instanzen ihn finden können.
|
||||
* @param port Der Port, auf dem der lokale WebSocket-Server lauscht.
|
||||
* @param preferredIp Optional eine IP-Adresse, an die der Discovery-Dienst gebunden werden soll.
|
||||
*/
|
||||
fun registerService(port: Int, preferredIp: String? = null)
|
||||
|
||||
/**
|
||||
* Gibt die Liste der aktuell entdeckten Dienste zurück (Snapshot).
|
||||
*/
|
||||
fun getDiscoveredServices(): List<DiscoveredService>
|
||||
/**
|
||||
* Gibt die Liste der aktuell entdeckten Dienste zurück (Snapshot).
|
||||
*/
|
||||
fun getDiscoveredServices(): List<DiscoveredService>
|
||||
}
|
||||
|
||||
+35
-35
@@ -9,49 +9,49 @@ import kotlin.time.Duration.Companion.milliseconds
|
||||
* Er lauscht auf neu entdeckte Dienste und baut automatisch Verbindungen auf.
|
||||
*/
|
||||
class SyncManager(
|
||||
private val discoveryService: NetworkDiscoveryService,
|
||||
private val syncService: P2pSyncService
|
||||
private val discoveryService: NetworkDiscoveryService,
|
||||
private val syncService: P2pSyncService
|
||||
) {
|
||||
private val scope = CoroutineScope(SupervisorJob())
|
||||
private val knownPeers = mutableSetOf<String>()
|
||||
private val scope = CoroutineScope(SupervisorJob())
|
||||
private val knownPeers = mutableSetOf<String>()
|
||||
|
||||
fun start(port: Int) {
|
||||
// Eigenen Dienst registrieren und Server starten
|
||||
discoveryService.registerService(port)
|
||||
syncService.startServer(port)
|
||||
discoveryService.startDiscovery()
|
||||
fun start(port: Int, preferredIp: String? = null) {
|
||||
// Eigenen Dienst registrieren und Server starten
|
||||
discoveryService.registerService(port, preferredIp)
|
||||
syncService.startServer(port)
|
||||
discoveryService.startDiscovery(preferredIp)
|
||||
|
||||
// Regelmäßig nach neuen Peers suchen und verbinden
|
||||
scope.launch {
|
||||
while (isActive) {
|
||||
val discovered = discoveryService.getDiscoveredServices()
|
||||
discovered.forEach { service ->
|
||||
val peerKey = "${service.host}:${service.port}"
|
||||
if (!knownPeers.contains(peerKey)) {
|
||||
// TODO: Node-ID Vergleich (Selbst-Verbindung vermeiden)
|
||||
println("[SyncManager] Neuer Peer entdeckt: $peerKey. Verbinde...")
|
||||
syncService.connectToPeer(service.host, service.port)
|
||||
knownPeers.add(peerKey)
|
||||
}
|
||||
}
|
||||
delay(5000.milliseconds) // Alle 5 Sekunden prüfen
|
||||
}
|
||||
// Regelmäßig nach neuen Peers suchen und verbinden
|
||||
scope.launch {
|
||||
while (isActive) {
|
||||
val discovered = discoveryService.getDiscoveredServices()
|
||||
discovered.forEach { service ->
|
||||
val peerKey = "${service.host}:${service.port}"
|
||||
if (!knownPeers.contains(peerKey)) {
|
||||
// TODO: Node-ID Vergleich (Selbst-Verbindung vermeiden)
|
||||
println("[SyncManager] Neuer Peer entdeckt: $peerKey. Verbinde...")
|
||||
syncService.connectToPeer(service.host, service.port)
|
||||
knownPeers.add(peerKey)
|
||||
}
|
||||
}
|
||||
delay(5000.milliseconds) // Alle 5 Sekunden prüfen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getConnectedPeers() = syncService.connectedPeers
|
||||
fun getConnectedPeers() = syncService.connectedPeers
|
||||
|
||||
fun broadcastEvent(event: SyncEvent) {
|
||||
scope.launch {
|
||||
syncService.broadcastEvent(event)
|
||||
}
|
||||
fun broadcastEvent(event: SyncEvent) {
|
||||
scope.launch {
|
||||
syncService.broadcastEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
fun getIncomingEvents() = syncService.incomingEvents
|
||||
fun getIncomingEvents() = syncService.incomingEvents
|
||||
|
||||
fun stop() {
|
||||
scope.cancel()
|
||||
discoveryService.stopDiscovery()
|
||||
syncService.stopServer()
|
||||
}
|
||||
fun stop() {
|
||||
scope.cancel()
|
||||
discoveryService.stopDiscovery()
|
||||
syncService.stopServer()
|
||||
}
|
||||
}
|
||||
|
||||
+56
-49
@@ -15,64 +15,71 @@ import javax.jmdns.ServiceListener
|
||||
*/
|
||||
class JmDnsDiscoveryService : NetworkDiscoveryService {
|
||||
|
||||
private var jmdns: JmDNS? = null
|
||||
private var jmdns: JmDNS? = null
|
||||
private val SERVICE_TYPE = "_meldestelle._tcp.local."
|
||||
private val discoveredServicesMap = ConcurrentHashMap<String, DiscoveredService>()
|
||||
private val discoveredServicesMap = ConcurrentHashMap<String, DiscoveredService>()
|
||||
|
||||
private val _discoveredServices = MutableStateFlow<List<DiscoveredService>>(emptyList())
|
||||
override val discoveredServices: StateFlow<List<DiscoveredService>> = _discoveredServices.asStateFlow()
|
||||
|
||||
override fun startDiscovery() {
|
||||
if (jmdns == null) {
|
||||
jmdns = JmDNS.create(InetAddress.getLocalHost())
|
||||
}
|
||||
|
||||
jmdns?.addServiceListener(SERVICE_TYPE, object : ServiceListener {
|
||||
override fun serviceAdded(event: ServiceEvent) {
|
||||
// Bei ServiceAdded fordern wir die Details an
|
||||
jmdns?.requestServiceInfo(event.type, event.name)
|
||||
}
|
||||
|
||||
override fun serviceRemoved(event: ServiceEvent) {
|
||||
discoveredServicesMap.remove(event.name)
|
||||
_discoveredServices.value = discoveredServicesMap.values.toList()
|
||||
println("[Discovery] Service entfernt: ${event.name}")
|
||||
}
|
||||
|
||||
override fun serviceResolved(event: ServiceEvent) {
|
||||
val info = event.info
|
||||
val service = DiscoveredService(
|
||||
name = event.name,
|
||||
host = info.inetAddresses.firstOrNull()?.hostAddress ?: "unknown",
|
||||
port = info.port,
|
||||
metadata = info.propertyNames.asSequence().associateWith { info.getPropertyString(it) }
|
||||
)
|
||||
discoveredServicesMap[event.name] = service
|
||||
_discoveredServices.value = discoveredServicesMap.values.toList()
|
||||
println("[Discovery] Service gefunden: ${service.name} @ ${service.host}:${service.port}")
|
||||
}
|
||||
})
|
||||
override fun startDiscovery(preferredIp: String?) {
|
||||
if (jmdns == null) {
|
||||
val addr = preferredIp?.let { InetAddress.getByName(it) } ?: InetAddress.getLocalHost()
|
||||
println("[Discovery] Starte Discovery gebunden an: $addr")
|
||||
jmdns = JmDNS.create(addr)
|
||||
}
|
||||
|
||||
override fun stopDiscovery() {
|
||||
jmdns?.close()
|
||||
jmdns = null
|
||||
discoveredServicesMap.clear()
|
||||
_discoveredServices.value = emptyList()
|
||||
}
|
||||
jmdns?.addServiceListener(SERVICE_TYPE, object : ServiceListener {
|
||||
override fun serviceAdded(event: ServiceEvent) {
|
||||
// Bei ServiceAdded fordern wir die Details an
|
||||
jmdns?.requestServiceInfo(event.type, event.name)
|
||||
}
|
||||
|
||||
override fun registerService(port: Int) {
|
||||
val serviceInfo = ServiceInfo.create(
|
||||
SERVICE_TYPE,
|
||||
"Meldestelle-${System.getProperty("user.name")}",
|
||||
port,
|
||||
"Offline-First Sync Node"
|
||||
override fun serviceRemoved(event: ServiceEvent) {
|
||||
discoveredServicesMap.remove(event.name)
|
||||
_discoveredServices.value = discoveredServicesMap.values.toList()
|
||||
println("[Discovery] Service entfernt: ${event.name}")
|
||||
}
|
||||
|
||||
override fun serviceResolved(event: ServiceEvent) {
|
||||
val info = event.info
|
||||
val service = DiscoveredService(
|
||||
name = event.name,
|
||||
host = info.inetAddresses.firstOrNull()?.hostAddress ?: "unknown",
|
||||
port = info.port,
|
||||
metadata = info.propertyNames.asSequence().associateWith { info.getPropertyString(it) }
|
||||
)
|
||||
jmdns?.registerService(serviceInfo)
|
||||
println("[Discovery] Eigenen Dienst registriert auf Port $port")
|
||||
}
|
||||
discoveredServicesMap[event.name] = service
|
||||
_discoveredServices.value = discoveredServicesMap.values.toList()
|
||||
println("[Discovery] Service gefunden: ${service.name} @ ${service.host}:${service.port}")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun getDiscoveredServices(): List<DiscoveredService> {
|
||||
return discoveredServicesMap.values.toList()
|
||||
override fun stopDiscovery() {
|
||||
jmdns?.close()
|
||||
jmdns = null
|
||||
discoveredServicesMap.clear()
|
||||
_discoveredServices.value = emptyList()
|
||||
}
|
||||
|
||||
override fun registerService(port: Int, preferredIp: String?) {
|
||||
if (jmdns == null) {
|
||||
val addr = preferredIp?.let { InetAddress.getByName(it) } ?: InetAddress.getLocalHost()
|
||||
println("[Discovery] Registriere Dienst gebunden an: $addr")
|
||||
jmdns = JmDNS.create(addr)
|
||||
}
|
||||
val serviceInfo = ServiceInfo.create(
|
||||
SERVICE_TYPE,
|
||||
"Meldestelle-${System.getProperty("user.name")}",
|
||||
port,
|
||||
"Offline-First Sync Node"
|
||||
)
|
||||
jmdns?.registerService(serviceInfo)
|
||||
println("[Discovery] Eigenen Dienst registriert auf Port $port")
|
||||
}
|
||||
|
||||
override fun getDiscoveredServices(): List<DiscoveredService> {
|
||||
return discoveredServicesMap.values.toList()
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -10,14 +10,15 @@ import org.koin.dsl.module
|
||||
* Wasm-spezifische Implementierung (vorerst No-op).
|
||||
*/
|
||||
actual val discoveryModule: Module = module {
|
||||
single<NetworkDiscoveryService> { NoOpDiscoveryService() }
|
||||
single<NetworkDiscoveryService> { NoOpDiscoveryService() }
|
||||
}
|
||||
|
||||
class NoOpDiscoveryService : NetworkDiscoveryService {
|
||||
override val discoveredServices: StateFlow<List<DiscoveredService>> =
|
||||
MutableStateFlow<List<DiscoveredService>>(emptyList()).asStateFlow()
|
||||
override fun startDiscovery() {}
|
||||
override fun stopDiscovery() {}
|
||||
override fun registerService(port: Int) {}
|
||||
override fun getDiscoveredServices(): List<DiscoveredService> = emptyList()
|
||||
|
||||
override fun startDiscovery(preferredIp: String?) {}
|
||||
override fun stopDiscovery() {}
|
||||
override fun registerService(port: Int, preferredIp: String?) {}
|
||||
override fun getDiscoveredServices(): List<DiscoveredService> = emptyList()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user