vanilla-wow-addons – Rev 1

Subversion Repositories:
Rev:
--[[
Name: AceDB-2.0
Revision: $Rev: 8838 $
Author(s): ckknight (ckknight@gmail.com)
Inspired By: AceDB 1.x by Turan (<email here>)
Website: http://www.wowace.com/
Documentation: http://wiki.wowace.com/index.php/AceDB-2.0
SVN: http://svn.wowace.com/root/trunk/Ace2/AceDB-2.0
Description: Mixin to allow for fast, clean, and featureful saved variable
             access.
Dependencies: AceLibrary, AceOO-2.0, AceEvent-2.0
]]

local MAJOR_VERSION = "AceDB-2.0"
local MINOR_VERSION = "$Revision: 8838 $"

if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end
if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end

if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end

-- localize --
local ACTIVE = "Active"
local STATE = "State"
local TOGGLE_ACTIVE = "Suspend/resume this addon"
local MAP_ACTIVESUSPENDED = { [true] = "|cff00ff00Active|r", [false] = "|cffff0000Suspended|r" }
local SET_PROFILE = "Set profile for this addon"
local SET_PROFILE_USAGE = "{char || class || realm || <profile name>}"
local PROFILE = "Profile"
local PLAYER_OF_REALM = "%s of %s"
-- localize --

local AceOO = AceLibrary("AceOO-2.0")
local AceEvent
local Mixin = AceOO.Mixin
local AceDB = Mixin {
                                                "RegisterDB",
                                                "RegisterDefaults",
                                                "ResetDB",
                                                "SetProfile",
                                                "GetProfile",
                                                "ToggleActive",
                                                "IsActive",
                                                "AcquireDBNamespace",
                                        }
local Dewdrop = AceLibrary:HasInstance("Dewdrop-2.0") and AceLibrary("Dewdrop-2.0")

local _G = getfenv(0)

local function inheritDefaults(t, defaults)
        if not defaults then
                return t
        end
        for k,v in pairs(defaults) do
                if k == "*" then
                        local v = v
                        if type(v) == "table" then
                                setmetatable(t, {
                                        __index = function(self, key)
                                                if key == nil then
                                                        return nil
                                                end
                                                self[key] = {}
                                                inheritDefaults(self[key], v)
                                                return self[key]
                                        end
                                } )
                        else
                                setmetatable(t, {
                                        __index = function(self, key)
                                                if key == nil then
                                                        return nil
                                                end
                                                self[key] = v
                                                return self[key]
                                        end
                                } )
                        end
                        for key in pairs(t) do
                                if (defaults[key] == nil or key == "*") and type(t[key]) == "table" then
                                        inheritDefaults(t[key], v)
                                end
                        end
                else
                        if type(v) == "table" then
                                if type(t[k]) ~= "table" then
                                        t[k] = {}
                                end
                                inheritDefaults(t[k], v)
                        elseif t[k] == nil then
                                t[k] = v
                        end
                end
        end
        return t
end

local _,race = UnitRace("player")
local faction
if race == "Orc" or race == "Scourge" or race == "Troll" or race == "Tauren" then
        faction = FACTION_HORDE
else
        faction = FACTION_ALLIANCE
end
local charID = string.format(PLAYER_OF_REALM, UnitName("player"), (string.gsub(GetRealmName(), "^%s*(.-)%s*$", "%1")))
local realm = string.gsub(GetRealmName(), "^%s*(.-)%s*$", "%1")
local realmID = realm .. " - " .. faction
local classID = UnitClass("player")

AceDB.CHAR_ID = charID
AceDB.REALM_ID = realmID
AceDB.CLASS_ID = classID

AceDB.FACTION = faction
AceDB.REALM = realm
AceDB.NAME = UnitName("player")

local new, del
do
        local list = setmetatable({}, {__mode="k"})
        function new()
                local t = next(list)
                if t then
                        list[t] = nil
                        return t
                else
                        return {}
                end
        end
        
        function del(t)
                setmetatable(t, nil)
                for k in pairs(t) do
                        t[k] = nil
                end
                table.setn(t, 0)
                list[t] = true
        end
end

local caseInsensitive_mt = {
        __index = function(self, key)
                if type(key) ~= "string" then
                        return nil
                end
                local lowerKey = string.lower(key)
                for k,v in pairs(self) do
                        if string.lower(k) == lowerKey then
                                return self[k]
                        end
                end
        end,
        __newindex = function(self, key, value)
                if type(key) ~= "string" then
                        return error("table index is nil", 2)
                end
                local lowerKey = string.lower(key)
                for k in pairs(self) do
                        if string.lower(k) == lowerKey then
                                rawset(self, k, nil)
                                rawset(self, key, value)
                                return
                        end
                end
                rawset(self, key, value)
        end
}

