let's do this too
[QuestHelper.git] / error.lua
blob764210238d549b684db556fb3de53fe9e8a8bac9
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.
6 ]]
8 QuestHelper_local_version = QuestHelper_File["error.lua"]
9 QuestHelper_toc_version = GetAddOnMetadata("QuestHelper", "Version")
11 local origHandler = geterrorhandler()
13 local QuestHelper_ErrorCatcher = { }
15 local startup_errors = {}
16 local completely_started = false
17 local yelled_at_user = false
19 local first_error = nil
21 QuestHelper_Errors = {}
23 function QuestHelper_ErrorCatcher.TextError(text)
24 DEFAULT_CHAT_FRAME:AddMessage(string.format("|cffff8080QuestHelper Error Handler: |r%s", text))
25 end
28 -- ganked verbatim from Swatter
29 function QuestHelper_ErrorCatcher.GetAddOns()
30 local addlist = ""
31 for i = 1, GetNumAddOns() do
32 local name, title, notes, enabled, loadable, reason, security = GetAddOnInfo(i)
34 local loaded = IsAddOnLoaded(i)
35 if (loaded) then
36 if not name then name = "Anonymous" end
37 name = name:gsub("[^a-zA-Z0-9]+", "")
38 local version = GetAddOnMetadata(i, "Version")
39 local class = getglobal(name)
40 if not class or type(class)~='table' then class = getglobal(name:lower()) end
41 if not class or type(class)~='table' then class = getglobal(name:sub(1,1):upper()..name:sub(2):lower()) end
42 if not class or type(class)~='table' then class = getglobal(name:upper()) end
43 if class and type(class)=='table' then
44 if (class.version) then
45 version = class.version
46 elseif (class.Version) then
47 version = class.Version
48 elseif (class.VERSION) then
49 version = class.VERSION
50 end
51 end
52 local const = getglobal(name:upper().."_VERSION")
53 if (const) then version = const end
55 if type(version)=='table' then
56 version = table.concat(version,":")
57 end
59 if (version) then
60 addlist = addlist.." "..name..", v"..version.."\n"
61 else
62 addlist = addlist.." "..name.."\n"
63 end
64 end
65 end
66 return addlist
67 end
69 local error_uniqueness_whitelist = {
70 ["count"] = true,
71 ["timestamp"] = true,
74 -- here's the logic
75 function QuestHelper_ErrorCatcher.CondenseErrors()
76 if completely_started then
77 while next(startup_errors) do
78 _, err = next(startup_errors)
79 table.remove(startup_errors)
81 if not QuestHelper_Errors[err.type] then QuestHelper_Errors[err.type] = {} end
83 local found = false
85 for _, item in ipairs(QuestHelper_Errors[err.type]) do
86 local match = true
87 for k, v in pairs(err.dat) do
88 if not error_uniqueness_whitelist[k] and item[k] ~= v then match = false break end
89 end
90 if match then for k, v in pairs(item) do
91 if not error_uniqueness_whitelist[k] and err.dat[k] ~= v then match = false break end
92 end end
93 if match then
94 found = true
95 item.count = (item.count or 1) + 1
96 break
97 end
98 end
100 if not found then
101 table.insert(QuestHelper_Errors[err.type], err.dat)
107 function QuestHelper_ErrorCatcher_RegisterError(typ, dat)
108 table.insert(startup_errors, {type = typ, dat = dat})
109 QuestHelper_ErrorCatcher.CondenseErrors()
112 function QuestHelper_ErrorPackage(depth)
113 return {
114 timestamp = date("%Y-%m-%d %H:%M:%S"),
115 local_version = QuestHelper_local_version,
116 toc_version = QuestHelper_toc_version,
117 game_version = GetBuildInfo(),
118 locale = GetLocale(),
119 mutation_passes_exceeded = QuestHelper and QuestHelper.mutation_passes_exceeded,
120 stack = debugstack(depth or 4, 20, 20),
124 function QuestHelper_ErrorCatcher_ExplicitError(o_msg, o_frame, o_stack, ...)
125 msg = o_msg or ""
127 -- We toss it into StartupErrors, and then if we're running properly, we'll merge it into the main DB.
128 local terror = QuestHelper_ErrorPackage()
130 terror.message = msg
131 terror.addons = QuestHelper_ErrorCatcher.GetAddOns()
132 terror.stack = o_stack or terror.stack
134 QuestHelper_ErrorCatcher_RegisterError("crash", terror)
136 if not first_error then first_error = terror end
138 QuestHelper_ErrorCatcher.CondenseErrors()
140 if not yelled_at_user then
141 message("QuestHelper has broken. You may have to restart WoW. Type \"/qh error\" for a detailed error message.")
142 yelled_at_user = true
146 function QuestHelper_ErrorCatcher.OnError(o_msg, o_frame, o_stack, o_etype, ...)
147 if o_msg and
150 string.find(o_msg, "QuestHelper") -- Obviously we care about our bugs
152 or (
153 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.
154 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.
157 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.
158 then
159 QuestHelper_ErrorCatcher_ExplicitError(o_msg, o_frame, o_stack)
162 return origHandler(o_msg, o_frame, o_stack, o_etype, unpack(arg or {})) -- pass it on
165 seterrorhandler(QuestHelper_ErrorCatcher.OnError) -- at this point we can catch errors
167 function QuestHelper_ErrorCatcher.CompletelyStarted()
168 completely_started = true
170 -- 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.
171 if not QuestHelper_Errors.version or QuestHelper_Errors.version ~= 1 then
172 QuestHelper_Errors = {version = 1}
175 QuestHelper_ErrorCatcher.CondenseErrors()
178 function QuestHelper_ErrorCatcher_CompletelyStarted()
179 QuestHelper_ErrorCatcher.CompletelyStarted()
184 -- and here is the GUI
186 local QHE_Gui = {}
188 function QHE_Gui.ErrorUpdate()
189 QHE_Gui.ErrorTextinate()
190 QHE_Gui.Error.Box:SetText(QHE_Gui.Error.curError)
191 QHE_Gui.Error.Scroll:UpdateScrollChildRect()
192 QHE_Gui.Error.Box:ClearFocus()
195 function QHE_Gui.ErrorTextinate()
196 if first_error then
197 QHE_Gui.Error.curError = string.format("msg: %s\ntoc: %s\nv: %s\ngame: %s\nlocale: %s\ntimestamp: %s\nmutation: %s\n\n%s\naddons:\n%s", first_error.message, first_error.toc_version, first_error.local_version, first_error.game_version, first_error.locale, first_error.timestamp, tostring(first_error.mutation_passes_exceeded), first_error.stack, first_error.addons)
198 else
199 QHE_Gui.Error.curError = "None"
203 function QHE_Gui.ErrorClicked()
204 if (QHE_Gui.Error.selected) then return end
205 QHE_Gui.Error.Box:HighlightText()
206 QHE_Gui.Error.selected = true
209 function QHE_Gui.ErrorDone()
210 QHE_Gui.Error:Hide()
214 -- Create our error message frame. Most of this is also ganked from Swatter.
215 QHE_Gui.Error = CreateFrame("Frame", "QHE_GUIErrorFrame", UIParent)
216 QHE_Gui.Error:Hide()
217 QHE_Gui.Error:SetPoint("CENTER", "UIParent", "CENTER")
218 QHE_Gui.Error:SetFrameStrata("TOOLTIP")
219 QHE_Gui.Error:SetHeight(300)
220 QHE_Gui.Error:SetWidth(600)
221 QHE_Gui.Error:SetBackdrop({
222 bgFile = "Interface/Tooltips/ChatBubble-Background",
223 edgeFile = "Interface/Tooltips/ChatBubble-BackDrop",
224 tile = true, tileSize = 32, edgeSize = 32,
225 insets = { left = 32, right = 32, top = 32, bottom = 32 }
227 QHE_Gui.Error:SetBackdropColor(0.2,0,0, 1)
228 QHE_Gui.Error:SetScript("OnShow", QHE_Gui.ErrorShow)
229 QHE_Gui.Error:SetMovable(true)
231 QHE_Gui.ProxyFrame = CreateFrame("Frame", "QHE_GuiProxyFrame")
232 QHE_Gui.ProxyFrame:SetParent(QHE_Gui.Error)
233 QHE_Gui.ProxyFrame.IsShown = function() return QHE_Gui.Error:IsShown() end
234 QHE_Gui.ProxyFrame.escCount = 0
235 QHE_Gui.ProxyFrame.timer = 0
236 QHE_Gui.ProxyFrame.Hide = (
237 function( self )
238 local numEscapes = QHE_Gui.numEscapes or 1
239 self.escCount = self.escCount + 1
240 if ( self.escCount >= numEscapes ) then
241 self:GetParent():Hide()
242 self.escCount = 0
244 if ( self.escCount == 1 ) then
245 self.timer = 0
249 QHE_Gui.ProxyFrame:SetScript("OnUpdate",
250 function( self, elapsed )
251 local timer = self.timer + elapsed
252 if ( timer >= 1 ) then
253 self.escCount = 0
255 self.timer = timer
258 table.insert(UISpecialFrames, "QHE_GuiProxyFrame")
260 QHE_Gui.Drag = CreateFrame("Button", nil, QHE_Gui.Error)
261 QHE_Gui.Drag:SetPoint("TOPLEFT", QHE_Gui.Error, "TOPLEFT", 10,-5)
262 QHE_Gui.Drag:SetPoint("TOPRIGHT", QHE_Gui.Error, "TOPRIGHT", -10,-5)
263 QHE_Gui.Drag:SetHeight(8)
264 QHE_Gui.Drag:SetHighlightTexture("Interface\\FriendsFrame\\UI-FriendsFrame-HighlightBar")
266 QHE_Gui.Drag:SetScript("OnMouseDown", function() QHE_Gui.Error:StartMoving() end)
267 QHE_Gui.Drag:SetScript("OnMouseUp", function() QHE_Gui.Error:StopMovingOrSizing() end)
269 QHE_Gui.Error.Done = CreateFrame("Button", "", QHE_Gui.Error, "OptionsButtonTemplate")
270 QHE_Gui.Error.Done:SetText("Close")
271 QHE_Gui.Error.Done:SetPoint("BOTTOMRIGHT", QHE_Gui.Error, "BOTTOMRIGHT", -10, 10)
272 QHE_Gui.Error.Done:SetScript("OnClick", QHE_Gui.ErrorDone)
274 QHE_Gui.Error.Mesg = QHE_Gui.Error:CreateFontString("", "OVERLAY", "GameFontNormalSmall")
275 QHE_Gui.Error.Mesg:SetJustifyH("LEFT")
276 QHE_Gui.Error.Mesg:SetPoint("TOPRIGHT", QHE_Gui.Error.Prev, "TOPLEFT", -10, 0)
277 QHE_Gui.Error.Mesg:SetPoint("LEFT", QHE_Gui.Error, "LEFT", 15, 0)
278 QHE_Gui.Error.Mesg:SetHeight(20)
279 QHE_Gui.Error.Mesg:SetText("Select All and Copy the above error message to report this bug.")
281 QHE_Gui.Error.Scroll = CreateFrame("ScrollFrame", "QHE_GUIErrorInputScroll", QHE_Gui.Error, "UIPanelScrollFrameTemplate")
282 QHE_Gui.Error.Scroll:SetPoint("TOPLEFT", QHE_Gui.Error, "TOPLEFT", 20, -20)
283 QHE_Gui.Error.Scroll:SetPoint("RIGHT", QHE_Gui.Error, "RIGHT", -30, 0)
284 QHE_Gui.Error.Scroll:SetPoint("BOTTOM", QHE_Gui.Error.Done, "TOP", 0, 10)
286 QHE_Gui.Error.Box = CreateFrame("EditBox", "QHE_GUIErrorEditBox", QHE_Gui.Error.Scroll)
287 QHE_Gui.Error.Box:SetWidth(500)
288 QHE_Gui.Error.Box:SetHeight(85)
289 QHE_Gui.Error.Box:SetMultiLine(true)
290 QHE_Gui.Error.Box:SetAutoFocus(false)
291 QHE_Gui.Error.Box:SetFontObject(GameFontHighlight)
292 QHE_Gui.Error.Box:SetScript("OnEscapePressed", QHE_Gui.ErrorDone)
293 QHE_Gui.Error.Box:SetScript("OnTextChanged", QHE_Gui.ErrorUpdate)
294 QHE_Gui.Error.Box:SetScript("OnEditFocusGained", QHE_Gui.ErrorClicked)
296 QHE_Gui.Error.Scroll:SetScrollChild(QHE_Gui.Error.Box)
298 function QuestHelper_ErrorCatcher_ReportError()
299 QHE_Gui.Error.selected = false
300 QHE_Gui.ErrorUpdate()
301 QHE_Gui.Error:Show()