269 lines
11 KiB
Lua
269 lines
11 KiB
Lua
|
-- ------------------------------------------------------------------------------ --
|
||
|
-- TradeSkillMaster --
|
||
|
-- https://tradeskillmaster.com --
|
||
|
-- All Rights Reserved - Detailed license information included with addon. --
|
||
|
-- ------------------------------------------------------------------------------ --
|
||
|
|
||
|
local _, TSM = ...
|
||
|
local Mirror = TSM.Init("Service.SyncClasses.Mirror")
|
||
|
local Delay = TSM.Include("Util.Delay")
|
||
|
local TempTable = TSM.Include("Util.TempTable")
|
||
|
local Math = TSM.Include("Util.Math")
|
||
|
local Log = TSM.Include("Util.Log")
|
||
|
local Settings = TSM.Include("Service.Settings")
|
||
|
local Constants = TSM.Include("Service.SyncClasses.Constants")
|
||
|
local Comm = TSM.Include("Service.SyncClasses.Comm")
|
||
|
local Connection = TSM.Include("Service.SyncClasses.Connection")
|
||
|
local private = {
|
||
|
numConnected = 0,
|
||
|
accountStatus = {},
|
||
|
callbacks = {},
|
||
|
}
|
||
|
local BROADCAST_INTERVAL = 3
|
||
|
|
||
|
|
||
|
|
||
|
-- ============================================================================
|
||
|
-- Module Loading
|
||
|
-- ============================================================================
|
||
|
|
||
|
Mirror:OnModuleLoad(function()
|
||
|
Connection.RegisterConnectionChangedCallback(private.ConnectionChangedHandler)
|
||
|
Comm.RegisterHandler(Constants.DATA_TYPES.CHARACTER_HASHES_BROADCAST, private.CharacterHashesBroadcastHandler)
|
||
|
Comm.RegisterHandler(Constants.DATA_TYPES.CHARACTER_SETTING_HASHES_REQUEST, private.CharacterSettingHashesRequestHandler)
|
||
|
Comm.RegisterHandler(Constants.DATA_TYPES.CHARACTER_SETTING_HASHES_RESPONSE, private.CharacterSettingHashesResponseHandler)
|
||
|
Comm.RegisterHandler(Constants.DATA_TYPES.CHARACTER_SETTING_DATA_REQUEST, private.CharacterSettingDataRequestHandler)
|
||
|
Comm.RegisterHandler(Constants.DATA_TYPES.CHARACTER_SETTING_DATA_RESPONSE, private.CharacterSettingDataResponseHandler)
|
||
|
end)
|
||
|
|
||
|
|
||
|
|
||
|
-- ============================================================================
|
||
|
-- Module Functions
|
||
|
-- ============================================================================
|
||
|
|
||
|
function Mirror.GetStatus(account)
|
||
|
local status = private.accountStatus[account]
|
||
|
if not status then
|
||
|
return false, false
|
||
|
elseif status == "UPDATING" then
|
||
|
return true, false
|
||
|
elseif status == "SYNCED" then
|
||
|
return true, true
|
||
|
else
|
||
|
error("Invalid status: "..tostring(status))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function Mirror.RegisterCallback(callback)
|
||
|
tinsert(private.callbacks, callback)
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
-- ============================================================================
|
||
|
-- Connection Callback Handlers
|
||
|
-- ============================================================================
|
||
|
|
||
|
function private.ConnectionChangedHandler(account, player, connected)
|
||
|
if connected == nil then
|
||
|
-- new account, but not yet connected
|
||
|
return
|
||
|
end
|
||
|
if connected then
|
||
|
Log.Info("Connected to %s (%s)", account, player)
|
||
|
else
|
||
|
Log.Info("Disconnected from %s (%s)", account, player)
|
||
|
end
|
||
|
private.numConnected = private.numConnected + (connected and 1 or -1)
|
||
|
assert(private.numConnected >= 0)
|
||
|
if connected then
|
||
|
private.accountStatus[account] = "UPDATING"
|
||
|
Delay.AfterTime("mirrorCharacterHashes", 0, private.SendCharacterHashes, BROADCAST_INTERVAL)
|
||
|
else
|
||
|
private.accountStatus[account] = nil
|
||
|
if private.numConnected == 0 then
|
||
|
Delay.Cancel("mirrorCharacterHashes")
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
-- ============================================================================
|
||
|
-- Delay-Based Last Update Send Function
|
||
|
-- ============================================================================
|
||
|
|
||
|
function private.SendCharacterHashes()
|
||
|
assert(private.numConnected > 0)
|
||
|
|
||
|
-- calculate the hashes of the sync settings for all characters on this account
|
||
|
local hashes = TempTable.Acquire()
|
||
|
for _, character in Settings.CharacterByAccountFactionrealmIterator() do
|
||
|
hashes[character] = private.CalculateCharacterHash(character)
|
||
|
end
|
||
|
|
||
|
-- send the hashes to all connected accounts
|
||
|
for _, character in Connection.ConnectedAccountIterator() do
|
||
|
Comm.SendData(Constants.DATA_TYPES.CHARACTER_HASHES_BROADCAST, character, hashes)
|
||
|
end
|
||
|
TempTable.Release(hashes)
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
-- ============================================================================
|
||
|
-- Message Handlers
|
||
|
-- ============================================================================
|
||
|
|
||
|
function private.CharacterHashesBroadcastHandler(dataType, sourceAccount, sourcePlayer, data)
|
||
|
assert(dataType == Constants.DATA_TYPES.CHARACTER_HASHES_BROADCAST)
|
||
|
if not Connection.IsCharacterConnected(sourcePlayer) then
|
||
|
-- we're not connected to this player
|
||
|
Log.Warn("Got CHARACTER_HASHES_BROADCAST for player which isn't connected")
|
||
|
return
|
||
|
end
|
||
|
local didChange = false
|
||
|
for _, character in Settings.CharacterByAccountFactionrealmIterator(sourceAccount) do
|
||
|
if not data[character] then
|
||
|
-- this character doesn't exist anymore, so remove it
|
||
|
Log.Info("Removed character: '%s'", character)
|
||
|
Settings.RemoveSyncCharacter(character)
|
||
|
didChange = true
|
||
|
end
|
||
|
end
|
||
|
for character, hash in pairs(data) do
|
||
|
if not Settings.GetCharacterSyncAccountKey(character) then
|
||
|
-- this is a new character, so add it to our DB
|
||
|
Log.Info("New character: '%s' '%s'", character, sourceAccount)
|
||
|
Settings.NewSyncCharacter(sourceAccount, character)
|
||
|
didChange = true
|
||
|
end
|
||
|
if hash ~= private.CalculateCharacterHash(character) then
|
||
|
-- this character's data has changed so request a hash of each of the keys
|
||
|
Log.Info("Character data has changed: '%s'", character)
|
||
|
Comm.SendData(Constants.DATA_TYPES.CHARACTER_SETTING_HASHES_REQUEST, sourcePlayer, character)
|
||
|
didChange = true
|
||
|
end
|
||
|
end
|
||
|
if didChange then
|
||
|
private.accountStatus[sourceAccount] = "UPDATING"
|
||
|
else
|
||
|
private.accountStatus[sourceAccount] = "SYNCED"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function private.CharacterSettingHashesRequestHandler(dataType, sourceAccount, sourcePlayer, data)
|
||
|
assert(dataType == Constants.DATA_TYPES.CHARACTER_SETTING_HASHES_REQUEST)
|
||
|
if not Connection.IsCharacterConnected(sourcePlayer) then
|
||
|
-- we're not connected to this player
|
||
|
Log.Warn("Got CHARACTER_HASHES_BROADCAST for player which isn't connected")
|
||
|
return
|
||
|
elseif Settings.GetCharacterSyncAccountKey(data) ~= Settings.GetCurrentSyncAccountKey() then
|
||
|
-- we don't own this character
|
||
|
Log.Err("Request for character we don't own ('%s', '%s')", tostring(data), tostring(Settings.GetCharacterSyncAccountKey(data)))
|
||
|
return
|
||
|
end
|
||
|
Log.Info("CHARACTER_SETTING_HASHES_REQUEST (%s)", data)
|
||
|
local responseData = TempTable.Acquire()
|
||
|
responseData._character = data
|
||
|
for _, namespace, settingKey in Settings.SyncSettingIterator() do
|
||
|
responseData[namespace.."."..settingKey] = private.CalculateCharacterSettingHash(data, namespace, settingKey)
|
||
|
end
|
||
|
Comm.SendData(Constants.DATA_TYPES.CHARACTER_SETTING_HASHES_RESPONSE, sourcePlayer, responseData)
|
||
|
TempTable.Release(responseData)
|
||
|
end
|
||
|
|
||
|
function private.CharacterSettingHashesResponseHandler(dataType, sourceAccount, sourcePlayer, data)
|
||
|
assert(dataType == Constants.DATA_TYPES.CHARACTER_SETTING_HASHES_RESPONSE)
|
||
|
if not Connection.IsCharacterConnected(sourcePlayer) then
|
||
|
-- we're not connected to this player
|
||
|
Log.Warn("Got CHARACTER_HASHES_BROADCAST for player which isn't connected")
|
||
|
return
|
||
|
end
|
||
|
local character = data._character
|
||
|
data._character = nil
|
||
|
Log.Info("CHARACTER_SETTING_HASHES_RESPONSE (%s)", character)
|
||
|
for key, hash in pairs(data) do
|
||
|
local namespace, settingKey = strsplit(".", key)
|
||
|
if private.CalculateCharacterSettingHash(character, namespace, settingKey) ~= hash then
|
||
|
-- the settings data for key changed, so request the latest data for it
|
||
|
Log.Info("Setting data has changed: '%s', '%s'", character, key)
|
||
|
Comm.SendData(Constants.DATA_TYPES.CHARACTER_SETTING_DATA_REQUEST, sourcePlayer, character.."."..key)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function private.CharacterSettingDataRequestHandler(dataType, sourceAccount, sourcePlayer, data)
|
||
|
assert(dataType == Constants.DATA_TYPES.CHARACTER_SETTING_DATA_REQUEST)
|
||
|
local character, namespace, settingKey = strsplit(".", data)
|
||
|
if not Connection.IsCharacterConnected(sourcePlayer) then
|
||
|
-- we're not connected to this player
|
||
|
Log.Warn("Got CHARACTER_HASHES_BROADCAST for player which isn't connected")
|
||
|
return
|
||
|
elseif Settings.GetCharacterSyncAccountKey(character) ~= Settings.GetCurrentSyncAccountKey() then
|
||
|
-- we don't own this character
|
||
|
Log.Err("Request for character we don't own ('%s', '%s')", tostring(character), tostring(Settings.GetCharacterSyncAccountKey(character)))
|
||
|
return
|
||
|
end
|
||
|
Log.Info("CHARACTER_SETTING_DATA_REQUEST (%s,%s,%s)", character, namespace, settingKey)
|
||
|
local responseData = TempTable.Acquire()
|
||
|
responseData.character = character
|
||
|
responseData.namespace = namespace
|
||
|
responseData.settingKey = settingKey
|
||
|
responseData.data = Settings.Get("sync", Settings.GetSyncScopeKeyByCharacter(character), namespace, settingKey)
|
||
|
Comm.SendData(Constants.DATA_TYPES.CHARACTER_SETTING_DATA_RESPONSE, sourcePlayer, responseData)
|
||
|
TempTable.Release(responseData)
|
||
|
end
|
||
|
|
||
|
function private.CharacterSettingDataResponseHandler(dataType, sourceAccount, sourcePlayer, data)
|
||
|
assert(dataType == Constants.DATA_TYPES.CHARACTER_SETTING_DATA_RESPONSE)
|
||
|
if not Connection.IsCharacterConnected(sourcePlayer) then
|
||
|
-- we're not connected to this player
|
||
|
Log.Warn("Got CHARACTER_HASHES_BROADCAST for player which isn't connected")
|
||
|
return
|
||
|
end
|
||
|
local dataValueType = type(data.data)
|
||
|
Log.Info("CHARACTER_SETTING_DATA_RESPONSE (%s,%s,%s,%s,%s)", data.character, data.namespace, data.settingKey, dataValueType, (dataValueType == "string" or dataValueType == "table") and #dataValueType or "-")
|
||
|
if dataValueType == "table" then
|
||
|
local tbl = Settings.Get("sync", Settings.GetSyncScopeKeyByCharacter(data.character), data.namespace, data.settingKey)
|
||
|
wipe(tbl)
|
||
|
for i, v in pairs(data.data) do
|
||
|
tbl[i] = v
|
||
|
end
|
||
|
else
|
||
|
Settings.Set("sync", Settings.GetSyncScopeKeyByCharacter(data.character), data.namespace, data.settingKey, data.data)
|
||
|
end
|
||
|
for _, callback in ipairs(private.callbacks) do
|
||
|
callback()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
-- ============================================================================
|
||
|
-- Helper Functions
|
||
|
-- ============================================================================
|
||
|
|
||
|
function private.CalculateCharacterHash(character)
|
||
|
local hash = nil
|
||
|
local settingKeys = TempTable.Acquire()
|
||
|
for _, namespace, settingKey in Settings.SyncSettingIterator() do
|
||
|
tinsert(settingKeys, strjoin(".", namespace, settingKey))
|
||
|
end
|
||
|
sort(settingKeys)
|
||
|
for _, key in ipairs(settingKeys) do
|
||
|
hash = Math.CalculateHash(key, hash)
|
||
|
local namespace, settingKey = strsplit(".", key)
|
||
|
local settingValue = Settings.Get("sync", Settings.GetSyncScopeKeyByCharacter(character), namespace, settingKey)
|
||
|
hash = Math.CalculateHash(settingValue, hash)
|
||
|
end
|
||
|
assert(hash)
|
||
|
TempTable.Release(settingKeys)
|
||
|
return hash
|
||
|
end
|
||
|
|
||
|
function private.CalculateCharacterSettingHash(character, namespace, settingKey)
|
||
|
return Math.CalculateHash(Settings.Get("sync", Settings.GetSyncScopeKeyByCharacter(character), namespace, settingKey))
|
||
|
end
|