local db_mt = { __index = function(db, key)
        if key == "char" then
                if db.charName then
                        if type(_G[db.charName]) ~= "table" then
                                _G[db.charName] = {}
                        end
                        if type(_G[db.charName].global) ~= "table" then
                                _G[db.charName].global = {}
                        end
                        rawset(db, 'char', _G[db.charName].global)
                else
                        if type(db.raw.chars) ~= "table" then
                                db.raw.chars = {}
                        end
                        local id = charID
                        if type(db.raw.chars[id]) ~= "table" then
                                db.raw.chars[id] = {}
                        end
                        rawset(db, 'char', db.raw.chars[id])
                end
                if db.defaults and db.defaults.char then
                        inheritDefaults(db.char, db.defaults.char)
                end
                return db.char
        elseif key == "realm" then
                if type(db.raw.realms) ~= "table" then
                        db.raw.realms = {}
                end
                local id = realmID
                if type(db.raw.realms[id]) ~= "table" then
                        db.raw.realms[id] = {}
                end
                rawset(db, 'realm', db.raw.realms[id])
                if db.defaults and db.defaults.realm then
                        inheritDefaults(db.realm, db.defaults.realm)
                end
                return db.realm
        elseif key == "account" then
                if type(db.raw.account) ~= "table" then
                        db.raw.account = {}
                end
                rawset(db, 'account', db.raw.account)
                if db.defaults and db.defaults.account then
                        inheritDefaults(db.account, db.defaults.account)
                end
                return db.account
        elseif key == "class" then
                if type(db.raw.classes) ~= "table" then
                        db.raw.classes = {}
                end
                local id = classID
                if type(db.raw.classes[id]) ~= "table" then
                        db.raw.classes[id] = {}
                end
                rawset(db, 'class', db.raw.classes[id])
                if db.defaults and db.defaults.class then
                        inheritDefaults(db.class, db.defaults.class)
                end
                return db.class
        elseif key == "profile" then
                if type(db.raw.profiles) ~= "table" then
                        db.raw.profiles = setmetatable({}, caseInsensitive_mt)
                else
                        setmetatable(db.raw.profiles, caseInsensitive_mt)
                end
                local id = db.raw.currentProfile[charID]
                if id == "char" then
                        id = "char/" .. charID
                elseif id == "class" then
                        id = "class/" .. classID
                elseif id == "realm" then
                        id = "realm/" .. realmID
                end
                if type(db.raw.profiles[id]) ~= "table" then
                        db.raw.profiles[id] = {}
                end
                rawset(db, 'profile', db.raw.profiles[id])
                if db.defaults and db.defaults.profile then
                        inheritDefaults(db.profile, db.defaults.profile)
                end
                return db.profile
        elseif key == "raw" or key == "defaults" or key == "name" or key == "charName" or key == "namespaces" then
                return nil
        end
        error(string.format('Cannot access key %q in db table. You may want to use db.profile[%q]', tostring(key), tostring(key)), 2)
end, __newindex = function(db, key, value)
        error(string.format('Cannot access key %q in db table. You may want to use db.profile[%q]', tostring(key), tostring(key)), 2)
end }

local CrawlForSerialization
local CrawlForDeserialization

local function SerializeObject(o)
        local t = { o:Serialize() }
        t[0] = o.class:GetLibraryVersion()
        CrawlForSerialization(t)
        return t
end

local function DeserializeObject(t)
        CrawlForDeserialization(t)
        local className = t[0]
        for i = 20, 1, -1 do
                if t[i] then
                        t.n = i
                        break
                end
        end
        local o = AceLibrary(className):Deserialize(unpack(t))
        t.n = 0
        return o
end

local function IsSerializable(t)
        return AceOO.inherits(t, AceOO.Class) and t.class and type(t.class.Deserialize) == "function" and type(t.Serialize) == "function" and type(t.class.GetLibraryVersion) == "function"
end

function CrawlForSerialization(t)
        local tmp = new()
        for k,v in pairs(t) do
                tmp[k] = v
        end
        for k,v in pairs(tmp) do
                if type(v) == "table" and type(v[0]) ~= "userdata" then
                        if IsSerializable(v) then
                                v = SerializeObject(v)
                                t[k] = v
                        else
                                CrawlForSerialization(v)
                        end
                end
                if type(k) == "table" and type(k[0]) ~= "userdata" then
                        if IsSerializable(k) then
                                t[k] = nil
                                t[SerializeObject(k)] = v
                        else
                                CrawlForSerialization(k)
                        end
                end
                tmp[k] = nil
                k = nil
        end
        tmp = del(tmp)
end

local function IsDeserializable(t)
        return type(t[0]) == "string" and AceLibrary:HasInstance(t[0])
end

function CrawlForDeserialization(t)
        local tmp = new()
        for k,v in pairs(t) do
                tmp[k] = v
        end
        for k,v in pairs(tmp) do
                if type(v) == "table" then
                        if IsDeserializable(v) then
                                t[k] = DeserializeObject(v)
                                del(v)
                                v = t[k]
                        elseif type(v[0]) ~= "userdata" then
                                CrawlForDeserialization(v)
                        end
                end
                if type(k) == "table" then
                        if IsDeserializable(k) then
                                t[k] = nil
                                t[DeserializeObject(k)] = v
                                del(k)
                        elseif type(k[0]) ~= "userdata" then
                                CrawlForDeserialization(k)
                        end
                end
                tmp[k] = nil
                k = nil
        end
        tmp = del(tmp)
end

