initial commit

This commit is contained in:
Gitea
2020-11-13 14:13:12 -05:00
commit 05df49ff60
368 changed files with 128754 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
TSM:NewPackage("Shopping")
local Threading = TSM.Include("Service.Threading")
local ShoppingSearchContext = TSM.Include("LibTSMClass").DefineClass("ShoppingSearchContext")
TSM.Shopping.ShoppingSearchContext = ShoppingSearchContext
-- ============================================================================
-- ShoppingSearchContext - Public Class Methods
-- ============================================================================
function ShoppingSearchContext.__init(self, threadId, marketValueFunc)
assert(threadId and marketValueFunc)
self._threadId = threadId
self._marketValueFunc = marketValueFunc
self._name = nil
self._filterInfo = nil
self._postContext = nil
self._buyCallback = nil
self._stateCallback = nil
self._pctTooltip = nil
end
function ShoppingSearchContext.SetScanContext(self, name, filterInfo, postContext, pctTooltip)
assert(name)
self._name = name
self._filterInfo = filterInfo
self._postContext = postContext
-- clear the callbacks when the scan context changes
self._buyCallback = nil
self._stateCallback = nil
self._pctTooltip = pctTooltip
return self
end
function ShoppingSearchContext.SetCallbacks(self, buyCallback, stateCallback)
self._buyCallback = buyCallback
self._stateCallback = stateCallback
return self
end
function ShoppingSearchContext.StartThread(self, callback, auctionScan)
Threading.SetCallback(self._threadId, callback)
Threading.Start(self._threadId, auctionScan, self._filterInfo, self._postContext)
end
function ShoppingSearchContext.KillThread(self)
Threading.Kill(self._threadId)
end
function ShoppingSearchContext.GetMarketValueFunc(self)
return self._marketValueFunc
end
function ShoppingSearchContext.GetPctTooltip(self)
return self._pctTooltip
end
function ShoppingSearchContext.GetMaxCanBuy(self, itemString)
return nil
end
function ShoppingSearchContext.OnBuy(self, itemString, quantity)
if self._buyCallback then
self._buyCallback(itemString, quantity)
end
end
function ShoppingSearchContext.OnStateChanged(self, state)
if self._stateCallback then
self._stateCallback(state)
end
end
function ShoppingSearchContext.GetName(self)
return self._name
end
function ShoppingSearchContext.GetPostContext(self)
return self._postContext
end

View File

@@ -0,0 +1,104 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local DisenchantSearch = TSM.Shopping:NewPackage("DisenchantSearch")
local L = TSM.Include("Locale").GetTable()
local Log = TSM.Include("Util.Log")
local Threading = TSM.Include("Service.Threading")
local ItemInfo = TSM.Include("Service.ItemInfo")
local CustomPrice = TSM.Include("Service.CustomPrice")
local private = {
itemList = {},
scanThreadId = nil,
searchContext = nil,
}
-- ============================================================================
-- Module Functions
-- ============================================================================
function DisenchantSearch.OnInitialize()
-- initialize thread
private.scanThreadId = Threading.New("DISENCHANT_SEARCH", private.ScanThread)
private.searchContext = TSM.Shopping.ShoppingSearchContext(private.scanThreadId, private.MarketValueFunction)
end
function DisenchantSearch.GetSearchContext()
return private.searchContext:SetScanContext(L["Disenchant Search"], nil, nil, L["Disenchant Value"])
end
-- ============================================================================
-- Scan Thread
-- ============================================================================
function private.ScanThread(auctionScan)
if (TSM.AuctionDB.GetLastCompleteScanTime() or 0) < time() - 60 * 60 * 12 then
Log.PrintUser(L["No recent AuctionDB scan data found."])
return false
end
-- create the list of items
wipe(private.itemList)
for _, itemString, _, minBuyout in TSM.AuctionDB.LastScanIteratorThreaded() do
if minBuyout and private.ShouldInclude(itemString, minBuyout) then
tinsert(private.itemList, itemString)
end
Threading.Yield()
end
-- run the scan
auctionScan:AddItemListQueriesThreaded(private.itemList)
for _, query in auctionScan:QueryIterator() do
query:AddCustomFilter(private.QueryFilter)
end
if not auctionScan:ScanQueriesThreaded() then
Log.PrintUser(L["TSM failed to scan some auctions. Please rerun the scan."])
end
end
function private.ShouldInclude(itemString, minBuyout)
if not ItemInfo.IsDisenchantable(itemString) then
return false
end
local itemLevel = ItemInfo.GetItemLevel(itemString) or -1
if itemLevel < TSM.db.global.shoppingOptions.minDeSearchLvl or itemLevel > TSM.db.global.shoppingOptions.maxDeSearchLvl then
return false
end
if private.IsItemBuyoutTooHigh(itemString, minBuyout) then
return false
end
return true
end
function private.QueryFilter(_, row)
local itemString = row:GetItemString()
if not itemString then
return false
end
local _, itemBuyout = row:GetBuyouts()
if not itemBuyout then
return false
end
return private.IsItemBuyoutTooHigh(itemString, itemBuyout)
end
function private.IsItemBuyoutTooHigh(itemString, itemBuyout)
local disenchantValue = CustomPrice.GetItemPrice(itemString, "Destroy")
return not disenchantValue or itemBuyout > TSM.db.global.shoppingOptions.maxDeSearchPercent / 100 * disenchantValue
end
function private.MarketValueFunction(row)
return CustomPrice.GetItemPrice(row:GetItemString() or row:GetBaseItemString(), "Destroy")
end

