initial commit
This commit is contained in:
287
Core/Service/Accounting/Sync.lua
Normal file
287
Core/Service/Accounting/Sync.lua
Normal file
@@ -0,0 +1,287 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- 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
|
||||
Reference in New Issue
Block a user