local namespace_mt = { __index = function(namespace, key)
        local db = namespace.db
        local name = namespace.name
        if key == "char" then
                if db.charName then
                        if type(_G[db.charName]) ~= "table" then
                                _G[db.charName] = {}
                        end
                        if type(_G[db.charName].namespaces) ~= "table" then
                                _G[db.charName].namespaces = {}
                        end
                        if type(_G[db.charName].namespaces[name]) ~= "table" then
                                _G[db.charName].namespaces[name] = {}
                        end
                        rawset(namespace, 'char', _G[db.charName].namespaces[name])
                else
                        if type(db.raw.namespaces) ~= "table" then
                                db.raw.namespaces = {}
                        end
                        if type(db.raw.namespaces[name]) ~= "table" then
                                db.raw.namespaces[name] = {}
                        end
                        if type(db.raw.namespaces[name].chars) ~= "table" then
                                db.raw.namespaces[name].chars = {}
                        end
                        local id = charID
                        if type(db.raw.namespaces[name].chars[id]) ~= "table" then
                                db.raw.namespaces[name].chars[id] = {}
                        end
                        rawset(namespace, 'char', db.raw.namespaces[name].chars[id])
                end
                if namespace.defaults and namespace.defaults.char then
                        inheritDefaults(namespace.char, namespace.defaults.char)
                end
                return namespace.char
        elseif key == "realm" then
                if type(db.raw.namespaces) ~= "table" then
                        db.raw.namespaces = {}
                end
                if type(db.raw.namespaces[name]) ~= "table" then
                        db.raw.namespaces[name] = {}
                end
                if type(db.raw.namespaces[name].realms) ~= "table" then
                        db.raw.namespaces[name].realms = {}
                end
                local id = realmID
                if type(db.raw.namespaces[name].realms[id]) ~= "table" then
                        db.raw.namespaces[name].realms[id] = {}
                end
                rawset(namespace, 'realm', db.raw.namespaces[name].realms[id])
                if namespace.defaults and namespace.defaults.realm then
                        inheritDefaults(namespace.realm, namespace.defaults.realm)
                end
                return namespace.realm
        elseif key == "account" then
                if type(db.raw.namespaces) ~= "table" then
                        db.raw.namespaces = {}
                end
                if type(db.raw.namespaces[name]) ~= "table" then
                        db.raw.namespaces[name] = {}
                end
                if type(db.raw.namespaces[name].account) ~= "table" then
                        db.raw.namespaces[name].account = {}
                end
                rawset(namespace, 'account', db.raw.namespaces[name].account)
                if namespace.defaults and namespace.defaults.account then
                        inheritDefaults(namespace.account, namespace.defaults.account)
                end
                return namespace.account
        elseif key == "class" then
                if type(db.raw.namespaces) ~= "table" then
                        db.raw.namespaces = {}
                end
                if type(db.raw.namespaces[name]) ~= "table" then
                        db.raw.namespaces[name] = {}
                end
                if type(db.raw.namespaces[name].classes) ~= "table" then
                        db.raw.namespaces[name].classes = {}
                end
                local id = classID
                if type(db.raw.namespaces[name].classes[id]) ~= "table" then
                        db.raw.namespaces[name].classes[id] = {}
                end
                rawset(namespace, 'class', db.raw.namespaces[name].classes[id])
                if namespace.defaults and namespace.defaults.class then
                        inheritDefaults(namespace.class, namespace.defaults.class)
                end
                return namespace.class
        elseif key == "profile" then
                if type(db.raw.namespaces) ~= "table" then
                        db.raw.namespaces = {}
                end
                if type(db.raw.namespaces[name]) ~= "table" then
                        db.raw.namespaces[name] = {}
                end
                if type(db.raw.namespaces[name].profiles) ~= "table" then
                        db.raw.namespaces[name].profiles = setmetatable({}, caseInsensitive_mt)
                else
                        setmetatable(db.raw.namespaces[name].profiles, caseInsensitive_mt)
                end
                local id = db.raw.currentProfile[charID]
                if id == "char" then
                        id = "char/" .. charID
                elseif id == "class" then
                        id = "class/" .. classID
                elseif id == "realm" then
                        id = "realm/" .. realmID
                end
                if type(db.raw.namespaces[name].profiles[id]) ~= "table" then
                        db.raw.namespaces[name].profiles[id] = {}
                end
                rawset(namespace, 'profile', db.raw.namespaces[name].profiles[id])
                if namespace.defaults and namespace.defaults.profile then
                        inheritDefaults(namespace.profile, namespace.defaults.profile)
                end
                return namespace.profile
        elseif key == "defaults" or key == "name" or key == "db" then
                return nil
        end
        error(string.format('Cannot access key %q in db table. You may want to use db.profile[%q]', tostring(key), tostring(key)), 2)
end, __newindex = function(db, key, value)
        error(string.format('Cannot access key %q in db table. You may want to use db.profile[%q]', tostring(key), tostring(key)), 2)
end }

function AceDB:InitializeDB(addonName)
        local db = self.db
        
        if not db then
                if addonName then
                        AceDB.addonsLoaded[addonName] = true
                end
                return
        end
        
        if db.raw then
                -- someone manually initialized
                return
        end
        
        if type(_G[db.name]) ~= "table" then
                _G[db.name] = {}
        else
                CrawlForDeserialization(_G[db.name])
        end
        if type(_G[db.charName]) == "table" then
                CrawlForDeserialization(_G[db.charName])
        end
        rawset(db, 'raw', _G[db.name])
        if not db.raw.currentProfile then
                db.raw.currentProfile = {}
        end
        if not db.raw.currentProfile[charID] then
                db.raw.currentProfile[charID] = "Default"
        end
        if db.raw.disabled then
                setmetatable(db.raw.disabled, caseInsensitive_mt)
        end
        if self['acedb-profile-copylist'] then
                local t = self['acedb-profile-copylist']
                for k,v in pairs(t) do
                        t[k] = nil
                end
                if db.raw.profiles then
                        for k in pairs(db.raw.profiles) do
                                if string.find(k, '^char/') then
                                        local name = string.sub(k, 6)
                                        if name ~= charID then
                                                t[k] =  'Character: ' .. name
                                        end
                                elseif string.find(k, '^realm/') then
                                        local name = string.sub(k, 7)
                                        if name ~= realmID then
                                                t[k] =  'Realm: ' .. name
                                        end
                                elseif string.find(k, '^class/') then
                                        local name = string.sub(k, 7)
                                        if name ~= classID then
                                                t[k] =  'Class: ' .. name
                                        end
                                end
                        end
                end
        end
        if self['acedb-profile-list'] then
                local t = self['acedb-profile-list']
                for k,v in pairs(t) do
                        t[k] = nil
                end
                t.char = 'Character: ' .. charID
                t.realm = 'Realm: ' .. realmID
                t.class = 'Class: ' .. classID
                t.Default = "Default"
                if db.raw.profiles then
                        for k in pairs(db.raw.profiles) do
                                if not string.find(k, '^char/') and not string.find(k, '^realm/') and not string.find(k, '^class/') then
                                        t[k] = k
                                end
                        end
                end
        end
        setmetatable(db, db_mt)
