WoWInterface SVN RaidWatch2

[/] [trunk/] [RaidWatch/] [Libs/] [AceDB-3.0/] [AceDB-3.0.lua] - Rev 22

Compare with Previous | Blame | View Log

--- **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

Compare with Previous | Blame