diff --git a/src/urChatBasic/backend/logging/URLogger.java b/src/urChatBasic/backend/logging/URLogger.java index df33c72..c4291e1 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; @@ -34,10 +35,15 @@ public static void init () throws IOException, URISyntaxException logDir.mkdir(); } - System.setProperty("log4j2.configurationFile", DriverGUI.class.getResource(LOG4J_CONFIG_FILE).toURI().toString()); + 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()); - LOGGER = LoggerFactory.getLogger(URLogger.class); + LOGGER = LoggerFactory.getLogger("urchat"); Logger testLog = getLogger(LOGGER.getName(), Logger.class); @@ -84,6 +90,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/URPreferencesUtil.java b/src/urChatBasic/backend/utils/URPreferencesUtil.java index f56a601..8a16e6f 100644 --- a/src/urChatBasic/backend/utils/URPreferencesUtil.java +++ b/src/urChatBasic/backend/utils/URPreferencesUtil.java @@ -135,7 +135,7 @@ else if (URProfilesUtil.getActiveProfilePath().nodeExists(styleName)) } catch (Exception e) { - Constants.LOGGER.error("Active Profile: ["+URProfilesUtil.getActiveProfileName()+"] Unable to load ["+loadedStyle.getAttribute("name")+"]"+ " attempted with path: " + stylePrefPath); + Constants.LOGGER.error("Active Profile: ["+URProfilesUtil.getActiveProfileName()+"] Unable to load ["+loadedStyle.getAttribute("name")+"]"+ " attempted with path: " + stylePrefPath, e); return targetStyle; } diff --git a/src/urChatBasic/backend/utils/URProfilesUtil.java b/src/urChatBasic/backend/utils/URProfilesUtil.java index d060f2b..942147f 100644 --- a/src/urChatBasic/backend/utils/URProfilesUtil.java +++ b/src/urChatBasic/backend/utils/URProfilesUtil.java @@ -59,22 +59,17 @@ public static String[] getProfiles () return profileNames; } - public static void deleteProfile (String profileName) + { + deleteProfile(profileName, true); + } + + public static void deleteProfile (String profileName, boolean fireListeners) { try { String[] allProfiles = getProfiles(); - if(allProfiles.length > 1) - { - Constants.LOGGER.info( "Deleting profile [" + profileName + "]."); - Constants.BASE_PREFS.node(profileName).removeNode(); - fireListeners(EventType.DELETE); - } - else - throw new BackingStoreException("Unable to delete the last profile."); - if(profileName.equals(getActiveProfileName())) { if(profileExists(getDefaultProfile())) @@ -85,6 +80,16 @@ public static void deleteProfile (String profileName) } } + if(allProfiles.length > 1) + { + Constants.LOGGER.info( "Deleting profile [" + profileName + "]."); + Constants.BASE_PREFS.node(profileName).removeNode(); + if(fireListeners) + fireListeners(EventType.DELETE); + } + else + throw new BackingStoreException("Unable to delete the last profile."); + } catch (BackingStoreException e) { Constants.LOGGER.error("Problem deleting profile [" + profileName +"]." + e.getLocalizedMessage()); @@ -96,7 +101,7 @@ public static void deleteProfile (String profileName) */ public static void deleteProfile () { - deleteProfile(getActiveProfileName()); + deleteProfile(getActiveProfileName(), true); } public static String getActiveProfileName () diff --git a/src/urChatBasic/backend/utils/URStyle.java b/src/urChatBasic/backend/utils/URStyle.java index 2faf3ba..41ac2ff 100644 --- a/src/urChatBasic/backend/utils/URStyle.java +++ b/src/urChatBasic/backend/utils/URStyle.java @@ -36,7 +36,7 @@ public class URStyle extends SimpleAttributeSet * * @param defaultFont */ - public URStyle(String name, Font defaultFont) + public URStyle (String name, Font defaultFont) { super(); this.addAttribute("name", name); @@ -50,7 +50,7 @@ public URStyle(String name, Font defaultFont) * * @param defaultFont */ - public URStyle(String name, Font defaultFont, Color defaultForeground, Color defaultBackground) + public URStyle (String name, Font defaultFont, Color defaultForeground, Color defaultBackground) { super(); this.addAttribute("name", name); @@ -59,17 +59,22 @@ public URStyle(String name, Font defaultFont, Color defaultForeground, Color def setBackground(defaultBackground); } - public URStyle(SimpleAttributeSet fromAttributeSet) + public URStyle (SimpleAttributeSet fromAttributeSet) { super(fromAttributeSet); } - public String getName() + public String getName () { return getAttribute("name").toString(); } - public Font getFont() + public void setName (String newName) + { + addAttribute("name", newName); + } + + public Font getFont () { // int savedFontBoldItalic = 0; @@ -84,8 +89,7 @@ public Font getFont() fontMap.put(TextAttribute.POSTURE, StyleConstants.isItalic(this) ? TextAttribute.POSTURE_OBLIQUE : TextAttribute.POSTURE_REGULAR); fontMap.put(TextAttribute.UNDERLINE, StyleConstants.isUnderline(this) ? TextAttribute.UNDERLINE_ON : -1); - Font styleFont = new Font(StyleConstants.getFontFamily(this), Font.PLAIN, StyleConstants.getFontSize(this)) - .deriveFont(fontMap); + Font styleFont = new Font(StyleConstants.getFontFamily(this), Font.PLAIN, StyleConstants.getFontSize(this)).deriveFont(fontMap); return styleFont; // Font styleFont = new Font(StyleConstants.getFontFamily(this), savedFontBoldItalic, @@ -94,7 +98,7 @@ public Font getFont() // return styleFont; } - public void setFont(Font newFont) + public void setFont (Font newFont) { StyleConstants.setFontFamily(this, newFont.getFamily()); StyleConstants.setBold(this, newFont.isBold()); @@ -103,23 +107,23 @@ public void setFont(Font newFont) StyleConstants.setUnderline(this, isUnderline(newFont)); } - public Optional getForeground() + public Optional getForeground () { - if(getAttribute(StyleConstants.Foreground) != null) + if (getAttribute(StyleConstants.Foreground) != null) return Optional.of(StyleConstants.getForeground(this)); else return Optional.empty(); } - public Optional getBackground() + public Optional getBackground () { - if(getAttribute(StyleConstants.Background) != null) + if (getAttribute(StyleConstants.Background) != null) return Optional.of(StyleConstants.getBackground(this)); else return Optional.empty(); } - public Optional isBold() + public Optional isBold () { if (getAttribute(StyleConstants.Bold) != null) return Optional.of(StyleConstants.isBold(this)); @@ -127,7 +131,7 @@ public Optional isBold() return Optional.empty(); } - public Optional isItalic() + public Optional isItalic () { if (getAttribute(StyleConstants.Italic) != null) return Optional.of(StyleConstants.isItalic(this)); @@ -135,7 +139,7 @@ public Optional isItalic() return Optional.empty(); } - public Optional getFamily() + public Optional getFamily () { if (getAttribute(StyleConstants.FontFamily) != null) return Optional.of(StyleConstants.getFontFamily(this)); @@ -143,7 +147,7 @@ public Optional getFamily() return Optional.empty(); } - public Optional getSize() + public Optional getSize () { if (getAttribute(StyleConstants.FontSize) != null) return Optional.of(StyleConstants.getFontSize(this)); @@ -151,16 +155,16 @@ public Optional getSize() return Optional.empty(); } - public Optional isUnderline() + public Optional isUnderline () { - if (getAttribute(StyleConstants.Underline) != null) + if (getAttribute(StyleConstants.Underline) != null) return Optional.of(StyleConstants.isUnderline(this)); else return Optional.empty(); } // https://docs.oracle.com/javase/6/docs/api/java/awt/font/TextAttribute.html#UNDERLINE - public static boolean isUnderline(Font targetFont) + public static boolean isUnderline (Font targetFont) { if (targetFont != null && targetFont.getAttributes().get(TextAttribute.UNDERLINE) != null) return (int) targetFont.getAttributes().get(TextAttribute.UNDERLINE) == TextAttribute.UNDERLINE_ON; @@ -168,19 +172,20 @@ public static boolean isUnderline(Font targetFont) return false; } - public void setForeground(Color newColour) + public void setForeground (Color newColour) { StyleConstants.setForeground(this, newColour); } - public void setBackground(Color newColour) + public void setBackground (Color newColour) { StyleConstants.setBackground(this, newColour); } - public void load(Preferences prefPath) + public void load (Preferences prefPath) { - try { + try + { URStyle loadedStyle = URPreferencesUtil.loadStyle(this, prefPath); setFont(loadedStyle.getFont()); loadedStyle.getForeground().ifPresent(fg -> setForeground(fg)); @@ -191,24 +196,24 @@ public void load(Preferences prefPath) } } - public static String getKeymap(Object attributeObject) + public static String getKeymap (Object attributeObject) { return getKeymap(attributeObject.toString()); } - public static String getKeymap(String attributeName) + public static String getKeymap (String attributeName) { return ATTRIBUTE_KEYMAP.get(attributeName); } - public boolean equals(URStyle otherStyle) + public boolean equals (URStyle otherStyle) { return getFont().equals(otherStyle.getFont()) && getForeground().equals(otherStyle.getForeground()) && getBackground().equals(otherStyle.getBackground()); } @Override - public URStyle clone() + public URStyle clone () { return (URStyle) super.clone(); } diff --git a/src/urChatBasic/backend/utils/URUncaughtExceptionHandler.java b/src/urChatBasic/backend/utils/URUncaughtExceptionHandler.java index 8cdcc75..ab83635 100644 --- a/src/urChatBasic/backend/utils/URUncaughtExceptionHandler.java +++ b/src/urChatBasic/backend/utils/URUncaughtExceptionHandler.java @@ -6,6 +6,6 @@ public class URUncaughtExceptionHandler implements Thread.UncaughtExceptionHandl @Override public void uncaughtException(Thread t, Throwable e) { - Constants.LOGGER.error( "Uncaught exception: " + e.getLocalizedMessage(), e); + Constants.LOGGER.error( "Uncaught exception: " + e.getStackTrace(), e); } } \ No newline at end of file diff --git a/src/urChatBasic/base/Constants.java b/src/urChatBasic/base/Constants.java index 2308ebb..1e17f20 100644 --- a/src/urChatBasic/base/Constants.java +++ b/src/urChatBasic/base/Constants.java @@ -152,6 +152,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)"; diff --git a/src/urChatBasic/base/IRCChannelBase.java b/src/urChatBasic/base/IRCChannelBase.java index 34ed5ba..d1e7ba9 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; @@ -215,7 +217,7 @@ private void initChannel() // this.myMenu = new ChannelPopUp(); createChannelPopUp(); fontDialog.setVisible(false); - fontDialog.addSaveListener(new SaveFontListener()); + fontDialog.addFontSaveListener(new SaveFontListener()); myActions = new IRCActions(this); } @@ -458,28 +460,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 String getLine() { + public Message(Date date, String line, String fromUser) { + this.date = Optional.of(date); + this.line = line; + this.fromUser = fromUser; + } + + 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 MessagePair(line, fromUser)); + 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 Message(messageDate, message, fromUser)); if(!messageQueueInProgress) handleMessageQueue(); @@ -494,37 +523,38 @@ 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(); + Optional messageDate = message.getDate(); + String line = message.getLine(); + String fromUser = message.getUser(); Document document = lineFormatter.getDocument(); Element root = lineFormatter.getDocument().getDefaultRootElement(); 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 && root.getElementCount() > lineLimit) { Element firstLine = root.getElement(0); int endIndex = firstLine.getEndOffset(); @@ -532,8 +562,7 @@ public void run() try { document.remove(0, endIndex); - } - catch(BadLocationException ble) + } catch (BadLocationException ble) { Constants.LOGGER.error(ble.getLocalizedMessage()); } @@ -545,12 +574,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 +590,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 +877,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 +893,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 +950,32 @@ public void actionPerformed(ActionEvent arg0) } } + private class LoadChannelHistory implements ActionListener + { + @Override + public void actionPerformed(ActionEvent arg0) + { + 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(); + while ((line = br.readLine()) != null && lineFormatter.getLineCount() < maxCount) { + if(messageQueueInProgress && messageQueue.remainingCapacity() == 0) + Thread.sleep(10); + + Map parsedLine = parseLogLineFull(line); + if(parsedLine.size() != 0) + printText((Date) parsedLine.get("DATE"), parsedLine.get("MESSAGE").toString(), parsedLine.get("USER").toString()); + } + } catch (Exception e) { + e.printStackTrace(); + } + }); + fileReadingThread.start(); + } + } + private class QuitItem implements ActionListener { @Override @@ -1014,7 +1081,8 @@ public void setFont(Font f) { public void run() { - lineFormatter.setFont(fontDialog.getFontPanel().getFont()); + // lineFormatter.setFont(fontDialog.getFontPanel().getFont()); + lineFormatter.setStyle(fontDialog.getFontPanel().getStyle()); } }); } else diff --git a/src/urChatBasic/frontend/LineFormatter.java b/src/urChatBasic/frontend/LineFormatter.java index 34a3d05..bda00fe 100644 --- a/src/urChatBasic/frontend/LineFormatter.java +++ b/src/urChatBasic/frontend/LineFormatter.java @@ -84,6 +84,13 @@ public LineFormatter(URStyle baseStyle, JTextPane docOwner ,final IRCServerBase initStyles(baseStyle); } + public void setStyle (URStyle newStyle) + { + targetStyle = newStyle.clone(); + if (doc.getLength() > 0) + updateStyles(targetStyle); + } + public void setFont (Font newFont) { targetStyle.setFont(newFont); @@ -156,6 +163,7 @@ public URStyle defaultStyle(String name, boolean load) StyleConstants.setItalic(tempStyle, targetStyle.getFont().isItalic()); StyleConstants.setForeground(tempStyle, myForeground); + StyleConstants.setBackground(tempStyle, myBackground); if (load) tempStyle.load(settingsPath); @@ -453,14 +461,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) @@ -746,6 +766,12 @@ public String getLineAtPosition(int position) throws BadLocationException { return lineText; } + public int getLineCount () + { + Element root = doc.getDefaultRootElement(); + return root.getElementCount(); + } + private int getLinePosition(String targetLine) throws BadLocationException { Element root = doc.getDefaultRootElement(); @@ -783,11 +809,10 @@ public URStyle getStyleAtPosition(int position, String relativeLine) Constants.LOGGER.error(ble.getLocalizedMessage()); } AttributeSet textStyle = doc.getCharacterElement(position).getAttributes(); - 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<>(); @@ -842,14 +867,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; } /** @@ -859,10 +886,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; @@ -907,12 +941,12 @@ public void formattedDocument(Date lineDate, IRCUser fromUser, String fromString { // 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)); + insertPosition = addString(DriverGUI.gui.getTimeStampString(timeLine.get()) + " ", dateStyle(timePositionStyle, lineDate.get(), false), 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) { @@ -922,16 +956,16 @@ public void formattedDocument(Date lineDate, IRCUser fromUser, String fromString 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"); @@ -940,8 +974,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 01ff111..eff3218 100644 --- a/src/urChatBasic/frontend/UserGUI.java +++ b/src/urChatBasic/frontend/UserGUI.java @@ -1,7 +1,6 @@ package urChatBasic.frontend; import java.awt.*; -import java.util.logging.Level; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; @@ -185,6 +184,12 @@ public UROptionsPanel getConnectionPanel () return interfacePanel; } + + public UROptionsPanel getAppearancePanel () + { + return appearancePanel; + } + // /** // * Sets the current active profile - if the newProfileName doesn't exist it will be created. // * @param newProfileName @@ -266,7 +271,7 @@ public Component getListCellRendererComponent (JList list, Object value, int }); // clientFontPanel.getSaveButton().addActionListener(new SaveFontListener()); - clientFontPanel.addSaveListener(new SaveFontListener()); + clientFontPanel.addFontSaveListener(new SaveFontListener()); // clientFontPanel.getResetButton().addActionListener(new ResetFontListener()); previewTextScroll.setPreferredSize(new Dimension(700, 150)); @@ -347,14 +352,14 @@ 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, + previewLineFormatter.appendMessage(Optional.empty(), 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(), 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"), + previewLineFormatter.appendMessage(Optional.empty(), tempUser2, System.getProperty("user.name"), "Go to https://github.com/matty-r/urChat"); - previewLineFormatter.formattedDocument(new Date(), tempUser2, System.getProperty("user.name"), + previewLineFormatter.appendMessage(Optional.empty(), tempUser2, System.getProperty("user.name"), "Join #urchat on irc.libera.chat"); } else { @@ -377,7 +382,7 @@ public void mouseClicked (MouseEvent mouseEvent) FontDialog styleFontDialog = new FontDialog(styleName, previewLineFormatter.getStyleDefault(styleName), URProfilesUtil.getActiveProfilePath()); - styleFontDialog.addSaveListener(new SaveFontListener()); + styleFontDialog.addFontSaveListener(new SaveFontListener()); styleFontDialog.setVisible(true); } else if (SwingUtilities.isLeftMouseButton(mouseEvent) && null != isClickableText) { @@ -689,7 +694,7 @@ public void stateChanged (ChangeEvent e) } } - protected class SaveFontListener implements ActionListener + public class SaveFontListener implements ActionListener { @Override public void actionPerformed (ActionEvent arg0) @@ -779,6 +784,11 @@ public void run () }); } + public FontPanel getFontPanel () + { + return clientFontPanel; + } + /** * Returns the clientFontPanel style, otherwise creates the new default style. * @@ -839,7 +849,7 @@ public void setNewLAF (String newLAFname) } } catch (Exception e) { - Constants.LOGGER.error("Failed to set Pluggable LAF! " + e.getLocalizedMessage()); + Constants.LOGGER.error("Failed to set Pluggable LAF! ", e); } finally { if (!flatLafAvailable) @@ -849,7 +859,7 @@ public void setNewLAF (String newLAFname) UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { - Constants.LOGGER.error("Failed to setLookAndFeel! " + e.getLocalizedMessage()); + Constants.LOGGER.error("Failed to setLookAndFeel! ", e); } } } @@ -868,10 +878,14 @@ public void setNewLAF (String newLAFname) clientFontPanel.setDefaultStyle(defaultStyle); - SwingUtilities.updateComponentTreeUI(DriverGUI.frame); + if(DriverGUI.frame.isVisible()) + SwingUtilities.updateComponentTreeUI(DriverGUI.frame); + updateExtras(); + // DriverGUI.frame.dispose(); - DriverGUI.frame.validate(); + if(DriverGUI.frame.isVisible()) + DriverGUI.frame.validate(); } // Update the fonts and popup menus - these aren't under the component tree diff --git a/src/urChatBasic/frontend/components/FontPanel.java b/src/urChatBasic/frontend/components/FontPanel.java index 42e1a5f..6a46f42 100644 --- a/src/urChatBasic/frontend/components/FontPanel.java +++ b/src/urChatBasic/frontend/components/FontPanel.java @@ -62,7 +62,7 @@ public void actionPerformed (ActionEvent arg0) { colourDialog = new ColourDialog(styleName, getDefaultStyle(), getSettingsPath()); - colourDialog.getColourPanel().addSaveListener(e -> { + getColourPanel().addSaveListener(e -> { Constants.LOGGER.info( "Font Panel says: Save Colour pressed"); }); @@ -73,7 +73,7 @@ public void actionPerformed (ActionEvent arg0) { if (listeners[i] == ActionListener.class) { - colourDialog.getColourPanel().addSaveListener((ActionListener) listenerList.getListeners(ActionListener.class)[i]); + getColourPanel().addSaveListener((ActionListener) listenerList.getListeners(ActionListener.class)[i]); } } @@ -88,7 +88,7 @@ public void actionPerformed (ActionEvent arg0) setFont(targetStyle, true); // now fire the rest of the save listeners - fireSaveListeners(); + fireFontSaveListeners(); }); FONT_COMBO_BOX.addItemListener(new FontSelectionChange()); @@ -154,6 +154,11 @@ public void actionPerformed (ActionEvent arg0) add(MAKE_UNDERLINE, c); } + public ColourPanel getColourPanel () + { + return colourDialog.getColourPanel(); + } + public JButton getSaveButton () { return SAVE_BUTTON; @@ -168,9 +173,9 @@ public void setDefaultStyle (final URStyle f) { // defaultFont = f; defaultStyle = f.clone(); - colourDialog.getColourPanel().setDefaultStyle(defaultStyle); + getColourPanel().setDefaultStyle(defaultStyle); loadStyle(); - fireSaveListeners(); + fireFontSaveListeners(); } private URStyle getDefaultStyle () @@ -212,7 +217,7 @@ public void setFont (Font f) public void setStyle (final URStyle newStyle) { targetStyle = newStyle.clone(); - colourDialog.getColourPanel().setStyle(targetStyle); + getColourPanel().setStyle(targetStyle); setFont(targetStyle, false); } @@ -225,21 +230,22 @@ public void setStyle (final URStyle newStyle) */ public void setFont (final URStyle newStyle, Boolean saveToSettings) { - Font newFont = newStyle.getFont(); + URStyle tempStyle = newStyle.clone(); + Font newFont = tempStyle.getFont(); if (!getFont().equals(newFont) || saveToSettings) { - newStyle.isBold().ifPresent(bold -> MAKE_BOLD.setSelected(bold)); - newStyle.isItalic().ifPresent(italic -> MAKE_ITALIC.setSelected(italic)); - newStyle.isUnderline().ifPresent(underline -> MAKE_UNDERLINE.setSelected(underline)); - newStyle.getFamily().ifPresent(family -> FONT_COMBO_BOX.setSelectedItem(family)); - newStyle.getSize().ifPresent(size -> SIZES_COMBO_BOX.setSelectedItem(size)); + tempStyle.isBold().ifPresent(bold -> MAKE_BOLD.setSelected(bold)); + tempStyle.isItalic().ifPresent(italic -> MAKE_ITALIC.setSelected(italic)); + tempStyle.isUnderline().ifPresent(underline -> MAKE_UNDERLINE.setSelected(underline)); + tempStyle.getFamily().ifPresent(family -> FONT_COMBO_BOX.setSelectedItem(family)); + tempStyle.getSize().ifPresent(size -> SIZES_COMBO_BOX.setSelectedItem(size)); targetStyle.setFont(newFont); if (saveToSettings) { - URStyle colourPanelStyle = colourDialog.getColourPanel().getStyle(); + URStyle colourPanelStyle = getColourPanel().getStyle(); colourPanelStyle.getForeground().ifPresent(fg -> targetStyle.setForeground(fg)); colourPanelStyle.getBackground().ifPresent(bg -> targetStyle.setBackground(bg)); URPreferencesUtil.saveStyle(defaultStyle, targetStyle, settingsPath); @@ -270,12 +276,12 @@ private void previewFont () targetStyle.setFont(getFont()); } - public void addSaveListener (ActionListener actionListener) + public void addFontSaveListener (ActionListener actionListener) { listenerList.add(ActionListener.class, actionListener); } - protected void fireSaveListeners () + protected void fireFontSaveListeners () { Object[] listeners = this.listenerList.getListenerList(); @@ -335,7 +341,7 @@ public void setSettingsPath (Preferences settingsPath) this.settingsPath = settingsPath; loadStyle(); - colourDialog.getColourPanel().setSettingsPath(settingsPath); + getColourPanel().setSettingsPath(settingsPath); } public Preferences getSettingsPath () diff --git a/src/urChatBasic/frontend/dialogs/FontDialog.java b/src/urChatBasic/frontend/dialogs/FontDialog.java index 60b749a..9c3586c 100644 --- a/src/urChatBasic/frontend/dialogs/FontDialog.java +++ b/src/urChatBasic/frontend/dialogs/FontDialog.java @@ -52,10 +52,10 @@ public void setVisible(boolean b) super.setVisible(b); } - public void addSaveListener(ActionListener newActionListener) + public void addFontSaveListener(ActionListener newActionListener) { // fontPanel.getSaveButton().addActionListener(newActionListener); - fontPanel.addSaveListener(newActionListener); + fontPanel.addFontSaveListener(newActionListener); } public void addResetListener(ActionListener newActionListener) diff --git a/tests/backend/LogParsingTests.java b/tests/backend/LogParsingTests.java new file mode 100644 index 0000000..dfc2738 --- /dev/null +++ b/tests/backend/LogParsingTests.java @@ -0,0 +1,70 @@ +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.Test; +import urChatBasic.backend.utils.LogPatternParser; +import urChatBasic.backend.utils.LogPatternParser.LogPattern; +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 + 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 a6066e5..07487fb 100644 --- a/tests/backend/MessageHandlerTests.java +++ b/tests/backend/MessageHandlerTests.java @@ -65,7 +65,7 @@ public void tearDown () throws Exception testServer.quitChannels(); // URProfilesUtil.getActiveProfilePath().sync(); // URProfilesUtil.getActiveProfilePath().sync(); - URProfilesUtil.deleteProfile(testDriver.getTestProfileName()); + URProfilesUtil.deleteProfile(testDriver.getTestProfileName(), false); TestDriverGUI.closeWindow(); } diff --git a/tests/backend/ProfileTests.java b/tests/backend/ProfileTests.java index 12c3d08..f54a10c 100644 --- a/tests/backend/ProfileTests.java +++ b/tests/backend/ProfileTests.java @@ -1,6 +1,9 @@ package backend; -import static org.testng.AssertJUnit.*; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertTrue; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.Reporter.log; import java.nio.file.Path; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -9,8 +12,7 @@ import java.util.Optional; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; -import org.testng.Reporter; -import org.testng.annotations.AfterMethod; +import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import urChatBasic.backend.utils.URPreferencesUtil; @@ -29,11 +31,10 @@ public void setUp() throws Exception testDriver = new TestDriverGUI(); } - @AfterMethod(alwaysRun = true) + @AfterClass(alwaysRun = true) public void tearDown () throws Exception { - Reporter.log("Deleting testing profile.", true); - URProfilesUtil.deleteProfile(testDriver.getTestProfileName()); + TestDriverGUI.cleanupTestProfiles(); TestDriverGUI.closeWindow(); } @@ -49,54 +50,68 @@ public void createdTestProfileTest () } @Test - public void deleteTestProfileTest () + public void deleteTestProfileTest () throws InterruptedException { + log("Check it exists", true); if(!URProfilesUtil.profileExists(testDriver.getTestProfileName())) URProfilesUtil.createProfile(testDriver.getTestProfileName()); + + TestDriverGUI.waitForEverything(TestDriverGUI.gui); URProfilesUtil.setActiveProfileName(testDriver.getTestProfileName()); + TestDriverGUI.waitForEverything(TestDriverGUI.gui); assertTrue(URProfilesUtil.getActiveProfileName().equals(testDriver.getTestProfileName())); // Delete the active profile - URProfilesUtil.deleteProfile(testDriver.getTestProfileName()); + URProfilesUtil.deleteProfile(testDriver.getTestProfileName(), false); assertFalse(URProfilesUtil.profileExists(testDriver.getTestProfileName())); } @Test - public void createProfileAndDeleteTest () + public void createProfileAndDeleteTest () throws InterruptedException { String anotherTestProfileName = "createProfileAndDeleteTest" + (new SimpleDateFormat("yyMMdd")).format(new Date()); + log("Create Profile ["+anotherTestProfileName+"]", true); URProfilesUtil.createProfile(anotherTestProfileName); + + log("Wait for stuff", true); + TestDriverGUI.waitForEverything(TestDriverGUI.gui); // Profile Exists assertTrue(URProfilesUtil.profileExists(anotherTestProfileName)); + log("Set profile ["+anotherTestProfileName+"]", true); URProfilesUtil.setActiveProfileName(anotherTestProfileName); - + TestDriverGUI.waitForEverything(TestDriverGUI.gui); // Has the default setting assertEquals(Constants.DEFAULT_TIME_STAMP_FORMAT, URProfilesUtil.getActiveProfilePath().get(Constants.KEY_TIME_STAMP_FORMAT, "ERROR!")); - URProfilesUtil.deleteProfile(anotherTestProfileName); + URProfilesUtil.deleteProfile(anotherTestProfileName, false); } @Test - public void invalidProfileTest () + public void invalidProfileTest () throws InterruptedException { + log("Active Profile [" + URProfilesUtil.getActiveProfileName() + "]", true); String originalActiveProfile = URProfilesUtil.getActiveProfileName(); String anotherTestProfileName = "invalidProfileTest" + (new SimpleDateFormat("yyMMdd")).format(new Date()); // Profile Exists assertFalse("Profile ["+anotherTestProfileName+"] shouldn't exist!",URProfilesUtil.profileExists(anotherTestProfileName)); - + TestDriverGUI.waitForEverything(TestDriverGUI.gui); URProfilesUtil.setActiveProfileName(anotherTestProfileName); - + TestDriverGUI.waitForEverything(TestDriverGUI.gui); assertEquals(originalActiveProfile, URProfilesUtil.getActiveProfileName()); } @Test - public void cloneProfileTest () throws BackingStoreException + public void cloneProfileTest () throws BackingStoreException, InterruptedException { + log("Loading Profile [" + testDriver.getTestProfileName() + "]", true); Preferences originalPathRoot = URProfilesUtil.getProfilePath(testDriver.getTestProfileName()); + + log("Clone Profile [" + testDriver.getTestProfileName() + "]", true); Preferences clonedProfileRoot = URProfilesUtil.cloneProfile(testDriver.getTestProfileName(), Optional.empty()); ArrayList originalNodes = URPreferencesUtil.getAllNodes(originalPathRoot); - + TestDriverGUI.waitForEverything(TestDriverGUI.gui); + log("Checking preferences match original profile", true); for (Preferences originalPrefPath : originalNodes) { Preferences clonedPath = clonedProfileRoot; @@ -122,14 +137,16 @@ public void cloneProfileTest () throws BackingStoreException } @Test - public void switchToClonedProfileTest () throws BackingStoreException + public void switchToClonedProfileTest () throws BackingStoreException, InterruptedException { Preferences clonedProfileRoot = URProfilesUtil.cloneProfile(testDriver.getTestProfileName(), Optional.empty()); final String clonedProfileName; - + TestDriverGUI.waitForEverything(TestDriverGUI.gui); clonedProfileName = Arrays.stream(URProfilesUtil.getProfiles()).filter(e -> clonedProfileRoot.toString().endsWith(e)).findFirst().get(); URProfilesUtil.setActiveProfileName(clonedProfileName); + + TestDriverGUI.waitForEverything(TestDriverGUI.gui); // Delete the cloned profile clonedProfileRoot.removeNode(); } diff --git a/tests/frontend/AppearanceTests.java b/tests/frontend/AppearanceTests.java new file mode 100644 index 0000000..e5283f3 --- /dev/null +++ b/tests/frontend/AppearanceTests.java @@ -0,0 +1,221 @@ +package frontend; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertTrue; +import static org.testng.Reporter.log; +import java.awt.Font; +import java.awt.GraphicsEnvironment; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +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.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 AppearanceTests +{ + MessageHandler testHandler; + IRCServer testServer; + TestDriverGUI testDriver; + UserGUI testGUI; + final static int MAX_CHANNEL_NAMES = 10; + final static String CHANNEL_PREFIX = "#someChannel"; + final List PUB_CHANNEL_NAMES = new ArrayList<>(); + IRCUser testUser; + Connection testConnection; + + @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(); + + for (String channelName : PUB_CHANNEL_NAMES) + { + String rawMessage = ":someuser!~someuser@urchatclient PRIVMSG " + channelName + " :Welcome to " + channelName; + + Message testMessage = testHandler.new Message(rawMessage); + testHandler.parseMessage(testMessage); + } + + TestDriverGUI.waitForEverything(testGUI); + } + + @AfterClass(alwaysRun = true) + public void tearDown () throws Exception + { + // Reporter.log("Deleting testing profile.", true); + testServer.quitChannels(); + // URProfilesUtil.getActiveProfilePath().sync(); + // URProfilesUtil.getActiveProfilePath().sync(); + URProfilesUtil.deleteProfile(testDriver.getTestProfileName(), false); + TestDriverGUI.closeWindow(); + } + + /* + * Tests: - Change Font - Change Font Size - Change Foreground Colour - Change Background Colour + */ + + @Test + public void changeDefaultFontAndSizeTest () throws BadLocationException, InterruptedException + { + // Get Current Font in Appearance panel + URStyle guiStyle = testGUI.getStyle(); + + // Get Current Font in all rooms + for (String pubChannelName : PUB_CHANNEL_NAMES) + { + IRCChannel pubChannel = testServer.getCreatedChannel(pubChannelName); + log("Have joined " + pubChannelName + " successfully?", true); + String welcomeMessage = pubChannel.getLineFormatter().getLineAtPosition(13).split("] ")[1].trim(); + assertEquals(" Welcome to " + pubChannelName, welcomeMessage); + + log("Check current style in the channel is correct.", true); + URStyle channelStyle = pubChannel.getLineFormatter().getStyleAtPosition(22, welcomeMessage); + + assertTrue(guiStyle.equals(channelStyle)); + } + + String[] FONT_LIST = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + + URStyle newStyle = guiStyle.clone(); + 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(); + + + assertEquals(newStyle, guiStyle); + + for (String pubChannelName : PUB_CHANNEL_NAMES) + { + IRCChannel pubChannel = testServer.getCreatedChannel(pubChannelName); + String welcomeMessage = pubChannel.getLineFormatter().getLineAtPosition(13).split("] ")[1].trim(); + log("Check current style has updated.", true); + + URStyle 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(); + TestDriverGUI.waitForEverything(testGUI); + 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 changeDefaultForegroundAndBackgroundTest () throws BadLocationException, InterruptedException + { + // Get Current Font in Appearance panel + URStyle guiStyle = testGUI.getStyle(); + + // Get Current Font in all rooms + for (String pubChannelName : PUB_CHANNEL_NAMES) + { + IRCChannel pubChannel = testServer.getCreatedChannel(pubChannelName); + log("Have joined " + pubChannelName + " successfully?", true); + String welcomeMessage = pubChannel.getLineFormatter().getLineAtPosition(13).split("] ")[1].trim(); + assertEquals(" Welcome to " + pubChannelName, welcomeMessage); + + log("Check current style in the channel is correct.", true); + URStyle channelStyle = pubChannel.getLineFormatter().getStyleAtPosition(22, welcomeMessage); + + assertTrue(guiStyle.equals(channelStyle)); + } + + URStyle newStyle = guiStyle.clone(); + newStyle.setForeground(TestUtils.getRandomColour()); + + log("Set foreground to " +URColour.hexEncode(newStyle.getForeground().get()), true); + + newStyle.setBackground(TestUtils.getRandomColour()); + + log("Set background to " +URColour.hexEncode(newStyle.getBackground().get()), true); + testGUI.getFontPanel().setDefaultStyle(newStyle); + TestDriverGUI.waitForEverything(testGUI); + guiStyle = testGUI.getStyle(); + + assertEquals(newStyle, guiStyle); + + for (String pubChannelName : PUB_CHANNEL_NAMES) + { + IRCChannel pubChannel = testServer.getCreatedChannel(pubChannelName); + String welcomeMessage = pubChannel.getLineFormatter().getLineAtPosition(13).split("] ")[1].trim(); + log("Check current style has updated.", true); + + URStyle 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(); + assertEquals("Channel font family doesn't match GUI font family.", testStyleFont, channelStyleFont); + log(pubChannelName + " font is good.", true); + + int testStyleSize = guiStyle.getSize().get(); + int channelStyleSize = channelStyle.getSize().get(); + assertEquals("Channel 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); + log(pubChannelName + " Foreground is good.", true); + + 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); + log(pubChannelName + " background is good.", true); + } + } +} diff --git a/tests/frontend/LAFTests.java b/tests/frontend/LAFTests.java index 6ed827d..42c6965 100644 --- a/tests/frontend/LAFTests.java +++ b/tests/frontend/LAFTests.java @@ -33,7 +33,7 @@ public void setUp() throws Exception public void tearDown () throws Exception { Reporter.log("Deleting testing profile.", true); - URProfilesUtil.deleteProfile(testDriver.getTestProfileName()); + URProfilesUtil.deleteProfile(testDriver.getTestProfileName(), false); TestDriverGUI.closeWindow(); } diff --git a/tests/frontend/LineFormatterTests.java b/tests/frontend/LineFormatterTests.java index 2707c11..a28066b 100644 --- a/tests/frontend/LineFormatterTests.java +++ b/tests/frontend/LineFormatterTests.java @@ -24,6 +24,7 @@ import urChatBasic.frontend.IRCUser; import urChatBasic.frontend.UserGUI; import utils.TestDriverGUI; +import static org.testng.Reporter.log; public class LineFormatterTests { @@ -54,8 +55,11 @@ public void setUp () throws Exception @AfterClass(alwaysRun = true) public void tearDown () throws Exception { + log("Quit channels", true); testServer.quitChannels(); - URProfilesUtil.deleteProfile(testDriver.getTestProfileName()); + log("Delete test profile", true); + URProfilesUtil.deleteProfile(testDriver.getTestProfileName(), false); + log("Close test window", true); TestDriverGUI.closeWindow(); } diff --git a/tests/utils/TestDriverGUI.java b/tests/utils/TestDriverGUI.java index 2f8a5b0..451b78f 100644 --- a/tests/utils/TestDriverGUI.java +++ b/tests/utils/TestDriverGUI.java @@ -1,16 +1,19 @@ package utils; + import java.awt.Toolkit; import java.awt.event.WindowEvent; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; -import java.util.logging.Handler; -import java.util.logging.Level; import javax.swing.JFrame; import javax.swing.SwingUtilities; import org.testng.Reporter; +import static org.testng.Reporter.log; import urChatBasic.backend.utils.URProfilesUtil; import urChatBasic.base.Constants; import urChatBasic.base.IRCChannelBase; @@ -22,53 +25,68 @@ public class TestDriverGUI extends DriverGUI { final String testProfileName = "testingprofile" + (new SimpleDateFormat("yyMMddss")).format(new Date()); + static List testProfiles = new ArrayList<>(); public String getTestProfileName () { return testProfileName; } - public TestDriverGUI () throws IOException + public TestDriverGUI () throws IOException, InvocationTargetException, InterruptedException { - Reporter.log("Creating test gui", true); + log("Creating test gui", true); Constants.init(); initLAFLoader(); - frame = new JFrame("urChat"); - URProfilesUtil.createProfile(testProfileName); - gui = createGUI(Optional.of(testProfileName)); - gui.setTimeLineString(Constants.DEFAULT_TIME_STAMP_FORMAT); - gui.setNickFormatString(Constants.DEFAULT_NICK_FORMAT); - gui.setupUserGUI(); - // testGUI.setupUserGUI(); + + SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + frame = new JFrame("urChat"); + log("Creating test profile [" + testProfileName + "]", true); + 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(); + frame.setVisible(false); + } + }); } public static void waitForEverything (UserGUI gui) throws InterruptedException { boolean wait = true; // wait for message queue to finish, and for updating styles to finish - while(wait) + while (wait) { wait = false; TimeUnit.SECONDS.sleep(1); - if(gui.previewLineFormatter.updateStylesInProgress.get()) + if (gui.previewLineFormatter.updateStylesInProgress.get()) { - Reporter.log("Update styles in Progress.. waiting", true); + log("Update styles in Progress.. waiting", true); wait = true; continue; } - for (IRCServerBase server : gui.getCreatedServers()) { - for (IRCChannelBase channel : ((IRCServer) server).createdChannels) { - if(channel.messageQueueInProgress) + for (IRCServerBase server : gui.getCreatedServers()) + { + for (IRCChannelBase channel : ((IRCServer) server).createdChannels) + { + if (channel.messageQueueInProgress) { - Reporter.log("Message Queue in Progress.. waiting", true); + log("Message Queue in Progress.. waiting", true); wait = true; break; } - if(channel.getLineFormatter().updateStylesInProgress.get()) + if (channel.getLineFormatter().updateStylesInProgress.get()) { - Reporter.log("Update styles in Progress.. waiting", true); + log("Update styles in Progress.. waiting", true); wait = true; break; } @@ -77,22 +95,23 @@ public static void waitForEverything (UserGUI gui) throws InterruptedException } } - public static void startTestGUI(UserGUI gui) throws InterruptedException + public static void cleanupTestProfiles () { - // frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - waitForEverything(gui); - - frame.setContentPane(gui); - frame.pack(); + URProfilesUtil.setActiveProfileName(URProfilesUtil.getDefaultProfile()); + for (String testProfileName : testProfiles) { + Reporter.log("Deleting testing profile ["+testProfileName+"]", true); + URProfilesUtil.deleteProfile(testProfileName, false); + } + } + public static void startTestGUI (UserGUI gui) throws InterruptedException + { + waitForEverything(gui); SwingUtilities.invokeLater(gui); - - Constants.LOGGER.info( "Started"); - - frame.setVisible(false); + log("Started", true); } - public static void closeWindow() + public static void closeWindow () { WindowEvent closingEvent = new WindowEvent(frame, WindowEvent.WINDOW_CLOSING); Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(closingEvent); diff --git a/tests/utils/TestUtils.java b/tests/utils/TestUtils.java new file mode 100644 index 0000000..3439c92 --- /dev/null +++ b/tests/utils/TestUtils.java @@ -0,0 +1,16 @@ +package utils; + +import java.awt.Color; +import java.util.Random; + +public class TestUtils { + + public static Color getRandomColour () + { + int red = new Random().nextInt(256); + int green = new Random().nextInt(256); + int blue = new Random().nextInt(256); + + return new Color(red, green, blue); + } +}