end

function AceDB:OnEmbedInitialize(target, name)
        if name then
                self:ADDON_LOADED(name)
        end
        self.InitializeDB(target, name)
end

function AceDB:RegisterDB(name, charName)
        AceDB:argCheck(name, 2, "string")
        AceDB:argCheck(charName, 3, "string", "nil")
        if self.db then
                AceDB:error("Cannot call \"RegisterDB\" if self.db is set.")
        end
        local stack = debugstack()
        local addonName = string.gsub(stack, ".-\n.-\\AddOns\\(.-)\\.*", "%1")
        self.db = {
                name = name,
                charName = charName
        }
        if AceDB.addonsLoaded[addonName] then
                AceDB.InitializeDB(self, addonName)
        else
                AceDB.addonsToBeInitialized[self] = addonName
        end
        AceDB.registry[self] = true
end

function AceDB:RegisterDefaults(kind, defaults, a3)
        local name
        if a3 then
                name, kind, defaults = kind, defaults, a3
                AceDB:argCheck(name, 2, "string")
                AceDB:argCheck(kind, 3, "string")
                AceDB:argCheck(defaults, 4, "table")
                if kind ~= "char" and kind ~= "class" and kind ~= "profile" and kind ~= "account" and kind ~= "realm" then
                        AceDB:error("Bad argument #3 to `RegisterDefaults' (\"char\", \"class\", \"profile\", \"account\", or \"realm\" expected, got %q)", kind)
                end
        else
                AceDB:argCheck(kind, 2, "string")
                AceDB:argCheck(defaults, 3, "table")
                if kind ~= "char" and kind ~= "class" and kind ~= "profile" and kind ~= "account" and kind ~= "realm" then
                        AceDB:error("Bad argument #2 to `RegisterDefaults' (\"char\", \"class\", \"profile\", \"account\", or \"realm\" expected, got %q)", kind)
                end
        end
        if type(self.db) ~= "table" or type(self.db.name) ~= "string" then
                AceDB:error("Cannot call \"RegisterDefaults\" unless \"RegisterDB\" has been previously called.")
        end
        local db
        if name then
                local namespace = self:AcquireDBNamespace(name)
                if namespace.defaults and namespace.defaults[kind] then
                        AceDB:error("\"RegisterDefaults\" has already been called for %q::%q.", name, kind)
                end
                db = namespace
        else
                if self.db.defaults and self.db.defaults[kind] then
                        AceDB:error("\"RegisterDefaults\" has already been called for %q.", kind)
                end
                db = self.db
        end
        if not db.defaults then
                rawset(db, 'defaults', {})
        end
        db.defaults[kind] = defaults
        if rawget(db, kind) then
                inheritDefaults(db[kind], defaults)
        end
end

function AceDB:ResetDB(kind)
        AceDB:argCheck(kind, 2, "nil", "string")
        if not self.db or not self.db.raw then
                AceDB:error("Cannot call \"ResetDB\" before \"RegisterDB\" has been called and before \"ADDON_LOADED\" has been fired.")
        end
        local db = self.db
        if kind == nil then
                if db.charName then
                        _G[db.charName] = nil
                end
                _G[db.name] = nil
                rawset(db, 'raw', nil)
                AceDB.InitializeDB(self)
                if db.namespaces then
                        for name,v in pairs(db.namespaces) do
                                rawset(v, 'account', nil)
                                rawset(v, 'char', nil)
                                rawset(v, 'class', nil)
                                rawset(v, 'profile', nil)
                                rawset(v, 'realm', nil)
                        end
                end
        elseif kind == "account" then
                db.raw.account = nil
                rawset(db, 'account', nil)
                if db.namespaces then
                        for name,v in pairs(db.namespaces) do
                                rawset(v, 'account', nil)
                        end
                end
        elseif kind == "char" then
                if db.charName then
                        _G[db.charName] = nil
                else
                        if db.raw.chars then
                                db.raw.chars[charID] = nil
                        end
                        if db.raw.namespaces then
                                for name,v in pairs(db.raw.namespaces) do
                                        if v.chars then
                                                v.chars[charID] = nil
                                        end
                                end
                        end
                end
                rawset(db, 'char', nil)
                if db.namespaces then
                        for name,v in pairs(db.namespaces) do
                                rawset(v, 'char', nil)
                        end
                end
        elseif kind == "realm" then
                if db.raw.realms then
                        db.raw.realms[realmID] = nil
                end
                rawset(db, 'realm', nil)
                if db.raw.namespaces then
                        for name,v in pairs(db.raw.namespaces) do
                                if v.realms then
                                        v.realms[realmID] = nil
                                end
                        end
                end
                if db.namespaces then
                        for name,v in pairs(db.namespaces) do
                                rawset(v, 'realm', nil)
                        end
                end
        elseif kind == "class" then
                if db.raw.realms then
                        db.raw.realms[classID] = nil
                end
                rawset(db, 'class', nil)
                if db.raw.namespaces then
                        for name,v in pairs(db.raw.namespaces) do
                                if v.classes then
                                        v.classes[classID] = nil
                                end
                        end
                end
                if db.namespaces then
                        for name,v in pairs(db.namespaces) do
                                rawset(v, 'class', nil)
                        end
                end
        elseif kind == "profile" then
                local id = db.raw.currentProfile and db.raw.currentProfile[charID] or "Default"
                if id == "char" then
                        id = "char/" .. charID
                elseif id == "class" then
                        id = "class/" .. classID
                elseif id == "realm" then
                        id = "realm/" .. realmID
                end
                if db.raw.profiles then
                        db.raw.profiles[id] = nil
                end
                rawset(db, 'profile', nil)
                if db.raw.namespaces then
                        for name,v in pairs(db.raw.namespaces) do
                                if v.profiles then
                                        v.profiles[id] = nil
                                end
                        end
                end
                if db.namespaces then
                        for name,v in pairs(db.namespaces) do
                                rawset(v, 'profile', nil)
                        end
                end
        end
