Zerotorescue@0: --- **AceDB-3.0** manages the SavedVariables of your addon. Zerotorescue@0: -- It offers profile management, smart defaults and namespaces for modules.\\ Zerotorescue@0: -- Data can be saved in different data-types, depending on its intended usage. Zerotorescue@0: -- The most common data-type is the `profile` type, which allows the user to choose Zerotorescue@0: -- the active profile, and manage the profiles of all of his characters.\\ Zerotorescue@0: -- The following data types are available: Zerotorescue@0: -- * **char** Character-specific data. Every character has its own database. Zerotorescue@0: -- * **realm** Realm-specific data. All of the players characters on the same realm share this database. Zerotorescue@0: -- * **class** Class-specific data. All of the players characters of the same class share this database. Zerotorescue@0: -- * **race** Race-specific data. All of the players characters of the same race share this database. Zerotorescue@0: -- * **faction** Faction-specific data. All of the players characters of the same faction share this database. Zerotorescue@0: -- * **factionrealm** Faction and realm specific data. All of the players characters on the same realm and of the same faction share this database. Zerotorescue@0: -- * **global** Global Data. All characters on the same account share this database. Zerotorescue@0: -- * **profile** Profile-specific data. All characters using the same profile share this database. The user can control which profile should be used. Zerotorescue@0: -- Zerotorescue@0: -- Creating a new Database using the `:New` function will return a new DBObject. A database will inherit all functions Zerotorescue@0: -- of the DBObjectLib listed here. \\ Zerotorescue@0: -- If you create a new namespaced child-database (`:RegisterNamespace`), you'll get a DBObject as well, but note Zerotorescue@0: -- that the child-databases cannot individually change their profile, and are linked to their parents profile - and because of that, Zerotorescue@0: -- the profile related APIs are not available. Only `:RegisterDefaults` and `:ResetProfile` are available on child-databases. Zerotorescue@0: -- Zerotorescue@0: -- For more details on how to use AceDB-3.0, see the [[AceDB-3.0 Tutorial]]. Zerotorescue@0: -- Zerotorescue@0: -- You may also be interested in [[libdualspec-1-0|LibDualSpec-1.0]] to do profile switching automatically when switching specs. Zerotorescue@0: -- Zerotorescue@0: -- @usage Zerotorescue@0: -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("DBExample") Zerotorescue@0: -- Zerotorescue@0: -- -- declare defaults to be used in the DB Zerotorescue@0: -- local defaults = { Zerotorescue@0: -- profile = { Zerotorescue@0: -- setting = true, Zerotorescue@0: -- } Zerotorescue@0: -- } Zerotorescue@0: -- Zerotorescue@0: -- function MyAddon:OnInitialize() Zerotorescue@0: -- -- Assuming the .toc says ## SavedVariables: MyAddonDB Zerotorescue@0: -- self.db = LibStub("AceDB-3.0"):New("MyAddonDB", defaults, true) Zerotorescue@0: -- end Zerotorescue@0: -- @class file Zerotorescue@0: -- @name AceDB-3.0.lua Zerotorescue@0: -- @release $Id: AceDB-3.0.lua 940 2010-06-19 08:01:47Z nevcairiel $ Zerotorescue@0: local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 21 Zerotorescue@0: local AceDB, oldminor = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR) Zerotorescue@0: Zerotorescue@0: if not AceDB then return end -- No upgrade needed Zerotorescue@0: Zerotorescue@0: -- Lua APIs Zerotorescue@0: local type, pairs, next, error = type, pairs, next, error Zerotorescue@0: local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget Zerotorescue@0: Zerotorescue@0: -- WoW APIs Zerotorescue@0: local _G = _G Zerotorescue@0: Zerotorescue@0: -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded Zerotorescue@0: -- List them here for Mikk's FindGlobals script Zerotorescue@0: -- GLOBALS: LibStub Zerotorescue@0: Zerotorescue@0: AceDB.db_registry = AceDB.db_registry or {} Zerotorescue@0: AceDB.frame = AceDB.frame or CreateFrame("Frame") Zerotorescue@0: Zerotorescue@0: local CallbackHandler Zerotorescue@0: local CallbackDummy = { Fire = function() end } Zerotorescue@0: Zerotorescue@0: local DBObjectLib = {} Zerotorescue@0: Zerotorescue@0: --[[------------------------------------------------------------------------- Zerotorescue@0: AceDB Utility Functions Zerotorescue@0: ---------------------------------------------------------------------------]] Zerotorescue@0: Zerotorescue@0: -- Simple shallow copy for copying defaults Zerotorescue@0: local function copyTable(src, dest) Zerotorescue@0: if type(dest) ~= "table" then dest = {} end Zerotorescue@0: if type(src) == "table" then Zerotorescue@0: for k,v in pairs(src) do Zerotorescue@0: if type(v) == "table" then Zerotorescue@0: -- try to index the key first so that the metatable creates the defaults, if set, and use that table Zerotorescue@0: v = copyTable(v, dest[k]) Zerotorescue@0: end Zerotorescue@0: dest[k] = v Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: return dest Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- Called to add defaults to a section of the database Zerotorescue@0: -- Zerotorescue@0: -- When a ["*"] default section is indexed with a new key, a table is returned Zerotorescue@0: -- and set in the host table. These tables must be cleaned up by removeDefaults Zerotorescue@0: -- in order to ensure we don't write empty default tables. Zerotorescue@0: local function copyDefaults(dest, src) Zerotorescue@0: -- this happens if some value in the SV overwrites our default value with a non-table Zerotorescue@0: --if type(dest) ~= "table" then return end Zerotorescue@0: for k, v in pairs(src) do Zerotorescue@0: if k == "*" or k == "**" then Zerotorescue@0: if type(v) == "table" then Zerotorescue@0: -- This is a metatable used for table defaults Zerotorescue@0: local mt = { Zerotorescue@0: -- This handles the lookup and creation of new subtables Zerotorescue@0: __index = function(t,k) Zerotorescue@0: if k == nil then return nil end Zerotorescue@0: local tbl = {} Zerotorescue@0: copyDefaults(tbl, v) Zerotorescue@0: rawset(t, k, tbl) Zerotorescue@0: return tbl Zerotorescue@0: end, Zerotorescue@0: } Zerotorescue@0: setmetatable(dest, mt) Zerotorescue@0: -- handle already existing tables in the SV Zerotorescue@0: for dk, dv in pairs(dest) do Zerotorescue@0: if not rawget(src, dk) and type(dv) == "table" then Zerotorescue@0: copyDefaults(dv, v) Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: else Zerotorescue@0: -- Values are not tables, so this is just a simple return Zerotorescue@0: local mt = {__index = function(t,k) return k~=nil and v or nil end} Zerotorescue@0: setmetatable(dest, mt) Zerotorescue@0: end Zerotorescue@0: elseif type(v) == "table" then Zerotorescue@0: if not rawget(dest, k) then rawset(dest, k, {}) end Zerotorescue@0: if type(dest[k]) == "table" then Zerotorescue@0: copyDefaults(dest[k], v) Zerotorescue@0: if src['**'] then Zerotorescue@0: copyDefaults(dest[k], src['**']) Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: else Zerotorescue@0: if rawget(dest, k) == nil then Zerotorescue@0: rawset(dest, k, v) Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- Called to remove all defaults in the default table from the database Zerotorescue@0: local function removeDefaults(db, defaults, blocker) Zerotorescue@0: -- remove all metatables from the db, so we don't accidentally create new sub-tables through them Zerotorescue@0: setmetatable(db, nil) Zerotorescue@0: -- loop through the defaults and remove their content Zerotorescue@0: for k,v in pairs(defaults) do Zerotorescue@0: if k == "*" or k == "**" then Zerotorescue@0: if type(v) == "table" then Zerotorescue@0: -- Loop through all the actual k,v pairs and remove Zerotorescue@0: for key, value in pairs(db) do Zerotorescue@0: if type(value) == "table" then Zerotorescue@0: -- if the key was not explicitly specified in the defaults table, just strip everything from * and ** tables Zerotorescue@0: if defaults[key] == nil and (not blocker or blocker[key] == nil) then Zerotorescue@0: removeDefaults(value, v) Zerotorescue@0: -- if the table is empty afterwards, remove it Zerotorescue@0: if next(value) == nil then Zerotorescue@0: db[key] = nil Zerotorescue@0: end Zerotorescue@0: -- if it was specified, only strip ** content, but block values which were set in the key table Zerotorescue@0: elseif k == "**" then Zerotorescue@0: removeDefaults(value, v, defaults[key]) Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: elseif k == "*" then Zerotorescue@0: -- check for non-table default Zerotorescue@0: for key, value in pairs(db) do Zerotorescue@0: if defaults[key] == nil and v == value then Zerotorescue@0: db[key] = nil Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: elseif type(v) == "table" and type(db[k]) == "table" then Zerotorescue@0: -- if a blocker was set, dive into it, to allow multi-level defaults Zerotorescue@0: removeDefaults(db[k], v, blocker and blocker[k]) Zerotorescue@0: if next(db[k]) == nil then Zerotorescue@0: db[k] = nil Zerotorescue@0: end Zerotorescue@0: else Zerotorescue@0: -- check if the current value matches the default, and that its not blocked by another defaults table Zerotorescue@0: if db[k] == defaults[k] and (not blocker or blocker[k] == nil) then Zerotorescue@0: db[k] = nil Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- This is called when a table section is first accessed, to set up the defaults Zerotorescue@0: local function initSection(db, section, svstore, key, defaults) Zerotorescue@0: local sv = rawget(db, "sv") Zerotorescue@0: Zerotorescue@0: local tableCreated Zerotorescue@0: if not sv[svstore] then sv[svstore] = {} end Zerotorescue@0: if not sv[svstore][key] then Zerotorescue@0: sv[svstore][key] = {} Zerotorescue@0: tableCreated = true Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: local tbl = sv[svstore][key] Zerotorescue@0: Zerotorescue@0: if defaults then Zerotorescue@0: copyDefaults(tbl, defaults) Zerotorescue@0: end Zerotorescue@0: rawset(db, section, tbl) Zerotorescue@0: Zerotorescue@0: return tableCreated, tbl Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- Metatable to handle the dynamic creation of sections and copying of sections. Zerotorescue@0: local dbmt = { Zerotorescue@0: __index = function(t, section) Zerotorescue@0: local keys = rawget(t, "keys") Zerotorescue@0: local key = keys[section] Zerotorescue@0: if key then Zerotorescue@0: local defaultTbl = rawget(t, "defaults") Zerotorescue@0: local defaults = defaultTbl and defaultTbl[section] Zerotorescue@0: Zerotorescue@0: if section == "profile" then Zerotorescue@0: local new = initSection(t, section, "profiles", key, defaults) Zerotorescue@0: if new then Zerotorescue@0: -- Callback: OnNewProfile, database, newProfileKey Zerotorescue@0: t.callbacks:Fire("OnNewProfile", t, key) Zerotorescue@0: end Zerotorescue@0: elseif section == "profiles" then Zerotorescue@0: local sv = rawget(t, "sv") Zerotorescue@0: if not sv.profiles then sv.profiles = {} end Zerotorescue@0: rawset(t, "profiles", sv.profiles) Zerotorescue@0: elseif section == "global" then Zerotorescue@0: local sv = rawget(t, "sv") Zerotorescue@0: if not sv.global then sv.global = {} end Zerotorescue@0: if defaults then Zerotorescue@0: copyDefaults(sv.global, defaults) Zerotorescue@0: end Zerotorescue@0: rawset(t, section, sv.global) Zerotorescue@0: else Zerotorescue@0: initSection(t, section, section, key, defaults) Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: return rawget(t, section) Zerotorescue@0: end Zerotorescue@0: } Zerotorescue@0: Zerotorescue@0: local function validateDefaults(defaults, keyTbl, offset) Zerotorescue@0: if not defaults then return end Zerotorescue@0: offset = offset or 0 Zerotorescue@0: for k in pairs(defaults) do Zerotorescue@0: if not keyTbl[k] or k == "profiles" then Zerotorescue@0: error(("Usage: AceDBObject:RegisterDefaults(defaults): '%s' is not a valid datatype."):format(k), 3 + offset) Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: local preserve_keys = { Zerotorescue@0: ["callbacks"] = true, Zerotorescue@0: ["RegisterCallback"] = true, Zerotorescue@0: ["UnregisterCallback"] = true, Zerotorescue@0: ["UnregisterAllCallbacks"] = true, Zerotorescue@0: ["children"] = true, Zerotorescue@0: } Zerotorescue@0: Zerotorescue@0: local realmKey = GetRealmName() Zerotorescue@0: local charKey = UnitName("player") .. " - " .. realmKey Zerotorescue@0: local _, classKey = UnitClass("player") Zerotorescue@0: local _, raceKey = UnitRace("player") Zerotorescue@0: local factionKey = UnitFactionGroup("player") Zerotorescue@0: local factionrealmKey = factionKey .. " - " .. realmKey Zerotorescue@0: -- Actual database initialization function Zerotorescue@0: local function initdb(sv, defaults, defaultProfile, olddb, parent) Zerotorescue@0: -- Generate the database keys for each section Zerotorescue@0: Zerotorescue@0: -- map "true" to our "Default" profile Zerotorescue@0: if defaultProfile == true then defaultProfile = "Default" end Zerotorescue@0: Zerotorescue@0: local profileKey Zerotorescue@0: if not parent then Zerotorescue@0: -- Make a container for profile keys Zerotorescue@0: if not sv.profileKeys then sv.profileKeys = {} end Zerotorescue@0: Zerotorescue@0: -- Try to get the profile selected from the char db Zerotorescue@0: profileKey = sv.profileKeys[charKey] or defaultProfile or charKey Zerotorescue@0: Zerotorescue@0: -- save the selected profile for later Zerotorescue@0: sv.profileKeys[charKey] = profileKey Zerotorescue@0: else Zerotorescue@0: -- Use the profile of the parents DB Zerotorescue@0: profileKey = parent.keys.profile or defaultProfile or charKey Zerotorescue@0: Zerotorescue@0: -- clear the profileKeys in the DB, namespaces don't need to store them Zerotorescue@0: sv.profileKeys = nil Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- This table contains keys that enable the dynamic creation Zerotorescue@0: -- of each section of the table. The 'global' and 'profiles' Zerotorescue@0: -- have a key of true, since they are handled in a special case Zerotorescue@0: local keyTbl= { Zerotorescue@0: ["char"] = charKey, Zerotorescue@0: ["realm"] = realmKey, Zerotorescue@0: ["class"] = classKey, Zerotorescue@0: ["race"] = raceKey, Zerotorescue@0: ["faction"] = factionKey, Zerotorescue@0: ["factionrealm"] = factionrealmKey, Zerotorescue@0: ["profile"] = profileKey, Zerotorescue@0: ["global"] = true, Zerotorescue@0: ["profiles"] = true, Zerotorescue@0: } Zerotorescue@0: Zerotorescue@0: validateDefaults(defaults, keyTbl, 1) Zerotorescue@0: Zerotorescue@0: -- This allows us to use this function to reset an entire database Zerotorescue@0: -- Clear out the old database Zerotorescue@0: if olddb then Zerotorescue@0: for k,v in pairs(olddb) do if not preserve_keys[k] then olddb[k] = nil end end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- Give this database the metatable so it initializes dynamically Zerotorescue@0: local db = setmetatable(olddb or {}, dbmt) Zerotorescue@0: Zerotorescue@0: if not rawget(db, "callbacks") then Zerotorescue@0: -- try to load CallbackHandler-1.0 if it loaded after our library Zerotorescue@0: if not CallbackHandler then CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0", true) end Zerotorescue@0: db.callbacks = CallbackHandler and CallbackHandler:New(db) or CallbackDummy Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- Copy methods locally into the database object, to avoid hitting Zerotorescue@0: -- the metatable when calling methods Zerotorescue@0: Zerotorescue@0: if not parent then Zerotorescue@0: for name, func in pairs(DBObjectLib) do Zerotorescue@0: db[name] = func Zerotorescue@0: end Zerotorescue@0: else Zerotorescue@0: -- hack this one in Zerotorescue@0: db.RegisterDefaults = DBObjectLib.RegisterDefaults Zerotorescue@0: db.ResetProfile = DBObjectLib.ResetProfile Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- Set some properties in the database object Zerotorescue@0: db.profiles = sv.profiles Zerotorescue@0: db.keys = keyTbl Zerotorescue@0: db.sv = sv Zerotorescue@0: --db.sv_name = name Zerotorescue@0: db.defaults = defaults Zerotorescue@0: db.parent = parent Zerotorescue@0: Zerotorescue@0: -- store the DB in the registry Zerotorescue@0: AceDB.db_registry[db] = true Zerotorescue@0: Zerotorescue@0: return db Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- handle PLAYER_LOGOUT Zerotorescue@0: -- strip all defaults from all databases Zerotorescue@0: -- and cleans up empty sections Zerotorescue@0: local function logoutHandler(frame, event) Zerotorescue@0: if event == "PLAYER_LOGOUT" then Zerotorescue@0: for db in pairs(AceDB.db_registry) do Zerotorescue@0: db.callbacks:Fire("OnDatabaseShutdown", db) Zerotorescue@0: db:RegisterDefaults(nil) Zerotorescue@0: Zerotorescue@0: -- cleanup sections that are empty without defaults Zerotorescue@0: local sv = rawget(db, "sv") Zerotorescue@0: for section in pairs(db.keys) do Zerotorescue@0: if rawget(sv, section) then Zerotorescue@0: -- global is special, all other sections have sub-entrys Zerotorescue@0: -- also don't delete empty profiles on main dbs, only on namespaces Zerotorescue@0: if section ~= "global" and (section ~= "profiles" or rawget(db, "parent")) then Zerotorescue@0: for key in pairs(sv[section]) do Zerotorescue@0: if not next(sv[section][key]) then Zerotorescue@0: sv[section][key] = nil Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: if not next(sv[section]) then Zerotorescue@0: sv[section] = nil Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: AceDB.frame:RegisterEvent("PLAYER_LOGOUT") Zerotorescue@0: AceDB.frame:SetScript("OnEvent", logoutHandler) Zerotorescue@0: Zerotorescue@0: Zerotorescue@0: --[[------------------------------------------------------------------------- Zerotorescue@0: AceDB Object Method Definitions Zerotorescue@0: ---------------------------------------------------------------------------]] Zerotorescue@0: Zerotorescue@0: --- Sets the defaults table for the given database object by clearing any Zerotorescue@0: -- that are currently set, and then setting the new defaults. Zerotorescue@0: -- @param defaults A table of defaults for this database Zerotorescue@0: function DBObjectLib:RegisterDefaults(defaults) Zerotorescue@0: if defaults and type(defaults) ~= "table" then Zerotorescue@0: error("Usage: AceDBObject:RegisterDefaults(defaults): 'defaults' - table or nil expected.", 2) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: validateDefaults(defaults, self.keys) Zerotorescue@0: Zerotorescue@0: -- Remove any currently set defaults Zerotorescue@0: if self.defaults then Zerotorescue@0: for section,key in pairs(self.keys) do Zerotorescue@0: if self.defaults[section] and rawget(self, section) then Zerotorescue@0: removeDefaults(self[section], self.defaults[section]) Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- Set the DBObject.defaults table Zerotorescue@0: self.defaults = defaults Zerotorescue@0: Zerotorescue@0: -- Copy in any defaults, only touching those sections already created Zerotorescue@0: if defaults then Zerotorescue@0: for section,key in pairs(self.keys) do Zerotorescue@0: if defaults[section] and rawget(self, section) then Zerotorescue@0: copyDefaults(self[section], defaults[section]) Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: --- Changes the profile of the database and all of it's namespaces to the Zerotorescue@0: -- supplied named profile Zerotorescue@0: -- @param name The name of the profile to set as the current profile Zerotorescue@0: function DBObjectLib:SetProfile(name) Zerotorescue@0: if type(name) ~= "string" then Zerotorescue@0: error("Usage: AceDBObject:SetProfile(name): 'name' - string expected.", 2) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- changing to the same profile, dont do anything Zerotorescue@0: if name == self.keys.profile then return end Zerotorescue@0: Zerotorescue@0: local oldProfile = self.profile Zerotorescue@0: local defaults = self.defaults and self.defaults.profile Zerotorescue@0: Zerotorescue@0: -- Callback: OnProfileShutdown, database Zerotorescue@0: self.callbacks:Fire("OnProfileShutdown", self) Zerotorescue@0: Zerotorescue@0: if oldProfile and defaults then Zerotorescue@0: -- Remove the defaults from the old profile Zerotorescue@0: removeDefaults(oldProfile, defaults) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: self.profile = nil Zerotorescue@0: self.keys["profile"] = name Zerotorescue@0: Zerotorescue@0: -- if the storage exists, save the new profile Zerotorescue@0: -- this won't exist on namespaces. Zerotorescue@0: if self.sv.profileKeys then Zerotorescue@0: self.sv.profileKeys[charKey] = name Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- populate to child namespaces Zerotorescue@0: if self.children then Zerotorescue@0: for _, db in pairs(self.children) do Zerotorescue@0: DBObjectLib.SetProfile(db, name) Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- Callback: OnProfileChanged, database, newProfileKey Zerotorescue@0: self.callbacks:Fire("OnProfileChanged", self, name) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: --- Returns a table with the names of the existing profiles in the database. Zerotorescue@0: -- You can optionally supply a table to re-use for this purpose. Zerotorescue@0: -- @param tbl A table to store the profile names in (optional) Zerotorescue@0: function DBObjectLib:GetProfiles(tbl) Zerotorescue@0: if tbl and type(tbl) ~= "table" then Zerotorescue@0: error("Usage: AceDBObject:GetProfiles(tbl): 'tbl' - table or nil expected.", 2) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- Clear the container table Zerotorescue@0: if tbl then Zerotorescue@0: for k,v in pairs(tbl) do tbl[k] = nil end Zerotorescue@0: else Zerotorescue@0: tbl = {} Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: local curProfile = self.keys.profile Zerotorescue@0: Zerotorescue@0: local i = 0 Zerotorescue@0: for profileKey in pairs(self.profiles) do Zerotorescue@0: i = i + 1 Zerotorescue@0: tbl[i] = profileKey Zerotorescue@0: if curProfile and profileKey == curProfile then curProfile = nil end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- Add the current profile, if it hasn't been created yet Zerotorescue@0: if curProfile then Zerotorescue@0: i = i + 1 Zerotorescue@0: tbl[i] = curProfile Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: return tbl, i Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: --- Returns the current profile name used by the database Zerotorescue@0: function DBObjectLib:GetCurrentProfile() Zerotorescue@0: return self.keys.profile Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: --- Deletes a named profile. This profile must not be the active profile. Zerotorescue@0: -- @param name The name of the profile to be deleted Zerotorescue@0: -- @param silent If true, do not raise an error when the profile does not exist Zerotorescue@0: function DBObjectLib:DeleteProfile(name, silent) Zerotorescue@0: if type(name) ~= "string" then Zerotorescue@0: error("Usage: AceDBObject:DeleteProfile(name): 'name' - string expected.", 2) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: if self.keys.profile == name then Zerotorescue@0: error("Cannot delete the active profile in an AceDBObject.", 2) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: if not rawget(self.profiles, name) and not silent then Zerotorescue@0: error("Cannot delete profile '" .. name .. "'. It does not exist.", 2) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: self.profiles[name] = nil Zerotorescue@0: Zerotorescue@0: -- populate to child namespaces Zerotorescue@0: if self.children then Zerotorescue@0: for _, db in pairs(self.children) do Zerotorescue@0: DBObjectLib.DeleteProfile(db, name, true) Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- Callback: OnProfileDeleted, database, profileKey Zerotorescue@0: self.callbacks:Fire("OnProfileDeleted", self, name) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: --- Copies a named profile into the current profile, overwriting any conflicting Zerotorescue@0: -- settings. Zerotorescue@0: -- @param name The name of the profile to be copied into the current profile Zerotorescue@0: -- @param silent If true, do not raise an error when the profile does not exist Zerotorescue@0: function DBObjectLib:CopyProfile(name, silent) Zerotorescue@0: if type(name) ~= "string" then Zerotorescue@0: error("Usage: AceDBObject:CopyProfile(name): 'name' - string expected.", 2) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: if name == self.keys.profile then Zerotorescue@0: error("Cannot have the same source and destination profiles.", 2) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: if not rawget(self.profiles, name) and not silent then Zerotorescue@0: error("Cannot copy profile '" .. name .. "'. It does not exist.", 2) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- Reset the profile before copying Zerotorescue@0: DBObjectLib.ResetProfile(self, nil, true) Zerotorescue@0: Zerotorescue@0: local profile = self.profile Zerotorescue@0: local source = self.profiles[name] Zerotorescue@0: Zerotorescue@0: copyTable(source, profile) Zerotorescue@0: Zerotorescue@0: -- populate to child namespaces Zerotorescue@0: if self.children then Zerotorescue@0: for _, db in pairs(self.children) do Zerotorescue@0: DBObjectLib.CopyProfile(db, name, true) Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- Callback: OnProfileCopied, database, sourceProfileKey Zerotorescue@0: self.callbacks:Fire("OnProfileCopied", self, name) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: --- Resets the current profile to the default values (if specified). Zerotorescue@0: -- @param noChildren if set to true, the reset will not be populated to the child namespaces of this DB object Zerotorescue@0: -- @param noCallbacks if set to true, won't fire the OnProfileReset callback Zerotorescue@0: function DBObjectLib:ResetProfile(noChildren, noCallbacks) Zerotorescue@0: local profile = self.profile Zerotorescue@0: Zerotorescue@0: for k,v in pairs(profile) do Zerotorescue@0: profile[k] = nil Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: local defaults = self.defaults and self.defaults.profile Zerotorescue@0: if defaults then Zerotorescue@0: copyDefaults(profile, defaults) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- populate to child namespaces Zerotorescue@0: if self.children and not noChildren then Zerotorescue@0: for _, db in pairs(self.children) do Zerotorescue@0: DBObjectLib.ResetProfile(db, nil, noCallbacks) Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- Callback: OnProfileReset, database Zerotorescue@0: if not noCallbacks then Zerotorescue@0: self.callbacks:Fire("OnProfileReset", self) Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: --- Resets the entire database, using the string defaultProfile as the new default Zerotorescue@0: -- profile. Zerotorescue@0: -- @param defaultProfile The profile name to use as the default Zerotorescue@0: function DBObjectLib:ResetDB(defaultProfile) Zerotorescue@0: if defaultProfile and type(defaultProfile) ~= "string" then Zerotorescue@0: error("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or nil expected.", 2) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: local sv = self.sv Zerotorescue@0: for k,v in pairs(sv) do Zerotorescue@0: sv[k] = nil Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: local parent = self.parent Zerotorescue@0: Zerotorescue@0: initdb(sv, self.defaults, defaultProfile, self) Zerotorescue@0: Zerotorescue@0: -- fix the child namespaces Zerotorescue@0: if self.children then Zerotorescue@0: if not sv.namespaces then sv.namespaces = {} end Zerotorescue@0: for name, db in pairs(self.children) do Zerotorescue@0: if not sv.namespaces[name] then sv.namespaces[name] = {} end Zerotorescue@0: initdb(sv.namespaces[name], db.defaults, self.keys.profile, db, self) Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- Callback: OnDatabaseReset, database Zerotorescue@0: self.callbacks:Fire("OnDatabaseReset", self) Zerotorescue@0: -- Callback: OnProfileChanged, database, profileKey Zerotorescue@0: self.callbacks:Fire("OnProfileChanged", self, self.keys["profile"]) Zerotorescue@0: Zerotorescue@0: return self Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: --- Creates a new database namespace, directly tied to the database. This Zerotorescue@0: -- is a full scale database in it's own rights other than the fact that Zerotorescue@0: -- it cannot control its profile individually Zerotorescue@0: -- @param name The name of the new namespace Zerotorescue@0: -- @param defaults A table of values to use as defaults Zerotorescue@0: function DBObjectLib:RegisterNamespace(name, defaults) Zerotorescue@0: if type(name) ~= "string" then Zerotorescue@0: error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - string expected.", 2) Zerotorescue@0: end Zerotorescue@0: if defaults and type(defaults) ~= "table" then Zerotorescue@0: error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'defaults' - table or nil expected.", 2) Zerotorescue@0: end Zerotorescue@0: if self.children and self.children[name] then Zerotorescue@0: error ("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - a namespace with that name already exists.", 2) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: local sv = self.sv Zerotorescue@0: if not sv.namespaces then sv.namespaces = {} end Zerotorescue@0: if not sv.namespaces[name] then Zerotorescue@0: sv.namespaces[name] = {} Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: local newDB = initdb(sv.namespaces[name], defaults, self.keys.profile, nil, self) Zerotorescue@0: Zerotorescue@0: if not self.children then self.children = {} end Zerotorescue@0: self.children[name] = newDB Zerotorescue@0: return newDB Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: --- Returns an already existing namespace from the database object. Zerotorescue@0: -- @param name The name of the new namespace Zerotorescue@0: -- @param silent if true, the addon is optional, silently return nil if its not found Zerotorescue@0: -- @usage Zerotorescue@0: -- local namespace = self.db:GetNamespace('namespace') Zerotorescue@0: -- @return the namespace object if found Zerotorescue@0: function DBObjectLib:GetNamespace(name, silent) Zerotorescue@0: if type(name) ~= "string" then Zerotorescue@0: error("Usage: AceDBObject:GetNamespace(name): 'name' - string expected.", 2) Zerotorescue@0: end Zerotorescue@0: if not silent and not (self.children and self.children[name]) then Zerotorescue@0: error ("Usage: AceDBObject:GetNamespace(name): 'name' - namespace does not exist.", 2) Zerotorescue@0: end Zerotorescue@0: if not self.children then self.children = {} end Zerotorescue@0: return self.children[name] Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: --[[------------------------------------------------------------------------- Zerotorescue@0: AceDB Exposed Methods Zerotorescue@0: ---------------------------------------------------------------------------]] Zerotorescue@0: Zerotorescue@0: --- Creates a new database object that can be used to handle database settings and profiles. Zerotorescue@0: -- By default, an empty DB is created, using a character specific profile. Zerotorescue@0: -- Zerotorescue@0: -- You can override the default profile used by passing any profile name as the third argument, Zerotorescue@0: -- or by passing //true// as the third argument to use a globally shared profile called "Default". Zerotorescue@0: -- Zerotorescue@0: -- Note that there is no token replacement in the default profile name, passing a defaultProfile as "char" Zerotorescue@0: -- will use a profile named "char", and not a character-specific profile. Zerotorescue@0: -- @param tbl The name of variable, or table to use for the database Zerotorescue@0: -- @param defaults A table of database defaults Zerotorescue@0: -- @param defaultProfile The name of the default profile. If not set, a character specific profile will be used as the default. Zerotorescue@0: -- You can also pass //true// to use a shared global profile called "Default". Zerotorescue@0: -- @usage Zerotorescue@0: -- -- Create an empty DB using a character-specific default profile. Zerotorescue@0: -- self.db = LibStub("AceDB-3.0"):New("MyAddonDB") Zerotorescue@0: -- @usage Zerotorescue@0: -- -- Create a DB using defaults and using a shared default profile Zerotorescue@0: -- self.db = LibStub("AceDB-3.0"):New("MyAddonDB", defaults, true) Zerotorescue@0: function AceDB:New(tbl, defaults, defaultProfile) Zerotorescue@0: if type(tbl) == "string" then Zerotorescue@0: local name = tbl Zerotorescue@0: tbl = _G[name] Zerotorescue@0: if not tbl then Zerotorescue@0: tbl = {} Zerotorescue@0: _G[name] = tbl Zerotorescue@0: end Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: if type(tbl) ~= "table" then Zerotorescue@0: error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'tbl' - table expected.", 2) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: if defaults and type(defaults) ~= "table" then Zerotorescue@0: error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaults' - table expected.", 2) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then Zerotorescue@0: error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaultProfile' - string or true expected.", 2) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: return initdb(tbl, defaults, defaultProfile) Zerotorescue@0: end Zerotorescue@0: Zerotorescue@0: -- upgrade existing databases Zerotorescue@0: for db in pairs(AceDB.db_registry) do Zerotorescue@0: if not db.parent then Zerotorescue@0: for name,func in pairs(DBObjectLib) do Zerotorescue@0: db[name] = func Zerotorescue@0: end Zerotorescue@0: else Zerotorescue@0: db.RegisterDefaults = DBObjectLib.RegisterDefaults Zerotorescue@0: db.ResetProfile = DBObjectLib.ResetProfile Zerotorescue@0: end Zerotorescue@0: end