Fixed AS preferences implementation, DXcluster Server and the documentation
This commit is contained in:
+164
-116
@@ -1,150 +1,198 @@
|
||||
package kst4contest.controller;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.TimerTask;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import kst4contest.locatorUtils.Location;
|
||||
import kst4contest.model.ChatMember;
|
||||
|
||||
|
||||
/**
|
||||
* Sends periodical path requests and an AirScout watchlist for the currently
|
||||
* active ON4KST stations.
|
||||
*/
|
||||
public class AirScoutPeriodicalAPReflectionInquirerTask extends TimerTask {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(AirScoutPeriodicalAPReflectionInquirerTask.class.getName());
|
||||
private ChatController client;
|
||||
private static final Logger LOGGER = Logger.getLogger(
|
||||
AirScoutPeriodicalAPReflectionInquirerTask.class.getName()
|
||||
);
|
||||
|
||||
public AirScoutPeriodicalAPReflectionInquirerTask(ChatController client) {
|
||||
private static final String BROADCAST_ADDRESS = "255.255.255.255";
|
||||
|
||||
private final ChatController client;
|
||||
|
||||
public AirScoutPeriodicalAPReflectionInquirerTask(
|
||||
ChatController client
|
||||
) {
|
||||
this.client = client;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
Thread.currentThread().setName("AirscoutPeriodicalReflectionInquirierTask");
|
||||
Thread.currentThread().setName(
|
||||
"AirscoutPeriodicalReflectionInquirerTask"
|
||||
);
|
||||
|
||||
String KSTClientsNameForQuery = this.client.getChatPreferences().getAirScout_asClientNameString();
|
||||
String ASServerNameStringForAnswer = this.client.getChatPreferences().getAirScout_asServerNameString();
|
||||
|
||||
//TODO: Manage prefixes kst and as via preferences file and instance
|
||||
//TODO: Check if locator is changeable via the preferences object, need to be correct if it changes
|
||||
DatagramSocket dsocket;
|
||||
|
||||
// String prefix_asSetpath ="ASSETPATH: \"KST\" \"AS\" "; //working original
|
||||
// String prefix_asWatchList = "ASWATCHLIST: \"KST\" \"AS\" "; //working original
|
||||
|
||||
String prefix_asSetpath ="ASSETPATH: \"" + this.client.getChatPreferences().getAirScout_asClientNameString() + "\" \"" + this.client.getChatPreferences().getAirScout_asServerNameString() + "\" ";
|
||||
String prefix_asWatchList = "ASWATCHLIST: \""+ this.client.getChatPreferences().getAirScout_asClientNameString()+ "\" \"" + this.client.getChatPreferences().getAirScout_asServerNameString() + "\" ";
|
||||
|
||||
String bandString = "1440000"; //TODO: this must variable in case of higher bands! ... default: 1440000
|
||||
// String myCallAndMyLocString = this.client.getChatPreferences().getStn_loginCallSign() + "," + this.client.getChatPreferences().getStn_loginLocatorMainCat(); //before fix 1.266
|
||||
|
||||
|
||||
String ownCallSign = this.client.getChatPreferences().getStn_loginCallSign();
|
||||
try {
|
||||
if (this.client.getChatPreferences().getStn_loginCallSign().contains("-")) {
|
||||
ownCallSign = this.client.getChatPreferences().getStn_loginCallSign().split("-")[0];
|
||||
} else {
|
||||
ownCallSign = this.client.getChatPreferences().getStn_loginCallSign();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, "[ASPERIODICAL] Error parsing callsign", e);
|
||||
}
|
||||
String myCallAndMyLocString = ownCallSign + "," + this.client.getChatPreferences().getStn_loginLocatorMainCat(); //bugfix, Airscout do not process 9A1W-2 but 9A1W like formatted calls
|
||||
|
||||
|
||||
String suffix = ""; //"FOREIGNCALL,FOREIGNLOC " -- dont forget the space at the end!!!
|
||||
String asWatchListString = prefix_asWatchList + bandString + "," + myCallAndMyLocString;
|
||||
String asWatchListStringSuffix = asWatchListString;
|
||||
|
||||
String host = "255.255.255.255";
|
||||
// int port = 9872;
|
||||
|
||||
int port = client.getChatPreferences().getAirScout_asCommunicationPort();
|
||||
// byte[] message = "ASSETPATH: \"KST\" \"AS\" 1440000,DO5AMF,JN49GL,OK1MZM,JN89IW ".getBytes(); Original, ging
|
||||
InetAddress address;
|
||||
|
||||
|
||||
/**
|
||||
* Iterate over chatmemberlist and asking airscout for plane reflection information
|
||||
* To avoid a concurrentmodifyexception, we have to convert the original list to an array at first
|
||||
* since the iterator brakes if the list changing during the iteration time
|
||||
/*
|
||||
* Keep the scheduled task installed so that AirScout can be enabled at
|
||||
* runtime, but do not send anything while the integration is disabled.
|
||||
*/
|
||||
ObservableList<ChatMember> praktiKSTActiveUserList = this.client.getLst_chatMemberList();
|
||||
|
||||
|
||||
ChatMember[] ary_threadSafeChatMemberArray = new ChatMember[praktiKSTActiveUserList.size()];
|
||||
praktiKSTActiveUserList.toArray(ary_threadSafeChatMemberArray);
|
||||
|
||||
for (ChatMember i : ary_threadSafeChatMemberArray) {
|
||||
if (!client.getChatPreferences().isAirScout_asUDPListenerEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (i.getQrb() < this.client.getChatPreferences().getStn_maxQRBDefault())
|
||||
//Here: check if maximum distance to the chatmember is reached, only ask AS if distance is lower!
|
||||
//this counts for AS request and Aswatchlist
|
||||
{
|
||||
suffix = i.getCallSign() + "," + i.getQra() + " ";
|
||||
String clientIdentifier =
|
||||
client.getChatPreferences().getAirScout_asClientNameString();
|
||||
String serverIdentifier =
|
||||
client.getChatPreferences().getAirScout_asServerNameString();
|
||||
String bandValue =
|
||||
client.getChatPreferences().getAirScout_asBandString();
|
||||
|
||||
String queryStringToAirScout = "";
|
||||
String ownCallSign = normalizeOwnCallSign(
|
||||
client.getChatPreferences().getStn_loginCallSign()
|
||||
);
|
||||
String ownLocator =
|
||||
client.getChatPreferences().getStn_loginLocatorMainCat();
|
||||
|
||||
queryStringToAirScout += prefix_asSetpath + bandString + "," + myCallAndMyLocString + "," + suffix;
|
||||
if (ownCallSign == null
|
||||
|| ownCallSign.isBlank()
|
||||
|| ownLocator == null
|
||||
|| ownLocator.isBlank()) {
|
||||
LOGGER.warning(
|
||||
"AirScout queries were skipped because the own callsign "
|
||||
+ "or locator is missing."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] queryStringToAirScoutMSG = queryStringToAirScout.getBytes();
|
||||
String setPathPrefix =
|
||||
"ASSETPATH: \"" + clientIdentifier
|
||||
+ "\" \"" + serverIdentifier + "\" ";
|
||||
|
||||
try {
|
||||
address = InetAddress.getByName("255.255.255.255");
|
||||
DatagramPacket packet = new DatagramPacket(queryStringToAirScoutMSG, queryStringToAirScoutMSG.length, address, port);
|
||||
dsocket = new DatagramSocket();
|
||||
dsocket.setBroadcast(true);
|
||||
dsocket.send(packet);
|
||||
dsocket.close();
|
||||
} catch (UnknownHostException e1) {
|
||||
LOGGER.log(Level.SEVERE, "[ASPERIODICAL] Unknown host", e1);
|
||||
} catch (NoRouteToHostException e) {
|
||||
LOGGER.log(Level.SEVERE, "[ASPERIODICAL] No route to host", e);
|
||||
} catch (SocketException e) {
|
||||
LOGGER.log(Level.SEVERE, "[ASPERIODICAL] Socket error", e);
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.SEVERE, "[ASPERIODICAL] IO error sending query", e);
|
||||
String watchListPrefix =
|
||||
"ASWATCHLIST: \"" + clientIdentifier
|
||||
+ "\" \"" + serverIdentifier + "\" ";
|
||||
|
||||
String ownStation = ownCallSign + "," + ownLocator;
|
||||
StringBuilder watchListMessage = new StringBuilder(
|
||||
watchListPrefix
|
||||
+ bandValue
|
||||
+ ","
|
||||
+ ownStation
|
||||
);
|
||||
|
||||
List<ChatMember> activeMembers = client.snapshotChatMembers();
|
||||
int port = client.getChatPreferences()
|
||||
.getAirScout_asCommunicationPort();
|
||||
|
||||
try (
|
||||
DatagramSocket socket = new DatagramSocket()
|
||||
) {
|
||||
socket.setBroadcast(true);
|
||||
InetAddress broadcastAddress =
|
||||
InetAddress.getByName(BROADCAST_ADDRESS);
|
||||
|
||||
for (ChatMember member : activeMembers) {
|
||||
if (!isUsableAirScoutTarget(member)) {
|
||||
continue;
|
||||
}
|
||||
// System.out.println("[ASUDPTask, info:] sent query " + queryStringToAirScout);
|
||||
|
||||
asWatchListStringSuffix += "," + i.getCallSign() + "," + i.getQra();
|
||||
if (member.getQrb()
|
||||
>= client.getChatPreferences().getStn_maxQRBDefault()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String targetStation =
|
||||
member.getCallSign() + "," + member.getQra();
|
||||
|
||||
String pathQuery =
|
||||
setPathPrefix
|
||||
+ bandValue
|
||||
+ ","
|
||||
+ ownStation
|
||||
+ ","
|
||||
+ targetStation
|
||||
+ " ";
|
||||
|
||||
sendPacket(
|
||||
socket,
|
||||
broadcastAddress,
|
||||
port,
|
||||
pathQuery
|
||||
);
|
||||
|
||||
watchListMessage
|
||||
.append(",")
|
||||
.append(targetStation);
|
||||
}
|
||||
|
||||
watchListMessage.append(" ");
|
||||
|
||||
sendPacket(
|
||||
socket,
|
||||
broadcastAddress,
|
||||
port,
|
||||
watchListMessage.toString()
|
||||
);
|
||||
} catch (IOException exception) {
|
||||
LOGGER.log(
|
||||
Level.WARNING,
|
||||
"Could not send the periodical AirScout queries.",
|
||||
exception
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* As next we will set the ASWatchlist. All stations in chat will be watched by airscout causing following code.\n\n
|
||||
* ASWATCHLIST: "KST" "AS" 4320000,DO5AMF,JN49GL,DF9QX,JO42HD,DG2KBC,JN58MI,DJ0PY,JO32MF,DL1YDI,JO42FA,DL6BF,JO32QI,F1NZC,JN15MR,F4TXU,JN23CX,F5GHP,IN96LE,F6HTJ,JN12KQ,G0GGG,IO81VE,G0JCC,IO82MA,G0JDL,JO02SI,G0MBL,JO01QH,G4AEP,IO91MB,G4CLA,IO92JL,G4DCV,IO91OF,G4LOH,IO70JC,G4MKF,IO91HJ,G4TRA,IO81WN,G8GXP,IO93FQ,G8VHI,IO92FM,GW0RHC,IO71UN,HA4ND,JN97MJ,I5/HB9SJV/P,JN52JS,IW2DAL,JN45NN,OK1FPR,JO80CE,OK6M,JN99CR,OV3T,JO46CM,OZ2M,JO65FR,PA0V,JO33II,PA2RU,JO32LT,PA3DOL,JO22MT,PA9R,JO22JK,PE1EVX,JO22MP,S51AT,JN75GW,SM7KOJ,JO66ND,SP9TTG,JO90KW�
|
||||
* The watchlist-String is bult by the for loop which builds the AP queries
|
||||
*/
|
||||
asWatchListStringSuffix += " ";
|
||||
|
||||
byte[] queryStringToAirScoutMSG = asWatchListStringSuffix.getBytes();
|
||||
|
||||
try {
|
||||
address = InetAddress.getByName("255.255.255.255");
|
||||
DatagramPacket packet = new DatagramPacket(queryStringToAirScoutMSG, queryStringToAirScoutMSG.length, address, port);
|
||||
dsocket = new DatagramSocket();
|
||||
dsocket.setBroadcast(true);
|
||||
dsocket.send(packet);
|
||||
dsocket.close();
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.SEVERE, "[ASPERIODICAL] IO error sending watchlist", e);
|
||||
}
|
||||
|
||||
// System.out.println("[ASUDPTask, info:] set watchlist: " + asWatchListStringSuffix);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Removes the ON4KST login suffix because AirScout expects the actual
|
||||
* station callsign, for example 9A1W instead of 9A1W-2.
|
||||
*
|
||||
* @param callSign configured ON4KST login callsign
|
||||
* @return callsign without an ON4KST login suffix
|
||||
*/
|
||||
private String normalizeOwnCallSign(String callSign) {
|
||||
if (callSign == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String normalizedCallSign = callSign.trim();
|
||||
int suffixSeparator = normalizedCallSign.indexOf("-");
|
||||
|
||||
if (suffixSeparator > 0) {
|
||||
return normalizedCallSign.substring(0, suffixSeparator);
|
||||
}
|
||||
|
||||
return normalizedCallSign;
|
||||
}
|
||||
|
||||
private boolean isUsableAirScoutTarget(ChatMember member) {
|
||||
return member != null
|
||||
&& member.getCallSign() != null
|
||||
&& !member.getCallSign().isBlank()
|
||||
&& member.getQra() != null
|
||||
&& !member.getQra().isBlank();
|
||||
}
|
||||
|
||||
private void sendPacket(
|
||||
DatagramSocket socket,
|
||||
InetAddress address,
|
||||
int port,
|
||||
String message
|
||||
) throws IOException {
|
||||
byte[] payload = message.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
DatagramPacket packet = new DatagramPacket(
|
||||
payload,
|
||||
payload.length,
|
||||
address,
|
||||
port
|
||||
);
|
||||
|
||||
socket.send(packet);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
|
||||
|
||||
@@ -472,66 +473,106 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
|
||||
* @param remoteChatMember with callsign of the foreign station
|
||||
*/
|
||||
|
||||
public void airScout_SendAsShowPathPacket(ChatMember remoteChatMember) {
|
||||
/**
|
||||
* Requests AirScout to display the path between the own station and the
|
||||
* selected remote station.
|
||||
*
|
||||
* The configured server identifier, client identifier, band and UDP port are
|
||||
* used for every request. This allows several AirScout servers or clients to
|
||||
* coexist in the same network.
|
||||
*
|
||||
* @param remoteChatMember selected remote station
|
||||
*/
|
||||
public void airScout_SendAsShowPathPacket(
|
||||
ChatMember remoteChatMember
|
||||
) {
|
||||
if (!chatPreferences.isAirScout_asUDPListenerEnabled()) {
|
||||
System.out.println(
|
||||
"[AirScout, info]: Show-path request ignored because "
|
||||
+ "the AirScout integration is disabled."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
DatagramSocket dsocket;
|
||||
if (remoteChatMember == null
|
||||
|| remoteChatMember.getCallSign() == null
|
||||
|| remoteChatMember.getCallSign().isBlank()
|
||||
|| remoteChatMember.getQra() == null
|
||||
|| remoteChatMember.getQra().isBlank()) {
|
||||
System.out.println(
|
||||
"[AirScout, warning]: Show-path request ignored because "
|
||||
+ "the selected station has no usable callsign or locator."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
String prefix_asSetpath ="ASSHOWPATH: \""+ this.getChatPreferences().getAirScout_asClientNameString()+ "\" \"" + this.getChatPreferences().getAirScout_asServerNameString() + "\" ";
|
||||
String ownCallSign = chatPreferences.getStn_loginCallSign();
|
||||
if (ownCallSign == null || ownCallSign.isBlank()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// String prefix_asSetpath ="ASSHOWPATH: \"KST\" \"AS\" "; Old hard coded
|
||||
String bandString = "1440000";
|
||||
// String myCallAndMyLocString = chatPreferences.getStn_loginCallSign() + "," + chatPreferences.getStn_loginLocatorMainCat(); // original b4 bugfix 1266
|
||||
String remoteCallAndLocString = remoteChatMember.getCallSign() +"," + remoteChatMember.getQra();
|
||||
int suffixSeparator = ownCallSign.indexOf("-");
|
||||
if (suffixSeparator > 0) {
|
||||
ownCallSign = ownCallSign.substring(0, suffixSeparator);
|
||||
}
|
||||
|
||||
String ownCallSign="";
|
||||
try {
|
||||
if (chatPreferences.getStn_loginCallSign().contains("-")) {
|
||||
ownCallSign = chatPreferences.getStn_loginCallSign().split("-")[0];
|
||||
} else {
|
||||
ownCallSign = chatPreferences.getStn_loginCallSign();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("[ASPERIODICAL, Error]: " + e.getMessage());
|
||||
}
|
||||
String ownLocator =
|
||||
chatPreferences.getStn_loginLocatorMainCat();
|
||||
|
||||
String myCallAndMyLocString = ownCallSign + "," + chatPreferences.getStn_loginLocatorMainCat(); // original b4 bugfix 1266
|
||||
if (ownLocator == null || ownLocator.isBlank()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String host = "255.255.255.255";
|
||||
// int port = 9872;
|
||||
int port = chatPreferences.getAirScout_asCommunicationPort();
|
||||
// System.out.println("<<<<<<<<<<<<<<<<<<<<ASPERI: " + port);
|
||||
String clientIdentifier =
|
||||
chatPreferences.getAirScout_asClientNameString();
|
||||
String serverIdentifier =
|
||||
chatPreferences.getAirScout_asServerNameString();
|
||||
String bandValue =
|
||||
chatPreferences.getAirScout_asBandString();
|
||||
int port =
|
||||
chatPreferences.getAirScout_asCommunicationPort();
|
||||
|
||||
// byte[] message = "ASSETPATH: \"KST\" \"AS\" 1440000,DO5AMF,JN49GL,OK1MZM,JN89IW ".getBytes(); Original, ging
|
||||
InetAddress address;
|
||||
String query =
|
||||
"ASSHOWPATH: \""
|
||||
+ clientIdentifier
|
||||
+ "\" \""
|
||||
+ serverIdentifier
|
||||
+ "\" "
|
||||
+ bandValue
|
||||
+ ","
|
||||
+ ownCallSign
|
||||
+ ","
|
||||
+ ownLocator
|
||||
+ ","
|
||||
+ remoteChatMember.getCallSign()
|
||||
+ ","
|
||||
+ remoteChatMember.getQra()
|
||||
+ " ";
|
||||
|
||||
String queryStringToAirScout = "";
|
||||
byte[] payload = query.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
queryStringToAirScout += prefix_asSetpath + bandString + "," + myCallAndMyLocString + "," + remoteCallAndLocString+ "Å";
|
||||
try (
|
||||
DatagramSocket socket = new DatagramSocket()
|
||||
) {
|
||||
socket.setBroadcast(true);
|
||||
|
||||
byte[] queryStringToAirScoutMSG = queryStringToAirScout.getBytes();
|
||||
|
||||
try {
|
||||
address = InetAddress.getByName("255.255.255.255");
|
||||
DatagramPacket packet = new DatagramPacket(queryStringToAirScoutMSG, queryStringToAirScoutMSG.length, address, port);
|
||||
dsocket = new DatagramSocket();
|
||||
dsocket.setBroadcast(true);
|
||||
dsocket.send(packet);
|
||||
dsocket.close();
|
||||
} catch (UnknownHostException e1) {
|
||||
// TODO Auto-generated catch block
|
||||
e1.printStackTrace();
|
||||
} catch (NoRouteToHostException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (SocketException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
InetAddress address =
|
||||
InetAddress.getByName("255.255.255.255");
|
||||
|
||||
DatagramPacket packet = new DatagramPacket(
|
||||
payload,
|
||||
payload.length,
|
||||
address,
|
||||
port
|
||||
);
|
||||
|
||||
socket.send(packet);
|
||||
} catch (IOException exception) {
|
||||
System.out.println(
|
||||
"[AirScout, error]: Could not send show-path request: "
|
||||
+ exception.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -582,7 +623,7 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
|
||||
|
||||
stopScoreScheduler();
|
||||
|
||||
this.dxClusterServer.stop();
|
||||
stopDxClusterServer();
|
||||
|
||||
this.setDisconnectionPerformedByUser(true);
|
||||
|
||||
@@ -692,8 +733,6 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
|
||||
|
||||
dbHandler.closeDBConnection();
|
||||
|
||||
dxClusterServer.stop();
|
||||
|
||||
rotatorClient.stopRotor();
|
||||
rotatorClient.stop();
|
||||
|
||||
@@ -1132,7 +1171,9 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
|
||||
// mine
|
||||
private FilteredList<ChatMessage> lst_toOtherMessageList = new FilteredList<>(lst_globalChatMessageList);
|
||||
|
||||
private ObservableList<String> lstNotify_QSOSniffer_sniffedCallSignList = FXCollections.observableArrayList();
|
||||
// private ObservableList<String> lstNotify_QSOSniffer_sniffedCallSignList = FXCollections.observableArrayList();
|
||||
private ObservableList<String>
|
||||
lstNotify_QSOSniffer_sniffedCallSignList;
|
||||
/**
|
||||
* we do some trick here with the chatmemberlist to not make it neccessary to change all boolean properties if the
|
||||
* chatmember object to observables. We trigger the list for changes on an object which we change whenever a list
|
||||
@@ -1864,6 +1905,9 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
|
||||
chatPreferences = new ChatPreferences();
|
||||
chatPreferences.readPreferencesFromXmlFile();
|
||||
// this.statusListener = listener;
|
||||
lstNotify_QSOSniffer_sniffedCallSignList =
|
||||
chatPreferences
|
||||
.getLstNotify_QSOSniffer_sniffedCallSignList();
|
||||
|
||||
String dnsFromPrefs = chatPreferences.getStn_on4kstServersDns();
|
||||
if (dnsFromPrefs != null && !dnsFromPrefs.isEmpty()) {
|
||||
@@ -2004,7 +2048,7 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
|
||||
|
||||
});
|
||||
|
||||
lstNotify_QSOSniffer_sniffedCallSignList.add("DF0GEB");
|
||||
// lstNotify_QSOSniffer_sniffedCallSignList.add("DF0GEB");
|
||||
|
||||
|
||||
lst_toMeMessageList.setPredicate(chatFilterPredicate); //sniffed callsign filter predicate is here!
|
||||
@@ -2147,6 +2191,38 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
|
||||
return dxClusterServer;
|
||||
}
|
||||
|
||||
public synchronized void startDxClusterServerIfEnabled() {
|
||||
if (!chatPreferences.isNotify_dxClusterServerEnabled()
|
||||
|| dxClusterServer != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
dxClusterServer = new DXClusterThreadPooledServer(
|
||||
chatPreferences.getNotify_dxclusterServerPort(),
|
||||
this,
|
||||
this
|
||||
);
|
||||
|
||||
Thread serverThread = new Thread(dxClusterServer);
|
||||
serverThread.setName("DXCluster-thread-pooled-server");
|
||||
serverThread.setDaemon(true);
|
||||
serverThread.start();
|
||||
}
|
||||
|
||||
public synchronized void stopDxClusterServer() {
|
||||
DXClusterThreadPooledServer serverToStop = dxClusterServer;
|
||||
dxClusterServer = null;
|
||||
|
||||
if (serverToStop != null) {
|
||||
serverToStop.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void restartDxClusterServerIfEnabled() {
|
||||
stopDxClusterServer();
|
||||
startDxClusterServerIfEnabled();
|
||||
}
|
||||
|
||||
// public void setChatMemberTable(Hashtable<String, ChatMember> chatMemberTable) {
|
||||
// this.chatMemberTable = chatMemberTable;
|
||||
// }
|
||||
@@ -2230,7 +2306,11 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
|
||||
messageProcessor.setName("messagebusManagementThread");
|
||||
messageProcessor.start();
|
||||
|
||||
airScoutUDPReaderThread = new ReadUDPbyAirScoutMessageThread(chatPreferences.getAirScout_asCommunicationPort(), this, this.getChatPreferences().getAirScout_asServerNameString(), this.getChatPreferences().getAirScout_asServerNameString(), this); //working original
|
||||
airScoutUDPReaderThread = new ReadUDPbyAirScoutMessageThread(
|
||||
chatPreferences.getAirScout_asCommunicationPort(),
|
||||
this,
|
||||
this
|
||||
);
|
||||
airScoutUDPReaderThread.setName("airscoutudpreaderThread");
|
||||
airScoutUDPReaderThread.start();
|
||||
|
||||
@@ -2249,8 +2329,7 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
|
||||
/**
|
||||
* DX cluster service running config
|
||||
*/
|
||||
dxClusterServer = new DXClusterThreadPooledServer(this.getChatPreferences().getNotify_dxclusterServerPort(), this, this);
|
||||
new Thread(dxClusterServer).start();
|
||||
startDxClusterServerIfEnabled();
|
||||
|
||||
|
||||
this.setConnectedAndLoggedIn(true);
|
||||
|
||||
@@ -1,238 +1,363 @@
|
||||
package kst4contest.controller;
|
||||
|
||||
import kst4contest.model.ChatMember;
|
||||
import kst4contest.model.ChatPreferences;
|
||||
import kst4contest.model.ThreadStateMessage;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class DXClusterThreadPooledServer implements Runnable{
|
||||
public class DXClusterThreadPooledServer implements Runnable {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(DXClusterThreadPooledServer.class.getName());
|
||||
private List<Socket> clientSockets = Collections.synchronizedList(new ArrayList<>()); //list of all connected clients
|
||||
private static final Logger LOGGER =
|
||||
Logger.getLogger(DXClusterThreadPooledServer.class.getName());
|
||||
private static final String THREAD_NICKNAME = "DXCluster-Server";
|
||||
|
||||
private ThreadStatusCallback callBackToController;
|
||||
private String ThreadNickName = "DXCluster-Server";
|
||||
ChatController chatController = null;
|
||||
protected int serverPort = 8080;
|
||||
protected ServerSocket serverSocket = null;
|
||||
protected boolean isStopped = false;
|
||||
protected Thread runningThread= null;
|
||||
protected ExecutorService threadPool =
|
||||
private final List<Socket> clientSockets =
|
||||
Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
private final ChatController chatController;
|
||||
private final ThreadStatusCallback callBackToController;
|
||||
private final int serverPort;
|
||||
|
||||
private final ExecutorService threadPool =
|
||||
Executors.newFixedThreadPool(10);
|
||||
Socket clientSocket;
|
||||
|
||||
public DXClusterThreadPooledServer(int port, ChatController chatController, ThreadStatusCallback callback){
|
||||
private final ScheduledExecutorService keepAliveExecutor =
|
||||
Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
private volatile boolean stopped;
|
||||
private ServerSocket serverSocket;
|
||||
|
||||
public DXClusterThreadPooledServer(
|
||||
int port,
|
||||
ChatController chatController,
|
||||
ThreadStatusCallback callback
|
||||
) {
|
||||
this.serverPort = port;
|
||||
this.chatController = chatController;
|
||||
this.callBackToController = callback;
|
||||
}
|
||||
|
||||
public void run(){
|
||||
@Override
|
||||
public void run() {
|
||||
Thread.currentThread().setName("DXCluster-thread-pooled-server");
|
||||
|
||||
ThreadStateMessage threadStateMessage = new ThreadStateMessage(this.ThreadNickName, true, "initialized", false);
|
||||
callBackToController.onThreadStatus(ThreadNickName,threadStateMessage);
|
||||
try {
|
||||
serverSocket = new ServerSocket(serverPort);
|
||||
|
||||
synchronized(this){
|
||||
this.runningThread = Thread.currentThread();
|
||||
runningThread.setName("DXCluster-thread-pooled-server");
|
||||
}
|
||||
openServerSocket();
|
||||
while(! isStopped()){
|
||||
clientSocket = null;
|
||||
try {
|
||||
clientSocket = this.serverSocket.accept();
|
||||
|
||||
synchronized(clientSockets) {
|
||||
clientSockets.add(clientSocket); // add dx cluster client to the "clients list" for broadcasting
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
if(isStopped()) {
|
||||
System.out.println("Server Stopped.") ;
|
||||
break;
|
||||
}
|
||||
throw new RuntimeException(
|
||||
"Error accepting client connection", e);
|
||||
if (stopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
DXClusterServerWorkerRunnable worker = new DXClusterServerWorkerRunnable(clientSocket, "Thread Pooled DXCluster Server ", chatController, clientSockets, chatController);
|
||||
callBackToController.onThreadStatus(
|
||||
THREAD_NICKNAME,
|
||||
new ThreadStateMessage(
|
||||
THREAD_NICKNAME,
|
||||
true,
|
||||
"Listening on TCP port " + serverPort,
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
this.threadPool.execute(worker);
|
||||
keepAliveExecutor.scheduleAtFixedRate(
|
||||
this::sendKeepAlive,
|
||||
30,
|
||||
30,
|
||||
TimeUnit.SECONDS
|
||||
);
|
||||
|
||||
}
|
||||
this.threadPool.shutdown();
|
||||
System.out.println("Server Stopped.") ;
|
||||
}
|
||||
while (!stopped) {
|
||||
try {
|
||||
Socket clientSocket = serverSocket.accept();
|
||||
clientSockets.add(clientSocket);
|
||||
|
||||
private synchronized boolean isStopped() {
|
||||
return this.isStopped;
|
||||
}
|
||||
|
||||
public synchronized void stop(){
|
||||
this.isStopped = true;
|
||||
try {
|
||||
this.serverSocket.close();
|
||||
synchronized(clientSockets) {
|
||||
for (Socket socket : clientSockets) {
|
||||
socket.close(); // close all client connections
|
||||
threadPool.execute(
|
||||
new DXClusterServerWorkerRunnable(
|
||||
clientSocket,
|
||||
clientSockets
|
||||
)
|
||||
);
|
||||
} catch (IOException exception) {
|
||||
if (!stopped) {
|
||||
LOGGER.log(
|
||||
Level.SEVERE,
|
||||
"Error accepting DX Cluster client connection",
|
||||
exception
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("DXCCSERVER Error closing server", e);
|
||||
} catch (IOException exception) {
|
||||
if (!stopped) {
|
||||
LOGGER.log(
|
||||
Level.SEVERE,
|
||||
"Cannot open DX Cluster TCP port " + serverPort,
|
||||
exception
|
||||
);
|
||||
|
||||
callBackToController.onThreadStatus(
|
||||
THREAD_NICKNAME,
|
||||
new ThreadStateMessage(
|
||||
THREAD_NICKNAME,
|
||||
false,
|
||||
"Cannot open TCP port "
|
||||
+ serverPort
|
||||
+ ": "
|
||||
+ exception.getMessage(),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
closeServerSocket();
|
||||
closeClientSockets();
|
||||
keepAliveExecutor.shutdownNow();
|
||||
threadPool.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private void openServerSocket() {
|
||||
try {
|
||||
this.serverSocket = new ServerSocket(this.serverPort);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("DXCCSERVER Cannot open port ", e);
|
||||
public synchronized void stop() {
|
||||
stopped = true;
|
||||
closeServerSocket();
|
||||
closeClientSockets();
|
||||
keepAliveExecutor.shutdownNow();
|
||||
threadPool.shutdownNow();
|
||||
}
|
||||
|
||||
public boolean hasConnectedClients() {
|
||||
synchronized (clientSockets) {
|
||||
removeClosedClients();
|
||||
return !clientSockets.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a DX cluster message to ALL connected log programs via telnet, returns true if sent
|
||||
* Sends one DX Cluster spot to all currently connected clients.
|
||||
*
|
||||
* @param aChatMember
|
||||
* @return boolean true if message had been sent
|
||||
* @return true if the spot was delivered to at least one client
|
||||
*/
|
||||
public boolean broadcastSingleDXClusterEntryToLoggers(ChatMember aChatMember) {
|
||||
synchronized(clientSockets) {
|
||||
public boolean broadcastSingleDXClusterEntryToLoggers(
|
||||
ChatMember chatMember
|
||||
) {
|
||||
final String clusterMessage;
|
||||
|
||||
System.out.println("DXClusterSrvr: broadcasting message to clients: " + clientSockets.size());
|
||||
try {
|
||||
String frequency = Utils4KST.normalizeFrequencyString(
|
||||
chatMember.getFrequency().getValue(),
|
||||
chatController
|
||||
.getChatPreferences()
|
||||
.getNotify_optionalFrequencyPrefix()
|
||||
);
|
||||
|
||||
try {
|
||||
clusterMessage =
|
||||
"DX de "
|
||||
+ chatController
|
||||
.getChatPreferences()
|
||||
.getNotify_DXCSrv_SpottersCallSign()
|
||||
.getValue()
|
||||
+ ": "
|
||||
+ frequency
|
||||
+ " "
|
||||
+ chatMember.getCallSign().toUpperCase()
|
||||
+ " "
|
||||
+ chatMember.getQra().toUpperCase()
|
||||
+ " "
|
||||
+ new Utils4KST()
|
||||
.time_generateCurrenthhmmZTimeStringForClusterMessage()
|
||||
+ ((char) 7)
|
||||
+ ((char) 7)
|
||||
+ "\r\n";
|
||||
} catch (Exception exception) {
|
||||
LOGGER.log(
|
||||
Level.SEVERE,
|
||||
"Cannot build DX Cluster message",
|
||||
exception
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
System.out.println("-------------> ORIGINALEE VAL: " + aChatMember.getFrequency().getValue());
|
||||
System.out.println("-------------> NORMALIZED VAL: " + Utils4KST.normalizeFrequencyString(aChatMember.getFrequency().getValue(), chatController.getChatPreferences().getNotify_optionalFrequencyPrefix()) + " ");
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, "DXCThPooledServer: Error accessing value in chatmember object", e);
|
||||
}
|
||||
int deliveredClients = 0;
|
||||
|
||||
for (Socket socket : clientSockets) {
|
||||
synchronized (clientSockets) {
|
||||
Iterator<Socket> iterator = clientSockets.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Socket socket = iterator.next();
|
||||
|
||||
if (socket == null || socket.isClosed()) {
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
OutputStream output = socket.getOutputStream();
|
||||
output.write(
|
||||
clusterMessage.getBytes(
|
||||
StandardCharsets.US_ASCII
|
||||
)
|
||||
);
|
||||
output.flush();
|
||||
deliveredClients++;
|
||||
} catch (IOException exception) {
|
||||
LOGGER.log(
|
||||
Level.WARNING,
|
||||
"DX Cluster client disconnected while sending a spot",
|
||||
exception
|
||||
);
|
||||
|
||||
OutputStream output = socket.getOutputStream();
|
||||
|
||||
String singleDXClusterMessage = "DX de ";
|
||||
|
||||
// singleDXClusterMessage += chatController.getChatPreferences().getLoginCallSign() + ": ";
|
||||
|
||||
|
||||
|
||||
|
||||
singleDXClusterMessage += this.chatController.getChatPreferences().getNotify_DXCSrv_SpottersCallSign().getValue() + ": ";
|
||||
singleDXClusterMessage += Utils4KST.normalizeFrequencyString(aChatMember.getFrequency().getValue(), chatController.getChatPreferences().getNotify_optionalFrequencyPrefix()) + " ";
|
||||
singleDXClusterMessage += aChatMember.getCallSign().toUpperCase() + " "; //we need such an amount of spaces for n1mm to work, otherwise bullshit happens
|
||||
singleDXClusterMessage += aChatMember.getQra().toUpperCase() + " ";
|
||||
singleDXClusterMessage += new Utils4KST().time_generateCurrenthhmmZTimeStringForClusterMessage() + ((char)7) + ((char)7) + "\r\n";
|
||||
|
||||
// singleDXClusterMessage += chatController.getChatPreferences().getLoginCallSign() + ": ";
|
||||
// singleDXClusterMessage += Utils4KST.normalizeFrequencyString(aChatMember.getFrequency().getValue(), chatController.getChatPreferences().getNotify_optionalFrequencyPrefix()) + " ";
|
||||
// singleDXClusterMessage += aChatMember.getCallSign().toUpperCase() + " ";
|
||||
// singleDXClusterMessage += aChatMember.getQra().toUpperCase() + " ";
|
||||
// singleDXClusterMessage += new Utils4KST().time_generateCurrenthhmmZTimeStringForClusterMessage() + ((char)7) + ((char)7) + "\r\n";
|
||||
|
||||
output.write((singleDXClusterMessage).getBytes());
|
||||
|
||||
ThreadStateMessage threadStateMessage = new ThreadStateMessage(this.ThreadNickName, true, "Last msg to " + clientSockets.size() + " Cluster Clients:\n" + singleDXClusterMessage, false);
|
||||
callBackToController.onThreadStatus(ThreadNickName,threadStateMessage);
|
||||
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.SEVERE, "[DXClusterSrvr] broadcasting DXC-message to clients went wrong", e);
|
||||
return false;
|
||||
closeSocket(socket);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true; //if message had been sent, return true for "ok"
|
||||
|
||||
if (deliveredClients > 0) {
|
||||
callBackToController.onThreadStatus(
|
||||
THREAD_NICKNAME,
|
||||
new ThreadStateMessage(
|
||||
THREAD_NICKNAME,
|
||||
true,
|
||||
"Last spot sent to "
|
||||
+ deliveredClients
|
||||
+ " DX Cluster client(s):\n"
|
||||
+ clusterMessage,
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return deliveredClients > 0;
|
||||
}
|
||||
|
||||
private void sendKeepAlive() {
|
||||
synchronized (clientSockets) {
|
||||
Iterator<Socket> iterator = clientSockets.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Socket socket = iterator.next();
|
||||
|
||||
if (socket == null || socket.isClosed()) {
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
OutputStream output = socket.getOutputStream();
|
||||
output.write(
|
||||
"\r\n".getBytes(StandardCharsets.US_ASCII)
|
||||
);
|
||||
output.flush();
|
||||
} catch (IOException exception) {
|
||||
closeSocket(socket);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeClosedClients() {
|
||||
clientSockets.removeIf(
|
||||
socket -> socket == null || socket.isClosed()
|
||||
);
|
||||
}
|
||||
|
||||
private synchronized void closeServerSocket() {
|
||||
if (serverSocket == null || serverSocket.isClosed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
serverSocket.close();
|
||||
} catch (IOException exception) {
|
||||
LOGGER.log(
|
||||
Level.WARNING,
|
||||
"Error closing DX Cluster server socket",
|
||||
exception
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeClientSockets() {
|
||||
synchronized (clientSockets) {
|
||||
for (Socket socket : clientSockets) {
|
||||
closeSocket(socket);
|
||||
}
|
||||
|
||||
clientSockets.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static void closeSocket(Socket socket) {
|
||||
if (socket == null || socket.isClosed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ignored) {
|
||||
// The connection is already unusable.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DXClusterServerWorkerRunnable implements Runnable{
|
||||
class DXClusterServerWorkerRunnable implements Runnable {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(DXClusterServerWorkerRunnable.class.getName());
|
||||
protected Socket clientSocket = null;
|
||||
protected String serverText = null;
|
||||
private ChatController client = null;
|
||||
private List<Socket> dxClusterClientSocketsConnectedList;
|
||||
private ThreadStatusCallback callBackToController;
|
||||
private String ThreadNickName = "DXCluster-Server";
|
||||
private static final Logger LOGGER =
|
||||
Logger.getLogger(DXClusterServerWorkerRunnable.class.getName());
|
||||
|
||||
public DXClusterServerWorkerRunnable(Socket clientSocket, String serverText, ChatController chatController, List<Socket> clientSockets, ThreadStatusCallback callback) {
|
||||
private final Socket clientSocket;
|
||||
private final List<Socket> clientSockets;
|
||||
|
||||
DXClusterServerWorkerRunnable(
|
||||
Socket clientSocket,
|
||||
List<Socket> clientSockets
|
||||
) {
|
||||
this.clientSocket = clientSocket;
|
||||
this.serverText = serverText;
|
||||
this.client = chatController;
|
||||
this.dxClusterClientSocketsConnectedList = clientSockets;
|
||||
this.callBackToController = callback;
|
||||
|
||||
this.clientSockets = clientSockets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
OutputStream output = clientSocket.getOutputStream();
|
||||
dxClusterClientSocketsConnectedList.add(clientSocket);
|
||||
output.write(
|
||||
"login: ".getBytes(StandardCharsets.US_ASCII)
|
||||
);
|
||||
output.flush();
|
||||
|
||||
Timer dXCkeepAliveTimer = new Timer();
|
||||
dXCkeepAliveTimer.schedule(new TimerTask() {
|
||||
System.out.println(
|
||||
"[DXClusterServer] New client connected: "
|
||||
+ clientSocket.getInetAddress()
|
||||
);
|
||||
} catch (IOException exception) {
|
||||
LOGGER.log(
|
||||
Level.WARNING,
|
||||
"Cannot initialise DX Cluster client connection",
|
||||
exception
|
||||
);
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (clientSockets) {
|
||||
clientSockets.remove(clientSocket);
|
||||
}
|
||||
|
||||
StringBuilder connectedClients = new StringBuilder(); //only for statistics
|
||||
|
||||
for (Socket socket : dxClusterClientSocketsConnectedList) {
|
||||
|
||||
connectedClients.append(socket.getInetAddress()).append("\n");
|
||||
|
||||
try {
|
||||
OutputStream output = socket.getOutputStream();
|
||||
output.write(("\r\n").getBytes());
|
||||
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.SEVERE, "[DXClusterSrvr] keep-alive broadcast to client failed", e);
|
||||
dXCkeepAliveTimer.purge();
|
||||
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ex) {
|
||||
LOGGER.log(Level.SEVERE, "[DXClusterSrvr] error closing client socket", ex);
|
||||
}
|
||||
finally {
|
||||
this.cancel();
|
||||
}
|
||||
dxClusterClientSocketsConnectedList.remove(socket); //if socket is closed by client, remove it from the broadcast list and close it
|
||||
}
|
||||
}
|
||||
|
||||
// ThreadStateMessage threadStateMessage = new ThreadStateMessage(ThreadNickName, true, "Connected clients: " + connectedClients.toString(), false);
|
||||
// callBackToController.onThreadStatus(ThreadNickName,threadStateMessage);
|
||||
|
||||
}
|
||||
}, 30000, 30000);
|
||||
|
||||
|
||||
output.write(("login: ").getBytes()); //say hello to the client, it will answer with a callsign
|
||||
System.out.println("[DXClusterThreadPooledServer, Info:] New cluster client connected! "); //TODO: maybe integrate non blocking reader for client identification
|
||||
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.SEVERE, "[DXClusterSrvr] error in worker runnable", e);
|
||||
} finally {
|
||||
synchronized(dxClusterClientSocketsConnectedList) {
|
||||
dxClusterClientSocketsConnectedList.remove(clientSocket); // Entferne den Client nach Verarbeitung
|
||||
try {
|
||||
clientSocket.close();
|
||||
} catch (IOException ignored) {
|
||||
// The connection is already unusable.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
package kst4contest.controller;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.ArrayList;
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
@@ -21,188 +25,245 @@ import kst4contest.model.ThreadStateMessage;
|
||||
* @author www.codejava.net
|
||||
*/
|
||||
public class ReadUDPbyAirScoutMessageThread extends Thread {
|
||||
private BufferedReader reader;
|
||||
private Socket socket;
|
||||
private ChatController client;
|
||||
private int localPort;
|
||||
private String ASIdentificator, ChatClientIdentificator;
|
||||
private ThreadStatusCallback callBackToController;
|
||||
private String ThreadNickName = "AirScout msg";
|
||||
// public ReadUDPbyAirScoutMessageThread(int localPort) {
|
||||
// this.localPort = localPort;
|
||||
// }
|
||||
|
||||
public ReadUDPbyAirScoutMessageThread(int localPort, ChatController client, String ASIdentificator,
|
||||
String ChatClientIdentificator, ThreadStatusCallback callback) {
|
||||
private final ChatController client;
|
||||
private final int localPort;
|
||||
private final ThreadStatusCallback callBackToController;
|
||||
|
||||
this.callBackToController = callback;
|
||||
private final String threadNickName = "AirScout msg";
|
||||
|
||||
private DatagramSocket socket;
|
||||
|
||||
public ReadUDPbyAirScoutMessageThread(
|
||||
int localPort,
|
||||
ChatController client,
|
||||
ThreadStatusCallback callback
|
||||
) {
|
||||
this.localPort = localPort;
|
||||
this.client = client;
|
||||
this.ASIdentificator = ASIdentificator;
|
||||
this.ChatClientIdentificator = ChatClientIdentificator;
|
||||
this.callBackToController = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interrupt() {
|
||||
System.out.println("ReadUDP");
|
||||
super.interrupt();
|
||||
try {
|
||||
if (this.socket != null) {
|
||||
|
||||
this.socket.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
|
||||
if (socket != null && !socket.isClosed()) {
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void callThreadStateToUi (ThreadStateMessage threadStateMessage) {
|
||||
if (callBackToController != null) {
|
||||
//update the visual control of running thread
|
||||
callBackToController.onThreadStatus("AirScout", threadStateMessage);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks whether an AirScout response is addressed to the currently
|
||||
* configured server and client identifiers.
|
||||
*
|
||||
* Outgoing message:
|
||||
* ASSETPATH: "client" "server" ...
|
||||
*
|
||||
* Corresponding response:
|
||||
* ASNEAREST: "server" "client" ...
|
||||
*
|
||||
* The comparison is deliberately case-sensitive. In a setup with several
|
||||
* clients, KST-A and kst-a must not silently become the same destination.
|
||||
*
|
||||
* @param message received UDP message
|
||||
* @return true if the response belongs to this KST4Contest instance
|
||||
*/
|
||||
private boolean isMessageForConfiguredClient(String message) {
|
||||
if (message == null || !message.startsWith("ASNEAREST:")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
String[] quotedParts = message.split("\"");
|
||||
if (quotedParts.length < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String receivedServerIdentifier = quotedParts[1].trim();
|
||||
String receivedClientIdentifier = quotedParts[3].trim();
|
||||
|
||||
String configuredServerIdentifier =
|
||||
client.getChatPreferences()
|
||||
.getAirScout_asServerNameString();
|
||||
String configuredClientIdentifier =
|
||||
client.getChatPreferences()
|
||||
.getAirScout_asClientNameString();
|
||||
|
||||
if (configuredServerIdentifier == null
|
||||
|| configuredClientIdentifier == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return configuredServerIdentifier.equals(
|
||||
receivedServerIdentifier
|
||||
) && configuredClientIdentifier.equals(
|
||||
receivedClientIdentifier
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Thread.currentThread().setName("ReadUDPByAirScoutThread");
|
||||
|
||||
DatagramSocket socket = null;
|
||||
boolean running;
|
||||
byte[] buf = new byte[1777];
|
||||
DatagramPacket packet;
|
||||
// DatagramPacket packet = new DatagramPacket(buf, buf.length); //changed due to save memory
|
||||
packet = new DatagramPacket(buf, buf.length);
|
||||
Thread.currentThread().setName(
|
||||
"ReadUDPByAirScoutThread"
|
||||
);
|
||||
|
||||
try {
|
||||
|
||||
socket = new DatagramSocket(null);
|
||||
socket.setReuseAddress(true);
|
||||
socket.bind(new InetSocketAddress(localPort));
|
||||
socket.receive(packet);
|
||||
socket.setSoTimeout(3000);
|
||||
|
||||
} catch (SocketException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
// e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
while (true) {
|
||||
// packet = new DatagramPacket(buf, buf.length);
|
||||
// DatagramPacket packet = new DatagramPacket(SRPDefinitions.BYTE_BUFFER_MAX_LENGTH);
|
||||
try {
|
||||
if (this.client.isDisconnectionPerformedByUser()) {
|
||||
break;//TODO: what if it´s not the finally closage but a band channel change?
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
if (client.isDisconnectionPerformedByUser()) {
|
||||
break;
|
||||
}
|
||||
|
||||
socket.receive(packet);
|
||||
|
||||
byte[] buffer = new byte[1777];
|
||||
DatagramPacket packet = new DatagramPacket(
|
||||
buffer,
|
||||
buffer.length
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} catch (SocketTimeoutException e2) {
|
||||
// this will catch the repeating Sockettimeoutexception...nothing to do
|
||||
// e2.printStackTrace();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
InetAddress address = packet.getAddress();
|
||||
int port = packet.getPort();
|
||||
// packet = new DatagramPacket(buf, buf.length, address, port);
|
||||
String received = new String(packet.getData(), packet.getOffset(), packet.getLength());
|
||||
received = received.trim();
|
||||
|
||||
if (received.contains(ApplicationConstants.DISCONNECT_RDR_POISONPILL)) {
|
||||
System.out.println("ReadUdpByASMsgTh, Info: got poison, now dieing....");
|
||||
try {
|
||||
terminateConnection();
|
||||
} catch (Exception e) {
|
||||
System.out.println("ASUDPRDR: catched error " + e.getMessage());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (received.contains("ASSETPATH") || received.contains("ASWATCHLIST")) {
|
||||
// do nothing, that is your own message
|
||||
} else if (received.contains("ASNEAREST:")) { //answer by airscout
|
||||
|
||||
// processASUDPMessage(received); //TODO: 2025-11-Zeile deaktiviert. Fand hier Doppelberechnung statt?!
|
||||
|
||||
AirPlaneReflectionInfo apReflectInfoForChatMember;
|
||||
|
||||
apReflectInfoForChatMember = processASUDPMessage(received);
|
||||
if (!this.client.getLst_chatMemberList().isEmpty()) {
|
||||
|
||||
try {
|
||||
|
||||
// this.client.getLst_chatMemberList()
|
||||
// .get(this.client.checkListForChatMemberIndexByCallSign(
|
||||
// apReflectInfoForChatMember.getReceiver()))
|
||||
// .setAirPlaneReflectInfo(apReflectInfoForChatMember); // TODO: here we set the ap info at
|
||||
// // the central instance of
|
||||
// // chatmember list .... -1 is a
|
||||
// // problem!
|
||||
|
||||
ArrayList<Integer> addApInfoToThese = this.client.checkListForChatMemberIndexesByCallSign(apReflectInfoForChatMember.getReceiver());
|
||||
addApInfoToThese.forEach((integerIndex) -> {this.client.getLst_chatMemberList().get(integerIndex).setAirPlaneReflectInfo(apReflectInfoForChatMember); });
|
||||
|
||||
// AirScout availability strongly affects priority => request recompute the score of the chatmember
|
||||
this.client.getScoreService().requestRecompute("airscout-update");
|
||||
|
||||
/**
|
||||
* CK| MSGBUS BGFX Listactualizer Exception in thread "Thread-10"
|
||||
* java.util.ConcurrentModificationException at
|
||||
* java.base/java.util.AbstractList$Itr.checkForComodification(AbstractList.java:399)
|
||||
* at java.base/java.util.AbstractList$Itr.next(AbstractList.java:368) at
|
||||
* kst4contest.controller.ChatController.checkListForChatMemberIndexByCallSign(ChatController.java:173)
|
||||
* at
|
||||
* kst4contest.controller.ReadUDPbyAirScoutMessageThread.run(ReadUDPbyAirScoutMessageThread.java:93)
|
||||
*
|
||||
*/
|
||||
// System.out.println("[ReadUdpByASth, AP-Info catched: ] " + apReflectInfoForChatMember.toString());
|
||||
// }
|
||||
} catch (Exception e) {
|
||||
|
||||
System.out.println("ReadUdpByAsMsgTh, Warning:"
|
||||
+ apReflectInfoForChatMember.getReceiver().getCallSign()
|
||||
+ " is not in the Chatmemberlist or the Chatmemberlist is modified by another Thread");
|
||||
// TODO: handle exception
|
||||
}
|
||||
|
||||
// String[] newState = new String[3];
|
||||
// newState[0] = "On";
|
||||
// newState[1] = "received line";
|
||||
// newState[2] = apReflectInfoForChatMember.toString();
|
||||
// callThreadStateToUi(newState);
|
||||
ThreadStateMessage threadStateMessage = new ThreadStateMessage(this.ThreadNickName, true, "received line\n" + apReflectInfoForChatMember.toString(), false);
|
||||
callBackToController.onThreadStatus(ThreadNickName,threadStateMessage);
|
||||
socket.receive(packet);
|
||||
} catch (SocketTimeoutException timeoutException) {
|
||||
/*
|
||||
* AirScout may remain silent for some time. Continue with a
|
||||
* new packet instead of processing the previous packet again.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
// packet = null; //reset packet
|
||||
buf = new byte[1777]; // reset buffer for future smaller packets
|
||||
String received = new String(
|
||||
packet.getData(),
|
||||
packet.getOffset(),
|
||||
packet.getLength()
|
||||
).trim();
|
||||
|
||||
if (received.contains(
|
||||
ApplicationConstants.DISCONNECT_RDR_POISONPILL
|
||||
)) {
|
||||
System.out.println(
|
||||
"[AirScout UDP, info]: Received shutdown packet."
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* The socket remains bound so that AirScout can be enabled
|
||||
* without reconnecting. Disabled means that received data is
|
||||
* discarded and no station state is changed.
|
||||
*/
|
||||
if (!client.getChatPreferences()
|
||||
.isAirScout_asUDPListenerEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isMessageForConfiguredClient(received)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
processAirScoutResponse(received);
|
||||
}
|
||||
} catch (SocketException exception) {
|
||||
if (!Thread.currentThread().isInterrupted()) {
|
||||
System.out.println(
|
||||
"[AirScout UDP, error]: Could not use UDP port "
|
||||
+ localPort
|
||||
+ ": "
|
||||
+ exception.getMessage()
|
||||
);
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
if (!Thread.currentThread().isInterrupted()) {
|
||||
System.out.println(
|
||||
"[AirScout UDP, error]: Communication failed: "
|
||||
+ exception.getMessage()
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (socket != null && !socket.isClosed()) {
|
||||
socket.close();
|
||||
}
|
||||
|
||||
socket = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an AirScout response and applies it to every active category
|
||||
* instance of the reported station.
|
||||
*
|
||||
* @param received received ASNEAREST message
|
||||
*/
|
||||
private void processAirScoutResponse(String received) {
|
||||
try {
|
||||
AirPlaneReflectionInfo reflectionInfo =
|
||||
processASUDPMessage(received);
|
||||
|
||||
if (reflectionInfo == null
|
||||
|| reflectionInfo.getReceiver() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String receiverCallSign =
|
||||
reflectionInfo.getReceiver().getCallSignRaw();
|
||||
|
||||
if (receiverCallSign == null
|
||||
|| receiverCallSign.isBlank()) {
|
||||
receiverCallSign =
|
||||
reflectionInfo.getReceiver().getCallSign();
|
||||
}
|
||||
|
||||
if (receiverCallSign == null
|
||||
|| receiverCallSign.isBlank()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<ChatMember> matchingMembers =
|
||||
client.findActiveChatMembersByRawCall(
|
||||
receiverCallSign
|
||||
);
|
||||
|
||||
for (ChatMember matchingMember : matchingMembers) {
|
||||
matchingMember.setAirPlaneReflectInfo(
|
||||
reflectionInfo
|
||||
);
|
||||
}
|
||||
|
||||
if (!matchingMembers.isEmpty()) {
|
||||
client.getScoreService().requestRecompute(
|
||||
"airscout-update"
|
||||
);
|
||||
}
|
||||
|
||||
if (callBackToController != null) {
|
||||
ThreadStateMessage threadStateMessage =
|
||||
new ThreadStateMessage(
|
||||
threadNickName,
|
||||
true,
|
||||
"Received AirScout response\n"
|
||||
+ reflectionInfo,
|
||||
false
|
||||
);
|
||||
|
||||
callBackToController.onThreadStatus(
|
||||
threadNickName,
|
||||
threadStateMessage
|
||||
);
|
||||
}
|
||||
} catch (RuntimeException exception) {
|
||||
System.out.println(
|
||||
"[AirScout UDP, warning]: Could not process response: "
|
||||
+ exception.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
public AirPlaneReflectionInfo processASUDPMessage(String udpStringToProcess) {
|
||||
|
||||
// System.out.println("RDUDPAS RECV: " + udpStringToProcess);
|
||||
|
||||
// TODO: filter messages which are directed to another client
|
||||
|
||||
|
||||
/*
|
||||
* Example mesage: ASNEAREST: "AS" "KST"
|
||||
@@ -304,11 +365,8 @@ public class ReadUDPbyAirScoutMessageThread extends Thread {
|
||||
}
|
||||
|
||||
public boolean terminateConnection() {
|
||||
|
||||
try {
|
||||
this.socket.close();
|
||||
} catch (Exception e) {
|
||||
System.out.println("udpbyas: catched " + e.getMessage());
|
||||
if (socket != null && !socket.isClosed()) {
|
||||
socket.close();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -50,7 +50,7 @@ public class ChatPreferences {
|
||||
* Reading must stay backwards compatible: missing/unknown tags should fall back to defaults.
|
||||
*/
|
||||
// private static final int CONFIG_VERSION = 2;
|
||||
public static final int CONFIG_VERSION = 3;
|
||||
public static final int CONFIG_VERSION = 4;
|
||||
|
||||
// Prefer writing tag names that mirror variable names (human readable). Keep legacy tags for compatibility.
|
||||
private static final String TAG_CONFIG_VERSION = "configVersion";
|
||||
@@ -240,9 +240,11 @@ public class ChatPreferences {
|
||||
/**
|
||||
* AirScout prefs
|
||||
*/
|
||||
boolean AirScout_asUDPListenerEnabled;
|
||||
String AirScout_asServerNameString, AirScout_asClientNameString, AirScout_asBandString;
|
||||
int AirScout_asCommunicationPort;
|
||||
boolean AirScout_asUDPListenerEnabled = true;
|
||||
String AirScout_asServerNameString = "AS";
|
||||
String AirScout_asClientNameString = "KST";
|
||||
String AirScout_asBandString = "1440000";
|
||||
int AirScout_asCommunicationPort = 9872;
|
||||
|
||||
/**
|
||||
* Notification prefs
|
||||
@@ -265,6 +267,9 @@ public class ChatPreferences {
|
||||
boolean notify_DXClusterServerTriggerBearing;
|
||||
boolean notify_DXClusterServerTriggerOnQRGDetect;
|
||||
|
||||
ObservableList<String>
|
||||
lstNotify_QSOSniffer_sniffedCallSignList =
|
||||
FXCollections.observableArrayList();
|
||||
// ObservableList<String> lstNotify_QSOSniffer_sniffedCallSignList = FXCollections.observableArrayList();
|
||||
ObservableList<String> lstNotify_QSOSniffer_sniffedWordsList = FXCollections.observableArrayList();
|
||||
ObservableList<String> lstNotify_QSOSniffer_sniffedPrefixLocList = FXCollections.observableArrayList();
|
||||
@@ -453,6 +458,11 @@ public class ChatPreferences {
|
||||
this.stn_on4kstServersDns = stn_on4kstServersDns;
|
||||
}
|
||||
|
||||
public ObservableList<String>
|
||||
getLstNotify_QSOSniffer_sniffedCallSignList() {
|
||||
return lstNotify_QSOSniffer_sniffedCallSignList;
|
||||
}
|
||||
|
||||
public ObservableList<String> getLstNotify_QSOSniffer_sniffedWordsList() {
|
||||
return lstNotify_QSOSniffer_sniffedWordsList;
|
||||
}
|
||||
@@ -799,8 +809,16 @@ public class ChatPreferences {
|
||||
return notify_dxclusterServerPort;
|
||||
}
|
||||
|
||||
public void setNotify_dxclusterServerPort(int notify_dxclusterServerPort) {
|
||||
this.notify_dxclusterServerPort = notify_dxclusterServerPort;
|
||||
public void setNotify_dxclusterServerPort(
|
||||
int notify_dxclusterServerPort
|
||||
) {
|
||||
if (notify_dxclusterServerPort < 1
|
||||
|| notify_dxclusterServerPort > 65535) {
|
||||
this.notify_dxclusterServerPort = 8000;
|
||||
} else {
|
||||
this.notify_dxclusterServerPort =
|
||||
notify_dxclusterServerPort;
|
||||
}
|
||||
}
|
||||
|
||||
public double[] getGUIscn_ChatwindowMainSceneSizeHW() {
|
||||
@@ -921,35 +939,100 @@ public class ChatPreferences {
|
||||
return stn_loginCallSignRaw;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes an AirScout routing identifier.
|
||||
*
|
||||
* AirScout encloses the identifiers in quotation marks. Empty identifiers,
|
||||
* quotation marks and line breaks would therefore produce an invalid protocol
|
||||
* message and are replaced with the supplied default value.
|
||||
*
|
||||
* @param identifier configured identifier
|
||||
* @param defaultIdentifier fallback value
|
||||
* @return normalized identifier
|
||||
*/
|
||||
private String normalizeAirScoutIdentifier(
|
||||
String identifier,
|
||||
String defaultIdentifier
|
||||
) {
|
||||
if (identifier == null) {
|
||||
return defaultIdentifier;
|
||||
}
|
||||
|
||||
String normalizedIdentifier = identifier.trim();
|
||||
|
||||
if (normalizedIdentifier.isEmpty()
|
||||
|| normalizedIdentifier.contains("\"")
|
||||
|| normalizedIdentifier.contains("\r")
|
||||
|| normalizedIdentifier.contains("\n")) {
|
||||
return defaultIdentifier;
|
||||
}
|
||||
|
||||
return normalizedIdentifier;
|
||||
}
|
||||
|
||||
public String getAirScout_asBandString() {
|
||||
return AirScout_asBandString;
|
||||
}
|
||||
|
||||
public void setAirScout_asBandString(String airScout_asBandString) {
|
||||
AirScout_asBandString = airScout_asBandString;
|
||||
if (airScout_asBandString == null) {
|
||||
AirScout_asBandString = "1440000";
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
long parsedBandValue = Long.parseLong(
|
||||
airScout_asBandString.trim()
|
||||
);
|
||||
|
||||
AirScout_asBandString = parsedBandValue > 0
|
||||
? Long.toString(parsedBandValue)
|
||||
: "1440000";
|
||||
} catch (NumberFormatException exception) {
|
||||
AirScout_asBandString = "1440000";
|
||||
}
|
||||
}
|
||||
|
||||
public String getAirScout_asServerNameString() {
|
||||
return AirScout_asServerNameString;
|
||||
}
|
||||
|
||||
public void setAirScout_asServerNameString(String airScout_asServerNameString) {
|
||||
AirScout_asServerNameString = airScout_asServerNameString;
|
||||
public void setAirScout_asServerNameString(
|
||||
String airScout_asServerNameString
|
||||
) {
|
||||
AirScout_asServerNameString = normalizeAirScoutIdentifier(
|
||||
airScout_asServerNameString,
|
||||
"AS"
|
||||
);
|
||||
}
|
||||
|
||||
public String getAirScout_asClientNameString() {
|
||||
return AirScout_asClientNameString;
|
||||
}
|
||||
|
||||
public void setAirScout_asClientNameString(String airScout_asClientNameString) {
|
||||
AirScout_asClientNameString = airScout_asClientNameString;
|
||||
public void setAirScout_asClientNameString(
|
||||
String airScout_asClientNameString
|
||||
) {
|
||||
AirScout_asClientNameString = normalizeAirScoutIdentifier(
|
||||
airScout_asClientNameString,
|
||||
"KST"
|
||||
);
|
||||
}
|
||||
|
||||
public int getAirScout_asCommunicationPort() {
|
||||
return AirScout_asCommunicationPort;
|
||||
}
|
||||
|
||||
public void setAirScout_asCommunicationPort(int airScout_asCommunicationPort) {
|
||||
public void setAirScout_asCommunicationPort(
|
||||
int airScout_asCommunicationPort
|
||||
) {
|
||||
if (airScout_asCommunicationPort < 1
|
||||
|| airScout_asCommunicationPort > 65535) {
|
||||
AirScout_asCommunicationPort = 9872;
|
||||
return;
|
||||
}
|
||||
|
||||
AirScout_asCommunicationPort = airScout_asCommunicationPort;
|
||||
}
|
||||
|
||||
@@ -957,10 +1040,13 @@ public class ChatPreferences {
|
||||
return AirScout_asUDPListenerEnabled;
|
||||
}
|
||||
|
||||
public void setAirScout_asUDPListenerEnabled(boolean airScout_asUDPListenerEnabled) {
|
||||
public void setAirScout_asUDPListenerEnabled(
|
||||
boolean airScout_asUDPListenerEnabled
|
||||
) {
|
||||
AirScout_asUDPListenerEnabled = airScout_asUDPListenerEnabled;
|
||||
}
|
||||
|
||||
|
||||
public String getChatState() {
|
||||
return chatState;
|
||||
}
|
||||
@@ -1667,6 +1753,18 @@ public class ChatPreferences {
|
||||
snifferWords.appendChild(temp);
|
||||
}
|
||||
|
||||
Element snifferCallSigns =
|
||||
doc.createElement("snifferCallSigns");
|
||||
rootElement.appendChild(snifferCallSigns);
|
||||
|
||||
for (String callSign
|
||||
: lstNotify_QSOSniffer_sniffedCallSignList) {
|
||||
Element temp = doc.createElement("callSign");
|
||||
temp.setTextContent(callSign);
|
||||
snifferCallSigns.appendChild(temp);
|
||||
}
|
||||
|
||||
|
||||
Element snifferPrefixes = doc.createElement("snifferPrefixes");
|
||||
rootElement.appendChild(snifferPrefixes);
|
||||
|
||||
@@ -2265,7 +2363,13 @@ public class ChatPreferences {
|
||||
notify_dxClusterServerEnabled = getBoolean(notificationsEl, notify_dxClusterServerEnabled, "notify_dxClusterServerEnabled");
|
||||
notify_DXClusterServerTriggerBearing = getBoolean(notificationsEl, notify_DXClusterServerTriggerBearing, "notify_DXClusterServerTriggerBearing");
|
||||
notify_DXClusterServerTriggerOnQRGDetect = getBoolean(notificationsEl, notify_DXClusterServerTriggerOnQRGDetect, "notify_DXClusterServerTriggerOnQRGDetect");
|
||||
notify_dxclusterServerPort = getInt(notificationsEl, notify_dxclusterServerPort, "notify_dxclusterServerPort");
|
||||
setNotify_dxclusterServerPort(
|
||||
getInt(
|
||||
notificationsEl,
|
||||
notify_dxclusterServerPort,
|
||||
"notify_dxclusterServerPort"
|
||||
)
|
||||
);
|
||||
|
||||
String spotter = getText(notificationsEl, null, "notify_DXCSrv_SpottersCallSign");
|
||||
if (spotter != null) {
|
||||
@@ -2316,15 +2420,58 @@ public class ChatPreferences {
|
||||
|
||||
Element airScoutEl = getFirstElement(doc, "AirScoutQuerier");
|
||||
if (airScoutEl != null) {
|
||||
AirScout_asUDPListenerEnabled = getBoolean(airScoutEl, AirScout_asUDPListenerEnabled, "asQry_airScoutCommunicationEnabled");
|
||||
AirScout_asServerNameString = getText(airScoutEl, AirScout_asServerNameString, "asQry_airScoutServerName");
|
||||
AirScout_asClientNameString = getText(airScoutEl, AirScout_asClientNameString, "asQry_airScoutClientName");
|
||||
AirScout_asCommunicationPort = getInt(airScoutEl, AirScout_asCommunicationPort, "asQry_airScoutUDPPort");
|
||||
AirScout_asBandString = getText(airScoutEl, AirScout_asBandString, "asQry_airScoutBandValue");
|
||||
setAirScout_asUDPListenerEnabled(
|
||||
getBoolean(
|
||||
airScoutEl,
|
||||
AirScout_asUDPListenerEnabled,
|
||||
"asQry_airScoutCommunicationEnabled"
|
||||
)
|
||||
);
|
||||
|
||||
setAirScout_asServerNameString(
|
||||
getText(
|
||||
airScoutEl,
|
||||
AirScout_asServerNameString,
|
||||
"asQry_airScoutServerName"
|
||||
)
|
||||
);
|
||||
|
||||
setAirScout_asClientNameString(
|
||||
getText(
|
||||
airScoutEl,
|
||||
AirScout_asClientNameString,
|
||||
"asQry_airScoutClientName"
|
||||
)
|
||||
);
|
||||
|
||||
setAirScout_asCommunicationPort(
|
||||
getInt(
|
||||
airScoutEl,
|
||||
AirScout_asCommunicationPort,
|
||||
"asQry_airScoutUDPPort"
|
||||
)
|
||||
);
|
||||
|
||||
setAirScout_asBandString(
|
||||
getText(
|
||||
airScoutEl,
|
||||
AirScout_asBandString,
|
||||
"asQry_airScoutBandValue"
|
||||
)
|
||||
);
|
||||
|
||||
System.out.println(
|
||||
"[ChatPreferences, info]: AirScout querier enabled=" + AirScout_asUDPListenerEnabled
|
||||
+ ", band=" + AirScout_asBandString);
|
||||
"[ChatPreferences, info]: AirScout integration enabled="
|
||||
+ AirScout_asUDPListenerEnabled
|
||||
+ ", server identifier="
|
||||
+ AirScout_asServerNameString
|
||||
+ ", client identifier="
|
||||
+ AirScout_asClientNameString
|
||||
+ ", port="
|
||||
+ AirScout_asCommunicationPort
|
||||
+ ", band="
|
||||
+ AirScout_asBandString
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2374,6 +2521,45 @@ public class ChatPreferences {
|
||||
/**
|
||||
* Case QSO-sniffer lists (added later; older configs won't have them)
|
||||
*/
|
||||
|
||||
list = doc.getElementsByTagName("snifferCallSigns");
|
||||
|
||||
if (list != null && list.getLength() != 0) {
|
||||
lstNotify_QSOSniffer_sniffedCallSignList.clear();
|
||||
|
||||
for (int temp = 0; temp < list.getLength(); temp++) {
|
||||
Node node = list.item(temp);
|
||||
|
||||
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||
NodeList children = node.getChildNodes();
|
||||
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
Node child = children.item(i);
|
||||
|
||||
if (child.getNodeType()
|
||||
== Node.ELEMENT_NODE) {
|
||||
String callSign =
|
||||
child.getTextContent();
|
||||
|
||||
if (callSign != null
|
||||
&& !callSign.isBlank()) {
|
||||
String normalizedCallSign =
|
||||
callSign
|
||||
.trim()
|
||||
.toUpperCase();
|
||||
|
||||
if (!lstNotify_QSOSniffer_sniffedCallSignList
|
||||
.contains(normalizedCallSign)) {
|
||||
lstNotify_QSOSniffer_sniffedCallSignList
|
||||
.add(normalizedCallSign);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list = doc.getElementsByTagName("snifferWords");
|
||||
if (list != null && list.getLength() != 0) {
|
||||
// reset to avoid duplicates when reloading
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user