end

local function cleanDefaults(t, defaults)
        if defaults then
                for k,v in pairs(defaults) do
                        if k == "*" then
                                if type(v) == "table" then
                                        for k in pairs(t) do
                                                if (defaults[k] == nil or k == "*") and type(t[k]) == "table" then
                                                        if cleanDefaults(t[k], v) then
                                                                t[k] = nil
                                                        end
                                                end
                                        end
                                else
                                        for k in pairs(t) do
                                                if (defaults[k] == nil or k == "*") and t[k] == v then
                                                        t[k] = nil
                                                end
                                        end
                                end
                        else
                                if type(v) == "table" then
                                        if type(t[k]) == "table" then
                                                if cleanDefaults(t[k], v) then
                                                        t[k] = nil
                                                end
                                        end
                                elseif t[k] == v then
                                        t[k] = nil
                                end
                        end
                end
        end
        return t and not next(t)
end

function AceDB:GetProfile()
        if not self.db or not self.db.raw then
                return nil
        end
        if not self.db.raw.currentProfile then
                self.db.raw.currentProfile = {}
        end
        if not self.db.raw.currentProfile[charID] then
                self.db.raw.currentProfile[charID] = "Default"
        end
        local profile = self.db.raw.currentProfile[charID]
        if profile == "char" then
                return "char", "char/" .. charID
        elseif profile == "class" then
                return "class", "class/" .. classID
        elseif profile == "realm" then
                return "realm", "realm/" .. realmID
        end
        return profile, profile
end

local function copyTable(to, from)
        setmetatable(to, nil)
        for k,v in pairs(from) do
                if type(k) == "table" then
                        k = copyTable({}, k)
                end
                if type(v) == "table" then
                        v = copyTable({}, v)
                end
                to[k] = v
        end
        table.setn(to, table.getn(from))
        setmetatable(to, from)
        return to
end

