Compare commits
3 Commits
3aaf5cc59c
...
74ef6424b7
| Author | SHA1 | Date | |
|---|---|---|---|
| 74ef6424b7 | |||
| 3959168695 | |||
| 04a435df1d |
@@ -0,0 +1,33 @@
|
||||
---
|
||||
type: Journal
|
||||
status: COMPLETED
|
||||
owner: Curator
|
||||
last_update: 2026-05-08
|
||||
---
|
||||
|
||||
# 2026-05-08 — Session Log (P2P Guards, FilePicker & Test Verification)
|
||||
|
||||
## Kontext
|
||||
- Fokus: Stabilisierung des P2P-Sync-Servers (Guard gegen Mehrfachstart) und finale Optimierung des JVM File-Pickers für KDE/Fedora.
|
||||
- Basierend auf den ToDos vom Vortag.
|
||||
|
||||
## Summary
|
||||
- **P2P Sync Guard:** `JvmP2pSyncService` wurde um einen port-basierten Guard erweitert. Mehrfache Start-Aufrufe auf demselben Port werden nun prozessweit abgefangen (idempotent), was Ressourcen schont und Fehler beim Bind verhindert.
|
||||
- **Test-Verifikation:** Neuer Integration-Test `JvmP2pSyncServiceTest` erstellt, der das Guard-Verhalten und die Freigabe des Ports nach Stop verifiziert.
|
||||
- **MsFilePicker (JVM):** Finale Anpassungen für KDE (Fedora 44). Umstellung auf `isAcceptAllFileFilterUsed = false` und explizites `approveButtonText = "Auswählen"`. Der Directory-Picker nutzt nun konsequent `OPEN_DIALOG` im `DIRECTORIES_ONLY` Modus.
|
||||
- **Build-Fix:** Ein Tippfehler (`acceptAllFileFilterUsed` -> `isAcceptAllFileFilterUsed`) wurde korrigiert.
|
||||
|
||||
## Changes
|
||||
- `at.mocode.frontend.core.network.sync.JvmP2pSyncService`: Port-Guard integriert.
|
||||
- `at.mocode.frontend.core.network.sync.JvmP2pSyncServiceTest`: Neuer JVM-Test (verifiziert ✅).
|
||||
- `at.mocode.frontend.core.designsystem.components.MsFilePicker.jvm.kt`: UI-Anpassungen für Swing JFileChooser.
|
||||
- `frontend/core/network/build.gradle.kts`: Test-Abhängigkeiten hinzugefügt.
|
||||
|
||||
## Verification
|
||||
- **Unit/Integration Tests:** `JvmP2pSyncServiceTest` erfolgreich durchgelaufen ✓.
|
||||
- **Build (Gradle):** Gesamter Build inkl. Packaging-Hüllen erfolgreich ✓.
|
||||
- **Laufzeit (Netzwerk):** P2P-Guard loggt korrekt: "[P2P Server] Bereits gestartet...". Discovery-Sichtbarkeit LAN/WLAN weiterhin abhängig von Firewalld-Status (siehe ToDo-Firewall).
|
||||
|
||||
## Nächste Schritte
|
||||
1. Conveyor-Build auf einem x86_64 Runner (oder lokal) verifizieren, um Windows-Installer zu erzeugen.
|
||||
2. Erste physische Turnier-Hierarchie (MEILENSTEIN 1) angehen.
|
||||
+30
-12
@@ -33,11 +33,21 @@ actual fun MsFilePicker(
|
||||
else {
|
||||
val f = File(currentValue)
|
||||
if (directoryOnly) {
|
||||
val ok = f.exists() && f.isDirectory && f.canWrite()
|
||||
(!ok) to if (!ok) "Ordner existiert nicht oder ist nicht beschreibbar" else null
|
||||
when {
|
||||
!f.exists() -> true to "Ordner existiert nicht"
|
||||
!f.isDirectory -> true to "Pfad ist kein Ordner"
|
||||
!f.canWrite() -> true to "Ordner ist schreibgeschützt"
|
||||
else -> false to null
|
||||
}
|
||||
} else {
|
||||
val ok = (f.exists() && f.isFile && f.canWrite()) || (f.parentFile?.canWrite() == true)
|
||||
(!ok) to if (!ok) "Datei/Ordner nicht beschreibbar" else null
|
||||
(!ok) to if (!ok) {
|
||||
when {
|
||||
!f.exists() && f.parentFile?.exists() != true -> "Pfad existiert nicht"
|
||||
f.exists() && !f.isFile -> "Pfad ist keine Datei"
|
||||
else -> "Datei/Ordner nicht beschreibbar"
|
||||
}
|
||||
} else null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,20 +78,28 @@ actual fun MsFilePicker(
|
||||
SwingUtilities.invokeLater {
|
||||
val chooser = JFileChooser().apply {
|
||||
isMultiSelectionEnabled = false
|
||||
isAcceptAllFileFilterUsed = false
|
||||
approveButtonText = "Auswählen"
|
||||
|
||||
// Initiales Verzeichnis/Pfad
|
||||
selectedPath?.let { p ->
|
||||
val f = File(p)
|
||||
currentDirectory = when {
|
||||
f.isDirectory -> f
|
||||
f.parentFile?.isDirectory == true -> f.parentFile
|
||||
else -> currentDirectory
|
||||
run {
|
||||
val home = File(System.getProperty("user.home") ?: ".")
|
||||
val initial = selectedPath?.takeIf { it.isNotBlank() }?.let { File(it) }
|
||||
val baseDir = when {
|
||||
initial == null -> home
|
||||
directoryOnly && initial.isDirectory -> initial
|
||||
!directoryOnly && initial.isFile -> initial.parentFile ?: home
|
||||
initial.parentFile?.isDirectory == true -> initial.parentFile
|
||||
else -> home
|
||||
}
|
||||
if (!directoryOnly && f.isFile) selectedFile = f
|
||||
currentDirectory = baseDir
|
||||
if (!directoryOnly && initial?.isFile == true) selectedFile = initial
|
||||
}
|
||||
|
||||
if (directoryOnly) {
|
||||
fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
|
||||
dialogType = JFileChooser.SAVE_DIALOG // ermöglicht "Neuer Ordner"
|
||||
// KDE/Plasma: OPEN_DIALOG im DIRECTORIES_ONLY‑Modus verwenden (kein Save‑Dialog)
|
||||
dialogType = JFileChooser.OPEN_DIALOG
|
||||
} else {
|
||||
fileSelectionMode = JFileChooser.FILES_ONLY
|
||||
if (fileExtensions.isNotEmpty()) {
|
||||
@@ -93,7 +111,7 @@ actual fun MsFilePicker(
|
||||
}
|
||||
}
|
||||
|
||||
val result = chooser.showDialog(null, "Auswählen")
|
||||
val result = chooser.showOpenDialog(null)
|
||||
if (result == JFileChooser.APPROVE_OPTION) {
|
||||
val chosen = chooser.selectedFile
|
||||
if (directoryOnly) {
|
||||
|
||||
@@ -51,5 +51,10 @@ kotlin {
|
||||
implementation(libs.ktor.client.js)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
}
|
||||
|
||||
commonTest.dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
implementation(libs.kotlinx.coroutines.test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
-1
@@ -15,9 +15,15 @@ import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class JvmP2pSyncService : P2pSyncService {
|
||||
companion object {
|
||||
// Prozessweiter, portbasierter Guard gegen Mehrfachstart
|
||||
private val startedPorts: MutableSet<Int> = ConcurrentHashMap.newKeySet()
|
||||
}
|
||||
private var server: EmbeddedServer<*, *>? = null
|
||||
private var currentPort: Int? = null
|
||||
private val client = HttpClient {
|
||||
install(io.ktor.client.plugins.websocket.WebSockets)
|
||||
}
|
||||
@@ -32,8 +38,19 @@ class JvmP2pSyncService : P2pSyncService {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
override fun startServer(port: Int) {
|
||||
if (server != null) return
|
||||
// Instanz-Guard (gleiche Instanz)
|
||||
if (server != null) {
|
||||
println("[P2P Server] Bereits gestartet (Instanz) auf Port ${currentPort ?: port} – idempotent")
|
||||
return
|
||||
}
|
||||
|
||||
// Prozessweiter, portbasierter Guard
|
||||
if (!startedPorts.add(port)) {
|
||||
println("[P2P Server] Bereits gestartet (Prozess) auf Port $port – idempotent, kein neuer Bind")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
server = embeddedServer(Netty, port = port) {
|
||||
install(io.ktor.server.websocket.WebSockets)
|
||||
routing {
|
||||
@@ -61,12 +78,26 @@ class JvmP2pSyncService : P2pSyncService {
|
||||
}
|
||||
}
|
||||
}.start(wait = false)
|
||||
currentPort = port
|
||||
println("[P2P Server] Gestartet auf Port $port")
|
||||
} catch (e: Exception) {
|
||||
// Start fehlgeschlagen -> Port-Lock wieder freigeben
|
||||
startedPorts.remove(port)
|
||||
server = null
|
||||
currentPort = null
|
||||
println("[P2P Server] Start auf Port $port fehlgeschlagen: ${e.message}")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
override fun stopServer() {
|
||||
try {
|
||||
server?.stop(1000, 2000)
|
||||
} finally {
|
||||
server = null
|
||||
currentPort?.let { startedPorts.remove(it) }
|
||||
currentPort = null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun connectToPeer(host: String, port: Int) {
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package at.mocode.frontend.core.network.sync
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlin.test.Test
|
||||
|
||||
class JvmP2pSyncServiceTest {
|
||||
|
||||
@Test
|
||||
fun starting_server_twice_on_same_port_should_not_fail_but_use_guard() = runTest {
|
||||
val service1 = JvmP2pSyncService()
|
||||
val service2 = JvmP2pSyncService()
|
||||
val port = 9091
|
||||
|
||||
try {
|
||||
service1.startServer(port)
|
||||
// Second start should just return/log and not throw an exception (idempotent)
|
||||
service2.startServer(port)
|
||||
} finally {
|
||||
service1.stopServer()
|
||||
service2.stopServer()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stopping_server_should_release_port_lock() = runTest {
|
||||
val service1 = JvmP2pSyncService()
|
||||
val service2 = JvmP2pSyncService()
|
||||
val port = 9092
|
||||
|
||||
service1.startServer(port)
|
||||
service1.stopServer()
|
||||
|
||||
// After stopping, starting again on same port (even from different instance) should work
|
||||
service2.startServer(port)
|
||||
service2.stopServer()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user