package com.laytonsmith.tools; import com.laytonsmith.PureUtilities.Common.StreamUtils; import com.laytonsmith.PureUtilities.Common.StringUtils; import com.laytonsmith.PureUtilities.DaemonManager; import com.laytonsmith.PureUtilities.TermColors; import static com.laytonsmith.PureUtilities.TermColors.BG_RED; import static com.laytonsmith.PureUtilities.TermColors.BLINKOFF; import static com.laytonsmith.PureUtilities.TermColors.BLINKON; import static com.laytonsmith.PureUtilities.TermColors.BLUE; import static com.laytonsmith.PureUtilities.TermColors.BOLD; import static com.laytonsmith.PureUtilities.TermColors.BRIGHT_WHITE; import static com.laytonsmith.PureUtilities.TermColors.CYAN; import static com.laytonsmith.PureUtilities.TermColors.GREEN; import static com.laytonsmith.PureUtilities.TermColors.MAGENTA; import static com.laytonsmith.PureUtilities.TermColors.RED; import static com.laytonsmith.PureUtilities.TermColors.WHITE; import static com.laytonsmith.PureUtilities.TermColors.YELLOW; import static com.laytonsmith.PureUtilities.TermColors.cls; import static com.laytonsmith.PureUtilities.TermColors.p; import static com.laytonsmith.PureUtilities.TermColors.prompt; import static com.laytonsmith.PureUtilities.TermColors.reset; import com.laytonsmith.abstraction.Implementation; import com.laytonsmith.commandhelper.CommandHelperFileLocations; import com.laytonsmith.core.CHLog; import com.laytonsmith.core.Installer; import com.laytonsmith.core.MethodScriptCompiler; import com.laytonsmith.core.MethodScriptExecutionQueue; import com.laytonsmith.core.MethodScriptFileLocations; import com.laytonsmith.core.Profiles; import com.laytonsmith.core.Static; import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.CommandHelperEnvironment; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigCompileGroupException; import com.laytonsmith.core.profiler.Profiler; import com.laytonsmith.core.taskmanager.TaskManager; import com.laytonsmith.persistence.DataSource; import com.laytonsmith.persistence.DataSourceException; import com.laytonsmith.persistence.DataSourceFactory; import com.laytonsmith.persistence.DataSourceFilter; import com.laytonsmith.persistence.PersistenceNetwork; import com.laytonsmith.persistence.ReadOnlyException; import com.laytonsmith.persistence.io.ConnectionMixinFactory; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; /** * */ public class Manager { private static Profiler profiler; private static GlobalEnv gEnv; private static final File jarLocation = new File(Interpreter.class.getProtectionDomain().getCodeSource().getLocation().getFile()).getParentFile(); private static final File chDirectory = new File(jarLocation, "CommandHelper"); private static PersistenceNetwork persistenceNetwork; public static PrintStream out = StreamUtils.GetSystemOut(); public static final String[] options = new String[]{ "refactor", "print", "cleardb", "edit", "interpreter", "merge", "hidden-keys" }; @SuppressWarnings("ResultOfObjectAllocationIgnored") public static void start() throws IOException, DataSourceException, URISyntaxException, Profiles.InvalidProfileException { Implementation.useAbstractEnumThread(false); Implementation.forceServerType(Implementation.Type.BUKKIT); ConnectionMixinFactory.ConnectionMixinOptions options = new ConnectionMixinFactory.ConnectionMixinOptions(); options.setWorkingDirectory(chDirectory); persistenceNetwork = new PersistenceNetwork(CommandHelperFileLocations.getDefault().getPersistenceConfig(), CommandHelperFileLocations.getDefault().getDefaultPersistenceDBFile().toURI(), options); Installer.Install(chDirectory); CHLog.initialize(chDirectory); profiler = new Profiler(CommandHelperFileLocations.getDefault().getProfilerConfigFile()); gEnv = new GlobalEnv(new MethodScriptExecutionQueue("Manager", "default"), profiler, persistenceNetwork, chDirectory, new Profiles(MethodScriptFileLocations.getDefault().getProfilesFile()), new TaskManager()); cls(); pl("\n" + Static.Logo() + "\n\n" + Static.DataManagerLogo()); pl("Starting the Data Manager..."); try { Environment env = Environment.createEnvironment(gEnv, new CommandHelperEnvironment()); MethodScriptCompiler.execute(MethodScriptCompiler.compile(MethodScriptCompiler.lex("player()", null, true)), env, null, null); } catch (ConfigCompileException | ConfigCompileGroupException ex) { } pl(GREEN + "Welcome to the CommandHelper " + CYAN + "Data Manager!"); pl(BLINKON + RED + "Warning!" + BLINKOFF + YELLOW + " Be sure your server is not running before using this tool to make changes to your database!"); pl("------------------------"); boolean finished = false; do { pl(YELLOW + "What function would you like to run? Type \"help\" for a full list of options."); String input = prompt(); pl(); if (input.toLowerCase().startsWith("help")) { help(input.replaceFirst("help ?", "").toLowerCase().split(" ")); } else if (input.equalsIgnoreCase("refactor")) { refactor(); } else if (input.toLowerCase().startsWith("print")) { print(input.replaceFirst("print ?", "").toLowerCase().split(" ")); } else if (input.equalsIgnoreCase("cleardb")) { cleardb(); } else if (input.equalsIgnoreCase("edit")) { edit(); } else if (input.equalsIgnoreCase("merge")) { merge(); } else if (input.equalsIgnoreCase("interpreter")) { new Interpreter(null, System.getProperty("user.dir")); } else if (input.equalsIgnoreCase("hidden-keys")){ hiddenKeys(); } else if (input.equalsIgnoreCase("exit")) { pl("Thanks for using the " + CYAN + BOLD + "Data Manager!" + reset()); finished = true; } else { pl("I'm sorry, that's not a valid command. Here's the help:"); help(new String[]{}); } } while (finished == false); StreamUtils.GetSystemOut().println(TermColors.reset()); } public static void merge() { cls(); pl(GREEN + "Transferring a database takes all the keys from a database connection, and puts\n" + "them straight into another database. If there are key conflicts, this tool will prompt\n" + "you for an action."); ConnectionMixinFactory.ConnectionMixinOptions mixinOptions = new ConnectionMixinFactory.ConnectionMixinOptions(); mixinOptions.setWorkingDirectory(chDirectory); DataSource source; DataSource destination; do { //Verify that the destination doesn't have any of the keys in the source, barring keys that have //the exact same value. do { //Get the source connection set up pl(YELLOW + "What is the source connection you would like to read the keys from?\n" + "(Type the connection exactly as you would in the persistence configuration,\n" + "aliases are not supported)"); String ssource = prompt(); try { source = DataSourceFactory.GetDataSource(ssource, mixinOptions); break; } catch (DataSourceException ex) { pl(RED + ex.getMessage()); } catch (URISyntaxException ex) { pl(RED + ex.getMessage()); } } while (true); do { //Get the destination connection set up pl(YELLOW + "What is the destination connection?"); String sdestination = prompt(); try { destination = DataSourceFactory.GetDataSource(sdestination, mixinOptions); break; } catch (DataSourceException ex) { pl(RED + ex.getMessage()); } catch (URISyntaxException ex) { pl(RED + ex.getMessage()); } } while (true); try { //Run through all the source's keys, and check to see that either the //destination's key doesn't exist, or the value at that key is the //exact same as the source's value boolean acceptAllDestination = false; boolean acceptAllSource = false; DaemonManager dm = new DaemonManager(); for (String[] key : source.keySet(new String[0])) { if (destination.hasKey(key)) { if (!source.get(key).equals(destination.get(key))) { String data; //If the key is null, it's empty, so we can just stick it in, no //problem. If there is data there, it's a conflict, and we need to //ask. if (destination.get(key) != null) { boolean useSource = false; boolean useDestination = false; if (acceptAllDestination || acceptAllSource) { p(RED + "Conflict found for " + StringUtils.Join(key, ".") + ", using "); if (useSource) { useSource = true; p("source"); } else { useDestination = true; p("destination"); } pl(" value."); } else { pl(BG_RED + BRIGHT_WHITE + "The key " + StringUtils.Join(key, ".") + " has a different value" + " in the source and the destination: " + reset()); pl(WHITE + "Source: " + source.get(key)); pl(WHITE + "Destination: " + destination.get(key)); do { pl(YELLOW + "Would you like to keep " + CYAN + "S" + YELLOW + "ource, " + "keep " + GREEN + "D" + YELLOW + "estination, keep " + MAGENTA + "A" + YELLOW + "ll " + MAGENTA + "S" + YELLOW + "ource, or keep " + BLUE + "A" + YELLOW + "ll " + BLUE + "D" + YELLOW + "estination?"); pl(WHITE + "[" + CYAN + "S" + WHITE + "/" + GREEN + "D" + WHITE + "/" + MAGENTA + "AS" + WHITE + "/" + BLUE + "AD" + WHITE + "]"); String response = prompt(); if ("AS".equalsIgnoreCase(response)) { acceptAllSource = true; useSource = true; } else if ("AD".equalsIgnoreCase(response)) { acceptAllDestination = true; useDestination = true; } else if ("S".equalsIgnoreCase(response)) { useSource = true; } else if ("D".equalsIgnoreCase(response)) { useDestination = true; } else { continue; } break; } while (true); } if (useSource) { data = source.get(key); } else if (useDestination) { data = destination.get(key); } else { throw new RuntimeException("Invalid state, both useSource and useDestination are false"); } //Ok, now put the data in the destination } else { //Otherwise, just use the data in the source data = source.get(key); } destination.set(dm, key, data); } } else { //Else there is no conflict, it's not in the destination. destination.set(dm, key, source.get(key)); } } try { dm.waitForThreads(); } catch (InterruptedException ex) { // } break; } catch (DataSourceException ex) { pl(RED + ex.getMessage()); } catch (ReadOnlyException ex) { pl(RED + ex.getMessage()); } catch (IOException ex) { pl(RED + ex.getMessage()); } } while (true); pl(GREEN + "Done merging!"); } public static void cleardb() { try{ pl(RED + "Are you absolutely sure you want to clear out your database? " + BLINKON + "No backup is going to be made." + BLINKOFF); pl(WHITE + "This will completely wipe your persistence information out. (No other data will be changed)"); pl("[YES/No]"); String choice = prompt(); if (choice.equals("YES")) { pl("Positive? [YES/No]"); if (prompt().equals("YES")) { p("Ok, here we go... "); Set<String[]> keySet = persistenceNetwork.getNamespace(new String[]{}).keySet(); DaemonManager dm = new DaemonManager(); for(String [] key : keySet){ try { persistenceNetwork.clearKey(dm, key); } catch (ReadOnlyException ex) { pl(RED + "Read only data source found: " + ex.getMessage()); } } try{ dm.waitForThreads(); } catch(InterruptedException e){ // } pl("Done!"); } } else if (choice.equalsIgnoreCase("yes")) { pl("No, you have to type YES exactly."); } } catch(DataSourceException ex){ pl(RED + ex.getMessage()); } catch(IOException ex){ pl(RED + ex.getMessage()); } } public static void help(String[] args) { if (args.length < 1 || args[0].isEmpty()) { pl("Currently, your options are:\n" + "\t" + GREEN + "refactor" + WHITE + " - Allows you to shuffle data around in the persistence network more granularly than the merge tool.\n" + "\t" + GREEN + "print" + WHITE + " - Prints out the information from your persisted data\n" + "\t" + GREEN + "cleardb" + WHITE + " - Clears out your database of persisted data\n" + "\t" + GREEN + "edit" + WHITE + " - Allows you to edit individual fields\n" + "\t" + GREEN + "interpreter" + WHITE + " - Command Line Interpreter mode. Most minecraft related functions don't work.\n" + "\t" + GREEN + "merge" + WHITE + " - Merges an entire database from one backend into another, even across formats. (Not individual keys.)\n" + "\t\tYou can also use this tool to an extent to import or export data.\n" + "\t" + GREEN + "hidden-keys" + WHITE + " - Lists all hidden keys in known data sources" + "\n\t" + RED + "exit" + WHITE + " - Quits the Data Manager\n"); pl("Type " + MAGENTA + "help <command>" + WHITE + " for more details about a specific command"); } else { if(null != args[0])switch (args[0]) { case "refactor": pl("This tool allows you to granularly move individual keys from one datasource to another." + " Unlike the merge tool, this works with individual keys, not necessarily keys that are" + " within a particular data source. There are three required inputs, the transfer key pattern," + " the input configuration file, and the output configuration file. Data is transferred from" + " one configuration to the other, that is, it is added in the new place, and removed in the old place." + " This tool is more complicated" + " than the merge tool, so consider using the other tool for simple tasks."); break; case "upgrade": pl("Converts any old formatted data into the new format. Any data that doesn't explicitely" + " match the old format is not touched. Do not use this utility unless specifically" + " told to during upgrade notices."); break; case "print": pl("Prints out the information in your persistence file. Entries may be narrowed down by" + " specifying the namespace (for instance " + MAGENTA + "print user.username" + WHITE + " will only show that particular users's aliases.) This is namespace based, so you" + " must provide the entire namespace that your are trying to narrow down." + "(" + MAGENTA + "print storage" + WHITE + " is valid, but " + MAGENTA + "print stor" + WHITE + " is not)"); break; case "cleardb": pl("Wipes your database clean of CommandHelper's persistence entries, but not other data. This" + " includes any data that CommandHelper would have inserted into the database, or data" + " that CommandHelper otherwise knows how to use. If using Serialized Persistence (ser), this" + " means the entire file. For other data backends, this may vary slightly, for instance," + " an SQL backend would only have the CH specific tables truncated, but the rest of the" + " database would remain untouched."); break; case "edit": pl("Allows you to manually edit the values in the database. You have the option to add or edit an existing" + " value, delete a single value, or view the value of an individual key."); break; case "interpreter": pl("Generally speaking, works the same as the in game interpreter mode, but none" + " of the minecraft related functions will work. You should not" + " run this while the server is operational."); break; case "merge": pl("The merge tool allows you to shuffle persisted data around as entire databases, not as individual keys, however." + " You specify the source database, and the output database, and it copies all the database entries. This" + " can be used to an extent to import and export values, but it is not granular at all. Key conflicts are" + " handled by prompting the user for an action, whether to overwrite the destination's value, or to keep" + " it as is. Thusly, this operation is very safe from accidentally deleting your data. Keys that don't exist" + " in the destination already are simply copied, and keys that have the same value are skipped. No changes" + " are made to the source database."); break; case "hidden-keys": pl("The hidden-keys tool allows you to locate any \"hidden keys,\" that is, keys that exist in a data source," + " but can't be accessed normally. This can happen if you make changes to your persistence.ini file" + " but don't refactor or otherwise migrate the data when you \"hide\" the keys. For instance, say you" + " only have \"**=sqlite://persistence.db\" in your file, and you store some value in \"storage.a\". Later, you" + " add \"storage.a=json://file.json\" to your persistence.ini file, but you don't refactor. The value stored" + " at \"storage.a\" in the sqlite file is now inaccessible, and if you store another value in \"storage.a,\" the" + " new value would be stored in file.json, and the original value in the sqlite file would simply be dead" + " memory. This wouldn't cause any direct issues, but if a significant number of keys are \"dead,\" this would" + " take up hard disk space for no reason. Additionally, refactors could have unexpected results.\n\n" + "You will have the option to view or delete the hidden keys. Viewing them will print out a summary" + " of all the keys, and deleting them will allow you to wholesale delete them."); break; case "exit": pl("Exits the data manager"); break; default: pl("That's not a recognized command: '" + args[0] + "'"); break; } } } public static void edit() { cls(); while (true) { pl("Would you like to " + GREEN + "(a)dd/edit" + WHITE + " a value, " + RED + "(r)emove" + WHITE + " a value, " + CYAN + "(v)iew" + WHITE + " a single value, or " + MAGENTA + "(s)top" + WHITE + " editting? [" + GREEN + "A" + WHITE + "/" + RED + "R" + WHITE + "/" + CYAN + "V" + WHITE + "/" + MAGENTA + "S" + WHITE + "]"); String choice = prompt(); if (choice.equalsIgnoreCase("s") || choice.equalsIgnoreCase("exit")) { break; } else if (choice.equalsIgnoreCase("a")) { pl("Type the name of the key " + YELLOW + "EXACTLY" + WHITE + " as shown in the" + " persistence format,\nnot the format you use when using store_value()."); String key = prompt(); pl("Provide a value for " + CYAN + key + WHITE + ". This value you provide will" + " be interpreted as pure MethodScript. (So things like array() will work)"); String value = prompt(); if (doAddEdit(key, value)) { pl("Value changed!"); } } else if (choice.equalsIgnoreCase("r")) { pl("Type the name of the key " + YELLOW + "EXACTLY" + WHITE + " as shown in the" + " persistence format,\nnot the format you use when using store_value()."); String key = prompt(); if (doRemove(key)) { pl("Value removed!"); } else { pl("That value wasn't in the database to start with"); } } else if (choice.equalsIgnoreCase("v")) { pl("Type the name of the key " + YELLOW + "EXACTLY" + WHITE + " as shown in the" + " persistence format,\nnot the format you use when using store_value()."); String key = prompt(); doView(key); } else { pl("I'm sorry, that's not a valid choice."); } } } public static boolean doView(String key) { try { String [] k = key.split("\\."); if (!persistenceNetwork.hasKey(k)) { pl(RED + "That value is not set!"); return true; } pl(CYAN + key + ":" + WHITE + persistenceNetwork.get(k)); return true; } catch (DataSourceException ex) { pl(RED + ex.getMessage()); return false; } } public static boolean doAddEdit(String key, String valueScript) { try { Environment env = Environment.createEnvironment(gEnv, new CommandHelperEnvironment()); Construct c = MethodScriptCompiler.execute(MethodScriptCompiler.compile(MethodScriptCompiler.lex(valueScript, null, true)), env, null, null); String value = Construct.json_encode(c, Target.UNKNOWN); pl(CYAN + "Adding: " + WHITE + value); String [] k = key.split("\\."); DaemonManager dm = new DaemonManager(); persistenceNetwork.set(dm, k, value); try{ dm.waitForThreads(); } catch(InterruptedException e){ // } return true; } catch (Exception ex) { pl(RED + ex.getMessage()); return false; } } public static boolean doRemove(String key) { try { String [] k = key.split("\\."); if (persistenceNetwork.hasKey(k)) { DaemonManager dm = new DaemonManager(); persistenceNetwork.clearKey(dm, k); try{ dm.waitForThreads(); } catch(InterruptedException e){ // } return true; } else { return false; } } catch (Exception ex) { pl(RED + ex.getMessage()); return false; } } public static void print(String[] args) { try{ int count = 0; for(String [] key : persistenceNetwork.getNamespace(new String[]{}).keySet()){ count++; pl(CYAN + StringUtils.Join(key, ".") + ": " + WHITE + persistenceNetwork.get(key)); } pl(BLUE + count + " items found"); } catch(Exception e){ pl(RED + e.getMessage()); } } public static void refactor(){ pl("This tool allows you to granularly move individual keys from one datasource to another." + " Unlike the merge tool, this works with individual keys, not necessarily keys that are" + " within a particular data source. There are three required inputs, the transfer key pattern," + " the input configuration file, and the output configuration file. Data is transferred from" + " one configuration to the other, that is, it is added in the new place, and removed in the old place." + " This tool is more complicated" + " than the merge tool, so consider using the other tool for simple tasks.\n\n"); pl("Would you like to continue? [" + GREEN + "Y" + WHITE + "/" + RED + "N" + WHITE + "]"); String choice = prompt(); if("Y".equals(choice)){ String filter; File input; File output; while(true){ while(true){ pl("What keys are you interested in transferring? The filter should be in the same format as the persistence.ini file, i.e." + " \"storage.test\" or \"storage.test.**\". If a wildcard is used, multiple keys may be moved, otherwise, only one will" + " be."); filter = prompt(); break; } File def = MethodScriptFileLocations.getDefault().getPersistenceConfig(); while(true) { pl("What is the input configuration (where keys will be read in from, then deleted)? Leave blank for the default, which is " + def.toString() + ". The path should be relative to " + jarLocation.toString()); String sinput = prompt(); if("".equals(sinput.trim())){ input = def; } else { File temp = new File(sinput); if(!temp.isAbsolute()){ temp = new File(jarLocation, sinput); } input = temp; } if(!input.exists() || !input.isFile()){ pl(RED + input.toString() + " isn't a file. Please enter an existing file."); } else { break; } } while(true){ pl("What is the output configuration (where keys will be written to)? The path should be relative to " + jarLocation.toString()); String soutput = prompt(); if("".equals(soutput.trim())){ pl(RED + "The output cannot be empty"); continue; } else { File temp = new File(soutput); if(!temp.isAbsolute()){ temp = new File(jarLocation, soutput); } output = temp; } if(!output.exists() || !output.isFile()){ pl(RED + output.toString() + " isn't a file. Please enter an existing file."); } else { break; } } pl("The filter is \"" + MAGENTA + filter + WHITE + "\"."); pl("The input configuration is \"" + MAGENTA + input.toString() + WHITE + "\"."); pl("The output configuration is \"" + MAGENTA + output.toString() + WHITE + "\"."); pl("Is this correct? [" + GREEN + "Y" + WHITE + "/" + RED + "N" + WHITE + "]"); if("Y".equals(prompt())){ break; } } pl(YELLOW + "Now beginning transfer..."); URI defaultURI; try { defaultURI = new URI("file://persistence.db"); } catch (URISyntaxException ex) { throw new Error(ex); } ConnectionMixinFactory.ConnectionMixinOptions mixinOptions = new ConnectionMixinFactory.ConnectionMixinOptions(); try { DaemonManager dm = new DaemonManager(); mixinOptions.setWorkingDirectory(chDirectory); PersistenceNetwork pninput = new PersistenceNetwork(input, defaultURI, mixinOptions); PersistenceNetwork pnoutput = new PersistenceNetwork(output, defaultURI, mixinOptions); Pattern p = Pattern.compile(DataSourceFilter.toRegex(filter)); Map<String[], String> inputData = pninput.getNamespace(new String[]{}); boolean errors = false; int transferred = 0; int skipped = 0; for(String [] k : inputData.keySet()){ String key = StringUtils.Join(k, "."); if(p.matcher(key).matches()){ pl(GREEN + "transferring " + YELLOW + key); //This key matches, so we need to add it to the output network, and then remove it //from the input network if(pnoutput.getKeySource(k).equals(pninput.getKeySource(k))){ continue; //Don't transfer it if it's the same source, otherwise we would //end up just deleting it. } try { pnoutput.set(dm, k, inputData.get(k)); transferred++; try { pninput.clearKey(dm, k); } catch (ReadOnlyException ex) { pl(RED + "Could not clear out original key for the value for \"" + MAGENTA + StringUtils.Join(k, ".") + RED + "\", as the input" + " file is set to read only."); errors = true; } } catch (ReadOnlyException ex) { pl(RED + "Could not write out the value for \"" + MAGENTA + StringUtils.Join(k, ".") + RED + "\", as the output" + " file is set to read only."); errors = true; } } else { skipped++; } } pl(YELLOW + StringUtils.PluralTemplateHelper(transferred, "%d key was", "%d keys were") + " transferred."); pl(YELLOW + StringUtils.PluralTemplateHelper(skipped, "%d key was", "%d keys were") + " skipped."); if(errors){ pl(YELLOW + "Other than the errors listed above, all keys were transferred successfully."); } else { pl(GREEN + "Done!"); } pl(GREEN + "If this is being done as part of an entire transfer process, don't forget to set " + output.toString() + " as your main Persistence Network configuration file."); } catch (IOException ex) { Logger.getLogger(Manager.class.getName()).log(Level.SEVERE, null, ex); } catch (DataSourceException ex) { Logger.getLogger(Manager.class.getName()).log(Level.SEVERE, null, ex); } } } public static void hiddenKeys(){ String action; while(true){ pl(WHITE + "Would you like to \"" + GREEN + "view" + WHITE + "\" or \"" + RED + "delete" + WHITE + "\" the hidden keys (default: view)? [view/delete]"); action = prompt(); if("".equals(action)){ action = "view"; } if("view".equals(action) || "delete".equals(action)){ break; } else { pl(RED + "Invalid selection."); } } File configuration = MethodScriptFileLocations.getDefault().getPersistenceConfig(); while(true){ pl("Currently, " + configuration.getAbsolutePath() + " is being used as the persistence config file, but you may" + " specify another (blank to use the default)."); String file = prompt(); if("".equals(file)){ break; } else { File f = new File(file); if(f.exists()){ configuration = f; break; } else { pl(RED + "The file you specified doesn't seem to exist, please enter it again."); } } } pl(YELLOW + "Using " + configuration.getAbsolutePath() + " as our Persistence Network config."); File workingDirectory = MethodScriptFileLocations.getDefault().getConfigDirectory(); while(true){ pl("Currently, " + workingDirectory.getAbsolutePath() + " is being used as the default \"working directory\" for the" + " persistence config file, but you may specify another (blank to use the default)."); String file = prompt(); if("".equals(file)){ break; } else { File f = new File(file); if(f.exists()){ workingDirectory = f; break; } else { pl(RED + "The file you specified doesn't seem to exist, please enter it again."); } } } pl(YELLOW + "Using " + workingDirectory.getAbsolutePath() + " as our Persistence Network config working directory."); try { DataSourceFilter filter = new DataSourceFilter(configuration, new URI("sqlite://persistence.db")); ConnectionMixinFactory.ConnectionMixinOptions options = new ConnectionMixinFactory.ConnectionMixinOptions(); options.setWorkingDirectory(workingDirectory); Set<URI> uris = filter.getAllConnections(); boolean noneFound = true; int runningTotal = 0; for (URI uri : uris) { DataSource ds = DataSourceFactory.GetDataSource(uri, options); Map<String[], String> db = ds.getValues(new String[0]); Map<String[], String> map = new HashMap<>(); DaemonManager dm = new DaemonManager(); try { for(String[] key : db.keySet()){ if(!filter.getConnection(key).equals(uri)){ map.put(key, db.get(key)); if("delete".equals(action)){ ds.clearKey(dm, key); } } } runningTotal += map.size(); } catch (ReadOnlyException ex) { pl(RED + "Cannot delete any keys from " + uri + " as it is marked as read only, so it is being skipped."); } if("delete".equals(action)){ try { dm.waitForThreads(); } catch (InterruptedException ex) { // Ignored } } if(!map.isEmpty()){ noneFound = false; if("view".equals(action)){ pl("Found " + StringUtils.PluralTemplateHelper(map.size(), "one hidden key", "%d hidden keys") + " in data source " + MAGENTA + uri.toString()); for(String[] key : map.keySet()){ pl("\t" + GREEN + StringUtils.Join(key, ".") + WHITE + ":" + CYAN + map.get(key)); } if(ds.hasModifier(DataSource.DataSourceModifier.READONLY)){ pl(YELLOW + "This data source is marked as read only, and the keys cannot be deleted from it by this utility."); } pl(); } } } if(noneFound){ pl(GREEN + "Done searching, no hidden keys were found."); } else { if("delete".equals(action)){ pl(GREEN + "Done, " + StringUtils.PluralTemplateHelper(runningTotal, "one hidden key was", "%d hidden keys were") + " deleted."); } else { pl(GREEN + "Found " + StringUtils.PluralTemplateHelper(runningTotal, "one hidden key", "%d hidden keys") + " in total."); } } } catch(URISyntaxException | IOException | DataSourceException ex){ pl(RED + ex.getMessage()); ex.printStackTrace(StreamUtils.GetSystemErr()); } } private static void pl(){ out.println(); } private static void pl(String string){ out.println(string + WHITE); } }