arrow works
[QuestHelper.git] / error.lua
blobaa75f91236e2598e0119976f5cdde88dd1764f6e
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(loud, 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
133 terror.silent = silent
135 QuestHelper_ErrorCatcher_RegisterError("crash", terror)
137 if not first_error then first_error = terror end
139 QuestHelper_ErrorCatcher.CondenseErrors()
141 if loud and not yelled_at_user then
142 message("QuestHelper has broken. You may have to restart WoW. Type \"/qh error\" for a detailed error message.")
143 yelled_at_user = true
147 function QuestHelper_ErrorCatcher.OnError(o_msg, o_frame, o_stack, o_etype, ...)
148 local errorize = false
149 local loud = false
150 if string.find(o_msg, "QuestHelper") and not string.find(o_msg, "Cannot find a library with name") then loud = true end
151 for lin in string.gmatch(debugstack(2, 20, 20), "([^\n]*)") do
152 if string.find(lin, "QuestHelper") and not string.find(lin, "QuestHelper\\AstrolabeQH\\DongleStub.lua") then errorize = true end
155 if loud then errorize = true end
157 if errorize then QuestHelper_ErrorCatcher_ExplicitError(loud, o_msg, o_frame, o_stack) end
159 --[[
160 if o_msg and
163 string.find(o_msg, "QuestHelper") -- Obviously we care about our bugs
165 or (
166 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.
167 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.
170 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.
171 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.
172 then
173 QuestHelper_ErrorCatcher_ExplicitError(o_msg, o_frame, o_stack)
174 end]]
176 return origHandler(o_msg, o_frame, o_stack, o_etype, unpack(arg or {})) -- pass it on
179 seterrorhandler(QuestHelper_ErrorCatcher.OnError) -- at this point we can catch errors
181 function QuestHelper_ErrorCatcher.CompletelyStarted()
182 completely_started = true
184 -- 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.
185 if not QuestHelper_Errors.version or QuestHelper_Errors.version ~= 1 then
186 QuestHelper_Errors = {version = 1}
189 QuestHelper_ErrorCatcher.CondenseErrors()
192 function QuestHelper_ErrorCatcher_CompletelyStarted()
193 QuestHelper_ErrorCatcher.CompletelyStarted()
198 -- and here is the GUI
200 local QHE_Gui = {}
202 function QHE_Gui.ErrorUpdate()
203 QHE_Gui.ErrorTextinate()
204 QHE_Gui.Error.Box:SetText(QHE_Gui.Error.curError)
205 QHE_Gui.Error.Scroll:UpdateScrollChildRect()
206 QHE_Gui.Error.Box:ClearFocus()
209 function QHE_Gui.ErrorTextinate()
210 if first_error then
211 QHE_Gui.Error.curError = string.format("msg: %s\ntoc: %s\nv: %s\ngame: %s\nlocale: %s\ntimestamp: %s\nmutation: %s\nsilent: %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), tostring(first_error.silent), first_error.stack, first_error.addons)
212 else
213 QHE_Gui.Error.curError = "None"
217 function QHE_Gui.ErrorClicked()
218 if (QHE_Gui.Error.selected) then return end
219 QHE_Gui.Error.Box:HighlightText()
220 QHE_Gui.Error.selected = true
223 function QHE_Gui.ErrorDone()
224 QHE_Gui.Error:Hide()
228 -- Create our error message frame. Most of this is also ganked from Swatter.
229 QHE_Gui.Error = CreateFrame("Frame", "QHE_GUIErrorFrame", UIParent)
230 QHE_Gui.Error:Hide()
231 QHE_Gui.Error:SetPoint("CENTER", "UIParent", "CENTER")
232 QHE_Gui.Error:SetFrameStrata("TOOLTIP")
233 QHE_Gui.Error:SetHeight(300)
234 QHE_Gui.Error:SetWidth(600)
235 QHE_Gui.Error:SetBackdrop({
236 bgFile = "Interface/Tooltips/ChatBubble-Background",
237 edgeFile = "Interface/Tooltips/ChatBubble-BackDrop",
238 tile = true, tileSize = 32, edgeSize = 32,
239 insets = { left = 32, right = 32, top = 32, bottom = 32 }
241 QHE_Gui.Error:SetBackdropColor(0.2,0,0, 1)
242 QHE_Gui.Error:SetScript("OnShow", QHE_Gui.ErrorShow)
243 QHE_Gui.Error:SetMovable(true)
245 QHE_Gui.ProxyFrame = CreateFrame("Frame", "QHE_GuiProxyFrame")
246 QHE_Gui.ProxyFrame:SetParent(QHE_Gui.Error)
247 QHE_Gui.ProxyFrame.IsShown = function() return QHE_Gui.Error:IsShown() end
248 QHE_Gui.ProxyFrame.escCount = 0
249 QHE_Gui.ProxyFrame.timer = 0
250 QHE_Gui.ProxyFrame.Hide = (
251 function( self )
252 local numEscapes = QHE_Gui.numEscapes or 1
253 self.escCount = self.escCount + 1
254 if ( self.escCount >= numEscapes ) then
255 self:GetParent():Hide()
256 self.escCount = 0
258 if ( self.escCount == 1 ) then
259 self.timer = 0
263 QHE_Gui.ProxyFrame:SetScript("OnUpdate",
264 function( self, elapsed )
265 local timer = self.timer + elapsed
266 if ( timer >= 1 ) then
267 self.escCount = 0
269 self.timer = timer
272 table.insert(UISpecialFrames, "QHE_GuiProxyFrame")
274 QHE_Gui.Drag = CreateFrame("Button", nil, QHE_Gui.Error)
275 QHE_Gui.Drag:SetPoint("TOPLEFT", QHE_Gui.Error, "TOPLEFT", 10,-5)
276 QHE_Gui.Drag:SetPoint("TOPRIGHT", QHE_Gui.Error, "TOPRIGHT", -10,-5)
277 QHE_Gui.Drag:SetHeight(8)
278 QHE_Gui.Drag:SetHighlightTexture("Interface\\FriendsFrame\\UI-FriendsFrame-HighlightBar")
280 QHE_Gui.Drag:SetScript("OnMouseDown", function() QHE_Gui.Error:StartMoving() end)
281 QHE_Gui.Drag:SetScript("OnMouseUp", function() QHE_Gui.Error:StopMovingOrSizing() end)
283 QHE_Gui.Error.Done = CreateFrame("Button", "", QHE_Gui.Error, "OptionsButtonTemplate")
284 QHE_Gui.Error.Done:SetText("Close")
285 QHE_Gui.Error.Done:SetPoint("BOTTOMRIGHT", QHE_Gui.Error, "BOTTOMRIGHT", -10, 10)
286 QHE_Gui.Error.Done:SetScript("OnClick", QHE_Gui.ErrorDone)
288 QHE_Gui.Error.Mesg = QHE_Gui.Error:CreateFontString("", "OVERLAY", "GameFontNormalSmall")
289 QHE_Gui.Error.Mesg:SetJustifyH("LEFT")
290 QHE_Gui.Error.Mesg:SetPoint("TOPRIGHT", QHE_Gui.Error.Prev, "TOPLEFT", -10, 0)
291 QHE_Gui.Error.Mesg:SetPoint("LEFT", QHE_Gui.Error, "LEFT", 15, 0)
292 QHE_Gui.Error.Mesg:SetHeight(20)
293 QHE_Gui.Error.Mesg:SetText("Select All and Copy the above error message to report this bug.")
295 QHE_Gui.Error.Scroll = CreateFrame("ScrollFrame", "QHE_GUIErrorInputScroll", QHE_Gui.Error, "UIPanelScrollFrameTemplate")
296 QHE_Gui.Error.Scroll:SetPoint("TOPLEFT", QHE_Gui.Error, "TOPLEFT", 20, -20)
297 QHE_Gui.Error.Scroll:SetPoint("RIGHT", QHE_Gui.Error, "RIGHT", -30, 0)
298 QHE_Gui.Error.Scroll:SetPoint("BOTTOM", QHE_Gui.Error.Done, "TOP", 0, 10)
300 QHE_Gui.Error.Box = CreateFrame("EditBox", "QHE_GUIErrorEditBox", QHE_Gui.Error.Scroll)
301 QHE_Gui.Error.Box:SetWidth(500)
302 QHE_Gui.Error.Box:SetHeight(85)
303 QHE_Gui.Error.Box:SetMultiLine(true)
304 QHE_Gui.Error.Box:SetAutoFocus(false)
305 QHE_Gui.Error.Box:SetFontObject(GameFontHighlight)
306 QHE_Gui.Error.Box:SetScript("OnEscapePressed", QHE_Gui.ErrorDone)
307 QHE_Gui.Error.Box:SetScript("OnTextChanged", QHE_Gui.ErrorUpdate)
308 QHE_Gui.Error.Box:SetScript("OnEditFocusGained", QHE_Gui.ErrorClicked)
310 QHE_Gui.Error.Scroll:SetScrollChild(QHE_Gui.Error.Box)
312 function QuestHelper_ErrorCatcher_ReportError()
313 QHE_Gui.Error.selected = false
314 QHE_Gui.ErrorUpdate()
315 QHE_Gui.Error:Show()