diff --git a/http-api/src/main/java/net/runelite/http/api/item/ItemPrice.java b/http-api/src/main/java/net/runelite/http/api/item/ItemPrice.java index 147a90673b..001c63deb8 100644 --- a/http-api/src/main/java/net/runelite/http/api/item/ItemPrice.java +++ b/http-api/src/main/java/net/runelite/http/api/item/ItemPrice.java @@ -30,7 +30,8 @@ import lombok.Data; @Data public class ItemPrice { - private Item item; + private int id; + private String name; private int price; private Instant time; } diff --git a/http-service/src/main/java/net/runelite/http/service/item/ItemController.java b/http-service/src/main/java/net/runelite/http/service/item/ItemController.java index efc0afe4a9..9c05889330 100644 --- a/http-service/src/main/java/net/runelite/http/service/item/ItemController.java +++ b/http-service/src/main/java/net/runelite/http/service/item/ItemController.java @@ -28,7 +28,6 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import java.time.Duration; import java.time.Instant; import java.util.Arrays; import java.util.List; @@ -50,7 +49,6 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/item") public class ItemController { - private static final Duration CACHE_DUATION = Duration.ofMinutes(30); private static final String RUNELITE_CACHE = "RuneLite-Cache"; private static final int MAX_BATCH_LOOKUP = 1024; @@ -70,11 +68,9 @@ public class ItemController memorizedPrices = Suppliers.memoizeWithExpiration(() -> itemService.fetchPrices().stream() .map(priceEntry -> { - Item item = new Item(); - item.setId(priceEntry.getItem()); // fake item - ItemPrice itemPrice = new ItemPrice(); - itemPrice.setItem(item); + itemPrice.setId(priceEntry.getItem()); + itemPrice.setName(priceEntry.getName()); itemPrice.setPrice(priceEntry.getPrice()); itemPrice.setTime(priceEntry.getTime()); return itemPrice; @@ -173,7 +169,8 @@ public class ItemController } ItemPrice itemPrice = new ItemPrice(); - itemPrice.setItem(item.toItem()); + itemPrice.setId(item.getId()); + itemPrice.setName(item.getName()); itemPrice.setPrice(priceEntry.getPrice()); itemPrice.setTime(priceEntry.getTime()); @@ -209,11 +206,9 @@ public class ItemController return prices.stream() .map(priceEntry -> { - Item item = new Item(); - item.setId(priceEntry.getItem()); // fake item - ItemPrice itemPrice = new ItemPrice(); - itemPrice.setItem(item); + itemPrice.setId(priceEntry.getItem()); + itemPrice.setName(priceEntry.getName()); itemPrice.setPrice(priceEntry.getPrice()); itemPrice.setTime(priceEntry.getTime()); return itemPrice; diff --git a/http-service/src/main/java/net/runelite/http/service/item/ItemEntry.java b/http-service/src/main/java/net/runelite/http/service/item/ItemEntry.java index b92f171cc7..634b2507c0 100644 --- a/http-service/src/main/java/net/runelite/http/service/item/ItemEntry.java +++ b/http-service/src/main/java/net/runelite/http/service/item/ItemEntry.java @@ -30,7 +30,7 @@ import net.runelite.http.api.item.Item; import net.runelite.http.api.item.ItemType; @Data -public class ItemEntry +class ItemEntry { private int id; private String name; diff --git a/http-service/src/main/java/net/runelite/http/service/item/ItemService.java b/http-service/src/main/java/net/runelite/http/service/item/ItemService.java index 1cb9ca88bd..67fac7188c 100644 --- a/http-service/src/main/java/net/runelite/http/service/item/ItemService.java +++ b/http-service/src/main/java/net/runelite/http/service/item/ItemService.java @@ -124,14 +124,14 @@ public class ItemService { if (time != null) { - return con.createQuery("select item, price, time, fetched_time from prices where item = :item and time <= :time order by time desc limit 1") + return con.createQuery("select item, name, price, time, fetched_time from prices t1 join items t2 on t1.item=t2.id where item = :item and time <= :time order by time desc limit 1") .addParameter("item", itemId) .addParameter("time", time.toString()) .executeAndFetchFirst(PriceEntry.class); } else { - return con.createQuery("select item, price, time, fetched_time from prices where item = :item order by time desc limit 1") + return con.createQuery("select item, name, price, time, fetched_time from prices t1 join items t2 on t1.item=t2.id where item = :item order by time desc limit 1") .addParameter("item", itemId) .executeAndFetchFirst(PriceEntry.class); } @@ -295,9 +295,10 @@ public class ItemService { try (Connection con = sql2o.beginTransaction()) { - Query query = con.createQuery("select t2.item, t2.time, prices.price, prices.fetched_time from (select t1.item as item, max(t1.time) as time from prices t1 group by item) t2 join prices on t2.item=prices.item and t2.time=prices.time"); - List entries = query.executeAndFetch(PriceEntry.class); - return entries; + Query query = con.createQuery("select t2.item, t3.name, t2.time, prices.price, prices.fetched_time from (select t1.item as item, max(t1.time) as time from prices t1 group by item) t2 " + + " join prices on t2.item=prices.item and t2.time=prices.time" + + " join items t3 on t2.item=t3.id"); + return query.executeAndFetch(PriceEntry.class); } } diff --git a/http-service/src/main/java/net/runelite/http/service/item/PriceEntry.java b/http-service/src/main/java/net/runelite/http/service/item/PriceEntry.java index d7dc536232..00f0e6815c 100644 --- a/http-service/src/main/java/net/runelite/http/service/item/PriceEntry.java +++ b/http-service/src/main/java/net/runelite/http/service/item/PriceEntry.java @@ -28,9 +28,10 @@ import java.time.Instant; import lombok.Data; @Data -public class PriceEntry +class PriceEntry { private int item; + private String name; private int price; private Instant time; private Instant fetched_time; diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java index 2fc4a8f569..56c28d5bb4 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java +++ b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java @@ -32,7 +32,9 @@ import com.google.common.eventbus.Subscribe; import java.awt.Color; import java.awt.image.BufferedImage; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledExecutorService; @@ -51,7 +53,6 @@ import net.runelite.api.events.GameStateChanged; import net.runelite.client.callback.ClientThread; import net.runelite.http.api.item.ItemClient; import net.runelite.http.api.item.ItemPrice; -import net.runelite.http.api.item.SearchResult; @Singleton @Slf4j @@ -78,7 +79,6 @@ public class ItemManager private final ClientThread clientThread; private final ItemClient itemClient = new ItemClient(); - private final LoadingCache itemSearches; private Map itemPrices = Collections.emptyMap(); private final LoadingCache itemImages; private final LoadingCache itemCompositions; @@ -93,18 +93,6 @@ public class ItemManager scheduledExecutorService.scheduleWithFixedDelay(this::loadPrices, 0, 30, TimeUnit.MINUTES); - itemSearches = CacheBuilder.newBuilder() - .maximumSize(512L) - .expireAfterAccess(1, TimeUnit.HOURS) - .build(new CacheLoader() - { - @Override - public SearchResult load(String key) throws Exception - { - return itemClient.search(key); - } - }); - itemImages = CacheBuilder.newBuilder() .maximumSize(128L) .expireAfterAccess(1, TimeUnit.HOURS) @@ -152,7 +140,7 @@ public class ItemManager ImmutableMap.Builder map = ImmutableMap.builderWithExpectedSize(prices.length); for (ItemPrice price : prices) { - map.put(price.getItem().getId(), price); + map.put(price.getId(), price); } itemPrices = map.build(); } @@ -205,16 +193,25 @@ public class ItemManager } /** - * Look up an item's composition + * Search for tradeable items based on item name * * @param itemName item name - * @return item search result - * @throws java.util.concurrent.ExecutionException exception when item - * is not found + * @return */ - public SearchResult searchForItem(String itemName) throws ExecutionException + public List search(String itemName) { - return itemSearches.get(itemName); + itemName = itemName.toLowerCase(); + + List result = new ArrayList<>(); + for (ItemPrice itemPrice : itemPrices.values()) + { + final String name = itemPrice.getName(); + if (name.toLowerCase().contains(itemName)) + { + result.add(itemPrice); + } + } + return result; } /** diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java index 7de9f99cd0..26866c534f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java @@ -29,7 +29,6 @@ import com.google.common.eventbus.Subscribe; import com.google.inject.Provides; import java.io.IOException; import java.util.List; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -69,8 +68,7 @@ import net.runelite.http.api.hiscore.HiscoreResult; import net.runelite.http.api.hiscore.HiscoreSkill; import net.runelite.http.api.hiscore.SingleHiscoreSkillResult; import net.runelite.http.api.hiscore.Skill; -import net.runelite.http.api.item.Item; -import net.runelite.http.api.item.SearchResult; +import net.runelite.http.api.item.ItemPrice; import net.runelite.http.api.kc.KillCountClient; @PluginDescriptor( @@ -205,7 +203,7 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener String search = message.substring(PRICE_COMMAND_STRING.length() + 1); log.debug("Running price lookup for {}", search); - executor.submit(() -> itemPriceLookup(setMessage.getMessageNode(), search)); + itemPriceLookup(setMessage.getMessageNode(), search); } else if (config.lvl() && message.toLowerCase().startsWith(LEVEL_COMMAND_STRING + " ")) { @@ -457,29 +455,14 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener */ private void itemPriceLookup(MessageNode messageNode, String search) { - SearchResult result; + List results = itemManager.search(search); - try + if (!results.isEmpty()) { - result = itemManager.searchForItem(search); - } - catch (ExecutionException ex) - { - log.warn("Unable to search for item {}", search, ex); - return; - } - - if (result != null && !result.getItems().isEmpty()) - { - Item item = retrieveFromList(result.getItems(), search); - if (item == null) - { - log.debug("Unable to find item {} in result {}", search, result); - return; - } + ItemPrice item = retrieveFromList(results, search); int itemId = item.getId(); - int itemPrice = itemManager.getItemPrice(itemId); + int itemPrice = item.getPrice(); final ChatMessageBuilder builder = new ChatMessageBuilder() .append(ChatColorType.NORMAL) @@ -767,23 +750,31 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener /** * Compares the names of the items in the list with the original input. - * Returns the item if its name is equal to the original input or null - * if it can't find the item. + * Returns the item if its name is equal to the original input or the + * shortest match if no exact match is found. * * @param items List of items. * @param originalInput String with the original input. * @return Item which has a name equal to the original input. */ - private Item retrieveFromList(List items, String originalInput) + private ItemPrice retrieveFromList(List items, String originalInput) { - for (Item item : items) + ItemPrice shortest = null; + for (ItemPrice item : items) { if (item.getName().toLowerCase().equals(originalInput.toLowerCase())) { return item; } + + if (shortest == null || item.getName().length() < shortest.getName().length()) + { + shortest = item; + } } - return null; + + // Take a guess + return shortest; } /** diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java index 870df19458..af827227ec 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java @@ -50,8 +50,7 @@ import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.components.IconTextField; import net.runelite.client.ui.components.PluginErrorPanel; import net.runelite.client.util.RunnableExceptionLogger; -import net.runelite.http.api.item.Item; -import net.runelite.http.api.item.SearchResult; +import net.runelite.http.api.item.ItemPrice; /** * This panel holds the search section of the Grand Exchange Plugin. @@ -169,19 +168,13 @@ class GrandExchangeSearchPanel extends JPanel searchBar.setEditable(false); searchBar.setIcon(IconTextField.Icon.LOADING); - SearchResult result; - - try + List result = itemManager.search(lookup); + if (result.isEmpty()) { - result = itemManager.searchForItem(lookup); - } - catch (Exception ex) // handle com.google.common.cache.CacheLoader$InvalidCacheLoadException - { - log.warn("Unable to search for item {}", lookup, ex); searchBar.setIcon(IconTextField.Icon.ERROR); - searchBar.setEditable(true); - errorPanel.setContent("Error fetching results", "An error occurred while trying to fetch item data, please try again later."); + errorPanel.setContent("No results found.", "No items were found with that name, please try again."); cardLayout.show(centerPanel, ERROR_PANEL); + searchBar.setEditable(true); return; } @@ -189,42 +182,33 @@ class GrandExchangeSearchPanel extends JPanel clientThread.invokeLater(() -> processResult(result, lookup, exactMatch)); } - private void processResult(SearchResult result, String lookup, boolean exactMatch) + private void processResult(List result, String lookup, boolean exactMatch) { itemsList.clear(); - if (result != null && !result.getItems().isEmpty()) - { - cardLayout.show(centerPanel, RESULTS_PANEL); + cardLayout.show(centerPanel, RESULTS_PANEL); - for (Item item : result.getItems()) + for (ItemPrice item : result) + { + int itemId = item.getId(); + + ItemComposition itemComp = itemManager.getItemComposition(itemId); + if (itemComp == null) { - int itemId = item.getId(); - - ItemComposition itemComp = itemManager.getItemComposition(itemId); - if (itemComp == null) - { - continue; - } - - int itemPrice = itemManager.getItemPrice(itemId); - int itemLimit = itemGELimits.getOrDefault(itemId, 0); - AsyncBufferedImage itemImage = itemManager.getImage(itemId); - - itemsList.add(new GrandExchangeItems(itemImage, item.getName(), itemId, itemPrice, itemComp.getPrice() * 0.6, itemLimit)); - - // If using hotkey to lookup item, stop after finding match. - if (exactMatch && item.getName().equalsIgnoreCase(lookup)) - { - break; - } + continue; + } + + int itemPrice = item.getPrice(); + int itemLimit = itemGELimits.getOrDefault(itemId, 0); + AsyncBufferedImage itemImage = itemManager.getImage(itemId); + + itemsList.add(new GrandExchangeItems(itemImage, item.getName(), itemId, itemPrice, itemComp.getPrice() * 0.6, itemLimit)); + + // If using hotkey to lookup item, stop after finding match. + if (exactMatch && item.getName().equalsIgnoreCase(lookup)) + { + break; } - } - else - { - searchBar.setIcon(IconTextField.Icon.ERROR); - errorPanel.setContent("No results found.", "No items were found with that name, please try again."); - cardLayout.show(centerPanel, ERROR_PANEL); } SwingUtilities.invokeLater(() ->