refactoring of the SimpleLogFile-parser

This commit is contained in:
Marc Froehlich
2026-08-28 23:19:56 +02:00
parent d475c6b2c4
commit 94fc13e3c3
8 changed files with 414 additions and 364 deletions
@@ -3,6 +3,7 @@ package kst4contest.controller;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.Selector;
import java.nio.file.Path;
import java.sql.SQLException;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
@@ -2016,6 +2017,115 @@ private ObservableList<String>
return matchingMembers;
}
/**
* Applies the global Worked state from the Simplelogfile to every active
* callsign variant with the same base callsign. UI-backed state is changed only
* on the JavaFX Application Thread.
*
* @param workedBaseCalls normalized callsigns detected in the selected file
*/
public void applySimpleLogWorkedBaseCalls(Set<String> workedBaseCalls) {
Set<String> normalizedWorkedBaseCalls = normalizeWorkedBaseCalls(workedBaseCalls);
if (normalizedWorkedBaseCalls.isEmpty()) {
return;
}
runOnFxThread(() -> {
int changedMembers = markSimpleLogWorkedMembers(
activeChatMembersByCallAndCategory.values(), normalizedWorkedBaseCalls);
int changedClusterMessages = markSimpleLogWorkedClusterMessages(
lst_clusterMemberList, normalizedWorkedBaseCalls);
if (changedMembers > 0) {
fireUserListUpdate("Simplelogfile Worked status updated");
}
LOGGER.log(Level.FINE,
"Simplelogfile marked {0} active members and {1} cluster messages as worked.",
new Object[] { changedMembers, changedClusterMessages });
});
}
static int markSimpleLogWorkedMembers(
Collection<ChatMember> members,
Set<String> workedBaseCalls
) {
if (members == null || workedBaseCalls == null || workedBaseCalls.isEmpty()) {
return 0;
}
int changed = 0;
for (ChatMember member : members) {
if (member == null || member.isWorked()) {
continue;
}
String memberCall = member.getCallSignRaw() != null
? member.getCallSignRaw() : member.getCallSign();
String baseCall = ChatMember.normalizeCallSignToBaseCallSign(memberCall);
if (baseCall != null && workedBaseCalls.contains(baseCall.toUpperCase(Locale.ROOT))) {
member.setWorked(true);
changed++;
}
}
return changed;
}
static int markSimpleLogWorkedClusterMessages(
Collection<ClusterMessage> clusterMessages,
Set<String> workedBaseCalls
) {
if (clusterMessages == null || workedBaseCalls == null || workedBaseCalls.isEmpty()) {
return 0;
}
int changed = 0;
for (ClusterMessage clusterMessage : clusterMessages) {
if (clusterMessage == null || clusterMessage.isReceiverWkd()
|| clusterMessage.getReceiver() == null) {
continue;
}
ChatMember receiver = clusterMessage.getReceiver();
String receiverCall = receiver.getCallSignRaw() != null
? receiver.getCallSignRaw() : receiver.getCallSign();
String baseCall = ChatMember.normalizeCallSignToBaseCallSign(receiverCall);
if (baseCall != null && workedBaseCalls.contains(baseCall.toUpperCase(Locale.ROOT))) {
clusterMessage.setReceiverWkd(true);
changed++;
}
}
return changed;
}
private static Set<String> normalizeWorkedBaseCalls(Set<String> workedBaseCalls) {
if (workedBaseCalls == null || workedBaseCalls.isEmpty()) {
return Set.of();
}
Set<String> normalizedCalls = new HashSet<>();
for (String callSign : workedBaseCalls) {
String baseCall = ChatMember.normalizeCallSignToBaseCallSign(callSign);
if (baseCall != null && !baseCall.isBlank()) {
normalizedCalls.add(baseCall.toUpperCase(Locale.ROOT));
}
}
return Set.copyOf(normalizedCalls);
}
/**
* Notifies the UI after a missing Simplelogfile was created successfully.
*
* @param filePath absolute path of the new file
*/
public void notifySimpleLogFileCreated(Path filePath) {
if (filePath == null || statusListener == null) {
return;
}
runOnFxThread(() -> statusListener.onSimpleLogFileCreated(filePath));
}
/**
* Copies the band-specific NOT-QRV state to every active category variant of the
* same base callsign. The database already uses callSignRaw as its key; applying
@@ -4098,4 +4208,4 @@ private ObservableList<String>
return "Sniffed: (" + senderCall + " > " + receiverCall + ") " + msgText;
}
}
}
@@ -1,5 +1,7 @@
package kst4contest.controller;
import java.nio.file.Path;
import kst4contest.model.ThreadStateMessage;
public interface StatusUpdateListener {
@@ -33,4 +35,13 @@ public interface StatusUpdateListener {
// Optional for non-UI listeners.
}
}
/**
* Called after KST4Contest successfully creates a missing Simplelogfile.
*
* @param filePath absolute path of the newly created file
*/
default void onSimpleLogFileCreated(Path filePath) {
// Optional for non-UI listeners.
}
}
@@ -1,12 +1,13 @@
package kst4contest.controller;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -14,73 +15,50 @@ import kst4contest.model.ChatMember;
public class UCXLogFileToHashsetParser {
public BufferedReader fileReader;
// private final String PTRN_CallSign = "(([a-zA-Z]{1,2}[\\d{1}]?\\/)?(\\d{1}[a-zA-Z][\\d{1}][a-zA-Z]{1,3})((\\/p)|(\\/\\d))?)|(([a-zA-Z0-9]{1,2}[\\d{1}]?\\/)?(([a-zA-Z]{1,2}(\\d{1}[a-zA-Z]{1,4})))((\\/p)|(\\/\\d))?)"; //OLD, S51AR for example will not work
private final String PTRN_CallSign = "(([a-zA-Z]{1,2}[\\d]{1}?\\/)?(\\d{1}[a-zA-Z][\\d]{1}[a-zA-Z]{1,3})((\\/p)|(\\/\\d))?)|(([a-zA-Z0-9]{1,2}[\\d]{1}?\\/)?(([a-zA-Z]{1,2}(\\d{1}[a-zA-Z]{1,4})))((\\/p)|(\\/\\d))?)|([A-Z]\\d{2}[A-Z]{1,3})";
private static final Pattern CALL_SIGN_PATTERN = Pattern.compile(
"(([a-zA-Z]{1,2}[\\d]{1}?\\/)?(\\d{1}[a-zA-Z][\\d]{1}[a-zA-Z]{1,3})((\\/p)|(\\/\\d))?)"
+ "|(([a-zA-Z0-9]{1,2}[\\d]{1}?\\/)?(([a-zA-Z]{1,2}(\\d{1}[a-zA-Z]{1,4})))((\\/p)|(\\/\\d))?)"
+ "|([A-Z]\\d{2}[A-Z]{1,3})");
private final Path logFile;
public UCXLogFileToHashsetParser(String filePathAndName) {
try {
fileReader = new BufferedReader(new InputStreamReader(new FileInputStream(new File(filePathAndName))));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.logFile = Path.of(filePathAndName);
}
/**
* check if a line of the ucxlog-Logfile inhibits a Callsign<br/>
* <b>returns ChatMember = null, if no frequency found</b>
*
* @param chatMessage
*/
private ChatMember checkIfLineInhibitsCallSign(String line) {
Pattern pattern = Pattern.compile(PTRN_CallSign);
Matcher matcher = pattern.matcher(line);
String matchedString = "";
private String findLastCallSign(String line) {
Matcher matcher = CALL_SIGN_PATTERN.matcher(line);
String matchedCallSign = "";
while (matcher.find()) {
matchedString = matcher.group();
// System.out.println("[UCXLogFile:] Processed worked Callsign from file: " + matchedString);
matchedCallSign = matcher.group();
}
ChatMember newChatMember = new ChatMember();
newChatMember.setCallSign(matchedString.toUpperCase());
return newChatMember;
return matchedCallSign.toUpperCase(Locale.ROOT);
}
/**
* Parses an ucxlog-live-file (full qualified path given by constructor
* argument), looks by regex for callsigns and builds a hashmap with only one
* entry by callsign
* Parses the selected log file and returns every detected station as a
* normalized base callsign. The reader is closed after each pass so the logging
* application can continue replacing or rotating the file.
*
* @return unique normalized base callsigns found in the file
* @throws IOException if the file cannot be read
*/
public HashMap<String, String> parse() throws IOException {
public Set<String> parse() throws IOException {
Set<String> workedBaseCalls = new HashSet<>();
HashMap<String, String> chatMemberMap = new HashMap();
String line;
while ((line = fileReader.readLine()) != null) {
// System.out.println("raw: " + line);
ChatMember temp = checkIfLineInhibitsCallSign(line);
if (temp.getCallSign() != "") {
chatMemberMap.put(temp.getCallSign(), temp.getCallSign());
try (BufferedReader fileReader = Files.newBufferedReader(logFile, Charset.defaultCharset())) {
String line;
while ((line = fileReader.readLine()) != null) {
String matchedCallSign = findLastCallSign(line);
String baseCallSign = ChatMember.normalizeCallSignToBaseCallSign(matchedCallSign);
if (baseCallSign != null && !baseCallSign.isBlank()) {
workedBaseCalls.add(baseCallSign.toUpperCase(Locale.ROOT));
}
}
}
// System.out.println(chatMemberMap.size());
return chatMemberMap;
return workedBaseCalls;
}
}
@@ -1,325 +1,100 @@
package kst4contest.controller;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.Set;
import java.util.TimerTask;
import javafx.collections.ObservableList;
import kst4contest.model.ChatMember;
import kst4contest.model.ClusterMessage;
import kst4contest.view.GuiUtils;
import java.util.logging.Level;
import java.util.logging.Logger;
public class UserActualizationTask extends TimerTask {
private ChatController client;
private static final Logger LOGGER = Logger.getLogger(UserActualizationTask.class.getName());
private final ChatController client;
public UserActualizationTask(ChatController client) {
this.client = client;
}
@Override
public void run() {
Thread.currentThread().setName("UserActualizationTask");
/*
* File-based log synchronization is optional. Do not create, open or parse the
* configured file while the feature is disabled.
*/
try {
updateWorkedCallSignsFromFile();
} catch (RuntimeException exception) {
LOGGER.log(Level.WARNING,
"Unexpected failure while updating Worked callsigns from the Simplelogfile; "
+ "the periodic task will continue.",
exception);
}
}
private void updateWorkedCallSignsFromFile() {
if (!client.getChatPreferences().isLogsynch_fileBasedWkdCallInterpreterEnabled()) {
return;
}
// System.out.println("[Useract: ] Thread runned now");
// System.out.println("***********************Useract started");
/**
* ******************************************since here: old mechanic for
* marking worked stations by .ucx-file
*/
HashMap<String, String> fetchedWorkedSet = new HashMap<>();
// HashMap<String, String> fetchedWorkedSetUdpBckup = new HashMap<>();
File f = new File(this.client.getChatPreferences().getLogsynch_fileBasedWkdCallInterpreterFileNameReadOnly());
if (!f.exists() && !f.isDirectory()) {
try {
f.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String configuredFileName = client.getChatPreferences()
.getLogsynch_fileBasedWkdCallInterpreterFileNameReadOnly();
if (configuredFileName == null || configuredFileName.isBlank()) {
LOGGER.warning("Cannot read the Simplelogfile because no file is selected.");
return;
}
UCXLogFileToHashsetParser getWorkedCallsignsOfUCXLogFile = new UCXLogFileToHashsetParser(
this.client.getChatPreferences().getLogsynch_fileBasedWkdCallInterpreterFileNameReadOnly());
final Path logFile;
try {
logFile = Path.of(configuredFileName).toAbsolutePath().normalize();
} catch (InvalidPathException exception) {
LOGGER.log(Level.WARNING,
"Cannot use the configured Simplelogfile path: " + configuredFileName,
exception);
return;
}
boolean created = createMissingLogFile(logFile);
if (!Files.isRegularFile(logFile)) {
return;
}
try {
fetchedWorkedSet = getWorkedCallsignsOfUCXLogFile.parse();
System.out.println("USERACT: fetchedWorkedSet size: " + fetchedWorkedSet.size());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Set<String> workedBaseCalls = new UCXLogFileToHashsetParser(logFile.toString()).parse();
client.applySimpleLogWorkedBaseCalls(workedBaseCalls);
LOGGER.log(Level.FINE,
"Read {0} unique base callsigns from Simplelogfile {1}.",
new Object[] { workedBaseCalls.size(), logFile });
} catch (IOException exception) {
LOGGER.log(Level.WARNING, "Cannot read Simplelogfile " + logFile, exception);
}
ObservableList<ChatMember> praktiKSTActiveUserList = this.client.getLst_chatMemberList();
for (Iterator iterator = praktiKSTActiveUserList.iterator(); iterator.hasNext();) {
ChatMember chatMember = (ChatMember) iterator.next();
// System.out.println(chatMember.getCallSign());
// System.out.println("USERACT active user list entries " + praktiKSTActiveUserList.size());
if (fetchedWorkedSet.containsKey(chatMember.getCallSign())) {
chatMember.setWorked(true);
System.out.println("[USERACT, info:] marking Chatuser " + chatMember.getCallSign()
+ " as worked, based on READONLY-Logfile.");
}
// if (fetchedWorkedSetUdpBckup.containsKey(chatMember.getCallSign())) {
// chatMember.setWorked(true);
// System.out.println("[USERACT, info:] marking Chatuser " + chatMember.getCallSign() + " as worked, based on UDPLsnBackup-Logfile.");
// }
// GuiUtils.triggerGUIFilteredChatMemberListChange(this.client); //todo: quick and dirty gui fix
if (created) {
client.notifySimpleLogFileCreated(logFile);
}
ObservableList<ClusterMessage> praktiKSTClusterList = this.client.getLst_clusterMemberList();
for (Iterator iterator = praktiKSTClusterList.iterator(); iterator.hasNext();) {
ClusterMessage clusterMessage = (ClusterMessage) iterator.next();
if (fetchedWorkedSet.containsKey(clusterMessage.getReceiver().getCallSign())) {
clusterMessage.setReceiverWkd(true);
System.out.println("[USERACT, info:] marking Clusterspotted "
+ clusterMessage.getReceiver().getCallSign() + " as worked.");
}
// if (fetchedWorkedSetUdpBckup.containsKey(clusterMessage.getReceiver().getCallSign())) {
// clusterMessage.setReceiverWkd(true);
// System.out.println("[USERACT, info:] marking Clusterspotted "
// + clusterMessage.getReceiver().getCallSign() + " as worked.");
// }
}
/**
* ******************************************end here: old mechanic for marking
* worked stations by .ucx-file
*
*/
/**
* ******************************************since here: new mechanic for
* marking worked stations by udp/adif based information
*/
// HashMap<String, String> fetchedWorkedMap = new HashMap<>();
//
// fetchedWorkedMap = this.client.getMap_ucxLogInfoWorkedCalls();
// ObservableList<ChatMember> praktiKSTActiveUserList1 = this.client.getLst_chatMemberList();
//
// for (Iterator iterator = praktiKSTActiveUserList.iterator(); iterator.hasNext();) {
// ChatMember chatMember = (ChatMember) iterator.next();
//
// if (fetchedWorkedMap.containsKey(chatMember.getCallSign())) {
// chatMember.setWorked(true);
// System.out.println("[USERACT, info:] marking Chatuser " + chatMember.getCallSign() + " as worked based on UDP Log Info Collector.");
// }
// }
// ObservableList<ClusterMessage> praktiKSTClusterList1 = this.client.getLst_clusterMemberList();
//
// for (Iterator iterator = praktiKSTClusterList.iterator(); iterator.hasNext();) {
// ClusterMessage clusterMessage = (ClusterMessage) iterator.next();
//
// if (fetchedWorkedMap.containsKey(clusterMessage.getReceiver().getCallSign())) {
// clusterMessage.setReceiverWkd(true);
// System.out.println("[USERACT, info:] marking Clusterspotted "
// + clusterMessage.getReceiver().getCallSign() + " as worked based on UDP Log Info Collector.");
// }
//
// }
//
// UCXLogFileToHashsetParser getWorkedCallsignsOfUCXLogFile = new UCXLogFileToHashsetParser(
// "C:\\UcxLog\\Logs\\DO5AMF\\DVU322_I.UCX");
// try {
// fetchedWorkedSet = getWorkedCallsignsOfUCXLogFile.parse();
// } catch (IOException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
//
// ObservableList<ChatMember> praktiKSTActiveUserList = this.client.getLst_chatMemberList();
//
// for (Iterator iterator = praktiKSTActiveUserList.iterator(); iterator.hasNext();) {
// ChatMember chatMember = (ChatMember) iterator.next();
//
// if (fetchedWorkedSet.containsKey(chatMember.getCallSign())) {
// chatMember.setWorked(true);
// System.out.println("[USERACT, info:] marking Chatuser " + chatMember.getCallSign() + " as worked.");
// }
// }
//
//
// ObservableList<ClusterMessage> praktiKSTClusterList = this.client.getLst_clusterMemberList();
//
// for (Iterator iterator = praktiKSTClusterList.iterator(); iterator.hasNext();) {
// ClusterMessage clusterMessage = (ClusterMessage) iterator.next();
//
// if (fetchedWorkedSet.containsKey(clusterMessage.getReceiver().getCallSign())) {
// clusterMessage.setReceiverWkd(true);
// System.out.println("[USERACT, info:] marking Clusterspotted "
// + clusterMessage.getReceiver().getCallSign() + " as worked.");
// }
//
// }
/**
* ******************************************end here: new mechanic for marking
* worked stations by udp/adif based information
*
*/
// System.out.println("[UserActualizationtask:] Userlist actualization will be performed now. "
// + LocalDateTime.ofInstant(Instant.ofEpochMilli(scheduledExecutionTime()),
// ZoneId.systemDefault()));
// ChatMessage actualizeUserMsg = new ChatMessage();
// actualizeUserMsg.setDirectedToServer(true);
// actualizeUserMsg.setMessage("/show users");
// client.getMessageTXBus().add(actualizeUserMsg);
// Enumeration<String> e = this.client.getChatMemberTable().keys();
//
// while (e.hasMoreElements()) {
// String key = e.nextElement();
//
// System.out.println(this.client.getChatMemberTable().get(key).getCallSign() + ", "
// + this.client.getChatMemberTable().get(key).getQra() + ": "
// + this.client.getChatMemberTable().get(key).getFrequency());
//
// }
// System.out.println("[UserAct]: Show the Cluster with known frequencies now: ");
//
// Enumeration<String> e2 = this.client.getdXClusterMemberTable().keys();
//
// while (e2.hasMoreElements()) {
// String key = e2.nextElement();
//
// System.out.println(this.client.getdXClusterMemberTable().get(key).getCallSign() + ", "
// + this.client.getdXClusterMemberTable().get(key).getQra() + ": "
// + this.client.getdXClusterMemberTable().get(key).getFrequency());
//
// }
// for (int i = 0; i < 100; i++) {
//
// System.out.print("\n");
// }
/**
* keeepalive start
*/
// ChatMessage keepAliveMSG = new ChatMessage();
// keepAliveMSG.setMessageText("\r");
// keepAliveMSG.setMessageDirectedToServer(true);
//
// System.out.println(new Utils4KST().time_generateCurrentMMDDhhmmTimeString() + " [UserAct]: Sending keepalive: "
// + keepAliveMSG.getMessageText());
// /**
// * Sending keepalive
// */
// this.client.getMessageTXBus().add(keepAliveMSG);
/**
* keeepalive end
*/
// System.out.println("[UserAct]: Show the Userlist with known frequencies sorted now: ");
// ObservableList<ChatMember> userlist = this.client.getLst_chatMemberList();
// for (Iterator iterator = userlist.iterator(); iterator.hasNext();) {
// ChatMember chatMember = (ChatMember) iterator.next();
// System.out.println("[Useract] Entry " + this.client.getLst_chatMemberList().indexOf(chatMember) + ": " + chatMember.getCallSign());
// }
//
// String chatMembers ="";
// SortedSet<String> keys = new TreeSet<>(this.client.getChatMemberTable().keySet());
// for (String key : keys) {
//
// chatMembers += this.client.getChatMemberTable().get(key).getCallSign() + ", "
// + this.client.getChatMemberTable().get(key).getName() + " in "
// + this.client.getChatMemberTable().get(key).getQra() + " @ QRG: "
// + this.client.getChatMemberTable().get(key).getFrequency() + "\n";
//
// System.out.println(this.client.getChatMemberTable().get(key).getCallSign() + ", "
// + this.client.getChatMemberTable().get(key).getName() + " in "
// + this.client.getChatMemberTable().get(key).getQra() + " @ QRG: "
// + this.client.getChatMemberTable().get(key).getFrequency());
// }
// System.out.println("\n[UserAct]: Show the Clusterlist with known frequencies sorted now: ");
// String dxcMembers ="";
// SortedSet<String> keys2 = new TreeSet<>(this.client.getdXClusterMemberTable().keySet());
// for (String key : keys2) {
// System.out.println(this.client.getdXClusterMemberTable().get(key).getCallSign() + " in "
// + this.client.getdXClusterMemberTable().get(key).getQra() + " @ QRG: "
// + this.client.getdXClusterMemberTable().get(key).getFrequency());
//
// dxcMembers += this.client.getdXClusterMemberTable().get(key).getCallSign() + " in "
// + this.client.getdXClusterMemberTable().get(key).getQra() + " @ QRG: "
// + this.client.getdXClusterMemberTable().get(key).getFrequency();
//
// }
// File userListLogger = new File(new Utils4KST().time_generateCurrentMMddString() + "_praktiKST_userlist.txt");
//
// FileWriter fileWriterRAWChatMSGOut = null;
//
// try {
// fileWriterRAWChatMSGOut = new FileWriter(userListLogger, true);
// } catch (IOException e1) {
// // TODO Auto-generated catch block
// e1.printStackTrace();
// }
//
// BufferedWriter bufwrtrRawMSGOut;
//
// bufwrtrRawMSGOut = new BufferedWriter(fileWriterRAWChatMSGOut);
// System.out.println("#######################################" + chatMembers);
// try {
// bufwrtrRawMSGOut.write(new Utils4KST().time_generateCurrentMMDDhhmmTimeString() + " " +this.client.getChatMemberTable().size() + " Chatmembers:\n" + chatMembers+ "\n");
// bufwrtrRawMSGOut.write(new Utils4KST().time_generateCurrentMMDDhhmmTimeString() + " " + this.client.getdXClusterMemberTable().size() + " Clusterentries:\n" + dxcMembers + "\n");
// bufwrtrRawMSGOut.flush();
// } catch (IOException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// try {
// bufwrtrRawMSGOut.close();
// } catch (IOException e) {
// TODO Auto-generated catch block
// e.printStackTrace();
// }
}
private boolean createMissingLogFile(Path logFile) {
if (Files.exists(logFile)) {
if (!Files.isRegularFile(logFile)) {
LOGGER.log(Level.WARNING,
"The selected Simplelogfile path is not a regular file: {0}",
logFile);
}
return false;
}
try {
Files.createFile(logFile);
LOGGER.log(Level.INFO, "Created missing Simplelogfile {0}.", logFile);
return true;
} catch (FileAlreadyExistsException exception) {
return false;
} catch (IOException | SecurityException exception) {
LOGGER.log(Level.WARNING, "Cannot create Simplelogfile " + logFile, exception);
return false;
}
}
}
@@ -84,6 +84,9 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
private static final Logger LOGGER = Logger.getLogger(
Kst4ContestApplication.class.getName());
private static final String SIMPLE_LOG_MANUAL_URL =
"https://kst4contest.hamradioonline.de/manual/en/log-sync/"
+ "#method-1-universal-file-based-callsign-interpreter-simplelogfile";
private boolean gridSquareHighlightEnabled = false;
@@ -12129,6 +12132,28 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
});
}
@Override
public void onSimpleLogFileCreated(Path filePath) {
Alert alert = new Alert(AlertType.INFORMATION);
alert.setTitle("Simplelogfile created");
alert.setHeaderText("The selected Simplelogfile did not exist and has been created");
Label explanation = new Label(
"File: " + filePath + "\n\n"
+ "Configure your logging application to write its live log to this file. "
+ "Then log a test QSO and verify that the callsign is marked as worked "
+ "in KST4Contest within one minute.");
explanation.setWrapText(true);
Hyperlink manualLink = new Hyperlink("Open the Simplelogfile manual");
manualLink.setOnAction(event -> getHostServices().showDocument(SIMPLE_LOG_MANUAL_URL));
VBox content = new VBox(10, explanation, manualLink);
content.setPrefWidth(560);
alert.getDialogPane().setContent(content);
alert.show();
}
/**
* Forces the station FilteredList to evaluate all active predicates again.
@@ -0,0 +1,51 @@
package kst4contest.controller;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.Set;
import kst4contest.model.ChatMember;
import kst4contest.model.ClusterMessage;
import org.junit.jupiter.api.Test;
class ChatControllerSimpleLogTest {
@Test
void marksEveryActiveVariantOfWorkedBaseCallsign() {
ChatMember categoryTwoVariant = member("9A0BB-2");
ChatMember categoryThreeVariant = member("9A0BB-70");
ChatMember unrelated = member("DL1ABC");
int changed = ChatController.markSimpleLogWorkedMembers(
List.of(categoryTwoVariant, categoryThreeVariant, unrelated),
Set.of("9A0BB"));
assertEquals(2, changed);
assertTrue(categoryTwoVariant.isWorked());
assertTrue(categoryThreeVariant.isWorked());
assertFalse(unrelated.isWorked());
}
@Test
void marksClusterReceiverByBaseCallsignAndHandlesIncompleteMessages() {
ClusterMessage matching = new ClusterMessage();
matching.setReceiver(member("9A0BB-70"));
ClusterMessage incomplete = new ClusterMessage();
int changed = ChatController.markSimpleLogWorkedClusterMessages(
List.of(matching, incomplete), Set.of("9A0BB"));
assertEquals(1, changed);
assertTrue(matching.isReceiverWkd());
assertFalse(incomplete.isReceiverWkd());
}
private static ChatMember member(String callSign) {
ChatMember member = new ChatMember();
member.setCallSign(callSign);
return member;
}
}
@@ -0,0 +1,76 @@
package kst4contest.controller;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.nio.file.Files;
import java.nio.file.Path;
import kst4contest.model.ChatPreferences;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class UserActualizationTaskTest {
@TempDir
Path temporaryDirectory;
@Mock
ChatController controller;
@Mock
ChatPreferences preferences;
@BeforeEach
void configureControllerPreferences() {
when(controller.getChatPreferences()).thenReturn(preferences);
}
@Test
void disabledInterpreterDoesNotCreateOrReadFile() {
when(preferences.isLogsynch_fileBasedWkdCallInterpreterEnabled()).thenReturn(false);
new UserActualizationTask(controller).run();
verify(controller, never()).applySimpleLogWorkedBaseCalls(anySet());
verify(controller, never()).notifySimpleLogFileCreated(org.mockito.ArgumentMatchers.any());
}
@Test
void createsMissingFileAndNotifiesOnlyOnce() {
Path logFile = temporaryDirectory.resolve("created.log").toAbsolutePath().normalize();
when(preferences.isLogsynch_fileBasedWkdCallInterpreterEnabled()).thenReturn(true);
when(preferences.getLogsynch_fileBasedWkdCallInterpreterFileNameReadOnly())
.thenReturn(logFile.toString());
UserActualizationTask task = new UserActualizationTask(controller);
task.run();
task.run();
assertTrue(Files.isRegularFile(logFile));
verify(controller, times(2)).applySimpleLogWorkedBaseCalls(anySet());
verify(controller, times(1)).notifySimpleLogFileCreated(logFile);
}
@Test
void readFailureIsContainedAndDoesNotUpdateUiState() {
when(preferences.isLogsynch_fileBasedWkdCallInterpreterEnabled()).thenReturn(true);
when(preferences.getLogsynch_fileBasedWkdCallInterpreterFileNameReadOnly())
.thenReturn(temporaryDirectory.toString());
assertDoesNotThrow(() -> new UserActualizationTask(controller).run());
verify(controller, never()).applySimpleLogWorkedBaseCalls(anySet());
verify(controller, never()).notifySimpleLogFileCreated(org.mockito.ArgumentMatchers.any());
}
}
@@ -1,24 +1,48 @@
package kst4contest.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
import kst4contest.controller.UCXLogFileToHashsetParser;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
public class TestUCXLogFileToHashsetParser {
class TestUCXLogFileToHashsetParser {
@TempDir
Path temporaryDirectory;
@Test
public static void main(String[] args) {
// TODO Auto-generated method stub
void parsesUniqueWorkedBaseCallsignsWithFixedPattern() throws IOException {
Path logFile = temporaryDirectory.resolve("contest.log");
Files.writeString(logFile, String.join(System.lineSeparator(),
"QSO 001 S53CC 144 MHz",
"QSO 002 9A0BB-70 432 MHz",
"QSO 003 s53cc repeated",
"no station here"));
UCXLogFileToHashsetParser testTheParser = new UCXLogFileToHashsetParser("C:\\UcxLog\\Logs\\DO5AMF\\DVU322_I.UCX");
try {
testTheParser.parse();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Set<String> result = new UCXLogFileToHashsetParser(logFile.toString()).parse();
assertEquals(Set.of("S53CC", "9A0BB"), result);
}
@Test
void closesFileAfterEveryParsePass() throws IOException {
Path logFile = temporaryDirectory.resolve("replaceable.log");
Files.writeString(logFile, "QSO DL1ABC");
UCXLogFileToHashsetParser parser = new UCXLogFileToHashsetParser(logFile.toString());
assertEquals(Set.of("DL1ABC"), parser.parse());
Path replacement = temporaryDirectory.resolve("replacement.log");
Files.move(logFile, replacement);
assertFalse(Files.exists(logFile));
assertTrue(Files.exists(replacement));
}
}