Merge pull request #12834 from abextm/rsprofile-external

Per RuneScape-Profile configuration
This commit is contained in:
Adam
2020-11-27 00:00:54 -05:00
committed by GitHub
21 changed files with 998 additions and 256 deletions

View File

@@ -24,9 +24,13 @@
*/
package net.runelite.client.config;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
@@ -45,34 +49,50 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.security.SecureRandom;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.Player;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.PlayerChanged;
import net.runelite.api.events.UsernameChanged;
import net.runelite.api.events.WorldChanged;
import net.runelite.client.RuneLite;
import net.runelite.client.account.AccountSession;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ClientShutdown;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.events.RuneScapeProfileChanged;
import net.runelite.client.util.ColorUtil;
import net.runelite.http.api.config.ConfigClient;
import net.runelite.http.api.config.ConfigEntry;
@@ -83,30 +103,52 @@ import okhttp3.OkHttpClient;
@Slf4j
public class ConfigManager
{
public static final String RSPROFILE_GROUP = "rsprofile";
private static final String RSPROFILE_DISPLAY_NAME = "displayName";
private static final String RSPROFILE_TYPE = "type";
private static final String RSPROFILE_LOGIN_HASH = "loginHash";
private static final String RSPROFILE_LOGIN_SALT = "loginSalt";
private static final DateFormat TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
@VisibleForTesting
static final Pattern KEY_SPLITTER = Pattern.compile("([^.]+)\\.(?:(" + RSPROFILE_GROUP + "\\.[^.]+)\\.)?(.*)");
private static final int KEY_SPLITTER_GROUP = 1;
private static final int KEY_SPLITTER_PROFILE = 2;
private static final int KEY_SPLITTER_KEY = 3;
private final File settingsFileInput;
private final EventBus eventBus;
private final OkHttpClient okHttpClient;
private AccountSession session;
private ConfigClient client;
private ConfigClient configClient;
private File propertiesFile;
@Nullable
private final Client client;
private final ConfigInvocationHandler handler = new ConfigInvocationHandler(this);
private final Properties properties = new Properties();
private final Map<String, String> pendingChanges = new HashMap<>();
// null => we need to make a new profile
@Nullable
private String rsProfileKey;
@Inject
public ConfigManager(
@Named("config") File config,
ScheduledExecutorService scheduledExecutorService,
EventBus eventBus,
OkHttpClient okHttpClient)
OkHttpClient okHttpClient,
@Nullable Client client)
{
this.settingsFileInput = config;
this.eventBus = eventBus;
this.okHttpClient = okHttpClient;
this.client = client;
this.propertiesFile = getPropertiesFile();
scheduledExecutorService.scheduleWithFixedDelay(this::sendConfig, 30, 30, TimeUnit.SECONDS);
@@ -120,12 +162,12 @@ public class ConfigManager
if (session == null)
{
this.session = null;
this.client = null;
this.configClient = null;
}
else
{
this.session = session;
this.client = new ConfigClient(okHttpClient, session.getUuid());
this.configClient = new ConfigClient(okHttpClient, session.getUuid());
}
this.propertiesFile = getPropertiesFile();
@@ -154,7 +196,7 @@ public class ConfigManager
public void load()
{
if (client == null)
if (configClient == null)
{
loadFromFile();
return;
@@ -164,7 +206,7 @@ public class ConfigManager
try
{
configuration = client.get();
configuration = configClient.get();
}
catch (IOException ex)
{
@@ -186,26 +228,30 @@ 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("\\.", 2);
if (split.length != 2)
Matcher matcher = KEY_SPLITTER.matcher(entry.getKey());
if (!matcher.find())
{
continue;
}
final String groupName = split[0];
final String key = split[1];
final String groupName = matcher.group(KEY_SPLITTER_GROUP);
final String profile = matcher.group(KEY_SPLITTER_PROFILE);
final String key = matcher.group(KEY_SPLITTER_KEY);
final String value = entry.getValue();
final String oldValue = (String) properties.setProperty(entry.getKey(), value);
ConfigChanged configChanged = new ConfigChanged();
configChanged.setGroup(groupName);
configChanged.setProfile(profile);
configChanged.setKey(key);
configChanged.setOldValue(oldValue);
configChanged.setNewValue(value);
eventBus.post(configChanged);
}
migrateConfig();
try
{
saveToFile(propertiesFile);
@@ -232,44 +278,47 @@ public class ConfigManager
}
final Map<String, String> copy = (Map) ImmutableMap.copyOf(this.properties);
copy.forEach((groupAndKey, value) ->
copy.forEach((wholeKey, value) ->
{
if (!properties.containsKey(groupAndKey))
if (!properties.containsKey(wholeKey))
{
final String[] split = groupAndKey.split("\\.", 2);
if (split.length != 2)
Matcher matcher = KEY_SPLITTER.matcher(wholeKey);
if (!matcher.find())
{
return;
}
final String groupName = split[0];
final String key = split[1];
unsetConfiguration(groupName, key);
String groupName = matcher.group(KEY_SPLITTER_GROUP);
String profile = matcher.group(KEY_SPLITTER_PROFILE);
String key = matcher.group(KEY_SPLITTER_KEY);
unsetConfiguration(groupName, profile, key);
}
});
properties.forEach((objGroupAndKey, objValue) ->
properties.forEach((wholeKey, objValue) ->
{
final String groupAndKey = String.valueOf(objGroupAndKey);
final String[] split = groupAndKey.split("\\.", 2);
if (split.length != 2)
Matcher matcher = KEY_SPLITTER.matcher((String) wholeKey);
if (!matcher.find())
{
return;
}
final String groupName = split[0];
final String key = split[1];
final String value = String.valueOf(objValue);
setConfiguration(groupName, key, value);
String groupName = matcher.group(KEY_SPLITTER_GROUP);
String profile = matcher.group(KEY_SPLITTER_PROFILE);
String key = matcher.group(KEY_SPLITTER_KEY);
String value = String.valueOf(objValue);
setConfiguration(groupName, profile, key, value);
});
migrateConfig();
}
public void importLocal()
public Future<Void> importLocal()
{
if (session == null)
{
// No session, no import
return;
return null;
}
final File file = new File(propertiesFile.getParent(), propertiesFile.getName() + "." + TIME_FORMAT.format(new Date()));
@@ -281,10 +330,12 @@ public class ConfigManager
catch (IOException e)
{
log.warn("Backup failed, skipping import", e);
return;
return null;
}
syncPropertiesFromFile(getLocalPropertiesFile());
return sendConfig();
}
private synchronized void loadFromFile()
@@ -308,21 +359,23 @@ public class ConfigManager
try
{
Map<String, String> copy = (Map) ImmutableMap.copyOf(properties);
copy.forEach((groupAndKey, value) ->
copy.forEach((wholeKey, value) ->
{
final String[] split = groupAndKey.split("\\.", 2);
if (split.length != 2)
Matcher matcher = KEY_SPLITTER.matcher(wholeKey);
if (!matcher.find())
{
log.debug("Properties key malformed!: {}", groupAndKey);
properties.remove(groupAndKey);
log.debug("Properties key malformed!: {}", wholeKey);
properties.remove(wholeKey);
return;
}
final String groupName = split[0];
final String key = split[1];
String groupName = matcher.group(KEY_SPLITTER_GROUP);
String profile = matcher.group(KEY_SPLITTER_PROFILE);
String key = matcher.group(KEY_SPLITTER_KEY);
ConfigChanged configChanged = new ConfigChanged();
configChanged.setGroup(groupName);
configChanged.setProfile(profile);
configChanged.setKey(key);
configChanged.setOldValue(null);
configChanged.setNewValue(value);
@@ -333,6 +386,8 @@ public class ConfigManager
{
log.warn("Error posting config events", ex);
}
migrateConfig();
}
private void saveToFile(final File propertiesFile) throws IOException
@@ -381,14 +436,58 @@ public class ConfigManager
return properties.keySet().stream().filter(v -> ((String) v).startsWith(prefix)).map(String.class::cast).collect(Collectors.toList());
}
public static String getWholeKey(String groupName, String profile, String key)
{
if (profile == null)
{
return groupName + "." + key;
}
else
{
return groupName + "." + profile + "." + key;
}
}
public String getConfiguration(String groupName, String key)
{
return properties.getProperty(groupName + "." + key);
return getConfiguration(groupName, null, key);
}
public String getRSProfileConfiguration(String groupName, String key)
{
String rsProfileKey = this.rsProfileKey;
if (rsProfileKey == null)
{
return null;
}
return getConfiguration(groupName, rsProfileKey, key);
}
public String getConfiguration(String groupName, String profile, String key)
{
return properties.getProperty(getWholeKey(groupName, profile, key));
}
public <T> T getConfiguration(String groupName, String key, Class<T> clazz)
{
String value = getConfiguration(groupName, key);
return getConfiguration(groupName, null, key, clazz);
}
public <T> T getRSProfileConfiguration(String groupName, String key, Class<T> clazz)
{
String rsProfileKey = this.rsProfileKey;
if (rsProfileKey == null)
{
return null;
}
return getConfiguration(groupName, rsProfileKey, key, clazz);
}
public <T> T getConfiguration(String groupName, String profile, String key, Class<T> clazz)
{
String value = getConfiguration(groupName, profile, key);
if (!Strings.isNullOrEmpty(value))
{
try
@@ -397,7 +496,7 @@ public class ConfigManager
}
catch (Exception e)
{
log.warn("Unable to unmarshal {}.{} ", groupName, key, e);
log.warn("Unable to unmarshal {} ", getWholeKey(groupName, profile, key), e);
}
}
return null;
@@ -405,23 +504,31 @@ public class ConfigManager
public void setConfiguration(String groupName, String key, String value)
{
String oldValue = (String) properties.setProperty(groupName + "." + key, value);
setConfiguration(groupName, null, key, value);
}
public void setConfiguration(String groupName, String profile, String key, String value)
{
assert !key.startsWith(RSPROFILE_GROUP + ".");
String wholeKey = getWholeKey(groupName, profile, key);
String oldValue = (String) properties.setProperty(wholeKey, value);
if (Objects.equals(oldValue, value))
{
return;
}
log.debug("Setting configuration value for {}.{} to {}", groupName, key, value);
log.debug("Setting configuration value for {} to {}", wholeKey, value);
handler.invalidate();
synchronized (pendingChanges)
{
pendingChanges.put(groupName + "." + key, value);
pendingChanges.put(wholeKey, value);
}
ConfigChanged configChanged = new ConfigChanged();
configChanged.setGroup(groupName);
configChanged.setProfile(profile);
configChanged.setKey(key);
configChanged.setOldValue(oldValue);
configChanged.setNewValue(value);
@@ -429,26 +536,74 @@ public class ConfigManager
eventBus.post(configChanged);
}
public void setConfiguration(String groupName, String profile, String key, Object value)
{
setConfiguration(groupName, profile, key, objectToString(value));
}
public void setConfiguration(String groupName, String key, Object value)
{
setConfiguration(groupName, key, objectToString(value));
setConfiguration(groupName, null, key, value);
}
public void setRSProfileConfiguration(String groupName, String key, Object value)
{
String rsProfileKey = this.rsProfileKey;
if (rsProfileKey == null)
{
if (client == null)
{
log.warn("trying to use profile without injected client");
return;
}
String displayName = null;
Player p = client.getLocalPlayer();
if (p == null)
{
log.warn("trying to create profile without display name");
}
else
{
displayName = p.getName();
}
String username = client.getUsername();
if (Strings.isNullOrEmpty(username))
{
log.warn("trying to create profile without a set username");
return;
}
RuneScapeProfile prof = findRSProfile(getRSProfiles(), username, RuneScapeProfileType.getCurrent(client), displayName, true);
rsProfileKey = prof.getKey();
this.rsProfileKey = rsProfileKey;
}
setConfiguration(groupName, rsProfileKey, key, value);
}
public void unsetConfiguration(String groupName, String key)
{
String oldValue = (String) properties.remove(groupName + "." + key);
unsetConfiguration(groupName, null, key);
}
public void unsetConfiguration(String groupName, String profile, String key)
{
assert !key.startsWith(RSPROFILE_GROUP + ".");
String wholeKey = getWholeKey(groupName, profile, key);
String oldValue = (String) properties.remove(wholeKey);
if (oldValue == null)
{
return;
}
log.debug("Unsetting configuration value for {}.{}", groupName, key);
log.debug("Unsetting configuration value for {}", wholeKey);
handler.invalidate();
synchronized (pendingChanges)
{
pendingChanges.put(groupName + "." + key, null);
pendingChanges.put(wholeKey, null);
}
ConfigChanged configChanged = new ConfigChanged();
@@ -459,6 +614,17 @@ public class ConfigManager
eventBus.post(configChanged);
}
public void unsetRSProfileConfiguration(String groupName, String key)
{
String rsProfileKey = this.rsProfileKey;
if (rsProfileKey == null)
{
return;
}
unsetConfiguration(groupName, rsProfileKey, key);
}
public ConfigDescriptor getConfigDescriptor(Config configurationProxy)
{
Class<?> inter = configurationProxy.getClass().getInterfaces()[0];
@@ -656,6 +822,10 @@ public class ConfigManager
{
return Duration.ofMillis(Long.parseLong(str));
}
if (type == byte[].class)
{
return Base64.getUrlDecoder().decode(str);
}
return str;
}
@@ -703,6 +873,10 @@ public class ConfigManager
{
return Long.toString(((Duration) object).toMillis());
}
if (object instanceof byte[])
{
return Base64.getUrlEncoder().encodeToString((byte[]) object);
}
return object == null ? null : object.toString();
}
@@ -720,42 +894,280 @@ public class ConfigManager
private CompletableFuture<Void> sendConfig()
{
CompletableFuture<Void> future = null;
boolean changed;
synchronized (pendingChanges)
{
if (client != null)
if (pendingChanges.isEmpty())
{
future = CompletableFuture.allOf(pendingChanges.entrySet().stream().map(entry ->
{
String key = entry.getKey();
String value = entry.getValue();
if (Strings.isNullOrEmpty(value))
{
return client.unset(key);
}
else
{
return client.set(key, value);
}
}).toArray(CompletableFuture[]::new));
return null;
}
changed = !pendingChanges.isEmpty();
if (configClient != null)
{
Configuration patch = new Configuration(pendingChanges.entrySet().stream()
.map(e -> new ConfigEntry(e.getKey(), e.getValue()))
.collect(Collectors.toList()));
future = configClient.patch(patch);
}
pendingChanges.clear();
}
if (changed)
try
{
try
{
saveToFile(propertiesFile);
}
catch (IOException ex)
{
log.warn("unable to save configuration file", ex);
}
saveToFile(propertiesFile);
}
catch (IOException ex)
{
log.warn("unable to save configuration file", ex);
}
return future;
}
public List<RuneScapeProfile> getRSProfiles()
{
String prefix = RSPROFILE_GROUP + "." + RSPROFILE_GROUP + ".";
Set<String> profileKeys = new HashSet<>();
for (Object oKey : properties.keySet())
{
String key = (String) oKey;
if (!key.startsWith(prefix))
{
continue;
}
Matcher m = KEY_SPLITTER.matcher(key);
if (!m.find())
{
continue;
}
profileKeys.add(m.group(KEY_SPLITTER_PROFILE));
}
return profileKeys.stream()
.map(key ->
{
RuneScapeProfile prof = new RuneScapeProfile(
getConfiguration(RSPROFILE_GROUP, key, RSPROFILE_DISPLAY_NAME),
getConfiguration(RSPROFILE_GROUP, key, RSPROFILE_TYPE, RuneScapeProfileType.class),
getConfiguration(RSPROFILE_GROUP, key, RSPROFILE_LOGIN_HASH, byte[].class),
key
);
return prof;
})
.collect(Collectors.toList());
}
private synchronized RuneScapeProfile findRSProfile(List<RuneScapeProfile> profiles, String username, RuneScapeProfileType type, String displayName, boolean create)
{
byte[] salt = getConfiguration(RSPROFILE_GROUP, RSPROFILE_LOGIN_SALT, byte[].class);
if (salt == null)
{
salt = new byte[15];
new SecureRandom()
.nextBytes(salt);
setConfiguration(RSPROFILE_GROUP, RSPROFILE_LOGIN_SALT, salt);
}
Hasher h = Hashing.sha512().newHasher();
h.putBytes(salt);
h.putString(username.toLowerCase(Locale.US), StandardCharsets.UTF_8);
byte[] loginHash = h.hash().asBytes();
Set<RuneScapeProfile> matches = profiles.stream()
.filter(p -> Arrays.equals(p.getLoginHash(), loginHash) && p.getType() == type)
.collect(Collectors.toSet());
if (matches.size() > 1)
{
log.warn("multiple matching profiles");
}
if (matches.size() >= 1)
{
return matches.iterator().next();
}
if (!create)
{
return null;
}
// generate the new key deterministically so if you "create" the same profile on 2 different clients it doesn't duplicate
Set<String> keys = profiles.stream().map(RuneScapeProfile::getKey).collect(Collectors.toSet());
byte[] key = Arrays.copyOf(loginHash, 6);
key[0] += type.ordinal();
for (int i = 0; i < 0xFF; i++, key[1]++)
{
String keyStr = RSPROFILE_GROUP + "." + Base64.getUrlEncoder().encodeToString(key);
if (!keys.contains(keyStr))
{
log.info("creating new profile {} for user {}", key, username);
setConfiguration(RSPROFILE_GROUP, keyStr, RSPROFILE_LOGIN_HASH, loginHash);
setConfiguration(RSPROFILE_GROUP, keyStr, RSPROFILE_TYPE, type);
if (displayName != null)
{
setConfiguration(RSPROFILE_GROUP, keyStr, RSPROFILE_DISPLAY_NAME, displayName);
}
return new RuneScapeProfile(displayName, type, loginHash, keyStr);
}
}
throw new RuntimeException("too many rs profiles");
}
private void updateRSProfile()
{
if (client == null)
{
return;
}
List<RuneScapeProfile> profiles = getRSProfiles();
RuneScapeProfile prof = findRSProfile(profiles, client.getUsername(), RuneScapeProfileType.getCurrent(client), null, false);
String key = prof == null ? null : prof.getKey();
if (Objects.equals(key, rsProfileKey))
{
return;
}
rsProfileKey = key;
eventBus.post(new RuneScapeProfileChanged());
}
@Subscribe
private void onUsernameChanged(UsernameChanged ev)
{
updateRSProfile();
}
@Subscribe
private void onWorldChanged(WorldChanged ev)
{
updateRSProfile();
}
@Subscribe
private void onPlayerChanged(PlayerChanged ev)
{
if (ev.getPlayer() == client.getLocalPlayer())
{
String name = ev.getPlayer().getName();
setRSProfileConfiguration(RSPROFILE_GROUP, RSPROFILE_DISPLAY_NAME, name);
}
}
private synchronized void migrateConfig()
{
String migrationKey = "profileMigrationDone";
if (getConfiguration("runelite", migrationKey) != null)
{
return;
}
Map<String, String> profiles = new HashMap<>();
AtomicInteger changes = new AtomicInteger();
List<Predicate<String>> migrators = new ArrayList<>();
for (String[] tpl : new String[][]
{
{"(grandexchange)\\.buylimit_(%)\\.(#)", "$1.buylimit.$3"},
{"(timetracking)\\.(%)\\.(autoweed|contract)", "$1.$3"},
{"(timetracking)\\.(%)\\.(#\\.#)", "$1.$3"},
{"(timetracking)\\.(%)\\.(birdhouse)\\.(#)", "$1.$3.$4"},
{"(killcount|personalbest)\\.(%)\\.([^.]+)", "$1.$3"},
{"(geoffer)\\.(%)\\.(#)", "$1.$3"},
})
{
String replace = tpl[1];
String pat = ("^" + tpl[0] + "$")
.replace("#", "-?[0-9]+")
.replace("(%)", "(?<login>.*)");
Pattern p = Pattern.compile(pat);
migrators.add(oldkey ->
{
Matcher m = p.matcher(oldkey);
if (!m.find())
{
return false;
}
String newKey = m.replaceFirst(replace);
String username = m.group("login").toLowerCase(Locale.US);
if (username.startsWith(RSPROFILE_GROUP + "."))
{
return false;
}
String profKey = profiles.computeIfAbsent(username, u ->
findRSProfile(getRSProfiles(), u, RuneScapeProfileType.STANDARD, u, true).getKey());
Matcher oldKeyM = KEY_SPLITTER.matcher(oldkey);
if (!oldKeyM.find())
{
log.warn("skipping migration of invalid key \"{}\"", oldkey);
return false;
}
if (oldKeyM.group(KEY_SPLITTER_PROFILE) != null)
{
log.debug("skipping migrated key \"{}\"", oldkey);
return false;
}
Matcher newKeyM = KEY_SPLITTER.matcher(newKey);
if (!newKeyM.find() || newKeyM.group(KEY_SPLITTER_PROFILE) != null)
{
log.warn("migration produced a bad key: \"{}\" -> \"{}\"", oldkey, newKey);
return false;
}
if (changes.getAndAdd(1) <= 0)
{
File file = new File(propertiesFile.getParent(), propertiesFile.getName() + "." + TIME_FORMAT.format(new Date()));
log.info("backing up pre-migration config to {}", file);
try
{
saveToFile(file);
}
catch (IOException e)
{
log.error("Backup failed", e);
throw new RuntimeException(e);
}
}
String oldGroup = oldKeyM.group(KEY_SPLITTER_GROUP);
String oldKeyPart = oldKeyM.group(KEY_SPLITTER_KEY);
String value = getConfiguration(oldGroup, oldKeyPart);
setConfiguration(newKeyM.group(KEY_SPLITTER_GROUP), profKey, newKeyM.group(KEY_SPLITTER_KEY), value);
unsetConfiguration(oldGroup, oldKeyPart);
return true;
});
}
Set<String> keys = (Set<String>) ImmutableSet.copyOf((Set<?>) properties.keySet());
keys:
for (String key : keys)
{
for (Predicate<String> mig : migrators)
{
if (mig.test(key))
{
continue keys;
}
}
}
if (changes.get() > 0)
{
log.info("migrated {} config keys", changes);
}
setConfiguration("runelite", migrationKey, 1);
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2020 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.config;
import lombok.Data;
/**
* A profile/save of a OSRS account. Each account can 1 profile per {@link RuneScapeProfileType}
* (ie Standard/League/DMM}.
*/
@Data
public class RuneScapeProfile
{
private final String displayName;
private final RuneScapeProfileType type;
private final byte[] loginHash;
/**
* Profile key used to save configs for this profile to the config store. This will
* always start with {@link ConfigManager#RSPROFILE_GROUP}
*/
private final String key;
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2020 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.config;
import java.util.function.Predicate;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.runelite.api.Client;
import net.runelite.api.WorldType;
@Getter
@RequiredArgsConstructor
public enum RuneScapeProfileType
{
STANDARD(client -> true),
BETA(client -> client.getWorldType().contains(WorldType.TOURNAMENT)),
DEADMAN(client -> client.getWorldType().contains(WorldType.DEADMAN)),
TRAILBLAZER_LEAGUE(client -> client.getWorldType().contains(WorldType.LEAGUE)),
;
private final Predicate<Client> test;
public static RuneScapeProfileType getCurrent(Client client)
{
RuneScapeProfileType[] types = values();
for (int i = types.length - 1; i >= 0; i--)
{
RuneScapeProfileType type = types[i];
if (types[i].test.test(client))
{
return type;
}
}
return STANDARD;
}
}

View File

@@ -24,7 +24,9 @@
*/
package net.runelite.client.events;
import javax.annotation.Nullable;
import lombok.Data;
import net.runelite.client.config.RuneScapeProfile;
/**
* An event where a configuration entry has been modified.
@@ -39,6 +41,14 @@ public class ConfigChanged
* between other key values that may have the same name.
*/
private String group;
/**
* The profile that has changed, if any
*
* @see RuneScapeProfile#getKey()
*/
@Nullable
private String profile;
/**
* The configuration key that has been modified.
*/

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2020 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.events;
/**
* Posted when the user switches to a different RuneScape save profile
* This might be because they logged into a different account, or hopped
* to/from a Beta/Tournament/DMM/Leagues world
*/
public class RuneScapeProfileChanged
{
}

View File

@@ -230,27 +230,23 @@ public class ChatCommandsPlugin extends Plugin
private void setKc(String boss, int killcount)
{
configManager.setConfiguration("killcount." + client.getUsername().toLowerCase(),
boss.toLowerCase(), killcount);
configManager.setRSProfileConfiguration("killcount", boss.toLowerCase(), killcount);
}
private int getKc(String boss)
{
Integer killCount = configManager.getConfiguration("killcount." + client.getUsername().toLowerCase(),
boss.toLowerCase(), int.class);
Integer killCount = configManager.getRSProfileConfiguration("killcount", boss.toLowerCase(), int.class);
return killCount == null ? 0 : killCount;
}
private void setPb(String boss, int seconds)
{
configManager.setConfiguration("personalbest." + client.getUsername().toLowerCase(),
boss.toLowerCase(), seconds);
configManager.setRSProfileConfiguration("personalbest", boss.toLowerCase(), seconds);
}
private int getPb(String boss)
{
Integer personalBest = configManager.getConfiguration("personalbest." + client.getUsername().toLowerCase(),
boss.toLowerCase(), int.class);
Integer personalBest = configManager.getRSProfileConfiguration("personalbest", boss.toLowerCase(), int.class);
return personalBest == null ? 0 : personalBest;
}

View File

@@ -125,7 +125,7 @@ public class GrandExchangePlugin extends Plugin
private static final String OSB_GE_TEXT = "<br>OSBuddy Actively traded price: ";
private static final String BUY_LIMIT_GE_TEXT = "<br>Buy limit: ";
private static final String BUY_LIMIT_KEY = "buylimit_";
private static final String BUY_LIMIT_KEY = "buylimit";
private static final Gson GSON = new Gson();
private static final Duration BUY_LIMIT_RESET = Duration.ofHours(4);
@@ -244,7 +244,7 @@ public class GrandExchangePlugin extends Plugin
private SavedOffer getOffer(int slot)
{
String offer = configManager.getConfiguration("geoffer." + client.getUsername().toLowerCase(), Integer.toString(slot));
String offer = configManager.getRSProfileConfiguration("geoffer", Integer.toString(slot));
if (offer == null)
{
return null;
@@ -254,12 +254,12 @@ public class GrandExchangePlugin extends Plugin
private void setOffer(int slot, SavedOffer offer)
{
configManager.setConfiguration("geoffer." + client.getUsername().toLowerCase(), Integer.toString(slot), GSON.toJson(offer));
configManager.setRSProfileConfiguration("geoffer", Integer.toString(slot), GSON.toJson(offer));
}
private void deleteOffer(int slot)
{
configManager.unsetConfiguration("geoffer." + client.getUsername().toLowerCase(), Integer.toString(slot));
configManager.unsetRSProfileConfiguration("geoffer", Integer.toString(slot));
}
@Provides
@@ -782,20 +782,19 @@ public class GrandExchangePlugin extends Plugin
private void setLimitResetTime(int itemId)
{
Instant lastDateTime = configManager.getConfiguration(GrandExchangeConfig.CONFIG_GROUP,
BUY_LIMIT_KEY + client.getUsername().toLowerCase() + "." + itemId, Instant.class);
Instant lastDateTime = configManager.getRSProfileConfiguration(GrandExchangeConfig.CONFIG_GROUP,
BUY_LIMIT_KEY + "." + itemId, Instant.class);
if (lastDateTime == null || lastDateTime.isBefore(Instant.now()))
{
configManager.setConfiguration(GrandExchangeConfig.CONFIG_GROUP,
BUY_LIMIT_KEY + client.getUsername().toLowerCase() + "." + itemId,
configManager.setRSProfileConfiguration(GrandExchangeConfig.CONFIG_GROUP, BUY_LIMIT_KEY + "." + itemId,
Instant.now().plus(BUY_LIMIT_RESET));
}
}
private Instant getLimitResetTime(int itemId)
{
Instant lastDateTime = configManager.getConfiguration(GrandExchangeConfig.CONFIG_GROUP,
BUY_LIMIT_KEY + client.getUsername().toLowerCase() + "." + itemId, Instant.class);
Instant lastDateTime = configManager.getRSProfileConfiguration(GrandExchangeConfig.CONFIG_GROUP,
BUY_LIMIT_KEY + "." + itemId, Instant.class);
if (lastDateTime == null)
{
return null;
@@ -803,7 +802,7 @@ public class GrandExchangePlugin extends Plugin
if (lastDateTime.isBefore(Instant.now()))
{
configManager.unsetConfiguration(GrandExchangeConfig.CONFIG_GROUP, BUY_LIMIT_KEY + client.getUsername().toLowerCase() + "." + itemId);
configManager.unsetRSProfileConfiguration(GrandExchangeConfig.CONFIG_GROUP, BUY_LIMIT_KEY + "." + itemId);
return null;
}

View File

@@ -40,11 +40,11 @@ import net.runelite.api.coords.WorldPoint;
import net.runelite.client.events.ConfigChanged;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.UsernameChanged;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.RuneScapeProfileChanged;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
@@ -216,7 +216,7 @@ public class TimeTrackingPlugin extends Plugin
}
@Subscribe
public void onUsernameChanged(UsernameChanged e)
public void onRuneScapeProfileChanged(RuneScapeProfileChanged e)
{
farmingTracker.loadCompletionTimes();
birdHouseTracker.loadFromConfig();

View File

@@ -331,7 +331,7 @@ public class FarmingContractManager
{
try
{
return Produce.getByItemID(Integer.parseInt(configManager.getConfiguration(getConfigGroup(), CONFIG_KEY_CONTRACT)));
return Produce.getByItemID(Integer.parseInt(configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, CONFIG_KEY_CONTRACT)));
}
catch (NumberFormatException ignored)
{
@@ -343,17 +343,11 @@ public class FarmingContractManager
{
if (contract != null)
{
configManager.setConfiguration(getConfigGroup(), CONFIG_KEY_CONTRACT, String.valueOf(contract.getItemID()));
configManager.setRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, CONFIG_KEY_CONTRACT, String.valueOf(contract.getItemID()));
}
else
{
configManager.unsetConfiguration(getConfigGroup(), CONFIG_KEY_CONTRACT);
configManager.unsetRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, CONFIG_KEY_CONTRACT);
}
}
@Nonnull
private String getConfigGroup()
{
return TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername();
}
}

View File

@@ -83,11 +83,10 @@ public class FarmingTracker
boolean changed = false;
{
String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername();
String autoweed = Integer.toString(client.getVar(Varbits.AUTOWEED));
if (!autoweed.equals(configManager.getConfiguration(group, TimeTrackingConfig.AUTOWEED)))
if (!autoweed.equals(configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.AUTOWEED)))
{
configManager.setConfiguration(group, TimeTrackingConfig.AUTOWEED, autoweed);
configManager.setRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.AUTOWEED, autoweed);
changed = true;
}
}
@@ -100,16 +99,15 @@ public class FarmingTracker
}
// Write config with new varbits
// timetracking.<login-username>.<regionID>.<VarbitID>=<varbitValue>:<unix time>
String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + region.getRegionID();
// timetracking.<rsprofile>.<regionID>.<VarbitID>=<varbitValue>:<unix time>
long unixNow = Instant.now().getEpochSecond();
for (FarmingPatch patch : region.getPatches())
{
// Write the config value if it doesn't match what is current, or it is more than 5 minutes old
Varbits varbit = patch.getVarbit();
String key = Integer.toString(varbit.getId());
String key = region.getRegionID() + "." + varbit.getId();
String strVarbit = Integer.toString(client.getVar(varbit));
String storedValue = configManager.getConfiguration(group, key);
String storedValue = configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, key);
if (storedValue != null)
{
@@ -133,7 +131,7 @@ public class FarmingTracker
}
String value = strVarbit + ":" + unixNow;
configManager.setConfiguration(group, key, value);
configManager.setRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, key, value);
changed = true;
}
}
@@ -151,16 +149,11 @@ public class FarmingTracker
{
long unixNow = Instant.now().getEpochSecond();
boolean autoweed;
{
String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername();
autoweed = Integer.toString(Autoweed.ON.ordinal())
.equals(configManager.getConfiguration(group, TimeTrackingConfig.AUTOWEED));
}
boolean autoweed = Integer.toString(Autoweed.ON.ordinal())
.equals(configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.AUTOWEED));
String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + patch.getRegion().getRegionID();
String key = Integer.toString(patch.getVarbit().getId());
String storedValue = configManager.getConfiguration(group, key);
String key = patch.getRegion().getRegionID() + "." + patch.getVarbit().getId();
String storedValue = configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, key);
if (storedValue == null)
{

View File

@@ -91,12 +91,10 @@ public class BirdHouseTracker
{
birdHouseData.clear();
final String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + TimeTrackingConfig.BIRD_HOUSE;
for (BirdHouseSpace space : BirdHouseSpace.values())
{
String key = Integer.toString(space.getVarp().getId());
String storedValue = configManager.getConfiguration(group, key);
String key = TimeTrackingConfig.BIRD_HOUSE + "." + space.getVarp().getId();
String storedValue = configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, key);
if (storedValue != null)
{
@@ -242,12 +240,10 @@ public class BirdHouseTracker
private void saveToConfig(Map<BirdHouseSpace, BirdHouseData> updatedData)
{
final String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + TimeTrackingConfig.BIRD_HOUSE;
for (BirdHouseData data : updatedData.values())
{
String key = Integer.toString(data.getSpace().getVarp().getId());
configManager.setConfiguration(group, key, data.getVarp() + ":" + data.getTimestamp());
String key = TimeTrackingConfig.BIRD_HOUSE + "." + data.getSpace().getVarp().getId();
configManager.setRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, key, data.getVarp() + ":" + data.getTimestamp());
}
}
}