function AceDB:SetProfile(name, copyFrom)
        AceDB:argCheck(name, 2, "string")
        AceDB:argCheck(copyFrom, 3, "string", "nil")
        if not self.db or not self.db.raw then
                AceDB:error("Cannot call \"SetProfile\" before \"RegisterDB\" has been called and before \"ADDON_LOADED\" has been fired.")
        end
        local db = self.db
        local copy = false
        local lowerName = string.lower(name)
        local lowerCopyFrom = copyFrom and string.lower(copyFrom)
        if string.sub(lowerName, 1, 5) == "char/" or string.sub(lowerName, 1, 6) == "realm/" or string.sub(lowerName, 1, 6) == "class/" then
                if string.sub(lowerName, 1, 5) == "char/" then
                        name, copyFrom = "char", name
                else
                        name, copyFrom = string.sub(lowerName, 1, 5), name
                end
                lowerName = string.lower(name)
                lowerCopyFrom = string.lower(copyFrom)
        end
        if copyFrom then
                if string.sub(lowerCopyFrom, 1, 5) == "char/" then
                        AceDB:assert(lowerName == "char", "If argument #3 starts with `char/', argument #2 must be `char'")
                elseif string.sub(lowerCopyFrom, 1, 6) == "realm/" then
                        AceDB:assert(lowerName == "realm", "If argument #3 starts with `realm/', argument #2 must be `realm'")
                elseif string.sub(lowerCopyFrom, 1, 6) == "class/" then
                        AceDB:assert(lowerName == "class", "If argument #3 starts with `class/', argument #2 must be `class'")
                else
                        AceDB:assert(lowerName ~= "char" and lowerName ~= "realm" and lowerName ~= "class", "If argument #3 does not start with a special prefix, that prefix cannot be copied to.")
                end
                if not db.raw.profiles or not db.raw.profiles[copyFrom] then
                        AceDB:error("Cannot copy profile %q, it does not exist.", copyFrom)
                elseif (string.sub(lowerName, 1, 5) == "char/" and string.sub(lowerName, 6) == string.lower(charID)) or (string.sub(lowerName, 1, 6) == "realm/" and string.sub(lowerName, 7) == string.lower(realmID)) or (string.sub(lowerName, 1, 6) == "class/" and string.sub(lowerName, 7) == string.lower(classID)) then
                        AceDB:error("Cannot copy profile %q, it is currently in use.", name)
                end
        end
        local oldName = db.raw.currentProfile[charID]
        if string.lower(oldName) == string.lower(name) then
                return
        end
        local current = self.class
        while current and current ~= AceOO.Class do
                if current.mixins then
                        for mixin in pairs(current.mixins) do
                                if type(mixin.OnEmbedProfileDisable) == "function" then
                                        mixin:OnEmbedProfileDisable(self)
                                end
                        end
                end
                current = current.super
        end
        if type(self.OnProfileDisable) == "function" then
                self:OnProfileDisable()
        end
        local oldProfileData = db.profile
        local realName = name
        if lowerName == "char" then
                realName = name .. "/" .. charID
        elseif lowerName == "realm/" then
                realName = name .. "/" .. realmID
        elseif lowerName == "class/" then
                realName = name .. "/" .. classID
        end
        local active = self:IsActive()
        db.raw.currentProfile[charID] = name
        rawset(db, 'profile', nil)
        if copyFrom then
                for k,v in pairs(db.profile) do
                        db.profile[k] = nil
                end
                copyTable(db.profile, db.raw.profiles[copyFrom])
                inheritDefaults(db.profile, db.defaults and db.defaults.profile)
        end
        local current = self.class
        while current and current ~= AceOO.Class do
                if current.mixins then
                        for mixin in pairs(current.mixins) do
                                if type(mixin.OnEmbedProfileEnable) == "function" then
                                        mixin:OnEmbedProfileEnable(self, oldName, oldProfileData, copyFrom)
                                end
                        end
                end
                current = current.super
        end
        if type(self.OnProfileEnable) == "function" then
                self:OnProfileEnable(oldName, oldProfileData, copyFrom)
        end
        if cleanDefaults(oldProfileData, db.defaults and db.defaults.profile) then
                db.raw.profiles[oldName] = nil
                if not next(db.raw.profiles) then
                        db.raw.profiles = nil
                end
        end
        local newactive = self:IsActive()
        if active ~= newactive then
                if AceOO.inherits(self, "AceAddon-2.0") then
                        local AceAddon = AceLibrary("AceAddon-2.0")
                        if not AceAddon.addonsStarted[self] then
                                return
                        end
                end
                if newactive then
                        local current = self.class
                        while current and current ~= AceOO.Class do
                                if current.mixins then
                                        for mixin in pairs(current.mixins) do
                                                if type(mixin.OnEmbedEnable) == "function" then
                                                        mixin:OnEmbedEnable(self)
                                                end
                                        end
                                end
                                current = current.super
                        end
                        if type(self.OnEnable) == "function" then
                                self:OnEnable()
                        end
                else
                        local current = self.class
                        while current and current ~= AceOO.Class do
                                if current.mixins then
                                        for mixin in pairs(current.mixins) do
                                                if type(mixin.OnEmbedDisable) == "function" then
                                                        mixin:OnEmbedDisable(self)
                                                end
                                        end
                                end
                                current = current.super
                        end
                        if type(self.OnDisable) == "function" then
                                self:OnDisable()
                        end
                end
        end
        if self['acedb-profile-list'] then
                if not self['acedb-profile-list'][name] then
                        self['acedb-profile-list'][name] = name
                end
        end
        if Dewdrop then
                Dewdrop:Refresh(1)
                Dewdrop:Refresh(2)
                Dewdrop:Refresh(3)
                Dewdrop:Refresh(4)
                Dewdrop:Refresh(5)
        end
end

function AceDB:IsActive()
        return not self.db or not self.db.raw or not self.db.raw.disabled or not self.db.raw.disabled[self.db.raw.currentProfile[charID]]
end

function AceDB:ToggleActive(state)
        AceDB:argCheck(state, 2, "boolean", "nil")
        if not self.db or not self.db.raw then
                AceDB:error("Cannot call \"ToggleActive\" before \"RegisterDB\" has been called and before \"ADDON_LOADED\" has been fired.")
        end
        local db = self.db
        if not db.raw.disabled then
                db.raw.disabled = setmetatable({}, caseInsensitive_mt)
        end
        local profile = db.raw.currentProfile[charID]
        local disable
        if state == nil then
                disable = not db.raw.disabled[profile]
        else
                disable = not state
                if disable == db.raw.disabled[profile] then
                        return
                end
        end
        db.raw.disabled[profile] = disable or nil
        if AceOO.inherits(self, "AceAddon-2.0") then
                local AceAddon = AceLibrary("AceAddon-2.0")
                if not AceAddon.addonsStarted[self] then
                        return
                end
        end
        if not disable then
                local current = self.class
                while current and current ~= AceOO.Class do
                        if current.mixins then
                                for mixin in pairs(current.mixins) do
                                        if type(mixin.OnEmbedEnable) == "function" then
                                                mixin:OnEmbedEnable(self)
                                        end
                                end
                        end
                        current = current.super
                end
                if type(self.OnEnable) == "function" then
                        self:OnEnable()
                end
        else
                local current = self.class
                while current and current ~= AceOO.Class do
                        if current.mixins then
                                for mixin in pairs(current.mixins) do
                                        if type(mixin.OnEmbedDisable) == "function" then
                                                mixin:OnEmbedDisable(self)
                                        end
                                end
                        end
                        current = current.super
                end
                if type(self.OnDisable) == "function" then
                        self:OnDisable()
                end
        end
        return not disable
