initial commit
This commit is contained in:
675
LibTSM/Service/AuctionScanClasses/Query.lua
Normal file
675
LibTSM/Service/AuctionScanClasses/Query.lua
Normal file
@@ -0,0 +1,675 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- AuctionQuery Class.
|
||||
-- A class which is used to build a query to scan the auciton house.
|
||||
-- @classmod AuctionQuery
|
||||
|
||||
local _, TSM = ...
|
||||
local Query = TSM.Init("Service.AuctionScanClasses.Query")
|
||||
local String = TSM.Include("Util.String")
|
||||
local ObjectPool = TSM.Include("Util.ObjectPool")
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local AuctionHouseWrapper = TSM.Include("Service.AuctionHouseWrapper")
|
||||
local Scanner = TSM.Include("Service.AuctionScanClasses.Scanner")
|
||||
local LibTSMClass = TSM.Include("LibTSMClass")
|
||||
local AuctionQuery = LibTSMClass.DefineClass("AuctionQuery")
|
||||
local private = {
|
||||
objectPool = ObjectPool.New("AUCTION_SCAN_QUERY", AuctionQuery),
|
||||
}
|
||||
local ITEM_SPECIFIC = newproxy()
|
||||
local ITEM_BASE = newproxy()
|
||||
local DEFAULT_SORTS = TSM.IsWowClassic() and
|
||||
{ -- classic
|
||||
"seller",
|
||||
"quantity",
|
||||
"unitprice",
|
||||
} or
|
||||
{ -- retail
|
||||
{ sortOrder = Enum.AuctionHouseSortOrder.Price, reverseSort = false },
|
||||
{ sortOrder = Enum.AuctionHouseSortOrder.Name, reverseSort = false },
|
||||
}
|
||||
local EMPTY_SORTS = {}
|
||||
local INV_TYPES = {
|
||||
CHEST = TSM.IsShadowlands() and Enum.InventoryType.IndexChestType or LE_INVENTORY_TYPE_CHEST_TYPE,
|
||||
ROBE = TSM.IsShadowlands() and Enum.InventoryType.IndexRobeType or LE_INVENTORY_TYPE_ROBE_TYPE,
|
||||
NECK = TSM.IsShadowlands() and Enum.InventoryType.IndexNeckType or LE_INVENTORY_TYPE_NECK_TYPE,
|
||||
FINGER = TSM.IsShadowlands() and Enum.InventoryType.IndexFingerType or LE_INVENTORY_TYPE_FINGER_TYPE,
|
||||
TRINKET = TSM.IsShadowlands() and Enum.InventoryType.IndexTrinketType or LE_INVENTORY_TYPE_TRINKET_TYPE,
|
||||
HOLDABLE = TSM.IsShadowlands() and Enum.InventoryType.IndexHoldableType or LE_INVENTORY_TYPE_HOLDABLE_TYPE,
|
||||
BODY = TSM.IsShadowlands() and Enum.InventoryType.IndexBodyType or LE_INVENTORY_TYPE_BODY_TYPE,
|
||||
CLOAK = TSM.IsShadowlands() and Enum.InventoryType.IndexCloakType or LE_INVENTORY_TYPE_CLOAK_TYPE,
|
||||
}
|
||||
assert(Table.Count(INV_TYPES) == 8)
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Query.Get()
|
||||
return private.objectPool:Get()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Class Meta Methods
|
||||
-- ============================================================================
|
||||
|
||||
function AuctionQuery.__init(self)
|
||||
self._str = ""
|
||||
self._strLower = ""
|
||||
self._strMatch = ""
|
||||
self._exact = false
|
||||
self._minQuality = -math.huge
|
||||
self._maxQuality = math.huge
|
||||
self._minLevel = -math.huge
|
||||
self._maxLevel = math.huge
|
||||
self._minItemLevel = -math.huge
|
||||
self._maxItemLevel = math.huge
|
||||
self._class = nil
|
||||
self._subClass = nil
|
||||
self._invType = nil
|
||||
self._classFilter1 = {}
|
||||
self._classFilter2 = {}
|
||||
self._usable = false
|
||||
self._uncollected = false
|
||||
self._upgrades = false
|
||||
self._unlearned = false
|
||||
self._canLearn = false
|
||||
self._minPrice = 0
|
||||
self._maxPrice = math.huge
|
||||
self._items = {}
|
||||
self._customFilters = {}
|
||||
self._isBrowseDoneFunc = nil
|
||||
self._specifiedPage = nil
|
||||
self._getAll = nil
|
||||
self._resolveSellers = false
|
||||
self._callback = nil
|
||||
self._queryTemp = {}
|
||||
self._filtersTemp = {}
|
||||
self._classFiltersTemp = {}
|
||||
self._browseResults = {}
|
||||
self._page = 0
|
||||
end
|
||||
|
||||
function AuctionQuery.Release(self)
|
||||
self._str = ""
|
||||
self._strLower = ""
|
||||
self._strMatch = ""
|
||||
self._exact = false
|
||||
self._minQuality = -math.huge
|
||||
self._maxQuality = math.huge
|
||||
self._minLevel = -math.huge
|
||||
self._maxLevel = math.huge
|
||||
self._minItemLevel = -math.huge
|
||||
self._maxItemLevel = math.huge
|
||||
self._class = nil
|
||||
self._subClass = nil
|
||||
self._invType = nil
|
||||
wipe(self._classFilter1)
|
||||
wipe(self._classFilter2)
|
||||
self._usable = false
|
||||
self._uncollected = false
|
||||
self._upgrades = false
|
||||
self._unlearned = false
|
||||
self._canLearn = false
|
||||
self._minPrice = 0
|
||||
self._maxPrice = math.huge
|
||||
wipe(self._items)
|
||||
wipe(self._customFilters)
|
||||
self._isBrowseDoneFunc = nil
|
||||
self._specifiedPage = nil
|
||||
self._getAll = nil
|
||||
self._resolveSellers = false
|
||||
self._callback = nil
|
||||
wipe(self._queryTemp)
|
||||
wipe(self._filtersTemp)
|
||||
wipe(self._classFiltersTemp)
|
||||
for _, row in pairs(self._browseResults) do
|
||||
row:Release()
|
||||
end
|
||||
wipe(self._browseResults)
|
||||
self._page = 0
|
||||
private.objectPool:Recycle(self)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function AuctionQuery.SetStr(self, str, exact)
|
||||
self._str = str or ""
|
||||
self._strLower = strlower(self._str)
|
||||
self._strMatch = String.Escape(self._strLower)
|
||||
self._exact = exact or false
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.SetQualityRange(self, minQuality, maxQuality)
|
||||
self._minQuality = minQuality or -math.huge
|
||||
self._maxQuality = maxQuality or math.huge
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.SetLevelRange(self, minLevel, maxLevel)
|
||||
self._minLevel = minLevel or -math.huge
|
||||
self._maxLevel = maxLevel or math.huge
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.SetItemLevelRange(self, minItemLevel, maxItemLevel)
|
||||
self._minItemLevel = minItemLevel or -math.huge
|
||||
self._maxItemLevel = maxItemLevel or math.huge
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.SetClass(self, class, subClass, invType)
|
||||
self._class = class or nil
|
||||
self._subClass = subClass or nil
|
||||
self._invType = invType or nil
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.SetUsable(self, usable)
|
||||
self._usable = usable or false
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.SetUncollected(self, uncollected)
|
||||
self._uncollected = uncollected or false
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.SetUpgrades(self, upgrades)
|
||||
self._upgrades = upgrades or false
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.SetUnlearned(self, unlearned)
|
||||
self._unlearned = unlearned or false
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.SetCanLearn(self, canLearn)
|
||||
self._canLearn = canLearn or false
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.SetPriceRange(self, minPrice, maxPrice)
|
||||
self._minPrice = minPrice or 0
|
||||
self._maxPrice = maxPrice or math.huge
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.SetItems(self, items)
|
||||
wipe(self._items)
|
||||
if type(items) == "table" then
|
||||
for _, itemString in ipairs(items) do
|
||||
local baseItemString = ItemString.GetBaseFast(itemString)
|
||||
self._items[itemString] = ITEM_SPECIFIC
|
||||
if baseItemString ~= itemString then
|
||||
self._items[baseItemString] = self._items[baseItemString] or ITEM_BASE
|
||||
end
|
||||
end
|
||||
elseif type(items) == "string" then
|
||||
local itemString = items
|
||||
local baseItemString = ItemString.GetBaseFast(itemString)
|
||||
self._items[itemString] = ITEM_SPECIFIC
|
||||
if baseItemString ~= itemString then
|
||||
self._items[baseItemString] = self._items[baseItemString] or ITEM_BASE
|
||||
end
|
||||
elseif items ~= nil then
|
||||
error("Invalid items type: "..tostring(items))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.AddCustomFilter(self, func)
|
||||
self._customFilters[func] = true
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.SetIsBrowseDoneFunction(self, func)
|
||||
self._isBrowseDoneFunc = func
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.SetPage(self, page)
|
||||
if page == nil then
|
||||
self._specifiedPage = nil
|
||||
elseif type(page) == "number" or page == "FIRST" or page == "LAST" then
|
||||
assert(TSM.IsWowClassic())
|
||||
self._specifiedPage = page
|
||||
else
|
||||
error("Invalid page: "..tostring(page))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.SetGetAll(self, getAll)
|
||||
-- only currently support GetAll on classic
|
||||
assert(not getAll or TSM.IsWowClassic())
|
||||
self._getAll = getAll
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.SetResolveSellers(self, resolveSellers)
|
||||
self._resolveSellers = resolveSellers
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.SetCallback(self, callback)
|
||||
self._callback = callback
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionQuery.Browse(self, forceNoScan)
|
||||
assert(not TSM.IsWowClassic() or not forceNoScan)
|
||||
|
||||
local noScan = forceNoScan or false
|
||||
if not TSM.IsWowClassic() then
|
||||
local numItems = 0
|
||||
for _, itemType in pairs(self._items) do
|
||||
if itemType == ITEM_SPECIFIC then
|
||||
numItems = numItems + 1
|
||||
end
|
||||
end
|
||||
if numItems > 0 and numItems < 500 then
|
||||
-- it's faster to just issue individual item searches instead of a browse query
|
||||
noScan = true
|
||||
end
|
||||
end
|
||||
|
||||
if noScan then
|
||||
assert(not TSM.IsWowClassic())
|
||||
local itemKeys = TempTable.Acquire()
|
||||
for itemString in pairs(self._items) do
|
||||
if itemString == ItemString.GetBaseFast(itemString) then
|
||||
local itemId, battlePetSpeciesId = nil, nil
|
||||
if ItemString.IsPet(itemString) then
|
||||
itemId = ItemString.ToId(ItemString.GetPetCage())
|
||||
battlePetSpeciesId = ItemString.ToId(itemString)
|
||||
else
|
||||
itemId = ItemString.ToId(itemString)
|
||||
battlePetSpeciesId = 0
|
||||
end
|
||||
local itemKey = C_AuctionHouse.MakeItemKey(itemId, 0, 0, battlePetSpeciesId)
|
||||
-- FIX for 9.0.1 bug where MakeItemKey randomly adds an itemLevel which breaks scanning
|
||||
itemKey.itemLevel = 0
|
||||
tinsert(itemKeys, itemKey)
|
||||
end
|
||||
end
|
||||
local future = Scanner.BrowseNoScan(self, itemKeys, self._browseResults, self._callback)
|
||||
TempTable.Release(itemKeys)
|
||||
return future
|
||||
else
|
||||
self._page = 0
|
||||
return Scanner.Browse(self, self._resolveSellers, self._browseResults, self._callback)
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionQuery.GetSearchProgress(self)
|
||||
if TSM.IsWowClassic() then
|
||||
return 1
|
||||
end
|
||||
local progress, totalNum = 0, 0
|
||||
for _, row in pairs(self._browseResults) do
|
||||
progress = progress + row:_GetSearchProgress()
|
||||
totalNum = totalNum + 1
|
||||
end
|
||||
if totalNum == 0 then
|
||||
return 0
|
||||
end
|
||||
return progress / totalNum
|
||||
end
|
||||
|
||||
function AuctionQuery.GetBrowseResults(self, baseItemString)
|
||||
return self._browseResults[baseItemString]
|
||||
end
|
||||
|
||||
function AuctionQuery.ItemSubRowIterator(self, itemString)
|
||||
local result = TempTable.Acquire()
|
||||
local baseItemString = ItemString.GetBaseFast(itemString)
|
||||
local isBaseItemString = itemString == baseItemString
|
||||
local row = self._browseResults[baseItemString]
|
||||
if row then
|
||||
for _, subRow in row:SubRowIterator() do
|
||||
local subRowBaseItemString = subRow:GetBaseItemString()
|
||||
local subRowItemString = subRow:GetItemString()
|
||||
if (isBaseItemString and subRowBaseItemString == itemString) or (not isBaseItemString and subRowItemString == itemString) then
|
||||
tinsert(result, subRow)
|
||||
end
|
||||
end
|
||||
end
|
||||
return TempTable.Iterator(result)
|
||||
end
|
||||
|
||||
function AuctionQuery.GetCheapestSubRow(self, itemString)
|
||||
assert(not TSM.IsWowClassic())
|
||||
local cheapest, cheapestItemBuyout = nil, nil
|
||||
for _, subRow in self:ItemSubRowIterator(itemString) do
|
||||
local quantity = subRow:GetQuantities()
|
||||
local _, numOwnerItems = subRow:GetOwnerInfo()
|
||||
local _, itemBuyout = subRow:GetBuyouts()
|
||||
if numOwnerItems ~= quantity and itemBuyout < (cheapestItemBuyout or math.huge) then
|
||||
cheapest = subRow
|
||||
cheapestItemBuyout = itemBuyout
|
||||
end
|
||||
end
|
||||
return cheapest
|
||||
end
|
||||
|
||||
function AuctionQuery.BrowseResultsIterator(self)
|
||||
return pairs(self._browseResults)
|
||||
end
|
||||
|
||||
function AuctionQuery.RemoveResultRow(self, row)
|
||||
local baseItemString = row:GetBaseItemString()
|
||||
assert(baseItemString and self._browseResults[baseItemString])
|
||||
self._browseResults[baseItemString] = nil
|
||||
row:Release()
|
||||
if self._callback then
|
||||
self._callback(self)
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionQuery.Search(self, row, useCachedData)
|
||||
assert(not TSM.IsWowClassic())
|
||||
assert(self._browseResults)
|
||||
return Scanner.Search(self, self._resolveSellers, useCachedData, row, self._callback)
|
||||
end
|
||||
|
||||
function AuctionQuery.CancelBrowseOrSearch(self)
|
||||
Scanner.Cancel()
|
||||
end
|
||||
|
||||
function AuctionQuery.ItemIterator(self)
|
||||
return private.ItemIteratorHelper, self._items, nil
|
||||
end
|
||||
|
||||
function AuctionQuery.WipeBrowseResults(self)
|
||||
for _, row in pairs(self._browseResults) do
|
||||
row:Release()
|
||||
end
|
||||
wipe(self._browseResults)
|
||||
if self._callback then
|
||||
self._callback(self)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function AuctionQuery._SetSort(self)
|
||||
if not TSM.IsWowClassic() then
|
||||
return true
|
||||
end
|
||||
|
||||
local sorts = (type(self._specifiedPage) == "string" or self._getAll) and EMPTY_SORTS or DEFAULT_SORTS
|
||||
|
||||
if GetAuctionSort("list", #sorts + 1) == nil then
|
||||
local properlySorted = true
|
||||
for i, col in ipairs(sorts) do
|
||||
local sortCol, sortReversed = GetAuctionSort("list", #sorts - i + 1)
|
||||
-- we never care to reverse a sort so if it's reversed then it's not properly sorted
|
||||
if sortCol ~= col or sortReversed then
|
||||
properlySorted = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if properlySorted then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
SortAuctionClearSort("list")
|
||||
for _, col in ipairs(sorts) do
|
||||
SortAuctionSetSort("list", col, false)
|
||||
end
|
||||
SortAuctionApplySort("list")
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function AuctionQuery._SendWowQuery(self)
|
||||
-- build the class filters
|
||||
wipe(self._classFiltersTemp)
|
||||
wipe(self._classFilter1)
|
||||
wipe(self._classFilter2)
|
||||
if self._invType == INV_TYPES.CHEST or self._invType == INV_TYPES.ROBE then
|
||||
-- default AH only sends in queries for robe chest type, we need to mimic this when using a chest filter
|
||||
self._classFilter1.classID = LE_ITEM_CLASS_ARMOR
|
||||
self._classFilter1.subClassID = self._subClass
|
||||
self._classFilter1.inventoryType = INV_TYPES.CHEST
|
||||
tinsert(self._classFiltersTemp, self._classFilter1)
|
||||
self._classFilter2.classID = LE_ITEM_CLASS_ARMOR
|
||||
self._classFilter2.subClassID = self._subClass
|
||||
self._classFilter2.inventoryType = INV_TYPES.ROBE
|
||||
tinsert(self._classFiltersTemp, self._classFilter2)
|
||||
elseif self._invType == INV_TYPES.NECK or self._invType == INV_TYPES.FINGER or self._invType == INV_TYPES.TRINKET or self._invType == INV_TYPES.HOLDABLE or self._invType == INV_TYPES.BODY then
|
||||
self._classFilter1.classID = LE_ITEM_CLASS_ARMOR
|
||||
self._classFilter1.subClassID = LE_ITEM_ARMOR_GENERIC
|
||||
self._classFilter1.inventoryType = self._invType
|
||||
tinsert(self._classFiltersTemp, self._classFilter1)
|
||||
elseif self._invType == INV_TYPES.CLOAK then
|
||||
self._classFilter1.classID = LE_ITEM_CLASS_ARMOR
|
||||
self._classFilter1.subClassID = LE_ITEM_ARMOR_CLOTH
|
||||
self._classFilter1.inventoryType = self._invType
|
||||
tinsert(self._classFiltersTemp, self._classFilter1)
|
||||
elseif self._class then
|
||||
self._classFilter1.classID = self._class
|
||||
self._classFilter1.subClassID = self._subClass
|
||||
self._classFilter1.inventoryType = self._invType
|
||||
tinsert(self._classFiltersTemp, self._classFilter1)
|
||||
end
|
||||
|
||||
-- build the query
|
||||
local minLevel = self._minLevel ~= -math.huge and self._minLevel or nil
|
||||
local maxLevel = self._maxLevel ~= math.huge and self._maxLevel or nil
|
||||
if TSM.IsWowClassic() then
|
||||
if self._specifiedPage == "LAST" then
|
||||
self._page = max(ceil(select(2, GetNumAuctionItems("list")) / NUM_AUCTION_ITEMS_PER_PAGE) - 1, 0)
|
||||
elseif self._specifiedPage == "FIRST" then
|
||||
self._page = 0
|
||||
elseif self._specifiedPage then
|
||||
self._page = self._specifiedPage
|
||||
end
|
||||
local minQuality = self._minQuality == -math.huge and 0 or self._minQuality
|
||||
return AuctionHouseWrapper.QueryAuctionItems(self._str, minLevel, maxLevel, self._page, self._usable, minQuality, self._getAll, self._exact, self._classFiltersTemp)
|
||||
else
|
||||
wipe(self._filtersTemp)
|
||||
if self._uncollected then
|
||||
tinsert(self._filtersTemp, Enum.AuctionHouseFilter.UncollectedOnly)
|
||||
end
|
||||
if self._usable then
|
||||
tinsert(self._filtersTemp, Enum.AuctionHouseFilter.UsableOnly)
|
||||
end
|
||||
if self._upgrades then
|
||||
tinsert(self._filtersTemp, Enum.AuctionHouseFilter.UpgradesOnly)
|
||||
end
|
||||
if self._exact then
|
||||
tinsert(self._filtersTemp, Enum.AuctionHouseFilter.ExactMatch)
|
||||
end
|
||||
local minQuality = self._minQuality == -math.huge and 0 or self._minQuality
|
||||
for i = minQuality + Enum.AuctionHouseFilter.PoorQuality, min(self._maxQuality + Enum.AuctionHouseFilter.PoorQuality, Enum.AuctionHouseFilter.ArtifactQuality) do
|
||||
tinsert(self._filtersTemp, i)
|
||||
end
|
||||
wipe(self._queryTemp)
|
||||
self._queryTemp.searchString = self._str
|
||||
self._queryTemp.minLevel = minLevel
|
||||
self._queryTemp.maxLevel = maxLevel
|
||||
self._queryTemp.sorts = DEFAULT_SORTS
|
||||
self._queryTemp.filters = self._filtersTemp
|
||||
self._queryTemp.itemClassFilters = self._classFiltersTemp
|
||||
return AuctionHouseWrapper.SendBrowseQuery(self._queryTemp)
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionQuery._IsFiltered(self, row, isSubRow, itemKey)
|
||||
local baseItemString = row:GetBaseItemString()
|
||||
local itemString = row:GetItemString()
|
||||
assert(baseItemString)
|
||||
local name, quality, itemLevel, maxItemLevel = row:GetItemInfo(itemKey)
|
||||
local _, itemBuyout, minItemBuyout = row:GetBuyouts(itemKey)
|
||||
if row:IsSubRow() and itemBuyout == 0 then
|
||||
_, itemBuyout = row:GetBidInfo()
|
||||
end
|
||||
|
||||
if next(self._items) then
|
||||
if not self._items[baseItemString] then
|
||||
return true
|
||||
end
|
||||
if isSubRow and itemString and self._items[itemString] ~= ITEM_SPECIFIC and self._items[baseItemString] ~= ITEM_SPECIFIC then
|
||||
return true
|
||||
elseif not isSubRow and itemString and not self._items[itemString] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
if self._str ~= "" and name then
|
||||
name = strlower(name)
|
||||
if not strmatch(name, self._strMatch) or (self._exact and name ~= self._strLower) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
if self._minLevel ~= -math.huge or self._maxLevel ~= math.huge then
|
||||
local minLevel = TSM.IsShadowlands() and ItemString.IsPet(baseItemString) and (itemLevel or maxItemLevel) or ItemInfo.GetMinLevel(baseItemString)
|
||||
if minLevel < self._minLevel or minLevel > self._maxLevel then
|
||||
return true
|
||||
end
|
||||
end
|
||||
if itemLevel and (itemLevel < self._minItemLevel or itemLevel > self._maxItemLevel) then
|
||||
return true
|
||||
end
|
||||
if maxItemLevel and maxItemLevel < self._minItemLevel then
|
||||
return true
|
||||
end
|
||||
if quality and (quality < self._minQuality or quality > self._maxQuality) then
|
||||
return true
|
||||
end
|
||||
if self._class and ItemInfo.GetClassId(baseItemString) ~= self._class then
|
||||
return true
|
||||
end
|
||||
if self._subClass and ItemInfo.GetSubClassId(baseItemString) ~= self._subClass then
|
||||
return true
|
||||
end
|
||||
if self._invType and ItemInfo.GetInvSlotId(baseItemString) ~= self._invType then
|
||||
return true
|
||||
end
|
||||
if self._unlearned and CanIMogIt:PlayerKnowsTransmog(ItemInfo.GetLink(baseItemString)) then
|
||||
return true
|
||||
end
|
||||
if self._canLearn and not CanIMogIt:CharacterCanLearnTransmog(ItemInfo.GetLink(baseItemString)) then
|
||||
return true
|
||||
end
|
||||
if itemBuyout and (itemBuyout < self._minPrice or itemBuyout > self._maxPrice) then
|
||||
return true
|
||||
end
|
||||
if minItemBuyout and minItemBuyout > self._maxPrice then
|
||||
return true
|
||||
end
|
||||
for func in pairs(self._customFilters) do
|
||||
if func(self, row, isSubRow, itemKey) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function AuctionQuery._BrowseIsDone(self, isRetry)
|
||||
if TSM.IsWowClassic() then
|
||||
local numAuctions, totalAuctions = GetNumAuctionItems("list")
|
||||
if totalAuctions <= NUM_AUCTION_ITEMS_PER_PAGE and numAuctions ~= totalAuctions then
|
||||
-- there are cases where we get (0, 1) from the API - no idea why so just assume we're not done
|
||||
return false
|
||||
end
|
||||
local numPages = ceil(totalAuctions / NUM_AUCTION_ITEMS_PER_PAGE)
|
||||
if self._getAll then
|
||||
return true
|
||||
end
|
||||
if self._specifiedPage then
|
||||
if isRetry then
|
||||
return false
|
||||
end
|
||||
-- check if we're on the right page
|
||||
local specifiedPage = (self._specifiedPage == "FIRST" and 0) or (self._specifiedPage == "LAST" and numPages - 1) or self._specifiedPage
|
||||
return self._page == specifiedPage
|
||||
elseif self._isBrowseDoneFunc and self._isBrowseDoneFunc(self) then
|
||||
return true
|
||||
else
|
||||
return self._page >= numPages
|
||||
end
|
||||
else
|
||||
if self._isBrowseDoneFunc and self._isBrowseDoneFunc(self) then
|
||||
return true
|
||||
end
|
||||
return C_AuctionHouse.HasFullBrowseResults()
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionQuery._BrowseIsPageValid(self)
|
||||
if TSM.IsWowClassic() then
|
||||
if self._specifiedPage then
|
||||
return self:_BrowseIsDone()
|
||||
else
|
||||
return true
|
||||
end
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionQuery._BrowseRequestMore(self, isRetry)
|
||||
if TSM.IsWowClassic() then
|
||||
assert(not self._getAll)
|
||||
if self._specifiedPage then
|
||||
return self:_SendWowQuery()
|
||||
end
|
||||
if not isRetry then
|
||||
self._page = self._page + 1
|
||||
end
|
||||
return self:_SendWowQuery()
|
||||
else
|
||||
return AuctionHouseWrapper.RequestMoreBrowseResults()
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionQuery._OnSubRowRemoved(self, row)
|
||||
local baseItemString = row:GetBaseItemString()
|
||||
assert(row == self._browseResults[baseItemString])
|
||||
if row:GetNumSubRows() == 0 then
|
||||
self._browseResults[baseItemString] = nil
|
||||
row:Release()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.ItemIteratorHelper(items, index)
|
||||
while true do
|
||||
local itemString, itemType = next(items, index)
|
||||
if not itemString then
|
||||
return
|
||||
elseif itemType == ITEM_SPECIFIC then
|
||||
return itemString
|
||||
end
|
||||
index = itemString
|
||||
end
|
||||
end
|
||||
134
LibTSM/Service/AuctionScanClasses/QueryUtil.lua
Normal file
134
LibTSM/Service/AuctionScanClasses/QueryUtil.lua
Normal file
@@ -0,0 +1,134 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local QueryUtil = TSM.Init("Service.AuctionScanClasses.QueryUtil")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local Threading = TSM.Include("Service.Threading")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local Query = TSM.Include("Service.AuctionScanClasses.Query")
|
||||
local private = {
|
||||
itemListSortValue = {},
|
||||
}
|
||||
local MAX_ITEM_INFO_RETRIES = 30
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function QueryUtil.GenerateThreaded(itemList, callback, context)
|
||||
-- get all the item info into the game's cache
|
||||
for _ = 1, MAX_ITEM_INFO_RETRIES do
|
||||
local isMissingItemInfo = false
|
||||
for _, itemString in ipairs(itemList) do
|
||||
if not private.HasInfo(itemString) then
|
||||
isMissingItemInfo = true
|
||||
end
|
||||
Threading.Yield()
|
||||
end
|
||||
if not isMissingItemInfo then
|
||||
break
|
||||
end
|
||||
Threading.Sleep(0.1)
|
||||
end
|
||||
|
||||
-- remove items we're missing info for
|
||||
for i = #itemList, 1, -1 do
|
||||
if not private.HasInfo(itemList[i]) then
|
||||
Log.Err("Missing item info for %s", itemList[i])
|
||||
tremove(itemList, i)
|
||||
end
|
||||
Threading.Yield()
|
||||
end
|
||||
if #itemList == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- add all the items
|
||||
if TSM.IsWowClassic() then
|
||||
for _, itemString in ipairs(itemList) do
|
||||
private.GenerateQuery(callback, context, itemString, private.GetItemQueryInfo(itemString))
|
||||
end
|
||||
else
|
||||
-- sort the item list so all base items are grouped together but keep relative ordering between base items the same
|
||||
wipe(private.itemListSortValue)
|
||||
for i, itemString in ipairs(itemList) do
|
||||
local baseItemString = ItemString.GetBaseFast(itemString)
|
||||
private.itemListSortValue[baseItemString] = private.itemListSortValue[baseItemString] or i
|
||||
private.itemListSortValue[itemString] = private.itemListSortValue[baseItemString]
|
||||
end
|
||||
sort(itemList, private.ItemListSortHelper)
|
||||
local currentBaseItemString = nil
|
||||
local currentItems = TempTable.Acquire()
|
||||
for _, itemString in ipairs(itemList) do
|
||||
local baseItemString = ItemString.GetBaseFast(itemString)
|
||||
assert(baseItemString)
|
||||
if baseItemString == currentBaseItemString then
|
||||
-- same base item
|
||||
tinsert(currentItems, itemString)
|
||||
else
|
||||
-- new base item
|
||||
if currentBaseItemString then
|
||||
private.GenerateQuery(callback, context, currentItems, ItemInfo.GetName(currentBaseItemString))
|
||||
wipe(currentItems)
|
||||
end
|
||||
currentBaseItemString = baseItemString
|
||||
tinsert(currentItems, itemString)
|
||||
end
|
||||
end
|
||||
if currentBaseItemString then
|
||||
private.GenerateQuery(callback, context, currentItems, ItemInfo.GetName(currentBaseItemString))
|
||||
wipe(currentItems)
|
||||
end
|
||||
TempTable.Release(currentItems)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetItemQueryInfo(itemString)
|
||||
local name = ItemInfo.GetName(itemString)
|
||||
local level = ItemInfo.GetMinLevel(itemString) or 0
|
||||
local quality = ItemInfo.GetQuality(itemString)
|
||||
local classId = ItemInfo.GetClassId(itemString) or 0
|
||||
local subClassId = ItemInfo.GetSubClassId(itemString) or 0
|
||||
-- Ignoring level because level can now vary
|
||||
if itemString == ItemString.GetBase(itemString) and (classId == LE_ITEM_CLASS_WEAPON or classId == LE_ITEM_CLASS_ARMOR or (classId == LE_ITEM_CLASS_GEM and subClassId == LE_ITEM_GEM_ARTIFACTRELIC)) then
|
||||
level = nil
|
||||
end
|
||||
return name, level, level, quality, classId, subClassId
|
||||
end
|
||||
|
||||
function private.HasInfo(itemString)
|
||||
return ItemInfo.GetName(itemString) and ItemInfo.GetQuality(itemString) and ItemInfo.GetMinLevel(itemString)
|
||||
end
|
||||
|
||||
function private.GenerateQuery(callback, context, items, name, minLevel, maxLevel, quality, class, subClass)
|
||||
local query = Query.Get()
|
||||
:SetStr(name, false)
|
||||
:SetQualityRange(quality, quality)
|
||||
:SetLevelRange(minLevel, maxLevel)
|
||||
:SetClass(class, subClass)
|
||||
:SetItems(items)
|
||||
callback(query, context)
|
||||
end
|
||||
|
||||
function private.ItemListSortHelper(a, b)
|
||||
local aSortValue = private.itemListSortValue[a]
|
||||
local bSortValue = private.itemListSortValue[b]
|
||||
if aSortValue ~= bSortValue then
|
||||
return aSortValue < bSortValue
|
||||
end
|
||||
return a < b
|
||||
end
|
||||
671
LibTSM/Service/AuctionScanClasses/ResultRow.lua
Normal file
671
LibTSM/Service/AuctionScanClasses/ResultRow.lua
Normal file
@@ -0,0 +1,671 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local ResultRow = TSM.Init("Service.AuctionScanClasses.ResultRow")
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local ObjectPool = TSM.Include("Util.ObjectPool")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local LibTSMClass = TSM.Include("LibTSMClass")
|
||||
local Util = TSM.Include("Service.AuctionScanClasses.Util")
|
||||
local AuctionHouseWrapper = TSM.Include("Service.AuctionHouseWrapper")
|
||||
local ResultSubRow = TSM.Include("Service.AuctionScanClasses.ResultSubRow")
|
||||
local ResultRowWrapper = LibTSMClass.DefineClass("ResultRowWrapper")
|
||||
local private = {
|
||||
objectPool = ObjectPool.New("AUCTION_SCAN_RESULT_ROW", ResultRowWrapper),
|
||||
}
|
||||
local SUB_ROW_SEARCH_INDEX_MULTIPLIER = 1000000
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function ResultRow.Get(query, itemKey, minPrice, totalQuantity)
|
||||
local row = private.objectPool:Get()
|
||||
row:_Acquire(query, itemKey, minPrice, totalQuantity)
|
||||
return row
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- ResultRowWrapper - Meta Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ResultRowWrapper.__init(self)
|
||||
self._query = nil
|
||||
self._items = {}
|
||||
self._baseItemString = nil
|
||||
self._canHaveNonBaseItemString = nil
|
||||
self._minPrice = nil
|
||||
self._hasItemInfo = nil
|
||||
self._isCommodity = nil
|
||||
self._notFiltered = false
|
||||
self._searchIndex = nil
|
||||
self._subRows = {}
|
||||
self._minBrowseId = nil
|
||||
end
|
||||
|
||||
function ResultRowWrapper._Acquire(self, query, item, minPrice, totalQuantity)
|
||||
self._query = query
|
||||
if TSM.IsWowClassic() then
|
||||
assert(not minPrice and not totalQuantity)
|
||||
tinsert(self._items, item)
|
||||
self._baseItemString = ItemString.GetBase(item)
|
||||
else
|
||||
item._minPrice = minPrice
|
||||
item._totalQuantity = totalQuantity
|
||||
tinsert(self._items, item)
|
||||
self._baseItemString = ItemString.GetBaseFromItemKey(item)
|
||||
end
|
||||
self._canHaveNonBaseItemString = nil
|
||||
self._minPrice = nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- ResultRowWrapper - Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ResultRowWrapper.Merge(self, item, minPrice, totalQuantity)
|
||||
-- check if we already have this item
|
||||
for i = 1, #self._items do
|
||||
if item == self._items[i] then
|
||||
return
|
||||
end
|
||||
if type(item) == "table" then
|
||||
local isEqual = true
|
||||
for k in pairs(item) do
|
||||
if item[k] ~= self._items[i][k] then
|
||||
isEqual = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if isEqual then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
self._hasItemInfo = nil
|
||||
if TSM.IsWowClassic() then
|
||||
assert(not minPrice and not totalQuantity)
|
||||
assert(self._baseItemString == ItemString.GetBase(item))
|
||||
tinsert(self._items, item)
|
||||
self._notFiltered = false
|
||||
else
|
||||
assert(self._baseItemString == ItemString.GetBaseFromItemKey(item))
|
||||
item._minPrice = minPrice
|
||||
item._totalQuantity = totalQuantity
|
||||
tinsert(self._items, item)
|
||||
self._notFiltered = false
|
||||
end
|
||||
self._canHaveNonBaseItemString = nil
|
||||
end
|
||||
|
||||
function ResultRowWrapper.Release(self)
|
||||
wipe(self._items)
|
||||
self._baseItemString = nil
|
||||
self._canHaveNonBaseItemString = nil
|
||||
self._minPrice = nil
|
||||
self._hasItemInfo = nil
|
||||
self._isCommodity = nil
|
||||
self._notFiltered = false
|
||||
self._searchIndex = nil
|
||||
self._minBrowseId = nil
|
||||
for _, subRow in pairs(self._subRows) do
|
||||
subRow:Release()
|
||||
end
|
||||
wipe(self._subRows)
|
||||
private.objectPool:Recycle(self)
|
||||
end
|
||||
|
||||
function ResultRowWrapper.IsSubRow(self)
|
||||
return false
|
||||
end
|
||||
|
||||
function ResultRowWrapper.PopulateBrowseData(self)
|
||||
assert(self._baseItemString)
|
||||
if self._hasItemInfo then
|
||||
-- already have our item info
|
||||
return true
|
||||
elseif not Util.HasItemInfo(self._baseItemString) then
|
||||
-- don't have item info yet
|
||||
return false
|
||||
end
|
||||
|
||||
if not TSM.IsWowClassic() then
|
||||
-- cache the commodity status since it's referenced a ton
|
||||
if self._isCommodity == nil then
|
||||
self._isCommodity = ItemInfo.IsCommodity(self._baseItemString)
|
||||
assert(self._isCommodity ~= nil)
|
||||
end
|
||||
end
|
||||
|
||||
-- check if we have info for all the items and try to fetch it if not
|
||||
local missingInfo = false
|
||||
for _, item in ipairs(self._items) do
|
||||
if TSM.IsWowClassic() then
|
||||
if not Util.HasItemInfo(ItemString.Get(item)) then
|
||||
missingInfo = true
|
||||
end
|
||||
else
|
||||
if not item._itemKeyInfo then
|
||||
item._itemKeyInfo = C_AuctionHouse.GetItemKeyInfo(item, true)
|
||||
if not item._itemKeyInfo then
|
||||
missingInfo = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if missingInfo then
|
||||
return false
|
||||
end
|
||||
|
||||
self._hasItemInfo = true
|
||||
return true
|
||||
end
|
||||
|
||||
function ResultRowWrapper.IsFiltered(self, query)
|
||||
assert(#self._items > 0)
|
||||
if self._notFiltered then
|
||||
return false
|
||||
end
|
||||
|
||||
-- check if the whole row is filtered
|
||||
if query:_IsFiltered(self, false) then
|
||||
return true
|
||||
end
|
||||
|
||||
-- filter our items
|
||||
for i = #self._items, 1, -1 do
|
||||
if query:_IsFiltered(self, false, self._items[i]) then
|
||||
tremove(self._items, i)
|
||||
end
|
||||
end
|
||||
self._canHaveNonBaseItemString = nil
|
||||
self._minPrice = nil
|
||||
if #self._items == 0 then
|
||||
-- no more items, so the entire row is filtered
|
||||
return true
|
||||
end
|
||||
|
||||
-- not filtered (cache this result)
|
||||
self._notFiltered = true
|
||||
return false
|
||||
end
|
||||
|
||||
function ResultRowWrapper.SearchReset(self)
|
||||
assert(not TSM.IsWowClassic())
|
||||
assert(#self._items > 0)
|
||||
self._searchIndex = 1
|
||||
end
|
||||
|
||||
function ResultRowWrapper.SearchNext(self)
|
||||
assert(not TSM.IsWowClassic())
|
||||
assert(self._searchIndex)
|
||||
if self._searchIndex == #self._items then
|
||||
self._searchIndex = nil
|
||||
return false
|
||||
end
|
||||
self._searchIndex = self._searchIndex + 1
|
||||
return true
|
||||
end
|
||||
|
||||
function ResultRowWrapper.SearchIsReady(self)
|
||||
assert(not TSM.IsWowClassic())
|
||||
assert(self._searchIndex)
|
||||
-- the client needs to have the item key info cached before we can run the search
|
||||
return C_AuctionHouse.GetItemKeyInfo(self._items[self._searchIndex], true) and true or false
|
||||
end
|
||||
|
||||
function ResultRowWrapper.SearchSend(self)
|
||||
assert(not TSM.IsWowClassic())
|
||||
assert(self._searchIndex)
|
||||
local itemKey = self._items[self._searchIndex]
|
||||
-- send a sell query if we don't have browse results for the itemKey
|
||||
-- for some reason sell queries don't work for commodities or pets
|
||||
local isSellQuery = not self._isCommodity and not ItemString.IsPet(self._baseItemString) and not itemKey._totalQuantity
|
||||
return AuctionHouseWrapper.SendSearchQuery(itemKey, isSellQuery)
|
||||
end
|
||||
|
||||
function ResultRowWrapper.HasCachedSearchData(self)
|
||||
local itemKey = self._items[self._searchIndex]
|
||||
if self._isCommodity then
|
||||
return C_AuctionHouse.HasFullCommoditySearchResults(itemKey.itemID)
|
||||
else
|
||||
return C_AuctionHouse.HasFullItemSearchResults(itemKey)
|
||||
end
|
||||
end
|
||||
|
||||
function ResultRowWrapper.SearchCheckStatus(self)
|
||||
assert(not TSM.IsWowClassic())
|
||||
assert(self._searchIndex)
|
||||
local itemKey = self._items[self._searchIndex]
|
||||
|
||||
-- check if we have the full results
|
||||
local hasFullResults = nil
|
||||
if self._isCommodity then
|
||||
hasFullResults = C_AuctionHouse.HasFullCommoditySearchResults(itemKey.itemID)
|
||||
else
|
||||
hasFullResults = C_AuctionHouse.HasFullItemSearchResults(itemKey)
|
||||
end
|
||||
if hasFullResults then
|
||||
return true
|
||||
end
|
||||
|
||||
-- request more results
|
||||
if self._isCommodity then
|
||||
return false, AuctionHouseWrapper.RequestMoreCommoditySearchResults(itemKey.itemID)
|
||||
else
|
||||
return false, AuctionHouseWrapper.RequestMoreItemSearchResults(itemKey)
|
||||
end
|
||||
end
|
||||
|
||||
function ResultRowWrapper.PopulateSubRows(self, browseId, index, itemLink)
|
||||
if TSM.IsWowClassic() then
|
||||
-- remove any prior results with a different browseId
|
||||
assert(index and not self._searchIndex)
|
||||
local subRow = ResultSubRow.Get(self)
|
||||
subRow:_SetRawData(index, browseId, itemLink)
|
||||
local _, hashNoSeller = subRow:GetHashes()
|
||||
if self._minBrowseId and self._minBrowseId ~= browseId then
|
||||
-- check if this subRow already exists with a prior browseId
|
||||
for i, existingSubRow in ipairs(self._subRows) do
|
||||
local _, existingHashNoSeller = existingSubRow:GetHashes()
|
||||
local _, _, existingBrowseId = existingSubRow:GetListingInfo()
|
||||
if hashNoSeller == existingHashNoSeller and browseId ~= existingBrowseId then
|
||||
-- replace the existing subRow
|
||||
existingSubRow:Release()
|
||||
self._subRows[i] = subRow
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
tinsert(self._subRows, subRow)
|
||||
else
|
||||
assert(self._searchIndex and not index)
|
||||
local subRowOffset = self._searchIndex * SUB_ROW_SEARCH_INDEX_MULTIPLIER
|
||||
local itemKey = self._items[self._searchIndex]
|
||||
local numAuctions = nil
|
||||
if self:IsCommodity() then
|
||||
numAuctions = C_AuctionHouse.GetNumCommoditySearchResults(itemKey.itemID)
|
||||
else
|
||||
numAuctions = C_AuctionHouse.GetNumItemSearchResults(itemKey)
|
||||
end
|
||||
if itemKey._numAuctions and numAuctions ~= itemKey._numAuctions then
|
||||
-- the results changed so clear out our existing data
|
||||
for i = itemKey._numAuctions, 1, -1 do
|
||||
if i > numAuctions then
|
||||
self._subRows[subRowOffset + i]:Release()
|
||||
self._subRows[subRowOffset + i] = nil
|
||||
else
|
||||
self._subRows[subRowOffset + i]:_SetRawData(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
itemKey._numAuctions = numAuctions
|
||||
for i = 1, numAuctions do
|
||||
self._subRows[subRowOffset + i] = self._subRows[subRowOffset + i] or ResultSubRow.Get(self)
|
||||
local subRow = self._subRows[subRowOffset + i]
|
||||
if not subRow:HasRawData() or not subRow:HasOwners() then
|
||||
local result = nil
|
||||
if self:IsCommodity() then
|
||||
result = C_AuctionHouse.GetCommoditySearchResultInfo(itemKey.itemID, i)
|
||||
else
|
||||
result = C_AuctionHouse.GetItemSearchResultInfo(itemKey, i)
|
||||
end
|
||||
subRow:_SetRawData(result, browseId)
|
||||
end
|
||||
end
|
||||
end
|
||||
self._minBrowseId = min(self._minBrowseId or math.huge, browseId)
|
||||
end
|
||||
|
||||
function ResultRowWrapper.FilterSubRows(self, query)
|
||||
local subRowOffset = TSM.IsWowClassic() and 0 or (self._searchIndex * SUB_ROW_SEARCH_INDEX_MULTIPLIER)
|
||||
if TSM.IsWowClassic() then
|
||||
for i = #self._subRows, 1, -1 do
|
||||
if query:_IsFiltered(self._subRows[i], true) then
|
||||
self:_RemoveSubRowByIndex(i)
|
||||
end
|
||||
end
|
||||
else
|
||||
local itemKey = self._items[self._searchIndex]
|
||||
for j = itemKey._numAuctions, 1, -1 do
|
||||
local subRow = self._subRows[subRowOffset + j]
|
||||
if query:_IsFiltered(subRow, true) then
|
||||
self:_RemoveSubRowByIndex(j)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- merge subRows with identical hashes
|
||||
local numSubRows = nil
|
||||
local hashIndexLookup = TempTable.Acquire()
|
||||
local index = 1
|
||||
while true do
|
||||
numSubRows = TSM.IsWowClassic() and #self._subRows or self._items[self._searchIndex]._numAuctions
|
||||
if index > numSubRows then
|
||||
break
|
||||
end
|
||||
local subRow = self._subRows[subRowOffset + index]
|
||||
local hash = subRow:GetHashes()
|
||||
local prevIndex = hashIndexLookup[hash]
|
||||
if prevIndex then
|
||||
-- there was a previous subRow with the same hash
|
||||
self._subRows[subRowOffset + prevIndex]:Merge(subRow)
|
||||
-- remove this subRow
|
||||
self:_RemoveSubRowByIndex(index)
|
||||
else
|
||||
hashIndexLookup[hash] = index
|
||||
index = index + 1
|
||||
end
|
||||
end
|
||||
TempTable.Release(hashIndexLookup)
|
||||
return numSubRows == 0
|
||||
end
|
||||
|
||||
function ResultRowWrapper.GetNumSubRows(self)
|
||||
if TSM.IsWowClassic() then
|
||||
return #self._subRows
|
||||
else
|
||||
local result = 0
|
||||
for _, itemKey in ipairs(self._items) do
|
||||
result = result + (itemKey._numAuctions or 0)
|
||||
end
|
||||
return result
|
||||
end
|
||||
end
|
||||
|
||||
function ResultRowWrapper.SubRowIterator(self, searchOnly)
|
||||
if TSM.IsWowClassic() then
|
||||
return ipairs(self._subRows)
|
||||
else
|
||||
if searchOnly then
|
||||
local result = TempTable.Acquire()
|
||||
assert(self._searchIndex)
|
||||
for i = 1, self._items[self._searchIndex]._numAuctions do
|
||||
local subRow = self._subRows[self._searchIndex * SUB_ROW_SEARCH_INDEX_MULTIPLIER + i]
|
||||
assert(subRow)
|
||||
tinsert(result, subRow)
|
||||
end
|
||||
return TempTable.Iterator(result)
|
||||
else
|
||||
return private.SubRowIteratorHelper, self, SUB_ROW_SEARCH_INDEX_MULTIPLIER
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ResultRowWrapper.IsCommodity(self)
|
||||
assert(self._isCommodity ~= nil)
|
||||
return self._isCommodity
|
||||
end
|
||||
|
||||
function ResultRowWrapper.HasItemInfo(self)
|
||||
return self._hasItemInfo
|
||||
end
|
||||
|
||||
function ResultRowWrapper.GetBaseItemString(self)
|
||||
return self._baseItemString
|
||||
end
|
||||
|
||||
function ResultRowWrapper.GetItemString(self)
|
||||
if TSM.IsWowClassic() or not self._hasItemInfo or self._canHaveNonBaseItemString then
|
||||
return nil
|
||||
end
|
||||
if self._canHaveNonBaseItemString == nil then
|
||||
for _, itemKey in ipairs(self._items) do
|
||||
if ItemInfo.CanHaveVariations(self._baseItemString) or itemKey.battlePetSpeciesID ~= 0 or itemKey.itemSuffix ~= 0 or itemKey.itemLevel ~= 0 then
|
||||
-- this item can have variations, so we don't know its itemString
|
||||
self._canHaveNonBaseItemString = true
|
||||
return nil
|
||||
end
|
||||
end
|
||||
self._canHaveNonBaseItemString = false
|
||||
end
|
||||
return self._baseItemString
|
||||
end
|
||||
|
||||
function ResultRowWrapper.GetItemInfo(self, itemKey)
|
||||
if TSM.IsWowClassic() or not self._hasItemInfo then
|
||||
return nil, nil, nil, nil
|
||||
end
|
||||
itemKey = itemKey or (#self._items == 1 and self._items[1] or nil)
|
||||
assert(not itemKey or itemKey._itemKeyInfo)
|
||||
local baseItemString = self:GetBaseItemString()
|
||||
local itemString = self:GetItemString()
|
||||
local itemName, quality, itemLevel, maxItemLevel = nil, nil, nil, nil
|
||||
if itemString then
|
||||
-- this item can't have variations, so we can know the name / level / quality
|
||||
itemName = ItemInfo.GetName(baseItemString)
|
||||
itemLevel = ItemInfo.GetItemLevel(baseItemString)
|
||||
quality = ItemInfo.GetQuality(baseItemString)
|
||||
assert(itemName and itemLevel and quality)
|
||||
else
|
||||
if itemKey and not itemKey._totalQuantity then
|
||||
-- if we didn't do a browse, then don't use this itemKey
|
||||
itemKey = nil
|
||||
end
|
||||
if itemKey then
|
||||
-- grab the name from the itemKeyInfo
|
||||
itemName = itemKey._itemKeyInfo.itemName
|
||||
assert(itemName)
|
||||
end
|
||||
local hasSingleAuction = itemKey and itemKey._totalQuantity == 1
|
||||
if hasSingleAuction then
|
||||
-- grab the quality from the itemKeyInfo since there's only one listing
|
||||
quality = itemKey._itemKeyInfo.quality
|
||||
assert(quality)
|
||||
end
|
||||
if not ItemString.IsPet(self._baseItemString) then
|
||||
-- for non-pets, we can maybe grab the itemLevel from the itemKey
|
||||
if itemKey then
|
||||
itemLevel = itemKey.itemLevel ~= 0 and itemKey.itemLevel or nil
|
||||
else
|
||||
-- only use the itemLevel from the itemKeys if they are all the same
|
||||
local itemKeyItemLevel = self._items[1].itemLevel
|
||||
for i = 2, #self._items do
|
||||
if self._items[i].itemLevel ~= itemKeyItemLevel then
|
||||
itemKeyItemLevel = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
itemLevel = (itemKeyItemLevel or 0) ~= 0 and itemKeyItemLevel or nil
|
||||
end
|
||||
elseif itemKey and itemKey._itemKeyInfo.battlePetLink then
|
||||
if hasSingleAuction then
|
||||
-- grab the itemLevel from the link since there's only one listing
|
||||
itemLevel = ItemInfo.GetItemLevel(itemKey._itemKeyInfo.battlePetLink)
|
||||
assert(itemLevel)
|
||||
else
|
||||
-- grab the maxItemLevel from the link
|
||||
maxItemLevel = ItemInfo.GetItemLevel(itemKey._itemKeyInfo.battlePetLink)
|
||||
assert(maxItemLevel)
|
||||
end
|
||||
end
|
||||
end
|
||||
return itemName, quality, itemLevel, maxItemLevel
|
||||
end
|
||||
|
||||
function ResultRowWrapper.GetBuyouts(self, resultItemKey)
|
||||
if TSM.IsWowClassic() then
|
||||
return nil, nil, nil
|
||||
end
|
||||
assert(#self._items > 0)
|
||||
if resultItemKey then
|
||||
return nil, nil, resultItemKey._minPrice
|
||||
else
|
||||
if self._minPrice == nil then
|
||||
for _, itemKey in ipairs(self._items) do
|
||||
if not itemKey._minPrice then
|
||||
self._minPrice = -1
|
||||
return nil, nil, nil
|
||||
end
|
||||
self._minPrice = min(self._minPrice or math.huge, itemKey._minPrice)
|
||||
end
|
||||
elseif self._minPrice == -1 then
|
||||
return nil, nil, nil
|
||||
end
|
||||
return nil, nil, self._minPrice
|
||||
end
|
||||
end
|
||||
|
||||
function ResultRowWrapper.GetQuantities(self)
|
||||
local totalQuantity = 0
|
||||
if TSM.IsWowClassic() then
|
||||
for _, subRow in ipairs(self._subRows) do
|
||||
local quantity, numAuctions = subRow:GetQuantities()
|
||||
totalQuantity = totalQuantity + quantity * numAuctions
|
||||
end
|
||||
else
|
||||
for _, itemKey in ipairs(self._items) do
|
||||
if not itemKey._totalQuantity then
|
||||
return
|
||||
end
|
||||
totalQuantity = totalQuantity + itemKey._totalQuantity
|
||||
end
|
||||
end
|
||||
return totalQuantity, 1
|
||||
end
|
||||
|
||||
function ResultRowWrapper.GetMaxQuantities(self)
|
||||
assert(self:IsCommodity())
|
||||
local totalQuantity = 0
|
||||
for _, subRow in self:SubRowIterator() do
|
||||
local _, numOwnerItems = subRow:GetOwnerInfo()
|
||||
local quantityAvailable = subRow:GetQuantities() - numOwnerItems
|
||||
totalQuantity = totalQuantity + quantityAvailable
|
||||
end
|
||||
return totalQuantity
|
||||
end
|
||||
|
||||
function ResultRowWrapper.RemoveSubRow(self, subRow)
|
||||
local index = Table.KeyByValue(self._subRows, subRow)
|
||||
if TSM.IsWowClassic() then
|
||||
self:_RemoveSubRowByIndex(index)
|
||||
else
|
||||
local searchIndex = floor(index / SUB_ROW_SEARCH_INDEX_MULTIPLIER)
|
||||
index = index % SUB_ROW_SEARCH_INDEX_MULTIPLIER
|
||||
assert(self._subRows[searchIndex * SUB_ROW_SEARCH_INDEX_MULTIPLIER + index] == subRow)
|
||||
local prevSearchIndex = self._searchIndex
|
||||
self._searchIndex = searchIndex
|
||||
self:_RemoveSubRowByIndex(index)
|
||||
self._searchIndex = prevSearchIndex
|
||||
end
|
||||
self._query:_OnSubRowRemoved(self)
|
||||
end
|
||||
|
||||
function ResultRowWrapper.WipeSearchResults(self)
|
||||
wipe(self._subRows)
|
||||
if not TSM.IsWowClassic() then
|
||||
for _, itemKey in ipairs(self._items) do
|
||||
itemKey._numAuctions = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ResultRowWrapper.GetQuery(self)
|
||||
return self._query
|
||||
end
|
||||
|
||||
function ResultRowWrapper.DecrementQuantity(self, amount)
|
||||
assert(self:IsCommodity() and not TSM.IsWowClassic() and #self._items == 1)
|
||||
local index = 1
|
||||
while amount > 0 do
|
||||
local subRow = self._subRows[index + SUB_ROW_SEARCH_INDEX_MULTIPLIER]
|
||||
assert(subRow)
|
||||
local _, numOwnerItems = subRow:GetOwnerInfo()
|
||||
local quantityAvailable = subRow:GetQuantities() - numOwnerItems
|
||||
if quantityAvailable > 0 then
|
||||
local usedQuantity = min(quantityAvailable, amount)
|
||||
local prevItemBuyout = floor(subRow._buyout / subRow._quantity)
|
||||
amount = amount - usedQuantity
|
||||
subRow._quantity = subRow._quantity - usedQuantity
|
||||
subRow._buyout = prevItemBuyout * subRow._quantity
|
||||
subRow._minBid = subRow._buyout
|
||||
if numOwnerItems == 0 and subRow._quantity == 0 then
|
||||
self:RemoveSubRow(subRow)
|
||||
else
|
||||
index = index + 1
|
||||
end
|
||||
else
|
||||
index = index + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ResultRowWrapper.GetMinBrowseId(self)
|
||||
return self._minBrowseId
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- ResultRowWrapper - Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ResultRowWrapper._RemoveSubRowByIndex(self, index)
|
||||
if TSM.IsWowClassic() then
|
||||
self._subRows[index]:Release()
|
||||
tremove(self._subRows, index)
|
||||
else
|
||||
local subRowOffset = self._searchIndex * SUB_ROW_SEARCH_INDEX_MULTIPLIER
|
||||
local itemKey = self._items[self._searchIndex]
|
||||
self._subRows[subRowOffset + index]:Release()
|
||||
self._subRows[subRowOffset + index] = nil
|
||||
-- shift the other subRows for this item down
|
||||
for i = index, itemKey._numAuctions - 1 do
|
||||
self._subRows[subRowOffset + i] = self._subRows[subRowOffset + i + 1]
|
||||
end
|
||||
self._subRows[subRowOffset + itemKey._numAuctions] = nil
|
||||
itemKey._numAuctions = itemKey._numAuctions - 1
|
||||
end
|
||||
end
|
||||
|
||||
function ResultRowWrapper._GetSearchProgress(self)
|
||||
assert(not TSM.IsWowClassic())
|
||||
if #self._items == 0 then
|
||||
return 0
|
||||
end
|
||||
local numSearched = 0
|
||||
for _, itemKey in ipairs(self._items) do
|
||||
if itemKey._numAuctions then
|
||||
numSearched = numSearched + 1
|
||||
end
|
||||
end
|
||||
return numSearched / #self._items
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.SubRowIteratorHelper(row, index)
|
||||
local searchIndex = floor(index / SUB_ROW_SEARCH_INDEX_MULTIPLIER)
|
||||
local subRowIndex = index % SUB_ROW_SEARCH_INDEX_MULTIPLIER
|
||||
while true do
|
||||
local itemKey = row._items[searchIndex]
|
||||
if not itemKey then
|
||||
return
|
||||
end
|
||||
|
||||
if subRowIndex >= (itemKey._numAuctions or 0) then
|
||||
searchIndex = searchIndex + 1
|
||||
subRowIndex = 0
|
||||
else
|
||||
subRowIndex = subRowIndex + 1
|
||||
index = searchIndex * SUB_ROW_SEARCH_INDEX_MULTIPLIER + subRowIndex
|
||||
return index, row._subRows[index]
|
||||
end
|
||||
end
|
||||
end
|
||||
364
LibTSM/Service/AuctionScanClasses/ResultSubRow.lua
Normal file
364
LibTSM/Service/AuctionScanClasses/ResultSubRow.lua
Normal file
@@ -0,0 +1,364 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local ResultSubRow = TSM.Init("Service.AuctionScanClasses.ResultSubRow")
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local ObjectPool = TSM.Include("Util.ObjectPool")
|
||||
local Math = TSM.Include("Util.Math")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local LibTSMClass = TSM.Include("LibTSMClass")
|
||||
local Util = TSM.Include("Service.AuctionScanClasses.Util")
|
||||
local ResultSubRowWrapper = LibTSMClass.DefineClass("ResultSubRowWrapper")
|
||||
local private = {
|
||||
objectPool = ObjectPool.New("AUCTION_SCAN_RESULT_SUB_ROW", ResultSubRowWrapper),
|
||||
ownersTemp = {},
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function ResultSubRow.Get(resultRow)
|
||||
local subRow = private.objectPool:Get()
|
||||
subRow:_Acquire(resultRow)
|
||||
return subRow
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- ResultSubRowWrapper - Meta Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ResultSubRowWrapper.__init(self)
|
||||
self._resultRow = nil
|
||||
self._itemLink = nil
|
||||
self._buyout = nil
|
||||
self._minBid = nil
|
||||
self._currentBid = nil
|
||||
self._minIncrement = nil
|
||||
self._isHighBidder = nil
|
||||
self._quantity = nil
|
||||
self._timeLeft = nil
|
||||
self._ownerStr = nil
|
||||
self._hasOwners = false
|
||||
self._numOwnerItems = nil
|
||||
self._auctionId = nil
|
||||
self._hash = nil
|
||||
self._hashNoSeller = nil
|
||||
self._browseId = nil
|
||||
self._numAuctions = 1
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper._Acquire(self, resultRow)
|
||||
self._resultRow = resultRow
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- ResultSubRowWrapper - Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ResultSubRowWrapper.Merge(self, other)
|
||||
if TSM.IsWowClassic() then
|
||||
self._numAuctions = self._numAuctions + other._numAuctions
|
||||
else
|
||||
if self:IsCommodity() then
|
||||
self._quantity = self._quantity + other._quantity
|
||||
self._numOwnerItems = self._numOwnerItems + other._numOwnerItems
|
||||
else
|
||||
self._numAuctions = self._numAuctions + other._numAuctions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.Release(self)
|
||||
self._resultRow = nil
|
||||
self._numAuctions = 1
|
||||
self:_SetRawData(nil)
|
||||
private.objectPool:Recycle(self)
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.IsSubRow(self)
|
||||
return true
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.HasRawData(self)
|
||||
return self._timeLeft and true or false
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.HasOwners(self)
|
||||
return self._hasOwners
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.HasItemString(self)
|
||||
assert(self:HasRawData())
|
||||
local itemString = ItemString.Get(self._itemLink)
|
||||
if not Util.HasItemInfo(itemString) then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.IsCommodity(self)
|
||||
return self._resultRow:IsCommodity()
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.GetResultRow(self)
|
||||
return self._resultRow
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.GetBaseItemString(self)
|
||||
return self._resultRow:GetBaseItemString()
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.GetItemString(self)
|
||||
assert(self:HasRawData())
|
||||
local itemString = ItemString.Get(self._itemLink)
|
||||
return itemString or self._resultRow:GetItemString()
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.GetItemInfo(self)
|
||||
assert(self:HasItemString())
|
||||
local itemString = ItemString.Get(self._itemLink)
|
||||
local itemName = ItemInfo.GetName(itemString)
|
||||
local quality = ItemInfo.GetQuality(itemString)
|
||||
local itemLevel = ItemInfo.GetItemLevel(itemString)
|
||||
assert(itemName and quality and itemLevel)
|
||||
return itemName, quality, itemLevel, nil
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.GetBuyouts(self)
|
||||
assert(self:HasRawData())
|
||||
return self._buyout, floor(self._buyout / self._quantity), nil
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.GetBidInfo(self)
|
||||
assert(self:HasRawData())
|
||||
local itemMinBid = Math.Floor(self._minBid / self._quantity, TSM.IsWowClassic() and 1 or COPPER_PER_SILVER)
|
||||
return self._minBid, itemMinBid, self._currentBid, self._isHighBidder, self._minIncrement
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.GetRequiredBid(self)
|
||||
local requiredBid = nil
|
||||
if TSM.IsWowClassic() then
|
||||
requiredBid = self._currentBid == 0 and self._minBid or (self._currentBid + self._minIncrement)
|
||||
else
|
||||
requiredBid = self._minBid
|
||||
end
|
||||
return requiredBid
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.GetDisplayedBids(self)
|
||||
local displayedBid = self._currentBid == 0 and self._minBid or self._currentBid
|
||||
local itemDisplayedBid = Math.Floor(displayedBid / self._quantity, TSM.IsWowClassic() and 1 or COPPER_PER_SILVER)
|
||||
return displayedBid, itemDisplayedBid
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.GetLinks(self)
|
||||
assert(self:HasRawData())
|
||||
local rawLink = self._itemLink
|
||||
local itemLink = ItemInfo.GeneralizeLink(rawLink)
|
||||
return itemLink, rawLink
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.GetListingInfo(self)
|
||||
assert(self:HasRawData())
|
||||
return self._timeLeft, self._auctionId, self._browseId
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.GetQuantities(self)
|
||||
assert(self:HasRawData())
|
||||
return self._quantity, self._numAuctions
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.GetOwnerInfo(self)
|
||||
assert(self:HasRawData())
|
||||
return self._ownerStr, self._numOwnerItems
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.GetHashes(self)
|
||||
if not self._hash then
|
||||
assert(self:HasRawData())
|
||||
if TSM.IsWowClassic() then
|
||||
self._hash = strjoin("~", tostringall(self._itemLink, self._minBid, self._minIncrement, self._buyout, self._currentBid, self._ownerStr, self._timeLeft, self._quantity, self._isHighBidder))
|
||||
self._hashNoSeller = strjoin("~", tostringall(self._itemLink, self._minBid, self._minIncrement, self._buyout, self._currentBid, self._timeLeft, self._quantity, self._isHighBidder))
|
||||
else
|
||||
local baseItemString = self:GetBaseItemString()
|
||||
local itemMinBid = Math.Floor(self._minBid / self._quantity, COPPER_PER_SILVER)
|
||||
local itemBuyout = floor(self._buyout / self._quantity)
|
||||
local itemKeyId, itemKeySpeciesId = nil, nil
|
||||
if ItemString.IsPet(baseItemString) then
|
||||
itemKeyId = ItemString.ToId(ItemString.GetPetCage())
|
||||
itemKeySpeciesId = ItemString.ToId(baseItemString)
|
||||
elseif ItemString.IsItem(baseItemString) then
|
||||
itemKeyId = ItemString.ToId(baseItemString)
|
||||
itemKeySpeciesId = 0
|
||||
else
|
||||
error("Invalid baseItemString: "..tostring(baseItemString))
|
||||
end
|
||||
if self:IsCommodity() then
|
||||
self._hash = strjoin("~", tostringall(itemKeyId, itemBuyout, self._auctionId, self._ownerStr))
|
||||
self._hashNoSeller = strjoin("~", tostringall(itemKeyId, itemBuyout, self._auctionId))
|
||||
else
|
||||
self._hash = strjoin("~", tostringall(itemKeyId, itemKeySpeciesId, self._itemLink, itemMinBid, itemBuyout, self._currentBid, self._quantity, self._isHighBidder, self._ownerStr, self._auctionId))
|
||||
self._hashNoSeller = strjoin("~", tostringall(itemKeyId, itemKeySpeciesId, self._itemLink, itemMinBid, itemBuyout, self._currentBid, self._quantity, self._isHighBidder, self._auctionId))
|
||||
end
|
||||
end
|
||||
end
|
||||
return self._hash, self._hashNoSeller
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.EqualsIndex(self, index, noSeller)
|
||||
assert(TSM.IsWowClassic())
|
||||
local _, _, stackSize, _, _, _, _, minBid, minIncrement, buyout, bid, isHighBidder, _, seller, sellerFull = GetAuctionItemInfo("list", index)
|
||||
seller = Util.FixSellerName(seller, sellerFull) or "?"
|
||||
-- this is to get around a bug in Blizzard's code where the minIncrement value will be inconsistent for auctions where the player is the highest bidder
|
||||
minIncrement = isHighBidder and 0 or minIncrement
|
||||
if minBid ~= self._minBid or minIncrement ~= self._minIncrement or buyout ~= self._buyout or bid ~= self._currentBid or stackSize == self._quantity and isHighBidder ~= self._isHighBidder then
|
||||
return false
|
||||
elseif not noSeller and seller ~= self._ownerStr then
|
||||
return false
|
||||
elseif GetAuctionItemLink("list", index) ~= self._itemLink then
|
||||
return false
|
||||
elseif GetAuctionItemTimeLeft("list", index) ~= self._timeLeft then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.DecrementQuantity(self, amount)
|
||||
if TSM.IsWowClassic() then
|
||||
assert(amount == self._quantity)
|
||||
self._numAuctions = self._numAuctions - 1
|
||||
if self._numAuctions == 0 then
|
||||
self._resultRow:RemoveSubRow(self)
|
||||
end
|
||||
else
|
||||
if self:IsCommodity() then
|
||||
self._resultRow:DecrementQuantity(amount)
|
||||
else
|
||||
assert(amount == 1 and amount == self._quantity)
|
||||
self._numAuctions = self._numAuctions - 1
|
||||
assert(self._numOwnerItems <= self._numAuctions)
|
||||
if self._numAuctions == 0 then
|
||||
self._resultRow:RemoveSubRow(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ResultSubRowWrapper.UpdateResultInfo(self, newAuctionId, newResultInfo)
|
||||
if newResultInfo then
|
||||
self:_SetRawData(newResultInfo, self._browseId)
|
||||
else
|
||||
self._auctionId = newAuctionId
|
||||
self._hash = nil
|
||||
self._hashNoSeller = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- ResultRowWrapper - Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ResultSubRowWrapper._SetRawData(self, data, browseId, itemLink)
|
||||
self._hash = nil
|
||||
self._hashNoSeller = nil
|
||||
self._browseId = browseId
|
||||
if data then
|
||||
if TSM.IsWowClassic() then
|
||||
local _, _, stackSize, _, _, _, _, minBid, minIncrement, buyout, bid, isHighBidder, _, seller, sellerFull = GetAuctionItemInfo("list", data)
|
||||
seller = Util.FixSellerName(seller, sellerFull)
|
||||
-- this is to get around a bug in Blizzard's code where the minIncrement value will be inconsistent for auctions where the player is the highest bidder
|
||||
minIncrement = isHighBidder and 0 or minIncrement
|
||||
self._itemLink = itemLink
|
||||
self._buyout = buyout
|
||||
self._minBid = minBid
|
||||
self._currentBid = bid
|
||||
self._minIncrement = minIncrement
|
||||
self._isHighBidder = isHighBidder
|
||||
self._quantity = stackSize
|
||||
self._timeLeft = GetAuctionItemTimeLeft("list", data)
|
||||
self._ownerStr = seller or "?"
|
||||
self._hasOwners = seller and true or false
|
||||
self._numOwnerItems = 0
|
||||
self._auctionId = 0
|
||||
else
|
||||
if self._resultRow:IsCommodity() then
|
||||
local baseItemString = self._resultRow:GetBaseItemString()
|
||||
self._itemLink = ItemInfo.GetLink(baseItemString)
|
||||
else
|
||||
self._itemLink = data.itemLink
|
||||
end
|
||||
|
||||
if self:IsCommodity() then
|
||||
self._quantity = data.quantity
|
||||
self._buyout = data.unitPrice * data.quantity
|
||||
self._minBid = self._buyout
|
||||
self._currentBid = 0
|
||||
self._minIncrement = 0
|
||||
self._isHighBidder = data.bidder and data.bidder == UnitGUID("player") or false
|
||||
self._numOwnerItems = data.numOwnerItems or 0
|
||||
-- convert the timeLeftSeconds to regular timeLeft
|
||||
if data.timeLeftSeconds < 60 * 60 then
|
||||
self._timeLeft = 1
|
||||
elseif data.timeLeftSeconds < 2 * 60 * 60 then
|
||||
self._timeLeft = 2
|
||||
elseif data.timeLeftSeconds < 12 * 60 * 60 then
|
||||
self._timeLeft = 3
|
||||
else
|
||||
self._timeLeft = 4
|
||||
end
|
||||
else
|
||||
self._quantity = 1
|
||||
self._numAuctions = data.quantity
|
||||
self._buyout = data.buyoutAmount or 0
|
||||
self._minBid = data.minBid or data.buyoutAmount
|
||||
self._currentBid = data.bidAmount or 0
|
||||
self._minIncrement = 0
|
||||
self._isHighBidder = false
|
||||
self._numOwnerItems = data.containsAccountItem and data.quantity or 0
|
||||
self._timeLeft = data.timeLeft + 1
|
||||
end
|
||||
|
||||
self._hasOwners = #data.owners > 0
|
||||
assert(#private.ownersTemp == 0)
|
||||
for _, owner in ipairs(data.owners) do
|
||||
if owner == "player" then
|
||||
owner = UnitName("player")
|
||||
elseif owner == "" then
|
||||
owner = "?"
|
||||
self._hasOwners = false
|
||||
end
|
||||
tinsert(private.ownersTemp, owner)
|
||||
end
|
||||
self._ownerStr = table.concat(private.ownersTemp, ",")
|
||||
wipe(private.ownersTemp)
|
||||
self._auctionId = data.auctionID
|
||||
end
|
||||
assert(self._itemLink and self._quantity and self._buyout and self._minBid and self._currentBid and self._numOwnerItems and self._timeLeft and self._ownerStr and self._auctionId)
|
||||
else
|
||||
self._itemLink = nil
|
||||
self._buyout = nil
|
||||
self._minBid = nil
|
||||
self._currentBid = nil
|
||||
self._minIncrement = nil
|
||||
self._isHighBidder = nil
|
||||
self._quantity = nil
|
||||
self._timeLeft = nil
|
||||
self._ownerStr = nil
|
||||
self._hasOwners = false
|
||||
self._numOwnerItems = nil
|
||||
self._auctionId = nil
|
||||
end
|
||||
end
|
||||
620
LibTSM/Service/AuctionScanClasses/ScanManager.lua
Normal file
620
LibTSM/Service/AuctionScanClasses/ScanManager.lua
Normal file
@@ -0,0 +1,620 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
-- This file contains code for scanning the auction house
|
||||
local _, TSM = ...
|
||||
local ScanManager = TSM.Init("Service.AuctionScanClasses.ScanManager")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local Math = TSM.Include("Util.Math")
|
||||
local ObjectPool = TSM.Include("Util.ObjectPool")
|
||||
local AuctionHouseWrapper = TSM.Include("Service.AuctionHouseWrapper")
|
||||
local Threading = TSM.Include("Service.Threading")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local Query = TSM.Include("Service.AuctionScanClasses.Query")
|
||||
local QueryUtil = TSM.Include("Service.AuctionScanClasses.QueryUtil")
|
||||
local AuctionScanManager = TSM.Include("LibTSMClass").DefineClass("AuctionScanManager")
|
||||
local private = {
|
||||
objectPool = ObjectPool.New("AUCTION_SCAN_MANAGER", AuctionScanManager),
|
||||
}
|
||||
-- arbitrary estimate that finishing the browse request is worth 10% of the query's progress
|
||||
local BROWSE_PROGRESS = 0.1
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function ScanManager.Get()
|
||||
return private.objectPool:Get()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Class Meta Methods
|
||||
-- ============================================================================
|
||||
|
||||
function AuctionScanManager.__init(self)
|
||||
self._resolveSellers = nil
|
||||
self._ignoreItemLevel = nil
|
||||
self._queries = {}
|
||||
self._queriesScanned = 0
|
||||
self._queryDidBrowse = false
|
||||
self._onProgressUpdateHandler = nil
|
||||
self._onQueryDoneHandler = nil
|
||||
self._resultsUpdateCallbacks = {}
|
||||
self._nextSearchItemFunction = nil
|
||||
self._currentSearchChangedCallback = nil
|
||||
self._findResult = {}
|
||||
self._cancelled = false
|
||||
self._shouldPause = false
|
||||
self._paused = false
|
||||
self._scanQuery = nil
|
||||
self._findQuery = nil
|
||||
self._numItems = nil
|
||||
self._queryCallback = function(query, searchRow)
|
||||
for func in pairs(self._resultsUpdateCallbacks) do
|
||||
func(self, query, searchRow)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionScanManager._Release(self)
|
||||
self._resolveSellers = nil
|
||||
self._ignoreItemLevel = nil
|
||||
for _, query in ipairs(self._queries) do
|
||||
query:Release()
|
||||
end
|
||||
wipe(self._queries)
|
||||
self._queriesScanned = 0
|
||||
self._queryDidBrowse = false
|
||||
self._onProgressUpdateHandler = nil
|
||||
self._onQueryDoneHandler = nil
|
||||
wipe(self._resultsUpdateCallbacks)
|
||||
self._nextSearchItemFunction = nil
|
||||
self._currentSearchChangedCallback = nil
|
||||
self._cancelled = false
|
||||
self._shouldPause = false
|
||||
self._paused = false
|
||||
wipe(self._findResult)
|
||||
self._scanQuery = nil
|
||||
if self._findQuery then
|
||||
self._findQuery:Release()
|
||||
self._findQuery = nil
|
||||
end
|
||||
self._numItems = nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function AuctionScanManager.Release(self)
|
||||
self:_Release()
|
||||
private.objectPool:Recycle(self)
|
||||
end
|
||||
|
||||
function AuctionScanManager.SetResolveSellers(self, resolveSellers)
|
||||
self._resolveSellers = resolveSellers
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionScanManager.SetIgnoreItemLevel(self, ignoreItemLevel)
|
||||
self._ignoreItemLevel = ignoreItemLevel
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionScanManager.SetScript(self, script, handler)
|
||||
if script == "OnProgressUpdate" then
|
||||
self._onProgressUpdateHandler = handler
|
||||
elseif script == "OnQueryDone" then
|
||||
self._onQueryDoneHandler = handler
|
||||
elseif script == "OnCurrentSearchChanged" then
|
||||
self._currentSearchChangedCallback = handler
|
||||
else
|
||||
error("Unknown AuctionScanManager script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function AuctionScanManager.AddResultsUpdateCallback(self, func)
|
||||
self._resultsUpdateCallbacks[func] = true
|
||||
end
|
||||
|
||||
function AuctionScanManager.RemoveResultsUpdateCallback(self, func)
|
||||
self._resultsUpdateCallbacks[func] = nil
|
||||
end
|
||||
|
||||
function AuctionScanManager.SetNextSearchItemFunction(self, func)
|
||||
self._nextSearchItemFunction = func
|
||||
end
|
||||
|
||||
function AuctionScanManager.GetNumQueries(self)
|
||||
return #self._queries
|
||||
end
|
||||
|
||||
function AuctionScanManager.QueryIterator(self, offset)
|
||||
return private.QueryIteratorHelper, self._queries, offset or 0
|
||||
end
|
||||
|
||||
function AuctionScanManager.NewQuery(self)
|
||||
local query = Query.Get()
|
||||
self:_AddQuery(query)
|
||||
return query
|
||||
end
|
||||
|
||||
function AuctionScanManager.AddItemListQueriesThreaded(self, itemList)
|
||||
assert(Threading.IsThreadContext())
|
||||
-- remove duplicates
|
||||
local usedItems = TempTable.Acquire()
|
||||
for i = #itemList, 1, -1 do
|
||||
local itemString = itemList[i]
|
||||
if usedItems[itemString] then
|
||||
tremove(itemList, i)
|
||||
end
|
||||
usedItems[itemString] = true
|
||||
end
|
||||
TempTable.Release(usedItems)
|
||||
self._numItems = #itemList
|
||||
QueryUtil.GenerateThreaded(itemList, private.NewQueryCallback, self)
|
||||
end
|
||||
|
||||
function AuctionScanManager.ScanQueriesThreaded(self)
|
||||
assert(Threading.IsThreadContext())
|
||||
self._queriesScanned = 0
|
||||
self._cancelled = false
|
||||
AuctionHouseWrapper.GetAndResetTotalHookedTime()
|
||||
self:_NotifyProgressUpdate()
|
||||
|
||||
-- loop through each filter to perform
|
||||
local allSuccess = true
|
||||
while self._queriesScanned < #self._queries do
|
||||
local query = self._queries[self._queriesScanned + 1]
|
||||
-- run the browse query
|
||||
local querySuccess, numNewResults = self:_ProcessQuery(query)
|
||||
if not querySuccess then
|
||||
allSuccess = false
|
||||
break
|
||||
end
|
||||
self._queriesScanned = self._queriesScanned + 1
|
||||
self:_NotifyProgressUpdate()
|
||||
if self._onQueryDoneHandler then
|
||||
self:_onQueryDoneHandler(query, numNewResults)
|
||||
end
|
||||
self:_Pause()
|
||||
end
|
||||
|
||||
if allSuccess then
|
||||
local hookedTime, topAddon, topTime = AuctionHouseWrapper.GetAndResetTotalHookedTime()
|
||||
if hookedTime > 1 and topAddon ~= "Blizzard_AuctionHouseUI" then
|
||||
Log.PrintfUser(L["Scan was slowed down by %s seconds by other AH addons (%s seconds by %s)."], Math.Round(hookedTime, 0.1), Math.Round(topTime, 0.1), topAddon)
|
||||
end
|
||||
end
|
||||
return allSuccess
|
||||
end
|
||||
|
||||
function AuctionScanManager.FindAuctionThreaded(self, findSubRow, noSeller)
|
||||
assert(Threading.IsThreadContext())
|
||||
wipe(self._findResult)
|
||||
if TSM.IsWowClassic() then
|
||||
return self:_FindAuctionThreaded(findSubRow, noSeller)
|
||||
else
|
||||
return self:_FindAuctionThreaded83(findSubRow, noSeller)
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionScanManager.PrepareForBidOrBuyout(self, index, subRow, noSeller, quantity, itemBuyout)
|
||||
if TSM.IsWowClassic() then
|
||||
return subRow:EqualsIndex(index, noSeller)
|
||||
else
|
||||
local itemString = subRow:GetItemString()
|
||||
if ItemInfo.IsCommodity(itemString) then
|
||||
local future = AuctionHouseWrapper.StartCommoditiesPurchase(ItemString.ToId(itemString), quantity, itemBuyout)
|
||||
if not future then
|
||||
return false
|
||||
end
|
||||
return true, future
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionScanManager.PlaceBidOrBuyout(self, index, bidBuyout, subRow, quantity)
|
||||
if TSM.IsWowClassic() then
|
||||
PlaceAuctionBid("list", index, bidBuyout)
|
||||
return true
|
||||
else
|
||||
local itemString = subRow:GetItemString()
|
||||
local future = nil
|
||||
if ItemInfo.IsCommodity(itemString) then
|
||||
local itemId = ItemString.ToId(itemString)
|
||||
future = AuctionHouseWrapper.ConfirmCommoditiesPurchase(itemId, quantity)
|
||||
else
|
||||
local _, auctionId = subRow:GetListingInfo()
|
||||
future = AuctionHouseWrapper.PlaceBid(auctionId, bidBuyout)
|
||||
quantity = 1
|
||||
end
|
||||
if not future then
|
||||
return false
|
||||
end
|
||||
-- TODO: return this future and record the buyout once the future is done
|
||||
future:Cancel()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionScanManager.GetProgress(self)
|
||||
local numQueries = self:GetNumQueries()
|
||||
if self._queriesScanned == numQueries then
|
||||
return 1
|
||||
end
|
||||
local currentQuery = self._queries[self._queriesScanned + 1]
|
||||
local searchProgress = nil
|
||||
if not self._queryDidBrowse or TSM.IsWowClassic() then
|
||||
searchProgress = 0
|
||||
else
|
||||
searchProgress = currentQuery:GetSearchProgress() * (1 - BROWSE_PROGRESS) + BROWSE_PROGRESS
|
||||
end
|
||||
local queryStep = 1 / numQueries
|
||||
local progress = min((self._queriesScanned + searchProgress) * queryStep, 1)
|
||||
return progress, self._paused
|
||||
end
|
||||
|
||||
function AuctionScanManager.Cancel(self)
|
||||
self._cancelled = true
|
||||
if self._scanQuery then
|
||||
self._scanQuery:CancelBrowseOrSearch()
|
||||
self._scanQuery = nil
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionScanManager.SetPaused(self, paused)
|
||||
self._shouldPause = paused
|
||||
if self._scanQuery then
|
||||
self._scanQuery:CancelBrowseOrSearch()
|
||||
self._scanQuery = nil
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionScanManager.GetNumItems(self)
|
||||
return self._numItems
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function AuctionScanManager._AddQuery(self, query)
|
||||
query:SetResolveSellers(self._resolveSellers)
|
||||
query:SetCallback(self._queryCallback)
|
||||
tinsert(self._queries, query)
|
||||
end
|
||||
|
||||
function AuctionScanManager._IsCancelled(self)
|
||||
return self._cancelled
|
||||
end
|
||||
|
||||
function AuctionScanManager._Pause(self)
|
||||
if not self._shouldPause then
|
||||
return
|
||||
end
|
||||
self._paused = true
|
||||
self:_NotifyProgressUpdate()
|
||||
if self._currentSearchChangedCallback then
|
||||
self:_currentSearchChangedCallback()
|
||||
end
|
||||
while self._shouldPause do
|
||||
Threading.Yield(true)
|
||||
end
|
||||
self._paused = false
|
||||
self:_NotifyProgressUpdate()
|
||||
if self._currentSearchChangedCallback then
|
||||
self:_currentSearchChangedCallback()
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionScanManager._NotifyProgressUpdate(self)
|
||||
if self._onProgressUpdateHandler then
|
||||
self:_onProgressUpdateHandler()
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionScanManager._ProcessQuery(self, query)
|
||||
local prevMaxBrowseId = 0
|
||||
for _, row in query:BrowseResultsIterator() do
|
||||
prevMaxBrowseId = max(prevMaxBrowseId, row:GetMinBrowseId())
|
||||
end
|
||||
|
||||
-- run the browse query
|
||||
self._queryDidBrowse = false
|
||||
while not self:_DoBrowse(query) do
|
||||
if self._shouldPause then
|
||||
-- this browse failed due to a pause request, so try again after we're resumed
|
||||
self:_Pause()
|
||||
-- wipe the browse results since we're going to do another search
|
||||
query:WipeBrowseResults()
|
||||
else
|
||||
return false, 0
|
||||
end
|
||||
end
|
||||
self._queryDidBrowse = true
|
||||
self:_NotifyProgressUpdate()
|
||||
|
||||
local numNewResults = 0
|
||||
if TSM.IsWowClassic() then
|
||||
for _, row in query:BrowseResultsIterator() do
|
||||
if row:GetMinBrowseId() > prevMaxBrowseId then
|
||||
numNewResults = numNewResults + row:GetNumSubRows()
|
||||
end
|
||||
end
|
||||
return true, numNewResults
|
||||
end
|
||||
|
||||
local rows = Threading.AcquireSafeTempTable()
|
||||
for baseItemString, row in query:BrowseResultsIterator() do
|
||||
rows[baseItemString] = row
|
||||
end
|
||||
while true do
|
||||
local baseItemString, row = nil, nil
|
||||
if self._nextSearchItemFunction then
|
||||
baseItemString = self._nextSearchItemFunction()
|
||||
row = baseItemString and rows[baseItemString]
|
||||
end
|
||||
if not row then
|
||||
baseItemString, row = next(rows)
|
||||
end
|
||||
if not row then
|
||||
break
|
||||
end
|
||||
rows[baseItemString] = nil
|
||||
if self._currentSearchChangedCallback then
|
||||
self:_currentSearchChangedCallback(baseItemString)
|
||||
end
|
||||
-- store all the existing auctionIds so we can see what changed
|
||||
local prevAuctionIds = Threading.AcquireSafeTempTable()
|
||||
for _, subRow in row:SubRowIterator() do
|
||||
local _, auctionId = subRow:GetListingInfo()
|
||||
assert(not prevAuctionIds[auctionId])
|
||||
prevAuctionIds[auctionId] = true
|
||||
end
|
||||
-- send the query for this item
|
||||
while not self:_DoSearch(query, row) do
|
||||
if self._shouldPause then
|
||||
-- this search failed due to a pause request, so try again after we're resumed
|
||||
self:_Pause()
|
||||
-- wipe the search results since we're going to do another search
|
||||
row:WipeSearchResults()
|
||||
else
|
||||
Threading.ReleaseSafeTempTable(prevAuctionIds)
|
||||
Threading.ReleaseSafeTempTable(rows)
|
||||
return false, numNewResults
|
||||
end
|
||||
end
|
||||
|
||||
local numSubRows = row:GetNumSubRows()
|
||||
for _, subRow in row:SubRowIterator() do
|
||||
local _, auctionId = subRow:GetListingInfo()
|
||||
if not prevAuctionIds[auctionId] then
|
||||
numNewResults = numNewResults + 1
|
||||
end
|
||||
end
|
||||
Threading.ReleaseSafeTempTable(prevAuctionIds)
|
||||
if numSubRows == 0 then
|
||||
-- remove this row since there are no search results
|
||||
query:RemoveResultRow(row)
|
||||
end
|
||||
|
||||
self:_NotifyProgressUpdate()
|
||||
self:_Pause()
|
||||
Threading.Yield()
|
||||
end
|
||||
Threading.ReleaseSafeTempTable(rows)
|
||||
return true, numNewResults
|
||||
end
|
||||
|
||||
function AuctionScanManager._DoBrowse(self, query, ...)
|
||||
return self:_DoBrowseSearchHelper(query, query:Browse(...))
|
||||
end
|
||||
|
||||
function AuctionScanManager._DoSearch(self, query, ...)
|
||||
return self:_DoBrowseSearchHelper(query, query:Search(...))
|
||||
end
|
||||
|
||||
function AuctionScanManager._DoBrowseSearchHelper(self, query, future)
|
||||
if not future then
|
||||
return false
|
||||
end
|
||||
self._scanQuery = query
|
||||
local result = Threading.WaitForFuture(future)
|
||||
self._scanQuery = nil
|
||||
Threading.Yield()
|
||||
return result
|
||||
end
|
||||
|
||||
function AuctionScanManager._FindAuctionThreaded(self, row, noSeller)
|
||||
self._cancelled = false
|
||||
-- make sure we're not in the middle of a query where the results are going to change on us
|
||||
Threading.WaitForFunction(CanSendAuctionQuery)
|
||||
|
||||
-- search the current page for the auction
|
||||
if self:_FindAuctionOnCurrentPage(row, noSeller) then
|
||||
Log.Info("Found on current page")
|
||||
return self._findResult
|
||||
end
|
||||
|
||||
-- search for the item
|
||||
local page, maxPage = 0, nil
|
||||
while true do
|
||||
-- query the AH
|
||||
if self._findQuery then
|
||||
self._findQuery:Release()
|
||||
end
|
||||
local itemString = row:GetItemString()
|
||||
local level = ItemInfo.GetMinLevel(itemString)
|
||||
local quality = ItemInfo.GetQuality(itemString)
|
||||
assert(level and quality)
|
||||
self._findQuery = Query.Get()
|
||||
:SetStr(ItemInfo.GetName(itemString), true)
|
||||
:SetQualityRange(quality, quality)
|
||||
:SetLevelRange(level, level)
|
||||
:SetClass(ItemInfo.GetClassId(itemString), ItemInfo.GetSubClassId(itemString))
|
||||
:SetItems(itemString)
|
||||
:SetResolveSellers(not noSeller)
|
||||
:SetPage(page)
|
||||
local filterSuccess = self:_DoBrowse(self._findQuery)
|
||||
if self._findQuery then
|
||||
self._findQuery:Release()
|
||||
self._findQuery = nil
|
||||
end
|
||||
if not filterSuccess then
|
||||
break
|
||||
end
|
||||
-- search this page for the row
|
||||
if self:_FindAuctionOnCurrentPage(row, noSeller) then
|
||||
Log.Info("Found auction (%d)", page)
|
||||
return self._findResult
|
||||
elseif self:_IsCancelled() then
|
||||
break
|
||||
end
|
||||
|
||||
local numPages = ceil(select(2, GetNumAuctionItems("list")) / NUM_AUCTION_ITEMS_PER_PAGE)
|
||||
local canBeLater = private.FindAuctionCanBeOnLaterPage(row)
|
||||
maxPage = maxPage or numPages - 1
|
||||
if not canBeLater and page < maxPage then
|
||||
maxPage = page
|
||||
end
|
||||
if canBeLater and page < maxPage then
|
||||
Log.Info("Trying next page (%d)", page + 1)
|
||||
page = page + 1
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionScanManager._FindAuctionOnCurrentPage(self, subRow, noSeller)
|
||||
local found = false
|
||||
for i = 1, GetNumAuctionItems("list") do
|
||||
if subRow:EqualsIndex(i, noSeller) then
|
||||
tinsert(self._findResult, i)
|
||||
found = true
|
||||
end
|
||||
end
|
||||
return found
|
||||
end
|
||||
|
||||
function AuctionScanManager._FindAuctionThreaded83(self, findSubRow, noSeller)
|
||||
assert(findSubRow:IsSubRow())
|
||||
self._cancelled = false
|
||||
noSeller = noSeller or findSubRow:IsCommodity()
|
||||
|
||||
local row = findSubRow:GetResultRow()
|
||||
local findHash, findHashNoSeller = findSubRow:GetHashes()
|
||||
|
||||
if not self:_DoSearch(row:GetQuery(), row, false) then
|
||||
return nil
|
||||
end
|
||||
local result = nil
|
||||
-- first try to find a subRow with a full matching hash
|
||||
for _, subRow in row:SubRowIterator() do
|
||||
local quantity, numAuctions = subRow:GetQuantities()
|
||||
local hash = subRow:GetHashes()
|
||||
if hash == findHash then
|
||||
result = (result or 0) + quantity * numAuctions
|
||||
end
|
||||
end
|
||||
if result then
|
||||
return result
|
||||
end
|
||||
-- next try to find the first subRow with a matching no-seller hash
|
||||
local firstHash = nil
|
||||
for _, subRow in row:SubRowIterator() do
|
||||
local quantity, numAuctions = subRow:GetQuantities()
|
||||
local hash, hashNoSeller = subRow:GetHashes()
|
||||
if (not firstHash or hash == firstHash) and hashNoSeller == findHashNoSeller then
|
||||
firstHash = hash
|
||||
result = (result or 0) + quantity * numAuctions
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.NewQueryCallback(query, self)
|
||||
self:_AddQuery(query)
|
||||
end
|
||||
|
||||
function private.FindAuctionCanBeOnLaterPage(row)
|
||||
local pageAuctions = GetNumAuctionItems("list")
|
||||
if pageAuctions == 0 then
|
||||
-- there are no auctions on this page, so it cannot be on a later one
|
||||
return false
|
||||
end
|
||||
local _, _, stackSize, _, _, _, _, _, _, buyout, _, _, _, seller, sellerFull = GetAuctionItemInfo("list", pageAuctions)
|
||||
|
||||
local itemBuyout = (buyout > 0) and floor(buyout / stackSize) or 0
|
||||
local _, rowItemBuyout = row:GetBuyouts()
|
||||
if rowItemBuyout > itemBuyout then
|
||||
-- item must be on a later page since it would be sorted after the last auction on this page
|
||||
return true
|
||||
elseif rowItemBuyout < itemBuyout then
|
||||
-- item cannot be on a later page since it would be sorted before the last auction on this page
|
||||
return false
|
||||
end
|
||||
|
||||
local rowStackSize = row:GetQuantities()
|
||||
if rowStackSize > stackSize then
|
||||
-- item must be on a later page since it would be sorted after the last auction on this page
|
||||
return true
|
||||
elseif rowStackSize < stackSize then
|
||||
-- item cannot be on a later page since it would be sorted before the last auction on this page
|
||||
return false
|
||||
end
|
||||
|
||||
seller = private.FixSellerName(seller, sellerFull) or "?"
|
||||
local rowSeller = row:GetOwnerInfo()
|
||||
if rowSeller > seller then
|
||||
-- item must be on a later page since it would be sorted after the last auction on this page
|
||||
return true
|
||||
elseif rowSeller < seller then
|
||||
-- item cannot be on a later page since it would be sorted before the last auction on this page
|
||||
return false
|
||||
end
|
||||
|
||||
-- all the things we are sorting on are the same, so the auction could be on a later page
|
||||
return true
|
||||
end
|
||||
|
||||
function private.FixSellerName(seller, sellerFull)
|
||||
local realm = GetRealmName()
|
||||
if sellerFull and strjoin("-", seller, realm) ~= sellerFull then
|
||||
return sellerFull
|
||||
else
|
||||
return seller
|
||||
end
|
||||
end
|
||||
|
||||
function private.QueryIteratorHelper(tbl, index)
|
||||
index = index + 1
|
||||
if index > #tbl then
|
||||
return
|
||||
end
|
||||
return index, tbl[index]
|
||||
end
|
||||
511
LibTSM/Service/AuctionScanClasses/Scanner.lua
Normal file
511
LibTSM/Service/AuctionScanClasses/Scanner.lua
Normal file
@@ -0,0 +1,511 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Scanner = TSM.Init("Service.AuctionScanClasses.Scanner")
|
||||
local Delay = TSM.Include("Util.Delay")
|
||||
local FSM = TSM.Include("Util.FSM")
|
||||
local Future = TSM.Include("Util.Future")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local Event = TSM.Include("Util.Event")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local AuctionHouseWrapper = TSM.Include("Service.AuctionHouseWrapper")
|
||||
local Util = TSM.Include("Service.AuctionScanClasses.Util")
|
||||
local ResultRow = TSM.Include("Service.AuctionScanClasses.ResultRow")
|
||||
local private = {
|
||||
resolveSellers = nil,
|
||||
pendingFuture = nil,
|
||||
query = nil,
|
||||
browseResults = nil,
|
||||
callback = nil,
|
||||
browseId = 1,
|
||||
browseIsNoScan = false,
|
||||
browseIndex = 1,
|
||||
browsePendingIndexes = {},
|
||||
searchRow = nil,
|
||||
useCachedData = nil,
|
||||
retryCount = 0,
|
||||
requestFuture = Future.New("AUCTION_SCANNER_FUTURE"),
|
||||
requestResult = nil,
|
||||
fsm = nil,
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Loading
|
||||
-- ============================================================================
|
||||
|
||||
Scanner:OnModuleLoad(function()
|
||||
private.requestFuture:SetScript("OnCleanup", function()
|
||||
Delay.Cancel("AUCTION_SCANNER_DONE")
|
||||
private.fsm:ProcessEvent("EV_CANCEL")
|
||||
end)
|
||||
|
||||
if TSM.IsWowClassic() then
|
||||
Event.Register("AUCTION_ITEM_LIST_UPDATE", function()
|
||||
private.fsm:SetLoggingEnabled(false)
|
||||
private.fsm:ProcessEvent("EV_BROWSE_RESULTS_UPDATED")
|
||||
private.fsm:SetLoggingEnabled(true)
|
||||
end)
|
||||
else
|
||||
Event.Register("COMMODITY_SEARCH_RESULTS_UPDATED", function()
|
||||
private.fsm:ProcessEvent("EV_SEARCH_RESULTS_UPDATED")
|
||||
end)
|
||||
Event.Register("ITEM_SEARCH_RESULTS_UPDATED", function()
|
||||
private.fsm:ProcessEvent("EV_SEARCH_RESULTS_UPDATED")
|
||||
end)
|
||||
end
|
||||
|
||||
private.fsm = FSM.New("AUCTION_SCANNER_FSM")
|
||||
:AddState(FSM.NewState("ST_INIT")
|
||||
:SetOnEnter(function()
|
||||
private.query = nil
|
||||
private.resolveSellers = nil
|
||||
private.useCachedData = nil
|
||||
private.searchRow = nil
|
||||
private.callback = nil
|
||||
private.retryCount = 0
|
||||
Delay.Cancel("AUCTION_SCANNER_RETRY")
|
||||
if private.pendingFuture then
|
||||
private.pendingFuture:Cancel()
|
||||
private.pendingFuture = nil
|
||||
end
|
||||
end)
|
||||
:AddTransition("ST_BROWSE_SORT")
|
||||
:AddTransition("ST_BROWSE_CHECKING")
|
||||
:AddTransition("ST_SEARCH_GET_KEY")
|
||||
:AddEvent("EV_START_BROWSE", function(_, query, resolveSellers, browseResults, callback)
|
||||
assert(not private.query)
|
||||
private.query = query
|
||||
private.resolveSellers = resolveSellers
|
||||
private.browseResults = browseResults
|
||||
private.browseId = private.browseId + 1
|
||||
private.browseIsNoScan = false
|
||||
private.callback = callback
|
||||
return "ST_BROWSE_SORT"
|
||||
end)
|
||||
:AddEvent("EV_START_BROWSE_NO_SCAN", function(_, query, itemKeys, browseResults, callback)
|
||||
assert(not TSM.IsWowClassic())
|
||||
assert(not private.query)
|
||||
private.query = query
|
||||
private.browseResults = browseResults
|
||||
private.browseId = private.browseId + 1
|
||||
private.browseIsNoScan = true
|
||||
private.callback = callback
|
||||
for _, itemKey in ipairs(itemKeys) do
|
||||
local baseItemString = ItemString.GetBaseFromItemKey(itemKey)
|
||||
private.ProcessBrowseResult(baseItemString, itemKey)
|
||||
end
|
||||
return "ST_BROWSE_CHECKING"
|
||||
end)
|
||||
:AddEvent("EV_START_SEARCH", function(_, query, resolveSellers, useCachedData, searchRow, callback)
|
||||
assert(not TSM.IsWowClassic())
|
||||
assert(not private.query)
|
||||
private.query = query
|
||||
private.resolveSellers = resolveSellers
|
||||
private.useCachedData = useCachedData
|
||||
private.searchRow = searchRow
|
||||
private.callback = callback
|
||||
private.searchRow:SearchReset()
|
||||
return "ST_SEARCH_GET_KEY"
|
||||
end)
|
||||
)
|
||||
:AddState(FSM.NewState("ST_BROWSE_SORT")
|
||||
:SetOnEnter(function()
|
||||
if not private.query:_SetSort() then
|
||||
Delay.AfterTime("AUCTION_SCANNER_RETRY", 0.5, private.RetryHandler)
|
||||
return
|
||||
end
|
||||
return "ST_BROWSE_SEND"
|
||||
end)
|
||||
:AddTransition("ST_BROWSE_SORT")
|
||||
:AddTransition("ST_BROWSE_SEND")
|
||||
:AddTransition("ST_CANCELING")
|
||||
:AddEventTransition("EV_RETRY", "ST_BROWSE_SORT")
|
||||
:AddEventTransition("EV_CANCEL", "ST_CANCELING")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_BROWSE_SEND")
|
||||
:SetOnEnter(function()
|
||||
private.HandleAuctionHouseWrapperResult(private.query:_SendWowQuery())
|
||||
end)
|
||||
:AddTransition("ST_BROWSE_SEND")
|
||||
:AddTransition("ST_BROWSE_CHECKING")
|
||||
:AddTransition("ST_CANCELING")
|
||||
:AddEvent("EV_FUTURE_SUCCESS", function()
|
||||
if TSM.IsWowClassic() then
|
||||
private.browseIndex = 1
|
||||
wipe(private.browsePendingIndexes)
|
||||
else
|
||||
for _, result in ipairs(C_AuctionHouse.GetBrowseResults()) do
|
||||
local baseItemString = ItemString.GetBaseFromItemKey(result.itemKey)
|
||||
private.ProcessBrowseResult(baseItemString, result.itemKey, result.minPrice, result.totalQuantity)
|
||||
end
|
||||
end
|
||||
return "ST_BROWSE_CHECKING"
|
||||
end)
|
||||
:AddEventTransition("EV_RETRY", "ST_BROWSE_SEND")
|
||||
:AddEventTransition("EV_CANCEL", "ST_CANCELING")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_BROWSE_CHECKING")
|
||||
:SetOnEnter(function()
|
||||
if not private.query:_BrowseIsPageValid() then
|
||||
-- this page isn't valid, so go to the next page
|
||||
return "ST_BROWSE_REQUEST_MORE"
|
||||
elseif not private.CheckBrowseResults() then
|
||||
-- result's aren't valid yet, so check again
|
||||
Delay.AfterFrame("AUCTION_SCANNER_RETRY", 1, private.RetryHandler)
|
||||
return
|
||||
end
|
||||
-- we're done with this set of browse results
|
||||
if private.callback then
|
||||
private.callback(private.query)
|
||||
end
|
||||
if private.browseIsNoScan or private.query:_BrowseIsDone() then
|
||||
-- we're done
|
||||
return "ST_BROWSE_DONE"
|
||||
else
|
||||
-- move on to the next page
|
||||
return "ST_BROWSE_REQUEST_MORE"
|
||||
end
|
||||
end)
|
||||
:AddTransition("ST_BROWSE_CHECKING")
|
||||
:AddTransition("ST_BROWSE_DONE")
|
||||
:AddTransition("ST_BROWSE_REQUEST_MORE")
|
||||
:AddTransition("ST_CANCELING")
|
||||
:AddEventTransition("EV_RETRY", "ST_BROWSE_CHECKING")
|
||||
:AddEventTransition("EV_BROWSE_RESULTS_UPDATED", "ST_BROWSE_CHECKING")
|
||||
:AddEventTransition("EV_CANCEL", "ST_CANCELING")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_BROWSE_REQUEST_MORE")
|
||||
:SetOnEnter(function(_, isRetry)
|
||||
if private.query:_BrowseIsDone(isRetry) then
|
||||
return "ST_BROWSE_CHECKING"
|
||||
else
|
||||
private.HandleAuctionHouseWrapperResult(private.query:_BrowseRequestMore(isRetry))
|
||||
end
|
||||
end)
|
||||
:AddTransition("ST_BROWSE_REQUEST_MORE")
|
||||
:AddTransition("ST_BROWSE_CHECKING")
|
||||
:AddTransition("ST_CANCELING")
|
||||
:AddEvent("EV_FUTURE_SUCCESS", function(_, ...)
|
||||
if TSM.IsWowClassic() then
|
||||
private.browseIndex = 1
|
||||
wipe(private.browsePendingIndexes)
|
||||
else
|
||||
local newResults = ...
|
||||
for _, result in ipairs(newResults) do
|
||||
local baseItemString = ItemString.GetBaseFromItemKey(result.itemKey)
|
||||
private.ProcessBrowseResult(baseItemString, result.itemKey, result.minPrice, result.totalQuantity)
|
||||
end
|
||||
end
|
||||
return "ST_BROWSE_CHECKING"
|
||||
end)
|
||||
:AddEvent("EV_RETRY", function()
|
||||
return "ST_BROWSE_REQUEST_MORE", true
|
||||
end)
|
||||
:AddEventTransition("EV_CANCEL", "ST_CANCELING")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_BROWSE_DONE")
|
||||
:SetOnEnter(function()
|
||||
private.HandleRequestDone(true)
|
||||
return "ST_INIT"
|
||||
end)
|
||||
:AddTransition("ST_INIT")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_SEARCH_GET_KEY")
|
||||
:SetOnEnter(function()
|
||||
assert(not TSM.IsWowClassic())
|
||||
if not private.searchRow:SearchIsReady() then
|
||||
Delay.AfterTime("AUCTION_SCANNER_RETRY", 0.1, private.RetryHandler)
|
||||
return
|
||||
end
|
||||
return "ST_SEARCH_SEND"
|
||||
end)
|
||||
:AddTransition("ST_SEARCH_GET_KEY")
|
||||
:AddTransition("ST_SEARCH_SEND")
|
||||
:AddTransition("ST_CANCELING")
|
||||
:AddEventTransition("EV_FUTURE_SUCCESS", "ST_SEARCH_SEND")
|
||||
:AddEventTransition("EV_RETRY", "ST_SEARCH_GET_KEY")
|
||||
:AddEventTransition("EV_CANCEL", "ST_CANCELING")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_SEARCH_SEND")
|
||||
:SetOnEnter(function()
|
||||
assert(not TSM.IsWowClassic())
|
||||
if not AuctionHouseWrapper.IsOpen() then
|
||||
return "ST_CANCELING"
|
||||
end
|
||||
if private.useCachedData and private.searchRow:HasCachedSearchData() then
|
||||
return "ST_SEARCH_REQUEST_MORE"
|
||||
end
|
||||
local future, delayTime = private.searchRow:SearchSend()
|
||||
if future then
|
||||
private.HandleAuctionHouseWrapperResult(future)
|
||||
else
|
||||
if not delayTime then
|
||||
Log.Err("Failed to send search query - retrying")
|
||||
delayTime = 0.5
|
||||
end
|
||||
-- try again after a delay
|
||||
Delay.AfterTime("AUCTION_SCANNER_RETRY", delayTime, private.RetryHandler)
|
||||
end
|
||||
end)
|
||||
:AddTransition("ST_SEARCH_SEND")
|
||||
:AddTransition("ST_SEARCH_REQUEST_MORE")
|
||||
:AddTransition("ST_CANCELING")
|
||||
:AddEventTransition("EV_FUTURE_SUCCESS", "ST_SEARCH_REQUEST_MORE")
|
||||
:AddEventTransition("EV_RETRY", "ST_SEARCH_SEND")
|
||||
:AddEventTransition("EV_CANCEL", "ST_CANCELING")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_SEARCH_REQUEST_MORE")
|
||||
:SetOnEnter(function()
|
||||
assert(not TSM.IsWowClassic())
|
||||
local baseItemString = private.searchRow:GetBaseItemString()
|
||||
-- get if the item is a commodity or not
|
||||
local isCommodity = ItemInfo.IsCommodity(baseItemString)
|
||||
if isCommodity == nil then
|
||||
Delay.AfterTime("AUCTION_SCANNER_RETRY", 0.1, private.RetryHandler)
|
||||
return
|
||||
end
|
||||
|
||||
local isDone, future = private.searchRow:SearchCheckStatus()
|
||||
if isDone then
|
||||
return "ST_SEARCH_CHECKING"
|
||||
elseif future then
|
||||
private.HandleAuctionHouseWrapperResult(future)
|
||||
else
|
||||
Delay.AfterTime("AUCTION_SCANNER_RETRY", 0.5, private.RetryHandler)
|
||||
end
|
||||
end)
|
||||
:AddTransition("ST_SEARCH_SEND")
|
||||
:AddTransition("ST_SEARCH_CHECKING")
|
||||
:AddTransition("ST_CANCELING")
|
||||
:AddEventTransition("EV_FUTURE_SUCCESS", "ST_SEARCH_CHECKING")
|
||||
:AddEventTransition("EV_RETRY", "ST_SEARCH_SEND")
|
||||
:AddEventTransition("EV_CANCEL", "ST_CANCELING")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_SEARCH_CHECKING")
|
||||
:SetOnEnter(function()
|
||||
assert(not TSM.IsWowClassic())
|
||||
Delay.Cancel("AUCTION_SCANNER_RETRY")
|
||||
private.searchRow:PopulateSubRows(private.browseId)
|
||||
|
||||
-- check if all the sub rows have their data
|
||||
local isDone = true
|
||||
for _, subRow in private.searchRow:SubRowIterator(true) do
|
||||
if not subRow:HasRawData() or not subRow:HasItemString() then
|
||||
isDone = false
|
||||
elseif private.resolveSellers and not subRow:HasOwners() and not private.query:_IsFiltered(subRow, true) then
|
||||
-- waiting for owner info
|
||||
isDone = false
|
||||
end
|
||||
end
|
||||
|
||||
if not isDone and private.retryCount >= 100 then
|
||||
-- out of retries, so give up
|
||||
return "ST_SEARCH_DONE", false
|
||||
elseif not isDone then
|
||||
-- we'll try again
|
||||
private.retryCount = private.retryCount + 1
|
||||
Delay.AfterTime("AUCTION_SCANNER_RETRY", 0.5, private.RetryHandler)
|
||||
return
|
||||
end
|
||||
|
||||
-- filter the sub rows we don't care about
|
||||
private.searchRow:FilterSubRows(private.query)
|
||||
|
||||
if private.callback then
|
||||
private.callback(private.query, private.searchRow)
|
||||
end
|
||||
if private.searchRow:SearchNext() then
|
||||
-- there is more to search
|
||||
return "ST_SEARCH_GET_KEY"
|
||||
else
|
||||
-- scanned everything
|
||||
return "ST_SEARCH_DONE", true
|
||||
end
|
||||
end)
|
||||
:AddTransition("ST_SEARCH_GET_KEY")
|
||||
:AddTransition("ST_SEARCH_CHECKING")
|
||||
:AddTransition("ST_SEARCH_DONE")
|
||||
:AddTransition("ST_CANCELING")
|
||||
:AddEventTransition("EV_RETRY", "ST_SEARCH_CHECKING")
|
||||
:AddEventTransition("EV_SEARCH_RESULTS_UPDATED", "ST_SEARCH_CHECKING")
|
||||
:AddEventTransition("EV_CANCEL", "ST_CANCELING")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_SEARCH_DONE")
|
||||
:SetOnEnter(function(_, result)
|
||||
assert(not TSM.IsWowClassic())
|
||||
private.HandleRequestDone(result)
|
||||
return "ST_INIT"
|
||||
end)
|
||||
:AddTransition("ST_INIT")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_CANCELING")
|
||||
:SetOnEnter(function()
|
||||
Delay.Cancel("AUCTION_SCANNER_DONE")
|
||||
return "ST_INIT"
|
||||
end)
|
||||
:AddTransition("ST_INIT")
|
||||
)
|
||||
:Init("ST_INIT", nil)
|
||||
end)
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Scanner.Browse(query, resolveSellers, results, callback)
|
||||
private.requestFuture:Start()
|
||||
private.fsm:ProcessEvent("EV_START_BROWSE", query, resolveSellers, results, callback)
|
||||
return private.requestFuture
|
||||
end
|
||||
|
||||
function Scanner.BrowseNoScan(query, itemKeys, results, callback)
|
||||
assert(not TSM.IsWowClassic())
|
||||
private.requestFuture:Start()
|
||||
private.fsm:ProcessEvent("EV_START_BROWSE_NO_SCAN", query, itemKeys, results, callback)
|
||||
return private.requestFuture
|
||||
end
|
||||
|
||||
function Scanner.Search(query, resolveSellers, useCachedData, browseRow, callback)
|
||||
assert(not TSM.IsWowClassic())
|
||||
private.requestFuture:Start()
|
||||
private.fsm:ProcessEvent("EV_START_SEARCH", query, resolveSellers, useCachedData, browseRow, callback)
|
||||
return private.requestFuture
|
||||
end
|
||||
|
||||
function Scanner.Cancel()
|
||||
private.requestFuture:Done(false)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.ProcessBrowseResult(baseItemString, ...)
|
||||
if private.browseResults[baseItemString] then
|
||||
private.browseResults[baseItemString]:Merge(...)
|
||||
else
|
||||
private.browseResults[baseItemString] = ResultRow.Get(private.query, ...)
|
||||
end
|
||||
return private.browseResults[baseItemString]
|
||||
end
|
||||
|
||||
function private.PendingFutureDoneHandler()
|
||||
local result = private.pendingFuture:GetValue()
|
||||
private.pendingFuture = nil
|
||||
if result then
|
||||
private.fsm:ProcessEvent("EV_FUTURE_SUCCESS", result)
|
||||
else
|
||||
Delay.AfterTime("AUCTION_SCANNER_RETRY", 0.1, private.RetryHandler)
|
||||
end
|
||||
end
|
||||
|
||||
function private.RetryHandler()
|
||||
private.fsm:SetLoggingEnabled(false)
|
||||
private.fsm:ProcessEvent("EV_RETRY")
|
||||
private.fsm:SetLoggingEnabled(true)
|
||||
end
|
||||
|
||||
function private.RequestDoneHandler()
|
||||
local result = private.requestResult
|
||||
private.requestResult = nil
|
||||
private.requestFuture:Done(result)
|
||||
end
|
||||
|
||||
function private.HandleAuctionHouseWrapperResult(future)
|
||||
if future then
|
||||
private.pendingFuture = future
|
||||
private.pendingFuture:SetScript("OnDone", private.PendingFutureDoneHandler)
|
||||
else
|
||||
Delay.AfterTime("AUCTION_SCANNER_RETRY", 0.1, private.RetryHandler)
|
||||
end
|
||||
end
|
||||
|
||||
function private.HandleRequestDone(result)
|
||||
private.requestResult = result
|
||||
-- delay a bit so that we complete our current FSM transition
|
||||
Delay.AfterTime("AUCTION_SCANNER_DONE", 0, private.RequestDoneHandler)
|
||||
end
|
||||
|
||||
function private.CheckBrowseResults()
|
||||
if TSM.IsWowClassic() then
|
||||
-- process as many auctions as we can
|
||||
local numAuctions = GetNumAuctionItems("list")
|
||||
for i = #private.browsePendingIndexes, 1, -1 do
|
||||
local index = private.browsePendingIndexes[i]
|
||||
if private.ProcessBrowseResultClassic(index) then
|
||||
tremove(private.browsePendingIndexes, i)
|
||||
end
|
||||
end
|
||||
local index = private.browseIndex
|
||||
while index <= numAuctions and #private.browsePendingIndexes < 50 do
|
||||
if not private.ProcessBrowseResultClassic(index) then
|
||||
tinsert(private.browsePendingIndexes, index)
|
||||
end
|
||||
index = index + 1
|
||||
end
|
||||
private.browseIndex = index
|
||||
if private.browseIndex <= numAuctions or #private.browsePendingIndexes > 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- check if there's data still pending
|
||||
local hasPendingData = false
|
||||
for _, row in pairs(private.browseResults) do
|
||||
if not row:PopulateBrowseData() then
|
||||
hasPendingData = true
|
||||
-- keep going so we issue requests for all pending rows
|
||||
end
|
||||
end
|
||||
if hasPendingData then
|
||||
return false
|
||||
end
|
||||
|
||||
-- filter the results
|
||||
local numRemoved = 0
|
||||
for baseItemString, row in pairs(private.browseResults) do
|
||||
-- filter the itemKeys we don't care about and rows which don't match the query
|
||||
if row:IsFiltered(private.query) then
|
||||
private.browseResults[baseItemString] = nil
|
||||
numRemoved = numRemoved + 1
|
||||
end
|
||||
if TSM.IsWowClassic() then
|
||||
if row:FilterSubRows(private.query) then
|
||||
-- no more subRows, so filter the entire row
|
||||
private.browseResults[baseItemString] = nil
|
||||
numRemoved = numRemoved + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
if numRemoved > 0 then
|
||||
Log.Info("Removed %d results", numRemoved)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function private.ProcessBrowseResultClassic(index)
|
||||
local rawName, _, stackSize, _, _, _, _, _, _, buyout, _, _, _, seller, sellerFull = GetAuctionItemInfo("list", index)
|
||||
local itemLink = GetAuctionItemLink("list", index)
|
||||
local baseItemString = ItemString.GetBase(itemLink)
|
||||
local timeLeft = GetAuctionItemTimeLeft("list", index)
|
||||
seller = Util.FixSellerName(seller, sellerFull)
|
||||
if not rawName or rawName == "" or not baseItemString or not buyout or not stackSize or not timeLeft or (not seller and private.resolveSellers) then
|
||||
return false
|
||||
end
|
||||
local row = private.ProcessBrowseResult(baseItemString, itemLink)
|
||||
-- amazingly, GetAuctionItemLink could return nil the next time it's called (within the same frame), so pass through our itemLink
|
||||
row:PopulateSubRows(private.browseId, index, itemLink)
|
||||
return true
|
||||
end
|
||||
38
LibTSM/Service/AuctionScanClasses/Util.lua
Normal file
38
LibTSM/Service/AuctionScanClasses/Util.lua
Normal file
@@ -0,0 +1,38 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Util = TSM.Init("Service.AuctionScanClasses.Util")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Util.HasItemInfo(itemString)
|
||||
local itemName = ItemInfo.GetName(itemString)
|
||||
local itemLevel = ItemInfo.GetItemLevel(itemString)
|
||||
local quality = ItemInfo.GetQuality(itemString)
|
||||
local minLevel = ItemInfo.GetMinLevel(itemString)
|
||||
local hasIsCommodity = TSM.IsWowClassic() or ItemInfo.IsCommodity(itemString) ~= nil
|
||||
local hasCanHaveVariations = ItemInfo.CanHaveVariations(itemString) ~= nil
|
||||
local result = itemName and itemLevel and quality and minLevel and hasIsCommodity and hasCanHaveVariations
|
||||
if not result then
|
||||
ItemInfo.FetchInfo(itemString)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function Util.FixSellerName(seller, sellerFull)
|
||||
local realm = GetRealmName()
|
||||
if sellerFull and strjoin("-", seller, realm) ~= sellerFull then
|
||||
return sellerFull
|
||||
else
|
||||
return seller
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user