288 lines
9.8 KiB
Lua
288 lines
9.8 KiB
Lua
-- ------------------------------------------------------------------------------ --
|
|
-- TradeSkillMaster --
|
|
-- https://tradeskillmaster.com --
|
|
-- All Rights Reserved - Detailed license information included with addon. --
|
|
-- ------------------------------------------------------------------------------ --
|
|
|
|
local _, TSM = ...
|
|
local AccountingSync = TSM.Accounting:NewPackage("Sync")
|
|
local L = TSM.Include("Locale").GetTable()
|
|
local Delay = TSM.Include("Util.Delay")
|
|
local Log = TSM.Include("Util.Log")
|
|
local TempTable = TSM.Include("Util.TempTable")
|
|
local Theme = TSM.Include("Util.Theme")
|
|
local Sync = TSM.Include("Service.Sync")
|
|
local private = {
|
|
accountLookup = {},
|
|
accountStatus = {},
|
|
pendingChunks = {},
|
|
dataTemp = {},
|
|
}
|
|
local CHANGE_NOTIFICATION_DELAY = 5
|
|
local RETRY_DELAY = 5
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Module Functions
|
|
-- ============================================================================
|
|
|
|
function AccountingSync.OnInitialize()
|
|
Sync.RegisterConnectionChangedCallback(private.ConnectionChangedHandler)
|
|
Sync.RegisterRPC("ACCOUNTING_GET_PLAYER_HASH", private.RPCGetPlayerHash)
|
|
Sync.RegisterRPC("ACCOUNTING_GET_PLAYER_CHUNKS", private.RPCGetPlayerChunks)
|
|
Sync.RegisterRPC("ACCOUNTING_GET_PLAYER_DATA", private.RPCGetData)
|
|
Sync.RegisterRPC("ACCOUNTING_CHANGE_NOTIFICATION", private.RPCChangeNotification)
|
|
end
|
|
|
|
function AccountingSync.GetStatus(account)
|
|
local status = private.accountStatus[account]
|
|
if not status then
|
|
return Theme.GetFeedbackColor("RED"):ColorText(L["Not Connected"])
|
|
elseif status == "GET_PLAYER_HASH" or status == "GET_PLAYER_CHUNKS" or status == "GET_PLAYER_DATA" or status == "RETRY" then
|
|
return Theme.GetFeedbackColor("YELLOW"):ColorText(L["Updating"])
|
|
elseif status == "SYNCED" then
|
|
return Theme.GetFeedbackColor("GREEN"):ColorText(L["Up to date"])
|
|
else
|
|
error("Invalid status: "..tostring(status))
|
|
end
|
|
end
|
|
|
|
function AccountingSync.OnTransactionsChanged()
|
|
Delay.AfterTime("ACCOUNTING_SYNC_CHANGE", CHANGE_NOTIFICATION_DELAY, private.NotifyChange)
|
|
end
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- RPC Functions and Result Handlers
|
|
-- ============================================================================
|
|
|
|
function private.GetPlayerHash(player)
|
|
local account = private.accountLookup[player]
|
|
private.accountStatus[account] = "GET_PLAYER_HASH"
|
|
TSM.Accounting.Transactions.PrepareSyncHashes(player)
|
|
Sync.CallRPC("ACCOUNTING_GET_PLAYER_HASH", player, private.RPCGetPlayerHashResultHandler)
|
|
end
|
|
|
|
function private.RPCGetPlayerHash()
|
|
local player = UnitName("player")
|
|
return player, TSM.Accounting.Transactions.GetSyncHash(player)
|
|
end
|
|
|
|
function private.RPCGetPlayerHashResultHandler(player, hash)
|
|
local account = player and private.accountLookup[player]
|
|
if not account then
|
|
-- request timed out, so try again
|
|
Log.Warn("Getting player hash timed out")
|
|
private.QueueRetriesByStatus("GET_PLAYER_HASH")
|
|
return
|
|
elseif not hash then
|
|
-- the hash isn't ready yet, so try again
|
|
Log.Warn("Sync player hash not ready yet")
|
|
private.QueueRetryByPlayer(player)
|
|
return
|
|
end
|
|
if private.accountStatus[account] == "RETRY" then
|
|
-- There is a race condition where if we tried to issue GET_PLAYER_HASH for two players and one times out,
|
|
-- we would also queue a retry for the other one, so handle that here.
|
|
private.accountStatus[account] = "GET_PLAYER_HASH"
|
|
end
|
|
assert(private.accountStatus[account] == "GET_PLAYER_HASH")
|
|
|
|
local currentHash = TSM.Accounting.Transactions.GetSyncHash(player)
|
|
if not currentHash then
|
|
-- don't have our hash yet, so try again
|
|
Log.Warn("Current player hash not ready yet")
|
|
private.QueueRetryByPlayer(player)
|
|
return
|
|
end
|
|
|
|
if hash ~= currentHash then
|
|
Log.Info("Need updated transactions data from %s (%s, %s)", player, hash, currentHash)
|
|
private.GetPlayerChunks(player)
|
|
else
|
|
Log.Info("Transactions data for %s already up to date (%s, %s)", player, hash, currentHash)
|
|
private.accountStatus[account] = "SYNCED"
|
|
end
|
|
end
|
|
|
|
function private.GetPlayerChunks(player)
|
|
local account = private.accountLookup[player]
|
|
private.accountStatus[account] = "GET_PLAYER_CHUNKS"
|
|
Sync.CallRPC("ACCOUNTING_GET_PLAYER_CHUNKS", player, private.RPCGetPlayerChunksResultHandler)
|
|
end
|
|
|
|
function private.RPCGetPlayerChunks()
|
|
local player = UnitName("player")
|
|
return player, TSM.Accounting.Transactions.GetSyncHashByDay(player)
|
|
end
|
|
|
|
function private.RPCGetPlayerChunksResultHandler(player, chunks)
|
|
local account = player and private.accountLookup[player]
|
|
if not account then
|
|
-- request timed out, so try again from the start
|
|
Log.Warn("Getting chunks timed out")
|
|
private.QueueRetriesByStatus("GET_PLAYER_CHUNKS")
|
|
return
|
|
elseif not chunks then
|
|
-- the hashes have been invalidated, so try again from the start
|
|
Log.Warn("Sync player chunks not ready yet")
|
|
private.QueueRetryByPlayer(player)
|
|
return
|
|
end
|
|
assert(private.accountStatus[account] == "GET_PLAYER_CHUNKS")
|
|
|
|
local currentChunks = TSM.Accounting.Transactions.GetSyncHashByDay(player)
|
|
if not currentChunks then
|
|
-- our hashes have been invalidated, so try again from the start
|
|
Log.Warn("Local hashes are invalid")
|
|
private.QueueRetryByPlayer(player)
|
|
return
|
|
end
|
|
for day in pairs(currentChunks) do
|
|
if not chunks[day] then
|
|
-- remove day which no longer exists
|
|
TSM.Accounting.Transactions.RemovePlayerDay(player, day)
|
|
end
|
|
end
|
|
|
|
-- queue up all the pending chunks
|
|
private.pendingChunks[player] = private.pendingChunks[player] or TempTable.Acquire()
|
|
wipe(private.pendingChunks[player])
|
|
for day, hash in pairs(chunks) do
|
|
if currentChunks[day] ~= hash then
|
|
tinsert(private.pendingChunks[player], day)
|
|
end
|
|
end
|
|
|
|
local requestDay = private.GetNextPendingChunk(player)
|
|
if requestDay then
|
|
Log.Info("Requesting transactions data (%s, %s, %s, %s)", player, requestDay, tostring(currentChunks[requestDay]), chunks[requestDay])
|
|
private.GetPlayerData(player, requestDay)
|
|
else
|
|
Log.Info("All chunks are up to date (%s)", player)
|
|
private.accountStatus[account] = "SYNCED"
|
|
end
|
|
end
|
|
|
|
function private.GetPlayerData(player, requestDay)
|
|
local account = private.accountLookup[player]
|
|
private.accountStatus[account] = "GET_PLAYER_DATA"
|
|
Sync.CallRPC("ACCOUNTING_GET_PLAYER_DATA", player, private.RPCGetDataResultHandler, requestDay)
|
|
end
|
|
|
|
function private.RPCGetData(day)
|
|
local player = UnitName("player")
|
|
wipe(private.dataTemp)
|
|
TSM.Accounting.Transactions.GetSyncData(player, day, private.dataTemp)
|
|
return player, day, private.dataTemp
|
|
end
|
|
|
|
function private.RPCGetDataResultHandler(player, day, data)
|
|
local account = player and private.accountLookup[player]
|
|
if not account then
|
|
-- request timed out, so try again from the start
|
|
Log.Warn("Getting transactions data timed out")
|
|
private.QueueRetriesByStatus("GET_PLAYER_DATA")
|
|
return
|
|
elseif #data % 9 ~= 0 then
|
|
-- invalid data - just silently give up
|
|
Log.Warn("Got invalid transactions data")
|
|
return
|
|
end
|
|
assert(private.accountStatus[account] == "GET_PLAYER_DATA")
|
|
|
|
Log.Info("Received transactions data (%s, %s, %s)", player, day, #data)
|
|
TSM.Accounting.Transactions.HandleSyncedData(player, day, data)
|
|
|
|
local requestDay = private.GetNextPendingChunk(player)
|
|
if requestDay then
|
|
-- request the next chunk
|
|
Log.Info("Requesting transactions data (%s, %s)", player, requestDay)
|
|
private.GetPlayerData(player, requestDay)
|
|
else
|
|
-- request chunks again to check for other chunks we need to sync
|
|
private.GetPlayerChunks(player)
|
|
end
|
|
end
|
|
|
|
function private.RPCChangeNotification(player)
|
|
if private.accountStatus[private.accountLookup[player]] == "SYNCED" then
|
|
-- request the player hash
|
|
Log.Info("Got change notification - requesting player hash")
|
|
private.GetPlayerHash(player)
|
|
else
|
|
Log.Info("Got change notification - dropping (%s)", tostring(private.accountStatus[private.accountLookup[player]]))
|
|
end
|
|
end
|
|
|
|
function private.RPCChangeNotificationResultHandler()
|
|
-- nop
|
|
end
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Private Helper Functions
|
|
-- ============================================================================
|
|
|
|
function private.ConnectionChangedHandler(account, player, connected)
|
|
if connected then
|
|
private.accountLookup[player] = account
|
|
private.GetPlayerHash(player)
|
|
else
|
|
private.accountLookup[player] = nil
|
|
private.accountStatus[account] = nil
|
|
if private.pendingChunks[player] then
|
|
TempTable.Release(private.pendingChunks[player])
|
|
private.pendingChunks[player] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
function private.GetNextPendingChunk(player)
|
|
if not private.pendingChunks[player] then
|
|
return nil
|
|
end
|
|
local result = tremove(private.pendingChunks[player])
|
|
if not result then
|
|
TempTable.Release(private.pendingChunks[player])
|
|
private.pendingChunks[player] = nil
|
|
end
|
|
return result
|
|
end
|
|
|
|
function private.QueueRetriesByStatus(statusFilter)
|
|
for player, account in pairs(private.accountLookup) do
|
|
if private.accountStatus[account] == statusFilter then
|
|
private.QueueRetryByPlayer(player)
|
|
end
|
|
end
|
|
end
|
|
|
|
function private.QueueRetryByPlayer(player)
|
|
local account = private.accountLookup[player]
|
|
Log.Info("Retrying (%s, %s, %s)", player, account, private.accountStatus[account])
|
|
private.accountStatus[account] = "RETRY"
|
|
Delay.AfterTime(RETRY_DELAY, private.RetryGetPlayerHashRPC)
|
|
end
|
|
|
|
function private.RetryGetPlayerHashRPC()
|
|
for player, account in pairs(private.accountLookup) do
|
|
if private.accountStatus[account] == "RETRY" then
|
|
private.GetPlayerHash(player)
|
|
end
|
|
end
|
|
end
|
|
|
|
function private.NotifyChange()
|
|
for player, account in pairs(private.accountLookup) do
|
|
if private.accountStatus[account] == "SYNCED" then
|
|
-- notify the other account that our data has changed and request the other account's latest hash ourselves
|
|
private.GetPlayerHash(player)
|
|
Sync.CallRPC("ACCOUNTING_CHANGE_NOTIFICATION", player, private.RPCChangeNotificationResultHandler, UnitName("player"))
|
|
end
|
|
end
|
|
end
|