Merge branch 'master' of git://cams.pavlovian.net/questhelper
[QuestHelper.git] / error.lua
blobc88c86fd68d7adb961cec40bdc6d1d95240d57b3
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 = {}
22 QuestHelper_Errors.crashes = {}
24 function QuestHelper_ErrorCatcher.TextError(text)
25 DEFAULT_CHAT_FRAME:AddMessage(string.format("|cffff8080QuestHelper Error Handler: |r%s", text))
26 end
29 -- ganked verbatim from Swatter
30 function QuestHelper_ErrorCatcher.GetAddOns()
31 local addlist = ""
32 for i = 1, GetNumAddOns() do
33 local name, title, notes, enabled, loadable, reason, security = GetAddOnInfo(i)
35 local loaded = IsAddOnLoaded(i)
36 if (loaded) then
37 if not name then name = "Anonymous" end
38 name = name:gsub("[^a-zA-Z0-9]+", "")
39 local version = GetAddOnMetadata(i, "Version")
40 local class = getglobal(name)
41 if not class or type(class)~='table' then class = getglobal(name:lower()) end
42 if not class or type(class)~='table' then class = getglobal(name:sub(1,1):upper()..name:sub(2):lower()) end
43 if not class or type(class)~='table' then class = getglobal(name:upper()) end
44 if class and type(class)=='table' then
45 if (class.version) then
46 version = class.version
47 elseif (class.Version) then
48 version = class.Version
49 elseif (class.VERSION) then
50 version = class.VERSION
51 end
52 end
53 local const = getglobal(name:upper().."_VERSION")
54 if (const) then version = const end
56 if type(version)=='table' then
57 if (nLog) then
58 nLog.AddMessage("!swatter", "Swatter.lua", N_INFO, "version is a table", name, table.concat(version,":"))
59 end
60 version = table.concat(version,":")
61 end
63 if (version) then
64 addlist = addlist.." "..name..", v"..version.."\n"
65 else
66 addlist = addlist.." "..name.."\n"
67 end
68 end
69 end
70 return addlist
71 end
74 -- here's the logic
75 function QuestHelper_ErrorCatcher.CondenseErrors()
76 while next(startup_errors) do
77 _, err = next(startup_errors)
78 table.remove(startup_errors)
80 local found = false
82 for _, item in ipairs(QuestHelper_Errors.crashes) do
83 if item.message == err.message and item.stack == err.stack and item.local_version == err.local_version and item.toc_version == err.toc_version and item.addons == err.addons and item.game_version == err.game_version and item.locale == err.locale then
84 found = true
85 item.count = item.count + 1
86 end
87 end
89 if not found then
90 table.insert(QuestHelper_Errors.crashes, err)
91 end
92 end
93 end
95 function QuestHelper_ErrorPackage(depth)
96 return {
97 timestamp = date("%Y-%m-%d %H:%M:%S"),
98 stack = stack,
99 local_version = QuestHelper_local_version,
100 toc_version = QuestHelper_toc_version,
101 game_version = GetBuildInfo(),
102 locale = GetLocale(),
103 mutation_passes_exceeded = QuestHelper and QuestHelper.mutation_passes_exceeded,
104 stack = debugstack(depth or 4, 20, 20),
108 function QuestHelper_ErrorCatcher_ExplicitError(o_msg, o_frame, o_stack, ...)
109 msg = o_msg or ""
111 -- We toss it into StartupErrors, and then if we're running properly, we'll merge it into the main DB.
112 local terror = QuestHelper_ErrorPackage()
114 terror.message = msg
115 terror.count = 0
116 terror.addons = QuestHelper_ErrorCatcher.GetAddOns()
117 terror.stack = o_stack or terror.stack
119 table.insert(startup_errors, terror)
121 if not first_error then first_error = terror end
123 if completely_started then QuestHelper_ErrorCatcher.CondenseErrors() end
125 if not yelled_at_user then
126 message("QuestHelper has broken. You may have to restart WoW. Type \"/qh error\" for a detailed error message.")
127 yelled_at_user = true
131 function QuestHelper_ErrorCatcher.OnError(o_msg, o_frame, o_stack, o_etype, ...)
132 if (
133 string.find(o_msg, "QuestHelper") -- Obviously we care about our bugs
134 or 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.
136 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.
137 and not string.find(o_msg, "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.
138 then
139 QuestHelper_ErrorCatcher_ExplicitError(o_msg, o_frame, o_stack)
142 return origHandler(o_msg, o_frame, o_stack, o_etype, unpack(arg or {})) -- pass it on
145 seterrorhandler(QuestHelper_ErrorCatcher.OnError) -- at this point we can catch errors
147 function QuestHelper_ErrorCatcher.CompletelyStarted()
148 completely_started = true
149 QuestHelper_ErrorCatcher.CondenseErrors()
152 function QuestHelper_ErrorCatcher_CompletelyStarted()
153 QuestHelper_ErrorCatcher.CompletelyStarted()
158 -- and here is the GUI
160 local QHE_Gui = {}
162 function QHE_Gui.ErrorUpdate()
163 QHE_Gui.ErrorTextinate()
164 QHE_Gui.Error.Box:SetText(QHE_Gui.Error.curError)
165 QHE_Gui.Error.Scroll:UpdateScrollChildRect()
166 QHE_Gui.Error.Box:ClearFocus()
169 function QHE_Gui.ErrorTextinate()
170 if first_error then
171 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)
172 else
173 QHE_Gui.Error.curError = "None"
177 function QHE_Gui.ErrorClicked()
178 if (QHE_Gui.Error.selected) then return end
179 QHE_Gui.Error.Box:HighlightText()
180 QHE_Gui.Error.selected = true
183 function QHE_Gui.ErrorDone()
184 QHE_Gui.Error:Hide()
188 -- Create our error message frame. Most of this is also ganked from Swatter.
189 QHE_Gui.Error = CreateFrame("Frame", "QHE_GUIErrorFrame", UIParent)
190 QHE_Gui.Error:Hide()
191 QHE_Gui.Error:SetPoint("CENTER", "UIParent", "CENTER")
192 QHE_Gui.Error:SetFrameStrata("TOOLTIP")
193 QHE_Gui.Error:SetHeight(300)
194 QHE_Gui.Error:SetWidth(600)
195 QHE_Gui.Error:SetBackdrop({
196 bgFile = "Interface/Tooltips/ChatBubble-Background",
197 edgeFile = "Interface/Tooltips/ChatBubble-BackDrop",
198 tile = true, tileSize = 32, edgeSize = 32,
199 insets = { left = 32, right = 32, top = 32, bottom = 32 }
201 QHE_Gui.Error:SetBackdropColor(0.2,0,0, 1)
202 QHE_Gui.Error:SetScript("OnShow", QHE_Gui.ErrorShow)
203 QHE_Gui.Error:SetMovable(true)
205 QHE_Gui.ProxyFrame = CreateFrame("Frame", "QHE_GuiProxyFrame")
206 QHE_Gui.ProxyFrame:SetParent(QHE_Gui.Error)
207 QHE_Gui.ProxyFrame.IsShown = function() return QHE_Gui.Error:IsShown() end
208 QHE_Gui.ProxyFrame.escCount = 0
209 QHE_Gui.ProxyFrame.timer = 0
210 QHE_Gui.ProxyFrame.Hide = (
211 function( self )
212 local numEscapes = QHE_Gui.numEscapes or 1
213 self.escCount = self.escCount + 1
214 if ( self.escCount >= numEscapes ) then
215 self:GetParent():Hide()
216 self.escCount = 0
218 if ( self.escCount == 1 ) then
219 self.timer = 0
223 QHE_Gui.ProxyFrame:SetScript("OnUpdate",
224 function( self, elapsed )
225 local timer = self.timer + elapsed
226 if ( timer >= 1 ) then
227 self.escCount = 0
229 self.timer = timer
232 table.insert(UISpecialFrames, "QHE_GuiProxyFrame")
234 QHE_Gui.Drag = CreateFrame("Button", nil, QHE_Gui.Error)
235 QHE_Gui.Drag:SetPoint("TOPLEFT", QHE_Gui.Error, "TOPLEFT", 10,-5)
236 QHE_Gui.Drag:SetPoint("TOPRIGHT", QHE_Gui.Error, "TOPRIGHT", -10,-5)
237 QHE_Gui.Drag:SetHeight(8)
238 QHE_Gui.Drag:SetHighlightTexture("Interface\\FriendsFrame\\UI-FriendsFrame-HighlightBar")
240 QHE_Gui.Drag:SetScript("OnMouseDown", function() QHE_Gui.Error:StartMoving() end)
241 QHE_Gui.Drag:SetScript("OnMouseUp", function() QHE_Gui.Error:StopMovingOrSizing() end)
243 QHE_Gui.Error.Done = CreateFrame("Button", "", QHE_Gui.Error, "OptionsButtonTemplate")
244 QHE_Gui.Error.Done:SetText("Close")
245 QHE_Gui.Error.Done:SetPoint("BOTTOMRIGHT", QHE_Gui.Error, "BOTTOMRIGHT", -10, 10)
246 QHE_Gui.Error.Done:SetScript("OnClick", QHE_Gui.ErrorDone)
248 QHE_Gui.Error.Mesg = QHE_Gui.Error:CreateFontString("", "OVERLAY", "GameFontNormalSmall")
249 QHE_Gui.Error.Mesg:SetJustifyH("LEFT")
250 QHE_Gui.Error.Mesg:SetPoint("TOPRIGHT", QHE_Gui.Error.Prev, "TOPLEFT", -10, 0)
251 QHE_Gui.Error.Mesg:SetPoint("LEFT", QHE_Gui.Error, "LEFT", 15, 0)
252 QHE_Gui.Error.Mesg:SetHeight(20)
253 QHE_Gui.Error.Mesg:SetText("Select All and Copy the above error message to report this bug.")
255 QHE_Gui.Error.Scroll = CreateFrame("ScrollFrame", "QHE_GUIErrorInputScroll", QHE_Gui.Error, "UIPanelScrollFrameTemplate")
256 QHE_Gui.Error.Scroll:SetPoint("TOPLEFT", QHE_Gui.Error, "TOPLEFT", 20, -20)
257 QHE_Gui.Error.Scroll:SetPoint("RIGHT", QHE_Gui.Error, "RIGHT", -30, 0)
258 QHE_Gui.Error.Scroll:SetPoint("BOTTOM", QHE_Gui.Error.Done, "TOP", 0, 10)
260 QHE_Gui.Error.Box = CreateFrame("EditBox", "QHE_GUIErrorEditBox", QHE_Gui.Error.Scroll)
261 QHE_Gui.Error.Box:SetWidth(500)
262 QHE_Gui.Error.Box:SetHeight(85)
263 QHE_Gui.Error.Box:SetMultiLine(true)
264 QHE_Gui.Error.Box:SetAutoFocus(false)
265 QHE_Gui.Error.Box:SetFontObject(GameFontHighlight)
266 QHE_Gui.Error.Box:SetScript("OnEscapePressed", QHE_Gui.ErrorDone)
267 QHE_Gui.Error.Box:SetScript("OnTextChanged", QHE_Gui.ErrorUpdate)
268 QHE_Gui.Error.Box:SetScript("OnEditFocusGained", QHE_Gui.ErrorClicked)
270 QHE_Gui.Error.Scroll:SetScrollChild(QHE_Gui.Error.Box)
272 function QuestHelper_ErrorCatcher_ReportError()
273 QHE_Gui.Error.selected = false
274 QHE_Gui.ErrorUpdate()
275 QHE_Gui.Error:Show()