870 lines
30 KiB
Lua
870 lines
30 KiB
Lua
-- ------------------------------------------------------------------------------ --
|
|
-- TradeSkillMaster --
|
|
-- https://tradeskillmaster.com --
|
|
-- All Rights Reserved - Detailed license information included with addon. --
|
|
-- ------------------------------------------------------------------------------ --
|
|
|
|
local _, TSM = ...
|
|
local ImportExport = TSM.Groups:NewPackage("ImportExport")
|
|
local L = TSM.Include("Locale").GetTable()
|
|
local TempTable = TSM.Include("Util.TempTable")
|
|
local Table = TSM.Include("Util.Table")
|
|
local Log = TSM.Include("Util.Log")
|
|
local String = TSM.Include("Util.String")
|
|
local ItemString = TSM.Include("Util.ItemString")
|
|
local CustomPrice = TSM.Include("Service.CustomPrice")
|
|
local AceSerializer = LibStub("AceSerializer-3.0")
|
|
local LibDeflate = LibStub("LibDeflate")
|
|
local LibSerialize = LibStub("LibSerialize")
|
|
local private = {
|
|
isOperationSettingsTable = {},
|
|
importContext = {
|
|
groupName = nil,
|
|
items = nil,
|
|
groups = nil,
|
|
groupOperations = nil,
|
|
operations = nil,
|
|
customSources = nil,
|
|
numChangedOperations = nil,
|
|
filteredGroups = {},
|
|
},
|
|
}
|
|
local MAGIC_STR = "TSM_EXPORT"
|
|
local VERSION = 1
|
|
local EXPORT_OPERATION_MODULES = {
|
|
Auctioning = true,
|
|
Crafting = true,
|
|
Shopping = true,
|
|
Sniper = true,
|
|
Vendoring = true,
|
|
Warehousing = true,
|
|
}
|
|
local EXPORT_CUSTOM_STRINGS = {
|
|
Auctioning = {
|
|
postCap = true,
|
|
keepQuantity = true,
|
|
maxExpires = true,
|
|
undercut = true,
|
|
minPrice = true,
|
|
maxPrice = true,
|
|
normalPrice = true,
|
|
cancelRepostThreshold = true,
|
|
stackSize = TSM.IsWowClassic() or nil,
|
|
},
|
|
Crafting = {
|
|
minRestock = true,
|
|
maxRestock = true,
|
|
minProfit = true,
|
|
craftPriceMethod = true,
|
|
},
|
|
Shopping = {
|
|
restockQuantity = true,
|
|
maxPrice = true,
|
|
},
|
|
Sniper = {
|
|
belowPrice = true,
|
|
},
|
|
Vendoring = {
|
|
vsMarketValue = true,
|
|
vsMaxMarketValue = true,
|
|
vsDestroyValue = true,
|
|
vsMaxDestroyValue = true,
|
|
},
|
|
Warehousing = {},
|
|
}
|
|
local SERIALIZE_OPTIONS = {
|
|
stable = true,
|
|
filter = function(tbl, key, value)
|
|
return not private.isOperationSettingsTable[tbl] or not TSM.Operations.IsCommonKey(key)
|
|
end,
|
|
}
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Module Functions
|
|
-- ============================================================================
|
|
|
|
function ImportExport.GenerateExport(exportGroupPath, includeSubGroups, excludeOperations, excludeCustomSources)
|
|
assert(exportGroupPath ~= TSM.CONST.ROOT_GROUP_PATH)
|
|
local exportGroupName = TSM.Groups.Path.GetName(exportGroupPath)
|
|
|
|
-- collect the items and sub groups
|
|
local items = TempTable.Acquire()
|
|
local groups = TempTable.Acquire()
|
|
local groupOperations = TempTable.Acquire()
|
|
local operations = TempTable.Acquire()
|
|
local customSources = TempTable.Acquire()
|
|
for moduleName in pairs(EXPORT_OPERATION_MODULES) do
|
|
operations[moduleName] = {}
|
|
end
|
|
assert(not next(private.isOperationSettingsTable))
|
|
for _, groupPath in TSM.Groups.GroupIterator() do
|
|
local relGroupPath = nil
|
|
if TSM.Groups.Path.IsChild(groupPath, exportGroupPath) then
|
|
relGroupPath = TSM.Groups.Path.GetRelative(groupPath, exportGroupPath)
|
|
if not includeSubGroups[relGroupPath] then
|
|
relGroupPath = nil
|
|
end
|
|
elseif groupPath == exportGroupPath then
|
|
relGroupPath = TSM.CONST.ROOT_GROUP_PATH
|
|
end
|
|
if relGroupPath then
|
|
groups[relGroupPath] = true
|
|
for _, itemString in TSM.Groups.ItemIterator(groupPath) do
|
|
items[itemString] = relGroupPath
|
|
end
|
|
if not excludeOperations then
|
|
groupOperations[relGroupPath] = {}
|
|
for moduleName in pairs(EXPORT_OPERATION_MODULES) do
|
|
groupOperations[relGroupPath][moduleName] = {
|
|
-- always override at the top-level
|
|
override = TSM.Groups.HasOperationOverride(groupPath, moduleName) or groupPath == exportGroupPath or nil,
|
|
}
|
|
for _, operationName, operationSettings in TSM.Operations.GroupOperationIterator(moduleName, groupPath) do
|
|
tinsert(groupOperations[relGroupPath][moduleName], operationName)
|
|
operations[moduleName][operationName] = operationSettings
|
|
private.isOperationSettingsTable[operationSettings] = true
|
|
if not excludeCustomSources then
|
|
for key in pairs(EXPORT_CUSTOM_STRINGS[moduleName]) do
|
|
private.GetCustomSources(operationSettings[key], customSources)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local serialized = LibSerialize:SerializeEx(SERIALIZE_OPTIONS, MAGIC_STR, VERSION, exportGroupName, items, groups, groupOperations, operations, customSources)
|
|
local compressed = LibDeflate:EncodeForPrint(LibDeflate:CompressDeflate(serialized))
|
|
local numItems = Table.Count(items)
|
|
local numSubGroups = Table.Count(groups) - 1
|
|
local numOperations = 0
|
|
for _, moduleOperations in pairs(operations) do
|
|
numOperations = numOperations + Table.Count(moduleOperations)
|
|
end
|
|
local numCustomSources = Table.Count(customSources)
|
|
|
|
wipe(private.isOperationSettingsTable)
|
|
TempTable.Release(customSources)
|
|
TempTable.Release(operations)
|
|
TempTable.Release(groupOperations)
|
|
TempTable.Release(groups)
|
|
TempTable.Release(items)
|
|
|
|
return compressed, numItems, numSubGroups, numOperations, numCustomSources
|
|
end
|
|
|
|
function ImportExport.ProcessImport(str)
|
|
return private.DecodeNewImport(str) or private.DecodeOldImport(str) or private.DecodeOldGroupOrItemListImport(str)
|
|
end
|
|
|
|
function ImportExport.GetImportTotals()
|
|
local numExistingItems = 0
|
|
for itemString, groupPath in pairs(private.importContext.items) do
|
|
if not private.importContext.filteredGroups[groupPath] then
|
|
if TSM.Groups.IsItemInGroup(itemString) then
|
|
numExistingItems = numExistingItems + 1
|
|
end
|
|
end
|
|
end
|
|
wipe(private.importContext.customSources)
|
|
local numOperations, numExistingOperations = 0, 0
|
|
for moduleName, moduleOperations in pairs(private.importContext.operations) do
|
|
local usedOperations = TempTable.Acquire()
|
|
for groupPath, operations in pairs(private.importContext.groupOperations) do
|
|
if not private.importContext.filteredGroups[groupPath] then
|
|
for _, operationName in ipairs(operations[moduleName]) do
|
|
usedOperations[operationName] = true
|
|
end
|
|
end
|
|
end
|
|
for operationName, operationSettings in pairs(moduleOperations) do
|
|
if usedOperations[operationName] then
|
|
numOperations = numOperations + 1
|
|
if TSM.Operations.Exists(moduleName, operationName) then
|
|
numExistingOperations = numExistingOperations + 1
|
|
end
|
|
for key in pairs(EXPORT_CUSTOM_STRINGS[moduleName]) do
|
|
private.GetCustomSources(operationSettings[key], private.importContext.customSources)
|
|
end
|
|
end
|
|
end
|
|
TempTable.Release(usedOperations)
|
|
end
|
|
local numExistingCustomSources = 0
|
|
for name in pairs(private.importContext.customSources) do
|
|
if TSM.db.global.userData.customPriceSources[name] then
|
|
numExistingCustomSources = numExistingCustomSources + 1
|
|
end
|
|
end
|
|
local numItems = 0
|
|
for _, groupPath in pairs(private.importContext.items) do
|
|
if not private.importContext.filteredGroups[groupPath] then
|
|
numItems = numItems + 1
|
|
end
|
|
end
|
|
local numGroups = 0
|
|
for groupPath in pairs(private.importContext.groups) do
|
|
if not private.importContext.filteredGroups[groupPath] then
|
|
numGroups = numGroups + 1
|
|
end
|
|
end
|
|
return numItems, numGroups, numExistingItems, numOperations, numExistingOperations, numExistingCustomSources
|
|
end
|
|
|
|
function ImportExport.PendingImportGroupIterator()
|
|
assert(private.importContext.groupName)
|
|
return pairs(private.importContext.groups)
|
|
end
|
|
|
|
function ImportExport.GetPendingImportGroupName()
|
|
assert(private.importContext.groupName)
|
|
return private.importContext.groupName
|
|
end
|
|
|
|
function ImportExport.SetGroupFiltered(groupPath, isFiltered)
|
|
private.importContext.filteredGroups[groupPath] = isFiltered or nil
|
|
end
|
|
|
|
function ImportExport.CommitImport(moveExistingItems, includeOperations, replaceOperations)
|
|
assert(private.importContext.groupName)
|
|
local numOperations, numCustomSources = 0, 0
|
|
if includeOperations and next(private.importContext.operations) then
|
|
-- remove filtered operations
|
|
for moduleName, moduleOperations in pairs(private.importContext.operations) do
|
|
local usedOperations = TempTable.Acquire()
|
|
for groupPath, operations in pairs(private.importContext.groupOperations) do
|
|
if not private.importContext.filteredGroups[groupPath] then
|
|
for _, operationName in ipairs(operations[moduleName]) do
|
|
usedOperations[operationName] = true
|
|
end
|
|
end
|
|
end
|
|
for operationName in pairs(moduleOperations) do
|
|
if not usedOperations[operationName] then
|
|
moduleOperations[operationName] = nil
|
|
end
|
|
end
|
|
TempTable.Release(usedOperations)
|
|
end
|
|
if not replaceOperations then
|
|
-- remove existing operations and custom sources from the import context
|
|
for moduleName, moduleOperations in pairs(private.importContext.operations) do
|
|
for operationName in pairs(moduleOperations) do
|
|
if TSM.Operations.Exists(moduleName, operationName) then
|
|
moduleOperations[operationName] = nil
|
|
end
|
|
end
|
|
if not next(moduleOperations) then
|
|
private.importContext.operations[moduleName] = nil
|
|
end
|
|
end
|
|
for name in pairs(private.importContext.customSources) do
|
|
if TSM.db.global.userData.customPriceSources[name] then
|
|
private.importContext.customSources[name] = nil
|
|
end
|
|
end
|
|
end
|
|
if next(private.importContext.customSources) then
|
|
-- regenerate the list of custom sources in case some operations were filtered out
|
|
wipe(private.importContext.customSources)
|
|
for moduleName, moduleOperations in pairs(private.importContext.operations) do
|
|
for _, operationSettings in pairs(moduleOperations) do
|
|
for key in pairs(EXPORT_CUSTOM_STRINGS[moduleName]) do
|
|
private.GetCustomSources(operationSettings[key], private.importContext.customSources)
|
|
end
|
|
end
|
|
end
|
|
-- create the custom sources
|
|
numCustomSources = Table.Count(private.importContext.customSources)
|
|
CustomPrice.BulkCreateCustomPriceSourcesFromImport(private.importContext.customSources, replaceOperations)
|
|
end
|
|
-- create the operations
|
|
for _, moduleOperations in pairs(private.importContext.operations) do
|
|
numOperations = numOperations + Table.Count(moduleOperations)
|
|
end
|
|
TSM.Operations.BulkCreateFromImport(private.importContext.operations, replaceOperations)
|
|
end
|
|
if not includeOperations then
|
|
wipe(private.importContext.groupOperations)
|
|
end
|
|
-- filter the groups
|
|
for groupPath in pairs(private.importContext.filteredGroups) do
|
|
private.importContext.groups[groupPath] = nil
|
|
private.importContext.groupOperations[groupPath] = nil
|
|
end
|
|
for itemString, groupPath in pairs(private.importContext.items) do
|
|
if private.importContext.filteredGroups[groupPath] then
|
|
private.importContext.items[itemString] = nil
|
|
end
|
|
end
|
|
-- create the groups
|
|
local numItems = TSM.Groups.BulkCreateFromImport(private.importContext.groupName, private.importContext.items, private.importContext.groups, private.importContext.groupOperations, moveExistingItems)
|
|
|
|
-- print the message
|
|
Log.PrintfUser(L["Imported group (%s) with %d items, %d operations, and %d custom sources."], private.importContext.groupName, numItems, numOperations, numCustomSources)
|
|
ImportExport.ClearImportContext()
|
|
end
|
|
|
|
function ImportExport.ClearImportContext()
|
|
private.importContext.groupName = nil
|
|
private.importContext.items = nil
|
|
private.importContext.groups = nil
|
|
private.importContext.groupOperations = nil
|
|
private.importContext.operations = nil
|
|
private.importContext.customSources = nil
|
|
wipe(private.importContext.filteredGroups)
|
|
end
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Private Helper Functions
|
|
-- ============================================================================
|
|
|
|
function private.GetCustomSources(str, result)
|
|
for _, name, customSourceStr in CustomPrice.DependantCustomSourceIterator(str) do
|
|
if not result[name] then
|
|
result[name] = customSourceStr
|
|
private.GetCustomSources(customSourceStr, result)
|
|
end
|
|
end
|
|
end
|
|
|
|
function private.DecodeNewImport(str)
|
|
-- decode and decompress (if it's not a new import, the decode should fail)
|
|
str = LibDeflate:DecodeForPrint(str)
|
|
if not str then
|
|
Log.Info("Not a valid new import string")
|
|
return false
|
|
end
|
|
local numExtraBytes = nil
|
|
str, numExtraBytes = LibDeflate:DecompressDeflate(str)
|
|
if not str then
|
|
Log.Err("Failed to decompress new import string")
|
|
return false
|
|
elseif numExtraBytes > 0 then
|
|
Log.Err("Import string had extra bytes")
|
|
return false
|
|
end
|
|
|
|
-- deserialize and validate the data
|
|
local success, magicStr, version, groupName, items, groups, groupOperations, operations, customSources = LibSerialize:Deserialize(str)
|
|
if not success then
|
|
Log.Err("Failed to deserialize new import string")
|
|
return false
|
|
elseif magicStr ~= MAGIC_STR then
|
|
Log.Err("Invalid magic string: "..tostring(magicStr))
|
|
return false
|
|
elseif version ~= VERSION then
|
|
Log.Err("Invalid version: "..tostring(version))
|
|
return false
|
|
elseif type(groupName) ~= "string" or groupName == "" or strmatch(groupName, TSM.CONST.GROUP_SEP) then
|
|
Log.Err("Invalid groupName: "..tostring(groupName))
|
|
return false
|
|
elseif type(items) ~= "table" then
|
|
Log.Err("Invalid items type: "..tostring(items))
|
|
return false
|
|
elseif type(groups) ~= "table" then
|
|
Log.Err("Invalid groups type: "..tostring(groups))
|
|
return false
|
|
elseif type(groupOperations) ~= "table" then
|
|
Log.Err("Invalid groupOperations type: "..tostring(groupOperations))
|
|
return false
|
|
elseif type(operations) ~= "table" then
|
|
Log.Err("Invalid operations type: "..tostring(operations))
|
|
return false
|
|
elseif type(customSources) ~= "table" then
|
|
Log.Err("Invalid customSources type: "..tostring(customSources))
|
|
return false
|
|
end
|
|
|
|
-- validate the groups table
|
|
for groupPath, trueValue in pairs(groups) do
|
|
if not private.IsValidGroupPath(groupPath) then
|
|
Log.Err("Invalid groupPath (%s)", tostring(groupPath))
|
|
return false
|
|
elseif trueValue ~= true then
|
|
Log.Err("Invalid true value (%s)", tostring(trueValue))
|
|
return false
|
|
end
|
|
end
|
|
for groupPath in pairs(groups) do
|
|
local parentPath = TSM.Groups.Path.Split(groupPath)
|
|
while parentPath do
|
|
if not groups[parentPath] then
|
|
Log.Err("Orphaned group (%s)", groupPath)
|
|
return false
|
|
end
|
|
parentPath = TSM.Groups.Path.Split(parentPath)
|
|
end
|
|
end
|
|
|
|
-- validate the items table
|
|
local numInvalidItems = 0
|
|
for itemString, groupPath in pairs(items) do
|
|
if not private.IsValidGroupPath(groupPath) then
|
|
Log.Err("Invalid groupPath (%s, %s)", tostring(itemString), tostring(groupPath))
|
|
return false
|
|
elseif not groups[groupPath] then
|
|
Log.Err("Invalid item group (%s, %s)", itemString, groupPath)
|
|
return false
|
|
end
|
|
local newItemString = type(itemString) == "string" and ItemString.Get(itemString) or nil
|
|
if itemString ~= newItemString then
|
|
-- just remove this one item and continue
|
|
Log.Warn("Invalid itemString (%s, %s)", tostring(itemString), tostring(newItemString))
|
|
items[itemString] = nil
|
|
numInvalidItems = numInvalidItems + 1
|
|
end
|
|
end
|
|
if not next(items) and numInvalidItems > 0 then
|
|
Log.Err("All items were invalid")
|
|
return false
|
|
end
|
|
|
|
-- validate the customSources table
|
|
for name, customSourceStr in pairs(customSources) do
|
|
if type(name) ~= "string" or name == "" or gsub(name, "([a-z]+)", "") ~= "" then
|
|
Log.Err("Invalid name (%s)", tostring(name))
|
|
return false
|
|
elseif type(str) ~= "string" then
|
|
Log.Err("Invalid str (%s)", tostring(customSourceStr))
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- validate the operations table
|
|
local numChangedOperations = private.ValidateOperationsTable(operations, true)
|
|
if not numChangedOperations then
|
|
return false
|
|
end
|
|
|
|
-- validate the groupOperations table
|
|
if not private.ValidateGroupOperationsTable(groupOperations, groups, operations, true) then
|
|
return false
|
|
end
|
|
|
|
if numInvalidItems > 0 then
|
|
Log.PrintfUser(L["NOTE: The import contained %d invalid items which were ignored."], numInvalidItems)
|
|
end
|
|
if numChangedOperations > 0 then
|
|
Log.PrintfUser(L["NOTE: The import contained %d operations with at least one invalid setting which was reset."], numChangedOperations)
|
|
end
|
|
|
|
Log.Info("Decoded new import string")
|
|
private.importContext.groupName = private.DedupImportGroupName(groupName)
|
|
private.importContext.items = items
|
|
private.importContext.groups = groups
|
|
private.importContext.groupOperations = groupOperations
|
|
private.importContext.operations = operations
|
|
private.importContext.customSources = customSources
|
|
return true
|
|
end
|
|
|
|
function private.DecodeOldImport(str)
|
|
if strsub(str, 1, 1) ~= "^" then
|
|
Log.Info("Not an old import string")
|
|
return false
|
|
end
|
|
|
|
local isValid, data = AceSerializer:Deserialize(str)
|
|
if not isValid then
|
|
Log.Err("Failed to deserialize")
|
|
return false
|
|
elseif type(data) ~= "table" then
|
|
Log.Err("Invalid data type (%s)", tostring(data))
|
|
return false
|
|
elseif data.operations ~= nil and type(data.operations) ~= "table" then
|
|
Log.Err("Invalid operations type (%s)", tostring(data.operations))
|
|
return false
|
|
elseif data.groupExport ~= nil and type(data.groupExport) ~= "string" then
|
|
Log.Err("Invalid groupExport type (%s)", tostring(data.groupExport))
|
|
return false
|
|
elseif data.groupOperations ~= nil and type(data.groupOperations) ~= "table" then
|
|
Log.Err("Invalid groupOperations type (%s)", tostring(data.groupOperations))
|
|
return false
|
|
elseif not data.operations and not data.groupExport then
|
|
Log.Err("Doesn't contain operations or groupExport")
|
|
return false
|
|
end
|
|
local operations, numChangedOperations = nil, 0
|
|
if data.operations then
|
|
numChangedOperations = private.ValidateOperationsTable(data.operations, false)
|
|
if not numChangedOperations then
|
|
return false
|
|
end
|
|
operations = data.operations
|
|
else
|
|
operations = {}
|
|
end
|
|
local items, groups, numInvalidItems = nil, nil, nil
|
|
if data.groupExport then
|
|
items, groups, numInvalidItems = private.DecodeGroupExportHelper(data.groupExport)
|
|
if not items then
|
|
Log.Err("No items found")
|
|
return false
|
|
end
|
|
else
|
|
items = {}
|
|
groups = {}
|
|
numInvalidItems = 0
|
|
end
|
|
local groupOperations = nil
|
|
if data.groupOperations then
|
|
Log.Info("Parsing group operations")
|
|
local changeGroupPaths = TempTable.Acquire()
|
|
for groupPath in pairs(data.groupOperations) do
|
|
-- We export a "," in a group path as "``"
|
|
local newGroupPath = type(groupPath) == "string" and gsub(groupPath, "``", ",")
|
|
if newGroupPath and newGroupPath ~= groupPath then
|
|
changeGroupPaths[groupPath] = newGroupPath
|
|
if data.groupOperations[newGroupPath] then
|
|
Log.Err("Duplicated group operations (%s, %s)", tostring(groupPath), tostring(newGroupPath))
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
for groupPath, newGroupPath in pairs(changeGroupPaths) do
|
|
data.groupOperations[newGroupPath] = data.groupOperations[groupPath]
|
|
data.groupOperations[groupPath] = nil
|
|
end
|
|
TempTable.Release(changeGroupPaths)
|
|
if not private.ValidateGroupOperationsTable(data.groupOperations, groups, operations, false) then
|
|
Log.Err("Invalid group operations")
|
|
return false
|
|
end
|
|
groupOperations = data.groupOperations
|
|
else
|
|
groupOperations = {}
|
|
end
|
|
|
|
-- check if there's a common top-level group within the import
|
|
local commonTopLevelGroup = private.GetCommonTopLevelGroup(items, groups, groupOperations)
|
|
if commonTopLevelGroup then
|
|
private.UpdateTopLevelGroup(commonTopLevelGroup, items, groups, groupOperations)
|
|
end
|
|
|
|
if numInvalidItems > 0 then
|
|
Log.PrintfUser(L["NOTE: The import contained %d invalid items which were ignored."], numInvalidItems)
|
|
end
|
|
if numChangedOperations > 0 then
|
|
Log.PrintfUser(L["NOTE: The import contained %d operations with at least one invalid setting which was reset."], numChangedOperations)
|
|
end
|
|
|
|
Log.Info("Decoded old import string")
|
|
private.importContext.groupName = private.DedupImportGroupName(commonTopLevelGroup or L["Imported Group"])
|
|
private.importContext.items = items
|
|
private.importContext.groups = groups
|
|
private.importContext.groupOperations = groupOperations
|
|
private.importContext.operations = operations
|
|
private.importContext.customSources = {}
|
|
return true
|
|
end
|
|
|
|
function private.DecodeOldGroupOrItemListImport(str)
|
|
local items, groups, numInvalidItems = private.DecodeGroupExportHelper(str)
|
|
if not items then
|
|
Log.Err("No items found")
|
|
return false
|
|
end
|
|
local groupOperations = {}
|
|
|
|
-- check if there's a common top-level group within the import
|
|
local commonTopLevelGroup = private.GetCommonTopLevelGroup(items, groups, groupOperations)
|
|
if commonTopLevelGroup then
|
|
private.UpdateTopLevelGroup(commonTopLevelGroup, items, groups, groupOperations)
|
|
end
|
|
|
|
if numInvalidItems > 0 then
|
|
Log.PrintfUser(L["NOTE: The import contained %d invalid items which were ignored."], numInvalidItems)
|
|
end
|
|
|
|
Log.Info("Decoded old group or item list")
|
|
private.importContext.groupName = private.DedupImportGroupName(commonTopLevelGroup or L["Imported Group"])
|
|
private.importContext.items = items
|
|
private.importContext.groups = groups
|
|
private.importContext.groupOperations = groupOperations
|
|
private.importContext.operations = {}
|
|
private.importContext.customSources = {}
|
|
return true
|
|
end
|
|
|
|
function private.DecodeGroupExportHelper(str)
|
|
local items, groups, numInvalidItems = nil, nil, 0
|
|
if strmatch(str, "^[ip0-9%-:;]+$") then
|
|
-- this is likely a list of itemStrings separated by semicolons instead of commas, so attempt to fix it
|
|
str = gsub(str, ";", ",")
|
|
end
|
|
if strmatch(str, "^[0-9,]+$") then
|
|
-- this is likely a list of itemIds separated by commas, so attempt to fix it
|
|
str = gsub(str, "[0-9]+", "i:%1")
|
|
end
|
|
local relativePath = TSM.CONST.ROOT_GROUP_PATH
|
|
for part in String.SplitIterator(str, ",") do
|
|
part = strtrim(part)
|
|
local groupPath = strmatch(part, "^group:(.+)$")
|
|
local itemString = strmatch(part, "^[ip]?:?[0-9%-:]+$")
|
|
local newItemString = itemString and ItemString.Get(itemString) or nil
|
|
if newItemString and newItemString ~= itemString then
|
|
itemString = newItemString
|
|
numInvalidItems = numInvalidItems + 1
|
|
end
|
|
assert(not groupPath or not itemString)
|
|
if groupPath then
|
|
-- We export a "," in a group path as "``"
|
|
groupPath = gsub(groupPath, "``", ",")
|
|
if not private.IsValidGroupPath(groupPath) then
|
|
Log.Err("Invalid groupPath (%s)", tostring(groupPath))
|
|
return
|
|
end
|
|
relativePath = groupPath
|
|
groups = groups or {}
|
|
-- create the groups all the way up to the root
|
|
while groupPath do
|
|
groups[groupPath] = true
|
|
groupPath = TSM.Groups.Path.GetParent(groupPath)
|
|
end
|
|
elseif itemString then
|
|
items = items or {}
|
|
groups = groups or {}
|
|
groups[relativePath] = true
|
|
items[itemString] = relativePath
|
|
else
|
|
Log.Err("Unknown part: %s", part)
|
|
return
|
|
end
|
|
end
|
|
return items, groups, numInvalidItems
|
|
end
|
|
|
|
function private.ValidateOperationsTable(operations, strict)
|
|
local numChangedOperations = 0
|
|
for moduleName, moduleOperations in pairs(operations) do
|
|
local isInvalidModuleName, isNotExportOperationModule = private.IsValidOperationModule(moduleName)
|
|
if not isInvalidModuleName then
|
|
Log.Err("Invalid module name")
|
|
return nil
|
|
elseif isNotExportOperationModule then
|
|
if strict then
|
|
Log.Err("Invalid moduleName (%s)", tostring(moduleName))
|
|
return nil
|
|
else
|
|
Log.Warn("Ignoring module (%s)", moduleName)
|
|
operations[moduleName] = nil
|
|
wipe(moduleOperations)
|
|
end
|
|
elseif type(moduleOperations) ~= "table" then
|
|
Log.Err("Invalid moduleOperations type (%s)", tostring(moduleOperations))
|
|
return nil
|
|
end
|
|
for operationName, operationSettings in pairs(moduleOperations) do
|
|
if type(operationName) ~= "string" or not TSM.Operations.IsValidName(operationName) then
|
|
Log.Err("Invalid operationName (%s)", tostring(operationName))
|
|
return nil
|
|
elseif type(operationSettings) ~= "table" then
|
|
Log.Err("Invalid operationSettings type (%s)", tostring(operationSettings))
|
|
return nil
|
|
end
|
|
-- sanitize the operation settings
|
|
if TSM.Operations.SanitizeSettings(moduleName, operationName, operationSettings, true, true) then
|
|
numChangedOperations = numChangedOperations + 1
|
|
end
|
|
end
|
|
end
|
|
return numChangedOperations
|
|
end
|
|
|
|
function private.ValidateGroupOperationsTable(groupOperations, groups, operations, strict)
|
|
for groupPath, groupsOperationsTable in pairs(groupOperations) do
|
|
if not private.IsValidGroupPath(groupPath) then
|
|
Log.Err("Invalid groupPath (%s)", tostring(groupPath))
|
|
return false
|
|
elseif not groups[groupPath] then
|
|
if strict then
|
|
Log.Err("Invalid group (%s)", groupPath)
|
|
return false
|
|
else
|
|
Log.Info("Creating group with operations (%s)", groupPath)
|
|
groups[groupPath] = true
|
|
end
|
|
end
|
|
if not strict then
|
|
groupsOperationsTable.ignoreItemVariations = nil
|
|
end
|
|
for moduleName, moduleOperations in pairs(groupsOperationsTable) do
|
|
local isInvalidModuleName, isNotExportOperationModule = private.IsValidOperationModule(moduleName)
|
|
if not isInvalidModuleName then
|
|
Log.Err("Invalid module name")
|
|
return false
|
|
elseif isNotExportOperationModule then
|
|
if strict then
|
|
Log.Err("Invalid moduleName (%s)", tostring(moduleName))
|
|
return false
|
|
else
|
|
Log.Warn("Ignoring module (%s)", moduleName)
|
|
groupsOperationsTable[moduleName] = nil
|
|
wipe(moduleOperations)
|
|
end
|
|
elseif type(moduleOperations) ~= "table" then
|
|
Log.Err("Invalid moduleOperations type (%s)", tostring(moduleOperations))
|
|
return false
|
|
elseif moduleOperations.override ~= nil and moduleOperations.override ~= true then
|
|
Log.Err("Invalid moduleOperations override type (%s)", tostring(moduleOperations.override))
|
|
return false
|
|
elseif groupPath == TSM.CONST.ROOT_GROUP_PATH and not moduleOperations.override then
|
|
if strict then
|
|
Log.Err("Top-level group does not have override set")
|
|
return false
|
|
else
|
|
Log.Info("Setting override for top-level group")
|
|
moduleOperations.override = true
|
|
end
|
|
end
|
|
local numOperations = #moduleOperations
|
|
if numOperations > TSM.Operations.GetMaxNumber(moduleName) then
|
|
Log.Err("Too many operations (%s, %s, %d)", groupPath, moduleName, numOperations)
|
|
return false
|
|
end
|
|
for k, v in pairs(moduleOperations) do
|
|
if k == "override" then
|
|
-- pass
|
|
elseif type(k) ~= "number" or k < 1 or k > numOperations then
|
|
Log.Err("Unknown key (%s, %s, %s, %s)", groupPath, moduleName, tostring(k), tostring(v))
|
|
return false
|
|
elseif type(v) ~= "string" then
|
|
Log.Err("Invalid value (%s, %s, %s, %s)", groupPath, moduleName, k, tostring(v))
|
|
return false
|
|
end
|
|
end
|
|
-- some old imports had "" operations attached to groups, so remove them
|
|
for i = #moduleOperations, 1, -1 do
|
|
if moduleOperations[i] == "" then
|
|
tremove(moduleOperations, i)
|
|
end
|
|
end
|
|
for _, operationName in ipairs(moduleOperations) do
|
|
if type(operationName) ~= "string" or not TSM.Operations.IsValidName(operationName) then
|
|
Log.Err("Invalid operationName (%s)", tostring(operationName))
|
|
return false
|
|
elseif not operations[moduleName][operationName] then
|
|
Log.Err("Unknown operation (%s)", operationName)
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function private.DedupImportGroupName(groupName)
|
|
if TSM.Groups.Exists(groupName) then
|
|
local num = 1
|
|
while TSM.Groups.Exists(groupName.." "..num) do
|
|
num = num + 1
|
|
end
|
|
groupName = groupName.." "..num
|
|
end
|
|
return groupName
|
|
end
|
|
|
|
function private.IsValidGroupPath(groupPath)
|
|
return type(groupPath) == "string" and not strmatch(groupPath, "^`") and not strmatch(groupPath, "`$") and not strmatch(groupPath, "``")
|
|
end
|
|
|
|
function private.IsValidOperationModule(moduleName)
|
|
if type(moduleName) ~= "string" then
|
|
Log.Err("Invalid moduleName (%s)", tostring(moduleName))
|
|
return false
|
|
elseif not TSM.Operations.ModuleExists(moduleName) then
|
|
Log.Err("Invalid moduleName (%s)", tostring(moduleName))
|
|
return false
|
|
elseif not EXPORT_OPERATION_MODULES[moduleName] then
|
|
return true, true
|
|
end
|
|
return true
|
|
end
|
|
|
|
function private.GetCommonTopLevelGroup(items, groups, groupOperations)
|
|
local commonTopLevelGroup = nil
|
|
|
|
-- check the items
|
|
for _, groupPath in pairs(items) do
|
|
if groupPath == TSM.CONST.ROOT_GROUP_PATH then
|
|
return nil
|
|
end
|
|
local topLevelGroup = TSM.Groups.Path.GetTopLevel(groupPath)
|
|
if not commonTopLevelGroup then
|
|
commonTopLevelGroup = topLevelGroup
|
|
elseif topLevelGroup ~= commonTopLevelGroup then
|
|
return nil
|
|
end
|
|
end
|
|
|
|
-- check the groups
|
|
for groupPath in pairs(groups) do
|
|
if groupPath ~= TSM.CONST.ROOT_GROUP_PATH then
|
|
local topLevelGroup = TSM.Groups.Path.GetTopLevel(groupPath)
|
|
if not commonTopLevelGroup then
|
|
commonTopLevelGroup = topLevelGroup
|
|
elseif topLevelGroup ~= commonTopLevelGroup then
|
|
return nil
|
|
end
|
|
end
|
|
end
|
|
|
|
-- check the groupOperations
|
|
for groupPath in pairs(groupOperations) do
|
|
if groupPath == TSM.CONST.ROOT_GROUP_PATH then
|
|
return nil
|
|
end
|
|
local topLevelGroup = TSM.Groups.Path.GetTopLevel(groupPath)
|
|
if not commonTopLevelGroup then
|
|
commonTopLevelGroup = topLevelGroup
|
|
elseif topLevelGroup ~= commonTopLevelGroup then
|
|
return nil
|
|
end
|
|
end
|
|
|
|
return commonTopLevelGroup
|
|
end
|
|
|
|
function private.UpdateTopLevelGroup(topLevelGroup, items, groups, groupOperations)
|
|
-- update items
|
|
for itemString, groupPath in pairs(items) do
|
|
items[itemString] = TSM.Groups.Path.GetRelative(groupPath, topLevelGroup)
|
|
end
|
|
|
|
-- update groups
|
|
local newGroups = TempTable.Acquire()
|
|
groups[TSM.CONST.ROOT_GROUP_PATH] = nil
|
|
for groupPath in pairs(groups) do
|
|
newGroups[TSM.Groups.Path.GetRelative(groupPath, topLevelGroup)] = true
|
|
end
|
|
wipe(groups)
|
|
for groupPath in pairs(newGroups) do
|
|
groups[groupPath] = true
|
|
end
|
|
TempTable.Release(newGroups)
|
|
|
|
-- update groupOperations
|
|
local newGroupOperations = TempTable.Acquire()
|
|
for groupPath, groupOperationsTable in pairs(groupOperations) do
|
|
newGroupOperations[TSM.Groups.Path.GetRelative(groupPath, topLevelGroup)] = groupOperationsTable
|
|
end
|
|
wipe(groupOperations)
|
|
for groupPath, groupOperationsTable in pairs(newGroupOperations) do
|
|
groupOperations[groupPath] = groupOperationsTable
|
|
end
|
|
TempTable.Release(newGroupOperations)
|
|
|
|
-- set override on new top-level group
|
|
if groupOperations[TSM.CONST.ROOT_GROUP_PATH] then
|
|
for _, moduleOperations in pairs(groupOperations[TSM.CONST.ROOT_GROUP_PATH]) do
|
|
moduleOperations.override = true
|
|
end
|
|
end
|
|
end
|