-- ------------------------------------------------------------------------------ -- -- TradeSkillMaster -- -- https://tradeskillmaster.com -- -- All Rights Reserved - Detailed license information included with addon. -- -- ------------------------------------------------------------------------------ -- local _, TSM = ... local Banking = TSM:NewPackage("Banking") local Event = TSM.Include("Util.Event") local TempTable = TSM.Include("Util.TempTable") local String = TSM.Include("Util.String") 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 private = { moveThread = nil, moveItems = {}, restoreItems = {}, restoreFrame = nil, callback = nil, openFrame = nil, frameCallbacks = {}, } local MOVE_WAIT_TIMEOUT = 2 -- ============================================================================ -- Module Functions -- ============================================================================ function Banking.OnInitialize() private.moveThread = Threading.New("BANKING_MOVE", private.MoveThread) Event.Register("BANKFRAME_OPENED", private.BankOpened) Event.Register("BANKFRAME_CLOSED", private.BankClosed) if not TSM.IsWowClassic() then Event.Register("GUILDBANKFRAME_OPENED", private.GuildBankOpened) Event.Register("GUILDBANKFRAME_CLOSED", private.GuildBankClosed) end end function Banking.RegisterFrameCallback(callback) tinsert(private.frameCallbacks, callback) end function Banking.IsGuildBankOpen() return private.openFrame == "GUILD_BANK" end function Banking.IsBankOpen() return private.openFrame == "BANK" end function Banking.MoveToBag(items, callback) assert(private.openFrame) local context = Banking.IsGuildBankOpen() and Banking.MoveContext.GetGuildBankToBag() or Banking.MoveContext.GetBankToBag() private.StartMove(items, context, callback) end function Banking.MoveToBank(items, callback) assert(private.openFrame) local context = Banking.IsGuildBankOpen() and Banking.MoveContext.GetBagToGuildBank() or Banking.MoveContext.GetBagToBank() private.StartMove(items, context, callback) end function Banking.EmptyBags(callback) assert(private.openFrame) local items = TempTable.Acquire() for _, _, _, itemString, quantity in Banking.Util.BagIterator(false) do items[itemString] = (items[itemString] or 0) + quantity end wipe(private.restoreItems) private.restoreFrame = private.openFrame private.callback = callback local context = Banking.IsGuildBankOpen() and Banking.MoveContext.GetBagToGuildBank() or Banking.MoveContext.GetBagToBank() private.StartMove(items, context, private.EmptyBagsThreadCallbackWrapper) TempTable.Release(items) end function Banking.RestoreBags(callback) assert(private.openFrame) assert(Banking.CanRestoreBags()) private.callback = callback local context = Banking.IsGuildBankOpen() and Banking.MoveContext.GetGuildBankToBag() or Banking.MoveContext.GetBankToBag() private.StartMove(private.restoreItems, context, private.RestoreBagsThreadCallbackWrapper) end function Banking.CanRestoreBags() assert(private.openFrame) return private.openFrame == private.restoreFrame end function Banking.PutByFilter(filterStr) if not private.openFrame then return end local filterItemString = ItemString.Get(filterStr) filterStr = String.Escape(strlower(filterStr)) local items = TempTable.Acquire() for _, _, _, itemString, quantity in Banking.Util.BagIterator(false) do items[itemString] = (items[itemString] or 0) + quantity end for itemString in pairs(items) do if not private.MatchesFilter(itemString, filterStr, filterItemString) then -- remove this item items[itemString] = nil end end Banking.MoveToBank(items, private.GetPutCallback) TempTable.Release(items) end function Banking.GetByFilter(filterStr) if not private.openFrame then return end local filterItemString = ItemString.Get(filterStr) filterStr = String.Escape(strlower(filterStr)) local items = TempTable.Acquire() for _, _, _, itemString, quantity in Banking.Util.OpenBankIterator(false) do items[itemString] = (items[itemString] or 0) + quantity end for itemString in pairs(items) do if not private.MatchesFilter(itemString, filterStr, filterItemString) then -- remove this item items[itemString] = nil end end Banking.MoveToBag(items, private.GetPutCallback) TempTable.Release(items) end -- ============================================================================ -- Threads -- ============================================================================ function private.MoveThread(context, callback) local numMoves = 0 local emptySlotIds = Threading.AcquireSafeTempTable() context:GetEmptySlotsThreaded(emptySlotIds) local slotIds = Threading.AcquireSafeTempTable() local slotItemString = Threading.AcquireSafeTempTable() local slotMoveQuantity = Threading.AcquireSafeTempTable() local slotEndQuantity = Threading.AcquireSafeTempTable() for itemString, numQueued in pairs(private.moveItems) do for _, slotId, quantity in context:SlotIdIterator(itemString) do if numQueued > 0 then -- find a suitable empty slot local targetSlotId = context:GetTargetSlotId(itemString, emptySlotIds) if targetSlotId then assert(not slotIds[slotId]) slotIds[slotId] = targetSlotId slotItemString[slotId] = itemString slotMoveQuantity[slotId] = min(quantity, numQueued) slotEndQuantity[slotId] = max(quantity - numQueued, 0) numQueued = numQueued - slotMoveQuantity[slotId] numMoves = numMoves + 1 else Log.Err("No target slot") end end end if numQueued > 0 then Log.Err("No slots with item (%s)", itemString) end end local numDone = 0 while next(slotIds) do local movedSlotId = nil -- do all the pending moves for slotId, targetSlotId in pairs(slotIds) do context:MoveSlot(slotId, targetSlotId, slotMoveQuantity[slotId]) Threading.Yield() if private.openFrame == "GUILD_BANK" then movedSlotId = slotId break end end -- wait for at least one to finish or the timeout to elapse local didMove = false local timeout = GetTime() + MOVE_WAIT_TIMEOUT while not didMove and GetTime() < timeout do -- check which moves are done for slotId in pairs(slotIds) do if private.openFrame ~= "GUILD_BANK" or slotId == movedSlotId then if context:GetSlotQuantity(slotId) <= slotEndQuantity[slotId] then didMove = true slotIds[slotId] = nil numDone = numDone + 1 callback("MOVED", slotItemString[slotId], slotMoveQuantity[slotId]) end if didMove and slotId == movedSlotId then break end Threading.Yield() end end if didMove then callback("PROGRESS", numDone / numMoves) end Threading.Yield(true) end end if private.openFrame == "GUILD_BANK" then QueryGuildBankTab(GetCurrentGuildBankTab()) end Threading.ReleaseSafeTempTable(slotIds) Threading.ReleaseSafeTempTable(slotItemString) Threading.ReleaseSafeTempTable(slotMoveQuantity) Threading.ReleaseSafeTempTable(slotEndQuantity) Threading.ReleaseSafeTempTable(emptySlotIds) callback("DONE") end -- ============================================================================ -- Private Helper Functions -- ============================================================================ function private.BankOpened() if private.openFrame == "BANK" then return end assert(not private.openFrame) private.openFrame = "BANK" for _, callback in ipairs(private.frameCallbacks) do callback(private.openFrame) end end function private.GuildBankOpened() if private.openFrame == "GUILD_BANK" then return end assert(not private.openFrame) private.openFrame = "GUILD_BANK" for _, callback in ipairs(private.frameCallbacks) do callback(private.openFrame) end end function private.BankClosed() if not private.openFrame then return end private.openFrame = nil private.StopMove() for _, callback in ipairs(private.frameCallbacks) do callback(private.openFrame) end end function private.GuildBankClosed() if not private.openFrame then return end private.openFrame = nil private.StopMove() for _, callback in ipairs(private.frameCallbacks) do callback(private.openFrame) end end function private.StartMove(items, context, callback) private.StopMove() wipe(private.moveItems) for itemString, quantity in pairs(items) do private.moveItems[itemString] = quantity end Threading.Start(private.moveThread, context, callback) end function private.StopMove() Threading.Kill(private.moveThread) end function private.EmptyBagsThreadCallbackWrapper(event, ...) if event == "MOVED" then local itemString, numMoved = ... private.restoreItems[itemString] = (private.restoreItems[itemString] or 0) + numMoved elseif event == "DONE" then if not next(private.restoreItems) then private.restoreFrame = private.openFrame end end private.callback(event, ...) end function private.RestoreBagsThreadCallbackWrapper(event, ...) if event == "DONE" then wipe(private.restoreItems) private.restoreFrame = nil end private.callback(event, ...) end function private.GetPutCallback(event) if event == "DONE" then Log.PrintUser(DONE) end end function private.MatchesFilter(itemString, filterStr, filterItemString) local name = strlower(ItemInfo.GetName(itemString) or "") return strmatch(ItemString.GetBase(itemString), filterStr) or strmatch(name, filterStr) or (filterItemString and itemString == filterItemString) end