local GetTime = QuestHelper_GetTime QuestHelper_File["warning.lua"] = "4.0.1.$svnversion$" QuestHelper_Loadtime["warning.lua"] = GetTime() --[[ Much of this code is ganked wholesale from Swatter, and is Copyright (C) 2006 Norganna. Licensed under LGPL v3.0. ]] local debug_output = false if QuestHelper_File["warning.lua"] == "Development Version" then debug_output = true end QuestHelper_local_version = QuestHelper_File["warning.lua"] QuestHelper_toc_version = GetAddOnMetadata("QuestHelper", "Version") local origHandler = getwarninghandler() local QuestHelper_WarningCatcher = { } local startup_warnings = {} local completely_started = false local yelled_at_user = false local first_warning = nil QuestHelper_Warnings = {} function QuestHelper_WarningCatcher.TextWarning(text) DEFAULT_CHAT_FRAME:AddMessage(string.format("|cffff8080QuestHelper Warning Handler: |r%s", text)) end -- ganked verbatim from Swatter function QuestHelper_WarningCatcher.GetQuests() local return_string = "" for q = 1, GetNumQuestLogEntries() do local title, _, _, _, header, _, _, _, id = GetQuestLogTitle(q) if header then if id then return_string = return_string .. string.format("%s (%d)\n", title, id) else return_string = return_string .. string.format("[%s]\n", title) end else return_string = return_string .. string.format("\t%s (%d)\n", title, id) end end return return_string end function QuestHelper_WarningCatcher.GetAddOns() local addlist = "" for i = 1, GetNumAddOns() do local name, title, notes, enabled, loadable, reason, security = GetAddOnInfo(i) local loaded = IsAddOnLoaded(i) if (loaded) then if not name then name = "Anonymous" end name = name:gsub("[^a-zA-Z0-9]+", "") local version = GetAddOnMetadata(i, "Version") local class = getglobal(name) if not class or type(class)~='table' then class = getglobal(name:lower()) end if not class or type(class)~='table' then class = getglobal(name:sub(1,1):upper()..name:sub(2):lower()) end if not class or type(class)~='table' then class = getglobal(name:upper()) end if class and type(class)=='table' then if (class.version) then version = class.version elseif (class.Version) then version = class.Version elseif (class.VERSION) then version = class.VERSION end end local const = getglobal(name:upper().."_VERSION") if (const) then version = const end if type(version)=='table' then local allstr = true for k, v in pairs(version) do if type(v) ~= "string" then allstr = false end end if allstr then version = table.concat(version,":") end elseif type(version) == 'function' then local yay, v = pcall(version) if yay then version = v end end if (version) then addlist = addlist.." "..name..", v"..tostring(version).."\n" else addlist = addlist.." "..name.."\n" end end end return addlist end local warning_uniqueness_whitelist = { ["count"] = true, ["timestamp"] = true, } -- here's the logic function QuestHelper_WarningCatcher.CondenseWarnings() if completely_started then while next(startup_warnings) do _, warn = next(startup_warnings) table.remove(startup_warnings) if not QuestHelper_Warnings[warn.type] then QuestHelper_Warnings[warn.type] = {} end local found = false for _, item in ipairs(QuestHelper_Warnings[warn.type]) do local match = true for k, v in pairs(warn.dat) do if not warning_uniqueness_whitelist[k] and item[k] ~= v then match = false break end end if match then for k, v in pairs(item) do if not warning_uniqueness_whitelist[k] and warn.dat[k] ~= v then match = false break end end end if match then found = true item.count = (item.count or 1) + 1 break end end if not found then table.insert(QuestHelper_Warnings[warn.type], warn.dat) end end end end function QuestHelper_WarningCatcher_RegisterWarning(typ, dat) table.insert(startup_warnings, {type = typ, dat = dat}) QuestHelper_WarningCatcher.CondenseWarnings() end function QuestHelper_WarningPackage(depth) return { timestamp = date("%Y-%m-%d %H:%M:%S"), local_version = QuestHelper_local_version, toc_version = QuestHelper_toc_version, game_version = GetBuildInfo(), locale = GetLocale(), mutation_passes_exceeded = QuestHelper and QuestHelper.mutation_passes_exceeded, stack = debugstack(depth or 4, 20, 20), } end StaticPopupDialogs["QH_EXPLODEY"] = { text = "QuestHelper has broken. You may have to restart WoW. Type \"/qh warning\" for a detailed warning message.", button1 = OKAY, OnAccept = function(self) end, timeout = 0, whileDead = 1, hideOnEscape = 1 }; function QuestHelper_WarningCatcher_ExplicitWarning(loud, o_msg, o_frame, o_stack, ...) local msg = o_msg or "" -- We toss it into StartupWarnings, and then if we're running properly, we'll merge it into the main DB. local twarning = QuestHelper_WarningPackage() twarning.message = msg twarning.addons = QuestHelper_WarningCatcher.GetAddOns() twarning.stack = o_stack or twarning.stack twarning.silent = not loud twarning.quests = QuestHelper_WarningCatcher.GetQuests() QuestHelper_WarningCatcher_RegisterWarning("crash", twarning) if first_warning and first_warning.silent and not first_warning.next_loud and not twarning.silent then first_warning.next_loud = twarning first_warning.addons = "" end if not first_warning or first_warning.generated then first_warning = twarning end QuestHelper_WarningCatcher.CondenseWarnings() if (--[[debug_output or]] loud) and not yelled_at_user then --print("qhbroken") StaticPopupDialogs["QH_EXPLODEY"] = { text = "QuestHelper has broken. You may have to restart WoW. Type \"/qh warning\" for a detailed warning message.", button1 = OKAY, OnAccept = function(self) end, timeout = 0, whileDead = 1, hideOnEscape = 1 } StaticPopup_Show("QH_EXPLODEY") yelled_at_user = true end end function QuestHelper_WarningCatcher_GenerateReport() if first_warning then return end -- don't need to generate one local twarning = QuestHelper_WarningPackage() twarning.message = "(Full report)" twarning.addons = QuestHelper_WarningCatcher.GetAddOns() twarning.stack = "" twarning.silent = "(Full report)" twarning.generated = true twarning.quests = QuestHelper_WarningCatcher.GetQuests() first_warning = twarning end function QuestHelper_WarningCatcher.OnWarning(o_msg, o_frame, o_stack, o_etype, ...) local warningize = false local loud = false 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 for lin in string.gmatch(debugstack(2, 20, 20), "([^\n]*)") do if string.find(lin, "QuestHelper") and not string.find(lin, "QuestHelper\\AstrolabeQH\\DongleStub.lua") then warningize = true end end if string.find(o_msg, "SavedVariables") then warningize, loud = false, false end if string.find(o_msg, "C stack overflow") then if loud then warningize = true end loud = false end if loud then warningize = true end if warningize then QuestHelper_WarningCatcher_ExplicitWarning(loud, o_msg, o_frame, o_stack) end --[[ if o_msg and ( ( string.find(o_msg, "QuestHelper") -- Obviously we care about our bugs ) 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. ) ) 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. 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 warnings caused by other people mucking up their dongles. Ughh. then QuestHelper_WarningCatcher_ExplicitWarning(o_msg, o_frame, o_stack) end]] return origHandler(o_msg, o_frame, o_stack, o_etype, unpack(arg or {})) -- pass it on end setwarninghandler(QuestHelper_WarningCatcher.OnWarning) -- at this point we can catch warnings function QuestHelper_WarningCatcher.CompletelyStarted() completely_started = true -- 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. if not QuestHelper_Warnings.version or QuestHelper_Warnings.version ~= 1 then QuestHelper_Warnings = {version = 1} end QuestHelper_WarningCatcher.CondenseWarnings() end function QuestHelper_WarningCatcher_CompletelyStarted() QuestHelper_WarningCatcher.CompletelyStarted() end -- and here is the GUI local QHE_Gui = {} function QHE_Gui.WarningUpdate() QHE_Gui.WarningTextinate() QHE_Gui.Warning.Box:SetText(QHE_Gui.Warning.curWarning) QHE_Gui.Warning.Scroll:UpdateScrollChildRect() QHE_Gui.Warning.Box:ClearFocus() end function TextinateWarning(warn) local tswarn = string.format("msg: %s\ntoc: %s\nv: %s\ngame: %s\nlocale: %s\ntimestamp: %s\nmutation: %s\nsilent: %s\n\n%s\naddons:\n%s", warn.message, warn.toc_version, warn.local_version, warn.game_version, warn.locale, warn.timestamp, tostring(warn.mutation_passes_exceeded), tostring(warn.silent), warn.stack, warn.addons) if warn.next_loud then tswarn = tswarn .. "\n\n---- Following loud warning\n\n" .. TextinateWarning(warn.next_loud) end return tswarn end function QHE_Gui.WarningTextinate() if first_warning then QHE_Gui.Warning.curWarning = TextinateWarning(first_warning) else QHE_Gui.Warning.curWarning = "None" end end function QHE_Gui.WarningClicked() if (QHE_Gui.Warning.selected) then return end QHE_Gui.Warning.Box:HighlightText() QHE_Gui.Warning.selected = true end function QHE_Gui.WarningDone() QHE_Gui.Warning:Hide() end -- Create our warning message frame. Most of this is also ganked from Swatter. QHE_Gui.Warning = CreateFrame("Frame", "QHE_GUIWarningFrame", UIParent) QHE_Gui.Warning:Hide() QHE_Gui.Warning:SetPoint("CENTER", "UIParent", "CENTER") QHE_Gui.Warning:SetFrameStrata("TOOLTIP") QHE_Gui.Warning:SetHeight(300) QHE_Gui.Warning:SetWidth(600) QHE_Gui.Warning:SetBackdrop({ bgFile = "Interface/Tooltips/ChatBubble-Background", edgeFile = "Interface/Tooltips/ChatBubble-BackDrop", tile = true, tileSize = 32, edgeSize = 32, insets = { left = 32, right = 32, top = 32, bottom = 32 } }) QHE_Gui.Warning:SetBackdropColor(0.2,0,0, 1) QHE_Gui.Warning:SetScript("OnShow", QHE_Gui.WarningShow) QHE_Gui.Warning:SetMovable(true) QHE_Gui.ProxyFrame = CreateFrame("Frame", "QHE_GuiProxyFrame") QHE_Gui.ProxyFrame:SetParent(QHE_Gui.Warning) QHE_Gui.ProxyFrame.IsShown = function() return QHE_Gui.Warning:IsShown() end QHE_Gui.ProxyFrame.escCount = 0 QHE_Gui.ProxyFrame.timer = 0 QHE_Gui.ProxyFrame.Hide = ( function( self ) local numEscapes = QHE_Gui.numEscapes or 1 self.escCount = self.escCount + 1 if ( self.escCount >= numEscapes ) then self:GetParent():Hide() self.escCount = 0 end if ( self.escCount == 1 ) then self.timer = 0 end end ) QHE_Gui.ProxyFrame:SetScript("OnUpdate", function( self, elapsed ) local timer = self.timer + elapsed if ( timer >= 1 ) then self.escCount = 0 end self.timer = timer end ) table.insert(UISpecialFrames, "QHE_GuiProxyFrame") QHE_Gui.Drag = CreateFrame("Button", nil, QHE_Gui.Warning) QHE_Gui.Drag:SetPoint("TOPLEFT", QHE_Gui.Warning, "TOPLEFT", 10,-5) QHE_Gui.Drag:SetPoint("TOPRIGHT", QHE_Gui.Warning, "TOPRIGHT", -10,-5) QHE_Gui.Drag:SetHeight(8) QHE_Gui.Drag:SetHighlightTexture("Interface\\FriendsFrame\\UI-FriendsFrame-HighlightBar") QHE_Gui.Drag:SetScript("OnMouseDown", function() QHE_Gui.Warning:StartMoving() end) QHE_Gui.Drag:SetScript("OnMouseUp", function() QHE_Gui.Warning:StopMovingOrSizing() end) QHE_Gui.Warning.Done = CreateFrame("Button", "", QHE_Gui.Warning, "OptionsButtonTemplate") QHE_Gui.Warning.Done:SetText("Close") QHE_Gui.Warning.Done:SetPoint("BOTTOMRIGHT", QHE_Gui.Warning, "BOTTOMRIGHT", -10, 10) QHE_Gui.Warning.Done:SetScript("OnClick", QHE_Gui.WarningDone) QHE_Gui.Warning.Mesg = QHE_Gui.Warning:CreateFontString("", "OVERLAY", "GameFontNormalSmall") QHE_Gui.Warning.Mesg:SetJustifyH("LEFT") QHE_Gui.Warning.Mesg:SetPoint("TOPRIGHT", QHE_Gui.Warning.Prev, "TOPLEFT", -10, 0) QHE_Gui.Warning.Mesg:SetPoint("LEFT", QHE_Gui.Warning, "LEFT", 15, 0) QHE_Gui.Warning.Mesg:SetHeight(20) QHE_Gui.Warning.Mesg:SetText("Select All and Copy the above warning message to report this bug.") QHE_Gui.Warning.Scroll = CreateFrame("ScrollFrame", "QHE_GUIWarningInputScroll", QHE_Gui.Warning, "UIPanelScrollFrameTemplate") QHE_Gui.Warning.Scroll:SetPoint("TOPLEFT", QHE_Gui.Warning, "TOPLEFT", 20, -20) QHE_Gui.Warning.Scroll:SetPoint("RIGHT", QHE_Gui.Warning, "RIGHT", -30, 0) QHE_Gui.Warning.Scroll:SetPoint("BOTTOM", QHE_Gui.Warning.Done, "TOP", 0, 10) QHE_Gui.Warning.Box = CreateFrame("EditBox", "QHE_GUIWarningEditBox", QHE_Gui.Warning.Scroll) QHE_Gui.Warning.Box:SetWidth(500) QHE_Gui.Warning.Box:SetHeight(85) QHE_Gui.Warning.Box:SetMultiLine(true) QHE_Gui.Warning.Box:SetAutoFocus(false) QHE_Gui.Warning.Box:SetFontObject(GameFontHighlight) QHE_Gui.Warning.Box:SetScript("OnEscapePressed", QHE_Gui.WarningDone) QHE_Gui.Warning.Box:SetScript("OnTextChanged", QHE_Gui.WarningUpdate) QHE_Gui.Warning.Box:SetScript("OnEditFocusGained", QHE_Gui.WarningClicked) QHE_Gui.Warning.Scroll:SetScrollChild(QHE_Gui.Warning.Box) function QuestHelper_WarningCatcher_ReportWarning() QHE_Gui.Warning.selected = false QHE_Gui.WarningUpdate() QHE_Gui.Warning:Show() end