diff --git a/TradeSkillMaster_AuctionDB/Modules/ChannelSync.lua b/TradeSkillMaster_AuctionDB/Modules/ChannelSync.lua new file mode 100644 index 0000000..63fb189 --- /dev/null +++ b/TradeSkillMaster_AuctionDB/Modules/ChannelSync.lua @@ -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 diff --git a/TradeSkillMaster_AuctionDB/Modules/Scanning.lua b/TradeSkillMaster_AuctionDB/Modules/Scanning.lua index 9913da9..353a86b 100644 --- a/TradeSkillMaster_AuctionDB/Modules/Scanning.lua +++ b/TradeSkillMaster_AuctionDB/Modules/Scanning.lua @@ -337,6 +337,9 @@ function Scan.ProcessGetAllScan(self) -- Process the collected "GetAll" auction data as a new "complete scan" with today's date. TSM.db.realm.lastCompleteScan = time() TSM.Data:ProcessData(data, nil, verifyNewAlgorithm) + if TSM.ChannelSync then + TSM.ChannelSync:BroadcastScanData("GetAll") + end -- Show GUI progress while we're waiting for the processing. -- NOTE: The status text will be set to "complete" elsewhere, automatically. @@ -576,6 +579,9 @@ function Scan:ProcessScanData(scanData) -- Process the collected auction data. TSM.Data:ProcessData(data, Scan.groupItems, verifyNewAlgorithm) + if TSM.ChannelSync then + TSM.ChannelSync:BroadcastScanData(Scan.isScanning) + end end function Scan:ProcessImportedData(auctionData) diff --git a/TradeSkillMaster_AuctionDB/Modules/data.lua b/TradeSkillMaster_AuctionDB/Modules/data.lua index 1c660d0..2430ea3 100644 --- a/TradeSkillMaster_AuctionDB/Modules/data.lua +++ b/TradeSkillMaster_AuctionDB/Modules/data.lua @@ -170,6 +170,9 @@ function Data:ProcessExternalScanData(scanData, groupItems, scanTime) if not next(data) then return end Data:ProcessData(data, groupSet, nil, scanTime or time(), true) + if TSM.ChannelSync then + TSM.ChannelSync:BroadcastScanData("Search") + end end --- Process a table of new market scan data. diff --git a/TradeSkillMaster_AuctionDB/TradeSkillMaster_AuctionDB.toc b/TradeSkillMaster_AuctionDB/TradeSkillMaster_AuctionDB.toc index ef81bec..b76158c 100644 --- a/TradeSkillMaster_AuctionDB/TradeSkillMaster_AuctionDB.toc +++ b/TradeSkillMaster_AuctionDB/TradeSkillMaster_AuctionDB.toc @@ -25,6 +25,7 @@ Locale\ptBR.lua TradeSkillMaster_AuctionDB.lua AppData.lua Modules\data.lua +Modules\ChannelSync.lua Modules\Scanning.lua Modules\GUI.lua -Modules\config.lua \ No newline at end of file +Modules\config.lua