1 QuestHelper_File
["error.lua"] = "Development Version"
2 QuestHelper_Loadtime
["error.lua"] = GetTime()
5 Much of this code is ganked wholesale from Swatter, and is Copyright (C) 2006 Norganna.
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
))
28 -- ganked verbatim from Swatter
29 function QuestHelper_ErrorCatcher
.GetAddOns()
31 for i
= 1, GetNumAddOns() do
32 local name
, title
, notes
, enabled
, loadable
, reason
, security
= GetAddOnInfo(i
)
34 local loaded
= IsAddOnLoaded(i
)
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
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
,":")
60 addlist
= addlist
.." "..name
..", v"..version
.."\n"
62 addlist
= addlist
.." "..name
.."\n"
69 local error_uniqueness_whitelist
= {
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
85 for _
, item
in ipairs(QuestHelper_Errors
[err
.type]) do
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
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
95 item
.count
= (item
.count
or 1) + 1
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
)
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
, ...)
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()
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
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
163 string.find(o_msg, "QuestHelper") -- Obviously we care about our bugs
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.
173 QuestHelper_ErrorCatcher_ExplicitError(o_msg, o_frame, o_stack)
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
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()
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
)
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()
228 -- Create our error message frame. Most of this is also ganked from Swatter.
229 QHE_Gui
.Error
= CreateFrame("Frame", "QHE_GUIErrorFrame", UIParent
)
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
= (
252 local numEscapes
= QHE_Gui
.numEscapes
or 1
253 self
.escCount
= self
.escCount
+ 1
254 if ( self
.escCount
>= numEscapes
) then
255 self
:GetParent():Hide()
258 if ( self
.escCount
== 1 ) then
263 QHE_Gui
.ProxyFrame
:SetScript("OnUpdate",
264 function( self
, elapsed
)
265 local timer
= self
.timer
+ elapsed
266 if ( timer
>= 1 ) then
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()