initial commit
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user