467 lines
17 KiB
Lua
467 lines
17 KiB
Lua
-- ------------------------------------------------------------------------------ --
|
|
-- TradeSkillMaster --
|
|
-- https://tradeskillmaster.com --
|
|
-- All Rights Reserved - Detailed license information included with addon. --
|
|
-- ------------------------------------------------------------------------------ --
|
|
|
|
local _, TSM = ...
|
|
local Operations = TSM:NewPackage("Operations")
|
|
local TempTable = TSM.Include("Util.TempTable")
|
|
local Log = TSM.Include("Util.Log")
|
|
local Database = TSM.Include("Util.Database")
|
|
local private = {
|
|
db = nil,
|
|
operations = nil,
|
|
operationInfo = {},
|
|
operationModules = {},
|
|
shouldCreateDefaultOperations = false,
|
|
ignoreProfileUpdate = false,
|
|
}
|
|
local COMMON_OPERATION_INFO = {
|
|
ignorePlayer = { type = "table", default = {} },
|
|
ignoreFactionrealm = { type = "table", default = {} },
|
|
relationships = { type = "table", default = {} },
|
|
}
|
|
local FACTION_REALM = UnitFactionGroup("player").." - "..GetRealmName()
|
|
local PLAYER_KEY = UnitName("player").." - "..FACTION_REALM
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Modules Functions
|
|
-- ============================================================================
|
|
|
|
function Operations.OnInitialize()
|
|
private.db = Database.NewSchema("OPERATIONS")
|
|
:AddStringField("moduleName")
|
|
:AddStringField("operationName")
|
|
:AddIndex("moduleName")
|
|
:Commit()
|
|
if TSM.db.global.coreOptions.globalOperations then
|
|
private.operations = TSM.db.global.userData.operations
|
|
else
|
|
private.operations = TSM.db.profile.userData.operations
|
|
end
|
|
private.RebuildDB()
|
|
private.shouldCreateDefaultOperations = not TSM.db.profile.internalData.createdDefaultOperations
|
|
TSM.db.profile.internalData.createdDefaultOperations = true
|
|
TSM.db:RegisterCallback("OnProfileUpdated", private.OnProfileUpdated)
|
|
end
|
|
|
|
function Operations.Register(moduleName, localizedName, operationInfo, maxOperations, infoCallback, customSanitizeFunction)
|
|
for key, info in pairs(operationInfo) do
|
|
assert(type(key) == "string" and type(info) == "table")
|
|
assert(info.type == type(info.default))
|
|
end
|
|
for key, info in pairs(COMMON_OPERATION_INFO) do
|
|
assert(not operationInfo[key])
|
|
operationInfo[key] = info
|
|
end
|
|
tinsert(private.operationModules, moduleName)
|
|
private.operationInfo[moduleName] = {
|
|
info = operationInfo,
|
|
localizedName = localizedName,
|
|
maxOperations = maxOperations,
|
|
infoCallback = infoCallback,
|
|
customSanitizeFunction = customSanitizeFunction,
|
|
}
|
|
|
|
local shouldCreateDefaultOperations = private.shouldCreateDefaultOperations or not private.operations[moduleName]
|
|
private.operations[moduleName] = private.operations[moduleName] or {}
|
|
|
|
if shouldCreateDefaultOperations and not private.operations[moduleName]["#Default"] then
|
|
-- create default operation
|
|
Operations.Create(moduleName, "#Default")
|
|
end
|
|
private.ValidateOperations(moduleName)
|
|
private.RebuildDB()
|
|
end
|
|
|
|
function Operations.IsCommonKey(key)
|
|
return COMMON_OPERATION_INFO[key] and true or false
|
|
end
|
|
|
|
function Operations.IsValidName(operationName)
|
|
return operationName == strtrim(operationName) and operationName ~= "" and not strmatch(operationName, TSM.CONST.OPERATION_SEP)
|
|
end
|
|
|
|
function Operations.ModuleIterator()
|
|
return ipairs(private.operationModules)
|
|
end
|
|
|
|
function Operations.ModuleExists(moduleName)
|
|
return private.operationInfo[moduleName] and true or false
|
|
end
|
|
|
|
function Operations.GetLocalizedName(moduleName)
|
|
return private.operationInfo[moduleName].localizedName
|
|
end
|
|
|
|
function Operations.GetMaxNumber(moduleName)
|
|
return private.operationInfo[moduleName].maxOperations
|
|
end
|
|
|
|
function Operations.GetSettingDefault(moduleName, key)
|
|
local info = private.operationInfo[moduleName].info[key]
|
|
return info.type == "table" and CopyTable(info.default) or info.default
|
|
end
|
|
|
|
function Operations.OperationIterator(moduleName)
|
|
local operations = TempTable.Acquire()
|
|
for operationName in pairs(private.operations[moduleName]) do
|
|
tinsert(operations, operationName)
|
|
end
|
|
sort(operations)
|
|
return TempTable.Iterator(operations)
|
|
end
|
|
|
|
function Operations.Exists(moduleName, operationName)
|
|
return private.operations[moduleName][operationName] and true or false
|
|
end
|
|
|
|
function Operations.GetSettings(moduleName, operationName)
|
|
return private.operations[moduleName][operationName]
|
|
end
|
|
|
|
function Operations.Create(moduleName, operationName)
|
|
assert(not private.operations[moduleName][operationName])
|
|
private.operations[moduleName][operationName] = {}
|
|
Operations.Reset(moduleName, operationName)
|
|
private.RebuildDB()
|
|
end
|
|
|
|
function Operations.BulkCreateFromImport(operations, replaceExisting)
|
|
for moduleName, moduleOperations in pairs(operations) do
|
|
for operationName, operationSettings in pairs(moduleOperations) do
|
|
assert(replaceExisting or not private.operations[moduleName][operationName])
|
|
private.operations[moduleName][operationName] = operationSettings
|
|
end
|
|
end
|
|
private.RebuildDB()
|
|
end
|
|
|
|
function Operations.Rename(moduleName, oldName, newName)
|
|
assert(private.operations[moduleName][oldName])
|
|
private.operations[moduleName][newName] = private.operations[moduleName][oldName]
|
|
private.operations[moduleName][oldName] = nil
|
|
-- redirect relationships
|
|
for _, operation in pairs(private.operations[moduleName]) do
|
|
for key, target in pairs(operation.relationships) do
|
|
if target == oldName then
|
|
operation.relationships[key] = newName
|
|
end
|
|
end
|
|
end
|
|
TSM.Groups.OperationRenamed(moduleName, oldName, newName)
|
|
private.RebuildDB()
|
|
end
|
|
|
|
function Operations.Copy(moduleName, operationName, sourceOperationName)
|
|
assert(private.operations[moduleName][operationName] and private.operations[moduleName][sourceOperationName])
|
|
for key, info in pairs(private.operationInfo[moduleName].info) do
|
|
local sourceValue = private.operations[moduleName][sourceOperationName][key]
|
|
private.operations[moduleName][operationName][key] = info.type == "table" and CopyTable(sourceValue) or sourceValue
|
|
end
|
|
private.RemoveDeadRelationships(moduleName)
|
|
private.RebuildDB()
|
|
end
|
|
|
|
function Operations.Delete(moduleName, operationName)
|
|
assert(private.operations[moduleName][operationName])
|
|
private.operations[moduleName][operationName] = nil
|
|
private.RemoveDeadRelationships(moduleName)
|
|
TSM.Groups.RemoveOperationFromAllGroups(moduleName, operationName)
|
|
private.RebuildDB()
|
|
end
|
|
|
|
function Operations.DeleteList(moduleName, operationNames)
|
|
for _, operationName in ipairs(operationNames) do
|
|
assert(private.operations[moduleName][operationName])
|
|
private.operations[moduleName][operationName] = nil
|
|
private.RemoveDeadRelationships(moduleName)
|
|
TSM.Groups.RemoveOperationFromAllGroups(moduleName, operationName)
|
|
end
|
|
private.RebuildDB()
|
|
end
|
|
|
|
function Operations.Reset(moduleName, operationName)
|
|
for key in pairs(private.operationInfo[moduleName].info) do
|
|
private.operations[moduleName][operationName][key] = Operations.GetSettingDefault(moduleName, key)
|
|
end
|
|
end
|
|
|
|
function Operations.Update(moduleName, operationName)
|
|
for key in pairs(private.operations[moduleName][operationName].relationships) do
|
|
local operation = private.operations[moduleName][operationName]
|
|
while operation.relationships[key] do
|
|
local newOperation = private.operations[moduleName][operation.relationships[key]]
|
|
if not newOperation then
|
|
break
|
|
end
|
|
operation = newOperation
|
|
end
|
|
private.operations[moduleName][operationName][key] = operation[key]
|
|
end
|
|
end
|
|
|
|
function Operations.IsCircularRelationship(moduleName, operationName, key)
|
|
local visited = TempTable.Acquire()
|
|
while operationName do
|
|
if visited[operationName] then
|
|
TempTable.Release(visited)
|
|
return true
|
|
end
|
|
visited[operationName] = true
|
|
operationName = private.operations[moduleName][operationName].relationships[key]
|
|
end
|
|
TempTable.Release(visited)
|
|
return false
|
|
end
|
|
|
|
function Operations.GetFirstOperationByItem(moduleName, itemString)
|
|
local groupPath = TSM.Groups.GetPathByItem(itemString)
|
|
for _, operationName in TSM.Groups.OperationIterator(groupPath, moduleName) do
|
|
Operations.Update(moduleName, operationName)
|
|
if not private.IsIgnored(moduleName, operationName) then
|
|
return operationName, private.operations[moduleName][operationName]
|
|
end
|
|
end
|
|
end
|
|
|
|
function Operations.GroupOperationIterator(moduleName, groupPath)
|
|
local operations = TempTable.Acquire()
|
|
operations.moduleName = moduleName
|
|
for _, operationName in TSM.Groups.OperationIterator(groupPath, moduleName) do
|
|
Operations.Update(moduleName, operationName)
|
|
if not private.IsIgnored(moduleName, operationName) then
|
|
tinsert(operations, operationName)
|
|
end
|
|
end
|
|
return private.GroupOperationIteratorHelper, operations, 0
|
|
end
|
|
|
|
function Operations.GroupHasOperation(moduleName, groupPath, targetOperationName)
|
|
for _, operationName in TSM.Groups.OperationIterator(groupPath, moduleName) do
|
|
if operationName == targetOperationName then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function Operations.GetDescription(moduleName, operationName)
|
|
local operationSettings = private.operations[moduleName][operationName]
|
|
assert(operationSettings)
|
|
Operations.Update(moduleName, operationName)
|
|
return private.operationInfo[moduleName].infoCallback(operationSettings)
|
|
end
|
|
|
|
function Operations.SanitizeSettings(moduleName, operationName, operationSettings, silentMissingCommonKeys, noRelationshipCheck)
|
|
local didReset = false
|
|
local operationInfo = private.operationInfo[moduleName].info
|
|
if private.operationInfo[moduleName].customSanitizeFunction then
|
|
private.operationInfo[moduleName].customSanitizeFunction(operationSettings)
|
|
end
|
|
for key, value in pairs(operationSettings) do
|
|
if not noRelationshipCheck and Operations.IsCircularRelationship(moduleName, operationName, key) then
|
|
Log.Err("Removing circular relationship (%s, %s, %s)", moduleName, operationName, key)
|
|
operationSettings.relationships[key] = nil
|
|
end
|
|
if not operationInfo[key] then
|
|
operationSettings[key] = nil
|
|
elseif type(value) ~= operationInfo[key].type then
|
|
if operationInfo[key].type == "string" and type(value) == "number" then
|
|
-- some custom price settings were potentially stored as numbers previously, so just convert them
|
|
operationSettings[key] = tostring(value)
|
|
else
|
|
didReset = true
|
|
Log.Err("Resetting operation setting %s,%s,%s (%s)", moduleName, operationName, tostring(key), tostring(value))
|
|
operationSettings[key] = operationInfo[key].type == "table" and CopyTable(operationInfo[key].default) or operationInfo[key].default
|
|
end
|
|
elseif operationInfo[key].customSanitizeFunction then
|
|
operationSettings[key] = operationInfo[key].customSanitizeFunction(value)
|
|
end
|
|
end
|
|
for key in pairs(operationInfo) do
|
|
if operationSettings[key] == nil then
|
|
-- this key was missing
|
|
if operationInfo[key].type == "boolean" then
|
|
-- we previously stored booleans as nil instead of false
|
|
operationSettings[key] = false
|
|
else
|
|
if not silentMissingCommonKeys or not Operations.IsCommonKey(key) then
|
|
didReset = true
|
|
Log.Err("Resetting missing operation setting %s,%s,%s", moduleName, operationName, tostring(key))
|
|
end
|
|
operationSettings[key] = operationInfo[key].type == "table" and CopyTable(operationInfo[key].default) or operationInfo[key].default
|
|
end
|
|
end
|
|
end
|
|
return didReset
|
|
end
|
|
|
|
function Operations.HasRelationship(moduleName, operationName, settingKey)
|
|
return Operations.GetRelationship(moduleName, operationName, settingKey) and true or false
|
|
end
|
|
|
|
function Operations.GetRelationship(moduleName, operationName, settingKey)
|
|
assert(private.operationInfo[moduleName].info[settingKey])
|
|
return private.operations[moduleName][operationName].relationships[settingKey]
|
|
end
|
|
|
|
function Operations.SetRelationship(moduleName, operationName, settingKey, targetOperationName)
|
|
assert(targetOperationName == nil or private.operations[moduleName][targetOperationName])
|
|
assert(private.operationInfo[moduleName].info[settingKey])
|
|
private.operations[moduleName][operationName].relationships[settingKey] = targetOperationName
|
|
end
|
|
|
|
function Operations.GetRelationshipColors(operationType, operationName, settingKey, value)
|
|
local relationshipSet = Operations.HasRelationship(operationType, operationName, settingKey)
|
|
local linkColor = nil
|
|
if not value and relationshipSet then
|
|
linkColor = "INDICATOR_DISABLED"
|
|
elseif not value then
|
|
linkColor = "TEXT_DISABLED"
|
|
elseif relationshipSet then
|
|
linkColor = "INDICATOR"
|
|
else
|
|
linkColor = "TEXT"
|
|
end
|
|
local linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", linkColor)
|
|
return relationshipSet, linkTexture, value and not relationshipSet and "TEXT" or "TEXT_DISABLED"
|
|
end
|
|
|
|
function Operations.IsStoredGlobally()
|
|
return TSM.db.global.coreOptions.globalOperations
|
|
end
|
|
|
|
function Operations.SetStoredGlobally(storeGlobally)
|
|
TSM.db.global.coreOptions.globalOperations = storeGlobally
|
|
-- we shouldn't be running the OnProfileUpdated callback while switching profiles
|
|
private.ignoreProfileUpdate = true
|
|
if storeGlobally then
|
|
-- move current profile to global
|
|
TSM.db.global.userData.operations = CopyTable(TSM.db.profile.userData.operations)
|
|
-- clear out old operations
|
|
for _ in TSM.GetTSMProfileIterator() do
|
|
TSM.db.profile.userData.operations = nil
|
|
end
|
|
else
|
|
-- move global to all profiles
|
|
for _ in TSM.GetTSMProfileIterator() do
|
|
TSM.db.profile.userData.operations = CopyTable(TSM.db.global.userData.operations)
|
|
end
|
|
-- clear out old operations
|
|
TSM.db.global.userData.operations = nil
|
|
end
|
|
private.ignoreProfileUpdate = false
|
|
private.OnProfileUpdated()
|
|
end
|
|
|
|
function Operations.ReplaceProfileOperations(newOperations)
|
|
for k, v in pairs(newOperations) do
|
|
TSM.db.profile.userData.operations[k] = v
|
|
end
|
|
end
|
|
|
|
function Operations.CreateQuery()
|
|
return private.db:NewQuery()
|
|
end
|
|
|
|
function Operations.GroupIterator(moduleName, filterOperationName, overrideOnly)
|
|
local result = TempTable.Acquire()
|
|
|
|
-- check the base group
|
|
if Operations.GroupHasOperation(moduleName, TSM.CONST.ROOT_GROUP_PATH, filterOperationName) then
|
|
tinsert(result, TSM.CONST.ROOT_GROUP_PATH)
|
|
end
|
|
-- need to filter out the groups without operations
|
|
for _, groupPath in TSM.Groups.GroupIterator() do
|
|
if (not overrideOnly or TSM.Groups.HasOperationOverride(groupPath, moduleName)) and Operations.GroupHasOperation(moduleName, groupPath, filterOperationName) then
|
|
tinsert(result, groupPath)
|
|
end
|
|
end
|
|
return TempTable.Iterator(result)
|
|
end
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Private Helper Functions
|
|
-- ============================================================================
|
|
|
|
function private.OnProfileUpdated()
|
|
if private.ignoreProfileUpdate then
|
|
return
|
|
end
|
|
if TSM.db.global.coreOptions.globalOperations then
|
|
private.operations = TSM.db.global.userData.operations
|
|
else
|
|
private.operations = TSM.db.profile.userData.operations
|
|
end
|
|
for _, moduleName in Operations.ModuleIterator() do
|
|
private.ValidateOperations(moduleName)
|
|
end
|
|
private.RebuildDB()
|
|
TSM.Groups.RebuildDatabase()
|
|
end
|
|
|
|
function private.ValidateOperations(moduleName)
|
|
if not private.operations[moduleName] then
|
|
-- this is a new profile
|
|
private.operations[moduleName] = {}
|
|
Operations.Create(moduleName, "#Default")
|
|
return
|
|
end
|
|
for operationName, operationSettings in pairs(private.operations[moduleName]) do
|
|
if type(operationName) ~= "string" or not Operations.IsValidName(operationName) then
|
|
Log.Err("Removing %s operation with invalid name: ", moduleName, tostring(operationName))
|
|
private.operations[moduleName][operationName] = nil
|
|
else
|
|
Operations.SanitizeSettings(moduleName, operationName, operationSettings)
|
|
for key, target in pairs(operationSettings.relationships) do
|
|
if not private.operations[moduleName][target] then
|
|
Log.Err("Removing invalid relationship %s,%s,%s -> %s", moduleName, operationName, tostring(key), tostring(target))
|
|
operationSettings.relationships[key] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function private.IsIgnored(moduleName, operationName)
|
|
local operationSettings = private.operations[moduleName][operationName]
|
|
assert(operationSettings)
|
|
return operationSettings.ignorePlayer[PLAYER_KEY] or operationSettings.ignoreFactionrealm[FACTION_REALM]
|
|
end
|
|
|
|
function private.GroupOperationIteratorHelper(operations, index)
|
|
index = index + 1
|
|
if index > #operations then
|
|
TempTable.Release(operations)
|
|
return
|
|
end
|
|
local operationName = operations[index]
|
|
return index, operationName, private.operations[operations.moduleName][operationName]
|
|
end
|
|
|
|
function private.RemoveDeadRelationships(moduleName)
|
|
for _, operation in pairs(private.operations[moduleName]) do
|
|
for key, target in pairs(operation.relationships) do
|
|
if not private.operations[moduleName][target] then
|
|
operation.relationships[key] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function private.RebuildDB()
|
|
private.db:TruncateAndBulkInsertStart()
|
|
for moduleName, operations in pairs(private.operations) do
|
|
for operationName in pairs(operations) do
|
|
private.db:BulkInsertNewRow(moduleName, operationName)
|
|
end
|
|
end
|
|
private.db:BulkInsertEnd()
|
|
end
|