mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-08-23 18:47:34 +02:00
fix(chat): separate active members by full callsign and category
This commit is contained in:
@@ -1175,27 +1175,45 @@ private ObservableList<String>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the active-member key. The raw/base callsign alone is not enough
|
* Builds the unique identity key for one active ON4KST login.
|
||||||
* because the same station can be logged into multiple ON4KST categories at
|
*
|
||||||
* the same time. Therefore the category number is part of the key.
|
* <p>The complete callsign must be preserved here. Callsigns such as
|
||||||
|
* {@code 9A0BB-2}, {@code 9A0BB-70} and {@code 9A0BB-144} represent different
|
||||||
|
* active chat sessions and must therefore remain separate objects, even when
|
||||||
|
* they are logged into the same chat category.</p>
|
||||||
|
*
|
||||||
|
* <p>The normalized base callsign ({@code callSignRaw}) is deliberately not
|
||||||
|
* used for this key. It remains the common station key for worked, band and
|
||||||
|
* NOT-QRV information.</p>
|
||||||
*/
|
*/
|
||||||
private String buildActiveChatMemberKey(String callSign, ChatCategory category) {
|
private String buildActiveChatMemberKey(String callSign, ChatCategory category) {
|
||||||
String normalizedCallsign = ChatMember.normalizeCallSignToBaseCallSign(callSign);
|
|
||||||
if (normalizedCallsign == null || normalizedCallsign.isBlank()) {
|
if (callSign == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
int categoryNumber = category == null ? -1 : category.getCategoryNumber();
|
String fullCallSign = callSign.trim().toUpperCase(Locale.ROOT);
|
||||||
return normalizedCallsign.trim().toUpperCase(Locale.ROOT) + "|" + categoryNumber;
|
|
||||||
|
if (fullCallSign.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int categoryNumber =
|
||||||
|
category == null ? -1 : category.getCategoryNumber();
|
||||||
|
|
||||||
|
return fullCallSign + "|" + categoryNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildActiveChatMemberKey(ChatMember member) {
|
private String buildActiveChatMemberKey(ChatMember member) {
|
||||||
|
|
||||||
if (member == null) {
|
if (member == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String callSign = member.getCallSignRaw() != null ? member.getCallSignRaw() : member.getCallSign();
|
return buildActiveChatMemberKey(
|
||||||
return buildActiveChatMemberKey(callSign, member.getChatCategory());
|
member.getCallSign(),
|
||||||
|
member.getChatCategory()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -602,76 +602,129 @@ public class MessageBusManagementThread extends Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves a message sender from the thread-safe active-member model. If a
|
* Resolves a message sender from the thread-safe active-member model.
|
||||||
* CH/CR message arrives before the matching user-enter message, a marked
|
*
|
||||||
* fallback sender is used. The returned sender is never null.
|
* <p>The local login is handled before the active-user lookup because the own
|
||||||
|
* ChatMember is intentionally not stored in the visible user list. This also
|
||||||
|
* prevents another active login with the same base callsign from replacing the
|
||||||
|
* identity of our own message echo.</p>
|
||||||
*/
|
*/
|
||||||
private ChatMember resolveInboundSender(String senderCallSign, ChatCategory category, ChatMessage message) {
|
private ChatMember resolveInboundSender(
|
||||||
|
String senderCallSign,
|
||||||
|
ChatCategory category,
|
||||||
|
ChatMessage message
|
||||||
|
) {
|
||||||
|
|
||||||
|
String myCall =
|
||||||
|
this.client.getChatPreferences().getStn_loginCallSign();
|
||||||
|
|
||||||
|
if (senderCallSign != null
|
||||||
|
&& myCall != null
|
||||||
|
&& senderCallSign.equalsIgnoreCase(myCall)) {
|
||||||
|
|
||||||
|
ChatMember ownSender = new ChatMember();
|
||||||
|
ownSender.setCallSign(senderCallSign);
|
||||||
|
ownSender.setChatCategory(category);
|
||||||
|
ownSender.setAirPlaneReflectInfo(
|
||||||
|
new AirPlaneReflectionInfo()
|
||||||
|
);
|
||||||
|
|
||||||
|
return ownSender;
|
||||||
|
}
|
||||||
|
|
||||||
ChatMember lookup = new ChatMember();
|
ChatMember lookup = new ChatMember();
|
||||||
lookup.setCallSign(senderCallSign);
|
lookup.setCallSign(senderCallSign);
|
||||||
lookup.setChatCategory(category);
|
lookup.setChatCategory(category);
|
||||||
|
|
||||||
ChatMember senderObj = this.client.findActiveChatMember(lookup);
|
ChatMember senderObj =
|
||||||
|
this.client.findActiveChatMember(lookup);
|
||||||
|
|
||||||
if (senderObj != null) {
|
if (senderObj != null) {
|
||||||
senderObj.setActivityTimeLastInEpoch(new Utils4KST().time_generateCurrentEpochTime());
|
senderObj.setActivityTimeLastInEpoch(
|
||||||
|
new Utils4KST().time_generateCurrentEpochTime()
|
||||||
|
);
|
||||||
|
|
||||||
// Remember the last active category so later outgoing replies can be routed correctly.
|
this.client.rememberLastInboundCategory(
|
||||||
this.client.rememberLastInboundCategory(senderObj.getCallSignRaw(), senderObj.getChatCategory());
|
senderObj.getCallSignRaw(),
|
||||||
|
senderObj.getChatCategory()
|
||||||
|
);
|
||||||
|
|
||||||
// Metrics influence priority scoring; process them after message text is known.
|
|
||||||
this.client.getStationMetricsService().onInboundMessage(
|
this.client.getStationMetricsService().onInboundMessage(
|
||||||
senderObj.getCallSignRaw(),
|
senderObj.getCallSignRaw(),
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
message == null ? null : message.getMessageText(),
|
message == null ? null : message.getMessageText(),
|
||||||
this.client.getChatPreferences(),
|
this.client.getChatPreferences(),
|
||||||
this.client.getChatPreferences().getStn_loginCallSign()
|
myCall
|
||||||
|
);
|
||||||
|
|
||||||
|
this.client.getScoreService().requestRecompute(
|
||||||
|
"rx-chat-message"
|
||||||
);
|
);
|
||||||
|
|
||||||
this.client.getScoreService().requestRecompute("rx-chat-message");
|
|
||||||
return senderObj;
|
return senderObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatMember fallbackSender = new ChatMember();
|
ChatMember fallbackSender = new ChatMember();
|
||||||
String myCall = this.client.getChatPreferences().getStn_loginCallSign();
|
fallbackSender.setCallSign("[n/a]" + senderCallSign);
|
||||||
if (senderCallSign != null && senderCallSign.equalsIgnoreCase(myCall)) {
|
|
||||||
fallbackSender.setCallSign(myCall);
|
|
||||||
} else {
|
|
||||||
fallbackSender.setCallSign("[n/a]" + senderCallSign);
|
|
||||||
}
|
|
||||||
fallbackSender.setChatCategory(category);
|
fallbackSender.setChatCategory(category);
|
||||||
fallbackSender.setAirPlaneReflectInfo(new AirPlaneReflectionInfo());
|
fallbackSender.setAirPlaneReflectInfo(
|
||||||
|
new AirPlaneReflectionInfo()
|
||||||
|
);
|
||||||
|
|
||||||
return fallbackSender;
|
return fallbackSender;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves a message receiver from the active-member model. Unknown receivers
|
* Resolves a message receiver from the thread-safe active-member model.
|
||||||
* are represented as explicit fallback objects instead of null. This keeps PM
|
*
|
||||||
* echo display and historic messages stable even if the target station already
|
* <p>The local login is handled before the active-user lookup. The receiver
|
||||||
* left the chat.
|
* from the ON4KST packet therefore remains authoritative even if another
|
||||||
|
* station with the same base callsign is logged into the same category.</p>
|
||||||
*/
|
*/
|
||||||
private ChatMember resolveInboundReceiver(String receiverCallSign, ChatCategory category) {
|
private ChatMember resolveInboundReceiver(
|
||||||
if (receiverCallSign == null || receiverCallSign.equals("0")) {
|
String receiverCallSign,
|
||||||
|
ChatCategory category
|
||||||
|
) {
|
||||||
|
|
||||||
|
if (receiverCallSign == null
|
||||||
|
|| receiverCallSign.equals("0")) {
|
||||||
return createAllReceiver();
|
return createAllReceiver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String myCall =
|
||||||
|
this.client.getChatPreferences().getStn_loginCallSign();
|
||||||
|
|
||||||
|
if (myCall != null
|
||||||
|
&& receiverCallSign.equalsIgnoreCase(myCall)) {
|
||||||
|
|
||||||
|
ChatMember ownReceiver = new ChatMember();
|
||||||
|
ownReceiver.setCallSign(receiverCallSign);
|
||||||
|
ownReceiver.setChatCategory(category);
|
||||||
|
ownReceiver.setAirPlaneReflectInfo(
|
||||||
|
new AirPlaneReflectionInfo()
|
||||||
|
);
|
||||||
|
|
||||||
|
return ownReceiver;
|
||||||
|
}
|
||||||
|
|
||||||
ChatMember lookup = new ChatMember();
|
ChatMember lookup = new ChatMember();
|
||||||
lookup.setCallSign(receiverCallSign);
|
lookup.setCallSign(receiverCallSign);
|
||||||
lookup.setChatCategory(category);
|
lookup.setChatCategory(category);
|
||||||
|
|
||||||
ChatMember receiverObj = this.client.findActiveChatMember(lookup);
|
ChatMember receiverObj =
|
||||||
|
this.client.findActiveChatMember(lookup);
|
||||||
|
|
||||||
if (receiverObj != null) {
|
if (receiverObj != null) {
|
||||||
return receiverObj;
|
return receiverObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatMember fallbackReceiver = new ChatMember();
|
ChatMember fallbackReceiver = new ChatMember();
|
||||||
String myCall = this.client.getChatPreferences().getStn_loginCallSign();
|
fallbackReceiver.setCallSign(receiverCallSign + "(left)");
|
||||||
if (receiverCallSign.equalsIgnoreCase(myCall)) {
|
|
||||||
fallbackReceiver.setCallSign(myCall);
|
|
||||||
} else {
|
|
||||||
fallbackReceiver.setCallSign(receiverCallSign + "(left)");
|
|
||||||
}
|
|
||||||
fallbackReceiver.setChatCategory(category);
|
fallbackReceiver.setChatCategory(category);
|
||||||
fallbackReceiver.setAirPlaneReflectInfo(new AirPlaneReflectionInfo());
|
fallbackReceiver.setAirPlaneReflectInfo(
|
||||||
|
new AirPlaneReflectionInfo()
|
||||||
|
);
|
||||||
|
|
||||||
return fallbackReceiver;
|
return fallbackReceiver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2431,24 +2431,32 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares two ChatMember objects by their logical identity.
|
* Compares two ChatMember objects by their active chat identity.
|
||||||
*
|
*
|
||||||
* JavaFX may replace item instances during refreshes, so object identity alone is
|
* <p>JavaFX may replace item instances during list refreshes, so object
|
||||||
* not enough. For selection stability we compare callsign/raw callsign and chat
|
* identity alone is not sufficient. The complete callsign and the chat
|
||||||
* category number.
|
* category identify one active ON4KST login.</p>
|
||||||
|
*
|
||||||
|
* <p>The raw/base callsign must not be used here. Otherwise callsigns such as
|
||||||
|
* {@code 9A0BB-2} and {@code 9A0BB-70} would still be treated as the same
|
||||||
|
* selection inside one chat category.</p>
|
||||||
*/
|
*/
|
||||||
private boolean isSameLogicalChatMember(ChatMember a, ChatMember b) {
|
private boolean isSameLogicalChatMember(ChatMember a, ChatMember b) {
|
||||||
|
|
||||||
if (a == b) {
|
if (a == b) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a == null || b == null) {
|
if (a == null || b == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String aCall = a.getCallSignRaw() != null ? a.getCallSignRaw() : a.getCallSign();
|
String aCallSign = a.getCallSign();
|
||||||
String bCall = b.getCallSignRaw() != null ? b.getCallSignRaw() : b.getCallSign();
|
String bCallSign = b.getCallSign();
|
||||||
|
|
||||||
if (aCall == null || bCall == null || !aCall.equalsIgnoreCase(bCall)) {
|
if (aCallSign == null
|
||||||
|
|| bCallSign == null
|
||||||
|
|| !aCallSign.equalsIgnoreCase(bCallSign)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2458,11 +2466,13 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
|
|||||||
if (aCategory == bCategory) {
|
if (aCategory == bCategory) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aCategory == null || bCategory == null) {
|
if (aCategory == null || bCategory == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return aCategory.getCategoryNumber() == bCategory.getCategoryNumber();
|
return aCategory.getCategoryNumber()
|
||||||
|
== bCategory.getCategoryNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user