4 changed files with 202 additions and 1 deletions
@ -0,0 +1,191 @@
|
||||
-- ------------------------------------------------------------------------------ -- |
||||
-- TradeSkillMaster_AuctionDB -- |
||||
-- http://www.curse.com/addons/wow/tradeskillmaster_auctiondb -- |
||||
-- -- |
||||
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- |
||||
-- All Rights Reserved* - Detailed license information included with addon. -- |
||||
-- ------------------------------------------------------------------------------ -- |
||||
|
||||
-- Channel sync for sharing AuctionDB scan data between players. |
||||
|
||||
local TSM = select(2, ...) |
||||
local ChannelSync = TSM:NewModule("ChannelSync", "AceComm-3.0", "AceEvent-3.0") |
||||
|
||||
local CHANNEL_NAME = "TSM_AuctionDB" |
||||
local COMM_PREFIX = "TSMAuctionDB" |
||||
local CHUNK_SIZE = 220 |
||||
local BUNDLE_TIMEOUT = 45 |
||||
|
||||
local private = { |
||||
channelId = nil, |
||||
lastBroadcastHash = nil, |
||||
incoming = {}, |
||||
} |
||||
|
||||
local strbyte = string.byte |
||||
|
||||
local libS = LibStub:GetLibrary("AceSerializer-3.0") |
||||
local libC = LibStub:GetLibrary("LibCompress") |
||||
local libCE = libC:GetAddonEncodeTable() |
||||
|
||||
local function HashString(str) |
||||
local hash = 0 |
||||
for i = 1, #str do |
||||
hash = (hash * 31 + strbyte(str, i)) % 4294967296 |
||||
end |
||||
return tostring(hash) |
||||
end |
||||
|
||||
local function EnsureChannel() |
||||
local channelId = GetChannelName(CHANNEL_NAME) |
||||
if channelId == 0 then |
||||
JoinChannelByName(CHANNEL_NAME) |
||||
channelId = GetChannelName(CHANNEL_NAME) |
||||
end |
||||
private.channelId = channelId > 0 and channelId or nil |
||||
end |
||||
|
||||
local function ChatFilter(_, _, msg, _, _, _, _, _, channelName) |
||||
if channelName ~= CHANNEL_NAME then return end |
||||
if strsub(msg, 1, #COMM_PREFIX) == COMM_PREFIX then |
||||
return true |
||||
end |
||||
end |
||||
|
||||
function ChannelSync:OnEnable() |
||||
self:RegisterComm(COMM_PREFIX) |
||||
self:RegisterEvent("PLAYER_ENTERING_WORLD", "OnPlayerEnteringWorld") |
||||
self:RegisterEvent("CHAT_MSG_CHANNEL_NOTICE", "OnChannelNotice") |
||||
if ChatFrame_AddMessageEventFilter then |
||||
ChatFrame_AddMessageEventFilter("CHAT_MSG_CHANNEL", ChatFilter) |
||||
end |
||||
EnsureChannel() |
||||
end |
||||
|
||||
function ChannelSync:OnPlayerEnteringWorld() |
||||
EnsureChannel() |
||||
end |
||||
|
||||
function ChannelSync:OnChannelNotice() |
||||
EnsureChannel() |
||||
end |
||||
|
||||
local function EncodePayload(payload) |
||||
local serialized = libS:Serialize(payload) |
||||
local encoded = libCE:Encode(libC:CompressHuffman(serialized)) |
||||
if not encoded then |
||||
encoded = libCE:Encode(libC:CompressLZW(serialized)) |
||||
end |
||||
if not encoded then |
||||
encoded = libCE:Encode("\001" .. serialized) |
||||
end |
||||
return encoded |
||||
end |
||||
|
||||
local function DecodePayload(encoded) |
||||
local decoded = libCE:Decode(encoded) |
||||
if not decoded then return end |
||||
local decompressed = libC:Decompress(decoded) |
||||
if not decompressed then return end |
||||
local ok, payload = libS:Deserialize(decompressed) |
||||
if not ok then return end |
||||
return payload |
||||
end |
||||
|
||||
function ChannelSync:BroadcastScanData(scanType) |
||||
if scanType ~= "Full" and scanType ~= "GetAll" and scanType ~= "Group" and scanType ~= "Search" then |
||||
return |
||||
end |
||||
if TSM.processingData then |
||||
TSMAPI:CreateTimeDelay("auctionDBChannelSyncBroadcast", 0.5, function() |
||||
ChannelSync:BroadcastScanData(scanType) |
||||
end) |
||||
return |
||||
end |
||||
EnsureChannel() |
||||
if not private.channelId then return end |
||||
|
||||
TSM:Serialize() |
||||
local payload = { |
||||
scanTime = TSM.db.realm.lastCompleteScan, |
||||
scanData = TSM.db.realm.scanData, |
||||
} |
||||
local encoded = EncodePayload(payload) |
||||
if not encoded then return end |
||||
|
||||
local hash = HashString(encoded) |
||||
if hash == private.lastBroadcastHash then return end |
||||
private.lastBroadcastHash = hash |
||||
|
||||
local total = ceil(#encoded / CHUNK_SIZE) |
||||
for i = 1, total do |
||||
local chunk = strsub(encoded, (i - 1) * CHUNK_SIZE + 1, i * CHUNK_SIZE) |
||||
local msg = strjoin("|", COMM_PREFIX, hash, i .. "/" .. total, chunk) |
||||
self:SendCommMessage(COMM_PREFIX, msg, "CHANNEL", private.channelId) |
||||
end |
||||
end |
||||
|
||||
local function MergeIncomingData(payload) |
||||
if type(payload) ~= "table" or type(payload.scanData) ~= "string" then return end |
||||
|
||||
local incoming = {} |
||||
TSM:Deserialize(payload.scanData, incoming, true) |
||||
for itemID, data in pairs(incoming) do |
||||
local existing = TSM.data[itemID] |
||||
if existing then |
||||
TSM:DecodeItemData(itemID) |
||||
if data.lastScan and (not existing.lastScan or data.lastScan > existing.lastScan) then |
||||
TSM.data[itemID] = data |
||||
end |
||||
else |
||||
TSM.data[itemID] = data |
||||
end |
||||
end |
||||
|
||||
if payload.scanTime and (not TSM.db.realm.lastCompleteScan or payload.scanTime > TSM.db.realm.lastCompleteScan) then |
||||
TSM.db.realm.lastCompleteScan = payload.scanTime |
||||
end |
||||
|
||||
TSM:Serialize() |
||||
end |
||||
|
||||
function ChannelSync:OnCommReceived(_, msg, _, source) |
||||
source = ("-"):split(source or "") |
||||
if strlower(source or "") == strlower(UnitName("player") or "") then return end |
||||
|
||||
local header, hash, seqInfo, chunk = msg:match("^(.-)|([^|]+)|([^|]+)|(.+)$") |
||||
if header ~= COMM_PREFIX or not hash or not seqInfo or not chunk then return end |
||||
|
||||
local seq, total = seqInfo:match("^(%d+)%/(%d+)$") |
||||
seq, total = tonumber(seq), tonumber(total) |
||||
if not seq or not total then return end |
||||
|
||||
local bundle = private.incoming[hash] |
||||
if not bundle or (time() - bundle.time) > BUNDLE_TIMEOUT then |
||||
bundle = {time = time(), total = total, chunks = {}, received = 0} |
||||
private.incoming[hash] = bundle |
||||
end |
||||
|
||||
if bundle.total ~= total then |
||||
private.incoming[hash] = {time = time(), total = total, chunks = {}, received = 0} |
||||
bundle = private.incoming[hash] |
||||
end |
||||
|
||||
if not bundle.chunks[seq] then |
||||
bundle.chunks[seq] = chunk |
||||
bundle.received = bundle.received + 1 |
||||
end |
||||
|
||||
if bundle.received < bundle.total then return end |
||||
|
||||
local parts = {} |
||||
for i = 1, bundle.total do |
||||
if not bundle.chunks[i] then return end |
||||
tinsert(parts, bundle.chunks[i]) |
||||
end |
||||
private.incoming[hash] = nil |
||||
|
||||
local payload = DecodePayload(table.concat(parts)) |
||||
if not payload then return end |
||||
MergeIncomingData(payload) |
||||
end |
||||
Loading…
Reference in new issue