http-service: crawl tradable item prices instead of queueing on demand

This commit is contained in:
Adam
2018-06-09 21:27:11 -04:00
parent a1ae397e11
commit 7f25ec8238
5 changed files with 68 additions and 45 deletions

View File

@@ -43,7 +43,10 @@ import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.runelite.cache.ConfigType;
import net.runelite.cache.IndexType; import net.runelite.cache.IndexType;
import net.runelite.cache.definitions.ItemDefinition;
import net.runelite.cache.definitions.loaders.ItemLoader;
import net.runelite.cache.fs.ArchiveFiles; import net.runelite.cache.fs.ArchiveFiles;
import net.runelite.cache.fs.Container; import net.runelite.cache.fs.Container;
import net.runelite.cache.fs.FSFile; import net.runelite.cache.fs.FSFile;
@@ -226,4 +229,20 @@ public class CacheService
return cacheDao.findArchiveByName(con, cache, index, nameHash); return cacheDao.findArchiveByName(con, cache, index, nameHash);
} }
} }
public List<ItemDefinition> getItems() throws IOException
{
CacheEntry cache = findMostRecent();
IndexEntry indexEntry = findIndexForCache(cache, IndexType.CONFIGS.getNumber());
ArchiveEntry archiveEntry = findArchiveForIndex(indexEntry, ConfigType.ITEM.getId());
ArchiveFiles archiveFiles = getArchiveFiles(archiveEntry);
final ItemLoader itemLoader = new ItemLoader();
final List<ItemDefinition> result = new ArrayList<>(archiveFiles.getFiles().size());
for (FSFile file : archiveFiles.getFiles())
{
ItemDefinition itemDef = itemLoader.load(file.getFileId(), file.getContents());
result.add(itemDef);
}
return result;
}
} }

View File

@@ -168,20 +168,12 @@ public class ItemController
else if (priceEntry == null) else if (priceEntry == null)
{ {
// Price is unknown // Price is unknown
itemService.queuePriceLookup(itemId); // queue lookup
cachedEmpty.put(itemId, itemId); cachedEmpty.put(itemId, itemId);
return ResponseEntity.notFound() return ResponseEntity.notFound()
.header(RUNELITE_CACHE, "MISS") .header(RUNELITE_CACHE, "MISS")
.build(); .build();
} }
Instant cacheTime = now.minus(CACHE_DUATION);
if (priceEntry.getFetched_time().isBefore(cacheTime))
{
// Queue a check for the price
itemService.queuePriceLookup(itemId);
}
ItemPrice itemPrice = new ItemPrice(); ItemPrice itemPrice = new ItemPrice();
itemPrice.setItem(item.toItem()); itemPrice.setItem(item.toItem());
itemPrice.setPrice(priceEntry.getPrice()); itemPrice.setPrice(priceEntry.getPrice());

View File