View File

@@ -0,0 +1,405 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local FilterSearch = TSM.Shopping:NewPackage("FilterSearch")
local L = TSM.Include("Locale").GetTable()
local DisenchantInfo = TSM.Include("Data.DisenchantInfo")
local TempTable = TSM.Include("Util.TempTable")
local String = TSM.Include("Util.String")
local Log = TSM.Include("Util.Log")
local Math = TSM.Include("Util.Math")
local ItemString = TSM.Include("Util.ItemString")
local Threading = TSM.Include("Service.Threading")
local ItemFilter = TSM.Include("Service.ItemFilter")
local CustomPrice = TSM.Include("Service.CustomPrice")
local Conversions = TSM.Include("Service.Conversions")
local ItemInfo = TSM.Include("Service.ItemInfo")
local FilterSearchContext = TSM.Include("LibTSMClass").DefineClass("FilterSearchContext", TSM.Shopping.ShoppingSearchContext)
local private = {
scanThreadId = nil,
itemFilter = nil,
isSpecial = false,
marketValueSource = nil,
searchContext = nil,
gatheringSearchContext = nil,
targetItem = {},
itemList = {},
generalMaxQuantity = {},
}
-- ============================================================================
-- Module Functions
-- ============================================================================
function FilterSearch.OnInitialize()
-- initialize thread
private.scanThreadId = Threading.New("FILTER_SEARCH", private.ScanThread)
private.itemFilter = ItemFilter.New()
private.searchContext = FilterSearchContext(private.scanThreadId, private.MarketValueFunction)
private.gatheringSearchContext = FilterSearchContext(private.scanThreadId, private.MarketValueFunction)
end
function FilterSearch.GetGreatDealsSearchContext(filterStr)
filterStr = private.ValidateFilterStr(filterStr, "NORMAL")
if not filterStr then
return
end
private.marketValueSource = TSM.db.global.shoppingOptions.pctSource
private.isSpecial = true
return private.searchContext:SetScanContext(L["Great Deals Search"], filterStr, nil, L["Market Value"])
end
function FilterSearch.GetSearchContext(filterStr, itemInfo)
local errMsg = nil
filterStr, errMsg = private.ValidateFilterStr(filterStr, "NORMAL")
if not filterStr then
return nil, errMsg
end
private.marketValueSource = TSM.db.global.shoppingOptions.pctSource
private.isSpecial = false
return private.searchContext:SetScanContext(filterStr, filterStr, itemInfo, L["Market Value"])
end
function FilterSearch.GetGatheringSearchContext(filterStr, mode)
filterStr = private.ValidateFilterStr(filterStr, mode)
if not filterStr then
return
end
private.marketValueSource = "matprice"
private.isSpecial = true
return private.gatheringSearchContext:SetScanContext(L["Gathering Search"], filterStr, nil, L["Material Cost"])
end
-- ============================================================================
-- Scan Thread
-- ============================================================================
function private.ScanThread(auctionScan, filterStr)
wipe(private.generalMaxQuantity)
if not TSM.IsWowClassic() and filterStr == "" then
auctionScan:NewQuery()
:SetStr("")
wipe(private.targetItem)
wipe(private.itemList)
else
local hasFilter, errMsg = false, nil
for filter in String.SplitIterator(filterStr, ";") do
filter = strtrim(filter)
if filter ~= "" then
local filterIsValid, filterErrMsg = private.itemFilter:ParseStr(filter)
if filterIsValid then
hasFilter = true
else
errMsg = errMsg or filterErrMsg
end
end
end
if not hasFilter then
Log.PrintUser(format(L["Invalid search filter (%s)."], filterStr).." "..errMsg)
return false
end
wipe(private.targetItem)
wipe(private.itemList)
local itemFilter = private.itemFilter
for filterPart in String.SplitIterator(filterStr, ";") do
filterPart = strtrim(filterPart)
if filterPart ~= "" and itemFilter:ParseStr(filterPart) then
if itemFilter:GetCrafting() then
wipe(private.itemList)
local targetItem = Conversions.GetTargetItemByName(private.itemFilter:GetStr())
assert(targetItem)
-- populate the list of items
private.targetItem[targetItem] = targetItem
tinsert(private.itemList, targetItem)
local conversionInfo = Conversions.GetSourceItems(targetItem)
for itemString in pairs(conversionInfo) do
if not private.targetItem[itemString] then
private.targetItem[itemString] = targetItem
tinsert(private.itemList, itemString)
end
end
-- generate the queries and add our filter
local queryOffset = auctionScan:GetNumQueries()
auctionScan:AddItemListQueriesThreaded(private.itemList)
local maxQuantity = itemFilter:GetMaxQuantity()
local firstQuery = nil
for _, query in auctionScan:QueryIterator(queryOffset) do
private.targetItem[query] = targetItem
query:AddCustomFilter(private.TargetItemQueryFilter)
if maxQuantity then
if firstQuery then
-- redirect to the first query so the max quantity spans them all
private.generalMaxQuantity[query] = firstQuery
else
private.generalMaxQuantity[query] = maxQuantity
firstQuery = query
end
end
end
auctionScan:AddResultsUpdateCallback(private.ResultsUpdated)
auctionScan:SetScript("OnQueryDone", private.OnQueryDone)
elseif itemFilter:GetDisenchant() then
local queryOffset = auctionScan:GetNumQueries()
local targetItem = Conversions.GetTargetItemByName(itemFilter:GetStr())
assert(targetItem)
-- generate queries for groups of items that d/e into the target item
local disenchantInfo = DisenchantInfo.GetInfo(targetItem)
for _, info in ipairs(disenchantInfo.sourceInfo) do
auctionScan:NewQuery()
:SetLevelRange(disenchantInfo.minLevel, disenchantInfo.maxLevel)
:SetQualityRange(info.quality, info.quality)
:SetClass(info.classId)
:SetItemLevelRange(info.minItemLevel, info.maxItemLevel)
end
-- add a query for the target item itself
wipe(private.itemList)
tinsert(private.itemList, targetItem)
private.targetItem[targetItem] = targetItem
auctionScan:AddItemListQueriesThreaded(private.itemList)
-- add our filter to each query and generate a lookup from query to target item
local maxQuantity = itemFilter:GetMaxQuantity()
local firstQuery = nil
for _, query in auctionScan:QueryIterator(queryOffset) do
private.targetItem[query] = targetItem
query:AddCustomFilter(private.TargetItemQueryFilter)
if maxQuantity then
if firstQuery then
-- redirect to the first query so the max quantity spans them all
private.generalMaxQuantity[query] = firstQuery
else
private.generalMaxQuantity[query] = maxQuantity
firstQuery = query
end
end
end
auctionScan:AddResultsUpdateCallback(private.ResultsUpdated)
auctionScan:SetScript("OnQueryDone", private.OnQueryDone)
else
local query = auctionScan:NewQuery()
query:SetStr(itemFilter:GetStr(), itemFilter:GetExactOnly())
query:SetQualityRange(itemFilter:GetMinQuality(), itemFilter:GetMaxQuality())
query:SetLevelRange(itemFilter:GetMinLevel(), itemFilter:GetMaxLevel())
query:SetItemLevelRange(itemFilter:GetMinItemLevel(), itemFilter:GetMaxItemLevel())
query:SetClass(itemFilter:GetClass(), itemFilter:GetSubClass(), itemFilter:GetInvSlotId())
query:SetUsable(itemFilter:GetUsableOnly())
query:SetUncollected(itemFilter:GetUncollected())
query:SetUpgrades(itemFilter:GetUpgrades())
query:SetPriceRange(itemFilter:GetMinPrice(), itemFilter:GetMaxPrice())
query:SetItems(itemFilter:GetItem())
query:SetCanLearn(itemFilter:GetCanLearn())
query:SetUnlearned(itemFilter:GetUnlearned())
private.generalMaxQuantity[query] = itemFilter:GetMaxQuantity()
end
end
end
if not private.isSpecial then
TSM.Shopping.SavedSearches.RecordFilterSearch(filterStr)
end
end
-- run the scan
if not auctionScan:ScanQueriesThreaded() then
Log.PrintUser(L["TSM failed to scan some auctions. Please rerun the scan."])
end
return true
end
-- ============================================================================
-- FilterSearchContext Class
-- ============================================================================
function FilterSearchContext.GetMaxCanBuy(self, itemString)
local targetItemString = private.targetItem[itemString]
local maxNum = nil
local itemQuery = private.GetMaxQuantityQuery(targetItemString or itemString)
if itemQuery then
maxNum = private.generalMaxQuantity[itemQuery]
if targetItemString then
local rate, chunkSize = private.GetTargetItemRate(targetItemString, itemString)
maxNum = Math.Ceil(maxNum / rate, chunkSize)
end
end
return maxNum
end
function FilterSearchContext.OnBuy(self, itemString, quantity)
local targetItemString = private.targetItem[itemString]
if targetItemString then
quantity = quantity * private.GetTargetItemRate(targetItemString, itemString)
itemString = targetItemString
end
self.__super:OnBuy(itemString, quantity)
local itemQuery = private.GetMaxQuantityQuery(itemString)
if itemQuery then
private.generalMaxQuantity[itemQuery] = private.generalMaxQuantity[itemQuery] - quantity
if private.generalMaxQuantity[itemQuery] <= 0 then
itemQuery:WipeBrowseResults()
for query, maxQuantity in pairs(private.generalMaxQuantity) do
if maxQuantity == itemQuery then
query:WipeBrowseResults()
end
end
end
end
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.ValidateFilterStr(filterStr, mode)
assert(mode == "NORMAL" or mode == "CRAFTING" or mode == "DISENCHANT")
filterStr = strtrim(filterStr)
if mode == "NORMAL" and not TSM.IsWowClassic() and filterStr == "" then
return filterStr
end
local isValid, errMsg = true, nil
local filters = TempTable.Acquire()
for filter in String.SplitIterator(filterStr, ";") do
filter = strtrim(filter)
if isValid and gsub(filter, "/", "") ~= "" then
local filterIsValid, filterErrMsg = private.itemFilter:ParseStr(filter)
if filterIsValid then
local str = private.itemFilter:GetStr()
if mode == "CRAFTING" and not strfind(strlower(filter), "/crafting") and str then
filter = filter.."/crafting"
elseif mode == "DISENCHANT" and not strfind(strlower(filter), "/disenchant") and str then
filter = filter.."/disenchant"
end
if strfind(strlower(filter), "/crafting") then
local craftingTargetItem = str and Conversions.GetTargetItemByName(str) or nil
if not craftingTargetItem or not Conversions.GetSourceItems(craftingTargetItem) then
isValid = false
errMsg = errMsg or L["The specified item is not supported for crafting searches."]
end
end
if strfind(strlower(filter), "/disenchant") then
local targetItemString = str and Conversions.GetTargetItemByName(str) or nil
if not DisenchantInfo.IsTargetItem(targetItemString) then
isValid = false
errMsg = errMsg or L["The specified item is not supported for disenchant searches."]
end
end
else
isValid = false
errMsg = errMsg or filterErrMsg
end
else
isValid = false
end
if isValid then
tinsert(filters, filter)
end
end
local result = table.concat(filters, ";")
TempTable.Release(filters)
result = isValid and result ~= "" and result or nil
errMsg = errMsg or L["The specified filter was empty."]
return result, errMsg
end
function private.MarketValueFunction(subRow)
local baseItemString = subRow:GetBaseItemString()
local itemString = subRow:GetItemString()
if next(private.targetItem) then
local targetItemString = private.targetItem[itemString]
if not itemString or not targetItemString then
return nil
end
local targetItemRate = private.GetTargetItemRate(targetItemString, itemString)
return Math.Round(targetItemRate * CustomPrice.GetValue(private.marketValueSource, targetItemString))
else
return CustomPrice.GetValue(private.marketValueSource, itemString or baseItemString)
end
end
function private.GetTargetItemRate(targetItemString, itemString)
if itemString == targetItemString then
return 1, 1
end
if DisenchantInfo.IsTargetItem(targetItemString) then
local classId = ItemInfo.GetClassId(itemString)
local ilvl = ItemInfo.GetItemLevel(ItemString.GetBaseFast(itemString))
local quality = ItemInfo.GetQuality(itemString)
local amountOfMats = DisenchantInfo.GetTargetItemSourceInfo(targetItemString, classId, quality, ilvl)
if amountOfMats then
return amountOfMats, 1
end
end
local conversionInfo = Conversions.GetSourceItems(targetItemString)
local conversionChunkSize = 1
for _ in Conversions.TargetItemsByMethodIterator(itemString, Conversions.METHOD.MILL) do
conversionChunkSize = 5
end
for _ in Conversions.TargetItemsByMethodIterator(itemString, Conversions.METHOD.PROSPECT) do
conversionChunkSize = 5
end
return conversionInfo and conversionInfo[itemString] or 0, conversionChunkSize
end
function private.TargetItemQueryFilter(query, row)
local itemString = row:GetItemString()
local targetItemString = private.targetItem[itemString] or private.targetItem[query]
return itemString and targetItemString and private.GetTargetItemRate(targetItemString, itemString) == 0
end
function private.ResultsUpdated(_, query)
local targetItemString = private.targetItem[query]
if not targetItemString then
return
end
-- populate the targetItem table for each item in the results
for _, row in query:BrowseResultsIterator() do
if row:HasItemInfo() then
for _, subRow in row:SubRowIterator() do
local itemString = subRow:GetItemString()
if itemString then
private.targetItem[itemString] = targetItemString
end
end
end
end
end
function private.OnQueryDone(_, query)
private.ResultsUpdated(nil, query)
private.targetItem[query] = nil
end
function private.GetMaxQuantityQuery(itemString)
if not next(private.generalMaxQuantity) then
return
end
-- find the query this item belongs to
local itemQuery = nil
for query, value in pairs(private.generalMaxQuantity) do
local containsItem = false
for _ in query:ItemSubRowIterator(itemString) do
containsItem = true
end
if containsItem then
-- resolve any potential redirection to the base query
itemQuery = type(value) == "number" and query or value
break
end
end
if not itemQuery or not private.generalMaxQuantity[itemQuery] then
return
end
return itemQuery
end

