TradeSkillMaster/Core/Service/Operations/Core.lua

467 lines
17 KiB
Lua
Raw Normal View History

2020-11-13 14:13:12 -05:00
-- ------------------------------------------------------------------------------ --
-- 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