-- ------------------------------------------------------------------------------ -- -- 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", "AceEvent-3.0") local CHANNEL_NAME = "TSM_AuctionDB" local COMM_PREFIX = "TSMAuctionDB" local CHUNK_SIZE = 180 local BUNDLE_TIMEOUT = 45 local private = { channelId = nil, channelName = nil, lastBroadcastHash = nil, incoming = {}, } local strbyte = string.byte local libS = LibStub:GetLibrary("AceSerializer-3.0") local libC = LibStub:GetLibrary("LibCompress") local libCE local function GetCodec() if libCE then return libCE end libCE = libC:GetChatEncodeTable("", "\031") if not libCE then libCE = libC:GetChatEncodeTable() end return libCE end 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 private.channelName = private.channelId and CHANNEL_NAME or nil end local function ChatFilter(_, _, msg, _, _, _, _, _, channelName) if channelName ~= CHANNEL_NAME then return end if strsub(msg, 1, #COMM_PREFIX + 1) == COMM_PREFIX .. "|" then return true end end function ChannelSync:OnEnable() self:RegisterEvent("PLAYER_ENTERING_WORLD", "OnPlayerEnteringWorld") self:RegisterEvent("CHAT_MSG_CHANNEL_NOTICE", "OnChannelNotice") self:RegisterEvent("CHAT_MSG_CHANNEL", "OnChannelMessage") 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 codec = GetCodec() if not codec then return end local encoded = codec:Encode(libC:CompressHuffman(serialized)) if not encoded then encoded = codec:Encode(libC:CompressLZW(serialized)) end if not encoded then encoded = codec:Encode("\001" .. serialized) end return encoded end local function DecodePayload(encoded) local codec = GetCodec() if not codec then return end local decoded = codec: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.channelName or 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) SendChatMessage(msg, "CHANNEL", nil, 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:OnChannelMessage(_, msg, source, _, _, _, _, _, channelName) if channelName ~= CHANNEL_NAME then return end if strsub(msg or "", 1, #COMM_PREFIX + 1) ~= COMM_PREFIX .. "|" then return end 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