@@ -33,11 +33,14 @@ import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.runelite.cache.definitions.ItemDefinition;
import net.runelite.http.api.RuneLiteAPI; import net.runelite.http.api.RuneLiteAPI;
import net.runelite.http.api.item.ItemType; import net.runelite.http.api.item.ItemType;
import net.runelite.http.service.cache.CacheService;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
@@ -48,7 +51,6 @@ import org.springframework.stereotype.Service;
import org.sql2o.Connection; import org.sql2o.Connection;
import org.sql2o.Query; import org.sql2o.Query;
import org.sql2o.Sql2o; import org.sql2o.Sql2o;
import org.sql2o.Sql2oException;
@Service @Service
@Slf4j @Slf4j
@@ -80,18 +82,21 @@ public class ItemService
+ " KEY `item_fetched_time` (`item`,`fetched_time`)\n" + " KEY `item_fetched_time` (`item`,`fetched_time`)\n"
+ ") ENGINE=InnoDB"; + ") ENGINE=InnoDB";
private static final String CREATE_PRICES_FK = "ALTER TABLE `prices`\n"
+ " ADD CONSTRAINT `item` FOREIGN KEY (`item`) REFERENCES `items` (`id`);";
private static final int MAX_PENDING = 512; private static final int MAX_PENDING = 512;
private final Sql2o sql2o; private final Sql2o sql2o;
private final CacheService cacheService;
private final ConcurrentLinkedQueue<PendingLookup> pendingLookups = new ConcurrentLinkedQueue<PendingLookup>(); private final ConcurrentLinkedQueue<PendingLookup> pendingLookups = new ConcurrentLinkedQueue<PendingLookup>();
private int[] tradeableItems;
private final Random random = new Random();
@Autowired @Autowired
public ItemService(@Qualifier("Runelite SQL2O") Sql2o sql2o) public ItemService(@Qualifier("Runelite SQL2O") Sql2o sql2o,
CacheService cacheService)
{ {
this.sql2o = sql2o; this.sql2o = sql2o;
this.cacheService = cacheService;
try (Connection con = sql2o.open()) try (Connection con = sql2o.open())
{ {
@@ -100,16 +105,6 @@ public class ItemService
con.createQuery(CREATE_PRICES) con.createQuery(CREATE_PRICES)
.executeUpdate(); .executeUpdate();
try
{
con.createQuery(CREATE_PRICES_FK)
.executeUpdate();
}
catch (Sql2oException ex)
{
// Ignore, happens when index already exists
}
} }
} }
@@ -248,9 +243,19 @@ public class ItemService
public List<PriceEntry> fetchPrice(int itemId) public List<PriceEntry> fetchPrice(int itemId)
{ {
RSPrices rsprice;
try
{
rsprice = fetchRSPrices(itemId);
}
catch (IOException ex)
{
log.warn("unable to fetch price for item {}", itemId, ex);
return null;
}
try (Connection con = sql2o.beginTransaction()) try (Connection con = sql2o.beginTransaction())
{ {
RSPrices rsprice = fetchRSPrices(itemId);
List<PriceEntry> entries = new ArrayList<>(); List<PriceEntry> entries = new ArrayList<>();
Instant now = Instant.now(); Instant now = Instant.now();
@@ -284,11 +289,6 @@ public class ItemService
return entries; return entries;
} }
catch (IOException ex)
{
log.warn("unable to fetch price for item {}", itemId, ex);
return null;
}
} }
public List<PriceEntry> fetchPrices() public List<PriceEntry> fetchPrices()
@@ -407,18 +407,6 @@ public class ItemService
} }
} }
public void queuePriceLookup(int itemId)
{
if (pendingLookups.size() < MAX_PENDING)
{
pendingLookups.add(new PendingLookup(itemId, PendingLookup.Type.PRICE));
}
else
{
log.debug("Dropping pending price lookup for {}", itemId);
}
}
public void queueSearch(String search) public void queueSearch(String search)
{ {
if (pendingLookups.size() < MAX_PENDING) if (pendingLookups.size() < MAX_PENDING)
@@ -454,9 +442,6 @@ public class ItemService
switch (pendingLookup.getType()) switch (pendingLookup.getType())
{ {
case PRICE:
fetchPrice(pendingLookup.getItemId());
break;
case SEARCH: case SEARCH:
try try
{ {
@@ -475,4 +460,31 @@ public class ItemService
} }
} }
@Scheduled(fixedDelay = 20_000)
public void crawlPrices()
{
if (tradeableItems == null || tradeableItems.length == 0)
{
return;
}
int idx = random.nextInt(tradeableItems.length);
int id = tradeableItems[idx];
log.debug("Fetching price for {}", id);
fetchPrice(id);
}
@Scheduled(fixedDelay = 1_8000_000) // 30 minutes
public void reloadItems() throws IOException
{
List<ItemDefinition> items = cacheService.getItems();
tradeableItems = items.stream()
.filter(item -> item.isTradeable)
.mapToInt(item -> item.id)
.toArray();
log.debug("Loaded {} tradeable items", tradeableItems.length);
}
} }

View File

@@ -31,7 +31,6 @@ class PendingLookup
{ {
enum Type enum Type
{ {
PRICE,
SEARCH, SEARCH,
ITEM; ITEM;
} }

View File

@@ -7,3 +7,4 @@ minio.bucket=runelite
runelite.twitter.consumerkey=moo runelite.twitter.consumerkey=moo
runelite.twitter.secretkey=cow runelite.twitter.secretkey=cow
runelite.twitter.listid=968949795153948673 runelite.twitter.listid=968949795153948673
logging.level.net.runelite=DEBUG