Added a map to show where other stn are // refactored message adding to tables for performance, max 30.000 msg now

This commit is contained in:
Marc Froehlich
2026-06-22 22:04:03 +02:00
committed by Rsclub2_2
parent 3160f9558d
commit ce73553b4f
52 changed files with 15153 additions and 60 deletions
@@ -23,7 +23,6 @@ import kst4contest.locatorUtils.DirectionUtils;
import kst4contest.logic.PriorityCalculator;
import kst4contest.model.*;
import kst4contest.test.MockKstServer;
import kst4contest.utils.BoundedDequeObservableList;
import kst4contest.utils.PlayAudioUtils;
import kst4contest.view.Kst4ContestApplication;
@@ -33,6 +32,8 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
*
* Central Chat kst4contest.controller. Instantiate only one time per category of kst Chat.
@@ -1094,8 +1095,7 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
// ******All abstract types below here are used by the messageprocessor!
// ***************
private static final int MAX_CHAT_MESSAGES = 10000;
private final BoundedDequeObservableList<ChatMessage> lst_globalChatMessageList = new BoundedDequeObservableList<>(MAX_CHAT_MESSAGES); //All chatmessages will be put in there, later create filtered message lists
private ObservableList<ChatMessage> lst_globalChatMessageList = FXCollections.observableArrayList(); //All chatmessages will be put in there, later create filtered message lists
// private ObservableList<ChatMessage> lst_toAllMessageList = FXCollections.observableArrayList(); // directed to all
// (beacon)
private FilteredList<ChatMessage> lst_toAllMessageList = new FilteredList<>(lst_globalChatMessageList); // directed to all
@@ -1129,6 +1129,27 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
private ObservableList<Predicate<ChatMember>> lst_chatMemberListFilterPredicates = FXCollections.observableArrayList();
private ObservableList<ClusterMessage> lst_clusterMemberList = FXCollections.observableArrayList();
/*
* Message table update buffers.
*
* Do not write directly to lst_globalChatMessageList from worker threads.
* Use publishChatMessage(...) instead.
*
* The actual ObservableList mutation is batched and executed on the JavaFX
* application thread. The visible list order remains newest-first.
*/
private final Object pendingChatMessagesLock = new Object();
private final List<ChatMessage> pendingChatMessages = new ArrayList<>();
private boolean chatMessageFlushScheduled = false;
/*
* Same idea for DXCluster messages.
*/
private final Object pendingClusterMessagesLock = new Object();
private final List<ClusterMessage> pendingClusterMessages = new ArrayList<>();
private boolean clusterMessageFlushScheduled = false;
private ObservableList<ChatMember> lst_DBBasedWkdCallSignList = FXCollections.observableArrayList();
// private HashMap<String, ChatMember> map_ucxLogInfoWorkedCalls = new HashMap<String, ChatMember>(); //Destination of ucx-log worked-messages
@@ -1240,14 +1261,152 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
this.lst_selectedCallSignInfofilteredMessageList = lst_selectedCallSignInfofilteredMessageList;
}
public void addChatMessage(ChatMessage message) {
lst_globalChatMessageList.addFirst(message);
}
public ObservableList<ChatMessage> getLst_globalChatMessageList() {
return lst_globalChatMessageList;
}
/**
* Adds a chat message to the UI message store.
*
* Important:
* - This method may be called from worker threads.
* - The ObservableList is modified only on the JavaFX application thread.
* - The backing list remains newest-first.
* - Old messages are trimmed to avoid unlimited memory growth.
*/
public void publishChatMessage(ChatMessage message) {
if (message == null) {
return;
}
synchronized (pendingChatMessagesLock) {
pendingChatMessages.add(message);
if (chatMessageFlushScheduled) {
return;
}
chatMessageFlushScheduled = true;
}
Platform.runLater(this::flushPendingChatMessagesToUi);
}
private void flushPendingChatMessagesToUi() {
List<ChatMessage> batch;
synchronized (pendingChatMessagesLock) {
batch = new ArrayList<>(pendingChatMessages);
pendingChatMessages.clear();
chatMessageFlushScheduled = false;
}
if (batch.isEmpty()) {
return;
}
/*
* pendingChatMessages is collected in arrival order:
* old -> new
*
* lst_globalChatMessageList must stay newest-first:
* new -> old
*/
Collections.reverse(batch);
lst_globalChatMessageList.addAll(0, batch);
trimGlobalChatMessageListIfNeeded();
}
private void trimGlobalChatMessageListIfNeeded() {
int maxSize = ApplicationConstants.CHAT_MESSAGE_STORE_MAX_SIZE;
int trimToSize = ApplicationConstants.CHAT_MESSAGE_STORE_TRIM_TO_SIZE;
if (maxSize <= 0 || trimToSize <= 0 || trimToSize >= maxSize) {
return;
}
int currentSize = lst_globalChatMessageList.size();
if (currentSize <= maxSize) {
return;
}
/*
* List order is newest-first.
* Therefore old messages are at the end of the list.
*/
lst_globalChatMessageList.remove(trimToSize, currentSize);
}
/**
* Adds a DXCluster message to the UI cluster message store.
*
* Same policy as for chat messages:
* - batched UI update
* - JavaFX thread only for ObservableList mutation
* - newest-first visible order
* - bounded list size
*/
public void publishClusterMessage(ClusterMessage message) {
if (message == null) {
return;
}
synchronized (pendingClusterMessagesLock) {
pendingClusterMessages.add(message);
if (clusterMessageFlushScheduled) {
return;
}
clusterMessageFlushScheduled = true;
}
Platform.runLater(this::flushPendingClusterMessagesToUi);
}
private void flushPendingClusterMessagesToUi() {
List<ClusterMessage> batch;
synchronized (pendingClusterMessagesLock) {
batch = new ArrayList<>(pendingClusterMessages);
pendingClusterMessages.clear();
clusterMessageFlushScheduled = false;
}
if (batch.isEmpty()) {
return;
}
Collections.reverse(batch);
lst_clusterMemberList.addAll(0, batch);
trimClusterMessageListIfNeeded();
}
private void trimClusterMessageListIfNeeded() {
int maxSize = ApplicationConstants.CLUSTER_MESSAGE_STORE_MAX_SIZE;
int trimToSize = ApplicationConstants.CLUSTER_MESSAGE_STORE_TRIM_TO_SIZE;
if (maxSize <= 0 || trimToSize <= 0 || trimToSize >= maxSize) {
return;
}
int currentSize = lst_clusterMemberList.size();
if (currentSize <= maxSize) {
return;
}
lst_clusterMemberList.remove(trimToSize, currentSize);
}
public void setLst_globalChatMessageList(ObservableList<ChatMessage> lst_globalChatMessageList) {
this.lst_globalChatMessageList = lst_globalChatMessageList;
}
public String getHostname() {
return hostname;
@@ -1482,6 +1641,7 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
}
private void initLst_toMeMessageList() {
// ObservableList<String> sniffedList = chatPreferences.getLstNotify_QSOSniffer_sniffedCallSignList();
@@ -1498,14 +1658,18 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
// --- NEUE LOGIK: Sniffer Liste prüfen ---
// Wenn Absender ODER Empfänger in der Beobachtungsliste stehen -> Anzeigen
if ((lstNotify_QSOSniffer_sniffedCallSignList.contains(senderCall) ||
lstNotify_QSOSniffer_sniffedCallSignList.contains(receiverCall)) &&
(!receiverCall.equals(this.getChatPreferences().getStn_loginCallSignRaw()))) {
// if ((lstNotify_QSOSniffer_sniffedCallSignList.contains(senderCall) ||
// lstNotify_QSOSniffer_sniffedCallSignList.contains(receiverCall)) &&
// (!receiverCall.equals(this.getChatPreferences().getStn_loginCallSignRaw()))) {
//
// msgText = ("Sniffed: " + "(" + senderCall + " > ") + receiverCall +") " + msgText;
// chatMessage.setMessageText(msgText);
// return true;
// }
msgText = ("Sniffed: " + "(" + senderCall + " > ") + receiverCall +") " + msgText;
chatMessage.setMessageText(msgText);
return true;
}
if (isSniffedMessage(chatMessage)) {
return true;
}
// --- BESTEHENDE LOGIK ---
@@ -2388,4 +2552,69 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
return DirectionUtils.isAngleInRange(targetAz, myAz, beamWidth);
}
/**
* decides if a message in the in-queue is directed to me or if its directed to another station and sniffed
* @param chatMessage
* @return
*/
public boolean isSniffedMessage(ChatMessage chatMessage) {
if (chatMessage == null || chatMessage.getSender() == null || chatMessage.getReceiver() == null) {
return false;
}
String senderCall = chatMessage.getSender().getCallSign();
String receiverCall = chatMessage.getReceiver().getCallSign();
if (senderCall == null || receiverCall == null) {
return false;
}
if (lstNotify_QSOSniffer_sniffedCallSignList == null || lstNotify_QSOSniffer_sniffedCallSignList.isEmpty()) {
return false;
}
boolean observedCall =
lstNotify_QSOSniffer_sniffedCallSignList.contains(senderCall)
|| lstNotify_QSOSniffer_sniffedCallSignList.contains(receiverCall);
if (!observedCall) {
return false;
}
String myCall = getChatPreferences() != null ? getChatPreferences().getStn_loginCallSign() : null;
String myRawCall = getChatPreferences() != null ? getChatPreferences().getStn_loginCallSignRaw() : null;
/*
* Sniffed messages should appear in the private table only if they are not
* already direct messages to my own callsign.
*/
return !receiverCall.equals(myCall) && !receiverCall.equals(myRawCall);
}
/**
* changes the chatmessage if it had been a sniffed one and not directed to me. Only for marking.
* @param chatMessage
* @return
*/
public String formatChatMessageTextForDisplay(ChatMessage chatMessage) {
if (chatMessage == null) {
return "";
}
String msgText = chatMessage.getMessageText();
if (msgText == null) {
msgText = "";
}
if (!isSniffedMessage(chatMessage)) {
return msgText;
}
String senderCall = chatMessage.getSender() != null ? chatMessage.getSender().getCallSign() : "";
String receiverCall = chatMessage.getReceiver() != null ? chatMessage.getReceiver().getCallSign() : "";
return "Sniffed: (" + senderCall + " > " + receiverCall + ") " + msgText;
}
}