Merge pull request #5866 from Adam-/item-search

Move item search client side
This commit is contained in:
Adam
2018-10-08 18:37:29 -04:00
committed by GitHub
8 changed files with 80 additions and 110 deletions

View File

@@ -30,7 +30,8 @@ import lombok.Data;
@Data @Data
public class ItemPrice public class ItemPrice
{ {
private Item item; private int id;
private String name;
private int price; private int price;
private Instant time; private Instant time;
} }

View File

@@ -28,7 +28,6 @@ import com.google.common.base.Supplier;
import com.google.common.base.Suppliers; import com.google.common.base.Suppliers;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -50,7 +49,6 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/item") @RequestMapping("/item")
public class ItemController public class ItemController
{ {
private static final Duration CACHE_DUATION = Duration.ofMinutes(30);
private static final String RUNELITE_CACHE = "RuneLite-Cache"; private static final String RUNELITE_CACHE = "RuneLite-Cache";
private static final int MAX_BATCH_LOOKUP = 1024; private static final int MAX_BATCH_LOOKUP = 1024;
@@ -70,11 +68,9 @@ public class ItemController
memorizedPrices = Suppliers.memoizeWithExpiration(() -> itemService.fetchPrices().stream() memorizedPrices = Suppliers.memoizeWithExpiration(() -> itemService.fetchPrices().stream()
.map(priceEntry -> .map(priceEntry ->
{ {
Item item = new Item();
item.setId(priceEntry.getItem()); // fake item
ItemPrice itemPrice = new ItemPrice(); ItemPrice itemPrice = new ItemPrice();
itemPrice.setItem(item); itemPrice.setId(priceEntry.getItem());
itemPrice.setName(priceEntry.getName());
itemPrice.setPrice(priceEntry.getPrice()); itemPrice.setPrice(priceEntry.getPrice());
itemPrice.setTime(priceEntry.getTime()); itemPrice.setTime(priceEntry.getTime());
return itemPrice; return itemPrice;
@@ -173,7 +169,8 @@ public class ItemController
} }
ItemPrice itemPrice = new ItemPrice(); ItemPrice itemPrice = new ItemPrice();
itemPrice.setItem(item.toItem()); itemPrice.setId(item.getId());
itemPrice.setName(item.getName());
itemPrice.setPrice(priceEntry.getPrice()); itemPrice.setPrice(priceEntry.getPrice());
itemPrice.setTime(priceEntry.getTime()); itemPrice.setTime(priceEntry.getTime());
@@ -209,11 +206,9 @@ public class ItemController
return prices.stream() return prices.stream()
.map(priceEntry -> .map(priceEntry ->
{ {
Item item = new Item();
item.setId(priceEntry.getItem()); // fake item
ItemPrice itemPrice = new ItemPrice(); ItemPrice itemPrice = new ItemPrice();
itemPrice.setItem(item); itemPrice.setId(priceEntry.getItem());
itemPrice.setName(priceEntry.getName());
itemPrice.setPrice(priceEntry.getPrice()); itemPrice.setPrice(priceEntry.getPrice());
itemPrice.setTime(priceEntry.getTime()); itemPrice.setTime(priceEntry.getTime());
return itemPrice; return itemPrice;

View File

@@ -30,7 +30,7 @@ import net.runelite.http.api.item.Item;
import net.runelite.http.api.item.ItemType; import net.runelite.http.api.item.ItemType;
@Data @Data
public class ItemEntry class ItemEntry
{ {
private int id; private int id;
private String name; private String name;

View File

@@ -124,14 +124,14 @@ public class ItemService
{ {
if (time != null) 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("item", itemId)
.addParameter("time", time.toString()) .addParameter("time", time.toString())
.executeAndFetchFirst(PriceEntry.class); .executeAndFetchFirst(PriceEntry.class);
} }
else 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) .addParameter("item", itemId)
.executeAndFetchFirst(PriceEntry.class); .executeAndFetchFirst(PriceEntry.class);
} }
@@ -295,9 +295,10 @@ public class ItemService
{ {
try (Connection con = sql2o.beginTransaction()) 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"); 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 " +
List<PriceEntry> entries = query.executeAndFetch(PriceEntry.class); " join prices on t2.item=prices.item and t2.time=prices.time" +
return entries; " join items t3 on t2.item=t3.id");
return query.executeAndFetch(PriceEntry.class);
} }
} }

View File

@@ -28,9 +28,10 @@ import java.time.Instant;
import lombok.Data; import lombok.Data;
@Data @Data
public class PriceEntry class PriceEntry
{ {
private int item; private int item;
private String name;
private int price; private int price;
private Instant time; private Instant time;
private Instant fetched_time; private Instant fetched_time;

View File

@@ -32,7 +32,9 @@ import com.google.common.eventbus.Subscribe;
import java.awt.Color; import java.awt.Color;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@@ -51,7 +53,6 @@ import net.runelite.api.events.GameStateChanged;
import net.runelite.client.callback.ClientThread; import net.runelite.client.callback.ClientThread;
import net.runelite.http.api.item.ItemClient; import net.runelite.http.api.item.ItemClient;
import net.runelite.http.api.item.ItemPrice; import net.runelite.http.api.item.ItemPrice;
import net.runelite.http.api.item.SearchResult;
@Singleton @Singleton
@Slf4j @Slf4j
@@ -78,7 +79,6 @@ public class ItemManager
private final ClientThread clientThread; private final ClientThread clientThread;
private final ItemClient itemClient = new ItemClient(); private final ItemClient itemClient = new ItemClient();
private final LoadingCache<String, SearchResult> itemSearches;
private Map<Integer, ItemPrice> itemPrices = Collections.emptyMap(); private Map<Integer, ItemPrice> itemPrices = Collections.emptyMap();
private final LoadingCache<ImageKey, AsyncBufferedImage> itemImages; private final LoadingCache<ImageKey, AsyncBufferedImage> itemImages;
private final LoadingCache<Integer, ItemComposition> itemCompositions; private final LoadingCache<Integer, ItemComposition> itemCompositions;
@@ -93,18 +93,6 @@ public class ItemManager
scheduledExecutorService.scheduleWithFixedDelay(this::loadPrices, 0, 30, TimeUnit.MINUTES); scheduledExecutorService.scheduleWithFixedDelay(this::loadPrices, 0, 30, TimeUnit.MINUTES);
itemSearches = CacheBuilder.newBuilder()
.maximumSize(512L)
.expireAfterAccess(1, TimeUnit.HOURS)
.build(new CacheLoader<String, SearchResult>()
{
@Override
public SearchResult load(String key) throws Exception
{
return itemClient.search(key);
}
});
itemImages = CacheBuilder.newBuilder() itemImages = CacheBuilder.newBuilder()
.maximumSize(128L) .maximumSize(128L)
.expireAfterAccess(1, TimeUnit.HOURS) .expireAfterAccess(1, TimeUnit.HOURS)
@@ -152,7 +140,7 @@ public class ItemManager
ImmutableMap.Builder<Integer, ItemPrice> map = ImmutableMap.builderWithExpectedSize(prices.length); ImmutableMap.Builder<Integer, ItemPrice> map = ImmutableMap.builderWithExpectedSize(prices.length);
for (ItemPrice price : prices) for (ItemPrice price : prices)
{ {
map.put(price.getItem().getId(), price); map.put(price.getId(), price);
} }
itemPrices = map.build(); 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 * @param itemName item name
* @return item search result * @return
* @throws java.util.concurrent.ExecutionException exception when item
* is not found
*/ */
public SearchResult searchForItem(String itemName) throws ExecutionException public List<ItemPrice> search(String itemName)
{ {
return itemSearches.get(itemName); itemName = itemName.toLowerCase();
List<ItemPrice> result = new ArrayList<>();
for (ItemPrice itemPrice : itemPrices.values())
{
final String name = itemPrice.getName();
if (name.toLowerCase().contains(itemName))
{
result.add(itemPrice);
}
}
return result;
} }
/** /**

View File

@@ -29,7 +29,6 @@ import com.google.common.eventbus.Subscribe;
import com.google.inject.Provides; import com.google.inject.Provides;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; 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.HiscoreSkill;
import net.runelite.http.api.hiscore.SingleHiscoreSkillResult; import net.runelite.http.api.hiscore.SingleHiscoreSkillResult;
import net.runelite.http.api.hiscore.Skill; import net.runelite.http.api.hiscore.Skill;
import net.runelite.http.api.item.Item; import net.runelite.http.api.item.ItemPrice;
import net.runelite.http.api.item.SearchResult;
import net.runelite.http.api.kc.KillCountClient; import net.runelite.http.api.kc.KillCountClient;
@PluginDescriptor( @PluginDescriptor(
@@ -205,7 +203,7 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
String search = message.substring(PRICE_COMMAND_STRING.length() + 1); String search = message.substring(PRICE_COMMAND_STRING.length() + 1);
log.debug("Running price lookup for {}", search); 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 + " ")) 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) private void itemPriceLookup(MessageNode messageNode, String search)
{ {
SearchResult result; List<ItemPrice> results = itemManager.search(search);
try if (!results.isEmpty())
{ {
result = itemManager.searchForItem(search); ItemPrice item = retrieveFromList(results, 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;
}
int itemId = item.getId(); int itemId = item.getId();
int itemPrice = itemManager.getItemPrice(itemId); int itemPrice = item.getPrice();
final ChatMessageBuilder builder = new ChatMessageBuilder() final ChatMessageBuilder builder = new ChatMessageBuilder()
.append(ChatColorType.NORMAL) .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. * 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 * Returns the item if its name is equal to the original input or the
* if it can't find the item. * shortest match if no exact match is found.
* *
* @param items List of items. * @param items List of items.
* @param originalInput String with the original input. * @param originalInput String with the original input.
* @return Item which has a name equal to the original input. * @return Item which has a name equal to the original input.
*/ */
private Item retrieveFromList(List<Item> items, String originalInput) private ItemPrice retrieveFromList(List<ItemPrice> items, String originalInput)
{ {
for (Item item : items) ItemPrice shortest = null;
for (ItemPrice item : items)
{ {
if (item.getName().toLowerCase().equals(originalInput.toLowerCase())) if (item.getName().toLowerCase().equals(originalInput.toLowerCase()))
{ {
return item; return item;
} }
if (shortest == null || item.getName().length() < shortest.getName().length())
{
shortest = item;
}
} }
return null;
// Take a guess
return shortest;
} }
/** /**

View File

@@ -50,8 +50,7 @@ import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.components.IconTextField; import net.runelite.client.ui.components.IconTextField;
import net.runelite.client.ui.components.PluginErrorPanel; import net.runelite.client.ui.components.PluginErrorPanel;
import net.runelite.client.util.RunnableExceptionLogger; import net.runelite.client.util.RunnableExceptionLogger;
import net.runelite.http.api.item.Item; import net.runelite.http.api.item.ItemPrice;
import net.runelite.http.api.item.SearchResult;
/** /**
* This panel holds the search section of the Grand Exchange Plugin. * This panel holds the search section of the Grand Exchange Plugin.
@@ -169,19 +168,13 @@ class GrandExchangeSearchPanel extends JPanel
searchBar.setEditable(false); searchBar.setEditable(false);
searchBar.setIcon(IconTextField.Icon.LOADING); searchBar.setIcon(IconTextField.Icon.LOADING);
SearchResult result; List<ItemPrice> result = itemManager.search(lookup);
if (result.isEmpty())
try
{ {
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.setIcon(IconTextField.Icon.ERROR);
searchBar.setEditable(true); errorPanel.setContent("No results found.", "No items were found with that name, please try again.");
errorPanel.setContent("Error fetching results", "An error occurred while trying to fetch item data, please try again later.");
cardLayout.show(centerPanel, ERROR_PANEL); cardLayout.show(centerPanel, ERROR_PANEL);
searchBar.setEditable(true);
return; return;
} }
@@ -189,42 +182,33 @@ class GrandExchangeSearchPanel extends JPanel
clientThread.invokeLater(() -> processResult(result, lookup, exactMatch)); clientThread.invokeLater(() -> processResult(result, lookup, exactMatch));
} }
private void processResult(SearchResult result, String lookup, boolean exactMatch) private void processResult(List<ItemPrice> result, String lookup, boolean exactMatch)
{ {
itemsList.clear(); 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(); continue;
}
ItemComposition itemComp = itemManager.getItemComposition(itemId);
if (itemComp == null) int itemPrice = item.getPrice();
{ int itemLimit = itemGELimits.getOrDefault(itemId, 0);
continue; AsyncBufferedImage itemImage = itemManager.getImage(itemId);
}
itemsList.add(new GrandExchangeItems(itemImage, item.getName(), itemId, itemPrice, itemComp.getPrice() * 0.6, itemLimit));
int itemPrice = itemManager.getItemPrice(itemId);
int itemLimit = itemGELimits.getOrDefault(itemId, 0); // If using hotkey to lookup item, stop after finding match.
AsyncBufferedImage itemImage = itemManager.getImage(itemId); if (exactMatch && item.getName().equalsIgnoreCase(lookup))
{
itemsList.add(new GrandExchangeItems(itemImage, item.getName(), itemId, itemPrice, itemComp.getPrice() * 0.6, itemLimit)); break;
// 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(() -> SwingUtilities.invokeLater(() ->