View File

@@ -0,0 +1,45 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local GreatDealsSearch = TSM.Shopping:NewPackage("GreatDealsSearch")
local Vararg = TSM.Include("Util.Vararg")
local ItemInfo = TSM.Include("Service.ItemInfo")
local private = {
filter = nil,
}
-- ============================================================================
-- Module Functions
-- ============================================================================
function GreatDealsSearch.OnEnable()
local appData = TSMAPI.AppHelper and TSMAPI.AppHelper:FetchData("SHOPPING_SEARCHES")
if not appData then
return
end
for _, info in pairs(appData) do
local realmName, data = unpack(info)
if TSMAPI.AppHelper:IsCurrentRealm(realmName) then
private.filter = assert(loadstring(data))().greatDeals
if private.filter == "" then
break
end
-- populate item info cache
for _, item in Vararg.Iterator(strsplit(";", private.filter)) do
item = strsplit("/", item)
ItemInfo.FetchInfo(item)
end
break
end
end
end
function GreatDealsSearch.GetFilter()
return private.filter
end

View File

@@ -0,0 +1,172 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local GroupSearch = TSM.Shopping:NewPackage("GroupSearch")
local L = TSM.Include("Locale").GetTable()
local Log = TSM.Include("Util.Log")
local TempTable = TSM.Include("Util.TempTable")
local ItemString = TSM.Include("Util.ItemString")
local Threading = TSM.Include("Service.Threading")
local ItemInfo = TSM.Include("Service.ItemInfo")
local GroupSearchContext = TSM.Include("LibTSMClass").DefineClass("GroupSearchContext", TSM.Shopping.ShoppingSearchContext)
local private = {
groups = {},
itemList = {},
maxQuantity = {},
scanThreadId = nil,
seenMaxPrice = {},
searchContext = nil,
queries = {},
}
-- ============================================================================
-- Module Functions
-- ============================================================================
function GroupSearch.OnInitialize()
-- initialize thread
private.scanThreadId = Threading.New("GROUP_SEARCH", private.ScanThread)
private.searchContext = GroupSearchContext(private.scanThreadId, private.MarketValueFunction)
end
function GroupSearch.GetSearchContext(groupList)
return private.searchContext:SetScanContext(L["Group Search"], groupList, nil, L["Max Price"])
end
-- ============================================================================
-- Scan Thread
-- ============================================================================
function private.ScanThread(auctionScan, groupList)
wipe(private.seenMaxPrice)
-- create the list of items, and add filters for them
wipe(private.itemList)
wipe(private.maxQuantity)
wipe(private.queries)
for _, groupPath in ipairs(groupList) do
private.groups[groupPath] = true
for _, itemString in TSM.Groups.ItemIterator(groupPath) do
local isValid, maxQuantityOrErr = TSM.Operations.Shopping.ValidAndGetRestockQuantity(itemString)
if isValid then
private.maxQuantity[itemString] = maxQuantityOrErr
tinsert(private.itemList, itemString)
elseif maxQuantityOrErr then
Log.PrintfUser(L["Invalid custom price source for %s. %s"], ItemInfo.GetLink(itemString), maxQuantityOrErr)
end
end
end
if #private.itemList == 0 then
return false
end
auctionScan:AddItemListQueriesThreaded(private.itemList)
for _, query in auctionScan:QueryIterator() do
query:SetIsBrowseDoneFunction(private.QueryIsBrowseDoneFunction)
query:AddCustomFilter(private.QueryFilter)
tinsert(private.queries, query)
end
-- run the scan
if not auctionScan:ScanQueriesThreaded() then
Log.PrintUser(L["TSM failed to scan some auctions. Please rerun the scan."])
end
return true
end
-- ============================================================================
-- GroupSearchContext Class
-- ============================================================================
function GroupSearchContext.GetMaxCanBuy(self, itemString)
return private.maxQuantity[itemString]
end
function GroupSearchContext.OnBuy(self, itemString, quantity)
self.__super:OnBuy(itemString, quantity)
if not private.maxQuantity[itemString] then
return
end
private.maxQuantity[itemString] = private.maxQuantity[itemString] - quantity
if private.maxQuantity[itemString] <= 0 then
private.maxQuantity[itemString] = nil
local toRemove = TempTable.Acquire()
for _, query in ipairs(private.queries) do
for _, row in query:BrowseResultsIterator() do
if row:HasItemInfo() then
for _, subRow in row:SubRowIterator() do
if subRow:GetItemString() == itemString then
tinsert(toRemove, subRow)
end
end
for _, subRow in ipairs(toRemove) do
row:RemoveSubRow(subRow)
end
wipe(toRemove)
end
end
end
TempTable.Release(toRemove)
end
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.QueryIsBrowseDoneFunction(query)
local isDone = true
for itemString in query:ItemIterator() do
if TSM.Operations.Shopping.ShouldShowAboveMaxPrice(itemString) then
-- need to scan all the auctions
isDone = false
elseif not private.seenMaxPrice[itemString] then
-- we haven't seen any auctions above the max price, so need to keep scanning
isDone = false
end
end
return isDone
end
function private.QueryFilter(query, row)
local baseItemString = row:GetBaseItemString()
local itemString = row:GetItemString()
local _, itemBuyout, minItemBuyout = row:GetBuyouts()
itemBuyout = itemBuyout or minItemBuyout
if not itemBuyout then
return false
elseif itemBuyout == 0 then
return true
end
if itemString then
local isFiltered, aboveMax = TSM.Operations.Shopping.IsFiltered(itemString, itemBuyout)
private.seenMaxPrice[itemString] = private.seenMaxPrice[itemString] or aboveMax
return isFiltered
else
local allFiltered = true
for queryItemString in query:ItemIterator() do
if ItemString.GetBaseFast(queryItemString) == baseItemString and not TSM.Operations.Shopping.IsFiltered(queryItemString, itemBuyout) then
allFiltered = false
end
end
return allFiltered
end
end
function private.MarketValueFunction(row)
local itemString = row:GetItemString()
return itemString and TSM.Operations.Shopping.GetMaxPrice(itemString) or nil
end