end

function AceDB:embed(target)
        self.super.embed(self, target)
        if not AceEvent then
                AceDB:error(MAJOR_VERSION .. " requires AceEvent-2.0")
        end
end

function AceDB:ADDON_LOADED(name)
        AceDB.addonsLoaded[name] = true
        for addon, addonName in pairs(AceDB.addonsToBeInitialized) do
                if name == addonName then
                        AceDB.InitializeDB(addon, name)
                        AceDB.addonsToBeInitialized[addon] = nil
                end
        end
end

function AceDB:PLAYER_LOGOUT()
        for addon in pairs(AceDB.registry) do
                local db = addon.db
                if db then
                        setmetatable(db, nil)
                        CrawlForSerialization(db.raw)
                        if type(_G[db.charName]) == "table" then
                                CrawlForSerialization(_G[db.charName])
                        end
                        if db.char and cleanDefaults(db.char, db.defaults and db.defaults.char) then
                                if db.charName and _G[db.charName] and _G[db.charName].global == db.char then
                                        _G[db.charName].global = nil
                                        if not next(_G[db.charName]) then
                                                _G[db.charName] = nil
                                        end
                                else
                                        if db.raw.chars then
                                                db.raw.chars[charID] = nil
                                                if not next(db.raw.chars) then
                                                        db.raw.chars = nil
                                                end
                                        end
                                end
                        end
                        if db.realm and cleanDefaults(db.realm, db.defaults and db.defaults.realm) then
                                if db.raw.realms then
                                        db.raw.realms[realmID] = nil
                                        if not next(db.raw.realms) then
                                                db.raw.realms = nil
                                        end
                                end
                        end
                        if db.class and cleanDefaults(db.class, db.defaults and db.defaults.class) then
                                if db.raw.classes then
                                        db.raw.classes[classID] = nil
                                        if not next(db.raw.classes) then
                                                db.raw.classes = nil
                                        end
                                end
                        end
                        if db.account and cleanDefaults(db.account, db.defaults and db.defaults.account) then
                                db.raw.account = nil
                        end
                        if db.profile and cleanDefaults(db.profile, db.defaults and db.defaults.profile) then
                                if db.raw.profiles then
                                        db.raw.profiles[db.raw.currentProfile and db.raw.currentProfile[charID] or "Default"] = nil
                                        if not next(db.raw.profiles) then
                                                db.raw.profiles = nil
                                        end
                                end
                        end
                        if db.namespaces and db.raw.namespaces then
                                for name,v in pairs(db.namespaces) do
                                        if db.raw.namespaces[name] then
                                                setmetatable(v, nil)
                                                if v.char and cleanDefaults(v.char, v.defaults and v.defaults.char) then
                                                        if db.charName and _G[db.charName] and _G[db.charName].namespaces and _G[db.charName].namespaces[name] == v then
                                                                _G[db.charName].namespaces[name] = nil
                                                                if not next(_G[db.charName].namespaces) then
                                                                        _G[db.charName].namespaces = nil
                                                                        if not next(_G[db.charName]) then
                                                                                _G[db.charName] = nil
                                                                        end
                                                                end
                                                        else
                                                                if db.raw.namespaces[name].chars then
                                                                        db.raw.namespaces[name].chars[charID] = nil
                                                                        if not next(db.raw.namespaces[name].chars) then
                                                                                db.raw.namespaces[name].chars = nil
                                                                        end
                                                                end
                                                        end
                                                end
                                                if v.realm and cleanDefaults(v.realm, v.defaults and v.defaults.realm) then
                                                        if db.raw.namespaces[name].realms then
                                                                db.raw.namespaces[name].realms[realmID] = nil
                                                                if not next(db.raw.namespaces[name].realms) then
                                                                        db.raw.namespaces[name].realms = nil
                                                                end
                                                        end
                                                end
                                                if v.class and cleanDefaults(v.class, v.defaults and v.defaults.class) then
                                                        if db.raw.namespaces[name].classes then
                                                                db.raw.namespaces[name].classes[classID] = nil
                                                                if not next(db.raw.namespaces[name].classes) then
                                                                        db.raw.namespaces[name].classes = nil
                                                                end
                                                        end
                                                end
                                                if v.account and cleanDefaults(v.account, v.defaults and v.defaults.account) then
                                                        db.raw.namespaces[name].account = nil
                                                end
                                                if v.profile and cleanDefaults(v.profile, v.defaults and v.defaults.profile) then
                                                        if db.raw.namespaces[name].profiles then
                                                                db.raw.namespaces[name].profiles[db.raw.currentProfile[charID] or "Default"] = nil
                                                                if not next(db.raw.namespaces[name].profiles) then
                                                                        db.raw.namespaces[name].profiles = nil
                                                                end
                                                        end
                                                end
                                                if not next(db.raw.namespaces[name]) then
                                                        db.raw.namespaces[name] = nil
                                                end
                                        end
                                end
                                if not next(db.raw.namespaces) then
                                        db.raw.namespaces = nil
                                end
                        end
                        if db.raw.disabled and not next(db.raw.disabled) then
                                db.raw.disabled = nil
                        end
                        if db.raw.currentProfile then
                                for k,v in pairs(db.raw.currentProfile) do
                                        if string.lower(v) == "default" then
                                                db.raw.currentProfile[k] = nil
                                        end
                                end
                                if not next(db.raw.currentProfile) then
                                        db.raw.currentProfile = nil
                                end
                        end
                        if _G[db.name] and not next(_G[db.name]) then
                                _G[db.name] = nil
                        end
                end
        end
