From e769ee0a7b363d1f0f46f9767aae4693991cc80f Mon Sep 17 00:00:00 2001 From: Max Weber Date: Mon, 23 Nov 2020 00:14:55 -0700 Subject: [PATCH] http-api, http-service, rl-client: bulk upload configuration changes --- .../http/api/config/ConfigClient.java | 55 +++++++++++ .../runelite/http/api/config/ConfigEntry.java | 27 ++---- .../http/api/config/Configuration.java | 14 +-- .../http/service/config/ConfigController.java | 25 +++++ .../http/service/config/ConfigService.java | 91 ++++++++++++++----- .../runelite/client/config/ConfigManager.java | 40 ++++---- 6 files changed, 175 insertions(+), 77 deletions(-) diff --git a/http-api/src/main/java/net/runelite/http/api/config/ConfigClient.java b/http-api/src/main/java/net/runelite/http/api/config/ConfigClient.java index 334660fb85..b66b802399 100644 --- a/http-api/src/main/java/net/runelite/http/api/config/ConfigClient.java +++ b/http-api/src/main/java/net/runelite/http/api/config/ConfigClient.java @@ -24,6 +24,7 @@ */ package net.runelite.http.api.config; +import com.google.gson.Gson; import com.google.gson.JsonParseException; import java.io.IOException; import java.io.InputStream; @@ -48,6 +49,7 @@ import okhttp3.Response; public class ConfigClient { private static final MediaType TEXT_PLAIN = MediaType.parse("text/plain"); + private static final Gson GSON = RuneLiteAPI.GSON; private final OkHttpClient client; private final UUID uuid; @@ -114,6 +116,59 @@ public class ConfigClient return future; } + public CompletableFuture patch(Configuration configuration) + { + CompletableFuture future = new CompletableFuture<>(); + + HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() + .addPathSegment("config") + .build(); + + log.debug("Built URI: {}", url); + + Request request = new Request.Builder() + .patch(RequestBody.create(RuneLiteAPI.JSON, GSON.toJson(configuration))) + .header(RuneLiteAPI.RUNELITE_AUTH, uuid.toString()) + .url(url) + .build(); + + client.newCall(request).enqueue(new Callback() + { + @Override + public void onFailure(Call call, IOException e) + { + log.warn("Unable to synchronize configuration item", e); + future.completeExceptionally(e); + } + + @Override + public void onResponse(Call call, Response response) + { + if (response.code() != 200) + { + String body = "bad response"; + try + { + body = response.body().string(); + } + catch (IOException ignored) + { + } + + log.warn("failed to synchronize some of {} configuration values: {}", configuration.getConfig().size(), body); + } + else + { + log.debug("Synchronized {} configuration values", configuration.getConfig().size()); + } + response.close(); + future.complete(null); + } + }); + + return future; + } + public CompletableFuture unset(String key) { CompletableFuture future = new CompletableFuture<>(); diff --git a/http-api/src/main/java/net/runelite/http/api/config/ConfigEntry.java b/http-api/src/main/java/net/runelite/http/api/config/ConfigEntry.java index 783d68361e..d8b69dae72 100644 --- a/http-api/src/main/java/net/runelite/http/api/config/ConfigEntry.java +++ b/http-api/src/main/java/net/runelite/http/api/config/ConfigEntry.java @@ -24,28 +24,15 @@ */ package net.runelite.http.api.config; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor public class ConfigEntry { private String key; private String value; - - public String getKey() - { - return key; - } - - public void setKey(String key) - { - this.key = key; - } - - public String getValue() - { - return value; - } - - public void setValue(String value) - { - this.value = value; - } } diff --git a/http-api/src/main/java/net/runelite/http/api/config/Configuration.java b/http-api/src/main/java/net/runelite/http/api/config/Configuration.java index 7b256e0c7a..44269b735a 100644 --- a/http-api/src/main/java/net/runelite/http/api/config/Configuration.java +++ b/http-api/src/main/java/net/runelite/http/api/config/Configuration.java @@ -26,18 +26,12 @@ package net.runelite.http.api.config; import java.util.ArrayList; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +@Data +@AllArgsConstructor public class Configuration { private List config = new ArrayList<>(); - - public Configuration(List config) - { - this.config = config; - } - - public List getConfig() - { - return config; - } } diff --git a/http-service/src/main/java/net/runelite/http/service/config/ConfigController.java b/http-service/src/main/java/net/runelite/http/service/config/ConfigController.java index 971094a585..c247ecc0bc 100644 --- a/http-service/src/main/java/net/runelite/http/service/config/ConfigController.java +++ b/http-service/src/main/java/net/runelite/http/service/config/ConfigController.java @@ -25,6 +25,7 @@ package net.runelite.http.service.config; import java.io.IOException; +import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.runelite.http.api.config.Configuration; @@ -32,6 +33,7 @@ import net.runelite.http.service.account.AuthFilter; import net.runelite.http.service.account.beans.SessionEntry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -66,6 +68,29 @@ public class ConfigController return configService.get(session.getUser()); } + @PatchMapping + public List patch( + HttpServletRequest request, + HttpServletResponse response, + @RequestBody Configuration changes + ) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + if (session == null) + { + return null; + } + + List failures = configService.patch(session.getUser(), changes); + if (failures.size() != 0) + { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return failures; + } + + return null; + } + @RequestMapping(path = "/{key:.+}", method = PUT) public void setKey( HttpServletRequest request, diff --git a/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java b/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java index 727620570f..7c3f5c37b3 100644 --- a/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java +++ b/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java @@ -25,6 +25,7 @@ package net.runelite.http.service.config; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -38,6 +39,7 @@ import static com.mongodb.client.model.Filters.eq; import com.mongodb.client.model.IndexOptions; import com.mongodb.client.model.Indexes; import com.mongodb.client.model.UpdateOptions; +import static com.mongodb.client.model.Updates.combine; import static com.mongodb.client.model.Updates.set; import static com.mongodb.client.model.Updates.unset; import java.util.ArrayList; @@ -50,6 +52,7 @@ import net.runelite.http.api.RuneLiteAPI; import net.runelite.http.api.config.ConfigEntry; import net.runelite.http.api.config.Configuration; import org.bson.Document; +import org.bson.conversions.Bson; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -131,31 +134,79 @@ public class ConfigService return new Configuration(config); } + public List patch(int userID, Configuration config) + { + List failures = new ArrayList<>(); + List sets = new ArrayList<>(config.getConfig().size()); + for (ConfigEntry entry : config.getConfig()) + { + Bson s = setForKV(entry.getKey(), entry.getValue()); + if (s == null) + { + failures.add(entry.getKey()); + } + else + { + sets.add(s); + } + } + + if (sets.size() > 0) + { + mongoCollection.updateOne( + eq("_userId", userID), + combine(sets), + upsertUpdateOptions + ); + } + + return failures; + } + + @Nullable + private Bson setForKV(String key, @Nullable String value) + { + if (key.startsWith("$") || key.startsWith("_")) + { + return null; + } + + String[] split = key.split("\\.", 2); + if (split.length != 2) + { + return null; + } + + String dbKey = split[0] + "." + split[1].replace('.', ':'); + + if (Strings.isNullOrEmpty(value)) + { + return unset(dbKey); + } + + if (!validateJson(value)) + { + return null; + } + + Object jsonValue = parseJsonString(value); + return set(dbKey, jsonValue); + } + public boolean setKey( int userId, String key, @Nullable String value ) { - if (key.startsWith("$") || key.startsWith("_")) + Bson set = setForKV(key, value); + if (set == null) { return false; } - String[] split = key.split("\\.", 2); - if (split.length != 2) - { - return false; - } - - if (!validateJson(value)) - { - return false; - } - - Object jsonValue = parseJsonString(value); mongoCollection.updateOne(eq("_userId", userId), - set(split[0] + "." + split[1].replace('.', ':'), jsonValue), + set, upsertUpdateOptions); return true; } @@ -165,19 +216,13 @@ public class ConfigService String key ) { - if (key.startsWith("$") || key.startsWith("_")) + Bson set = setForKV(key, null); + if (set == null) { return false; } - String[] split = key.split("\\.", 2); - if (split.length != 2) - { - return false; - } - - mongoCollection.updateOne(eq("_userId", userId), - unset(split[0] + "." + split[1].replace('.', ':'))); + mongoCollection.updateOne(eq("_userId", userId), set); return true; } 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 64c1fa14b7..3ef6744b54 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 @@ -894,40 +894,32 @@ public class ConfigManager private CompletableFuture sendConfig() { CompletableFuture future = null; - boolean changed; synchronized (pendingChanges) { + if (pendingChanges.isEmpty()) + { + return null; + } + if (configClient != null) { - future = CompletableFuture.allOf(pendingChanges.entrySet().stream().map(entry -> - { - String key = entry.getKey(); - String value = entry.getValue(); + Configuration patch = new Configuration(pendingChanges.entrySet().stream() + .map(e -> new ConfigEntry(e.getKey(), e.getValue())) + .collect(Collectors.toList())); - if (Strings.isNullOrEmpty(value)) - { - return configClient.unset(key); - } - else - { - return configClient.set(key, value); - } - }).toArray(CompletableFuture[]::new)); + future = configClient.patch(patch); } - changed = !pendingChanges.isEmpty(); + 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;