View File

@@ -0,0 +1,143 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local SavedSearches = TSM.Shopping:NewPackage("SavedSearches")
local Log = TSM.Include("Util.Log")
local Database = TSM.Include("Util.Database")
local TempTable = TSM.Include("Util.TempTable")
local Settings = TSM.Include("Service.Settings")
local private = {
settings = nil,
db = nil,
}
local MAX_RECENT_SEARCHES = 2000
-- ============================================================================
-- Module Functions
-- ============================================================================
function SavedSearches.OnInitialize()
private.settings = Settings.NewView()
:AddKey("global", "userData", "savedShoppingSearches")
-- remove duplicates
local seen = TempTable.Acquire()
for i = #private.settings.savedShoppingSearches.filters, 1, -1 do
local filter = private.settings.savedShoppingSearches.filters[i]
local filterLower = strlower(private.settings.savedShoppingSearches.filters[i])
if seen[filterLower] then
tremove(private.settings.savedShoppingSearches.filters, i)
private.settings.savedShoppingSearches.name[filter] = nil
private.settings.savedShoppingSearches.isFavorite[filter] = nil
else
seen[filterLower] = true
end
end
TempTable.Release(seen)
-- remove old recent searches
local remainingRecentSearches = MAX_RECENT_SEARCHES
local numRemoved = 0
for i = #private.settings.savedShoppingSearches.filters, 1, -1 do
local filter = private.settings.savedShoppingSearches.filters
if not private.settings.savedShoppingSearches.isFavorite[filter] then
if remainingRecentSearches > 0 then
remainingRecentSearches = remainingRecentSearches - 1
else
tremove(private.settings.savedShoppingSearches.filters, i)
private.settings.savedShoppingSearches.name[filter] = nil
numRemoved = numRemoved + 1
end
end
end
if numRemoved > 0 then
Log.Info("Removed %d old recent searches", numRemoved)
end
private.db = Database.NewSchema("SHOPPING_SAVED_SEARCHES")
:AddUniqueNumberField("index")
:AddStringField("name")
:AddBooleanField("isFavorite")
:AddStringField("filter")
:AddIndex("index")
:AddIndex("name")
:Commit()
private.RebuildDB()
end
function SavedSearches.CreateRecentSearchesQuery()
return private.db:NewQuery()
:OrderBy("index", false)
end
function SavedSearches.CreateFavoriteSearchesQuery()
return private.db:NewQuery()
:Equal("isFavorite", true)
:OrderBy("name", true)
end
function SavedSearches.SetSearchIsFavorite(dbRow, isFavorite)
local filter = dbRow:GetField("filter")
private.settings.savedShoppingSearches.isFavorite[filter] = isFavorite or nil
dbRow:SetField("isFavorite", isFavorite)
:Update()
end
function SavedSearches.RenameSearch(dbRow, newName)
local filter = dbRow:GetField("filter")
private.settings.savedShoppingSearches.name[filter] = newName ~= filter and newName or nil
dbRow:SetField("name", newName)
:Update()
end
function SavedSearches.DeleteSearch(dbRow)
local index, filter = dbRow:GetFields("index", "filter")
tremove(private.settings.savedShoppingSearches.filters, index)
private.settings.savedShoppingSearches.name[filter] = nil
private.settings.savedShoppingSearches.isFavorite[filter] = nil
private.RebuildDB()
end
function SavedSearches.RecordFilterSearch(filter)
for i, existingFilter in ipairs(private.settings.savedShoppingSearches.filters) do
if strlower(existingFilter) == strlower(filter) then
-- move this to the end of the list and rebuild the DB
-- insert the existing filter so we don't need to update the isFavorite and name tables
tremove(private.settings.savedShoppingSearches.filters, i)
tinsert(private.settings.savedShoppingSearches.filters, existingFilter)
private.RebuildDB()
return
end
end
-- didn't find an existing entry, so add a new one
tinsert(private.settings.savedShoppingSearches.filters, filter)
private.db:NewRow()
:SetField("index", #private.settings.savedShoppingSearches.filters)
:SetField("name", filter)
:SetField("isFavorite", false)
:SetField("filter", filter)
:Create()
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.RebuildDB()
private.db:TruncateAndBulkInsertStart()
for index, filter in ipairs(private.settings.savedShoppingSearches.filters) do
local name = private.settings.savedShoppingSearches.name[filter] or filter
local isFavorite = private.settings.savedShoppingSearches.isFavorite[filter] and true or false
private.db:BulkInsertNewRow(index, name, isFavorite, filter)
end
private.db:BulkInsertEnd()
end

View File

@@ -0,0 +1,77 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local SearchCommon = TSM.Shopping:NewPackage("SearchCommon")
local Delay = TSM.Include("Util.Delay")
local Threading = TSM.Include("Service.Threading")
local private = {
findThreadId = nil,
callback = nil,
isRunning = false,
pendingStartArgs = {},
}
-- ============================================================================
-- Module Functions
-- ============================================================================
function SearchCommon.OnInitialize()
-- initialize threads
private.findThreadId = Threading.New("FIND_SEARCH", private.FindThread)
Threading.SetCallback(private.findThreadId, private.ThreadCallback)
end
function SearchCommon.StartFindAuction(auctionScan, auction, callback, noSeller)
wipe(private.pendingStartArgs)
private.pendingStartArgs.auctionScan = auctionScan
private.pendingStartArgs.auction = auction
private.pendingStartArgs.callback = callback
private.pendingStartArgs.noSeller = noSeller
Delay.AfterTime("SEARCH_COMMON_THREAD_START", 0, private.StartThread)
end
function SearchCommon.StopFindAuction(noKill)
wipe(private.pendingStartArgs)
private.callback = nil
if not noKill then
Threading.Kill(private.findThreadId)
end
end
-- ============================================================================
-- Find Thread
-- ============================================================================
function private.FindThread(auctionScan, row, noSeller)
return auctionScan:FindAuctionThreaded(row, noSeller)
end
function private.StartThread()
if not private.pendingStartArgs.auctionScan then
return
end
if private.isRunning then
Delay.AfterTime("SEARCH_COMMON_THREAD_START", 0.1, private.StartThread)
return
end
private.isRunning = true
private.callback = private.pendingStartArgs.callback
Threading.Start(private.findThreadId, private.pendingStartArgs.auctionScan, private.pendingStartArgs.auction, private.pendingStartArgs.noSeller)
wipe(private.pendingStartArgs)
end
function private.ThreadCallback(...)
private.isRunning = false
if private.callback then
private.callback(...)
end
end

View File

@@ -0,0 +1,83 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local VendorSearch = TSM.Shopping:NewPackage("VendorSearch")
local L = TSM.Include("Locale").GetTable()
local Log = TSM.Include("Util.Log")
local Threading = TSM.Include("Service.Threading")
local ItemInfo = TSM.Include("Service.ItemInfo")
local private = {
itemList = {},
scanThreadId = nil,
searchContext = nil,
}
-- ============================================================================
-- Module Functions
-- ============================================================================
function VendorSearch.OnInitialize()
-- initialize thread
private.scanThreadId = Threading.New("VENDOR_SEARCH", private.ScanThread)
private.searchContext = TSM.Shopping.ShoppingSearchContext(private.scanThreadId, private.MarketValueFunction)
end
function VendorSearch.GetSearchContext()
return private.searchContext:SetScanContext(L["Vendor Search"], nil, nil, L["Vendor Sell Price"])
end
-- ============================================================================
-- Scan Thread
-- ============================================================================
function private.ScanThread(auctionScan)
if (TSM.AuctionDB.GetLastCompleteScanTime() or 0) < time() - 60 * 60 * 12 then
Log.PrintUser(L["No recent AuctionDB scan data found."])
return false
end
-- create the list of items
wipe(private.itemList)
for _, itemString, _, minBuyout in TSM.AuctionDB.LastScanIteratorThreaded() do
local vendorSell = ItemInfo.GetVendorSell(itemString) or 0
if vendorSell and minBuyout and minBuyout < vendorSell then
tinsert(private.itemList, itemString)
end
Threading.Yield()
end
-- run the scan
auctionScan:AddItemListQueriesThreaded(private.itemList)
for _, query in auctionScan:QueryIterator() do
query:AddCustomFilter(private.QueryFilter)
end
if not auctionScan:ScanQueriesThreaded() then
Log.PrintUser(L["TSM failed to scan some auctions. Please rerun the scan."])
end
return true
end
function private.QueryFilter(_, row)
local itemString = row:GetItemString()
if not itemString then
return false
end
local _, itemBuyout = row:GetBuyouts()
if not itemBuyout then
return false
end
local vendorSell = ItemInfo.GetVendorSell(itemString)
return not vendorSell or itemBuyout == 0 or itemBuyout >= vendorSell
end
function private.MarketValueFunction(row)
return ItemInfo.GetVendorSell(row:GetItemString() or row:GetBaseItemString())
end