diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java index 2cde4900ad..b24c9ccb96 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java +++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java @@ -43,8 +43,11 @@ import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.nio.channels.FileLock; import java.nio.charset.Charset; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.time.Instant; import java.util.Arrays; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -70,6 +73,7 @@ import net.runelite.http.api.config.Configuration; public class ConfigManager { private static final String SETTINGS_FILE_NAME = "settings.properties"; + private static final DateFormat TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss"); @Inject EventBus eventBus; @@ -111,12 +115,17 @@ public class ConfigManager load(); // load profile specific config } + private File getLocalPropertiesFile() + { + return new File(RuneLite.RUNELITE_DIR, SETTINGS_FILE_NAME); + } + private File getPropertiesFile() { // Sessions that aren't logged in have no username if (session == null || session.getUsername() == null) { - return new File(RuneLite.RUNELITE_DIR, SETTINGS_FILE_NAME); + return getLocalPropertiesFile(); } else { @@ -158,7 +167,13 @@ public class ConfigManager for (ConfigEntry entry : configuration.getConfig()) { log.debug("Loading configuration value from client {}: {}", entry.getKey(), entry.getValue()); - final String[] split = entry.getKey().split("\\."); + final String[] split = entry.getKey().split("\\.", 2); + + if (split.length != 2) + { + continue; + } + final String groupName = split[0]; final String key = split[1]; final String value = entry.getValue(); @@ -174,7 +189,7 @@ public class ConfigManager try { - saveToFile(); + saveToFile(propertiesFile); log.debug("Updated configuration on disk with the latest version"); } @@ -184,6 +199,75 @@ public class ConfigManager } } + private synchronized void syncPropertiesFromFile(File propertiesFile) + { + final Properties properties = new Properties(); + try (FileInputStream in = new FileInputStream(propertiesFile)) + { + properties.load(new InputStreamReader(in, Charset.forName("UTF-8"))); + } + catch (Exception e) + { + log.debug("Malformed properties, skipping update"); + return; + } + + final Map copy = (Map) ImmutableMap.copyOf(this.properties); + copy.forEach((groupAndKey, value) -> + { + if (!properties.containsKey(groupAndKey)) + { + final String[] split = groupAndKey.split("\\.", 2); + if (split.length != 2) + { + return; + } + + final String groupName = split[0]; + final String key = split[1]; + unsetConfiguration(groupName, key); + } + }); + + properties.forEach((objGroupAndKey, objValue) -> + { + final String groupAndKey = String.valueOf(objGroupAndKey); + final String[] split = groupAndKey.split("\\.", 2); + if (split.length != 2) + { + return; + } + + final String groupName = split[0]; + final String key = split[1]; + final String value = String.valueOf(objValue); + setConfiguration(groupName, key, value); + }); + } + + public void importLocal() + { + if (session == null) + { + // No session, no import + return; + } + + final File file = new File(propertiesFile.getParent(), propertiesFile.getName() + "." + TIME_FORMAT.format(new Date())); + + try + { + saveToFile(file); + } + catch (IOException e) + { + log.warn("Backup failed, skipping import", e); + return; + } + + syncPropertiesFromFile(getLocalPropertiesFile()); + } + private synchronized void loadFromFile() { properties.clear(); @@ -231,7 +315,7 @@ public class ConfigManager } } - private synchronized void saveToFile() throws IOException + private synchronized void saveToFile(final File propertiesFile) throws IOException { propertiesFile.getParentFile().mkdirs(); @@ -294,8 +378,6 @@ public class ConfigManager public void setConfiguration(String groupName, String key, String value) { - log.debug("Setting configuration value for {}.{} to {}", groupName, key, value); - String oldValue = (String) properties.setProperty(groupName + "." + key, value); if (Objects.equals(oldValue, value)) @@ -303,6 +385,8 @@ public class ConfigManager return; } + log.debug("Setting configuration value for {}.{} to {}", groupName, key, value); + synchronized (pendingChanges) { pendingChanges.put(groupName + "." + key, value); @@ -312,7 +396,7 @@ public class ConfigManager { try { - saveToFile(); + saveToFile(propertiesFile); } catch (IOException ex) { @@ -337,8 +421,6 @@ public class ConfigManager public void unsetConfiguration(String groupName, String key) { - log.debug("Unsetting configuration value for {}.{}", groupName, key); - String oldValue = (String) properties.remove(groupName + "." + key); if (oldValue == null) @@ -346,6 +428,8 @@ public class ConfigManager return; } + log.debug("Unsetting configuration value for {}.{}", groupName, key); + synchronized (pendingChanges) { pendingChanges.put(groupName + "." + key, null); @@ -355,7 +439,7 @@ public class ConfigManager { try { - saveToFile(); + saveToFile(propertiesFile); } catch (IOException ex) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java index 2edb250135..203b688b4e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java @@ -40,6 +40,7 @@ import javax.inject.Singleton; import javax.swing.Box; import javax.swing.ImageIcon; import javax.swing.JLabel; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.border.EmptyBorder; import javax.swing.event.HyperlinkEvent; @@ -48,6 +49,7 @@ import net.runelite.api.events.SessionClose; import net.runelite.api.events.SessionOpen; import net.runelite.client.RuneLiteProperties; import net.runelite.client.account.SessionManager; +import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.ui.ColorScheme; @@ -66,9 +68,12 @@ public class InfoPanel extends PluginPanel private static final ImageIcon DISCORD_ICON; private static final ImageIcon PATREON_ICON; private static final ImageIcon WIKI_ICON; + private static final ImageIcon IMPORT_ICON; private final JLabel loggedLabel = new JLabel(); private final JRichTextPane emailLabel = new JRichTextPane(); + private JPanel syncPanel; + private JPanel actionsContainer; @Inject @Nullable @@ -86,6 +91,9 @@ public class InfoPanel extends PluginPanel @Inject private ScheduledExecutorService executor; + @Inject + private ConfigManager configManager; + static { ARROW_RIGHT_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(InfoPanel.class, "/util/arrow_right.png")); @@ -93,6 +101,7 @@ public class InfoPanel extends PluginPanel DISCORD_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(InfoPanel.class, "discord_icon.png")); PATREON_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(InfoPanel.class, "patreon_icon.png")); WIKI_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(InfoPanel.class, "wiki_icon.png")); + IMPORT_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(InfoPanel.class, "import_icon.png")); } void init() @@ -150,11 +159,22 @@ public class InfoPanel extends PluginPanel versionPanel.add(loggedLabel); versionPanel.add(emailLabel); - updateLoggedIn(); - - JPanel actionsContainer = new JPanel(); + actionsContainer = new JPanel(); actionsContainer.setBorder(new EmptyBorder(10, 0, 0, 0)); - actionsContainer.setLayout(new GridLayout(4, 1, 0, 10)); + actionsContainer.setLayout(new GridLayout(0, 1, 0, 10)); + + syncPanel = buildLinkPanel(IMPORT_ICON, "Import local settings", "to remote RuneLite account", () -> + { + final int result = JOptionPane.showOptionDialog(syncPanel, + "This will replace your current RuneLite account settings with settings from your local profile.", + "Are you sure?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, + null, new String[]{"Yes", "No"}, "No"); + + if (result == JOptionPane.YES_OPTION) + { + configManager.importLocal(); + } + }); actionsContainer.add(buildLinkPanel(GITHUB_ICON, "Report an issue or", "make a suggestion", runeLiteProperties.getGithubLink())); actionsContainer.add(buildLinkPanel(DISCORD_ICON, "Talk to us on our", "discord server", runeLiteProperties.getDiscordInvite())); @@ -164,6 +184,7 @@ public class InfoPanel extends PluginPanel add(versionPanel, BorderLayout.NORTH); add(actionsContainer, BorderLayout.CENTER); + updateLoggedIn(); eventBus.register(this); } @@ -171,6 +192,14 @@ public class InfoPanel extends PluginPanel * Builds a link panel with a given icon, text and url to redirect to. */ private static JPanel buildLinkPanel(ImageIcon icon, String topText, String bottomText, String url) + { + return buildLinkPanel(icon, topText, bottomText, () -> LinkBrowser.browse(url)); + } + + /** + * Builds a link panel with a given icon, text and callable to call. + */ + private static JPanel buildLinkPanel(ImageIcon icon, String topText, String bottomText, Runnable callback) { JPanel container = new JPanel(); container.setBackground(ColorScheme.DARKER_GRAY_COLOR); @@ -193,7 +222,6 @@ public class InfoPanel extends PluginPanel @Override public void mousePressed(MouseEvent mouseEvent) { - LinkBrowser.browse(url); container.setBackground(pressedColor); textContainer.setBackground(pressedColor); } @@ -201,6 +229,7 @@ public class InfoPanel extends PluginPanel @Override public void mouseReleased(MouseEvent e) { + callback.run(); container.setBackground(hoverColor); textContainer.setBackground(hoverColor); } @@ -252,12 +281,14 @@ public class InfoPanel extends PluginPanel emailLabel.setContentType("text/plain"); emailLabel.setText(name); loggedLabel.setText("Logged in as"); + actionsContainer.add(syncPanel, 0); } else { emailLabel.setContentType("text/html"); emailLabel.setText("Login to sync settings to the cloud."); loggedLabel.setText("Not logged in"); + actionsContainer.remove(syncPanel); } } diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/info/import_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/info/import_icon.png new file mode 100644 index 0000000000..32263bf159 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/info/import_icon.png differ