From c8ffa3994d66ac5a508c13899160e5cfe9bbe870 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 18 Aug 2019 12:26:33 -0400 Subject: [PATCH] loot tracker: batch loot submissions --- .../api/loottracker/LootTrackerClient.java | 4 +- .../loottracker/LootTrackerController.java | 4 +- .../loottracker/LootTrackerService.java | 46 +++++++++---- .../LootTrackerControllerTest.java | 4 +- .../loottracker/LootTrackerPlugin.java | 67 ++++++++++++++++--- 5 files changed, 98 insertions(+), 27 deletions(-) diff --git a/http-api/src/main/java/net/runelite/http/api/loottracker/LootTrackerClient.java b/http-api/src/main/java/net/runelite/http/api/loottracker/LootTrackerClient.java index d250620905..59d1a567ff 100644 --- a/http-api/src/main/java/net/runelite/http/api/loottracker/LootTrackerClient.java +++ b/http-api/src/main/java/net/runelite/http/api/loottracker/LootTrackerClient.java @@ -53,7 +53,7 @@ public class LootTrackerClient private final UUID uuid; - public void submit(LootRecord lootRecord) + public void submit(Collection lootRecords) { HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() .addPathSegment("loottracker") @@ -61,7 +61,7 @@ public class LootTrackerClient Request request = new Request.Builder() .header(RuneLiteAPI.RUNELITE_AUTH, uuid.toString()) - .post(RequestBody.create(JSON, GSON.toJson(lootRecord))) + .post(RequestBody.create(JSON, GSON.toJson(lootRecords))) .url(url) .build(); diff --git a/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerController.java b/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerController.java index 2c1bd77140..1033d62187 100644 --- a/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerController.java +++ b/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerController.java @@ -53,7 +53,7 @@ public class LootTrackerController private AuthFilter auth; @RequestMapping(method = RequestMethod.POST) - public void storeLootRecord(HttpServletRequest request, HttpServletResponse response, @RequestBody LootRecord record) throws IOException + public void storeLootRecord(HttpServletRequest request, HttpServletResponse response, @RequestBody Collection records) throws IOException { SessionEntry e = auth.handle(request, response); if (e == null) @@ -62,7 +62,7 @@ public class LootTrackerController return; } - service.store(record, e.getUser()); + service.store(records, e.getUser()); response.setStatus(HttpStatusCodes.STATUS_CODE_OK); } diff --git a/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerService.java b/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerService.java index b999f4eabb..8f97ce144d 100644 --- a/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerService.java +++ b/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerService.java @@ -64,7 +64,7 @@ public class LootTrackerService // Queries for inserting kills private static final String INSERT_KILL_QUERY = "INSERT INTO kills (accountId, type, eventId) VALUES (:accountId, :type, :eventId)"; - private static final String INSERT_DROP_QUERY = "INSERT INTO drops (killId, itemId, itemQuantity) VALUES (LAST_INSERT_ID(), :itemId, :itemQuantity)"; + private static final String INSERT_DROP_QUERY = "INSERT INTO drops (killId, itemId, itemQuantity) VALUES (:killId, :itemId, :itemQuantity)"; private static final String SELECT_LOOT_QUERY = "SELECT killId,time,type,eventId,itemId,itemQuantity FROM kills JOIN drops ON drops.killId = kills.id WHERE accountId = :accountId ORDER BY TIME DESC LIMIT :limit OFFSET :offset"; @@ -89,29 +89,49 @@ public class LootTrackerService /** * Store LootRecord * - * @param record LootRecord to store + * @param records LootRecords to store * @param accountId runelite account id to tie data too */ - public void store(LootRecord record, int accountId) + public void store(Collection records, int accountId) { try (Connection con = sql2o.beginTransaction()) { // Kill Entry Query - con.createQuery(INSERT_KILL_QUERY, true) - .addParameter("accountId", accountId) - .addParameter("type", record.getType()) - .addParameter("eventId", record.getEventId()) - .executeUpdate(); + Query killQuery = con.createQuery(INSERT_KILL_QUERY, true); + + for (LootRecord record : records) + { + killQuery + .addParameter("accountId", accountId) + .addParameter("type", record.getType()) + .addParameter("eventId", record.getEventId()) + .addToBatch(); + } + + killQuery.executeBatch(); + Object[] keys = con.getKeys(); + + if (keys.length != records.size()) + { + throw new RuntimeException("Mismatch in keys vs records size"); + } Query insertDrop = con.createQuery(INSERT_DROP_QUERY); // Append all queries for inserting drops - for (GameItem drop : record.getDrops()) + int idx = 0; + for (LootRecord record : records) { - insertDrop - .addParameter("itemId", drop.getId()) - .addParameter("itemQuantity", drop.getQty()) - .addToBatch(); + for (GameItem drop : record.getDrops()) + { + insertDrop + .addParameter("killId", keys[idx]) + .addParameter("itemId", drop.getId()) + .addParameter("itemQuantity", drop.getQty()) + .addToBatch(); + } + + ++idx; } insertDrop.executeBatch(); diff --git a/http-service/src/test/java/net/runelite/http/service/loottracker/LootTrackerControllerTest.java b/http-service/src/test/java/net/runelite/http/service/loottracker/LootTrackerControllerTest.java index 3c80dd72d4..ccf7ab7603 100644 --- a/http-service/src/test/java/net/runelite/http/service/loottracker/LootTrackerControllerTest.java +++ b/http-service/src/test/java/net/runelite/http/service/loottracker/LootTrackerControllerTest.java @@ -83,10 +83,10 @@ public class LootTrackerControllerTest lootRecord.setTime(Instant.now()); lootRecord.setDrops(Collections.singletonList(new GameItem(4151, 1))); - String data = RuneLiteAPI.GSON.toJson(lootRecord); + String data = RuneLiteAPI.GSON.toJson(Collections.singletonList(lootRecord)); mockMvc.perform(post("/loottracker").content(data).contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); - verify(lootTrackerService).store(eq(lootRecord), anyInt()); + verify(lootTrackerService).store(eq(Collections.singletonList(lootRecord)), anyInt()); } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java index 72b89c8406..edec90e676 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java @@ -34,6 +34,7 @@ import com.google.inject.Provides; import java.awt.image.BufferedImage; import java.io.IOException; import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -81,6 +82,7 @@ import net.runelite.client.game.ItemStack; import net.runelite.client.game.SpriteManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.task.Schedule; import net.runelite.client.ui.ClientToolbar; import net.runelite.client.ui.NavigationButton; import net.runelite.client.util.ImageUtil; @@ -167,6 +169,7 @@ public class LootTrackerPlugin extends Plugin @Getter(AccessLevel.PACKAGE) private LootTrackerClient lootTrackerClient; + private final List queuedLoots = new ArrayList<>(); private static Collection stack(Collection items) { @@ -220,6 +223,7 @@ public class LootTrackerPlugin extends Plugin @Subscribe public void onSessionClose(SessionClose sessionClose) { + submitLoot(); lootTrackerClient = null; } @@ -300,6 +304,7 @@ public class LootTrackerPlugin extends Plugin @Override protected void shutDown() { + submitLoot(); clientToolbar.removeNavigation(navButton); lootTrackerClient = null; chestLooted = false; @@ -324,10 +329,13 @@ public class LootTrackerPlugin extends Plugin final LootTrackerItem[] entries = buildEntries(stack(items)); SwingUtilities.invokeLater(() -> panel.add(name, combat, entries)); - if (lootTrackerClient != null && config.saveLoot()) + if (config.saveLoot()) { LootRecord lootRecord = new LootRecord(name, LootRecordType.NPC, toGameItems(items), Instant.now()); - lootTrackerClient.submit(lootRecord); + synchronized (queuedLoots) + { + queuedLoots.add(lootRecord); + } } } @@ -347,10 +355,13 @@ public class LootTrackerPlugin extends Plugin final LootTrackerItem[] entries = buildEntries(stack(items)); SwingUtilities.invokeLater(() -> panel.add(name, combat, entries)); - if (lootTrackerClient != null && config.saveLoot()) + if (config.saveLoot()) { LootRecord lootRecord = new LootRecord(name, LootRecordType.PLAYER, toGameItems(items), Instant.now()); - lootTrackerClient.submit(lootRecord); + synchronized (queuedLoots) + { + queuedLoots.add(lootRecord); + } } } @@ -420,10 +431,13 @@ public class LootTrackerPlugin extends Plugin final LootTrackerItem[] entries = buildEntries(stack(items)); SwingUtilities.invokeLater(() -> panel.add(eventType, -1, entries)); - if (lootTrackerClient != null && config.saveLoot()) + if (config.saveLoot()) { LootRecord lootRecord = new LootRecord(eventType, LootRecordType.EVENT, toGameItems(items), Instant.now()); - lootTrackerClient.submit(lootRecord); + synchronized (queuedLoots) + { + queuedLoots.add(lootRecord); + } } } @@ -521,6 +535,40 @@ public class LootTrackerPlugin extends Plugin } } + @Schedule( + period = 5, + unit = ChronoUnit.MINUTES, + asynchronous = true + ) + public void submitLootTask() + { + submitLoot(); + } + + private void submitLoot() + { + List copy; + synchronized (queuedLoots) + { + if (queuedLoots.isEmpty()) + { + return; + } + + copy = new ArrayList<>(queuedLoots); + queuedLoots.clear(); + } + + if (lootTrackerClient == null || !config.saveLoot()) + { + return; + } + + log.debug("Submitting {} loot records", copy.size()); + + lootTrackerClient.submit(copy); + } + private void takeInventorySnapshot() { final ItemContainer itemContainer = client.getItemContainer(InventoryID.INVENTORY); @@ -549,10 +597,13 @@ public class LootTrackerPlugin extends Plugin final LootTrackerItem[] entries = buildEntries(stack(items)); SwingUtilities.invokeLater(() -> panel.add(chestType, -1, entries)); - if (lootTrackerClient != null && config.saveLoot()) + if (config.saveLoot()) { LootRecord lootRecord = new LootRecord(chestType, LootRecordType.EVENT, toGameItems(items), Instant.now()); - lootTrackerClient.submit(lootRecord); + synchronized (queuedLoots) + { + queuedLoots.add(lootRecord); + } } inventorySnapshot = null;