Quantcast

Use Ace3 for database management and associated cleanup

James Whitehead II [10-13-10 - 23:24]
Use Ace3 for database management and associated cleanup

  * Fix an issue where a macro could be created without a binding
  * Widen profile dropdowns
Filename
.pkgmeta
BindConfig.lua
Clique.lua
Clique.toc
Clique.xml
DatabaseDefaults.lua
OptionsPanel.lua
libs/AceDB-3.0/AceDB-3.0.lua
libs/AceDB-3.0/AceDB-3.0.xml
libs/CallbackHandler-1.0/CallbackHandler-1.0.lua
libs/CallbackHandler-1.0/CallbackHandler-1.0.xml
libs/LibStub/LibStub.lua
diff --git a/.pkgmeta b/.pkgmeta
new file mode 100644
index 0000000..30aca5f
--- /dev/null
+++ b/.pkgmeta
@@ -0,0 +1,5 @@
+enable-nolib-creation: no
+tools-used:
+    - Ace3
+    - LibStub
+    - CallbackHandler-1.0
diff --git a/BindConfig.lua b/BindConfig.lua
index cde555f..ba5e218 100755
--- a/BindConfig.lua
+++ b/BindConfig.lua
@@ -170,11 +170,7 @@ function CliqueConfig:Spellbook_OnBinding(button, key)
         icon = texture
     }

-    if not succ then
-        CliqueConfig:SetNotification(err)
-    else
-        CliqueConfig:UpdateList()
-    end
+    CliqueConfig:UpdateList()
 end

 function CliqueConfig:Button_OnClick(button)
@@ -212,8 +208,9 @@ function CliqueConfig:Button_OnClick(button)
                     config.page1:Hide()
                     config.page2.bindType = "macro"
                     -- Clear out the entries
-                    config.page2.bindText:SetText("")
+                    config.page2.bindText:SetText(L["No binding set"])
                     config.page2.editbox:SetText("")
+                    config.page2.button_save:Disable()
                     config.page2:Show()
                 end,
                 notCheckable = true,
@@ -245,12 +242,6 @@ function CliqueConfig:Button_OnClick(button)
     end
 end

