Merge branch 'master' of git://cams.pavlovian.net/questhelper
[QuestHelper.git] / error.lua
blobf5f73ab000712bfdc0b3f6a08f06f887cfba5fb1
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 function QuestHelper_ErrorCatcher_ExplicitError(loud, o_msg, o_frame, o_stack, ...)
128 local msg = o_msg or ""
129 if debug_output then loud = true end
131 -- We toss it into StartupErrors, and then if we're running properly, we'll merge it into the main DB.
132 local terror = QuestHelper_ErrorPackage()
134 terror.message = msg
135 terror.addons = QuestHelper_ErrorCatcher.GetAddOns()
136 terror.stack = o_stack or terror.stack
137 terror.silent = not loud
139 QuestHelper_ErrorCatcher_RegisterError("crash", terror)
141 if first_error and first_error.silent and not first_error.next_loud then first_error.next_loud = terror first_error.addons = "" end
142 if not first_error or first_error.generated then first_error = terror end
144 QuestHelper_ErrorCatcher.CondenseErrors()
146 if loud and not yelled_at_user then
147 message("QuestHelper has broken. You may have to restart WoW. Type \"/qh error\" for a detailed error message.")
148 yelled_at_user = true
152 function QuestHelper_ErrorCatcher_GenerateReport()
153 if first_error then return end -- don't need to generate one
155 local terror = QuestHelper_ErrorPackage()
157 terror.message = "(Full report)"
158 terror.addons = QuestHelper_ErrorCatcher.GetAddOns()
159 terror.stack = ""
160 terror.silent = "(Full report)"
161 terror.generated = true
163 first_error = terror
166 function QuestHelper_ErrorCatcher.OnError(o_msg, o_frame, o_stack, o_etype, ...)
167 local errorize = false
168 local loud = false
169 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
170 for lin in string.gmatch(debugstack(2, 20, 20), "([^\n]*)") do
171 if string.find(lin, "QuestHelper") and not string.find(lin, "QuestHelper\\AstrolabeQH\\DongleStub.lua") then errorize = true end
174 if string.find(o_msg, "SavedVariables") then errorize, loud = false, false end
176 if loud then errorize = true end
178 if errorize then QuestHelper_ErrorCatcher_ExplicitError(loud, o_msg, o_frame, o_stack) end
180 --[[
181 if o_msg and
184 string.find(o_msg, "QuestHelper") -- Obviously we care about our bugs
186 or (
187 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.
188 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.
191 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.
192 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.
193 then
194 QuestHelper_ErrorCatcher_ExplicitError(o_msg, o_frame, o_stack)
195 end]]
197 return origHandler(o_msg, o_frame, o_stack, o_etype, unpack(arg or {})) -- pass it on
200 seterrorhandler(QuestHelper_ErrorCatcher.OnError) -- at this point we can catch errors
202 function QuestHelper_ErrorCatcher.CompletelyStarted()
203 completely_started = true
205 -- 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.
206 if not QuestHelper_Errors.version or QuestHelper_Errors.version ~= 1 then
207 QuestHelper_Errors = {version = 1}
210 QuestHelper_ErrorCatcher.CondenseErrors()
213 function QuestHelper_ErrorCatcher_CompletelyStarted()
214 QuestHelper_ErrorCatcher.CompletelyStarted()
219 -- and here is the GUI
221 local QHE_Gui = {}
223 function QHE_Gui.ErrorUpdate()
224 QHE_Gui.ErrorTextinate()
225 QHE_Gui.Error.Box:SetText(QHE_Gui.Error.curError)
226 QHE_Gui.Error.Scroll:UpdateScrollChildRect()
227 QHE_Gui.Error.Box:ClearFocus()
230 function TextinateError(err)
231 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)
232 if err.next_loud then
233 tserr = tserr .. "\n\n---- Following loud error\n\n" .. TextinateError(err.next_loud)
235 return tserr
238 function QHE_Gui.ErrorTextinate()
239 if first_error then
240 QHE_Gui.Error.curError = TextinateError(first_error)
241 else
242 QHE_Gui.Error.curError = "None"
246 function QHE_Gui.ErrorClicked()
247 if (QHE_Gui.Error.selected) then return end
248 QHE_Gui.Error.Box:HighlightText()
249 QHE_Gui.Error.selected = true
252 function QHE_Gui.ErrorDone()
253 QHE_Gui.Error:Hide()
257 -- Create our error message frame. Most of this is also ganked from Swatter.
258 QHE_Gui.Error = CreateFrame("Frame", "QHE_GUIErrorFrame", UIParent)
259 QHE_Gui.Error:Hide()
260 QHE_Gui.Error:SetPoint("CENTER", "UIParent", "CENTER")
261 QHE_Gui.Error:SetFrameStrata("TOOLTIP")
262 QHE_Gui.Error:SetHeight(300)
263 QHE_Gui.Error:SetWidth(600)
264 QHE_Gui.Error:SetBackdrop({
265 bgFile = "Interface/Tooltips/ChatBubble-Background",
266 edgeFile = "Interface/Tooltips/ChatBubble-BackDrop",
267 tile = true, tileSize = 32, edgeSize = 32,
268 insets = { left = 32, right = 32, top = 32, bottom = 32 }
270 QHE_Gui.Error:SetBackdropColor(0.2,0,0, 1)
271 QHE_Gui.Error:SetScript("OnShow", QHE_Gui.ErrorShow)
272 QHE_Gui.Error:SetMovable(true)
274 QHE_Gui.ProxyFrame = CreateFrame("Frame", "QHE_GuiProxyFrame")
275 QHE_Gui.ProxyFrame:SetParent(QHE_Gui.Error)
276 QHE_Gui.ProxyFrame.IsShown = function() return QHE_Gui.Error:IsShown() end
277 QHE_Gui.ProxyFrame.escCount = 0
278 QHE_Gui.ProxyFrame.timer = 0
279 QHE_Gui.ProxyFrame.Hide = (
280 function( self )
281 local numEscapes = QHE_Gui.numEscapes or 1
282 self.escCount = self.escCount + 1
283 if ( self.escCount >= numEscapes ) then
284 self:GetParent():Hide()
285 self.escCount = 0
287 if ( self.escCount == 1 ) then
288 self.timer = 0
292 QHE_Gui.ProxyFrame:SetScript("OnUpdate",
293 function( self, elapsed )
294 local timer = self.timer + elapsed
295 if ( timer >= 1 ) then
296 self.escCount = 0
298 self.timer = timer
301 table.insert(UISpecialFrames, "QHE_GuiProxyFrame")
303 QHE_Gui.Drag = CreateFrame("Button", nil, QHE_Gui.Error)
304 QHE_Gui.Drag:SetPoint("TOPLEFT", QHE_Gui.Error, "TOPLEFT", 10,-5)
305 QHE_Gui.Drag:SetPoint("TOPRIGHT", QHE_Gui.Error, "TOPRIGHT", -10,-5)
306 QHE_Gui.Drag:SetHeight(8)
307 QHE_Gui.Drag:SetHighlightTexture("Interface\\FriendsFrame\\UI-FriendsFrame-HighlightBar")
309 QHE_Gui.Drag:SetScript("OnMouseDown", function() QHE_Gui.Error:StartMoving() end)
310 QHE_Gui.Drag:SetScript("OnMouseUp", function() QHE_Gui.Error:StopMovingOrSizing() end)
312 QHE_Gui.Error.Done = CreateFrame("Button", "", QHE_Gui.Error, "OptionsButtonTemplate")
313 QHE_Gui.Error.Done:SetText("Close")
314 QHE_Gui.Error.Done:SetPoint("BOTTOMRIGHT", QHE_Gui.Error, "BOTTOMRIGHT", -10, 10)
315 QHE_Gui.Error.Done:SetScript("OnClick", QHE_Gui.ErrorDone)
317 QHE_Gui.Error.Mesg = QHE_Gui.Error:CreateFontString("", "OVERLAY", "GameFontNormalSmall")
318 QHE_Gui.Error.Mesg:SetJustifyH("LEFT")
319 QHE_Gui.Error.Mesg:SetPoint("TOPRIGHT", QHE_Gui.Error.Prev, "TOPLEFT", -10, 0)
320 QHE_Gui.Error.Mesg:SetPoint("LEFT", QHE_Gui.Error, "LEFT", 15, 0)
321 QHE_Gui.Error.Mesg:SetHeight(20)
322 QHE_Gui.Error.Mesg:SetText("Select All and Copy the above error message to report this bug.")
324 QHE_Gui.Error.Scroll = CreateFrame("ScrollFrame", "QHE_GUIErrorInputScroll", QHE_Gui.Error, "UIPanelScrollFrameTemplate")
325 QHE_Gui.Error.Scroll:SetPoint("TOPLEFT", QHE_Gui.Error, "TOPLEFT", 20, -20)
326 QHE_Gui.Error.Scroll:SetPoint("RIGHT", QHE_Gui.Error, "RIGHT", -30, 0)
327 QHE_Gui.Error.Scroll:SetPoint("BOTTOM", QHE_Gui.Error.Done, "TOP", 0, 10)
329 QHE_Gui.Error.Box = CreateFrame("EditBox", "QHE_GUIErrorEditBox", QHE_Gui.Error.Scroll)
330 QHE_Gui.Error.Box:SetWidth(500)
331 QHE_Gui.Error.Box:SetHeight(85)
332 QHE_Gui.Error.Box:SetMultiLine(true)
333 QHE_Gui.Error.Box:SetAutoFocus(false)
334 QHE_Gui.Error.Box:SetFontObject(GameFontHighlight)
335 QHE_Gui.Error.Box:SetScript("OnEscapePressed", QHE_Gui.ErrorDone)
336 QHE_Gui.Error.Box:SetScript("OnTextChanged", QHE_Gui.ErrorUpdate)
337 QHE_Gui.Error.Box:SetScript("OnEditFocusGained", QHE_Gui.ErrorClicked)
339 QHE_Gui.Error.Scroll:SetScrollChild(QHE_Gui.Error.Box)
341 function QuestHelper_ErrorCatcher_ReportError()
342 QHE_Gui.Error.selected = false
343 QHE_Gui.ErrorUpdate()
344 QHE_Gui.Error:Show()