diff --git a/src/urChatBasic/backend/logging/URLogger.java b/src/urChatBasic/backend/logging/URLogger.java index 758155d..4d954ba 100644 --- a/src/urChatBasic/backend/logging/URLogger.java +++ b/src/urChatBasic/backend/logging/URLogger.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.lang.reflect.Field; import java.net.URISyntaxException; +import java.util.Map; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; @@ -28,19 +29,17 @@ public class URLogger public static void init () throws IOException, URISyntaxException { + // System.out.println("LOG CONFIG: "+ LOG4J_CONFIG_FILE); File logDir = new File(Constants.DIRECTORY_LOGS); if (!logDir.exists()) { logDir.mkdir(); } - File logConfigFile = new File(DriverGUI.class.getResource(LOG4J_CONFIG_FILE).toURI()); - if(!logConfigFile.exists()) - throw new IOException("LOG FILE NOT FOUND"); // System.setProperty("log4j2.debug", "true"); - System.setProperty("log4j2.configurationFile", logConfigFile.toString()); + System.setProperty("log4j2.configurationFile", DriverGUI.class.getResource(LOG4J_CONFIG_FILE).toURI().toString()); LOGGER = LoggerFactory.getLogger("urchat"); @@ -89,6 +88,30 @@ public static Marker getMarker (String markerName) // } // } + /** + * Get the path for the logfile associated with the given markerName. + * + * @param markerName The markerName associated with the logfile. + * @return The path for the associated logfile, or null if not found. + */ + public static String getLogFilePath(String markerName) { + // Get the root LoggerConfig + Configuration rootLoggerConfig = currentConfig; + if (rootLoggerConfig != null) { + // Find the appender associated with the given markerName + Map appenders = rootLoggerConfig.getAppenders(); + String appenderName = markerName + "Appender"; + Appender appender = appenders.get(appenderName); + if (appender instanceof FileAppender) { + // If the appender is a FileAppender, return its file name + FileAppender fileAppender = (FileAppender) appender; + return fileAppender.getFileName(); + } + } + // Return null if the logfile for the given markerName is not found + return null; + } + public static void logChannelComms (IRCChannelBase ircChannel, String message) { diff --git a/src/urChatBasic/backend/utils/LogPatternParser.java b/src/urChatBasic/backend/utils/LogPatternParser.java new file mode 100644 index 0000000..7cfcfe9 --- /dev/null +++ b/src/urChatBasic/backend/utils/LogPatternParser.java @@ -0,0 +1,177 @@ +package urChatBasic.backend.utils; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class LogPatternParser +{ + + // Define enum for log patterns + public enum LogPattern + { + DATE("%d", "(?.*UTC?)", Date.class, "UTC "), + SERVER("%marker", "\\s(?[A-Za-z0-9.-]+)-", String.class, "-"), + CHANNEL("%marker", "(?#.*?)\\s", String.class, " "), // Named group for channel, excluding the trailing whitespace + USER("%msg", "(?.*?):", String.class, ": "), + MESSAGE("%msg", "\\s(?.*)$", String.class, ""); + + private final String pattern; + private final String regex; + private final String appendString; + private final Class patternClass; + public final static String PATTERN_LAYOUT = "%d{yyy-MM-dd HH:mm:ss.SSS}{UTC}UTC %marker %msg%n"; + public final static String DATE_LAYOUT = "yyyy-MM-dd HH:mm:ss.SSS"; + + LogPattern (String pattern, String regex, Class patternClass, String appendString) + { + this.pattern = pattern; + this.regex = regex; + this.patternClass = patternClass; + this.appendString = appendString; + } + + public String getPattern () + { + return pattern; + } + + public Class getPatternClass () + { + return patternClass; + } + + public String getRegex () + { + return regex; + } + + public String getMatchGroup () + { + return this.toString(); + } + + public String getAppendString () + { + return appendString; + } + } + + public static Date parseDate (String dateString) + { + dateString = dateString.trim(); + // Step 1: Define the DateTimeFormatter with the pattern + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); + TemporalAccessor temporalAccessor = null; + try{ + // Step 2: Parse the string into a TemporalAccessor object using the formatter + temporalAccessor = formatter.parse(dateString.replace("UTC", "")); + } catch (Exception exc) + { + System.out.println(exc); + } + + // Step 3: Convert the TemporalAccessor to a LocalDateTime object + LocalDateTime localDateTime = LocalDateTime.from(temporalAccessor); + + // Step 4: Convert the LocalDateTime to the local timezone + LocalDateTime localDateTimeInLocalTimeZone = localDateTime + .atZone(ZoneId.of("UTC")) + .withZoneSameInstant(ZoneId.systemDefault()) + .toLocalDateTime(); + + // Step 5: Convert the LocalDateTime to a Date object + Date date = Date.from(localDateTimeInLocalTimeZone.atZone(ZoneId.systemDefault()).toInstant()); + + + return date; + } + + // Method to format Date object to string in UTC + public static String formatDateToString(Date date) { + // Define the DateTimeFormatter with the pattern + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); + + // Convert the Date object to LocalDateTime + LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); + + // Convert the LocalDateTime to UTC time zone + LocalDateTime localDateTimeInUtc = localDateTime.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneId.of("UTC")).toLocalDateTime(); + + // Format the LocalDateTime to string + return formatter.format(localDateTimeInUtc); + } + + // Parse log line using specified pattern + public static void parseLogLine (String logLine) + { + Map parsedValues = new HashMap(); + + for (LogPattern pattern : LogPattern.values()) + { + Pattern regexPattern = Pattern.compile(pattern.getRegex()); + Matcher matcher = regexPattern.matcher(logLine); + if (matcher.find()) + { + String fullMatch = matcher.group(0); + String match = matcher.group(pattern.getMatchGroup()); + System.out.println(pattern.name() + " group: " + match); + // parsedValues.put(pattern.toString(), match); + switch (pattern.getPatternClass().getSimpleName()) { + case "Date": + parsedValues.put(pattern.toString(), parseDate(match)); + logLine = logLine.replaceFirst(fullMatch, "").trim(); + break; + default: + parsedValues.put(pattern.toString(), match); + if(logLine.length() == fullMatch.length()) + break; + + logLine = logLine.replaceFirst(fullMatch, "").trim(); + break; + } + } + } + + System.out.println("Done"); + } + + public static Map parseLogLineFull (String logLine) { + logLine = logLine.trim(); + Map parsedValues = new HashMap<>(); + + StringBuilder combinedRegexBuilder = new StringBuilder(); + combinedRegexBuilder.append(LogPattern.DATE.getRegex()); + combinedRegexBuilder.append(LogPattern.SERVER.getRegex()); + combinedRegexBuilder.append(LogPattern.CHANNEL.getRegex()); + combinedRegexBuilder.append(LogPattern.USER.getRegex()); + combinedRegexBuilder.append(LogPattern.MESSAGE.getRegex()); + String combinedRegex = combinedRegexBuilder.toString(); + + Pattern regexPattern = Pattern.compile(combinedRegex); + Matcher matcher = regexPattern.matcher(logLine); + while (matcher.find()) { + for (LogPattern pattern : LogPattern.values()) { + if (matcher.group(pattern.toString()) != null) { + String match = matcher.group(pattern.getMatchGroup()); + switch (pattern.getPatternClass().getSimpleName()) { + case "Date": + parsedValues.put(pattern.toString(), parseDate(match)); + break; + default: + parsedValues.put(pattern.toString(), match); + break; + } + } + } + } + + return parsedValues; + } +} diff --git a/src/urChatBasic/backend/utils/ReverseLineInputStream.java b/src/urChatBasic/backend/utils/ReverseLineInputStream.java new file mode 100644 index 0000000..f69c150 --- /dev/null +++ b/src/urChatBasic/backend/utils/ReverseLineInputStream.java @@ -0,0 +1,101 @@ +package urChatBasic.backend.utils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; + +/** + * @see https://web.archive.org/web/20220701011119/https://stackoverflow.com/questions/8664705/how-to-read-file-from-end-to-start-in-reverse-order-in-javaa + */ +public class ReverseLineInputStream extends InputStream { + + RandomAccessFile in; + + long currentLineStart = -1; + long currentLineEnd = -1; + long currentPos = -1; + long lastPosInFile = -1; + int lastChar = -1; + + + public ReverseLineInputStream(File file) throws FileNotFoundException { + in = new RandomAccessFile(file, "r"); + currentLineStart = file.length(); + currentLineEnd = file.length(); + lastPosInFile = file.length() -1; + currentPos = currentLineEnd; + + } + + private void findPrevLine() throws IOException { + if (lastChar == -1) { + in.seek(lastPosInFile); + lastChar = in.readByte(); + } + + currentLineEnd = currentLineStart; + + // There are no more lines, since we are at the beginning of the file and no lines. + if (currentLineEnd == 0) { + currentLineEnd = -1; + currentLineStart = -1; + currentPos = -1; + return; + } + + long filePointer = currentLineStart -1; + + while ( true) { + filePointer--; + + // we are at start of file so this is the first line in the file. + if (filePointer < 0) { + break; + } + + in.seek(filePointer); + int readByte = in.readByte(); + + // We ignore last LF in file. search back to find the previous LF. + if (readByte == 0xA && filePointer != lastPosInFile ) { + break; + } + } + // we want to start at pointer +1 so we are after the LF we found or at 0 the start of the file. + currentLineStart = filePointer + 1; + currentPos = currentLineStart; + } + + public int read() throws IOException { + + if (currentPos < currentLineEnd ) { + in.seek(currentPos++); + int readByte = in.readByte(); + return readByte; + } else if (currentPos > lastPosInFile && currentLineStart < currentLineEnd) { + // last line in file (first returned) + findPrevLine(); + if (lastChar != '\n' && lastChar != '\r') { + // last line is not terminated + return '\n'; + } else { + return read(); + } + } else if (currentPos < 0) { + return -1; + } else { + findPrevLine(); + return read(); + } + } + + @Override + public void close() throws IOException { + if (in != null) { + in.close(); + in = null; + } + } +} diff --git a/src/urChatBasic/backend/utils/URProfilesUtil.java b/src/urChatBasic/backend/utils/URProfilesUtil.java index 942147f..f73fa54 100644 --- a/src/urChatBasic/backend/utils/URProfilesUtil.java +++ b/src/urChatBasic/backend/utils/URProfilesUtil.java @@ -82,6 +82,7 @@ public static void deleteProfile (String profileName, boolean fireListeners) if(allProfiles.length > 1) { + Constants.BASE_PREFS.node(profileName).clear(); Constants.LOGGER.info( "Deleting profile [" + profileName + "]."); Constants.BASE_PREFS.node(profileName).removeNode(); if(fireListeners) @@ -271,7 +272,7 @@ public static void setDefaultProfile (String profileName) BASE.put(Constants.KEY_DEFAULT_PROFILE_NAME, profileName); } - public static void createProfile (String profileName) + public static String createProfile (String profileName) { int newProfileNumber = 0; String newProfileName = profileName; @@ -292,6 +293,8 @@ public static void createProfile (String profileName) Constants.LOGGER.info( "Creating new profile [" + newProfileName + "]"); setDefaultSettings(newProfileName); fireListeners(EventType.CREATE); + + return newProfileName; } private static void setDefaultSettings (String profileName) diff --git a/src/urChatBasic/base/Constants.java b/src/urChatBasic/base/Constants.java index 2308ebb..a3b1131 100644 --- a/src/urChatBasic/base/Constants.java +++ b/src/urChatBasic/base/Constants.java @@ -83,6 +83,7 @@ public class Constants public static final String KEY_CLICKABLE_LINKS_ENABLED = "clickable links"; public static final String KEY_EVENT_TICKER_JOINS_QUITS = "show events in ticker"; public static final String KEY_MAIN_WINDOW_JOINS_QUITS = "show events in main window"; + public static final String KEY_LOAD_CHANNEL_LOGS_ON_JOIN = "load channel logs on join"; public static final String KEY_LOG_CHANNEL_ACTIVITY = "log channel history"; public static final String KEY_LOG_SERVER_ACTIVITY = "log server activity"; public static final String KEY_LIMIT_CHANNEL_LINES = "limit number of channel lines"; @@ -129,6 +130,7 @@ public class Constants public static final Boolean DEFAULT_USERS_LIST_ACTIVE = true; public static final Boolean DEFAULT_EVENT_TICKER_JOINS_QUITS = true; public static final Boolean DEFAULT_MAIN_WINDOW_JOINS_QUITS = true; + public static final Boolean DEFAULT_LOAD_CHANNEL_LOGS_ON_JOIN = true; public static final Boolean DEFAULT_LOG_CHANNEL_ACTIVITY = true; public static final Boolean DEFAULT_LOG_SERVER_ACTIVITY = true; public static final Boolean DEFAULT_AUTO_CONNECT_FAVOURITES = false; @@ -152,6 +154,8 @@ public class Constants public static final char SPACES_AHEAD_DELIMITER = ':'; public static final int MESSAGE_LIMIT = 510; public static final String END_MESSAGE = "\r\n"; + public static final int MAXIMUM_QUEUE_SIZE = 100; + // We 'must' match against http(s) in order to define the correct protocol to be used public static final String URL_REGEX = "((http:\\/\\/|https:\\/\\/)(www.)?(([a-zA-Z0-9-]){2,}\\.){1,4}([a-zA-Z]){2,6}(\\/([a-zA-Z-_\\/\\.0-9#:?=&;,]*)?)?)"; public static final String CHANNEL_REGEX = "(?:^|\s)(#([^\s,]+)(?!,))(?:$|\s)"; @@ -263,6 +267,7 @@ public enum ConfigKeys { KEY_CLICKABLE_LINKS_ENABLED(Constants.KEY_CLICKABLE_LINKS_ENABLED, DEFAULT_CLICKABLE_LINKS_ENABLED), KEY_EVENT_TICKER_JOINS_QUITS(Constants.KEY_EVENT_TICKER_JOINS_QUITS, DEFAULT_EVENT_TICKER_JOINS_QUITS), KEY_MAIN_WINDOW_JOINS_QUITS(Constants.KEY_MAIN_WINDOW_JOINS_QUITS, DEFAULT_MAIN_WINDOW_JOINS_QUITS), + KEY_LOAD_CHANNEL_LOGS_ON_JOIN(Constants.KEY_LOAD_CHANNEL_LOGS_ON_JOIN, DEFAULT_LOAD_CHANNEL_LOGS_ON_JOIN), KEY_LOG_CHANNEL_ACTIVITY(Constants.KEY_LOG_CHANNEL_ACTIVITY, DEFAULT_LOG_CHANNEL_ACTIVITY), KEY_LOG_SERVER_ACTIVITY(Constants.KEY_LOG_SERVER_ACTIVITY, DEFAULT_LOG_SERVER_ACTIVITY), KEY_LIMIT_CHANNEL_LINES(Constants.KEY_LIMIT_CHANNEL_LINES, DEFAULT_LIMIT_CHANNEL_LINES), diff --git a/src/urChatBasic/base/IRCChannelBase.java b/src/urChatBasic/base/IRCChannelBase.java index 891eb9e..de0dc9c 100644 --- a/src/urChatBasic/base/IRCChannelBase.java +++ b/src/urChatBasic/base/IRCChannelBase.java @@ -1,6 +1,7 @@ package urChatBasic.base; import urChatBasic.backend.logging.URLogger; +import urChatBasic.backend.utils.ReverseLineInputStream; import urChatBasic.backend.utils.URProfilesUtil; import urChatBasic.base.IRCChannelBase; import urChatBasic.base.Constants.EventType; @@ -18,6 +19,7 @@ import urChatBasic.frontend.UserGUI; import urChatBasic.frontend.UsersListModel; import java.awt.event.*; +import static urChatBasic.backend.utils.LogPatternParser.parseLogLineFull; import java.awt.*; import java.io.*; import java.util.*; @@ -88,7 +90,7 @@ public class IRCChannelBase extends JPanel // Text Area private JTextPane channelTextArea = new JTextPane(); protected JScrollPane channelScroll = new JScrollPane(channelTextArea); - private BlockingQueue messageQueue = new ArrayBlockingQueue<>(20); + private BlockingQueue messageQueue = new ArrayBlockingQueue<>(Constants.MAXIMUM_QUEUE_SIZE); public boolean messageQueueInProgress = false; private LineFormatter lineFormatter; @@ -186,14 +188,14 @@ private void initChannel() fontDialog = new FontDialog(channelName, gui.getStyle(), channelPrefs); - lineFormatter = new LineFormatter(getFontPanel().getStyle(), channelTextArea , getServer(), channelPrefs); + lineFormatter = new LineFormatter(getFontPanel().getStyle(), channelTextArea , channelScroll, getServer(), channelPrefs); } else { markerName = channelName; setSettingsPath(URProfilesUtil.getActiveFavouritesPath().node(channelName)); fontDialog = new FontDialog(channelName, gui.getStyle(), channelPrefs); - lineFormatter = new LineFormatter(getFontPanel().getStyle() , channelTextArea, null, channelPrefs); + lineFormatter = new LineFormatter(getFontPanel().getStyle() , channelTextArea, channelScroll, null, channelPrefs); } // Add Logging Marker @@ -218,6 +220,10 @@ private void initChannel() fontDialog.addFontSaveListener(new SaveFontListener()); myActions = new IRCActions(this); + + if(((InterfacePanel) gui.interfacePanel).isLoadChannelLogsEnabled()) + loadChannelHistory(); + } private class ProfileChangeListener implements ActionListener @@ -458,28 +464,55 @@ public String getMarker () return markerName; } - class MessagePair { + class Message { + private Optional date = Optional.empty(); private String line; private String fromUser; - public MessagePair(String line, String fromUser) { + public Message(String line, String fromUser) { + this.line = line; + this.fromUser = fromUser; + } + + public Message(Date date, String line, String fromUser) { + this.date = Optional.of(date); this.line = line; this.fromUser = fromUser; } - public String getLine() { + public String getLine () { return line; } - public String getUser() { + public String getUser () { return fromUser; } + + public Optional getDate () + { + return date; + } } // TODO: Change this to accept IRCUser instead - public void printText(String line, String fromUser) { + // TODO: Overload method with date object + public void printText(String line, String fromUser) + { + try { + messageQueue.put(new Message(line, fromUser)); + + if(!messageQueueInProgress) + handleMessageQueue(); + + } catch (InterruptedException e) { + Constants.LOGGER.warn(e.getLocalizedMessage(), e); + } + } + + public void printText(Date messageDate, String message, String fromUser) + { try { - messageQueue.put(new MessagePair(line, fromUser)); + messageQueue.put(new Message(messageDate, message, fromUser)); if(!messageQueueInProgress) handleMessageQueue(); @@ -489,54 +522,48 @@ public void printText(String line, String fromUser) { } } + public boolean messageQueueFull() + { + + return (messageQueue.remainingCapacity() == 0); + } + public boolean messageQueueWorking() { return (!messageQueue.isEmpty() || messageQueueInProgress); } - public void handleMessageQueue() + public void handleMessageQueue () { SwingUtilities.invokeLater(new Runnable() { - public void run() + public void run () { while (!messageQueue.isEmpty()) { try { messageQueueInProgress = true; - MessagePair messagePair = messageQueue.take(); + Message message = messageQueue.take(); - if(null == messagePair) + if (null == message) { messageQueueInProgress = false; continue; } - String line = messagePair.getLine(); - String fromUser = messagePair.getUser(); - - Document document = lineFormatter.getDocument(); - Element root = lineFormatter.getDocument().getDefaultRootElement(); + Optional messageDate = message.getDate(); + String line = message.getLine(); + String fromUser = message.getUser(); int lineLimit = ((InterfacePanel) gui.interfacePanel).getLimitChannelLinesCount(); - if(IRCChannelBase.this instanceof IRCServer) + if (IRCChannelBase.this instanceof IRCServer) lineLimit = ((InterfacePanel) gui.interfacePanel).getLimitServerLinesCount(); - if(null != messagePair && root.getElementCount() > lineLimit) + if (null != message && getLineFormatter().getLineCount() > lineLimit) { - Element firstLine = root.getElement(0); - int endIndex = firstLine.getEndOffset(); - - try - { - document.remove(0, endIndex); - } - catch(BadLocationException ble) - { - Constants.LOGGER.error(ble.getLocalizedMessage()); - } + getLineFormatter().removeFirstLine(); } if (null == channelTextArea) @@ -545,12 +572,13 @@ public void run() return; } + // StyledDocument doc = channelTextArea.getStyledDocument(); IRCUser fromIRCUser = getCreatedUser(fromUser); // If we received a message from a user that isn't in the channel // then add them to the users list. - // But don't add them if it's from the Event Ticker + // But don't add them if it's from the Event Ticker or we're loading historical if (fromIRCUser == null) { if (!fromUser.equals(Constants.EVENT_USER)) @@ -560,51 +588,57 @@ public void run() // fromIRCUser = getCreatedUsers(fromUser); // Constants.LOGGER.error("Message from a user that isn't in the user list!"); fromIRCUser = server.getIRCUser(fromUser); - addToUsersList(fromIRCUser); + + // Only add users if we haven't specified a date, which means we aren't loading historical messages + if (messageDate.isEmpty()) + addToUsersList(fromIRCUser); } } - if (fromUser.equals(Constants.EVENT_USER) || !fromIRCUser.isMuted()) { - lineFormatter.formattedDocument(new Date(), fromIRCUser, fromUser, line); - - if(IRCChannelBase.this instanceof IRCServerBase) - { - if (((InterfacePanel) gui.interfacePanel).saveServerHistory()) - URLogger.logChannelComms(IRCChannelBase.this, (fromIRCUser != null ? fromIRCUser.getName() : fromUser) + ": " + line); - } else if(!(IRCChannelBase.this instanceof IRCServerBase)) - { - if (((InterfacePanel) gui.interfacePanel).saveChannelHistory()) - URLogger.logChannelComms(IRCChannelBase.this, (fromIRCUser != null ? fromIRCUser.getName() : fromUser) + ": " + line); - } - - if (server.getNick() != null && line.indexOf(server.getNick()) > -1) - { - callForAttention(); - } + lineFormatter.appendMessage(messageDate, fromIRCUser, fromUser, line); - // Always alert on IRCPrivate messages - if (IRCChannelBase.this instanceof IRCPrivate) + // Only save history and call events if we aren't loading historical messages + if (messageDate.isEmpty()) { - callForAttention(); + if (IRCChannelBase.this instanceof IRCServerBase) + { + if (((InterfacePanel) gui.interfacePanel).saveServerHistory()) + URLogger.logChannelComms(IRCChannelBase.this, (fromIRCUser != null ? fromIRCUser.getName() : fromUser) + ": " + line); + } else if (!(IRCChannelBase.this instanceof IRCServerBase)) + { + if (((InterfacePanel) gui.interfacePanel).saveChannelHistory()) + URLogger.logChannelComms(IRCChannelBase.this, (fromIRCUser != null ? fromIRCUser.getName() : fromUser) + ": " + line); + } + + if (server.getNick() != null && line.indexOf(server.getNick()) > -1) + { + callForAttention(); + } + + // Always alert on IRCPrivate messages + if (IRCChannelBase.this instanceof IRCPrivate) + { + callForAttention(); + } + + // TODO: Scrolls to the bottom of the channelTextArea on message received, this should be + // disabled + // when the user has scrolled up + // channelTextArea.setCaretPosition(channelTextArea.getDocument().getLength()); } - - // TODO: Scrolls to the bottom of the channelTextArea on message received, this should be - // disabled - // when the user has scrolled up - channelTextArea.setCaretPosition(channelTextArea.getDocument().getLength()); - messageQueueInProgress = false; } + } catch (InterruptedException e) { Constants.LOGGER.error(e.getLocalizedMessage()); } } + messageQueueInProgress = false; } }); - } /** @@ -841,6 +875,7 @@ public class ChannelPopUp extends JPopupMenu private static final long serialVersionUID = 640768684923757684L; JMenuItem nameItem; JMenuItem quitItem; + JMenuItem loadChannelHistory; JMenuItem hideUsersItem; JMenuItem hideTickerItem; public JMenuItem addAsFavouriteItem; @@ -856,6 +891,10 @@ public ChannelPopUp() add(quitItem); quitItem.addActionListener(new QuitItem()); // + loadChannelHistory = new JMenuItem("Load Channel History"); + add(loadChannelHistory); + loadChannelHistory.addActionListener(new LoadChannelHistory()); + // hideUsersItem = new JMenuItem("Toggle Users List"); add(hideUsersItem); hideUsersItem.addActionListener(new ToggleHideUsersListItem()); @@ -909,6 +948,45 @@ public void actionPerformed(ActionEvent arg0) } } + private class LoadChannelHistory implements ActionListener + { + @Override + public void actionPerformed(ActionEvent arg0) + { + loadChannelHistory(); + } + } + + private void loadChannelHistory () + { + Thread fileReadingThread = new Thread(() -> { + String logFilePath = URLogger.getLogFilePath(getMarker()); + String line = ""; + try (BufferedReader br = new BufferedReader (new InputStreamReader (new ReverseLineInputStream(new File(logFilePath))))) { + int maxCount = ((InterfacePanel) gui.interfacePanel).getLimitChannelLinesCount(); + Constants.LOGGER.info("Loading channel history, max line count [" + maxCount + "]"); + int loadCount = 0; + while ((line = br.readLine()) != null && loadCount < maxCount) { + if(messageQueueFull()) + Thread.sleep(10); + + Map parsedLine = parseLogLineFull(line); + if(parsedLine.size() != 0) + { + printText((Date) parsedLine.get("DATE"), parsedLine.get("MESSAGE").toString(), parsedLine.get("USER").toString()); + loadCount++; + } + } + } catch (NullPointerException npe) + { + Constants.LOGGER.info("Log File doesn't yet exist."); + } catch (Exception e) { + e.printStackTrace(); + } + }); + fileReadingThread.start(); + } + private class QuitItem implements ActionListener { @Override diff --git a/src/urChatBasic/frontend/IRCServer.java b/src/urChatBasic/frontend/IRCServer.java index 7f87d3b..7663772 100644 --- a/src/urChatBasic/frontend/IRCServer.java +++ b/src/urChatBasic/frontend/IRCServer.java @@ -513,10 +513,16 @@ public void quitChannel (IRCChannelBase ircChannel) boolean tabExists = Arrays.stream(gui.tabbedPane.getComponents()).anyMatch(channel -> channel.equals(ircChannel)); + try { if (tabExists && gui.tabbedPane.getSelectedComponent().equals(ircChannel)) gui.tabbedPane.setSelectedComponent(gui.previousSelectedTab); + } catch (IllegalArgumentException iae) + { + Constants.LOGGER.debug("Previous Selected tab doesn't exist, unable to revert selection"); + } - gui.tabbedPane.remove(ircChannel); + if (tabExists) + gui.tabbedPane.remove(ircChannel); } @Override diff --git a/src/urChatBasic/frontend/LineFormatter.java b/src/urChatBasic/frontend/LineFormatter.java index 50d337b..d3503f3 100644 --- a/src/urChatBasic/frontend/LineFormatter.java +++ b/src/urChatBasic/frontend/LineFormatter.java @@ -2,7 +2,9 @@ import java.awt.Color; import java.awt.Desktop; +import java.awt.Dimension; import java.awt.Font; +import java.awt.Point; import java.awt.event.ActionEvent; import java.net.URL; import java.time.Duration; @@ -11,18 +13,20 @@ import java.util.Date; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; -import java.util.logging.Level; import java.util.prefs.Preferences; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.AbstractAction; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; import javax.swing.JTextPane; +import javax.swing.JViewport; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.text.AttributeSet; @@ -41,33 +45,37 @@ public class LineFormatter { private String myNick; - private URStyle targetStyle; private Color myForeground; private Color myBackground; private IRCServerBase myServer; private Preferences settingsPath; - private URStyle urlStyle; - private URStyle channelStyle; - private URStyle timeStyle; - private URStyle lineStyle; - private URStyle nickStyle; - private URStyle highStyle; - private URStyle mediumStyle; - private URStyle lowStyle; + // TODO: This should be an enum with all the styles + private URStyle targetStyle; + // private URStyle urlStyle; + // private URStyle channelStyle; + // private URStyle timeStyle; + // private URStyle lineStyle; + // private URStyle nickStyle; + // private URStyle highStyle; + // private URStyle mediumStyle; + // private URStyle lowStyle; private JTextPane docOwner; + private JScrollPane docScroller; public StyledDocument doc; - public URStyle myStyle; + // public URStyle myStyle; private Map formatterStyles = new HashMap<>(); + private Map updatedStyles = new HashMap<>(); private Optional timeLine = Optional.empty(); private AtomicLong updateStylesTime = new AtomicLong(0); public AtomicBoolean updateStylesInProgress = new AtomicBoolean(false); - public LineFormatter(URStyle baseStyle, JTextPane docOwner ,final IRCServerBase server, Preferences settingsPath) + public LineFormatter(URStyle baseStyle, JTextPane docOwner ,JScrollPane docScroller, final IRCServerBase server, Preferences settingsPath) { // TODO: Need to load attributes from formatterPrefs this.settingsPath = settingsPath; this.docOwner = docOwner; + this.docScroller = docScroller; // this.docOwner.setBackground(UIManager.getColor(Constants.DEFAULT_BACKGROUND_STRING)); doc = this.docOwner.getStyledDocument(); @@ -108,27 +116,74 @@ public void initStyles (URStyle baseStyle) targetStyle.getForeground().ifPresent(fg -> myForeground = fg); targetStyle.getBackground().ifPresent(bg -> myBackground = bg); - timeStyle = defaultStyle(null, true); - lineStyle = defaultStyle(null, true); - nickStyle = nickStyle(true); - myStyle = myStyle(true); - channelStyle = channelStyle(true); - urlStyle = urlStyle(true); - highStyle = highStyle(true); - mediumStyle = mediumStyle(true); - lowStyle = lowStyle(true); + // URStyle timeStyle = defaultStyle(null, true); + // URStyle lineStyle = defaultStyle(null, true); + URStyle defaultStyle = defaultStyle(null, true); + URStyle nickStyle = nickStyle(true); + URStyle myStyle = myStyle(true); + URStyle channelStyle = channelStyle(true); + URStyle urlStyle = urlStyle(true); + URStyle highStyle = highStyle(true); + URStyle mediumStyle = mediumStyle(true); + URStyle lowStyle = lowStyle(true); + + // updatedStyles.put(timeStyle.getName(), timeStyle); + // updatedStyles.put(lineStyle.getName(), lineStyle); + updatedStyles.put(defaultStyle.getName(), defaultStyle); + updatedStyles.put(nickStyle.getName(), nickStyle); + updatedStyles.put(myStyle.getName(), myStyle); + updatedStyles.put(channelStyle.getName(), channelStyle); + updatedStyles.put(urlStyle.getName(), urlStyle); + updatedStyles.put(highStyle.getName(), highStyle); + updatedStyles.put(mediumStyle.getName(), mediumStyle); + updatedStyles.put(lowStyle.getName(), lowStyle); + + // TODO: Styles should be an enum + + List changedStyles = new ArrayList<>(updatedStyles.values()); + + for (URStyle updatedStyle : updatedStyles.values()) { + if(formatterStyles.containsKey(updatedStyle.getName()) && formatterStyles.get(updatedStyle.getName()).equals(updatedStyle)) + changedStyles.remove(updatedStyle); + else + formatterStyles.put(updatedStyle.getName(), updatedStyle); + } + } + + public int getStylesHash () + { + int formatterStylesHash = formatterStyles.hashCode(); + int nickFormatHash = String.join("", DriverGUI.gui.getNickFormatString("nick")).hashCode(); + int timeStampHash = DriverGUI.gui.getTimeStampString(new Date(0L)).hashCode(); + + + return formatterStylesHash + nickFormatHash + timeStampHash; + } - formatterStyles.put(timeStyle.getName(), timeStyle); - formatterStyles.put(lineStyle.getName(), lineStyle); - formatterStyles.put(nickStyle.getName(), nickStyle); - formatterStyles.put(myStyle.getName(), myStyle); - formatterStyles.put(channelStyle.getName(), channelStyle); - formatterStyles.put(urlStyle.getName(), urlStyle); - formatterStyles.put(highStyle.getName(), highStyle); - formatterStyles.put(mediumStyle.getName(), mediumStyle); - formatterStyles.put(lowStyle.getName(), lowStyle); + class ViewPortRange { + private int start; + private int end; + public ViewPortRange () + { + JViewport viewport = docScroller.getViewport(); + Point startPoint = viewport.getViewPosition(); + Dimension size = viewport.getExtentSize(); + Point endPoint = new Point(startPoint.x + size.width, startPoint.y + size.height); + start = docOwner.viewToModel2D(startPoint); + end = docOwner.viewToModel2D(endPoint); + } + + public int getStart () + { + return start; + } + + public int getEnd () + { + return end; + } } public URStyle dateStyle(URStyle baseStyle, Date date, boolean load) @@ -461,14 +516,26 @@ private synchronized void insertString(String insertedString, URStyle style, int setDocAttributes(position, insertedString.length(), style); } - // Adds the string (with all needed attributes) to the end of the document - private void appendString(String insertedString, URStyle style) + /** + * Adds the string (with all needed attributes) to the document either at the end, or the docPosition. + * @param insertedString + * @param style + * @param docPosition + * @return doc position after inserted string + * @throws BadLocationException + */ + private int addString(String insertedString, URStyle style, Optional docPosition) throws BadLocationException { int position = doc.getLength(); + if(docPosition.isPresent()) + position = docPosition.get(); + if((myServer == null || !myServer.hasConnection()) || myServer.isConnected()) insertString(insertedString, style, position); + + return position + insertedString.length(); } public URStyle getStyleDefault(String styleName) @@ -514,8 +581,14 @@ public URStyle getStyle(String styleName, boolean load) */ public void updateStyles (URStyle newBaseStyle) { + int lastStylesHash = getStylesHash(); + + updateStylesInProgress.set(true); + initStyles(newBaseStyle); + int currentStylesHash = getStylesHash(); + if (doc.getLength() > 0) { SwingUtilities.invokeLater(new Runnable() @@ -524,13 +597,12 @@ public void run () { try { - if(!updateStylesInProgress.get()) + if (currentStylesHash != lastStylesHash) { Constants.LOGGER.info( "Updating styles for " + settingsPath.name()); - updateStylesTime.set(Instant.now().getEpochSecond()); - updateDocStyles(0); + updateDocStyles(0, getStylesHash()); } else { - Constants.LOGGER.info( "Update already in progress."); + Constants.LOGGER.debug("NOT updating styles for " + settingsPath.name()+". Styles hash matches."); } } catch (BadLocationException e) { @@ -543,9 +615,9 @@ public void run () } } - private void updateDocStyles (int currentPosition) throws BadLocationException + private void updateDocStyles (int currentPosition, int updateHash) throws BadLocationException { - updateStylesInProgress.set(true); + updateStylesTime.set(Instant.now().getEpochSecond()); Element root = doc.getDefaultRootElement(); int lineCount = root.getElementCount(); int lineIndex = 0; @@ -568,6 +640,10 @@ private void updateDocStyles (int currentPosition) throws BadLocationException // Has style to update if (currentStyle != null && currentStyle.getAttributeCount() > 0) { + // this line has already been updated + if(currentStyle.getAttribute("stylesHash") != null && currentStyle.getAttribute("stylesHash").equals(updateHash)) + break; + int styleLength = Integer.parseInt(currentStyle.getAttribute("styleLength").toString()); String styleString = doc.getText(currentPosition, styleLength); @@ -585,8 +661,10 @@ private void updateDocStyles (int currentPosition) throws BadLocationException doc.remove(currentPosition, styleLength); currentStyle = dateStyle(currentStyle, lineDate, false); // Inserts the new timestamp, and updates the formatting + currentStyle.addAttribute("stylesHash", getStylesHash()); insertString(newTimeStamp, currentStyle, currentPosition); } else { + currentStyle.addAttribute("stylesHash", getStylesHash()); setDocAttributes(currentPosition, styleLength, currentStyle); } } else @@ -601,6 +679,7 @@ private void updateDocStyles (int currentPosition) throws BadLocationException currentStyle = dateStyle(currentStyle, lineDate, false); // Inserts the new string, and updates the formatting + currentStyle.addAttribute("stylesHash", getStylesHash()); insertString(newTimeStamp, currentStyle, currentPosition); } } @@ -708,6 +787,19 @@ public String getFirstLine() throws BadLocationException return finalLine; } + public void removeFirstLine () + { + Element firstLine = doc.getDefaultRootElement().getElement(0); + int endIndex = firstLine.getEndOffset(); + try { + doc.remove(0, endIndex); + } catch (BadLocationException ble) + { + // TODO: + ble.printStackTrace(); + } + } + public String getLatestLine() throws BadLocationException { Element root = doc.getDefaultRootElement(); @@ -735,6 +827,33 @@ public String getLatestLine() throws BadLocationException return finalLine; } + public String getLine(int lineNumber) throws BadLocationException + { + Element root = doc.getDefaultRootElement(); + int lines = root.getElementCount(); + + String finalLine = ""; + + while (finalLine.isEmpty()) + { + + if (lines < 0) + break; + + Element line = root.getElement(lineNumber); + + if (null == line) + continue; + + int start = line.getStartOffset(); + int end = line.getEndOffset(); + String text = doc.getText(start, end - start); + finalLine = text.trim(); + } + + return finalLine; + } + // New method to get line at position public String getLineAtPosition(int position) throws BadLocationException { Element root = doc.getDefaultRootElement(); @@ -754,29 +873,38 @@ public String getLineAtPosition(int position) throws BadLocationException { return lineText; } - private int getLinePosition(String targetLine) throws BadLocationException + public int getLineCount () { + Element root = doc.getDefaultRootElement(); + return root.getElementCount(); + } + + private int getLinePosition(String targetLine) throws BadLocationException { Element root = doc.getDefaultRootElement(); int lines = root.getElementCount(); - for (int i = 0; i < lines; i++) - { + // Create a regex pattern to match the target line + Pattern pattern = Pattern.compile(".*"+Pattern.quote(targetLine.trim())+"$"); + + for (int i = 0; i < lines; i++) { Element line = root.getElement(i); - if (null == line) + if (line == null) continue; int start = line.getStartOffset(); int end = line.getEndOffset(); String text = doc.getText(start, end - start); - if (text.trim().equals(targetLine.trim())) - { + Matcher matcher = pattern.matcher(text.trim()); + + // If the pattern matches, return the start offset of the line + if (matcher.find()) { return start; } } - return 0; + return -1; // Return -1 if the target line is not found } public URStyle getStyleAtPosition(int position, String relativeLine) @@ -794,12 +922,12 @@ public URStyle getStyleAtPosition(int position, String relativeLine) return new URStyle(new SimpleAttributeSet(textStyle)); } - private void parseClickableText(IRCUser fromUser, String line, URStyle defaultStyle) + private int parseClickableText(IRCUser fromUser, String line, URStyle defaultStyle, int position) throws BadLocationException { HashMap regexStrings = new HashMap<>(); - regexStrings.put(Constants.URL_REGEX, urlStyle); - regexStrings.put(Constants.CHANNEL_REGEX, channelStyle); + regexStrings.put(Constants.URL_REGEX, getStyle("urlStyle", false)); + regexStrings.put(Constants.CHANNEL_REGEX, getStyle("channelStyle", false)); // final String line = getLatestLine(doc); final int relativePosition = getLinePosition(getLatestLine()); @@ -849,14 +977,16 @@ private void parseClickableText(IRCUser fromUser, String line, URStyle defaultSt int nextLineLength = Integer.parseInt(nextLine.getAttribute("styleLength").toString()); // Append the string that comes before the next clickable text - appendString(remainingLine.substring(0, nextLineStart - offset), defaultStyle); + position = addString(remainingLine.substring(0, nextLineStart - offset), defaultStyle, Optional.of(position)); - appendString(nextLine.getAttribute("clickableText").toString(), nextLine); + position = addString(nextLine.getAttribute("clickableText").toString(), nextLine, Optional.of(position)); remainingLine = remainingLine.substring((nextLineStart + nextLineLength) - offset); } - appendString(remainingLine, defaultStyle); + position = addString(remainingLine, defaultStyle, Optional.of(position)); + + return position; } /** @@ -866,10 +996,17 @@ private void parseClickableText(IRCUser fromUser, String line, URStyle defaultSt * @param fromString * @param line */ - public void formattedDocument(Date lineDate, IRCUser fromUser, String fromString, String line) + public void appendMessage(Optional lineDate, IRCUser fromUser, String fromString, String line) { + int insertPosition = doc.getLength(); + + if(lineDate.isEmpty()) + lineDate = Optional.of(new Date()); + else + insertPosition = 0; + // build the timeLine string - timeLine = Optional.of(lineDate); + timeLine = lineDate; String[] nickParts; final URStyle nickPositionStyle; @@ -879,16 +1016,16 @@ public void formattedDocument(Date lineDate, IRCUser fromUser, String fromString if (fromUser != null && null != myNick && myNick.equals(fromUser.toString())) { // This message is from me - nickPositionStyle = myStyle.clone(); - linePositionStyle = lineStyle.clone(); - timePositionStyle = timeStyle.clone(); + nickPositionStyle = getStyle("myStyle", false); + linePositionStyle = getStyle("defaultStyle", false); + timePositionStyle = getStyle("defaultStyle", false); nickParts = DriverGUI.gui.getNickFormatString(fromUser.getName()); } else if (fromUser == null && fromString.equals(Constants.EVENT_USER)) { // This is an event message - nickPositionStyle = lowStyle.clone(); - linePositionStyle = lowStyle.clone(); - timePositionStyle = lowStyle.clone(); + nickPositionStyle = getStyle("lowStyle", false); + linePositionStyle = getStyle("lowStyle", false); + timePositionStyle = getStyle("lowStyle", false); nickParts = DriverGUI.gui.getNickFormatString(fromString); } else { @@ -897,48 +1034,50 @@ public void formattedDocument(Date lineDate, IRCUser fromUser, String fromString // This message is from someone else // Does this message have my nick in it? if (myNick != null && line.indexOf(myNick) > -1) - nickPositionStyle = highStyle.clone(); + nickPositionStyle = getStyle("highStyle", false); else - nickPositionStyle = nickStyle.clone(); + nickPositionStyle = getStyle("nickStyle", false); - linePositionStyle = lineStyle.clone(); - timePositionStyle = timeStyle.clone(); + linePositionStyle = getStyle("defaultStyle", false); + timePositionStyle = getStyle("defaultStyle", false); } try { - // doc.insertString(doc.getLength(), timeLine, timeStyle); + // doc.insertString(doc.getLength(), timeLine, defaultStyle); // if(null != timeLine && !timeLine.isBlank()) if (((InterfacePanel) DriverGUI.gui.interfacePanel).isTimeStampsEnabled()) { // add the date to the end of the string to preserve the timestamp of the line // when updating styles - appendString(DriverGUI.gui.getTimeStampString(timeLine.get()) + " ", dateStyle(timePositionStyle, lineDate, false)); + URStyle lineDateStyle = dateStyle(timePositionStyle, lineDate.get(), false); + lineDateStyle.addAttribute("stylesHash", getStylesHash()); + insertPosition = addString(DriverGUI.gui.getTimeStampString(timeLine.get()) + " ", lineDateStyle, Optional.of(insertPosition)); } linePositionStyle.addAttribute("type", "nickPart0"); linePositionStyle.addAttribute("nickParts", nickParts); - appendString(nickParts[0], linePositionStyle); + insertPosition = addString(nickParts[0], linePositionStyle, Optional.of(insertPosition)); if (fromUser != null) { - URStyle clickableNameStyle = nickPositionStyle.clone(); + URStyle clickableNameStyle = nickPositionStyle; clickableNameStyle.addAttribute("type", "IRCUser"); clickableNameStyle.addAttribute("clickableText", new ClickableText(fromUser.toString(), nickPositionStyle, fromUser)); clickableNameStyle.addAttribute("nickParts", nickParts); - appendString(nickParts[1], clickableNameStyle); + insertPosition = addString(nickParts[1], clickableNameStyle, Optional.of(insertPosition)); } else { nickPositionStyle.addAttribute("type", "nick"); nickPositionStyle.addAttribute("nickParts", nickParts); - appendString(nickParts[1], nickPositionStyle); + insertPosition = addString(nickParts[1], nickPositionStyle, Optional.of(insertPosition)); } linePositionStyle.addAttribute("type", "nickPart2"); - appendString(nickParts[2], linePositionStyle); + insertPosition = addString(nickParts[2], linePositionStyle, Optional.of(insertPosition)); linePositionStyle.removeAttribute("type"); linePositionStyle.removeAttribute("nickParts"); @@ -947,8 +1086,8 @@ public void formattedDocument(Date lineDate, IRCUser fromUser, String fromString // appendString(doc, " "+line, lineStyle); // parse the outputted line for clickable text - parseClickableText(fromUser, " " + line, linePositionStyle); - appendString(System.getProperty("line.separator"), linePositionStyle); + insertPosition = parseClickableText(fromUser, " " + line, linePositionStyle, insertPosition); + insertPosition = addString(System.getProperty("line.separator"), linePositionStyle, Optional.of(insertPosition)); } catch (BadLocationException e) { Constants.LOGGER.error( e.getLocalizedMessage()); diff --git a/src/urChatBasic/frontend/UserGUI.java b/src/urChatBasic/frontend/UserGUI.java index 5fc6561..d71dd5c 100644 --- a/src/urChatBasic/frontend/UserGUI.java +++ b/src/urChatBasic/frontend/UserGUI.java @@ -67,8 +67,7 @@ public class UserGUI extends JPanel implements Runnable, UserGUIBase private final UROptionsPanel profilePanel = new ProfilePanel((MainOptionsPanel) optionsMainPanel); - private final JComboBox lafOptions = - new JComboBox(UIManager.getInstalledLookAndFeels()); + private final JComboBox lafOptions = new JComboBox(UIManager.getInstalledLookAndFeels()); // Appearance Panel private FontPanel clientFontPanel; @@ -191,46 +190,46 @@ public UROptionsPanel getAppearancePanel () } // /** - // * Sets the current active profile - if the newProfileName doesn't exist it will be created. - // * @param newProfileName - // */ + // * Sets the current active profile - if the newProfileName doesn't exist it will be created. + // * @param newProfileName + // */ // public void setActiveProfile (String newProfileName) // { - // // save the current profile settings, if it exists - // if (URProfilesUtil.profileExists(URProfilesUtil.getActiveProfileName())) - // { - // setClientSettings(); - // } - - // if(!URProfilesUtil.profileExists(newProfileName)) - // { - // URProfilesUtil.createProfile(newProfileName); - // } - - // // change the profile name - // URProfilesUtil.setActiveProfileName(newProfileName); - - // // now load the new profile settings - // getClientSettings(false); + // // save the current profile settings, if it exists + // if (URProfilesUtil.profileExists(URProfilesUtil.getActiveProfileName())) + // { + // setClientSettings(); + // } + + // if(!URProfilesUtil.profileExists(newProfileName)) + // { + // URProfilesUtil.createProfile(newProfileName); + // } + + // // change the profile name + // URProfilesUtil.setActiveProfileName(newProfileName); + + // // now load the new profile settings + // getClientSettings(false); // } // TODO: Is this needed any more or should we be adding IRCServer only? // @Override // public void addToCreatedServers (String serverName) // { - // if (getCreatedServer(serverName) == null) - // { - // createdServers.add(new IRCServer(serverName.trim(), userNameTextField.getText().trim(), - // realNameTextField.getText().trim(), new String(passwordTextField.getPassword()), - // serverPortTextField.getText().trim(), serverTLSCheckBox.isSelected(), - // proxyHostNameTextField.getText(), proxyPortTextField.getText(), serverProxyCheckBox.isSelected())); - // } + // if (getCreatedServer(serverName) == null) + // { + // createdServers.add(new IRCServer(serverName.trim(), userNameTextField.getText().trim(), + // realNameTextField.getText().trim(), new String(passwordTextField.getPassword()), + // serverPortTextField.getText().trim(), serverTLSCheckBox.isSelected(), + // proxyHostNameTextField.getText(), proxyPortTextField.getText(), serverProxyCheckBox.isSelected())); + // } // } @Override public void addToCreatedServers (IRCServerBase newServer) { - if(getCreatedServer(newServer.getName()) != null) + if (getCreatedServer(newServer.getName()) != null) createdServers.remove(newServer); createdServers.add(newServer); @@ -255,8 +254,7 @@ private void setupAppearancePanel () lafOptions.setRenderer(new DefaultListCellRenderer() { @Override - public Component getListCellRendererComponent (JList list, Object value, int index, boolean isSelected, - boolean cellHasFocus) + public Component getListCellRendererComponent (JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { LookAndFeelInfo info = (LookAndFeelInfo) value; return super.getListCellRendererComponent(list, info.getName(), index, isSelected, cellHasFocus); @@ -336,7 +334,7 @@ public void updatePreviewTextArea () // previewTextArea.setFont(clientFontPanel.getFont()); if (previewLineFormatter == null) { - previewLineFormatter = new LineFormatter(clientFontPanel.getStyle(), previewTextArea, null, URProfilesUtil.getActiveProfilePath()); + previewLineFormatter = new LineFormatter(clientFontPanel.getStyle(), previewTextArea, previewTextScroll, null, URProfilesUtil.getActiveProfilePath()); URProfilesUtil.addListener(EventType.CHANGE, e -> { previewLineFormatter.setSettingsPath(URProfilesUtil.getActiveProfilePath()); @@ -352,15 +350,12 @@ public void updatePreviewTextArea () IRCUser tempUser = new IRCUser(null, "matty_r"); IRCUser tempUser2 = new IRCUser(null, System.getProperty("user.name")); previewLineFormatter.setNick(System.getProperty("user.name")); - previewLineFormatter.formattedDocument(new Date(), null, Constants.EVENT_USER, - "urChat has loaded - this is an Event"); - previewLineFormatter.formattedDocument(new Date(), tempUser, "matty_r", "Normal line. Hello, world!"); - previewLineFormatter.formattedDocument(new Date(), tempUser, "matty_r", + previewLineFormatter.appendMessage(Optional.empty(), null, Constants.EVENT_USER, "urChat has loaded - this is an Event"); + previewLineFormatter.appendMessage(Optional.empty(), tempUser, "matty_r", "Normal line. Hello, world!"); + previewLineFormatter.appendMessage(Optional.empty(), tempUser, "matty_r", "This is what it looks like when your nick is mentioned, " + System.getProperty("user.name") + "!"); - previewLineFormatter.formattedDocument(new Date(), tempUser2, System.getProperty("user.name"), - "Go to https://github.com/matty-r/urChat"); - previewLineFormatter.formattedDocument(new Date(), tempUser2, System.getProperty("user.name"), - "Join #urchat on irc.libera.chat"); + previewLineFormatter.appendMessage(Optional.empty(), tempUser2, System.getProperty("user.name"), "Go to https://github.com/matty-r/urChat"); + previewLineFormatter.appendMessage(Optional.empty(), tempUser2, System.getProperty("user.name"), "Join #urchat on irc.libera.chat"); } else { previewLineFormatter.updateStyles(getStyle()); @@ -379,8 +374,7 @@ public void mouseClicked (MouseEvent mouseEvent) if (SwingUtilities.isRightMouseButton(mouseEvent) && wordAttributeSet.getAttribute("name") != null) { String styleName = styleLabel.getText(); - FontDialog styleFontDialog = - new FontDialog(styleName, previewLineFormatter.getStyleDefault(styleName), URProfilesUtil.getActiveProfilePath()); + FontDialog styleFontDialog = new FontDialog(styleName, previewLineFormatter.getStyleDefault(styleName), URProfilesUtil.getActiveProfilePath()); styleFontDialog.addFontSaveListener(new SaveFontListener()); styleFontDialog.setVisible(true); @@ -433,7 +427,7 @@ public String[] getNickFormatString (String nick) nickParts[1] = nick; String nickString = nickFormatField.getText(); - if(nickString.indexOf("nick") >= 0) + if (nickString.indexOf("nick") >= 0) { int nickIndex = nickString.indexOf("nick"); String leftPart = nickString.substring(0, nickIndex); @@ -441,7 +435,7 @@ public String[] getNickFormatString (String nick) nickParts[0] = leftPart; nickParts[2] = rightPart; - } else if(nickString.length() == 1) + } else if (nickString.length() == 1) { // both parts are the same nickParts[0] = nickString; @@ -470,7 +464,8 @@ public void setNickFormatString (String newFormat) */ public void setupServerTab (IRCServerBase server) { - SwingUtilities.invokeLater(new Runnable() { + SwingUtilities.invokeLater(new Runnable() + { @Override public void run () @@ -479,7 +474,7 @@ public void run () { boolean iconsShown = (boolean) URPanels.getKeyComponentValue(Constants.KEY_SHOW_TAB_ICON); int currentServerIndex = DriverGUI.gui.getTabIndex((IRCChannelBase) server); - if(currentServerIndex < 0) + if (currentServerIndex < 0) { tabbedPane.addTab(server.getName(), iconsShown ? ((IRCChannelBase) server).icon : null, ((IRCServer) server)); setCurrentTab(server.getName()); @@ -524,7 +519,7 @@ public void quitServers () } // if(createdServers.size() == 0) - // profilePicker.setEnabled(true); + // profilePicker.setEnabled(true); } /* @@ -540,7 +535,7 @@ public void quitServer (IRCServerBase server) createdServers.remove(server); // if(createdServers.size() == 0) - // profilePicker.setEnabled(true); + // profilePicker.setEnabled(true); } /** @@ -549,7 +544,8 @@ public void quitServer (IRCServerBase server) public void setClientSettings () { URPreferencesUtil.putPref(Constants.KEY_TIME_STAMP_FORMAT, timeStampField.getText(), URProfilesUtil.getActiveProfilePath()); - URPreferencesUtil.putPref(Constants.KEY_LAF_NAME, ((LookAndFeelInfo) lafOptions.getSelectedItem()).getClassName(), URProfilesUtil.getActiveProfilePath()); + URPreferencesUtil.putPref(Constants.KEY_LAF_NAME, ((LookAndFeelInfo) lafOptions.getSelectedItem()).getClassName(), + URProfilesUtil.getActiveProfilePath()); URPreferencesUtil.saveStyle(defaultStyle, clientFontPanel.getStyle(), URProfilesUtil.getActiveProfilePath()); URPreferencesUtil.putPref(Constants.KEY_WINDOW_X, (int) DriverGUI.frame.getBounds().getX(), URProfilesUtil.getActiveProfilePath()); URPreferencesUtil.putPref(Constants.KEY_WINDOW_Y, (int) DriverGUI.frame.getBounds().getY(), URProfilesUtil.getActiveProfilePath()); @@ -570,11 +566,9 @@ public void getClientSettings (boolean loadWindowSettings) clientFontPanel.loadStyle(); - timeStampField - .setText(URProfilesUtil.getActiveProfilePath().get(Constants.KEY_TIME_STAMP_FORMAT, Constants.DEFAULT_TIME_STAMP_FORMAT)); + timeStampField.setText(URProfilesUtil.getActiveProfilePath().get(Constants.KEY_TIME_STAMP_FORMAT, Constants.DEFAULT_TIME_STAMP_FORMAT)); - nickFormatField - .setText(URProfilesUtil.getActiveProfilePath().get(Constants.KEY_NICK_FORMAT, Constants.DEFAULT_NICK_FORMAT)); + nickFormatField.setText(URProfilesUtil.getActiveProfilePath().get(Constants.KEY_NICK_FORMAT, Constants.DEFAULT_NICK_FORMAT)); updatePreviewTextArea(); @@ -586,9 +580,8 @@ public void getClientSettings (boolean loadWindowSettings) URProfilesUtil.getActiveProfilePath().getInt(Constants.KEY_WINDOW_WIDTH, Constants.DEFAULT_WINDOW_WIDTH), URProfilesUtil.getActiveProfilePath().getInt(Constants.KEY_WINDOW_HEIGHT, Constants.DEFAULT_WINDOW_HEIGHT)); - this.setPreferredSize( - new Dimension(URProfilesUtil.getActiveProfilePath().getInt(Constants.KEY_WINDOW_WIDTH, Constants.DEFAULT_WINDOW_WIDTH), - URProfilesUtil.getActiveProfilePath().getInt(Constants.KEY_WINDOW_HEIGHT, Constants.DEFAULT_WINDOW_HEIGHT))); + this.setPreferredSize(new Dimension(URProfilesUtil.getActiveProfilePath().getInt(Constants.KEY_WINDOW_WIDTH, Constants.DEFAULT_WINDOW_WIDTH), + URProfilesUtil.getActiveProfilePath().getInt(Constants.KEY_WINDOW_HEIGHT, Constants.DEFAULT_WINDOW_HEIGHT))); } } @@ -622,8 +615,7 @@ private void setupTabbedPane () } /** - * Used to listen to the right click on the tabs to determine what type we clicked on and pop up a - * menu or exit. I will add menus to all types eventually. + * Used to listen to the right click on the tabs to determine what type we clicked on and pop up a menu or exit. I will add menus to all types eventually. * * @author Matt * @@ -694,7 +686,7 @@ public void stateChanged (ChangeEvent e) } } - protected class SaveFontListener implements ActionListener + public class SaveFontListener implements ActionListener { @Override public void actionPerformed (ActionEvent arg0) @@ -715,12 +707,12 @@ public void actionPerformed (ActionEvent arg0) // TODO: Favourites handling to be done elsewhere // for (int index = 0; index < favouritesList.getModel().getSize(); index++) // { - // FavouritesItem favouriteItem = favouritesList.getModel().getElementAt(index); - // if(favouriteItem.favFontDialog != null) - // { - // favouriteItem.favFontDialog.getFontPanel().setDefaultStyle(getStyle()); - // favouriteItem.favFontDialog.getFontPanel().loadStyle(); - // } + // FavouritesItem favouriteItem = favouritesList.getModel().getElementAt(index); + // if(favouriteItem.favFontDialog != null) + // { + // favouriteItem.favFontDialog.getFontPanel().setDefaultStyle(getStyle()); + // favouriteItem.favFontDialog.getFontPanel().loadStyle(); + // } // } // defaultStyle = clientFontPanel.getStyle(); @@ -730,7 +722,7 @@ public void actionPerformed (ActionEvent arg0) public UserGUI (Optional initialProfile) { - if(initialProfile.isPresent()) + if (initialProfile.isPresent()) URProfilesUtil.setActiveProfileName(initialProfile.get()); else URProfilesUtil.setActiveProfileName(URProfilesUtil.getDefaultProfile()); @@ -748,7 +740,7 @@ public void setupUserGUI () // this.setBackground(Color.gray); getClientSettings(true); - URProfilesUtil.addListener(EventType.CHANGE, e-> { + URProfilesUtil.addListener(EventType.CHANGE, e -> { getClientSettings(false); }); lafOptions.addActionListener(new ChangeLAFListener()); @@ -814,7 +806,7 @@ private LookAndFeelInfo getLAF (String lafClassName) } } - Constants.LOGGER.error( "Unable to set LAF to " + lafClassName); + Constants.LOGGER.error("Unable to set LAF to " + lafClassName); // Set to the System LAF if we've chosen an invalid/unavailable LAF theme return getLAF(UIManager.getSystemLookAndFeelClassName()); @@ -826,7 +818,7 @@ public void setNewLAF (String newLAFname) // String previousDefaultBackground = URColour.hexEncode(UIManager.getColor(Constants.DEFAULT_BACKGROUND_STRING)); // Font previousDefaultFont = getFont(); - Constants.LOGGER.info( "Setting to LookAndFeel to " + newLAFname); + Constants.LOGGER.info("Setting to LookAndFeel to " + newLAFname); boolean flatLafAvailable = false; try { @@ -869,29 +861,25 @@ public void setNewLAF (String newLAFname) defaultStyle.setFont(UIManager.getFont(Constants.DEFAULT_FONT_STRING)); - // reset the defaults on the guiStyle if they were already at the default - // if (previousDefaultForeground.equals(URColour.hexEncode(guiStyle.getForeground()))) - defaultStyle.setForeground(UIManager.getColor(Constants.DEFAULT_FOREGROUND_STRING)); - - // if (previousDefaultBackground.equals(URColour.hexEncode(guiStyle.getBackground()))) - defaultStyle.setBackground(UIManager.getColor(Constants.DEFAULT_BACKGROUND_STRING)); + defaultStyle.setForeground(UIManager.getColor(Constants.DEFAULT_FOREGROUND_STRING)); + defaultStyle.setBackground(UIManager.getColor(Constants.DEFAULT_BACKGROUND_STRING)); clientFontPanel.setDefaultStyle(defaultStyle); - if(DriverGUI.frame.isVisible()) + if (DriverGUI.frame.isVisible()) SwingUtilities.updateComponentTreeUI(DriverGUI.frame); updateExtras(); - // DriverGUI.frame.dispose(); - if(DriverGUI.frame.isVisible()) + if (DriverGUI.frame.isVisible()) DriverGUI.frame.validate(); } // Update the fonts and popup menus - these aren't under the component tree private void updateExtras () { - SwingUtilities.invokeLater(new Runnable() { + SwingUtilities.invokeLater(new Runnable() + { @Override public void run () diff --git a/src/urChatBasic/frontend/panels/ConnectionPanel.java b/src/urChatBasic/frontend/panels/ConnectionPanel.java index a61c807..02c6139 100644 --- a/src/urChatBasic/frontend/panels/ConnectionPanel.java +++ b/src/urChatBasic/frontend/panels/ConnectionPanel.java @@ -243,6 +243,7 @@ public String getChannelName () public void createPopUp () { myMenu = new FavouritesPopUp(); + myMenu.setUI(new JPopupMenu().getUI()); } private class FavouritesPopUp extends JPopupMenu diff --git a/src/urChatBasic/frontend/panels/InterfacePanel.java b/src/urChatBasic/frontend/panels/InterfacePanel.java index f70564f..4eeddc7 100644 --- a/src/urChatBasic/frontend/panels/InterfacePanel.java +++ b/src/urChatBasic/frontend/panels/InterfacePanel.java @@ -21,6 +21,7 @@ public class InterfacePanel extends UROptionsPanel private final JCheckBox enableClickableLinks = new JCheckBox("Make links clickable"); private final JCheckBox showJoinsQuitsEventTicker = new JCheckBox("Show Joins/Quits in the Event Ticker"); private final JCheckBox showJoinsQuitsMainWindow = new JCheckBox("Show Joins/Quits in the Chat Window"); + private final JCheckBox loadChannelLogsOnJoin = new JCheckBox("Load channel logs on join"); private final JCheckBox logChannelText = new JCheckBox("Save and log all channel text"); private final JCheckBox logServerActivity = new JCheckBox("Save and log all Server activity"); private final JCheckBox logClientText = new JCheckBox("Log client text (Allows up or down history)"); @@ -60,6 +61,7 @@ private void setupInterfacePanel () URPanels.addToPanel(this, enableClickableLinks, null, Placement.DEFAULT, null, Constants.KEY_CLICKABLE_LINKS_ENABLED); URPanels.addToPanel(this, showJoinsQuitsEventTicker, null, Placement.DEFAULT, null, Constants.KEY_EVENT_TICKER_JOINS_QUITS); URPanels.addToPanel(this, showJoinsQuitsMainWindow, null, Placement.DEFAULT, null, Constants.KEY_MAIN_WINDOW_JOINS_QUITS); + URPanels.addToPanel(this, loadChannelLogsOnJoin, null, Placement.DEFAULT, null, Constants.KEY_LOAD_CHANNEL_LOGS_ON_JOIN); URPanels.addToPanel(this, logChannelText, null, Placement.DEFAULT, null, Constants.KEY_LOG_CHANNEL_ACTIVITY); URPanels.addToPanel(this, logServerActivity, null, Placement.DEFAULT, null, Constants.KEY_LOG_SERVER_ACTIVITY); URPanels.addToPanel(this, logClientText, null, Placement.DEFAULT, null, Constants.KEY_LOG_CLIENT_TEXT); @@ -192,6 +194,11 @@ public Boolean isClickableLinksEnabled () return enableClickableLinks.isSelected(); } + public Boolean isLoadChannelLogsEnabled () + { + return loadChannelLogsOnJoin.isSelected(); + } + /* * (non-Javadoc) * diff --git a/tests/backend/LogParsingTests.java b/tests/backend/LogParsingTests.java new file mode 100644 index 0000000..8f171db --- /dev/null +++ b/tests/backend/LogParsingTests.java @@ -0,0 +1,71 @@ +package backend; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import org.testng.annotations.Ignore; +import org.testng.annotations.Test; +import urChatBasic.backend.utils.LogPatternParser; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.fail; +import static urChatBasic.backend.utils.LogPatternParser.LogPattern; + +public class LogParsingTests { + + + @Test + public void testLogLinesManual () + { + testParsing("2024-02-13 19:27:31.414UTC irc.libera.chat-#java matty_r: morning:asdjnwk 123AD?asd,123uADAjkalas[];'das[]"); + testParsing("2024-01-31 20:49:43.003UTC irc.libera.chat-#linux ****: You have joined #linux"); + testParsing("2024-02-11 02:17:40.207UTC irc.libera.chat-#linux ****: Welcome to #linux! Help & support for any Linux distribution or related topic -- Rules/Info: https://linux.chat -- Forum: https://linux.forum -- Pastebin: https://paste.linux.chat/ -- @linux.social on Mastodon: https://linux.social -- Need an op? !ops or join #linux-ops"); + testParsing("2024-01-31 20:58:55.016UTC irc.libera.chat-#linux user: 😢😢😢😢😢😢😢😢😢😢😢"); + testParsing("2024-01-01 03:58:55.016UTC irc.libera.chat-#linux another user: HAPPY NEW YEAR, for me"); + testParsing("2024-01-28 10:19:43.380UTC irc.libera.chat-#urchat ****: You have joined #urchat"); + } + + @Test + @Ignore + public void testLogLines() { + String logFilePath = "Logs/irc.libera.chat-#urchat.log"; + String line = ""; + + try (BufferedReader br = new BufferedReader(new FileReader(logFilePath))) { + while ((line = br.readLine()) != null) { + testParsing(line); + } + } catch (Exception e) { + fail("Error parsing line ["+ line +"] " + e.getLocalizedMessage()); + } + } + + public void testParsing (String logLine) + { + Map parsedValues = new HashMap<>(); + + // parseLogLine(logLine); + // parsedValues = parseLogLineFull(logLine); + parsedValues = LogPatternParser.parseLogLineFull(logLine); + + String assertString = ""; + + for (LogPattern logPattern : LogPattern.values()) { + Object parsedValue = parsedValues.get(logPattern.toString()); + + switch (logPattern.getPatternClass().getSimpleName()) { + case "Date": + assertString += LogPatternParser.formatDateToString((Date) parsedValue); + break; + default: + assertString += parsedValue.toString(); + break; + } + + assertString += logPattern.getAppendString(); + } + + assertEquals(logLine.trim(), assertString.trim()); + } +} diff --git a/tests/backend/MessageHandlerTests.java b/tests/backend/MessageHandlerTests.java index 07487fb..70302fa 100644 --- a/tests/backend/MessageHandlerTests.java +++ b/tests/backend/MessageHandlerTests.java @@ -1,6 +1,7 @@ package backend; import static org.testng.AssertJUnit.*; +import static org.testng.Reporter.log; import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; @@ -65,7 +66,7 @@ public void tearDown () throws Exception testServer.quitChannels(); // URProfilesUtil.getActiveProfilePath().sync(); // URProfilesUtil.getActiveProfilePath().sync(); - URProfilesUtil.deleteProfile(testDriver.getTestProfileName(), false); + TestDriverGUI.cleanupTestProfiles(); TestDriverGUI.closeWindow(); } @@ -77,13 +78,15 @@ public void nickIsHighStyleTest() throws BadLocationException, InterruptedExcept Message testMessage = testHandler.new Message(rawMessage); testHandler.parseMessage(testMessage); IRCPrivate someUserChannel = testServer.getCreatedPrivateChannel("someuser"); - String testLine = someUserChannel.getLineFormatter().getLatestLine(); // "[0629] hello testUser!" + while (someUserChannel.messageQueueWorking()) { - TimeUnit.SECONDS.sleep(1); + TimeUnit.MILLISECONDS.sleep(10); } + String testLine = someUserChannel.getLineFormatter().getLatestLine(); // "[0629] hello testUser!" + // Should be highStyle because someuser mentioned my nick, testUser assertEquals("highStyle", someUserChannel.getLineFormatter().getStyleAtPosition(11, testLine).getAttribute("name")); @@ -120,13 +123,14 @@ public void nickIsNickStyleTest() throws BadLocationException, InterruptedExcept String rawMessage = ":someuser!~someuser@urchatclient PRIVMSG "+PUB_CHANNEL_NAME+" :Welcome to somechannel!"; Message testMessage = testHandler.new Message(rawMessage); testHandler.parseMessage(testMessage); - String testLine = testPubChannel.getLineFormatter().getLatestLine(); // "[0629] hello world!" while (testPubChannel.messageQueueWorking()) { - TimeUnit.SECONDS.sleep(1); + TimeUnit.MILLISECONDS.sleep(10); } + String testLine = "Welcome to somechannel!"; // "[0629] Welcome to somechannel!" + // Should be nickStyle because the user didn't mention testUser and is just a normal message assertEquals("nickStyle", testPubChannel.getLineFormatter().getStyleAtPosition(11, testLine).getAttribute("name")); @@ -238,7 +242,7 @@ public void testChannelLineLimit() throws BadLocationException, InterruptedExcep while (testPubChannel.messageQueueWorking()) { - TimeUnit.SECONDS.sleep(1); + TimeUnit.MILLISECONDS.sleep(10); } // for (int i = 0; i < serverLinesLimit+10; i++) { @@ -251,8 +255,6 @@ public void testChannelLineLimit() throws BadLocationException, InterruptedExcep int channelLinesCount = testPubChannel.getLineFormatter().getDocument().getDefaultRootElement().getElementCount(); - - String firstLine = testPubChannel.getLineFormatter().getFirstLine(); String lastLine = testPubChannel.getLineFormatter().getLatestLine(); // " line # 509" @@ -280,9 +282,9 @@ public void testServerLineLimit() throws BadLocationException, InterruptedExcept testHandler.parseMessage(testMessage); } - while (testServer.messageQueueWorking()) + while (testServer.messageQueueInProgress) { - TimeUnit.SECONDS.sleep(1); + TimeUnit.MILLISECONDS.sleep(10); } int serverLinesCount = @@ -321,7 +323,7 @@ public void urlInMessage() throws BadLocationException, InterruptedException while (testPrivChannel.messageQueueWorking()) { - TimeUnit.SECONDS.sleep(1); + TimeUnit.MILLISECONDS.sleep(10); } String testLine = testPrivChannel.getLineFormatter().getLatestLine(); // "[0629] @@ -369,7 +371,7 @@ public void channelInMessage() throws BadLocationException, InterruptedException while (testPrivChannel.messageQueueWorking()) { - TimeUnit.SECONDS.sleep(1); + TimeUnit.MILLISECONDS.sleep(10); } String testLine = testPrivChannel.getLineFormatter().getLatestLine(); diff --git a/tests/backend/ProfileTests.java b/tests/backend/ProfileTests.java index f54a10c..f308fd9 100644 --- a/tests/backend/ProfileTests.java +++ b/tests/backend/ProfileTests.java @@ -70,7 +70,7 @@ public void createProfileAndDeleteTest () throws InterruptedException { String anotherTestProfileName = "createProfileAndDeleteTest" + (new SimpleDateFormat("yyMMdd")).format(new Date()); log("Create Profile ["+anotherTestProfileName+"]", true); - URProfilesUtil.createProfile(anotherTestProfileName); + anotherTestProfileName = URProfilesUtil.createProfile(anotherTestProfileName); log("Wait for stuff", true); TestDriverGUI.waitForEverything(TestDriverGUI.gui); @@ -133,7 +133,7 @@ public void cloneProfileTest () throws BackingStoreException, InterruptedExcepti } // Delete the cloned profile - clonedProfileRoot.removeNode(); + URProfilesUtil.deleteProfile(clonedProfileRoot.name(), false); } @Test @@ -148,6 +148,6 @@ public void switchToClonedProfileTest () throws BackingStoreException, Interrupt TestDriverGUI.waitForEverything(TestDriverGUI.gui); // Delete the cloned profile - clonedProfileRoot.removeNode(); + URProfilesUtil.deleteProfile(clonedProfileRoot.name(), false); } } diff --git a/tests/frontend/AppearanceTests.java b/tests/frontend/AppearanceTests.java index e5283f3..2d71da3 100644 --- a/tests/frontend/AppearanceTests.java +++ b/tests/frontend/AppearanceTests.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; +import java.util.concurrent.TimeUnit; import javax.swing.text.BadLocationException; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; @@ -34,7 +35,7 @@ public class AppearanceTests IRCServer testServer; TestDriverGUI testDriver; UserGUI testGUI; - final static int MAX_CHANNEL_NAMES = 10; + final static int MAX_CHANNEL_NAMES = 1; final static String CHANNEL_PREFIX = "#someChannel"; final List PUB_CHANNEL_NAMES = new ArrayList<>(); IRCUser testUser; @@ -81,7 +82,7 @@ public void tearDown () throws Exception testServer.quitChannels(); // URProfilesUtil.getActiveProfilePath().sync(); // URProfilesUtil.getActiveProfilePath().sync(); - URProfilesUtil.deleteProfile(testDriver.getTestProfileName(), false); + TestDriverGUI.cleanupTestProfiles(); TestDriverGUI.closeWindow(); } @@ -100,12 +101,21 @@ public void changeDefaultFontAndSizeTest () throws BadLocationException, Interru { IRCChannel pubChannel = testServer.getCreatedChannel(pubChannelName); log("Have joined " + pubChannelName + " successfully?", true); - String welcomeMessage = pubChannel.getLineFormatter().getLineAtPosition(13).split("] ")[1].trim(); + String welcomeMessage = String.join("",testGUI.getNickFormatString("someuser")) + " Welcome to " + pubChannelName; assertEquals(" Welcome to " + pubChannelName, welcomeMessage); - log("Check current style in the channel is correct.", true); - URStyle channelStyle = pubChannel.getLineFormatter().getStyleAtPosition(22, welcomeMessage); + log("Wait for styles to update correctly..", true); + TestDriverGUI.waitForEverything(testGUI); + URStyle channelStyle = null; + + while (channelStyle == null || !channelStyle.equals(guiStyle)) + { + TimeUnit.MILLISECONDS.sleep(10); + channelStyle = pubChannel.getLineFormatter().getStyleAtPosition(22, welcomeMessage); + } + + log("Check current style in the channel is correct.", true); assertTrue(guiStyle.equals(channelStyle)); } @@ -122,18 +132,26 @@ public void changeDefaultFontAndSizeTest () throws BadLocationException, Interru for (String pubChannelName : PUB_CHANNEL_NAMES) { + TestDriverGUI.waitForEverything(testGUI); + IRCChannel pubChannel = testServer.getCreatedChannel(pubChannelName); - String welcomeMessage = pubChannel.getLineFormatter().getLineAtPosition(13).split("] ")[1].trim(); - log("Check current style has updated.", true); + String welcomeMessage = pubChannel.getLineFormatter().getLineAtPosition(22).split("] ")[1].trim(); + log("Wait for current style has updated.", true); - URStyle channelStyle = pubChannel.getLineFormatter().getStyleAtPosition(22, welcomeMessage); + URStyle channelStyle = null; + + while (channelStyle == null || !channelStyle.equals(newStyle)) + { + TimeUnit.MILLISECONDS.sleep(10); + channelStyle = pubChannel.getLineFormatter().getStyleAtPosition(22, welcomeMessage); + channelStyle.removeAttribute("name"); + } log("Test Style: " + guiStyle, true); log("Channel Style: " + channelStyle, true); String testStyleFont = guiStyle.getFamily().get(); String channelStyleFont = channelStyle.getFamily().get(); - TestDriverGUI.waitForEverything(testGUI); log("Checking "+pubChannelName+" formatting...", true); assertEquals(pubChannelName + " font family doesn't match GUI font family.", testStyleFont, channelStyleFont); @@ -189,6 +207,8 @@ public void changeDefaultForegroundAndBackgroundTest () throws BadLocationExcept for (String pubChannelName : PUB_CHANNEL_NAMES) { + TestDriverGUI.waitForEverything(testGUI); + IRCChannel pubChannel = testServer.getCreatedChannel(pubChannelName); String welcomeMessage = pubChannel.getLineFormatter().getLineAtPosition(13).split("] ")[1].trim(); log("Check current style has updated.", true); diff --git a/tests/frontend/LAFTests.java b/tests/frontend/LAFTests.java index 42c6965..aca1623 100644 --- a/tests/frontend/LAFTests.java +++ b/tests/frontend/LAFTests.java @@ -2,6 +2,8 @@ import static org.testng.AssertJUnit.*; import java.awt.Color; +import java.util.concurrent.TimeUnit; +import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.text.StyleConstants; import org.testng.Reporter; @@ -27,6 +29,8 @@ public void setUp() throws Exception { testDriver = new TestDriverGUI(); gui = DriverGUI.gui; + // start the gui and wait for it + SwingUtilities.invokeAndWait(gui); } @AfterClass(alwaysRun = true) @@ -66,7 +70,14 @@ public void changingLAFUpdatesPreviewStyle() throws Exception TestDriverGUI.waitForEverything(gui); - Color newBackgroundColor = (Color) gui.previewLineFormatter.getStyleAtPosition(0, "urChat has loaded - this is an Event").getAttribute(StyleConstants.Background); + Color newBackgroundColor = null; + + while (newBackgroundColor == null || !newBackgroundColor.equals(UIManager.getColor(Constants.DEFAULT_BACKGROUND_STRING))) + { + TimeUnit.MILLISECONDS.sleep(10); + newBackgroundColor = (Color) gui.previewLineFormatter.getStyleAtPosition(0, "urChat has loaded - this is an Event").getAttribute(StyleConstants.Background); + } + Color lineFormatterBackground = gui.previewTextArea.getBackground(); diff --git a/tests/frontend/LineFormatterTests.java b/tests/frontend/LineFormatterTests.java index a28066b..ec6d548 100644 --- a/tests/frontend/LineFormatterTests.java +++ b/tests/frontend/LineFormatterTests.java @@ -58,7 +58,7 @@ public void tearDown () throws Exception log("Quit channels", true); testServer.quitChannels(); log("Delete test profile", true); - URProfilesUtil.deleteProfile(testDriver.getTestProfileName(), false); + TestDriverGUI.cleanupTestProfiles(); log("Close test window", true); TestDriverGUI.closeWindow(); } @@ -74,7 +74,7 @@ public void rightClickOnNickTest () throws BadLocationException, InterruptedExce while (testPubChannel.messageQueueWorking()) { - TimeUnit.SECONDS.sleep(1); + TimeUnit.MILLISECONDS.sleep(10); } testGUI.tabbedPane.setSelectedIndex(1); @@ -103,7 +103,7 @@ public void run() while(!canContinue.get()) { - TimeUnit.SECONDS.sleep(1); + TimeUnit.MILLISECONDS.sleep(10); } // Right-Click mouse event at the x-y coords of the caret in the text pane diff --git a/tests/frontend/UpdateStylesBenchmarkTests.java b/tests/frontend/UpdateStylesBenchmarkTests.java new file mode 100644 index 0000000..6667d20 --- /dev/null +++ b/tests/frontend/UpdateStylesBenchmarkTests.java @@ -0,0 +1,204 @@ +package frontend; + +import static org.junit.Assert.assertEquals; +import static org.testng.Reporter.log; +import java.awt.Font; +import java.awt.GraphicsEnvironment; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import javax.swing.text.BadLocationException; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import urChatBasic.backend.Connection; +import urChatBasic.backend.MessageHandler; +import urChatBasic.backend.MessageHandler.Message; +import urChatBasic.backend.utils.URProfilesUtil; +import urChatBasic.backend.utils.URStyle; +import urChatBasic.base.IRCChannelBase; +import urChatBasic.base.capabilities.CapabilityTypes; +import urChatBasic.base.proxy.ProxyTypes; +import urChatBasic.frontend.DriverGUI; +import urChatBasic.frontend.IRCChannel; +import urChatBasic.frontend.IRCServer; +import urChatBasic.frontend.IRCUser; +import urChatBasic.frontend.UserGUI; +import urChatBasic.frontend.utils.URColour; +import utils.TestDriverGUI; +import utils.TestUtils; + +public class UpdateStylesBenchmarkTests { + MessageHandler testHandler; + IRCServer testServer; + TestDriverGUI testDriver; + UserGUI testGUI; + final static int MAX_CHANNEL_NAMES = 1; + final static String CHANNEL_PREFIX = "#someChannel"; + final List PUB_CHANNEL_NAMES = new ArrayList<>(); + IRCUser testUser; + Connection testConnection; + + LinkedList importantInfo = new LinkedList<>(); + + @BeforeClass(alwaysRun = true) + public void setUp () throws Exception + { + testDriver = new TestDriverGUI(); + TestDriverGUI.startTestGUI(DriverGUI.gui); + testGUI = DriverGUI.gui; + testServer = new IRCServer("testServer", "testUser", "testUser", "testPassword", "1337", true, "testProxy", "1234", ProxyTypes.NONE.getType(), + CapabilityTypes.NONE.getType()); + testUser = new IRCUser(testServer, "testUser"); + + for (int i = 0; i < MAX_CHANNEL_NAMES; i++) + { + PUB_CHANNEL_NAMES.add(CHANNEL_PREFIX + i); + } + + for (String channelName : PUB_CHANNEL_NAMES) + { + testServer.addToCreatedChannels(channelName, false); + } + + testConnection = new Connection(testServer); + testHandler = testConnection.getMessageHandler(); + + Instant startBenchmark = Instant.now(); + for (IRCChannelBase channel : testServer.createdChannels) + { + String rawMessage = ":someuser!~someuser@urchatclient PRIVMSG " + channel.getName() + " :Welcome to " + channel.getName(); + + Message testMessage = testHandler.new Message(rawMessage); + testHandler.parseMessage(testMessage); + + testDriver.loadTestLogs(channel); + } + + TestDriverGUI.waitForEverything(testGUI); + logImportantInfo("Took " + Duration.between(startBenchmark, Instant.now()).toMillis() + "ms to load test logs."); + } + + private void logImportantInfo (String message) + { + importantInfo.add(message); + } + + @AfterClass(alwaysRun = true) + public void tearDown () throws Exception + { + for (String message : importantInfo) { + System.out.println(message); + } + // Reporter.log("Deleting testing profile.", true); + testServer.quitChannels(); + // URProfilesUtil.getActiveProfilePath().sync(); + // URProfilesUtil.getActiveProfilePath().sync(); + TestDriverGUI.cleanupTestProfiles(); + TestDriverGUI.closeWindow(); + } + + @Test + public void changeFontBenchmark () throws InterruptedException + { + Instant startBenchmark = Instant.now(); + // Get Current Font in Appearance panel + URStyle guiStyle = testGUI.getStyle(); + URStyle newStyle = guiStyle.clone(); + + String[] FONT_LIST = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + newStyle.setFont(new Font(FONT_LIST[new Random().nextInt(FONT_LIST.length)], Font.PLAIN, 8)); + testGUI.getFontPanel().setDefaultStyle(newStyle); + TestDriverGUI.waitForEverything(testGUI); + guiStyle = testGUI.getStyle(); + logImportantInfo( "Took " + Duration.between(startBenchmark, Instant.now()).toMillis() + "ms to update font."); + + } + + @Test(timeOut = 5000) + public void changeColoursBenchmark () throws InterruptedException, BadLocationException + { + Instant startBenchmark = Instant.now(); + // Get Current Font in Appearance panel + URStyle guiStyle = testGUI.getStyle(); + URStyle newStyle = guiStyle.clone(); + + newStyle.setForeground(TestUtils.getRandomColour()); + log("Set foreground to " +URColour.hexEncode(newStyle.getForeground().get()), true); + + testGUI.getFontPanel().setDefaultStyle(newStyle); + + guiStyle = testGUI.getStyle(); + logImportantInfo( "Took " + Duration.between(startBenchmark, Instant.now()).toMillis() + "ms to update colours."); + + assertEquals(newStyle, guiStyle); + + for (String pubChannelName : PUB_CHANNEL_NAMES) + { + TestDriverGUI.waitForEverything(testGUI); + + IRCChannel pubChannel = testServer.getCreatedChannel(pubChannelName); + // String welcomeMessage = pubChannel.getLineFormatter().getLineAtPosition(13).split("] ")[1].trim(; + String welcomeMessage = String.join("",testGUI.getNickFormatString("someuser")) + " Welcome to " + pubChannelName; + log("Check current style has updated.", true); + + URStyle channelStyle = null; + + while (channelStyle == null || !channelStyle.equals(newStyle)) + { + TimeUnit.MILLISECONDS.sleep(10); + channelStyle = pubChannel.getLineFormatter().getStyleAtPosition(22, welcomeMessage); + } + + log("Test Style: " + guiStyle, true); + log("Channel Style: " + channelStyle, true); + + String testStyleFont = guiStyle.getFamily().get(); + String channelStyleFont = channelStyle.getFamily().get(); + log("Checking "+pubChannelName+" formatting...", true); + + assertEquals(pubChannelName + " font family doesn't match GUI font family.", testStyleFont, channelStyleFont); + + int testStyleSize = guiStyle.getSize().get(); + int channelStyleSize = channelStyle.getSize().get(); + assertEquals(pubChannelName + " font size doesn't match GUI font size.", testStyleSize, channelStyleSize); + + String testStyleForeground = URColour.hexEncode(guiStyle.getForeground().get()); + String channelStyleForeground = URColour.hexEncode(channelStyle.getForeground().get()); + assertEquals(pubChannelName + " foreground doesn't match GUI font foreground.", testStyleForeground, channelStyleForeground); + + String testStyleBackground = URColour.hexEncode(guiStyle.getBackground().get()); + String channelStyleBackground = URColour.hexEncode(channelStyle.getBackground().get()); + assertEquals(pubChannelName + " background doesn't match GUI font background.", testStyleBackground, channelStyleBackground); + + } + } + + @Test + public void changeStylesBenchmark () throws InterruptedException + { + Instant startBenchmark = Instant.now(); + // Get Current Font in Appearance panel + URStyle guiStyle = testGUI.getStyle(); + URStyle newStyle = guiStyle.clone(); + + newStyle.setForeground(TestUtils.getRandomColour()); + log("Set foreground to " +URColour.hexEncode(newStyle.getForeground().get()), true); + + testGUI.getFontPanel().setDefaultStyle(newStyle); + + // String styleName = styleLabel.getText(); + // FontDialog styleFontDialog = new FontDialog(styleName, previewLineFormatter.getStyleDefault(styleName), URProfilesUtil.getActiveProfilePath()); + + + TestDriverGUI.waitForEverything(testGUI); + guiStyle = testGUI.getStyle(); + logImportantInfo( "Took " + Duration.between(startBenchmark, Instant.now()).toMillis() + "ms to update colours."); + } +} diff --git a/tests/utils/TestDriverGUI.java b/tests/utils/TestDriverGUI.java index 451b78f..534951f 100644 --- a/tests/utils/TestDriverGUI.java +++ b/tests/utils/TestDriverGUI.java @@ -2,18 +2,24 @@ import java.awt.Toolkit; import java.awt.event.WindowEvent; +import java.io.BufferedReader; +import java.io.File; import java.io.IOException; +import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; import javax.swing.JFrame; import javax.swing.SwingUtilities; import org.testng.Reporter; import static org.testng.Reporter.log; +import static urChatBasic.backend.utils.LogPatternParser.parseLogLineFull; +import urChatBasic.backend.utils.ReverseLineInputStream; import urChatBasic.backend.utils.URProfilesUtil; import urChatBasic.base.Constants; import urChatBasic.base.IRCChannelBase; @@ -21,11 +27,13 @@ import urChatBasic.frontend.DriverGUI; import urChatBasic.frontend.IRCServer; import urChatBasic.frontend.UserGUI; +import urChatBasic.frontend.panels.InterfacePanel; public class TestDriverGUI extends DriverGUI { - final String testProfileName = "testingprofile" + (new SimpleDateFormat("yyMMddss")).format(new Date()); + String testProfileName = "testingprofile" + (new SimpleDateFormat("yyMMddss")).format(new Date()); static List testProfiles = new ArrayList<>(); + static boolean loadingLogsInProgress = false; public String getTestProfileName () { @@ -42,14 +50,13 @@ public TestDriverGUI () throws IOException, InvocationTargetException, Interrupt public void run() { frame = new JFrame("urChat"); log("Creating test profile [" + testProfileName + "]", true); - URProfilesUtil.createProfile(testProfileName); + testProfileName = URProfilesUtil.createProfile(testProfileName); testProfiles.add(testProfileName); // This will load the default profile log("Initialize test gui using test profile", true); gui = createGUI(Optional.of(testProfileName)); gui.setTimeLineString(Constants.DEFAULT_TIME_STAMP_FORMAT); gui.setNickFormatString(Constants.DEFAULT_NICK_FORMAT); - gui.setupUserGUI(); // frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setContentPane(gui); frame.pack(); @@ -65,15 +72,22 @@ public static void waitForEverything (UserGUI gui) throws InterruptedException while (wait) { wait = false; - TimeUnit.SECONDS.sleep(1); + TimeUnit.MILLISECONDS.sleep(10); - if (gui.previewLineFormatter.updateStylesInProgress.get()) + if (gui.previewLineFormatter != null && gui.previewLineFormatter.updateStylesInProgress.get()) { log("Update styles in Progress.. waiting", true); wait = true; continue; } + if(loadingLogsInProgress) + { + log("Loading logs in Progress.. waiting", true); + wait = true; + continue; + } + for (IRCServerBase server : gui.getCreatedServers()) { for (IRCChannelBase channel : ((IRCServer) server).createdChannels) @@ -106,7 +120,7 @@ public static void cleanupTestProfiles () public static void startTestGUI (UserGUI gui) throws InterruptedException { - waitForEverything(gui); + // waitForEverything(gui); SwingUtilities.invokeLater(gui); log("Started", true); } @@ -117,4 +131,40 @@ public static void closeWindow () Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(closingEvent); // frame.dispose(); } + + public void loadTestLogs (IRCChannelBase channel) throws InterruptedException + { + Thread fileReadingThread = new Thread(() -> { + loadingLogsInProgress = true; + File logFile = new File("tests/Logs/testlogs-#somechannel.log"); + String line = ""; + try (BufferedReader br = new BufferedReader (new InputStreamReader (new ReverseLineInputStream(logFile)))) { + int maxCount = ((InterfacePanel) gui.interfacePanel).getLimitChannelLinesCount() * 2; + log("Load max count: [" + maxCount +"] Loading logs into " + channel.getName(), true); + int loadCount = 0; + while ((line = br.readLine()) != null && loadCount < maxCount) { + loadingLogsInProgress = true; + // Only wait if the queue is full + while(channel.messageQueueFull()) + { + System.out.println("Sleeping log loading thread."); + Thread.sleep(10); + } + + Map parsedLine = parseLogLineFull(line); + if(parsedLine.size() != 0) + { + channel.printText((Date) parsedLine.get("DATE"), parsedLine.get("MESSAGE").toString(), parsedLine.get("USER").toString()); + loadCount ++; + } + } + log("Done loading logs " + channel.getName(), true); + } catch (Exception e) { + e.printStackTrace(); + } finally { + loadingLogsInProgress = false; + } + }); + fileReadingThread.start(); + } }