-function CliqueConfig:SetNotification(text)
-end
-
-function CliqueConfig:ClearNotification()
-end
-
 local memoizeBindings = setmetatable({}, {__index = function(t, k, v)
     local binbits = addon:GetBinaryBindingKey(k)
     rawset(t, k, binbits)
@@ -392,6 +383,10 @@ function CliqueConfig:MacroBindingButton_OnClick(button, key)
     if key then
         self.page2.key = key
         self.page2.bindText:SetText(addon:GetBindingKeyComboText(key))
+        self.page2.button_save:Enable()
+    else
+        self.page2.bindText:SetText(L["No binding set"])
+        self.page2.button_save:Disable()
     end
 end

diff --git a/Clique.lua b/Clique.lua
index bbf65b7..1cc8543 100755
--- a/Clique.lua
+++ b/Clique.lua
@@ -40,7 +40,14 @@ local addonName, addon = ...
 local L = addon.L

 function addon:Initialize()
-    self:InitializeDatabase()
+    -- Create an AceDB, but it needs to be cleared first
+    self.db = LibStub("AceDB-3.0"):New("CliqueDB3", self.defaults)
+    self.db.RegisterCallback(self, "OnNewProfile", "OnNewProfile")
+    self.db.RegisterCallback(self, "OnProfileChanged", "OnProfileChanged")
+
+    self.settings = self.db.char
+    self.bindings = self.db.profile.bindings
+
     self.ccframes = {}
     self.hccframes = {}

@@ -120,23 +127,27 @@ function addon:Initialize()
     end
     self:EnableBlizzardFrames()

-    -- Trigger a profile change, updating all attributes
-    self:ChangeProfile()
-
     -- Register for combat events to ensure we can swap between the two states
     self:RegisterEvent("PLAYER_REGEN_DISABLED", "EnteringCombat")
     self:RegisterEvent("PLAYER_REGEN_ENABLED", "LeavingCombat")
-    self:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED", function()
-        self:ChangeProfile()
-    end)
+    self:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED", "TalentGroupChanged")
+
     -- Handle combat watching so we can change ooc based on party combat status
     addon:UpdateCombatWatch()
+
+    -- Trigger a 'TalentGroupChanged' so we end up on the right profile
+    addon:TalentGroupChanged()
+    addon:UpdateEverything()
 end

 function addon:RegisterFrame(button)
     self.ccframes[button] = true

-    button:RegisterForClicks("AnyDown")
+    if self.settings.downclick then
+        button:RegisterForClicks("AnyDown")
+    else
+        button:RegisterForClicks("AnyUp")
+    end

     -- Wrap the OnEnter/OnLeave scripts in order to handle keybindings
     addon.header:WrapScript(button, "OnEnter", addon.header:GetAttribute("setup_onenter"))
@@ -159,90 +170,29 @@ function addon:Enable()
     CliqueSpellTab.tooltip = L["Clique binding configuration"]
 end

--- Leave CliqueDB in place for now, to ease any migration that users might have.
--- Instead use CliqueDB2 for the active database and use versioning to move
--- forward from this point. The database consists of two sections:
---   * settings - used to handle the basic options Clique uses
---   * profiles - used for the binding configuration profiles, possibly shared
-local current_db_version = 6
-function addon:InitializeDatabase()
-    local realmKey = GetRealmName()
-    local charKey = UnitName("player") .. " - " .. realmKey
-    addon.staticProfileKey = charKey
-
-    local reset = false
-    if not CliqueDB2 then
-        reset = true
-    elseif CliqueDB2.dbversion == 5 then
-        if not CliqueDB2.settings or not CliqueDB2.settings[charKey] then
-            reset = true
-        else
-            -- Upgrade to add the blacklist table to settings
-            CliqueDB2.settings[charKey].blacklist = {}
-        end
-        CliqueDB2.dbversion = current_db_version
-    elseif CliqueDB2.dbversion ~= current_db_version then
-        reset = true
-    end
-
-    if reset then
-        CliqueDB2 = {
-            settings = {},
-            bindings = {},
-            dbversion = current_db_version,
-        }
-    end
-
-    local db = CliqueDB2
-
-    addon.db = db
-    if not db.settings[charKey] then
-        db.settings[charKey] = {
-            profileKey = charKey,
-            blacklist = {},
-            blizzframes = {
-                PlayerFrame = true,
-                PetFrame = true,
-                TargetFrame = true,
-                TargetFrameToT = true,
-                FocusFrame = true,
-                FocusFrameToT = true,
-                arena = true,
-                party = true,
-                compactraid = true,
-                compactparty = true,
-                boss = true,
-            },
-        }
-    end
-
-    addon.settings = db.settings[charKey]
-    self:InitializeBindingProfile()
+-- A new profile is being created in the db, called 'profile'
+function addon:OnNewProfile(event, db, profile)
+    table.insert(db.profile.bindings, {
+        key = "BUTTON1",
+        type = "target",
+        unit = "mouseover",
+        sets = {
+            default = true
+        },
+    })
+    table.insert(db.profile.bindings, {
+        key = "BUTTON2",
+        type = "menu",
+        sets = {
+            default = true
+        },
+    })
+    self.bindings = db.profile.bindings
 end

-function addon:InitializeBindingProfile()
-    local db = CliqueDB2
-    if not db.bindings[addon.settings.profileKey] then
-        db.bindings[addon.settings.profileKey] = {
-            [1] = {
-                key = "BUTTON1",
-                type = "target",
-                unit = "mouseover",
-                sets = {
-                    default = true
-                },
-            },
-            [2] = {
-                key = "BUTTON2",
-                type = "menu",
-                sets = {
-                    default = true
-                },
-            },
-        }
-    end
-
-    self.bindings = db.bindings[addon.settings.profileKey]
+function addon:OnProfileChanged(event, db, newProfile)
+    self.bindings = db.profile.bindings
+    self:UpdateEverything()
 end

 local function ATTR(prefix, attr, suffix, value)
@@ -412,7 +362,6 @@ end

 function addon:AddBinding(entry)
     -- TODO: Check to see if the new binding conflicts with an existing binding
-
     -- TODO: Validate the entry to ensure it has the correct arguments, etc.

     if not entry.sets then
@@ -420,9 +369,7 @@ function addon:AddBinding(entry)
     end

     table.insert(self.bindings, entry)
-
     self:UpdateAttributes()
-
     return true
 end

@@ -530,38 +477,23 @@ function addon:UpdateGlobalAttributes()
     globutton:Execute(globutton.setbinds)
 end

-function addon:ChangeProfile(profileName)
-    -- Clear the current profile
-    addon:ClearAttributes()
-    addon:ClearGlobalAttributes()
-
-    -- Check to see if this is a force-create of a new profile
-    if type(profileName) == "string" and #profileName > 0 then
-        -- Do nothing
-    else
-        -- Determine which profile to set, based on talent group
-        self.talentGroup = GetActiveTalentGroup()
-        if self.talentGroup == 1 and self.settings.pri_profileKey then
-            profileName = self.settings.pri_profileKey
-        elseif self.talentGroup == 2 and self.settings.sec_profileKey then
-            profileName = self.settings.sec_profileKey
-        end
+function addon:TalentGroupChanged()
+    local currentProfile = self.db:GetCurrentProfile()
+    local newProfile

-        if type(profileName) == "string" and addon.db.bindings[profileName] then
-            -- Do nothing
-        else
-            profileName = addon.staticProfileKey
-        end
+    -- Determine which profile to set, based on talent group
+    self.talentGroup = GetActiveTalentGroup()
+    if self.talentGroup == 1 and self.settings.pri_profileKey then
+        newProfile = self.settings.pri_profileKey
+    elseif self.talentGroup == 2 and self.settings.sec_profileKey then
+        newProfile = self.settings.sec_profileKey
     end

-    -- We've been given a profile name, so just change to it
-    addon.settings.profileKey = profileName
-    addon:InitializeBindingProfile()
-    addon:UpdateAttributes()
-    addon:UpdateGlobalAttributes()
-    addon:UpdateOptionsPanel()
+    if newProfile ~= currentProfile and type(newProfile) == "string" then
+        self.db:SetProfile(newProfile)
+    end

-    CliqueConfig:UpdateList()
+    self:UpdateEverything()
 end

 function addon:UpdateCombatWatch()
@@ -619,6 +551,14 @@ function addon:CheckPartyCombat(event, unit)
     end
 end

+function addon:UpdateEverything()
+    -- Update all running attributes and windows (block)
+    addon:UpdateAttributes()
+    addon:UpdateGlobalAttributes()
+    addon:UpdateOptionsPanel()
+    CliqueConfig:UpdateList()
+end
+
 SLASH_CLIQUE1 = "/clique"
 SlashCmdList["CLIQUE"] = function(msg, editbox)
     if SpellBookFrame:IsVisible() then
diff --git a/Clique.toc b/Clique.toc
index 5a230af..8423325 100755
--- a/Clique.toc
+++ b/Clique.toc
@@ -3,11 +3,17 @@
 ## Author: Cladhaire
 ## Version: @project-version@
 ## Notes: Simply powerful click-casting interface
-## SavedVariables: CliqueDB, CliqueDB2
+## SavedVariables: CliqueDB, CliqueDB3

 AddonCore.lua
 Localization.enUS.lua

+libs\LibStub\LibStub.lua
+libs\CallbackHandler-1.0\CallbackHandler-1.0.xml
+libs\AceDB-3.0\AceDB-3.0.xml
+
+DatabaseDefaults.lua
+
 ClickCastTemplate.xml

 Clique.xml
diff --git a/Clique.xml b/Clique.xml
index 4a7c38e..2db5b61 100755
--- a/Clique.xml
+++ b/Clique.xml
@@ -4,7 +4,7 @@
         <Size x="298" y="30"/>
         <Layers>
             <Layer level="BORDER">
-                <Texture name="$parentIcon" parentKey="icon" file="Interface\Icons\Ability_Druid_Nourish">
+                <Texture name="$parentIcon" parentKey="icon" file="Interface\Icons\INV_Misc_QuestionMark">
                     <Size x="25" y="25"/>
                     <Anchors>
                         <Anchor point="LEFT">
@@ -14,7 +14,7 @@
                         </Anchor>
                     </Anchors>
                 </Texture>
-                <FontString name="$parentName" parentKey="name" inherits="GameFontHighlight" justifyH="LEFT" text="Healing Touch">
+                <FontString name="$parentName" parentKey="name" inherits="GameFontHighlight" justifyH="LEFT" text="An error has occurred">
                     <Size>
                         <AbsDimension x="175" y="12"/>
                     </Size>
diff --git a/DatabaseDefaults.lua b/DatabaseDefaults.lua
new file mode 100644
index 0000000..3eb0917
--- /dev/null
+++ b/DatabaseDefaults.lua
@@ -0,0 +1,25 @@
+local addonName, addon = ...
+local L = addon.L
+
+addon.defaults = {
+    char = {
+        blacklist = {},
+        blizzframes = {
+            PlayerFrame = true,
+            PetFrame = true,
+            TargetFrame = true,
+            TargetFrameToT = true,
+            FocusFrame = true,
+            FocusFrameToT = true,
+            arena = true,
+            party = true,
+            compactraid = true,
+            compactparty = true,
+            boss = true,
+        },
+    },
+    profile = {
+        bindings = {
+        },
+    },
+}
diff --git a/OptionsPanel.lua b/OptionsPanel.lua
index 31e4f26..cfa3aa1 100644
--- a/OptionsPanel.lua
+++ b/OptionsPanel.lua
@@ -62,19 +62,19 @@ function panel:CreateOptions()
     self.prispeclabel = make_label("CliqueOptionsPriSpecLabel", self, "GameFontNormalSmall")
     self.prispeclabel:SetText(L["Primary talent spec profile:"])
     self.prispec = make_dropdown("CliqueOptionsPriSpec", self)
-    UIDropDownMenu_SetWidth(self.prispec, 150)
+    UIDropDownMenu_SetWidth(self.prispec, 200)
     BlizzardOptionsPanel_SetupDependentControl(self.specswap, self.prispec)

     self.secspeclabel = make_label("CliqueOptionsSecSpecLabel", self, "GameFontNormalSmall")
     self.secspeclabel:SetText(L["Secondary talent spec profile:"])
     self.secspec = make_dropdown("CliqueOptionsSecSpec", self)
-    UIDropDownMenu_SetWidth(self.secspec, 150)
+    UIDropDownMenu_SetWidth(self.secspec, 200)
     BlizzardOptionsPanel_SetupDependentControl(self.specswap, self.secspec)

     self.profilelabel = make_label("CliqueOptionsProfileMgmtLabel", self, "GameFontNormalSmall")
     self.profilelabel:SetText(L["Profile Management:"])
     self.profiledd = make_dropdown("CliqueOptionsProfileMgmt", self)
-    UIDropDownMenu_SetWidth(self.profiledd, 150)
+    UIDropDownMenu_SetWidth(self.profiledd, 200)

     -- Collect and anchor the bits together
     table.insert(bits, self.updown)
@@ -125,7 +125,7 @@ StaticPopupDialogs["CLIQUE_NEW_PROFILE"] = {
 		local base = self:GetName()
 		local editbox = getglobal(base .. "EditBox")
         local profileName = editbox:GetText()
-        addon:ChangeProfile(profileName)
+        addon.db:SetProfile(profileName)
 	end,
 	timeout = 0,
 	whileDead = 1,
@@ -143,7 +143,7 @@ StaticPopupDialogs["CLIQUE_NEW_PROFILE"] = {
             local base = self:GetParent():GetName()
             local editbox = _G[base .. "EditBox"]
             local profileName = editbox:GetText()
-            addon:ChangeProfile(profileName)
+            addon.db:SetProfile(profileName)
         end
         self:GetParent():Hide();
 	end,
@@ -163,9 +163,10 @@ StaticPopupDialogs["CLIQUE_NEW_PROFILE"] = {
 }

 local function getsorttbl()
+    local profiles = addon.db:GetProfiles()
     local sort = {}
-    for k, v in pairs(addon.db.bindings) do
-        table.insert(sort, k)
+    for idx, profileName in ipairs(profiles) do
+        table.insert(sort, profileName)
     end
     table.sort(sort)
     return sort
@@ -224,6 +225,7 @@ end
 local function mgmt_initialize(dropdown, level)
     local sort = getsorttbl()
     local paged = (#sort >= 15)
+    local currentProfile = addon.db:GetCurrentProfile()

     if not level or level == 1 then
         if not paged then
@@ -281,24 +283,25 @@ local function mgmt_initialize(dropdown, level)
             info.text = L["Select profile: %s"]:format(UIDROPDOWNMENU_MENU_VALUE)
             info.value = sort[UIDROPDOWNMENU_MENU_VALUE]
             info.notCheckable = true
-            info.disabled = addon.settings.specswap
+            -- Don't disable this, allow the user to make their own mistakes
+            --info.disabled = addon.settings.specswap
             info.func = function(frame)
                 UIDropDownMenu_SetSelectedValue(dropdown, UIDROPDOWNMENU_MENU_VALUE)
                 UIDropDownMenu_SetText(dropdown, UIDROPDOWNMENU_MENU_VALUE)
-                addon:ChangeProfile(UIDROPDOWNMENU_MENU_VALUE)
+                addon.db:SetProfile(UIDROPDOWNMENU_MENU_VALUE)
             end
             UIDropDownMenu_AddButton(info, level)

             info = UIDropDownMenu_CreateInfo()
             info.text = L["Delete profile: %s"]:format(UIDROPDOWNMENU_MENU_VALUE)
-            info.disabled = UIDROPDOWNMENU_MENU_VALUE == addon.settings.profileKey
+            info.disabled = UIDROPDOWNMENU_MENU_VALUE == currentProfile
             info.value = sort[UIDROPDOWNMENU_MENU_VALUE]
             info.notCheckable = true
             info.func = function(frame)
                 local dialog = StaticPopupDialogs["CLIQUE_CONFIRM_PROFILE_DELETE"]
                 dialog.text = L["Delete profile '%s'"]:format(UIDROPDOWNMENU_MENU_VALUE)
                 dialog.OnAccept = function(self)
-                    addon.db.bindings[UIDROPDOWNMENU_MENU_VALUE] = nil
+                    addon.db:DeleteProfile(UIDROPDOWNMENU_MENU_VALUE)
                 end
                 HideDropDownMenu(1)
                 StaticPopup_Show("CLIQUE_CONFIRM_PROFILE_DELETE")
@@ -313,19 +316,19 @@ local function mgmt_initialize(dropdown, level)
         info.func = function(frame)
             UIDropDownMenu_SetSelectedValue(dropdown, UIDROPDOWNMENU_MENU_VALUE)
             UIDropDownMenu_SetText(dropdown, UIDROPDOWNMENU_MENU_VALUE)
-            addon:ChangeProfile(UIDROPDOWNMENU_MENU_VALUE)
+            addon.db:SetProfile(UIDROPDOWNMENU_MENU_VALUE)
         end
         UIDropDownMenu_AddButton(info, level)

         info = UIDropDownMenu_CreateInfo()
         info.text = L["Delete profile: %s"]:format(UIDROPDOWNMENU_MENU_VALUE)
-        info.disabled = UIDROPDOWNMENU_MENU_VALUE == addon.settings.profileKey
+        info.disabled = UIDROPDOWNMENU_MENU_VALUE == currentProfile
         info.value = sort[UIDROPDOWNMENU_MENU_VALUE]
         info.func = function(frame)
             local dialog = StaticPopupDialogs["CLIQUE_CONFIRM_PROFILE_DELETE"]
             dialog.text = L["Delete profile '%s'"]:format(UIDROPDOWNMENU_MENU_VALUE)
             dialog.OnAccept = function(self)
-                addon.db.bindings[UIDROPDOWNMENU_MENU_VALUE] = nil
+                addon.db:DeleteProfile(UIDROPDOWNMENU_MENU_VALUE)
             end
             HideDropDownMenu(1)
             StaticPopup_Show("CLIQUE_CONFIRM_PROFILE_DELETE")
@@ -338,18 +341,19 @@ end
 function panel.refresh()
     -- Initialize the dropdowns
     local settings = addon.settings
-
+    local currentProfile = addon.db:GetCurrentProfile()
+
     UIDropDownMenu_Initialize(panel.prispec, spec_initialize)
-    UIDropDownMenu_SetSelectedValue(panel.prispec, settings.pri_profileKey or settings.profileKey)
-    UIDropDownMenu_SetText(panel.prispec, settings.pri_profileKey or settings.profileKey)
+    UIDropDownMenu_SetSelectedValue(panel.prispec, settings.pri_profileKey or currentProfile)
+    UIDropDownMenu_SetText(panel.prispec, settings.pri_profileKey or currentProfile)

     UIDropDownMenu_Initialize(panel.secspec, spec_initialize)
-    UIDropDownMenu_SetSelectedValue(panel.secspec, settings.sec_profileKey or settings.profileKey)
-    UIDropDownMenu_SetText(panel.secspec, settings.sec_profileKey or settings.profileKey)
+    UIDropDownMenu_SetSelectedValue(panel.secspec, settings.sec_profileKey or currentProfile)
+    UIDropDownMenu_SetText(panel.secspec, settings.sec_profileKey or currentProfile)

     UIDropDownMenu_Initialize(panel.profiledd, mgmt_initialize)
-    UIDropDownMenu_SetSelectedValue(panel.profiledd, settings.profileKey)
-    UIDropDownMenu_SetText(panel.profiledd, settings.profileKey)
+    UIDropDownMenu_SetSelectedValue(panel.profiledd, currentProfile)
+    UIDropDownMenu_SetText(panel.profiledd, L["Current: "] .. currentProfile)

     panel.updown:SetChecked(settings.downclick)
     panel.fastooc:SetChecked(settings.fastooc)
@@ -359,6 +363,7 @@ end

 function panel.okay()
     local settings = addon.settings
+    local currentProfile = addon.db:GetCurrentProfile()

     -- Update the saved variables
     settings.downclick = not not panel.updown:GetChecked()
@@ -366,10 +371,11 @@ function panel.okay()
     settings.specswap = not not panel.specswap:GetChecked()
     settings.pri_profileKey = UIDropDownMenu_GetSelectedValue(panel.prispec)
     settings.sec_profileKey = UIDropDownMenu_GetSelectedValue(panel.secspec)
-    settings.profileKey = UIDropDownMenu_GetSelectedValue(panel.profiledd)

+    if newProfile ~= currentProfile then
+        addon.db:SetProfile(newProfile)
+    end
     addon:UpdateCombatWatch()
-    addon:ChangeProfile()
 end

 panel.cancel = panel.refresh
diff --git a/libs/AceDB-3.0/AceDB-3.0.lua b/libs/AceDB-3.0/AceDB-3.0.lua
new file mode 100644
index 0000000..7125643
--- /dev/null
+++ b/libs/AceDB-3.0/AceDB-3.0.lua
@@ -0,0 +1,728 @@
+--- **AceDB-3.0** manages the SavedVariables of your addon.
+-- It offers profile management, smart defaults and namespaces for modules.\\
+-- Data can be saved in different data-types, depending on its intended usage.
+-- The most common data-type is the `profile` type, which allows the user to choose
+-- the active profile, and manage the profiles of all of his characters.\\
+-- The following data types are available:
+-- * **char** Character-specific data. Every character has its own database.
+-- * **realm** Realm-specific data. All of the players characters on the same realm share this database.
+-- * **class** Class-specific data. All of the players characters of the same class share this database.
+-- * **race** Race-specific data. All of the players characters of the same race share this database.
+-- * **faction** Faction-specific data. All of the players characters of the same faction share this database.
+-- * **factionrealm** Faction and realm specific data. All of the players characters on the same realm and of the same faction share this database.
+-- * **global** Global Data. All characters on the same account share this database.
+-- * **profile** Profile-specific data. All characters using the same profile share this database. The user can control which profile should be used.
+--
+-- Creating a new Database using the `:New` function will return a new DBObject. A database will inherit all functions
+-- of the DBObjectLib listed here. \\
+-- If you create a new namespaced child-database (`:RegisterNamespace`), you'll get a DBObject as well, but note
+-- that the child-databases cannot individually change their profile, and are linked to their parents profile - and because of that,
+-- the profile related APIs are not available. Only `:RegisterDefaults` and `:ResetProfile` are available on child-databases.
+--
+-- For more details on how to use AceDB-3.0, see the [[AceDB-3.0 Tutorial]].
+--
+-- You may also be interested in [[libdualspec-1-0|LibDualSpec-1.0]] to do profile switching automatically when switching specs.
+--
+-- @usage
+-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("DBExample")
+--
+-- -- declare defaults to be used in the DB
+-- local defaults = {
+--   profile = {
+--     setting = true,
+--   }
+-- }
+--
+-- function MyAddon:OnInitialize()
+--   -- Assuming the .toc says ## SavedVariables: MyAddonDB
+--   self.db = LibStub("AceDB-3.0"):New("MyAddonDB", defaults, true)
+-- end
+-- @class file
+-- @name AceDB-3.0.lua
+-- @release $Id: AceDB-3.0.lua 940 2010-06-19 08:01:47Z nevcairiel $
+local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 21
+local AceDB, oldminor = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR)
+
+if not AceDB then return end -- No upgrade needed
+
+-- Lua APIs
+local type, pairs, next, error = type, pairs, next, error
+local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget
+
+-- WoW APIs
+local _G = _G
+
+-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
+-- List them here for Mikk's FindGlobals script
+-- GLOBALS: LibStub
+
+AceDB.db_registry = AceDB.db_registry or {}
+AceDB.frame = AceDB.frame or CreateFrame("Frame")
+
+local CallbackHandler
+local CallbackDummy = { Fire = function() end }
+
+local DBObjectLib = {}
+
+--[[-------------------------------------------------------------------------
+	AceDB Utility Functions
+---------------------------------------------------------------------------]]
+
+-- Simple shallow copy for copying defaults
+local function copyTable(src, dest)
+	if type(dest) ~= "table" then dest = {} end
+	if type(src) == "table" then
+		for k,v in pairs(src) do
+			if type(v) == "table" then
+				-- try to index the key first so that the metatable creates the defaults, if set, and use that table
+				v = copyTable(v, dest[k])
+			end
+			dest[k] = v
+		end
+	end
+	return dest
+end
+
+-- Called to add defaults to a section of the database
+--
+-- When a ["*"] default section is indexed with a new key, a table is returned
+-- and set in the host table.  These tables must be cleaned up by removeDefaults
+-- in order to ensure we don't write empty default tables.
+local function copyDefaults(dest, src)
+	-- this happens if some value in the SV overwrites our default value with a non-table
+	--if type(dest) ~= "table" then return end
+	for k, v in pairs(src) do
+		if k == "*" or k == "**" then
+			if type(v) == "table" then
+				-- This is a metatable used for table defaults
+				local mt = {
+					-- This handles the lookup and creation of new subtables
+					__index = function(t,k)
+							if k == nil then return nil end
+							local tbl = {}
+							copyDefaults(tbl, v)
+							rawset(t, k, tbl)
+							return tbl
+						end,
+				}
+				setmetatable(dest, mt)
+				-- handle already existing tables in the SV
+				for dk, dv in pairs(dest) do
+					if not rawget(src, dk) and type(dv) == "table" then
+						copyDefaults(dv, v)
+					end
+				end
+			else
+				-- Values are not tables, so this is just a simple return
+				local mt = {__index = function(t,k) return k~=nil and v or nil end}
+				setmetatable(dest, mt)
+			end
+		elseif type(v) == "table" then
+			if not rawget(dest, k) then rawset(dest, k, {}) end
+			if type(dest[k]) == "table" then
+				copyDefaults(dest[k], v)
+				if src['**'] then
+					copyDefaults(dest[k], src['**'])
+				end
+			end
+		else
+			if rawget(dest, k) == nil then
+				rawset(dest, k, v)
+			end
+		end
+	end
+end
+
+-- Called to remove all defaults in the default table from the database
+local function removeDefaults(db, defaults, blocker)
+	-- remove all metatables from the db, so we don't accidentally create new sub-tables through them
+	setmetatable(db, nil)
+	-- loop through the defaults and remove their content
+	for k,v in pairs(defaults) do
+		if k == "*" or k == "**" then
+			if type(v) == "table" then
+				-- Loop through all the actual k,v pairs and remove
+				for key, value in pairs(db) do
+					if type(value) == "table" then
+						-- if the key was not explicitly specified in the defaults table, just strip everything from * and ** tables
+						if defaults[key] == nil and (not blocker or blocker[key] == nil) then
+							removeDefaults(value, v)
+							-- if the table is empty afterwards, remove it
+							if next(value) == nil then
+								db[key] = nil
+							end
+						-- if it was specified, only strip ** content, but block values which were set in the key table
+						elseif k == "**" then
+							removeDefaults(value, v, defaults[key])
+						end
+					end
+				end
+			elseif k == "*" then
+				-- check for non-table default
+				for key, value in pairs(db) do
+					if defaults[key] == nil and v == value then
+						db[key] = nil
+					end
+				end
+			end
+		elseif type(v) == "table" and type(db[k]) == "table" then
+			-- if a blocker was set, dive into it, to allow multi-level defaults
+			removeDefaults(db[k], v, blocker and blocker[k])
+			if next(db[k]) == nil then
+				db[k] = nil
+			end
+		else
+			-- check if the current value matches the default, and that its not blocked by another defaults table
+			if db[k] == defaults[k] and (not blocker or blocker[k] == nil) then
+				db[k] = nil
+			end
+		end
+	end
+end
+
+-- This is called when a table section is first accessed, to set up the defaults
+local function initSection(db, section, svstore, key, defaults)
+	local sv = rawget(db, "sv")
+
+	local tableCreated
+	if not sv[svstore] then sv[svstore] = {} end
+	if not sv[svstore][key] then
+		sv[svstore][key] = {}
+		tableCreated = true
+	end
+
+	local tbl = sv[svstore][key]
+
+	if defaults then
+		copyDefaults(tbl, defaults)
+	end
+	rawset(db, section, tbl)
+
+	return tableCreated, tbl
+end
+
+-- Metatable to handle the dynamic creation of sections and copying of sections.
+local dbmt = {
+	__index = function(t, section)
+			local keys = rawget(t, "keys")
+			local key = keys[section]
+			if key then
+				local defaultTbl = rawget(t, "defaults")
+				local defaults = defaultTbl and defaultTbl[section]
+
+				if section == "profile" then
+					local new = initSection(t, section, "profiles", key, defaults)
+					if new then
+						-- Callback: OnNewProfile, database, newProfileKey
+						t.callbacks:Fire("OnNewProfile", t, key)
+					end
+				elseif section == "profiles" then
+					local sv = rawget(t, "sv")
+					if not sv.profiles then sv.profiles = {} end
+					rawset(t, "profiles", sv.profiles)
+				elseif section == "global" then
+					local sv = rawget(t, "sv")
+					if not sv.global then sv.global = {} end
+					if defaults then
+						copyDefaults(sv.global, defaults)
+					end
+					rawset(t, section, sv.global)
+				else
+					initSection(t, section, section, key, defaults)
+				end
+			end
+
+			return rawget(t, section)
+		end
+}
+
+local function validateDefaults(defaults, keyTbl, offset)
+	if not defaults then return end
+	offset = offset or 0
+	for k in pairs(defaults) do
+		if not keyTbl[k] or k == "profiles" then
+			error(("Usage: AceDBObject:RegisterDefaults(defaults): '%s' is not a valid datatype."):format(k), 3 + offset)
+		end
+	end
+end
+
+local preserve_keys = {
+	["callbacks"] = true,
+	["RegisterCallback"] = true,
+	["UnregisterCallback"] = true,
+	["UnregisterAllCallbacks"] = true,
+	["children"] = true,
+}
+
+local realmKey = GetRealmName()
+local charKey = UnitName("player") .. " - " .. realmKey
+local _, classKey = UnitClass("player")
+local _, raceKey = UnitRace("player")
+local factionKey = UnitFactionGroup("player")
+local factionrealmKey = factionKey .. " - " .. realmKey
+-- Actual database initialization function
+local function initdb(sv, defaults, defaultProfile, olddb, parent)
+	-- Generate the database keys for each section
+
+	-- map "true" to our "Default" profile
+	if defaultProfile == true then defaultProfile = "Default" end
+
+	local profileKey
+	if not parent then
+		-- Make a container for profile keys
+		if not sv.profileKeys then sv.profileKeys = {} end
+
+		-- Try to get the profile selected from the char db
+		profileKey = sv.profileKeys[charKey] or defaultProfile or charKey
+
+		-- save the selected profile for later
+		sv.profileKeys[charKey] = profileKey
+	else
+		-- Use the profile of the parents DB
+		profileKey = parent.keys.profile or defaultProfile or charKey
+
+		-- clear the profileKeys in the DB, namespaces don't need to store them
+		sv.profileKeys = nil
+	end
+
+	-- This table contains keys that enable the dynamic creation
+	-- of each section of the table.  The 'global' and 'profiles'
+	-- have a key of true, since they are handled in a special case
+	local keyTbl= {
+		["char"] = charKey,
+		["realm"] = realmKey,
+		["class"] = classKey,
+		["race"] = raceKey,
+		["faction"] = factionKey,
+		["factionrealm"] = factionrealmKey,
+		["profile"] = profileKey,
+		["global"] = true,
+		["profiles"] = true,
+	}
+
+	validateDefaults(defaults, keyTbl, 1)
+
+	-- This allows us to use this function to reset an entire database
+	-- Clear out the old database
+	if olddb then
+		for k,v in pairs(olddb) do if not preserve_keys[k] then olddb[k] = nil end end
+	end
+
+	-- Give this database the metatable so it initializes dynamically
+	local db = setmetatable(olddb or {}, dbmt)
+
+	if not rawget(db, "callbacks") then
+		-- try to load CallbackHandler-1.0 if it loaded after our library
+		if not CallbackHandler then CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0", true) end
+		db.callbacks = CallbackHandler and CallbackHandler:New(db) or CallbackDummy
+	end
+
+	-- Copy methods locally into the database object, to avoid hitting
+	-- the metatable when calling methods
+
+	if not parent then
+		for name, func in pairs(DBObjectLib) do
+			db[name] = func
+		end
+	else
+		-- hack this one in
+		db.RegisterDefaults = DBObjectLib.RegisterDefaults
+		db.ResetProfile = DBObjectLib.ResetProfile
+	end
+
+	-- Set some properties in the database object
+	db.profiles = sv.profiles
+	db.keys = keyTbl
+	db.sv = sv
+	--db.sv_name = name
+	db.defaults = defaults
+	db.parent = parent
+
+	-- store the DB in the registry
+	AceDB.db_registry[db] = true
+
+	return db
+end
+
+-- handle PLAYER_LOGOUT
+-- strip all defaults from all databases
+-- and cleans up empty sections
+local function logoutHandler(frame, event)
+	if event == "PLAYER_LOGOUT" then
+		for db in pairs(AceDB.db_registry) do
+			db.callbacks:Fire("OnDatabaseShutdown", db)
+			db:RegisterDefaults(nil)
+
+			-- cleanup sections that are empty without defaults
+			local sv = rawget(db, "sv")
+			for section in pairs(db.keys) do
+				if rawget(sv, section) then
+					-- global is special, all other sections have sub-entrys
+					-- also don't delete empty profiles on main dbs, only on namespaces
+					if section ~= "global" and (section ~= "profiles" or rawget(db, "parent")) then
+						for key in pairs(sv[section]) do
+							if not next(sv[section][key]) then
+								sv[section][key] = nil
+							end
+						end
+					end
+					if not next(sv[section]) then
+						sv[section] = nil
+					end
+				end
+			end
+		end
+	end
+end
+
+AceDB.frame:RegisterEvent("PLAYER_LOGOUT")
+AceDB.frame:SetScript("OnEvent", logoutHandler)
+
+
+--[[-------------------------------------------------------------------------
+	AceDB Object Method Definitions
+---------------------------------------------------------------------------]]
+
+--- Sets the defaults table for the given database object by clearing any
+-- that are currently set, and then setting the new defaults.
+-- @param defaults A table of defaults for this database
+function DBObjectLib:RegisterDefaults(defaults)
+	if defaults and type(defaults) ~= "table" then
+		error("Usage: AceDBObject:RegisterDefaults(defaults): 'defaults' - table or nil expected.", 2)
+	end
+
+	validateDefaults(defaults, self.keys)
+
+	-- Remove any currently set defaults
+	if self.defaults then
+		for section,key in pairs(self.keys) do
+			if self.defaults[section] and rawget(self, section) then
+				removeDefaults(self[section], self.defaults[section])
+			end
+		end
+	end
+
+	-- Set the DBObject.defaults table
+	self.defaults = defaults
+
+	-- Copy in any defaults, only touching those sections already created
+	if defaults then
+		for section,key in pairs(self.keys) do
+			if defaults[section] and rawget(self, section) then
+				copyDefaults(self[section], defaults[section])
+			end
+		end
+	end
+end
+
+--- Changes the profile of the database and all of it's namespaces to the
+-- supplied named profile
+-- @param name The name of the profile to set as the current profile
+function DBObjectLib:SetProfile(name)
+	if type(name) ~= "string" then
+		error("Usage: AceDBObject:SetProfile(name): 'name' - string expected.", 2)
+	end
+
+	-- changing to the same profile, dont do anything
+	if name == self.keys.profile then return end
+
+	local oldProfile = self.profile
+	local defaults = self.defaults and self.defaults.profile
+
+	-- Callback: OnProfileShutdown, database
+	self.callbacks:Fire("OnProfileShutdown", self)
+
+	if oldProfile and defaults then
+		-- Remove the defaults from the old profile
+		removeDefaults(oldProfile, defaults)
+	end
+
+	self.profile = nil
+	self.keys["profile"] = name
+
+	-- if the storage exists, save the new profile
+	-- this won't exist on namespaces.
+	if self.sv.profileKeys then
+		self.sv.profileKeys[charKey] = name
+	end
+
+	-- populate to child namespaces
+	if self.children then
+		for _, db in pairs(self.children) do
+			DBObjectLib.SetProfile(db, name)
+		end
+	end
+
+	-- Callback: OnProfileChanged, database, newProfileKey
+	self.callbacks:Fire("OnProfileChanged", self, name)
+end
+
+--- Returns a table with the names of the existing profiles in the database.
+-- You can optionally supply a table to re-use for this purpose.
+-- @param tbl A table to store the profile names in (optional)
+function DBObjectLib:GetProfiles(tbl)
+	if tbl and type(tbl) ~= "table" then
+		error("Usage: AceDBObject:GetProfiles(tbl): 'tbl' - table or nil expected.", 2)
+	end
+
+	-- Clear the container table
+	if tbl then
+		for k,v in pairs(tbl) do tbl[k] = nil end
+	else
+		tbl = {}
+	end
+
+	local curProfile = self.keys.profile
+
+	local i = 0
+	for profileKey in pairs(self.profiles) do
+		i = i + 1
+		tbl[i] = profileKey
+		if curProfile and profileKey == curProfile then curProfile = nil end
+	end
+
+	-- Add the current profile, if it hasn't been created yet
+	if curProfile then
+		i = i + 1
+		tbl[i] = curProfile
+	end
+
+	return tbl, i
+end
+
+--- Returns the current profile name used by the database
+function DBObjectLib:GetCurrentProfile()
+	return self.keys.profile
+end
+
+--- Deletes a named profile.  This profile must not be the active profile.
+-- @param name The name of the profile to be deleted
+-- @param silent If true, do not raise an error when the profile does not exist
+function DBObjectLib:DeleteProfile(name, silent)
+	if type(name) ~= "string" then
+		error("Usage: AceDBObject:DeleteProfile(name): 'name' - string expected.", 2)
+	end
+
+	if self.keys.profile == name then
+		error("Cannot delete the active profile in an AceDBObject.", 2)
+	end
+
+	if not rawget(self.profiles, name) and not silent then
+		error("Cannot delete profile '" .. name .. "'. It does not exist.", 2)
+	end
+
+	self.profiles[name] = nil
+
+	-- populate to child namespaces
+	if self.children then
+		for _, db in pairs(self.children) do
+			DBObjectLib.DeleteProfile(db, name, true)
+		end
+	end
+
+	-- Callback: OnProfileDeleted, database, profileKey
+	self.callbacks:Fire("OnProfileDeleted", self, name)
+end
+
+--- Copies a named profile into the current profile, overwriting any conflicting
+-- settings.
+-- @param name The name of the profile to be copied into the current profile
+-- @param silent If true, do not raise an error when the profile does not exist
+function DBObjectLib:CopyProfile(name, silent)
+	if type(name) ~= "string" then
+		error("Usage: AceDBObject:CopyProfile(name): 'name' - string expected.", 2)
+	end
+
+	if name == self.keys.profile then
+		error("Cannot have the same source and destination profiles.", 2)
+	end
+
+	if not rawget(self.profiles, name) and not silent then
+		error("Cannot copy profile '" .. name .. "'. It does not exist.", 2)
+	end
+
+	-- Reset the profile before copying
+	DBObjectLib.ResetProfile(self, nil, true)
+
+	local profile = self.profile
+	local source = self.profiles[name]
+
+	copyTable(source, profile)
+
+	-- populate to child namespaces
+	if self.children then
+		for _, db in pairs(self.children) do
+			DBObjectLib.CopyProfile(db, name, true)
+		end
+	end
+
+	-- Callback: OnProfileCopied, database, sourceProfileKey
+	self.callbacks:Fire("OnProfileCopied", self, name)
+end
+
+--- Resets the current profile to the default values (if specified).
+-- @param noChildren if set to true, the reset will not be populated to the child namespaces of this DB object
+-- @param noCallbacks if set to true, won't fire the OnProfileReset callback
+function DBObjectLib:ResetProfile(noChildren, noCallbacks)
+	local profile = self.profile
+
+	for k,v in pairs(profile) do
+		profile[k] = nil
+	end
+
+	local defaults = self.defaults and self.defaults.profile
+	if defaults then
+		copyDefaults(profile, defaults)
+	end
+
+	-- populate to child namespaces
+	if self.children and not noChildren then
+		for _, db in pairs(self.children) do
+			DBObjectLib.ResetProfile(db, nil, noCallbacks)
+		end
+	end
+
+	-- Callback: OnProfileReset, database
+	if not noCallbacks then
+		self.callbacks:Fire("OnProfileReset", self)
+	end
+end
+
+--- Resets the entire database, using the string defaultProfile as the new default
+-- profile.
+-- @param defaultProfile The profile name to use as the default
+function DBObjectLib:ResetDB(defaultProfile)
+	if defaultProfile and type(defaultProfile) ~= "string" then
+		error("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or nil expected.", 2)
+	end
+
+	local sv = self.sv
+	for k,v in pairs(sv) do
+		sv[k] = nil
+	end
+
+	local parent = self.parent
+
+	initdb(sv, self.defaults, defaultProfile, self)
+
+	-- fix the child namespaces
+	if self.children then
+		if not sv.namespaces then sv.namespaces = {} end
+		for name, db in pairs(self.children) do
+			if not sv.namespaces[name] then sv.namespaces[name] = {} end
+			initdb(sv.namespaces[name], db.defaults, self.keys.profile, db, self)
+		end
+	end
+
+	-- Callback: OnDatabaseReset, database
+	self.callbacks:Fire("OnDatabaseReset", self)
+	-- Callback: OnProfileChanged, database, profileKey
+	self.callbacks:Fire("OnProfileChanged", self, self.keys["profile"])
+
+	return self
+end
+
+--- Creates a new database namespace, directly tied to the database.  This
+-- is a full scale database in it's own rights other than the fact that
+-- it cannot control its profile individually
+-- @param name The name of the new namespace
+-- @param defaults A table of values to use as defaults
+function DBObjectLib:RegisterNamespace(name, defaults)
+	if type(name) ~= "string" then
+		error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - string expected.", 2)
+	end
+	if defaults and type(defaults) ~= "table" then
+		error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'defaults' - table or nil expected.", 2)
+	end
+	if self.children and self.children[name] then
+		error ("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - a namespace with that name already exists.", 2)
+	end
+
+	local sv = self.sv
+	if not sv.namespaces then sv.namespaces = {} end
+	if not sv.namespaces[name] then
+		sv.namespaces[name] = {}
+	end
+
+	local newDB = initdb(sv.namespaces[name], defaults, self.keys.profile, nil, self)
+
+	if not self.children then self.children = {} end
+	self.children[name] = newDB
+	return newDB
+end
+
+--- Returns an already existing namespace from the database object.
+-- @param name The name of the new namespace
+-- @param silent if true, the addon is optional, silently return nil if its not found
+-- @usage
+-- local namespace = self.db:GetNamespace('namespace')
+-- @return the namespace object if found
+function DBObjectLib:GetNamespace(name, silent)
+	if type(name) ~= "string" then
+		error("Usage: AceDBObject:GetNamespace(name): 'name' - string expected.", 2)
+	end
+	if not silent and not (self.children and self.children[name]) then
+		error ("Usage: AceDBObject:GetNamespace(name): 'name' - namespace does not exist.", 2)
+	end
+	if not self.children then self.children = {} end
+	return self.children[name]
+end
+
+--[[-------------------------------------------------------------------------
+	AceDB Exposed Methods
+---------------------------------------------------------------------------]]
+
+--- Creates a new database object that can be used to handle database settings and profiles.
+-- By default, an empty DB is created, using a character specific profile.
+--
+-- You can override the default profile used by passing any profile name as the third argument,
+-- or by passing //true// as the third argument to use a globally shared profile called "Default".
+--
+-- Note that there is no token replacement in the default profile name, passing a defaultProfile as "char"
+-- will use a profile named "char", and not a character-specific profile.
+-- @param tbl The name of variable, or table to use for the database
+-- @param defaults A table of database defaults
+-- @param defaultProfile The name of the default profile. If not set, a character specific profile will be used as the default.
+-- You can also pass //true// to use a shared global profile called "Default".
+-- @usage
+-- -- Create an empty DB using a character-specific default profile.
+-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB")
+-- @usage
+-- -- Create a DB using defaults and using a shared default profile
+-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB", defaults, true)
+function AceDB:New(tbl, defaults, defaultProfile)
+	if type(tbl) == "string" then
+		local name = tbl
+		tbl = _G[name]
+		if not tbl then
+			tbl = {}
+			_G[name] = tbl
+		end
+	end
+
+	if type(tbl) ~= "table" then
+		error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'tbl' - table expected.", 2)
+	end
+
+	if defaults and type(defaults) ~= "table" then
+		error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaults' - table expected.", 2)
+	end
+
+	if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then
+		error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaultProfile' - string or true expected.", 2)
+	end
+
+	return initdb(tbl, defaults, defaultProfile)
+end
+
+-- upgrade existing databases
+for db in pairs(AceDB.db_registry) do
+	if not db.parent then
+		for name,func in pairs(DBObjectLib) do
+			db[name] = func
+		end
+	else
+		db.RegisterDefaults = DBObjectLib.RegisterDefaults
+		db.ResetProfile = DBObjectLib.ResetProfile
+	end
+end
diff --git a/libs/AceDB-3.0/AceDB-3.0.xml b/libs/AceDB-3.0/AceDB-3.0.xml
new file mode 100644
index 0000000..e825f7b
--- /dev/null
+++ b/libs/AceDB-3.0/AceDB-3.0.xml
@@ -0,0 +1,4 @@
+<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
+..\FrameXML\UI.xsd">
+	<Script file="AceDB-3.0.lua"/>
+</Ui>
\ No newline at end of file
diff --git a/libs/CallbackHandler-1.0/CallbackHandler-1.0.lua b/libs/CallbackHandler-1.0/CallbackHandler-1.0.lua
new file mode 100644
index 0000000..bc311d9
--- /dev/null
+++ b/libs/CallbackHandler-1.0/CallbackHandler-1.0.lua
@@ -0,0 +1,240 @@
+--[[ $Id: CallbackHandler-1.0.lua 965 2010-08-09 00:47:52Z mikk $ ]]
+local MAJOR, MINOR = "CallbackHandler-1.0", 6
+local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
+
+if not CallbackHandler then return end -- No upgrade needed
+
+local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end}
+
+-- Lua APIs
+local tconcat = table.concat
+local assert, error, loadstring = assert, error, loadstring
+local setmetatable, rawset, rawget = setmetatable, rawset, rawget
+local next, select, pairs, type, tostring = next, select, pairs, type, tostring
+
+-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
+-- List them here for Mikk's FindGlobals script
+-- GLOBALS: geterrorhandler
+
+local xpcall = xpcall
+
+local function errorhandler(err)
+	return geterrorhandler()(err)
+end
+
+local function CreateDispatcher(argCount)
+	local code = [[
+	local next, xpcall, eh = ...
+
+	local method, ARGS
+	local function call() method(ARGS) end
+
+	local function dispatch(handlers, ...)
+		local index
+		index, method = next(handlers)
+		if not method then return end
+		local OLD_ARGS = ARGS
+		ARGS = ...
+		repeat
+			xpcall(call, eh)
+			index, method = next(handlers, index)
+		until not method
+		ARGS = OLD_ARGS
+	end
+
+	return dispatch
+	]]
+
+	local ARGS, OLD_ARGS = {}, {}
+	for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end
+	code = code:gsub("OLD_ARGS", tconcat(OLD_ARGS, ", ")):gsub("ARGS", tconcat(ARGS, ", "))
+	return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler)
+end
+
+local Dispatchers = setmetatable({}, {__index=function(self, argCount)
+	local dispatcher = CreateDispatcher(argCount)
+	rawset(self, argCount, dispatcher)
+	return dispatcher
+end})
+
+--------------------------------------------------------------------------
+-- CallbackHandler:New
+--
+--   target            - target object to embed public APIs in
+--   RegisterName      - name of the callback registration API, default "RegisterCallback"
+--   UnregisterName    - name of the callback unregistration API, default "UnregisterCallback"
+--   UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API.
+
+function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused)
+	-- TODO: Remove this after beta has gone out
+	assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused")
+
+	RegisterName = RegisterName or "RegisterCallback"
+	UnregisterName = UnregisterName or "UnregisterCallback"
+	if UnregisterAllName==nil then	-- false is used to indicate "don't want this method"
+		UnregisterAllName = "UnregisterAllCallbacks"
+	end
+
+	-- we declare all objects and exported APIs inside this closure to quickly gain access
+	-- to e.g. function names, the "target" parameter, etc
+
+
+	-- Create the registry object
+	local events = setmetatable({}, meta)
+	local registry = { recurse=0, events=events }
+
+	-- registry:Fire() - fires the given event/message into the registry
+	function registry:Fire(eventname, ...)
+		if not rawget(events, eventname) or not next(events[eventname]) then return end
+		local oldrecurse = registry.recurse
+		registry.recurse = oldrecurse + 1
+
+		Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...)
+
+		registry.recurse = oldrecurse
+
+		if registry.insertQueue and oldrecurse==0 then
+			-- Something in one of our callbacks wanted to register more callbacks; they got queued
+			for eventname,callbacks in pairs(registry.insertQueue) do
+				local first = not rawget(events, eventname) or not next(events[eventname])	-- test for empty before. not test for one member after. that one member may have been overwritten.
+				for self,func in pairs(callbacks) do
+					events[eventname][self] = func
+					-- fire OnUsed callback?
+					if first and registry.OnUsed then
+						registry.OnUsed(registry, target, eventname)
+						first = nil
+					end
+				end
+			end
+			registry.insertQueue = nil
+		end
+	end
+
+	-- Registration of a callback, handles:
+	--   self["method"], leads to self["method"](self, ...)
+	--   self with function ref, leads to functionref(...)
+	--   "addonId" (instead of self) with function ref, leads to functionref(...)
+	-- all with an optional arg, which, if present, gets passed as first argument (after self if present)
+	target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]])
+		if type(eventname) ~= "string" then
+			error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2)
+		end
+
+		method = method or eventname
+
+		local first = not rawget(events, eventname) or not next(events[eventname])	-- test for empty before. not test for one member after. that one member may have been overwritten.
+
+		if type(method) ~= "string" and type(method) ~= "function" then
+			error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2)
+		end
+
+		local regfunc
+
+		if type(method) == "string" then
+			-- self["method"] calling style
+			if type(self) ~= "table" then
+				error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2)
+			elseif self==target then
+				error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2)
+			elseif type(self[method]) ~= "function" then
+				error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2)
+			end
+
+			if select("#",...)>=1 then	-- this is not the same as testing for arg==nil!
+				local arg=select(1,...)
+				regfunc = function(...) self[method](self,arg,...) end
+			else
+				regfunc = function(...) self[method](self,...) end
+			end
+		else
+			-- function ref with self=object or self="addonId" or self=thread
+			if type(self)~="table" and type(self)~="string" and type(self)~="thread" then
+				error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2)
+			end
+
+			if select("#",...)>=1 then	-- this is not the same as testing for arg==nil!
+				local arg=select(1,...)
+				regfunc = function(...) method(arg,...) end
+			else
+				regfunc = method
+			end
+		end
+
+
+		if events[eventname][self] or registry.recurse<1 then
+		-- if registry.recurse<1 then
+			-- we're overwriting an existing entry, or not currently recursing. just set it.
+			events[eventname][self] = regfunc
+			-- fire OnUsed callback?
+			if registry.OnUsed and first then
+				registry.OnUsed(registry, target, eventname)
+			end
+		else
+			-- we're currently processing a callback in this registry, so delay the registration of this new entry!
+			-- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency
+			registry.insertQueue = registry.insertQueue or setmetatable({},meta)
+			registry.insertQueue[eventname][self] = regfunc
+		end
+	end
+
+	-- Unregister a callback
+	target[UnregisterName] = function(self, eventname)
+		if not self or self==target then
+			error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2)
+		end
+		if type(eventname) ~= "string" then
+			error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2)
+		end
+		if rawget(events, eventname) and events[eventname][self] then
+			events[eventname][self] = nil
+			-- Fire OnUnused callback?
+			if registry.OnUnused and not next(events[eventname]) then
+				registry.OnUnused(registry, target, eventname)
+			end
+		end
+		if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then
+			registry.insertQueue[eventname][self] = nil
+		end
+	end
+
+	-- OPTIONAL: Unregister all callbacks for given selfs/addonIds
+	if UnregisterAllName then
+		target[UnregisterAllName] = function(...)
+			if select("#",...)<1 then
+				error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2)
+			end
+			if select("#",...)==1 and ...==target then
+				error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2)
+			end
+
+
+			for i=1,select("#",...) do
+				local self = select(i,...)
+				if registry.insertQueue then
+					for eventname, callbacks in pairs(registry.insertQueue) do
+						if callbacks[self] then
+							callbacks[self] = nil
+						end
+					end
+				end
+				for eventname, callbacks in pairs(events) do
+					if callbacks[self] then
+						callbacks[self] = nil
+						-- Fire OnUnused callback?
+						if registry.OnUnused and not next(callbacks) then
+							registry.OnUnused(registry, target, eventname)
+						end
+					end
+				end
+			end
+		end
+	end
+
+	return registry
+end
+
+
+-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it
+-- try to upgrade old implicit embeds since the system is selfcontained and
+-- relies on closures to work.
+
diff --git a/libs/CallbackHandler-1.0/CallbackHandler-1.0.xml b/libs/CallbackHandler-1.0/CallbackHandler-1.0.xml
new file mode 100644
index 0000000..1aad3a2
--- /dev/null
+++ b/libs/CallbackHandler-1.0/CallbackHandler-1.0.xml
@@ -0,0 +1,4 @@
+<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
+..\FrameXML\UI.xsd">
+	<Script file="CallbackHandler-1.0.lua"/>
+</Ui>
\ No newline at end of file
diff --git a/libs/LibStub/LibStub.lua b/libs/LibStub/LibStub.lua
new file mode 100644
index 0000000..cfc97de
--- /dev/null
+++ b/libs/LibStub/LibStub.lua
@@ -0,0 +1,30 @@
+-- LibStub is a simple versioning stub meant for use in Libraries.  http://www.wowace.com/wiki/LibStub for more info
+-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
+local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2  -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
+local LibStub = _G[LIBSTUB_MAJOR]
+
+if not LibStub or LibStub.minor < LIBSTUB_MINOR then
+	LibStub = LibStub or {libs = {}, minors = {} }
+	_G[LIBSTUB_MAJOR] = LibStub
+	LibStub.minor = LIBSTUB_MINOR
+
+	function LibStub:NewLibrary(major, minor)
+		assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
+		minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.")
+
+		local oldminor = self.minors[major]
+		if oldminor and oldminor >= minor then return nil end
+		self.minors[major], self.libs[major] = minor, self.libs[major] or {}
+		return self.libs[major], oldminor
+	end
+
+	function LibStub:GetLibrary(major, silent)
+		if not self.libs[major] and not silent then
+			error(("Cannot find a library instance of %q."):format(tostring(major)), 2)
+		end
+		return self.libs[major], self.minors[major]
+	end
+
+	function LibStub:IterateLibraries() return pairs(self.libs) end
+	setmetatable(LibStub, { __call = LibStub.GetLibrary })
+end