update to 1.1.10
[QuestHelper.git] / error.lua
blobff6f5a68f90e2eddb7899cf8cc9200e0809a8ec3
1 QuestHelper_File["error.lua"] = "Development Version"
2 QuestHelper_Loadtime["error.lua"] = GetTime()
4 --[[
5 Much of this code is ganked wholesale from Swatter, and is Copyright (C) 2006 Norganna. Licensed under LGPL v3.0.
6 ]]
8 local debug_output = false
9 if QuestHelper_File["error.lua"] == "Development Version" then debug_output = true end
11 QuestHelper_local_version = QuestHelper_File["error.lua"]
12 QuestHelper_toc_version = GetAddOnMetadata("QuestHelper", "Version")
14 local origHandler = geterrorhandler()
16 local QuestHelper_ErrorCatcher = { }
18 local startup_errors = {}
19 local completely_started = false
20 local yelled_at_user = false
22 local first_error = nil
24 QuestHelper_Errors = {}
26 function QuestHelper_ErrorCatcher.TextError(text)
27 DEFAULT_CHAT_FRAME:AddMessage(string.format("|cffff8080QuestHelper Error Handler: |r%s", text))
28 end
31 -- ganked verbatim from Swatter
32 function QuestHelper_ErrorCatcher.GetAddOns()
33 local addlist = ""
34 for i = 1, GetNumAddOns() do
35 local name, title, notes, enabled, loadable, reason, security = GetAddOnInfo(i)
37 local loaded = IsAddOnLoaded(i)
38 if (loaded) then
39 if not name then name = "Anonymous" end
40 name = name:gsub("[^a-zA-Z0-9]+", "")
41 local version = GetAddOnMetadata(i, "Version")
42 local class = getglobal(name)
43 if not class or type(class)~='table' then class = getglobal(name:lower()) end
44 if not class or type(class)~='table' then class = getglobal(name:sub(1,1):upper()..name:sub(2):lower()) end
45 if not class or type(class)~='table' then class = getglobal(name:upper()) end
46 if class and type(class)=='table' then
47 if (class.version) then
48 version = class.version
49 elseif (class.Version) then
50 version = class.Version
51 elseif (class.VERSION) then
52 version = class.VERSION
53 end
54 end
55 local const = getglobal(name:upper().."_VERSION")
56 if (const) then version = const end
58 if type(version)=='table' then
59 version = table.concat(version,":")
60 end
62 if (version) then
63 addlist = addlist.." "..name..", v"..version.."\n"
64 else
65 addlist = addlist.." "..name.."\n"
66 end
67 end
68 end
69 return addlist
70 end
72 local error_uniqueness_whitelist = {
73 ["count"] = true,
74 ["timestamp"] = true,
77 -- here's the logic
78 function QuestHelper_ErrorCatcher.CondenseErrors()
79 if completely_started then
80 while next(startup_errors) do
81 _, err = next(startup_errors)
82 table.remove(startup_errors)
84 if not QuestHelper_Errors[err.type] then QuestHelper_Errors[err.type] = {} end
86 local found = false
88 for _, item in ipairs(QuestHelper_Errors[err.type]) do
89 local match = true
90 for k, v in pairs(err.dat) do
91 if not error_uniqueness_whitelist[k] and item[k] ~= v then match = false break end
92 end
93 if match then for k, v in pairs(item) do
94 if not error_uniqueness_whitelist[k] and err.dat[k] ~= v then match = false break end
95 end end
96 if match then
97 found = true
98 item.count = (item.count or 1) + 1
99 break
103 if not found then
104 table.insert(QuestHelper_Errors[err.type], err.dat)
110 function QuestHelper_ErrorCatcher_RegisterError(typ, dat)
111 table.insert(startup_errors, {type = typ, dat = dat})
112 QuestHelper_ErrorCatcher.CondenseErrors()
115 function QuestHelper_ErrorPackage(depth)
116 return {
117 timestamp = date("%Y-%m-%d %H:%M:%S"),
118 local_version = QuestHelper_local_version,
119 toc_version = QuestHelper_toc_version,
120 game_version = GetBuildInfo(),
121 locale = GetLocale(),
122 mutation_passes_exceeded = QuestHelper and QuestHelper.mutation_passes_exceeded,
123 stack = debugstack(depth or 4, 20, 20),
127 StaticPopupDialogs["QH_EXPLODEY"] = {
128 text = "QuestHelper has broken. You may have to restart WoW. Type \"/qh error\" for a detailed error message.",
129 button1 = OKAY,
130 OnAccept = function(self)
131 end,
132 timeout = 0,
133 whileDead = 1,
134 hideOnEscape = 1
137 function QuestHelper_ErrorCatcher_ExplicitError(loud, o_msg, o_frame, o_stack, ...)
138 local msg = o_msg or ""
140 -- We toss it into StartupErrors, and then if we're running properly, we'll merge it into the main DB.
141 local terror = QuestHelper_ErrorPackage()
143 terror.message = msg
144 terror.addons = QuestHelper_ErrorCatcher.GetAddOns()
145 terror.stack = o_stack or terror.stack
146 terror.silent = not loud
148 QuestHelper_ErrorCatcher_RegisterError("crash", terror)
150 if first_error and first_error.silent and not first_error.next_loud and not terror.silent then first_error.next_loud = terror first_error.addons = "" end
151 if not first_error or first_error.generated then first_error = terror end
153 QuestHelper_ErrorCatcher.CondenseErrors()
155 if (--[[debug_output or]] loud) and not yelled_at_user then
156 --print("qhbroken")
157 StaticPopupDialogs["QH_EXPLODEY"] = {
158 text = "QuestHelper has broken. You may have to restart WoW. Type \"/qh error\" for a detailed error message.",
159 button1 = OKAY,
160 OnAccept = function(self)
161 end,
162 timeout = 0,
163 whileDead = 1,
164 hideOnEscape = 1
167 StaticPopup_Show("QH_EXPLODEY")
168 yelled_at_user = true
172 function QuestHelper_ErrorCatcher_GenerateReport()
173 if first_error then return end -- don't need to generate one
175 local terror = QuestHelper_ErrorPackage()
177 terror.message = "(Full report)"
178 terror.addons = QuestHelper_ErrorCatcher.GetAddOns()
179 terror.stack = ""
180 terror.silent = "(Full report)"
181 terror.generated = true
183 first_error = terror
186 function QuestHelper_ErrorCatcher.OnError(o_msg, o_frame, o_stack, o_etype, ...)
187 local errorize = false
188 local loud = false
189 if o_msg and string.find(o_msg, "QuestHelper") and not string.find(o_msg, "Cannot find a library with name") then loud = true end
191 for lin in string.gmatch(debugstack(2, 20, 20), "([^\n]*)") do
192 if string.find(lin, "QuestHelper") and not string.find(lin, "QuestHelper\\AstrolabeQH\\DongleStub.lua") then errorize = true end
195 if string.find(o_msg, "SavedVariables") then errorize, loud = false, false end
196 if string.find(o_msg, "C stack overflow") then
197 if loud then errorize = true end
198 loud = false
201 if loud then errorize = true end
203 if errorize then QuestHelper_ErrorCatcher_ExplicitError(loud, o_msg, o_frame, o_stack) end
205 --[[
206 if o_msg and
209 string.find(o_msg, "QuestHelper") -- Obviously we care about our bugs
211 or (
212 string.find(debugstack(2, 20, 20), "QuestHelper") -- We're being a little overzealous and catching any bug with "QuestHelper" in the stack. This possibly should be removed, I'm not sure it's ever caught anything interesting.
213 and not string.find(o_msg, "Cartographer_POI") -- Cartographer started throwing ridiculous numbers of errors on startup with QH in the stack, and since we caught stuff with QH in the stack, we decided these errors were ours. Urgh. Disabled.
216 and not string.match(o_msg, "WTF\\Account\\.*") -- Sometimes the WTF file gets corrupted. This isn't our fault, since we weren't involved in writing it, and there's also nothing we can do about it - in fact we can't even retrieve the remnants of the old file. We may as well just ignore it. I suppose we could pop up a little dialog saying "clear some space on your hard drive, dufus" but, meh.
217 and not (string.find(o_msg, "Cannot find a library with name") and string.find(debugstack(2, 20, 20), "QuestHelper\\AstrolabeQH\\DongleStub.lua")) -- We're catching errors caused by other people mucking up their dongles. Ughh.
218 then
219 QuestHelper_ErrorCatcher_ExplicitError(o_msg, o_frame, o_stack)
220 end]]
222 return origHandler(o_msg, o_frame, o_stack, o_etype, unpack(arg or {})) -- pass it on
225 seterrorhandler(QuestHelper_ErrorCatcher.OnError) -- at this point we can catch errors
227 function QuestHelper_ErrorCatcher.CompletelyStarted()
228 completely_started = true
230 -- Our old code generated a horrifying number of redundant items. My bad. I considered going and trying to collate them into one chunk, but I think I'm just going to wipe them - it's easier, faster, and should fix some performance issues.
231 if not QuestHelper_Errors.version or QuestHelper_Errors.version ~= 1 then
232 QuestHelper_Errors = {version = 1}
235 QuestHelper_ErrorCatcher.CondenseErrors()
238 function QuestHelper_ErrorCatcher_CompletelyStarted()
239 QuestHelper_ErrorCatcher.CompletelyStarted()
244 -- and here is the GUI
246 local QHE_Gui = {}
248 function QHE_Gui.ErrorUpdate()
249 QHE_Gui.ErrorTextinate()
250 QHE_Gui.Error.Box:SetText(QHE_Gui.Error.curError)
251 QHE_Gui.Error.Scroll:UpdateScrollChildRect()
252 QHE_Gui.Error.Box:ClearFocus()
255 function TextinateError(err)
256 local tserr = string.format("msg: %s\ntoc: %s\nv: %s\ngame: %s\nlocale: %s\ntimestamp: %s\nmutation: %s\nsilent: %s\n\n%s\naddons:\n%s", err.message, err.toc_version, err.local_version, err.game_version, err.locale, err.timestamp, tostring(err.mutation_passes_exceeded), tostring(err.silent), err.stack, err.addons)
257 if err.next_loud then
258 tserr = tserr .. "\n\n---- Following loud error\n\n" .. TextinateError(err.next_loud)
260 return tserr
263 function QHE_Gui.ErrorTextinate()
264 if first_error then
265 QHE_Gui.Error.curError = TextinateError(first_error)
266 else
267 QHE_Gui.Error.curError = "None"
271 function QHE_Gui.ErrorClicked()
272 if (QHE_Gui.Error.selected) then return end
273 QHE_Gui.Error.Box:HighlightText()
274 QHE_Gui.Error.selected = true
277 function QHE_Gui.ErrorDone()
278 QHE_Gui.Error:Hide()
282 -- Create our error message frame. Most of this is also ganked from Swatter.
283 QHE_Gui.Error = CreateFrame("Frame", "QHE_GUIErrorFrame", UIParent)
284 QHE_Gui.Error:Hide()
285 QHE_Gui.Error:SetPoint("CENTER", "UIParent", "CENTER")
286 QHE_Gui.Error:SetFrameStrata("TOOLTIP")
287 QHE_Gui.Error:SetHeight(300)
288 QHE_Gui.Error:SetWidth(600)
289 QHE_Gui.Error:SetBackdrop({
290 bgFile = "Interface/Tooltips/ChatBubble-Background",
291 edgeFile = "Interface/Tooltips/ChatBubble-BackDrop",
292 tile = true, tileSize = 32, edgeSize = 32,
293 insets = { left = 32, right = 32, top = 32, bottom = 32 }
295 QHE_Gui.Error:SetBackdropColor(0.2,0,0, 1)
296 QHE_Gui.Error:SetScript("OnShow", QHE_Gui.ErrorShow)
297 QHE_Gui.Error:SetMovable(true)
299 QHE_Gui.ProxyFrame = CreateFrame("Frame", "QHE_GuiProxyFrame")
300 QHE_Gui.ProxyFrame:SetParent(QHE_Gui.Error)
301 QHE_Gui.ProxyFrame.IsShown = function() return QHE_Gui.Error:IsShown() end
302 QHE_Gui.ProxyFrame.escCount = 0
303 QHE_Gui.ProxyFrame.timer = 0
304 QHE_Gui.ProxyFrame.Hide = (
305 function( self )
306 local numEscapes = QHE_Gui.numEscapes or 1
307 self.escCount = self.escCount + 1
308 if ( self.escCount >= numEscapes ) then
309 self:GetParent():Hide()
310 self.escCount = 0
312 if ( self.escCount == 1 ) then
313 self.timer = 0
317 QHE_Gui.ProxyFrame:SetScript("OnUpdate",
318 function( self, elapsed )
319 local timer = self.timer + elapsed
320 if ( timer >= 1 ) then
321 self.escCount = 0
323 self.timer = timer
326 table.insert(UISpecialFrames, "QHE_GuiProxyFrame")
328 QHE_Gui.Drag = CreateFrame("Button", nil, QHE_Gui.Error)
329 QHE_Gui.Drag:SetPoint("TOPLEFT", QHE_Gui.Error, "TOPLEFT", 10,-5)
330 QHE_Gui.Drag:SetPoint("TOPRIGHT", QHE_Gui.Error, "TOPRIGHT", -10,-5)
331 QHE_Gui.Drag:SetHeight(8)
332 QHE_Gui.Drag:SetHighlightTexture("Interface\\FriendsFrame\\UI-FriendsFrame-HighlightBar")
334 QHE_Gui.Drag:SetScript("OnMouseDown", function() QHE_Gui.Error:StartMoving() end)
335 QHE_Gui.Drag:SetScript("OnMouseUp", function() QHE_Gui.Error:StopMovingOrSizing() end)
337 QHE_Gui.Error.Done = CreateFrame("Button", "", QHE_Gui.Error, "OptionsButtonTemplate")
338 QHE_Gui.Error.Done:SetText("Close")
339 QHE_Gui.Error.Done:SetPoint("BOTTOMRIGHT", QHE_Gui.Error, "BOTTOMRIGHT", -10, 10)
340 QHE_Gui.Error.Done:SetScript("OnClick", QHE_Gui.ErrorDone)
342 QHE_Gui.Error.Mesg = QHE_Gui.Error:CreateFontString("", "OVERLAY", "GameFontNormalSmall")
343 QHE_Gui.Error.Mesg:SetJustifyH("LEFT")
344 QHE_Gui.Error.Mesg:SetPoint("TOPRIGHT", QHE_Gui.Error.Prev, "TOPLEFT", -10, 0)
345 QHE_Gui.Error.Mesg:SetPoint("LEFT", QHE_Gui.Error, "LEFT", 15, 0)
346 QHE_Gui.Error.Mesg:SetHeight(20)
347 QHE_Gui.Error.Mesg:SetText("Select All and Copy the above error message to report this bug.")
349 QHE_Gui.Error.Scroll = CreateFrame("ScrollFrame", "QHE_GUIErrorInputScroll", QHE_Gui.Error, "UIPanelScrollFrameTemplate")
350 QHE_Gui.Error.Scroll:SetPoint("TOPLEFT", QHE_Gui.Error, "TOPLEFT", 20, -20)
351 QHE_Gui.Error.Scroll:SetPoint("RIGHT", QHE_Gui.Error, "RIGHT", -30, 0)
352 QHE_Gui.Error.Scroll:SetPoint("BOTTOM", QHE_Gui.Error.Done, "TOP", 0, 10)
354 QHE_Gui.Error.Box = CreateFrame("EditBox", "QHE_GUIErrorEditBox", QHE_Gui.Error.Scroll)
355 QHE_Gui.Error.Box:SetWidth(500)
356 QHE_Gui.Error.Box:SetHeight(85)
357 QHE_Gui.Error.Box:SetMultiLine(true)
358 QHE_Gui.Error.Box:SetAutoFocus(false)
359 QHE_Gui.Error.Box:SetFontObject(GameFontHighlight)
360 QHE_Gui.Error.Box:SetScript("OnEscapePressed", QHE_Gui.ErrorDone)
361 QHE_Gui.Error.Box:SetScript("OnTextChanged", QHE_Gui.ErrorUpdate)
362 QHE_Gui.Error.Box:SetScript("OnEditFocusGained", QHE_Gui.ErrorClicked)
364 QHE_Gui.Error.Scroll:SetScrollChild(QHE_Gui.Error.Box)
366 function QuestHelper_ErrorCatcher_ReportError()
367 QHE_Gui.Error.selected = false
368 QHE_Gui.ErrorUpdate()
369 QHE_Gui.Error:Show()