From eb4d64d18999a30a29081756eff061cfe7162709 Mon Sep 17 00:00:00 2001 From: Sandy Mossgrave Date: Sat, 5 Oct 2024 15:20:55 -0700 Subject: [PATCH] Fixed message store. Enabled writing indirect configuration values to files. --- .../priestess/Configuration.java | 247 +++++++++++++----- .../tinyplantnews/priestess/MessageStore.java | 49 +++- .../tinyplantnews/priestess/Priestess.java | 50 ++-- 3 files changed, 240 insertions(+), 106 deletions(-) diff --git a/src/main/java/com/tinyplantnews/priestess/Configuration.java b/src/main/java/com/tinyplantnews/priestess/Configuration.java index e636c0f..f7ff4bd 100644 --- a/src/main/java/com/tinyplantnews/priestess/Configuration.java +++ b/src/main/java/com/tinyplantnews/priestess/Configuration.java @@ -31,34 +31,23 @@ import java.util.prefs.Preferences; /** * "Ms Merry," you may say, "You literally import the Preferences class, and yet - * you insist on writing your own configuration store. Why?" "Iunno," I reply. + * you insist on writing your own configuration store. Why?" I reply, "Iunno." * * @author sandy */ public class Configuration { private static String CONFIG_FILE_LOCATION = null; + private static Path WORKING_DIR = null; public static final String CONFIG_FILE_KEY = "PRIESTESS_CONFIG"; - public static final Properties props = System.getProperties(); public static final Preferences prefs = Preferences.userNodeForPackage(Configuration.class); public static final String DEFAULT_CONFIG_FILE_NAME = "priestessconfig.txt"; - private static HashMap configuration = new HashMap<>(); + private static HashMap configuration = new HashMap<>(); private static final Logger log = Logger.getLogger(Configuration.class.getName()); - public static String findConfigurationFilePath() { - if (getCONFIG_FILE_LOCATION() != null) { - return getCONFIG_FILE_LOCATION(); - } - setCONFIG_FILE_LOCATION(findOrCreateConfigurationFile(), false); - return getCONFIG_FILE_LOCATION(); - } - private static String findOrCreateConfigurationFile() { - /* - * Try the environment variable - */ JOptionPane.showConfirmDialog(null, "Select where cult files will be stored."); JFileChooser jfc = new JFileChooser("Config File for Discord Cult Priestess"); jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); @@ -67,8 +56,8 @@ public class Configuration { while (!approved) { rv = jfc.showDialog(null, "Select"); if (rv == JFileChooser.APPROVE_OPTION) { - File f = jfc.getSelectedFile(); - if (f.isDirectory() && f.canRead() && f.canWrite()) { + File f = resolveDirectoryToConfigFile(jfc.getSelectedFile()); + if (f.canRead() && f.canWrite()) { prefs.put(CONFIG_FILE_KEY, f.getAbsolutePath()); try { prefs.flush(); @@ -85,48 +74,78 @@ public class Configuration { return ""; } + public static Path findFileInConfigurationDirectory(Path a, Path p) { + a = a.normalize(); + Path p1 = a.resolveSibling(p); + log.log(Level.INFO, "Resolving {0} against {1} to get {2}", new Object[] { p, a, p1 }); + if (p1.normalize().startsWith(a.getParent())) { + return p1; + } else { + return null; + } + } + + public static Path findFileInConfigurationDirectory(Path p) { + return findFileInConfigurationDirectory(pathCONFIG_FILE_LOCATION(), p); + } + public static final String FILE_MARKER = "(file)"; - public static String resolveIndirectConfigurationValue(String v) { + public static String resolveIndirectConfigurationValue(String v, File configfile) throws FileNotFoundException { + if (configfile == null) { + throw new FileNotFoundException( + String.format("Could not resolve indirect value {0} against configuration file {1}", + new Object[] { v, configfile })); + } log.log(Level.INFO, "Index of <" + FILE_MARKER + "> in <" + v + "> is " + v.indexOf(FILE_MARKER)); - if (v.indexOf(FILE_MARKER) == 0) { - String relpath = v.substring(FILE_MARKER.length()).trim(); + String relpath; + if (v.contains(FILE_MARKER)) { + relpath = v.substring(v.indexOf(FILE_MARKER) + FILE_MARKER.length()); + } else { + relpath = v; + } + relpath = relpath.trim(); - File configFile = Paths.get(CONFIG_FILE_LOCATION).toFile(); - String configDir; - if (configFile.isDirectory()) { - configDir = configFile.getPath(); - } else { - configDir = configFile.getParent(); - } + Path indirectValuePath = findFileInConfigurationDirectory(configfile.toPath(), Paths.get(relpath)); + try (BufferedReader br = new BufferedReader(new FileReader(indirectValuePath.toFile()));) { + final StringBuilder sb = new StringBuilder(); + br.lines().forEach((s) -> { + sb.append(s); + }); + v = sb.toString(); + } catch (FileNotFoundException ex) { - Path indirectValuePath = Paths.get(configDir, relpath); - try (BufferedReader br = new BufferedReader(new FileReader(indirectValuePath.toFile()));) { - final StringBuilder sb = new StringBuilder(); - br.lines().forEach((s) -> { - sb.append(s); - }); - v = sb.toString(); - } catch (FileNotFoundException ex) { - - Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, - "Error loading indirect configuration value from (" + indirectValuePath.toString() + ") {0}", - ex); - } catch (IOException e) { - Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, - "Error loading indirect configuration value from (" + indirectValuePath.toString() + ") {0}", - e); - } + Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, + "Error loading indirect configuration value from (" + indirectValuePath.toString() + ") {0}", ex); + } catch (IOException e) { + Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, + "Error loading indirect configuration value from (" + indirectValuePath.toString() + ") {0}", e); } return v; } private static final String delimiter = ": "; + /** + * If given file is a directory, look for the default configuration filename in + * the given directory + * + * @param f The configuration file, or a directory containing the default one. + * @return + */ private static File resolveDirectoryToConfigFile(File f) { - if (f.isDirectory()) { f = Paths.get(f.getPath(), Configuration.DEFAULT_CONFIG_FILE_NAME).toFile(); + try { + if (f.createNewFile()) { + log.log(Level.INFO, "Creating new configuration file in provided directory."); + } else { + log.log(Level.FINEST, "Using default-named configuration file in provided directory."); + } + } catch (IOException e) { + log.log(Level.SEVERE, "Could not create a configuration file in the specified directory {0}: {1}", + new Object[] { f, e.toString() }); + } } return f; } @@ -134,29 +153,25 @@ public class Configuration { /** * */ - public static boolean readConfigurationFromFile(String path) throws IOException { + private static boolean readConfigurationFromFile(String path) throws IOException { if (path == null || path.isBlank()) { return false; } - File f = Paths.get(path).toFile(); - if (CONFIG_FILE_LOCATION == null) { - findOrCreateConfigurationFile(); - } - f = resolveDirectoryToConfigFile(f); + File f = resolveDirectoryToConfigFile(new File(path)); try (BufferedReader br = new BufferedReader(new FileReader(f))) { br.lines().forEach((s) -> { String key = s.substring(0, s.indexOf(delimiter)); - if (s.length() == s.indexOf(delimiter) + delimiter.length()) { - log.log(Level.FINE, "read empty value for key (" + key + ") from configuration file"); - configuration.put(key, ""); - } else { - String value = s.substring(s.indexOf(delimiter) + delimiter.length()); - value = resolveIndirectConfigurationValue(value); - configuration.put(key, value); + try { + configuration.put(key, new StringOrIndirect(s, f)); + } catch (FileNotFoundException fnfe) { + log.log(Level.SEVERE, + "Unable to resolve indirect configuration value against configuration directory." + + "This should never occur? {0}", + fnfe); } }); - CONFIG_FILE_LOCATION = f.toString(); + setCONFIG_FILE_LOCATION(f.toString(), true); return true; } catch (FileNotFoundException fnfe) { return false; @@ -166,12 +181,14 @@ public class Configuration { public static void findAndReadConfigurationFile() { try { if (readConfigurationFromFile(System.getenv(CONFIG_FILE_KEY))) { - return; - } else if (readConfigurationFromFile(props.getProperty(CONFIG_FILE_KEY, ""))) { + log.log(Level.FINE, "Using environment variable to locate configuration file"); return; } else if (readConfigurationFromFile(prefs.get(CONFIG_FILE_KEY, ""))) { + log.log(Level.FINE, "Using package preferences to locate configuration file"); return; } else if (readConfigurationFromFile(findOrCreateConfigurationFile())) { + log.log(Level.FINE, "Using interactive dialogue to locate configuration file"); + return; } else { log.log(Level.WARNING, "Could not resolve a configuration file path, using defaults."); } @@ -188,13 +205,16 @@ public class Configuration { return CONFIG_FILE_LOCATION; } + public static Path pathCONFIG_FILE_LOCATION() { + return Paths.get(CONFIG_FILE_LOCATION); + } + /** * @param aCONFIG_FILE_LOCATION the CONFIG_FILE_LOCATION to set */ public static void setCONFIG_FILE_LOCATION(String aCONFIG_FILE_LOCATION, boolean persistent) { - CONFIG_FILE_LOCATION = aCONFIG_FILE_LOCATION; + CONFIG_FILE_LOCATION = resolveDirectoryToConfigFile(new File(aCONFIG_FILE_LOCATION)).getAbsolutePath(); if (persistent) { - props.setProperty(CONFIG_FILE_KEY, CONFIG_FILE_LOCATION); prefs.put(CONFIG_FILE_KEY, CONFIG_FILE_LOCATION); try { prefs.flush(); @@ -207,25 +227,28 @@ public class Configuration { } - public static void setConfigurationParameter(String key, String value) { - configuration.put(key, value); + public static void setConfigurationParameter(String key, String value) throws FileNotFoundException { + configuration.put(key, new StringOrIndirect(value, null)); } public static String getConfigurationParameter(String key) { if (configuration.containsKey(key)) { - return configuration.get(key); + return configuration.get(key).value; } else { - configuration.put(key, ""); return ""; } } public static void commitConfigurationToFile() { + commitConfigurationToFile(Paths.get(getCONFIG_FILE_LOCATION())); + } + public static void commitConfigurationToFile(Path p) { + Path bkupPath = Paths.get(p.toString() + "bkup"); try { - Files.deleteIfExists(Paths.get(CONFIG_FILE_LOCATION + "bkup")); + Files.deleteIfExists(bkupPath); try { - Files.copy(Paths.get(CONFIG_FILE_LOCATION), Paths.get(CONFIG_FILE_LOCATION + "bkup")); + Files.copy(p, bkupPath); } catch (IOException ioe) { if (ioe instanceof NoSuchFileException) { log.log(Level.FINE, "No existing configuration file--not creating backup"); @@ -240,12 +263,12 @@ public class Configuration { "Making backup configuration file failed. Exiting without committing configuration to file"); return; } - File f = Paths.get(CONFIG_FILE_LOCATION).toFile(); - f = resolveDirectoryToConfigFile(f); + File f = resolveDirectoryToConfigFile(p.toFile()); + log.log(Level.FINE, "Committing configuration to {0}", p.toString()); try (BufferedWriter br = new BufferedWriter(new FileWriter(f, false))) { configuration.forEach((key, value) -> { try { - br.append(key + delimiter + value + "\n"); + br.append(key + delimiter + value.toRecord(f) + "\n"); } catch (IOException ex) { Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex); } @@ -296,4 +319,86 @@ public class Configuration { } } + public static class StringOrIndirect { + public String value; + public String path; + + public StringOrIndirect() { + value = ""; + } + + public StringOrIndirect(String source, File configfile) throws FileNotFoundException { + if (configfile == null) { + Path p = pathCONFIG_FILE_LOCATION(); + if (p != null) { + configfile = p.toFile(); + } else { + configfile = null; + } + } + + if (source.contains(delimiter)) { + + if (source.length() == source.indexOf(delimiter) + delimiter.length()) { + String key = source.substring(0, source.indexOf(delimiter)); + log.log(Level.FINE, "read empty value for key (" + key + ") from configuration file"); + value = ""; + } else { + } + source = source.substring(source.indexOf(delimiter) + delimiter.length()); + } + // After this point the 'source' string should only contain an immediate value + // or a (file) abc.xyz record + if (source.contains(FILE_MARKER)) { + path = source.substring(source.indexOf(FILE_MARKER) + FILE_MARKER.length()).trim(); + value = resolveIndirectConfigurationValue(source, configfile); + } else { + value = source; + } + } + + private boolean immediate() { + return path == null; + } + + /** + * Converts this to a record in a configuration file. If indirect, commits its + * value to the indirect file. + * + * @return + */ + public String toRecord(File f) { + if (immediate()) { + return value; + } else { + try (BufferedWriter br = new BufferedWriter(new FileWriter( + findFileInConfigurationDirectory(f.toPath(), Paths.get(path)).toFile(), false))) { + br.append(value); + br.flush(); + br.close(); + } catch (FileNotFoundException ex) { + Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex); + } catch (IOException ex) { + Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex); + } + return FILE_MARKER + path; + } + } + + @Override + public String toString() { + return value; + } + + public String toDebugString() { + if (immediate()) { + return String.format("StringOrIndirect ({0})", value); + } else { + return String.format("StringOrIndirect from {1}: ({0})", value, path); + } + + } + + } + } diff --git a/src/main/java/com/tinyplantnews/priestess/MessageStore.java b/src/main/java/com/tinyplantnews/priestess/MessageStore.java index 256fb81..0919d9a 100644 --- a/src/main/java/com/tinyplantnews/priestess/MessageStore.java +++ b/src/main/java/com/tinyplantnews/priestess/MessageStore.java @@ -19,11 +19,14 @@ import java.util.logging.Logger; /** * OH IT STORES THE PROPHECIES * - * The configuration file (priestessconfig.txt) contains a key, 'messages', for which the value is a path to the messages store. - * The messages store contains prophecies that are given every few bean goose invocations. - * This class first loads the file, scans it and generates a list of indices for the messages it contains. + * The configuration file (priestessconfig.txt) contains a key, 'messages', for + * which the value is a path to the messages store. The messages store contains + * prophecies that are given every few bean goose invocations. This class first + * loads the file, scans it and generates a list of indices for the messages it + * contains. * - * This class could definitely use a cache of the message starts--a special block at the beginning/end of the file with a list of message starts/ends, + * This class could definitely use a cache of the message starts--a special + * block at the beginning/end of the file with a list of message starts/ends, * but I'm not up to writing that today - 7 May 2023 * * @author atomb @@ -42,15 +45,30 @@ public class MessageStore { private boolean messagesCached = false; private long loadStart = Long.MAX_VALUE; - public MessageStore(File f1) throws FileNotFoundException, IOException { - loadStart = System.nanoTime(); - f = new RandomAccessFile(f1.getPath(), "r"); - System.out.println("Message store is " + f.length() + " bytes long"); - fr = f.getChannel(); - filelength = f.length(); - mc = new MessageCacher(); - t = new Thread(mc); - t.start(); + private Logger log = Logger.getLogger(MessageStore.class.getName()); + + public MessageStore(File f1) { + log.log(Level.INFO, "loading message store from {0}", f1); + if (f1 == null) { + log.log(Level.WARNING, "Message store not given, initializing blank one"); + messagestarts = new int[] {}; + } else { + try { + f1 = Configuration.findFileInConfigurationDirectory(f1.toPath()).toFile(); + loadStart = System.nanoTime(); + f = new RandomAccessFile(f1.getPath(), "r"); + log.log(Level.FINE, "Message store is {0} bytes long", f.length()); + fr = f.getChannel(); + filelength = f.length(); + mc = new MessageCacher(); + t = new Thread(mc); + t.start(); + log.log(Level.FINE, "Loading message store at {0}", f); + } catch (IOException fnfe) { + log.log(Level.WARNING, "Unable to open message store, initializing blank one {0}", fnfe); + messagestarts = new int[] {}; + } + } } public String messageAt(int i) { @@ -83,6 +101,9 @@ public class MessageStore { } public String randomMessage() { + if (messagestarts == null || messagestarts.length == 0) { + return null; + } return messageAt((int) (messagestarts.length * Math.random())); } @@ -135,7 +156,7 @@ public class MessageStore { messagestarts[i] = messagestarts1[i].intValue(); } } catch (IOException ex) { - Logger.getLogger(MessageStore.class.getName()).log(Level.SEVERE, null, ex); + log.log(Level.SEVERE, null, ex); } for (int i : messagestarts) { System.out.println("Found invocation message at position " + i); diff --git a/src/main/java/com/tinyplantnews/priestess/Priestess.java b/src/main/java/com/tinyplantnews/priestess/Priestess.java index 83ca79c..aaaa270 100644 --- a/src/main/java/com/tinyplantnews/priestess/Priestess.java +++ b/src/main/java/com/tinyplantnews/priestess/Priestess.java @@ -26,6 +26,7 @@ import discord4j.discordjson.json.ApplicationCommandRequest; import java.io.BufferedReader; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.nio.file.Paths; @@ -89,7 +90,7 @@ public class Priestess { public static final String MESSAGES = "messages"; public static final String TESTSUITE = "testsuite"; - private MessageStore ms = null; + private MessageStore ms = new MessageStore(null); public static final String DEBUG_LEVEL = "debug_level"; @@ -132,12 +133,21 @@ public class Priestess { break; case "debug-level": String newlevel = args[i].substring(args[i].indexOf('=') + 1); - Configuration.setConfigurationParameter(DEBUG_LEVEL, newlevel); + try { + Configuration.setConfigurationParameter(DEBUG_LEVEL, newlevel); + } catch (FileNotFoundException fnfe) { + log.log(Level.SEVERE, "Could not resolve debug level from given file? {0}", fnfe); + } trySetDebugLevel(newlevel); i++; break; case "debug": - Configuration.setConfigurationParameter(DEBUG_LEVEL, Level.ALL.toString()); + try { + Configuration.setConfigurationParameter(DEBUG_LEVEL, Level.ALL.toString()); + } catch (FileNotFoundException fnfe) { + log.log(Level.SEVERE, + "Could not resolve debug level from given file? This should never happen. {0}", fnfe); + } trySetDebugLevel(yarg); i++; break; @@ -351,18 +361,19 @@ public class Priestess { return Mono.empty(); }).subscribe(); - //dc.on(MessageCreateEvent.class).filter(event -> !event.getMessage().getAuthor().get().isBot()) - //.filter(g -> !g.getGuild().blockOptional().isEmpty()).flatMap(guildedmessagehandler).subscribe(); + // dc.on(MessageCreateEvent.class).filter(event -> + // !event.getMessage().getAuthor().get().isBot()) + // .filter(g -> + // !g.getGuild().blockOptional().isEmpty()).flatMap(guildedmessagehandler).subscribe(); dc.on(MessageCreateEvent.class).filter(event -> !event.getMessage().getAuthor().get().isBot()) - /*.filter(g -> g.getGuild().blockOptional().isEmpty())*/.flatMap(messagehandler).blockLast(); + /* .filter(g -> g.getGuild().blockOptional().isEmpty()) */.flatMap(messagehandler).blockLast(); } } private int invocationsBeforeThisSession = 0; private void setup() { - Configuration.findConfigurationFilePath(); Configuration.findAndReadConfigurationFile(); botToken = Configuration.getConfigurationParameter(TOKEN_KEY); @@ -418,7 +429,10 @@ public class Priestess { int invocationsTotal = invocationsBeforeThisSession + invocationsThisSession; log.log(Level.INFO, "Bean Goose has been invoked " + invocationsTotal + " times so far."); - Configuration.setConfigurationParameter(INVOCATIONS_KEY, Integer.toString(invocationsTotal)); + try { + Configuration.setConfigurationParameter(INVOCATIONS_KEY, Integer.toString(invocationsTotal)); + } catch (FileNotFoundException e) { + } Configuration.commitConfigurationToFile(); log.log(Level.INFO, "Configuration committed to file."); } @@ -535,18 +549,8 @@ public class Priestess { */ private MessageStore setupMessageStore() { String mesglist = Configuration.getConfigurationParameter(MESSAGES); - File f = Paths.get(mesglist).toFile(); - try { - MessageStore ms1 = new MessageStore(f); - return ms1; - } catch (IOException e) { - if (!f.exists()) { - log.log(Level.WARNING, "Message store file does not exist."); - } else { - log.log(Level.SEVERE, e.getMessage()); - } - } - return null; + File f = new File(mesglist); + return new MessageStore(f); } private void reloadMessageStore() { @@ -747,7 +751,11 @@ public class Priestess { String[] paytokens = tokenize(pay2); if (paytokens.length == 2) { log.log(Level.INFO, "setting configuration parameter " + paytokens[0] + " to " + paytokens[1]); - Configuration.setConfigurationParameter(paytokens[0], paytokens[1]); + try { + Configuration.setConfigurationParameter(paytokens[0], paytokens[1]); + } catch (FileNotFoundException e) { + o.replyWith(""); + } } else { log.log(Level.INFO, "Invalid argument count for set configuration parameter command"); }