-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAddonUpdates.lua
More file actions
244 lines (229 loc) · 8.41 KB
/
Copy pathAddonUpdates.lua
File metadata and controls
244 lines (229 loc) · 8.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
local L = _G.QuestTogether.Translate
-- Peer-reported release discovery. Saved state is account-wide; notification
-- suppression and broadcast pacing last only for this UI session.
local QT = _G.QuestTogether
local BROADCAST_INTERVAL, RETRY_INTERVAL = 300, 20
local function Component(text)
if not text or #text > 6 or (#text > 1 and text:sub(1, 1) == "0") then
return nil
end
return tonumber(text)
end
function QT:ParseAddonVersion(version)
if not self:CanAccessValue(version) or type(version) ~= "string" or #version > 48 then
return nil
end
local major, minor, patch, suffix = version:match("^(%d+)%.(%d+)%.(%d+)(.*)$")
major, minor, patch = Component(major), Component(minor), Component(patch)
if not major or not minor or not patch then
return nil
end
local stage, sequence = 2, 0
if suffix ~= "" then
local channel, number = suffix:match("^%-(%a+)%.(%d+)$")
sequence = Component(number)
if (channel ~= "alpha" and channel ~= "beta") or not sequence or sequence < 1 then
return nil
end
stage = channel == "alpha" and 0 or 1
end
return { major, minor, patch, stage, sequence }
end
function QT:CompareAddonVersions(left, right)
local first, second = self:ParseAddonVersion(left), self:ParseAddonVersion(right)
if not first or not second then
return nil
end
for index = 1, #first do
if first[index] ~= second[index] then
return first[index] > second[index] and 1 or -1
end
end
return 0
end
function QT:GetAddonUpdateState()
local state = rawget(self, "addonUpdateState")
if not state then
state = {}
self.addonUpdateState = state
end
return state
end
function QT:GetAvailableAddonUpdate()
local global = self.db and self.db.global
if type(global) ~= "table" then
return nil
end
local known = self:ParseAddonVersion(global.availableAddonVersion)
if not known or known[4] ~= 2 then
global.availableAddonVersion, global.addonUpdateAvailable = "", false
return nil
end
local comparison = self:CompareAddonVersions(global.availableAddonVersion, self:GetAddonVersion())
if comparison == nil then
return nil
end
global.addonUpdateAvailable = comparison == 1
if comparison == 1 then
return global.availableAddonVersion
else
-- Installing this release (or a newer one) resolves the saved notice.
-- Unknown local metadata cannot prove the update was installed.
global.availableAddonVersion = ""
end
return nil
end
function QT:NotifyAddonUpdate()
local version = self:GetAvailableAddonUpdate()
if not version or not self.hasLoggedIn or self.isLoggingOut then
return false
end
local state = self:GetAddonUpdateState()
if state.noticeShown then
return false
end
state.noticeShown = true
self:Print(
L("A newer QuestTogether version is available: ")
.. version
.. L(" (installed: ")
.. self:GetAddonVersion()
.. L("). Please update with your addon manager.")
)
return true
end
function QT:ObserveAddonVersion(version)
local parsed = self:ParseAddonVersion(version)
if not self.isEnabled or not parsed or parsed[4] ~= 2 then
return false
end
local global = self.db and self.db.global
if type(global) ~= "table" or self:CompareAddonVersions(version, self:GetAddonVersion()) ~= 1 then
return false
end
local previous = self:GetAvailableAddonUpdate()
if not previous or self:CompareAddonVersions(version, previous) == 1 then
global.availableAddonVersion = version
end
global.addonUpdateAvailable = true
self:NotifyAddonUpdate()
return true
end
function QT:RememberPlayerAddonVersion(sender, version)
local name = self:NormalizeMemberName(sender)
if not name or not self:ParseAddonVersion(version) or not self:IsKnownQTPlayer(name) then return false end
local state = self:GetQTPlayerPresenceState()
state.peerVersions = state.peerVersions or {}
state.peerVersions[name] = version
return true
end
function QT:GetPlayerAddonVersion(sender)
local name = self:NormalizeMemberName(sender)
if not name then return nil end
if self:IsSelfSender(name) then return self:GetAddonVersion() end
if not self:IsKnownQTPlayer(name) then return nil end
local state = rawget(self, "qtPlayerPresenceState")
return state and state.peerVersions and state.peerVersions[name] or nil
end
function QT:GetLocalPartySize()
if self:IsRuntimeRestricted() then return nil end
local getter = self.API and self.API.GetPartyJoinInfo
if type(getter) ~= "function" then return nil end
local ok, grouped, _, size = pcall(getter)
if not ok or not self:CanAccessValue(grouped) or type(grouped) ~= "boolean" then return nil end
size = self:SafeToNumber(size)
if not size or size < 0 or size > 40 or size ~= math.floor(size) then return nil end
if not grouped then return size == 0 and 0 or nil end
return size > 0 and size or nil
end
function QT:GetPlayerTooltipStats(sender)
local name = self:NormalizeMemberName(sender)
if not name then return nil end
if self:IsSelfSender(name) then
return { count = self:GetMonitoredQuestCount(), partySize = self:GetLocalPartySize() }
end
if not self:IsKnownQTPlayer(name) then return nil end
local state = rawget(self, "qtPlayerPresenceState")
local record = state and state.peerTooltipStats and state.peerTooltipStats[name]
local now = self.API.GetTime and self:SafeToNumber(self.API.GetTime())
if record and now and now >= record.at and now - record.at < (record.lifetime or 180) then return record end
end
function QT:GetPlayerMonitoredQuestCount(sender)
local stats = self:GetPlayerTooltipStats(sender)
return stats and stats.count
end
function QT:GetPlayerPartySize(sender)
local stats = self:GetPlayerTooltipStats(sender)
return stats and stats.partySize
end
function QT:HandleAddonVersionMessage(payload, sender)
if not self:CanAccessValue(payload) or type(payload) ~= "string" or #payload > 64 then
return false
end
local version = payload:match("^1,(.+)$")
local extended, count, partySize
if not version then
local rawCount, rawSize
version, rawCount, rawSize = payload:match("^2,([^,]+),(%d*),(%d*)$")
if not version then return false end
extended = true
if rawCount ~= "" then
count = self:SafeToNumber(rawCount)
if not count or count < 0 or count > 20000 or count ~= math.floor(count) then return false end
end
if rawSize ~= "" then
partySize = self:SafeToNumber(rawSize)
if not partySize or partySize > 40 or partySize < 0 or partySize ~= math.floor(partySize) then return false end
end
end
local now = self.API.GetTime and self:SafeToNumber(self.API.GetTime())
if not now or not self:ParseAddonVersion(version) or not self:RecordQTPlayerPresence(sender, true) then
return false
end
self:RememberPlayerAddonVersion(sender, version)
if extended then
local state = self:GetQTPlayerPresenceState()
state.peerTooltipStats = state.peerTooltipStats or {}
state.peerTooltipStats[self:NormalizeMemberName(sender)] = { count = count, partySize = partySize, at = now }
end
self:ObserveAddonVersion(version)
return true
end
function QT:BroadcastAddonVersion(forPresenceHeartbeat)
if not self.isEnabled or self.isLoggingOut then
return false
end
local now = self.API and self.API.GetTime and self:SafeToNumber(self.API.GetTime())
if not now then
return false
end
local state = self:GetAddonUpdateState()
local interval = forPresenceHeartbeat and math.min(state.interval or RETRY_INTERVAL, RETRY_INTERVAL) or state.interval
if state.lastAttempt and now >= state.lastAttempt and now - state.lastAttempt < interval then
return false
end
state.lastAttempt, state.interval = now, RETRY_INTERVAL
if forPresenceHeartbeat then self:BroadcastPartyVisualMetadata() end
local version = self:GetAddonVersion()
if not self:ParseAddonVersion(version) then
return false
end
-- Advertise only our installed version, never relay another peer's claim.
-- Keep legacy presence unchanged and reuse the existing paced update frame.
local payload = "1," .. version
-- Alternate extended and legacy version heartbeats. Old clients require an
-- exact version string; they must continue receiving readable advertisements.
if forPresenceHeartbeat and not state.lastVersionIncludedStats then
local count = self:GetMonitoredQuestCount()
local partySize = self:GetLocalPartySize()
payload = "2," .. version .. "," .. (count and tostring(count) or "") .. "," .. (partySize and tostring(partySize) or "")
state.lastVersionIncludedStats = true
else
state.lastVersionIncludedStats = false
end
local sent = self:SendWireMessageToAnnouncementRoutes(self:SerializeWireMessage("QTVR", payload), "addon version")
if sent then
state.interval = BROADCAST_INTERVAL
end
return sent
end