initial commit
This commit is contained in:
734
LibTSM/Service/AuctionHouseWrapper.lua
Normal file
734
LibTSM/Service/AuctionHouseWrapper.lua
Normal file
@@ -0,0 +1,734 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local AuctionHouseWrapper = TSM.Init("Service.AuctionHouseWrapper")
|
||||
local LibTSMClass = TSM.Include("LibTSMClass")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local Delay = TSM.Include("Util.Delay")
|
||||
local Event = TSM.Include("Util.Event")
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local Future = TSM.Include("Util.Future")
|
||||
local Vararg = TSM.Include("Util.Vararg")
|
||||
local Analytics = TSM.Include("Util.Analytics")
|
||||
local Math = TSM.Include("Util.Math")
|
||||
local Debug = TSM.Include("Util.Debug")
|
||||
local APIWrapper = LibTSMClass.DefineClass("APIWrapper")
|
||||
local private = {
|
||||
wrappers = {},
|
||||
events = {},
|
||||
argsTemp = {},
|
||||
sortsPartsTemp = {},
|
||||
itemKeyPartsTemp = {},
|
||||
searchQueryAPITimes = {},
|
||||
isAHOpen = false,
|
||||
lastResponseReceived = 0,
|
||||
hookedTime = {},
|
||||
lastAuctionCanceledAuctionId = nil,
|
||||
lastAuctionCanceledTime = 0,
|
||||
auctionIdUpdateCallbacks = {},
|
||||
}
|
||||
local API_TIMEOUT = 5
|
||||
local GET_ALL_TIMEOUT = 30
|
||||
local SEARCH_QUERY_THROTTLE_INTERVAL = 60
|
||||
local SEARCH_QUERY_THROTTLE_MAX = 100
|
||||
local EMPTY_SORTS_TABLE = {}
|
||||
local ITEM_KEY_KEYS = {
|
||||
"itemID",
|
||||
"itemLevel",
|
||||
"itemSuffix",
|
||||
"battlePetSpeciesID",
|
||||
}
|
||||
local SILENT_EVENTS = {
|
||||
AUCTION_ITEM_LIST_UPDATE = true,
|
||||
REPLICATE_ITEM_LIST_UPDATE = true,
|
||||
}
|
||||
local GENERIC_EVENTS = {
|
||||
CHAT_MSG_SYSTEM = 1,
|
||||
UI_ERROR_MESSAGE = 2,
|
||||
}
|
||||
local GENERIC_EVENT_SEP = "/"
|
||||
local API_EVENT_INFO = TSM.IsWowClassic() and
|
||||
{ -- Classic
|
||||
QueryAuctionItems = {
|
||||
AUCTION_ITEM_LIST_UPDATE = { result = true },
|
||||
},
|
||||
} or
|
||||
{ -- Retail
|
||||
SendBrowseQuery = {
|
||||
AUCTION_HOUSE_BROWSE_RESULTS_UPDATED = { result = true },
|
||||
},
|
||||
SearchForFavorites = {
|
||||
AUCTION_HOUSE_BROWSE_RESULTS_UPDATED = { result = true },
|
||||
},
|
||||
SearchForItemKeys = {
|
||||
AUCTION_HOUSE_BROWSE_RESULTS_UPDATED = { result = true },
|
||||
},
|
||||
ReplicateItems = {
|
||||
REPLICATE_ITEM_LIST_UPDATE = { result = true },
|
||||
},
|
||||
RequestMoreBrowseResults = {
|
||||
AUCTION_HOUSE_BROWSE_RESULTS_ADDED = { result = 1 },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_DATABASE_ERROR] = { timeoutChange = 1 },
|
||||
},
|
||||
SendSearchQuery = {
|
||||
COMMODITY_SEARCH_RESULTS_UPDATED = { result = true, eventArgIndex = 1, apiArgIndex = 1, apiArgKey = "itemID" },
|
||||
ITEM_SEARCH_RESULTS_UPDATED = { result = true, eventArgIndex = 1, apiArgIndex = 1 },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_DATABASE_ERROR] = { timeoutChange = 1 },
|
||||
},
|
||||
SendSellSearchQuery = {
|
||||
COMMODITY_SEARCH_RESULTS_UPDATED = { result = true, eventArgIndex = 1, apiArgIndex = 1, apiArgKey = "itemID" },
|
||||
ITEM_SEARCH_RESULTS_UPDATED = { result = true, eventArgIndex = 1, apiArgIndex = 1 },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_DATABASE_ERROR] = { timeoutChange = 1 },
|
||||
},
|
||||
RequestMoreCommoditySearchResults = {
|
||||
COMMODITY_SEARCH_RESULTS_ADDED = { result = true },
|
||||
},
|
||||
RequestMoreItemSearchResults = {
|
||||
ITEM_SEARCH_RESULTS_ADDED = { result = true },
|
||||
},
|
||||
RefreshCommoditySearchResults = {
|
||||
COMMODITY_SEARCH_RESULTS_UPDATED = { result = true },
|
||||
},
|
||||
RefreshItemSearchResults = {
|
||||
ITEM_SEARCH_RESULTS_UPDATED = { result = true },
|
||||
},
|
||||
QueryOwnedAuctions = {
|
||||
OWNED_AUCTIONS_UPDATED = { result = true },
|
||||
},
|
||||
QueryBids = {
|
||||
BIDS_UPDATED = { result = true },
|
||||
},
|
||||
CancelAuction = {
|
||||
AUCTION_CANCELED = { result = true, eventArgIndex = 1, apiArgIndex = 1, compareFunc = function(eventArg, apiArg) return eventArg == 0 or apiArg == eventArg end },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_ITEM_NOT_FOUND] = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_NOT_ENOUGH_MONEY] = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_DATABASE_ERROR] = { result = false },
|
||||
},
|
||||
StartCommoditiesPurchase = {
|
||||
COMMODITY_PRICE_UPDATED = { result = 2, rawFilterFunc = function(apiArgs, unitPrice, totalPrice) return Math.Ceil((totalPrice / apiArgs[2]), COPPER_PER_SILVER) == apiArgs[3] and true end },
|
||||
COMMODITY_PRICE_UNAVAILABLE = { result = false },
|
||||
},
|
||||
ConfirmCommoditiesPurchase = {
|
||||
COMMODITY_PURCHASE_SUCCEEDED = { result = true },
|
||||
COMMODITY_PURCHASE_FAILED = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_DATABASE_ERROR] = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_HIGHER_BID] = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_ITEM_NOT_FOUND] = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_BID_OWN] = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_NOT_ENOUGH_MONEY] = { result = false },
|
||||
},
|
||||
PlaceBid = {
|
||||
AUCTION_CANCELED = { result = true, eventArgIndex = 1, apiArgIndex = 1 },
|
||||
["CHAT_MSG_SYSTEM"..GENERIC_EVENT_SEP..ERR_AUCTION_BID_PLACED] = { result = true },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_DATABASE_ERROR] = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_HIGHER_BID] = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_ITEM_NOT_FOUND] = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_BID_OWN] = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_NOT_ENOUGH_MONEY] = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_ITEM_MAX_COUNT] = { result = false },
|
||||
},
|
||||
PostItem = {
|
||||
AUCTION_HOUSE_AUCTION_CREATED = { result = true, rawFilterFunc = function(apiArgs) return apiArgs[3] <= 1 end },
|
||||
AUCTION_MULTISELL_UPDATE = { result = true, rawFilterFunc = function(apiArgs, createdCount, totalToCreate) return createdCount == totalToCreate end },
|
||||
AUCTION_MULTISELL_FAILURE = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_ITEM_NOT_FOUND] = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_DATABASE_ERROR] = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_REPAIR_ITEM] = { result = nil },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_LIMITED_DURATION_ITEM] = { result = nil },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_USED_CHARGES] = { result = nil },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_WRAPPED_ITEM] = { result = nil },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_BAG] = { result = nil },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_NOT_ENOUGH_MONEY] = { result = nil },
|
||||
},
|
||||
PostCommodity = {
|
||||
AUCTION_HOUSE_AUCTION_CREATED = { result = true },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_ITEM_NOT_FOUND] = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_DATABASE_ERROR] = { result = false },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_REPAIR_ITEM] = { result = nil },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_LIMITED_DURATION_ITEM] = { result = nil },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_USED_CHARGES] = { result = nil },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_WRAPPED_ITEM] = { result = nil },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_AUCTION_BAG] = { result = nil },
|
||||
["UI_ERROR_MESSAGE"..GENERIC_EVENT_SEP..ERR_NOT_ENOUGH_MONEY] = { result = nil },
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Loading
|
||||
-- ============================================================================
|
||||
|
||||
AuctionHouseWrapper:OnModuleLoad(function()
|
||||
Event.Register("AUCTION_HOUSE_SHOW", private.AuctionHouseShowHandler)
|
||||
Event.Register("AUCTION_HOUSE_CLOSED", private.AuctionHouseClosedHandler)
|
||||
|
||||
-- setup wrappers
|
||||
for apiName in pairs(API_EVENT_INFO) do
|
||||
private.wrappers[apiName] = APIWrapper(apiName)
|
||||
end
|
||||
|
||||
if not TSM.IsWowClassic() then
|
||||
-- extra hooks to track search query calls since they are limited
|
||||
hooksecurefunc(C_AuctionHouse, "SendSearchQuery", function()
|
||||
tinsert(private.searchQueryAPITimes, GetTime())
|
||||
end)
|
||||
hooksecurefunc(C_AuctionHouse, "SendSellSearchQuery", function()
|
||||
tinsert(private.searchQueryAPITimes, GetTime())
|
||||
end)
|
||||
|
||||
-- events to track auction purchases
|
||||
Event.Register("AUCTION_CANCELED", private.AuctionCanceledHandler)
|
||||
Event.Register("ITEM_SEARCH_RESULTS_UPDATED", private.ItemSearchResultsUpdated)
|
||||
|
||||
-- general events
|
||||
Event.Register("AUCTION_HOUSE_THROTTLED_MESSAGE_RESPONSE_RECEIVED", private.ResponseReceivedHandler)
|
||||
|
||||
-- extra events that are interesting to log
|
||||
Event.Register("AUCTION_HOUSE_NEW_RESULTS_RECEIVED", private.UnusedEventHandler)
|
||||
Event.Register("AUCTION_HOUSE_THROTTLED_MESSAGE_DROPPED", private.UnusedEventHandler)
|
||||
Event.Register("AUCTION_HOUSE_THROTTLED_MESSAGE_QUEUED", private.UnusedEventHandler)
|
||||
Event.Register("AUCTION_HOUSE_THROTTLED_MESSAGE_SENT", private.UnusedEventHandler)
|
||||
if not TSM.IsShadowlands() then
|
||||
Event.Register("AUCTION_HOUSE_THROTTLED_SPECIFIC_SEARCH_READY", private.UnusedEventHandler)
|
||||
end
|
||||
Event.Register("AUCTION_HOUSE_THROTTLED_SYSTEM_READY", private.UnusedEventHandler)
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function AuctionHouseWrapper.RegisterAuctionIdUpdateCallback(callback)
|
||||
tinsert(private.auctionIdUpdateCallbacks, callback)
|
||||
end
|
||||
|
||||
function AuctionHouseWrapper.IsOpen()
|
||||
return private.isAHOpen
|
||||
end
|
||||
|
||||
function AuctionHouseWrapper.GetAndResetTotalHookedTime()
|
||||
local total, topTime, topAddon = 0, nil, nil
|
||||
for addon, hookedTime in pairs(private.hookedTime) do
|
||||
total = total + hookedTime
|
||||
if hookedTime > (topTime or 0) then
|
||||
topTime = hookedTime
|
||||
topAddon = addon
|
||||
end
|
||||
end
|
||||
wipe(private.hookedTime)
|
||||
return total, topAddon, topTime
|
||||
end
|
||||
|
||||
function AuctionHouseWrapper.SendBrowseQuery(query)
|
||||
assert(not TSM.IsWowClassic())
|
||||
if not private.CheckAllIdle() then
|
||||
return
|
||||
end
|
||||
return private.wrappers.SendBrowseQuery:Start(query)
|
||||
end
|
||||
|
||||
function AuctionHouseWrapper.RequestMoreBrowseResults()
|
||||
assert(not TSM.IsWowClassic())
|
||||
if not private.CheckAllIdle() then
|
||||
return
|
||||
end
|
||||
return private.wrappers.RequestMoreBrowseResults:Start()
|
||||
end
|
||||
|
||||
function AuctionHouseWrapper.SendSearchQuery(itemKey, isSell)
|
||||
assert(not TSM.IsWowClassic())
|
||||
if not private.CheckAllIdle() then
|
||||
return
|
||||
end
|
||||
-- remove times which are beyond the throttle interval
|
||||
for i = #private.searchQueryAPITimes, 1, -1 do
|
||||
if GetTime() - private.searchQueryAPITimes[i] >= SEARCH_QUERY_THROTTLE_INTERVAL then
|
||||
tremove(private.searchQueryAPITimes, i)
|
||||
end
|
||||
end
|
||||
if #private.searchQueryAPITimes >= SEARCH_QUERY_THROTTLE_MAX then
|
||||
local delayTime = private.searchQueryAPITimes[1] + SEARCH_QUERY_THROTTLE_INTERVAL - GetTime()
|
||||
assert(delayTime > 0, "Invalid delay time: "..tostring(delayTime))
|
||||
Log.Err("Search query can't be run for another %.3f seconds", delayTime)
|
||||
return nil, delayTime
|
||||
end
|
||||
assert(type(isSell) == "boolean")
|
||||
if isSell then
|
||||
return private.wrappers.SendSellSearchQuery:Start(itemKey, EMPTY_SORTS_TABLE, true)
|
||||
else
|
||||
return private.wrappers.SendSearchQuery:Start(itemKey, EMPTY_SORTS_TABLE, true)
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionHouseWrapper.RequestMoreCommoditySearchResults(itemId)
|
||||
assert(not TSM.IsWowClassic())
|
||||
if not private.CheckAllIdle() then
|
||||
return
|
||||
end
|
||||
return private.wrappers.RequestMoreCommoditySearchResults:Start(itemId)
|
||||
end
|
||||
|
||||
function AuctionHouseWrapper.RequestMoreItemSearchResults(itemKey)
|
||||
assert(not TSM.IsWowClassic())
|
||||
if not private.CheckAllIdle() then
|
||||
return
|
||||
end
|
||||
return private.wrappers.RequestMoreItemSearchResults:Start(itemKey)
|
||||
end
|
||||
|
||||
function AuctionHouseWrapper.QueryOwnedAuctions(sorts)
|
||||
assert(not TSM.IsWowClassic())
|
||||
if not private.CheckAllIdle() then
|
||||
return
|
||||
end
|
||||
return private.wrappers.QueryOwnedAuctions:Start(sorts)
|
||||
end
|
||||
|
||||
function AuctionHouseWrapper.CancelAuction(auctionId)
|
||||
assert(not TSM.IsWowClassic())
|
||||
-- if QueryOwnedAuctions is pending, just cancel it
|
||||
private.wrappers.QueryOwnedAuctions:CancelIfPending()
|
||||
if not private.CheckAllIdle() then
|
||||
return
|
||||
end
|
||||
return private.wrappers.CancelAuction:Start(auctionId)
|
||||
end
|
||||
|
||||
function AuctionHouseWrapper.StartCommoditiesPurchase(itemId, quantity, itemBuyout)
|
||||
assert(not TSM.IsWowClassic())
|
||||
if not private.CheckAllIdle() then
|
||||
return
|
||||
end
|
||||
return private.wrappers.StartCommoditiesPurchase:Start(itemId, quantity, itemBuyout)
|
||||
end
|
||||
|
||||
function AuctionHouseWrapper.ConfirmCommoditiesPurchase(itemId, quantity, totalBuyout)
|
||||
assert(not TSM.IsWowClassic())
|
||||
if not private.CheckAllIdle() then
|
||||
return
|
||||
end
|
||||
return private.wrappers.ConfirmCommoditiesPurchase:Start(itemId, quantity, totalBuyout)
|
||||
end
|
||||
|
||||
function AuctionHouseWrapper.PlaceBid(auctionId, bidBuyout)
|
||||
assert(not TSM.IsWowClassic())
|
||||
if not private.CheckAllIdle() then
|
||||
return
|
||||
end
|
||||
return private.wrappers.PlaceBid:Start(auctionId, bidBuyout)
|
||||
end
|
||||
|
||||
function AuctionHouseWrapper.PostItem(itemLocation, postTime, stackSize, bid, buyout)
|
||||
assert(not TSM.IsWowClassic())
|
||||
if not private.CheckAllIdle() then
|
||||
return
|
||||
end
|
||||
return private.wrappers.PostItem:Start(itemLocation, postTime, stackSize, bid, buyout)
|
||||
end
|
||||
|
||||
function AuctionHouseWrapper.PostCommodity(itemLocation, postTime, stackSize, itemBuyout)
|
||||
assert(not TSM.IsWowClassic())
|
||||
if not private.CheckAllIdle() then
|
||||
return
|
||||
end
|
||||
return private.wrappers.PostCommodity:Start(itemLocation, postTime, stackSize, itemBuyout)
|
||||
end
|
||||
|
||||
function AuctionHouseWrapper.QueryAuctionItems(name, minLevel, maxLevel, page, usable, quality, getAll, exact, filterData)
|
||||
assert(TSM.IsWowClassic())
|
||||
local canSendQuery, canSendGetAll = CanSendAuctionQuery()
|
||||
if not canSendQuery or (getAll and not canSendGetAll) or not private.CheckAllIdle() then
|
||||
return
|
||||
end
|
||||
return private.wrappers.QueryAuctionItems:Start(name, minLevel, maxLevel, page, usable, quality, getAll, exact, filterData)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- APIWrapper Class
|
||||
-- ============================================================================
|
||||
|
||||
function APIWrapper.__init(self, name)
|
||||
self._name = name
|
||||
self._args = {}
|
||||
self._state = "IDLE"
|
||||
self._callTime = nil
|
||||
self._future = Future.New(self._name.."_FUTURE")
|
||||
self._future:SetScript("OnCleanup", function()
|
||||
if self._state == "PENDING_REQUESTED" then
|
||||
-- switch the current call to a hooked call
|
||||
self._state = "PENDING_HOOKED"
|
||||
elseif self._state == "DONE" then
|
||||
self._state = "IDLE"
|
||||
end
|
||||
end)
|
||||
self._timeoutWrapper = function()
|
||||
Log.Err("API timed out: %s(%s)", self._name, private.ArgsToStr(unpack(self._args)))
|
||||
return self:_Done(false)
|
||||
end
|
||||
|
||||
-- hook the API
|
||||
hooksecurefunc(TSM.IsWowClassic() and _G or C_AuctionHouse, self._name, function(...)
|
||||
Log.Info("%s(%s)", self._name, private.ArgsToStr(...))
|
||||
if self:_IsPending() and select("#", ...) == 0 then
|
||||
return
|
||||
end
|
||||
self:CancelIfPending()
|
||||
if self:_HandleAPICall(...) then
|
||||
for _, wrapper in pairs(private.wrappers) do
|
||||
if wrapper ~= self and GetTime() ~= private.lastResponseReceived then
|
||||
wrapper:CancelIfPending()
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- register related events
|
||||
for eventName in pairs(API_EVENT_INFO[self._name]) do
|
||||
private.RegisterForEvent(eventName, self)
|
||||
end
|
||||
end
|
||||
|
||||
function APIWrapper.IsIdle(self)
|
||||
return self._state == "IDLE"
|
||||
end
|
||||
|
||||
function APIWrapper.CancelIfPending(self)
|
||||
if not self:_IsPending() then
|
||||
return
|
||||
end
|
||||
Log.Warn("Canceling pending (%s, %s)", self._name, self._state)
|
||||
self:_Done(false)
|
||||
end
|
||||
|
||||
function APIWrapper.Start(self, ...)
|
||||
if self._state ~= "IDLE" then
|
||||
Log.Err("API already in progress (%s)", self._name)
|
||||
return
|
||||
end
|
||||
self._state = "STARTING"
|
||||
self:_CallAPI(...)
|
||||
return self._future
|
||||
end
|
||||
|
||||
function APIWrapper._IsPending(self)
|
||||
return self._state == "PENDING_REQUESTED" or self._state == "PENDING_HOOKED"
|
||||
end
|
||||
|
||||
function APIWrapper._CallAPI(self, ...)
|
||||
return (TSM.IsWowClassic() and _G or C_AuctionHouse)[self._name](...)
|
||||
end
|
||||
|
||||
function APIWrapper._HandleAPICall(self, ...)
|
||||
self._callTime = GetTime()
|
||||
if self._state == "IDLE" then
|
||||
self._state = "PENDING_HOOKED"
|
||||
self._hookAddon = strmatch(Debug.GetStackLevelLocation(3), "AddOns\\([^\\]+)\\")
|
||||
elseif self._state == "STARTING" then
|
||||
self._future:Start()
|
||||
self._state = "PENDING_REQUESTED"
|
||||
else
|
||||
error("Unexpected state: "..self._state)
|
||||
end
|
||||
Vararg.IntoTable(self._args, ...)
|
||||
local timeout = nil
|
||||
if not private.isAHOpen then
|
||||
timeout = 0
|
||||
elseif self._name == "QueryAuctionItems" and select(7, ...) then
|
||||
timeout = GET_ALL_TIMEOUT
|
||||
else
|
||||
timeout = API_TIMEOUT
|
||||
end
|
||||
Delay.AfterTime(self._name.."_TIMEOUT", timeout, self._timeoutWrapper)
|
||||
return true
|
||||
end
|
||||
|
||||
function APIWrapper._HandleEvent(self, eventName, ...)
|
||||
if self._state ~= "PENDING_REQUESTED" and self._state ~= "PENDING_HOOKED" then
|
||||
return
|
||||
end
|
||||
local eventIsValid, result = self:_ValidateEvent(eventName, ...)
|
||||
if not eventIsValid then
|
||||
Log.Info("Ignoring invalidated event")
|
||||
return
|
||||
end
|
||||
self:_Done(result)
|
||||
end
|
||||
|
||||
function APIWrapper._ValidateEvent(self, eventName, ...)
|
||||
local info = nil
|
||||
if GENERIC_EVENTS[eventName] then
|
||||
local arg = ...
|
||||
info = API_EVENT_INFO[self._name][eventName..GENERIC_EVENT_SEP..arg]
|
||||
else
|
||||
info = API_EVENT_INFO[self._name][eventName]
|
||||
end
|
||||
assert(info)
|
||||
if info.timeoutChange then
|
||||
Delay.Cancel(self._name.."_TIMEOUT")
|
||||
Delay.AfterTime(self._name.."_TIMEOUT", info.timeoutChange, self._timeoutWrapper)
|
||||
return false
|
||||
end
|
||||
local eventIsValid, result = true, nil
|
||||
if type(info.result) == "number" then
|
||||
result = select(info.result, ...)
|
||||
else
|
||||
result = info.result
|
||||
end
|
||||
if info.rawFilterFunc then
|
||||
if not info.rawFilterFunc(self._args, ...) then
|
||||
eventIsValid = false
|
||||
end
|
||||
elseif info.eventArgIndex then
|
||||
local eventValue = select(info.eventArgIndex, ...)
|
||||
local apiValue = self._args[info.apiArgIndex]
|
||||
if info.apiArgKey then
|
||||
apiValue = apiValue[info.apiArgKey]
|
||||
end
|
||||
local argMatches = nil
|
||||
assert(type(eventValue) == type(apiValue))
|
||||
if info.compareFunc then
|
||||
argMatches = info.compareFunc(eventValue, apiValue)
|
||||
elseif private.IsItemKey(eventValue) then
|
||||
argMatches = true
|
||||
for _, key in ipairs(ITEM_KEY_KEYS) do
|
||||
if eventValue[key] ~= apiValue[key] then
|
||||
argMatches = false
|
||||
break
|
||||
end
|
||||
end
|
||||
elseif type(eventValue) == "table" then
|
||||
argMatches = Table.Equal(eventValue, apiValue)
|
||||
else
|
||||
argMatches = eventValue == apiValue
|
||||
end
|
||||
if not argMatches then
|
||||
eventIsValid = false
|
||||
end
|
||||
end
|
||||
return eventIsValid, result
|
||||
end
|
||||
|
||||
function APIWrapper._Done(self, result)
|
||||
wipe(self._args)
|
||||
local hookAddon = self._hookAddon
|
||||
self._hookAddon = nil
|
||||
local totalTime = Math.Round((GetTime() - (self._callTime or GetTime())) * 1000)
|
||||
self._callTime = nil
|
||||
Delay.Cancel(self._name.."_TIMEOUT")
|
||||
if self._state == "PENDING_REQUESTED" then
|
||||
if totalTime > 0 then
|
||||
Analytics.Action("AH_API_TIME", private.GetAnalyticsRegionRealm(), self._name, result and totalTime or -1)
|
||||
end
|
||||
self._state = "DONE"
|
||||
-- need to do this last as it might trigger another API call or OnCleanup on the future
|
||||
self._future:Done(result)
|
||||
elseif self._state == "PENDING_HOOKED" then
|
||||
self._state = "IDLE"
|
||||
if hookAddon then
|
||||
private.hookedTime[hookAddon] = (private.hookedTime[hookAddon] or 0) + totalTime / 1000
|
||||
end
|
||||
else
|
||||
error("Unexpected state: "..self._state)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.AuctionHouseShowHandler()
|
||||
private.isAHOpen = true
|
||||
end
|
||||
|
||||
function private.AuctionHouseClosedHandler()
|
||||
private.isAHOpen = false
|
||||
for _, wrapper in pairs(private.wrappers) do
|
||||
wrapper:CancelIfPending()
|
||||
end
|
||||
end
|
||||
|
||||
function private.IsItemKey(value)
|
||||
if type(value) ~= "table" then
|
||||
return false
|
||||
end
|
||||
for _, key in ipairs(ITEM_KEY_KEYS) do
|
||||
if not value[key] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function private.ItemKeyToStr(itemKey)
|
||||
assert(#private.itemKeyPartsTemp == 0)
|
||||
if itemKey.itemID ~= 0 then
|
||||
tinsert(private.itemKeyPartsTemp, "itemID="..itemKey.itemID)
|
||||
end
|
||||
if itemKey.itemLevel ~= 0 then
|
||||
tinsert(private.itemKeyPartsTemp, "itemLevel="..itemKey.itemLevel)
|
||||
end
|
||||
if itemKey.itemSuffix ~= 0 then
|
||||
tinsert(private.itemKeyPartsTemp, "itemSuffix="..itemKey.itemSuffix)
|
||||
end
|
||||
if itemKey.battlePetSpeciesID ~= 0 then
|
||||
tinsert(private.itemKeyPartsTemp, "battlePetSpeciesID="..itemKey.battlePetSpeciesID)
|
||||
end
|
||||
local result = format("{%s}", table.concat(private.itemKeyPartsTemp, ","))
|
||||
wipe(private.itemKeyPartsTemp)
|
||||
return result
|
||||
end
|
||||
|
||||
function private.SortsToStr(sorts)
|
||||
assert(#private.sortsPartsTemp == 0)
|
||||
for _, sort in ipairs(sorts) do
|
||||
local name = Table.KeyByValue(Enum.AuctionHouseSortOrder, sort.sortOrder) or "?"
|
||||
tinsert(private.sortsPartsTemp, format("%s%s", sort.reverseSort and "-" or "", name))
|
||||
end
|
||||
local result = format("{%s}", table.concat(private.sortsPartsTemp, ","))
|
||||
wipe(private.sortsPartsTemp)
|
||||
return result
|
||||
end
|
||||
|
||||
function private.ArgToStr(arg)
|
||||
if type(arg) == "table" then
|
||||
local count = Table.Count(arg)
|
||||
if private.IsItemKey(arg) then
|
||||
return private.ItemKeyToStr(arg)
|
||||
elseif arg.searchString then
|
||||
return format("{searchString=\"%s\", sorts=%s, minLevel=%s, maxLevel=%s, filters=%s, itemClassFilters=%s}", arg.searchString, private.SortsToStr(arg.sorts), private.ArgToStr(arg.minLevel), private.ArgToStr(arg.maxLevel), private.ArgToStr(arg.filters), private.ArgToStr(arg.itemClassFilters))
|
||||
elseif arg.IsBagAndSlot then
|
||||
return format("{<ItemLocation:(%d,%d)>}", arg:GetBagAndSlot())
|
||||
elseif count == 0 then
|
||||
return "{}"
|
||||
elseif count == #arg then
|
||||
if type(arg[1]) == "table" and arg[1].sortOrder then
|
||||
return format("{sorts=%s}", private.SortsToStr(arg))
|
||||
end
|
||||
return format("{<%d items>}", count)
|
||||
else
|
||||
return "{...}"
|
||||
end
|
||||
else
|
||||
return tostring(arg)
|
||||
end
|
||||
end
|
||||
|
||||
function private.ArgsToStr(...)
|
||||
assert(#private.argsTemp == 0)
|
||||
for _, arg in Vararg.Iterator(...) do
|
||||
tinsert(private.argsTemp, private.ArgToStr(arg))
|
||||
end
|
||||
local result = table.concat(private.argsTemp, ",")
|
||||
wipe(private.argsTemp)
|
||||
return result
|
||||
end
|
||||
|
||||
function private.RegisterForEvent(eventName, wrapper)
|
||||
local genericEventArg = nil
|
||||
eventName, genericEventArg = strsplit(GENERIC_EVENT_SEP, eventName)
|
||||
if not private.events[eventName] then
|
||||
private.events[eventName] = {}
|
||||
Event.Register(eventName, private.EventHandler)
|
||||
end
|
||||
if genericEventArg then
|
||||
private.events[eventName][genericEventArg] = private.events[eventName][genericEventArg] or {}
|
||||
tinsert(private.events[eventName][genericEventArg], wrapper)
|
||||
else
|
||||
tinsert(private.events[eventName], wrapper)
|
||||
end
|
||||
end
|
||||
|
||||
function private.EventHandler(eventName, ...)
|
||||
-- reduce the log spam of generic events by combining the message with the name and discarding arguments
|
||||
local genericEventArg = nil
|
||||
if eventName == "UI_ERROR_MESSAGE" and select(1, ...) == ERR_AUCTION_DATABASE_ERROR then
|
||||
-- log an analytics event for "Internal Auction Error" messages
|
||||
for apiName, wrapper in pairs(private.wrappers) do
|
||||
if not wrapper:IsIdle() then
|
||||
Analytics.Action("AH_INTERNAL_ERROR", private.GetAnalyticsRegionRealm(), apiName)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if GENERIC_EVENTS[eventName] then
|
||||
genericEventArg = select(GENERIC_EVENTS[eventName], ...)
|
||||
assert(genericEventArg)
|
||||
if not private.events[eventName][genericEventArg] then
|
||||
return
|
||||
end
|
||||
private.EventHandlerHelper(private.events[eventName][genericEventArg], eventName, genericEventArg)
|
||||
else
|
||||
private.EventHandlerHelper(private.events[eventName], eventName, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function private.ResponseReceivedHandler(eventName, ...)
|
||||
Log.Info("%s (%s)", eventName, private.ArgsToStr(...))
|
||||
private.lastResponseReceived = GetTime()
|
||||
end
|
||||
|
||||
function private.UnusedEventHandler(eventName, ...)
|
||||
Log.Info("%s (%s)", eventName, private.ArgsToStr(...))
|
||||
end
|
||||
|
||||
function private.EventHandlerHelper(wrappers, eventName, ...)
|
||||
if not SILENT_EVENTS[eventName] then
|
||||
Log.Info("%s (%s)", eventName, private.ArgsToStr(...))
|
||||
end
|
||||
for _, wrapper in ipairs(wrappers) do
|
||||
wrapper:_HandleEvent(eventName, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function private.CheckAllIdle()
|
||||
for apiName, wrapper in pairs(private.wrappers) do
|
||||
if not wrapper:IsIdle() then
|
||||
Log.Err("Another wrapper is pending (%s)", apiName)
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function private.AuctionCanceledHandler(_, auctionId)
|
||||
private.lastAuctionCanceledAuctionId = auctionId
|
||||
private.lastAuctionCanceledTime = GetTime()
|
||||
end
|
||||
|
||||
function private.ItemSearchResultsUpdated(_, itemKey, auctionId)
|
||||
if private.lastAuctionCanceledTime == GetTime() and auctionId then
|
||||
Log.Info("Auction ID changed from %s to %s", tostring(private.lastAuctionCanceledAuctionId), tostring(auctionId))
|
||||
local newResultInfo = nil
|
||||
for i = 1, C_AuctionHouse.GetNumItemSearchResults(itemKey) do
|
||||
local info = C_AuctionHouse.GetItemSearchResultInfo(itemKey, i)
|
||||
if info.auctionID == auctionId then
|
||||
newResultInfo = info
|
||||
break
|
||||
end
|
||||
end
|
||||
if not newResultInfo then
|
||||
Log.Warn("Failed to find new result info")
|
||||
end
|
||||
for _, callback in ipairs(private.auctionIdUpdateCallbacks) do
|
||||
callback(private.lastAuctionCanceledAuctionId, auctionId, newResultInfo)
|
||||
end
|
||||
private.lastAuctionCanceledAuctionId = nil
|
||||
private.lastAuctionCanceledTime = 0
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetAnalyticsRegionRealm()
|
||||
return TSM.GetRegion().."-"..gsub(GetRealmName(), "\226", "'")
|
||||
end
|
||||
Reference in New Issue
Block a user