end

function AceDB:AcquireDBNamespace(name)
        AceDB:argCheck(name, 2, "string")
        local db = self.db
        if not db then
                AceDB:error("Cannot call `AcquireDBNamespace' before `RegisterDB' has been called.", 2)
        end
        if not db.namespaces then
                rawset(db, 'namespaces', {})
        end
        if not db.namespaces[name] then
                local namespace = {}
                db.namespaces[name] = namespace
                namespace.db = db
                namespace.name = name
                setmetatable(namespace, namespace_mt)
        end
        return db.namespaces[name]
end

local options
function AceDB:GetAceOptionsDataTable(target)
        if not target['acedb-profile-list'] then
                target['acedb-profile-list'] = setmetatable({}, caseInsensitive_mt)
                local t = target['acedb-profile-list']
                for k,v in pairs(t) do
                        t[k] = nil
                end
                t.char = 'Character: ' .. charID
                t.realm = 'Realm: ' .. realmID
                t.class = 'Class: ' .. classID
                t.Default = "Default"
                if target.db and target.db.raw then
                        local db = target.db
                        if db.raw.profiles then
                                for k in pairs(db.raw.profiles) do
                                        if not string.find(k, '^char/') and not string.find(k, '^realm/') and not string.find(k, '^class/') then
                                                t[k] = k
                                        end
                                end
                        end
                end
        end
        if not target['acedb-profile-copylist'] then
                target['acedb-profile-copylist'] = setmetatable({}, caseInsensitive_mt)
                if target.db and target.db.raw then
                        local t = target['acedb-profile-copylist']
                        local db = target.db
                        
                        if db.raw.profiles then
                                for k in pairs(db.raw.profiles) do
                                        if string.find(k, '^char/') then
                                                local name = string.sub(k, 6)
                                                if name ~= charID then
                                                        t[k] =  'Character: ' .. name
                                                end
                                        elseif string.find(k, '^realm/') then
                                                local name = string.sub(k, 7)
                                                if name ~= realmID then
                                                        t[k] =  'Realm: ' .. name
                                                end
                                        elseif string.find(k, '^class/') then
                                                local name = string.sub(k, 7)
                                                if name ~= classID then
                                                        t[k] =  'Class: ' .. name
                                                end
                                        end
                                end
                        end
                end
        end
        if not options then
                options = {
                        standby = {
                                cmdName = STATE,
                                guiName = ACTIVE,
                                name = ACTIVE,
                                desc = TOGGLE_ACTIVE,
                                type = "toggle",
                                get = "IsActive",
                                set = "ToggleActive",
                                map = MAP_ACTIVESUSPENDED,
                                order = -3,
                        },
                        profile = {
                                type = 'group',
                                name = PROFILE,
                                desc = SET_PROFILE,
                                order = -3.5,
                                get = "GetProfile",
                                args = {
                                        choose = {
                                                guiName = "Choose",
                                                cmdName = PROFILE,
                                                desc = "Choose a profile",
                                                type = 'text',
                                                get = "GetProfile",
                                                set = "SetProfile",
                                                validate = target['acedb-profile-list']
                                        },
                                        copy = {
                                                guiName = "Copy from",
                                                cmdName = PROFILE,
                                                desc = "Copy settings from another profile",
                                                type = 'text',
                                                get = "GetProfile",
                                                set = "SetProfile",
                                                validate = target['acedb-profile-copylist'],
                                                disabled = function()
                                                        return not next(target['acedb-profile-copylist'])
                                                end,
                                        },
                                        other = {
                                                guiName = "Other",
                                                cmdName = PROFILE,
                                                desc = "Choose another profile",
                                                usage = "<profile name>",
                                                type = 'text',
                                                get = "GetProfile",
                                                set = "SetProfile",
                                        }
                                }
                        },
                }
        end
        return options
end

local function activate(self, oldLib, oldDeactivate)
        AceDB = self
        AceEvent = AceLibrary:HasInstance("AceEvent-2.0") and AceLibrary("AceEvent-2.0")
        
        self.super.activate(self, oldLib, oldDeactivate)
        
        for t in pairs(self.embedList) do
                if t.db then
                        rawset(t.db, 'char', nil)
                        rawset(t.db, 'realm', nil)
                        rawset(t.db, 'class', nil)
                        rawset(t.db, 'account', nil)
                        rawset(t.db, 'profile', nil)
                        setmetatable(t.db, db_mt)
                end
        end
        
        if oldLib then
                self.addonsToBeInitialized = oldLib.addonsToBeInitialized
                self.addonsLoaded = oldLib.addonsLoaded
                self.registry = oldLib.registry
        end
        if not self.addonsToBeInitialized then
                self.addonsToBeInitialized = {}
        end
        if not self.addonsLoaded then
                self.addonsLoaded = {}
        end
        if not self.registry then
                self.registry = {}
        end
        
        if oldLib then
                oldDeactivate(oldLib)
        end
end

local function external(self, major, instance)
        if major == "AceEvent-2.0" then
                AceEvent = instance
                
                AceEvent:embed(self)
                
                self:RegisterEvent("ADDON_LOADED")
                self:RegisterEvent("PLAYER_LOGOUT")
        elseif major == "Dewdrop-2.0" then
                Dewdrop = instance
        end
end

AceLibrary:Register(AceDB, MAJOR_VERSION, MINOR_VERSION, activate, nil, external)
AceDB = AceLibrary(MAJOR_VERSION)