diff --git a/Dongle.lua b/Dongle.lua
index d92dc93..aa02beb 100644
--- a/Dongle.lua
+++ b/Dongle.lua
@@ -1,5 +1,5 @@
--[[-------------------------------------------------------------------------
- Copyright (c) 2006, Dongle Development Team
+ Copyright (c) 2006-2007, Dongle Development Team
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -118,20 +118,18 @@ end
Begin Library Implementation
---------------------------------------------------------------------------]]
-local major = "Dongle-Beta0"
-local minor = tonumber(string.match("$Revision: 228 $", "(%d+)") or 1)
+local major = "Dongle-Beta1"
+local minor = tonumber(string.match("$Revision: 260 $", "(%d+)") or 1)
assert(DongleStub, string.format("Dongle requires DongleStub.", major))
-assert(DongleStub and DongleStub:GetVersion() == "DongleStub-Beta0",
- string.format("Dongle requires DongleStub-Beta0. You are using an older version.", major))
if not DongleStub:IsNewerVersion(major, minor) then return end
local Dongle = {}
local methods = {
- "RegisterEvent", "UnregisterEvent", "UnregisterAllEvents",
- "RegisterMessage", "UnregisterMessage", "UnregisterAllMessages", "TriggerMessage",
- "EnableDebug", "Print", "PrintF", "Debug", "DebugF",
+ "RegisterEvent", "UnregisterEvent", "UnregisterAllEvents", "IsEventRegistered",
+ "RegisterMessage", "UnregisterMessage", "UnregisterAllMessages", "TriggerMessage", "IsMessageRegistered",
+ "EnableDebug", "IsDebugEnabled", "Print", "PrintF", "Debug", "DebugF",
"InitializeDB",
"InitializeSlashCommand",
"NewModule", "HasModule", "IterateModules",
@@ -148,6 +146,43 @@ local messages = {}
local frame
+--[[-------------------------------------------------------------------------
+ Message Localization
+---------------------------------------------------------------------------]]
+
+local L = {
+ ["ADDMESSAGE_REQUIRED"] = "The frame you specify must have an 'AddMessage' method.",
+ ["ALREADY_REGISTERED"] = "A Dongle with the name '%s' is already registered.",
+ ["BAD_ARGUMENT"] = "bad argument #%d to '%s' (%s expected, got %s)",
+ ["BAD_ARGUMENT_DB"] = "bad argument #%d to '%s' (DongleDB expected)",
+ ["CANNOT_DELETE_ACTIVE_PROFILE"] = "You cannot delete your active profile. Change profiles, then attempt to delete.",
+ ["DELETE_NONEXISTANT_PROFILE"] = "You cannot delete a non-existant profile.",
+ ["MUST_CALLFROM_DBOBJECT"] = "You must call '%s' from a Dongle database object.",
+ ["MUST_CALLFROM_REGISTERED"] = "You must call '%s' from a registered Dongle.",
+ ["MUST_CALLFROM_SLASH"] = "You must call '%s' from a Dongle slash command object.",
+ ["PROFILE_DOES_NOT_EXIST"] = "Profile '%s' doesn't exist.",
+ ["REPLACE_DEFAULTS"] = "You are attempting to register defaults with a database that already contains defaults.",
+ ["SAME_SOURCE_DEST"] = "Source/Destination profile cannot be the same profile.",
+ ["EVENT_REGISTER_SPECIAL"] = "You cannot register for the '%s' event. Use the '%s' method instead.",
+ ["Unknown"] = "Unknown",
+ ["INJECTDB_USAGE"] = "Usage: DongleCmd:InjectDBCommands(db, ['copy', 'delete', 'list', 'reset', 'set'])",
+ ["DBSLASH_PROFILE_COPY_DESC"] = "profile copy <name> - Copies profile <name> into your current profile.",
+ ["DBSLASH_PROFILE_COPY_PATTERN"] = "^profile copy (.+)$",
+ ["DBSLASH_PROFILE_DELETE_DESC"] = "profile delete <name> - Deletes the profile <name>.",
+ ["DBSLASH_PROFILE_DELETE_PATTERN"] = "^profile delete (.+)$",
+ ["DBSLASH_PROFILE_LIST_DESC"] = "profile list - Lists all valid profiles.",
+ ["DBSLASH_PROFILE_LIST_PATTERN"] = "^profile list$",
+ ["DBSLASH_PROFILE_RESET_DESC"] = "profile reset - Resets the current profile.",
+ ["DBSLASH_PROFILE_RESET_PATTERN"] = "^profile reset$",
+ ["DBSLASH_PROFILE_SET_DESC"] = "profile set <name> - Sets the current profile to <name>.",
+ ["DBSLASH_PROFILE_SET_PATTERN"] = "^profile set (.+)$",
+ ["DBSLASH_PROFILE_LIST_OUT"] = "Profile List:",
+}
+
+--[[-------------------------------------------------------------------------
+ Utility functions for Dongle use
+---------------------------------------------------------------------------]]
+
local function assert(level,condition,message)
if not condition then
error(message,level)
@@ -156,16 +191,15 @@ end
local function argcheck(value, num, ...)
assert(1, type(num) == "number",
- "Bad argument #2 to 'argcheck' (number expected, got " .. type(level) .. ")")
+ string.format(L["BAD_ARGUMENT"], 2, "argcheck", "number", type(level)))
for i=1,select("#", ...) do
if type(value) == select(i, ...) then return end
end
local types = strjoin(", ", ...)
- local name = string.match(debugstack(), "`argcheck'.-[`<](.-)['>]") or "Unknown"
- error(string.format("bad argument #%d to '%s' (%s expected, got %s)",
- num, name, types, type(value)), 3)
+ local name = string.match(debugstack(2,2,0), ": in function [`<](.-)['>]")
+ error(string.format(L["BAD_ARGUMENT"], num, name, types, type(value)), 3)
end
local function safecall(func,...)
@@ -175,6 +209,10 @@ local function safecall(func,...)
end
end
+--[[-------------------------------------------------------------------------
+ Dongle constructor, and DongleModule system
+---------------------------------------------------------------------------]]
+
function Dongle:New(name, obj)
argcheck(name, 2, "string")
argcheck(obj, 3, "table", "nil")
@@ -184,7 +222,7 @@ function Dongle:New(name, obj)
end
if registry[name] then
- error("A Dongle with the name '"..name.."' is already registered.")
+ error(string.format(L["ALREADY_REGISTERED"], name))
end
local reg = {["obj"] = obj, ["name"] = name}
@@ -204,7 +242,7 @@ end
function Dongle:NewModule(name, obj)
local reg = lookup[self]
- assert(3, reg, "You must call 'NewModule' from a registered Dongle.")
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "NewModule"))
argcheck(name, 2, "string")
argcheck(obj, 3, "table", "nil")
@@ -213,15 +251,13 @@ function Dongle:NewModule(name, obj)
if not reg.modules then reg.modules = {} end
reg.modules[obj] = obj
reg.modules[name] = obj
- table.insert(reg.modules, name)
- table.sort(reg.modules)
return obj,name
end
function Dongle:HasModule(module)
local reg = lookup[self]
- assert(3, reg, "You must call 'HasModule' from a registered Dongle.")
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "HasModule"))
argcheck(module, 2, "string", "table")
return reg.modules[module]
@@ -229,56 +265,24 @@ end
local function ModuleIterator(t, name)
if not t then return end
- local module
+ local obj
repeat
- name,module = next(t, name)
+ name,obj = next(t, name)
until type(name) == "string" or not name
- if not name then return end
- return name, module
+ return name,obj
end
function Dongle:IterateModules()
local reg = lookup[self]
- assert(3, reg, "You must call 'IterateModules' from a registered Dongle.")
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "IterateModules"))
return ModuleIterator, reg.modules
end
-local function ADDON_LOADED(event, ...)
- for i=1, #loadqueue do
- local obj = loadqueue[i]
- table.insert(loadorder, obj)
-
- if type(obj.Initialize) == "function" then
- safecall(obj.Initialize, obj)
- end
-
- if Dongle.initialized and type(obj.Enable) == "function" then
- safecall(obj.Enable, obj)
- end
- loadqueue[i] = nil
- end
-end
-
-local function PLAYER_LOGOUT(event)
- self:ClearDBDefaults()
- for k,v in pairs(registry) do
- local obj = v.obj
- if type(obj["Disable"]) == "function" then
- safecall(obj["Disable"], obj)
- end
- end
-end
-
-local function PLAYER_LOGIN()
- Dongle.initialized = true
- for i,obj in ipairs(loadorder) do
- if type(obj.Enable) == "function" then
- safecall(obj.Enable, obj)
- end
- end
-end
+--[[-------------------------------------------------------------------------
+ Event registration system
+---------------------------------------------------------------------------]]
local function OnEvent(frame, event, ...)
local eventTbl = events[event]
@@ -295,11 +299,21 @@ local function OnEvent(frame, event, ...)
end
end
+local specialEvents = {
+ ["PLAYER_LOGIN"] = "Enable",
+ ["PLAYER_LOGOUT"] = "Disable",
+}
+
function Dongle:RegisterEvent(event, func)
local reg = lookup[self]
- assert(3, reg, "You must call 'RegisterEvent' from a registered Dongle.")
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "RegisterEvent"))
argcheck(event, 2, "string")
argcheck(func, 3, "string", "function", "nil")
+
+ local special = (self ~= Dongle) and specialEvents[event]
+ if special then
+ error(string.format(L["EVENT_REGISTER_SPECIAL"], event, special), 3)
+ end
-- Name the method the same as the event if necessary
if not func then func = event end
@@ -313,12 +327,13 @@ end
function Dongle:UnregisterEvent(event)
local reg = lookup[self]
- assert(3, reg, "You must call 'UnregisterEvent' from a registered Dongle.")
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "UnregisterEvent"))
argcheck(event, 2, "string")
- if events[event] then
- events[event][self] = nil
- if not next(events[event]) then
+ local tbl = events[event]
+ if tbl then
+ tbl[self] = nil
+ if not next(tbl) then
events[event] = nil
frame:UnregisterEvent(event)
end
@@ -326,7 +341,8 @@ function Dongle:UnregisterEvent(event)
end
function Dongle:UnregisterAllEvents()
- assert(3, lookup[self], "You must call 'UnregisterAllEvents' from a registered Dongle.")
+ local reg = lookup[self]
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "UnregisterAllEvents"))
for event,tbl in pairs(events) do
tbl[self] = nil
@@ -337,13 +353,26 @@ function Dongle:UnregisterAllEvents()
end
end
+function Dongle:IsEventRegistered(event)
+ local reg = lookup[self]
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "IsEventRegistered"))
+ argcheck(event, 2, "string")
+
+ local tbl = events[event]
+ return tbl
+end
+
+--[[-------------------------------------------------------------------------
+ Inter-Addon Messaging System
+---------------------------------------------------------------------------]]
+
function Dongle:RegisterMessage(msg, func)
local reg = lookup[self]
- assert(3, reg, "You must call 'RegisterMessage' from a registered Dongle.")
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "RegisterMessage"))
argcheck(msg, 2, "string")
argcheck(func, 3, "string", "function", "nil")
- -- Name the method the same as the event if necessary
+ -- Name the method the same as the message if necessary
if not func then func = msg end
if not messages[msg] then
@@ -354,19 +383,21 @@ end
function Dongle:UnregisterMessage(msg)
local reg = lookup[self]
- assert(3, reg, "You must call 'UnregisterMessage' from a registered Dongle.")
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "UnregisterMessage"))
argcheck(msg, 2, "string")
- if messages[msg] then
- messages[msg][self] = nil
- if not next(messages[msg]) then
+ local tbl = messages[msg]
+ if tbl then
+ tbl[self] = nil
+ if not next(tbl) then
messages[msg] = nil
end
end
end
function Dongle:UnregisterAllMessages()
- assert(3, lookup[self], "You must call 'UnregisterAllMessages' from a registered Dongle.")
+ local reg = lookup[self]
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "UnregisterAllMessages"))
for msg,tbl in pairs(messages) do
tbl[self] = nil
@@ -384,7 +415,7 @@ function Dongle:TriggerMessage(msg, ...)
for obj,func in pairs(msgTbl) do
if type(func) == "string" then
if type(obj[func]) == "function" then
- safecall(obj[func], obj, msg, ...)
+ safecall(obj[func], obj, msg, ...)
end
else
safecall(func, msg, ...)
@@ -392,141 +423,239 @@ function Dongle:TriggerMessage(msg, ...)
end
end
-function Dongle:EnableDebug(level)
+function Dongle:IsMessageRegistered(msg)
local reg = lookup[self]
- assert(3, reg, "You must call 'EnableDebug' from a registered Dongle.")
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "IsMessageRegistered"))
+ argcheck(msg, 2, "string")
+
+ local tbl = messages[msg]
+ return tbl[self]
+end
+
+--[[-------------------------------------------------------------------------
+ Debug and Print utility functions
+---------------------------------------------------------------------------]]
+
+function Dongle:EnableDebug(level, frame)
+ local reg = lookup[self]
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "EnableDebug"))
argcheck(level, 2, "number", "nil")
+ argcheck(frame, 3, "table", "nil")
+ assert(3, type(frame) == "nil" or type(frame.AddMessage) == "function", L["ADDMESSAGE_REQUIRED"])
+ reg.debugFrame = frame or ChatFrame1
reg.debugLevel = level
end
-do
- local function argsToStrings(a1, ...)
- if select("#", ...) > 0 then
- return tostring(a1), argsToStrings(...)
- else
- return tostring(a1)
- end
- end
-
- local function printHelp(obj, method, msg, ...)
- local reg = lookup[obj]
- assert(4, reg, "You must call '"..method.."' from a registered Dongle.")
+function Dongle:IsDebugEnabled()
+ local reg = lookup[self]
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "EnableDebug"))
- local name = reg.name
- msg = "|cFF33FF99"..name.."|r: "..tostring(msg)
- if select("#", ...) > 0 then
- msg = string.join(", ", msg, argsToStrings(...))
- end
+ return reg.debugLevel, reg.debugFrame
+end
- ChatFrame1:AddMessage(msg)
+local function argsToStrings(a1, ...)
+ if select("#", ...) > 0 then
+ return tostring(a1), argsToStrings(...)
+ else
+ return tostring(a1)
end
+end
- local function printFHelp(obj, method, msg, ...)
- local reg = lookup[obj]
- assert(4, reg, "You must call '"..method.."' from a registered Dongle.")
+local function printHelp(obj, method, frame, msg, ...)
+ local reg = lookup[obj]
+ assert(4, reg, string.format(L["MUST_CALLFROM_REGISTERED"], method))
- local name = reg.name
- local success,txt = pcall(string.format, "|cFF33FF99%s|r: "..msg, name, ...)
- if success then
- ChatFrame1:AddMessage(txt)
- else
- error(string.gsub(txt, "'%?'", string.format("'%s'", method)), 3)
- end
+ local name = reg.name
+ msg = "|cFF33FF99"..name.."|r: "..tostring(msg)
+ if select("#", ...) > 0 then
+ msg = string.join(", ", msg, argsToStrings(...))
end
- function Dongle:Print(...)
- return printHelp(self, "Print", ...)
- end
+ frame:AddMessage(msg)
+end
- function Dongle:PrintF(...)
- return printFHelp(self, "PrintF", ...)
+local function printFHelp(obj, method, frame, msg, ...)
+ local reg = lookup[obj]
+ assert(4, reg, string.format(L["MUST_CALLFROM_REGISTERED"], method))
+
+ local name = reg.name
+ local success,txt = pcall(string.format, "|cFF33FF99%s|r: "..msg, name, ...)
+ if success then
+ frame:AddMessage(txt)
+ else
+ error(string.gsub(txt, "'%?'", string.format("'%s'", method)), 3)
end
+end
- function Dongle:Debug(level, ...)
- local reg = lookup[self]
- assert(3, reg, "You must call 'Debug' from a registered Dongle.")
- argcheck(level, 2, "number")
+function Dongle:Print(msg, ...)
+ local reg = lookup[self]
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "Print"))
+ argcheck(msg, 2, "number", "string", "boolean", "table", "function", "thread", "userdata")
+ return printHelp(self, "Print", DEFAULT_CHAT_FRAME, msg, ...)
+end
- if reg.debugLevel and level <= reg.debugLevel then
- printHelp(self, "Debug", ...)
- end
+function Dongle:PrintF(msg, ...)
+ local reg = lookup[self]
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "PrintF"))
+ argcheck(msg, 2, "number", "string", "boolean", "table", "function", "thread", "userdata")
+ return printFHelp(self, "PrintF", DEFAULT_CHAT_FRAME, msg, ...)
+end
+
+function Dongle:Debug(level, ...)
+ local reg = lookup[self]
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "Debug"))
+ argcheck(level, 2, "number")
+
+ if reg.debugLevel and level <= reg.debugLevel then
+ printHelp(self, "Debug", reg.debugFrame, ...)
end
+end
- function Dongle:DebugF(level, ...)
- local reg = lookup[self]
- assert(3, reg, "You must call 'DebugF' from a registered Dongle.")
- argcheck(level, 2, "number")
+function Dongle:DebugF(level, ...)
+ local reg = lookup[self]
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "DebugF"))
+ argcheck(level, 2, "number")
- if reg.debugLevel and level <= reg.debugLevel then
- printFHelp(self, "DebugF", ...)
- end
+ if reg.debugLevel and level <= reg.debugLevel then
+ printFHelp(self, "DebugF", reg.debugFrame, ...)
end
end
+--[[-------------------------------------------------------------------------
+ Database System
+---------------------------------------------------------------------------]]
+
local dbMethods = {
"RegisterDefaults", "SetProfile", "GetProfiles", "DeleteProfile", "CopyProfile",
"ResetProfile", "ResetDB",
+ "RegisterNamespace",
}
-local function initdb(parent, name, defaults, defaultProfile, olddb)
- local sv = getglobal(name)
+local function copyDefaults(dest, src, force)
+ for k,v in pairs(src) do
+ if type(v) == "table" then
+ if not dest[k] then dest[k] = {} end
+ copyDefaults(dest[k], v, force)
+ else
+ if (dest[k] == nil) or force then
+ dest[k] = v
+ end
+ end
+ end
+end
+
+local function removeDefaults(db, defaults)
+ if not db then return end
+ for k,v in pairs(defaults) do
+ if type(v) == "table" and db[k] then
+ removeDefaults(db[k], v)
+ if not next(db[k]) then
+ db[k] = nil
+ end
+ else
+ if db[k] == defaults[k] then
+ db[k] = nil
+ end
+ end
+ end
+end
- if not sv then
- sv = {}
- setglobal(name, sv)
+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
+
+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
+ Dongle:TriggerMessage("DONGLE_PROFILE_CREATED", t, rawget(t, "parent"), rawget(t, "sv_name"), key)
+ end
+ 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
+}
- -- Lets do the initial setup
-
- sv.char = {}
- sv.faction = {}
- sv.realm = {}
- sv.class = {}
- sv.global = {}
- sv.profiles = {}
+local function initdb(parent, name, defaults, defaultProfile, olddb)
+ -- This allows us to use an arbitrary table as base instead of saved variable name
+ local sv
+ if type(name) == "string" then
+ sv = getglobal(name)
+ if not sv then
+ sv = {}
+ setglobal(name, sv)
+ end
+ elseif type(name) == "table" then
+ sv = name
end
- -- Initialize the specific databases
+ -- Generate the database keys for each section
local char = string.format("%s of %s", UnitName("player"), GetRealmName())
- local realm = string.format("%s", GetRealmName())
+ local realm = GetRealmName()
local class = UnitClass("player")
local race = select(2, UnitRace("player"))
local faction = UnitFactionGroup("player")
+ local factionrealm = string.format("%s - %s", faction, realm)
- -- Initialize the containers
- if not sv.char then sv.char = {} end
- if not sv.realm then sv.realm = {} end
- if not sv.class then sv.class = {} end
- if not sv.faction then sv.faction = {} end
- if not sv.global then sv.global = {} end
- if not sv.profiles then sv.profiles = {} end
+ -- Make a container for profile keys
if not sv.profileKeys then sv.profileKeys = {} end
- -- Initialize this characters profiles
- if not sv.char[char] then sv.char[char] = {} end
- if not sv.realm[realm] then sv.realm[realm] = {} end
- if not sv.class[class] then sv.class[class] = {} end
- if not sv.faction[faction] then sv.faction[faction] = {} end
-
-- Try to get the profile selected from the char db
local profileKey = sv.profileKeys[char] or defaultProfile or char
sv.profileKeys[char] = profileKey
- local profileCreated
- if not sv.profiles[profileKey] then sv.profiles[profileKey] = {} profileCreated = true end
+ local keyTbl= {
+ ["char"] = char,
+ ["realm"] = realm,
+ ["class"] = class,
+ ["race"] = race,
+ ["faction"] = faction,
+ ["factionrealm"] = factionrealm,
+ ["global"] = true,
+ ["profile"] = profileKey,
+ }
+ -- If we've been passed an old database, clear it out
if olddb then
for k,v in pairs(olddb) do olddb[k] = nil end
end
- local db = olddb or {}
- db.char = sv.char[char]
- db.realm = sv.realm[realm]
- db.class = sv.class[class]
- db.faction = sv.faction[faction]
- db.profile = sv.profiles[profileKey]
- db.global = sv.global
- db.profiles = sv.profiles
+ -- Give this database the metatable so it initializes dynamically
+ local db = setmetatable(olddb or {}, dbmt)
-- Copy methods locally
for idx,method in pairs(dbMethods) do
@@ -534,81 +663,41 @@ local function initdb(parent, name, defaults, defaultProfile, olddb)
end
-- Set some properties in the object we're returning
+ db.profiles = sv.profiles
+ db.keys = keyTbl
db.sv = sv
db.sv_name = name
- db.profileKey = profileKey
+ db.defaults = defaults
db.parent = parent
- db.charKey = char
- db.realmKey = realm
- db.classKey = class
- db.factionKey = faction
databases[db] = true
-
- if defaults then
- db:RegisterDefaults(defaults)
- end
-
- return db,profileCreated
+
+ return db
end
function Dongle:InitializeDB(name, defaults, defaultProfile)
local reg = lookup[self]
- assert(3, reg, "You must call 'InitializeDB' from a registered Dongle.")
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "InitializeDB"))
argcheck(name, 2, "string")
argcheck(defaults, 3, "table", "nil")
argcheck(defaultProfile, 4, "string", "nil")
- local db,profileCreated = initdb(self, name, defaults, defaultProfile)
-
- if profileCreated then
- Dongle:TriggerMessage("DONGLE_PROFILE_CREATED", db, self, db.sv_name, db.profileKey)
- end
- return db
-end
-
-local function copyDefaults(dest, src, force)
- for k,v in pairs(src) do
- if type(v) == "table" then
- if not dest[k] then dest[k] = {} end
- copyDefaults(dest[k], v, force)
- else
- if (dest[k] == nil) or force then
- dest[k] = v
- end
- end
- end
+ return initdb(self, name, defaults, defaultProfile)
end
-- This function operates on a Dongle DB object
function Dongle.RegisterDefaults(db, defaults)
- assert(3, databases[db], "You must call 'RegisterDefaults' from a Dongle database object.")
+ assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "RegisterDefaults"))
+ assert(3, db.defaults == nil, L["REPLACE_DEFAUTS"])
argcheck(defaults, 2, "table")
- if defaults.char then copyDefaults(db.char, defaults.char) end
- if defaults.realm then copyDefaults(db.realm, defaults.realm) end
- if defaults.class then copyDefaults(db.class, defaults.class) end
- if defaults.faction then copyDefaults(db.faction, defaults.faction) end
- if defaults.global then copyDefaults(db.global, defaults.global) end
- if defaults.profile then copyDefaults(db.profile, defaults.profile) end
-
- db.defaults = defaults
-end
-
-local function removeDefaults(db, defaults)
- if not db then return end
- for k,v in pairs(defaults) do
- if type(v) == "table" and db[k] then
- removeDefaults(db[k], v)
- if not next(db[k]) then
- db[k] = nil
- end
- else
- if db[k] == defaults[k] then
- db[k] = nil
- end
+ for section,key in pairs(db.keys) do
+ if defaults[section] and rawget(db, section) then
+ copyDefaults(db[section], defaults[section])
end
end
+
+ db.defaults = defaults
end
function Dongle:ClearDBDefaults()
@@ -617,70 +706,47 @@ function Dongle:ClearDBDefaults()
local sv = db.sv
if db and defaults then
- if defaults.char then removeDefaults(db.char, defaults.char) end
- if defaults.realm then removeDefaults(db.realm, defaults.realm) end
- if defaults.class then removeDefaults(db.class, defaults.class) end
- if defaults.faction then removeDefaults(db.faction, defaults.faction) end
- if defaults.global then removeDefaults(db.global, defaults.global) end
- if defaults.profile then
- for k,v in pairs(sv.profiles) do
- removeDefaults(sv.profiles[k], defaults.profile)
+ for section,key in pairs(db.keys) do
+ if defaults[section] and rawget(db, section) then
+ removeDefaults(db[section], defaults[section])
end
end
- -- Remove any blank "profiles"
- if not next(db.char) then sv.char[db.charKey] = nil end
- if not next(db.realm) then sv.realm[db.realmKey] = nil end
- if not next(db.class) then sv.class[db.classKey] = nil end
- if not next(db.faction) then sv.faction[db.factionKey] = nil end
- if not next(db.global) then sv.global = nil end
+ for section,key in pairs(db.keys) do
+ local tbl = rawget(db, section)
+ if tbl and not next(tbl) then
+ sv[section][key] = nil
+ end
+ end
end
end
end
function Dongle.SetProfile(db, name)
- assert(3, databases[db], "You must call 'SetProfile' from a Dongle database object.")
+ assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "SetProfile"))
argcheck(name, 2, "string")
- local sv = db.sv
- local old = sv.profiles[db.profileKey]
- local new = sv.profiles[name]
- local profileCreated
-
- if not new then
- sv.profiles[name] = {}
- new = sv.profiles[name]
- profileCreated = true
- end
+ local old = db.profile
+ local defaults = db.defaults and db.defaults.profile
- if db.defaults and db.defaults.profile then
+ if defaults then
-- Remove the defaults from the old profile
- removeDefaults(old, db.defaults.profile)
-
- -- Inject the defaults into the new profile
- copyDefaults(new, db.defaults.profile)
- end
-
- db.profile = new
-
- -- Save this new profile name
- sv.profileKeys[db.charKey] = name
- db.profileKey = name
-
- if profileCreated then
- Dongle:TriggerMessage("DONGLE_PROFILE_CREATED", db, db.parent, db.sv_name, db.profileKey)
+ removeDefaults(old, defaults)
end
- Dongle:TriggerMessage("DONGLE_PROFILE_CHANGED", db, db.parent, db.sv_name, db.profileKey)
+ db.profile = nil
+ db.keys["profile"] = name
+
+ Dongle:TriggerMessage("DONGLE_PROFILE_CHANGED", db, db.parent, db.sv_name, db.keys.profile)
end
function Dongle.GetProfiles(db, t)
- assert(3, databases[db], "You must call 'GetProfiles' from a Dongle database object.")
+ assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "GetProfiles"))
argcheck(t, 2, "table", "nil")
t = t or {}
local i = 1
- for profileKey in pairs(db.profiles) do
+ for profileKey in pairs(db.sv.profiles) do
t[i] = profileKey
i = i + 1
end
@@ -688,77 +754,110 @@ function Dongle.GetProfiles(db, t)
end
function Dongle.DeleteProfile(db, name)
- assert(3, databases[db], "You must call 'DeleteProfile' from a Dongle database object.")
+ assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "DeleteProfile"))
argcheck(name, 2, "string")
- if db.profileKey == name then
- error("You cannot delete your active profile. Change profiles, then attempt to delete.", 2)
+ if db.keys.profile == name then
+ error(L["CANNOT_DELETE_ACTIVE_PROFILE"], 2)
end
+
+ assert(type(db.sv.profiles[name]) == "table", L["DELETE_NONEXISTANT_PROFILE"])
db.sv.profiles[name] = nil
Dongle:TriggerMessage("DONGLE_PROFILE_DELETED", db, db.parent, db.sv_name, name)
end
function Dongle.CopyProfile(db, name)
- assert(3, databases[db], "You must call 'CopyProfile' from a Dongle database object.")
+ assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "CopyProfile"))
argcheck(name, 2, "string")
- assert(3, db.profileKey ~= name, "Source/Destination profile cannot be the same profile")
- assert(3, type(db.sv.profiles[name]) == "table", "Profile \""..name.."\" doesn't exist.")
+ assert(3, db.keys.profile ~= name, L["SAME_SOURCE_DEST"])
+ assert(3, type(db.sv.profiles[name]) == "table", string.format(L["PROFILE_DOES_NOT_EXIST"], name))
local profile = db.profile
local source = db.sv.profiles[name]
copyDefaults(profile, source, true)
- Dongle:TriggerMessage("DONGLE_PROFILE_COPIED", db, db.parent, db.sv_name, name, db.profileKey)
+ Dongle:TriggerMessage("DONGLE_PROFILE_COPIED", db, db.parent, db.sv_name, name, db.keys.profile)
end
function Dongle.ResetProfile(db)
- assert(3, databases[db], "You must call 'ResetProfile' from a Dongle database object.")
+ assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "ResetProfile"))
local profile = db.profile
for k,v in pairs(profile) do
profile[k] = nil
end
- if db.defaults and db.defaults.profile then
- copyDefaults(profile, db.defaults.profile)
+
+ local defaults = db.defaults and db.defaults.profile
+ if defaults then
+ copyDefaults(profile, defaults)
end
- Dongle:TriggerMessage("DONGLE_PROFILE_RESET", db, db.parent, db.sv_name, db.profileKey)
+ Dongle:TriggerMessage("DONGLE_PROFILE_RESET", db, db.parent, db.sv_name, db.keys.profile)
end
function Dongle.ResetDB(db, defaultProfile)
- assert(3, databases[db], "You must call 'ResetDB' from a Dongle database object.")
+ assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "ResetDB"))
argcheck(defaultProfile, 2, "nil", "string")
local sv = db.sv
for k,v in pairs(sv) do
sv[k] = nil
end
-
- local parent = db.parent
+ local parent = db.parent
+
initdb(parent, db.sv_name, db.defaults, defaultProfile, db)
- Dongle:TriggerMessage("DONGLE_DATABASE_RESET", db, parent, db.sv_name, db.profileKey)
- Dongle:TriggerMessage("DONGLE_PROFILE_CREATED", db, db.parent, db.sv_name, db.profileKey)
- Dongle:TriggerMessage("DONGLE_PROFILE_CHANGED", db, db.parent, db.sv_name, db.profileKey)
+ Dongle:TriggerMessage("DONGLE_DATABASE_RESET", db, parent, db.sv_name, db.keys.profile)
+ Dongle:TriggerMessage("DONGLE_PROFILE_CHANGED", db, db.parent, db.sv_name, db.keys.profile)
return db
end
+function Dongle.RegisterNamespace(db, name, defaults)
+ assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "RegisterNamespace"))
+ argcheck(name, 2, "string")
+ argcheck(defaults, 3, "nil", "string")
+
+ local sv = db.sv
+ if not sv.namespaces then sv.namespaces = {} end
+ if not sv.namespaces[name] then
+ sv.namespaces[name] = {}
+ end
+
+ local newDB = initdb(db, sv.namespaces[name], defaults, db.keys.profile)
+ if not db.children then db.children = {} end
+ table.insert(db.children, newDB)
+ return newDB
+end
+
+--[[-------------------------------------------------------------------------
+ Slash Command System
+---------------------------------------------------------------------------]]
+
local slashCmdMethods = {
+ "InjectDBCommands",
"RegisterSlashHandler",
"PrintUsage",
}
local function OnSlashCommand(cmd, cmd_line)
if cmd.patterns then
- for pattern, tbl in pairs(cmd.patterns) do
+ for idx,tbl in pairs(cmd.patterns) do
+ local pattern = tbl.pattern
if string.match(cmd_line, pattern) then
+ local handler = tbl.handler
if type(tbl.handler) == "string" then
- cmd.parent[tbl.handler](cmd.parent, string.match(cmd_line, pattern))
+ local obj
+ -- Look in the command object before we look at the parent object
+ if cmd[handler] then obj = cmd end
+ if cmd.parent[handler] then obj = cmd.parent end
+ if obj then
+ obj[handler](obj, string.match(cmd_line, pattern))
+ end
else
- tbl.handler(string.match(cmd_line, pattern))
+ handler(string.match(cmd_line, pattern))
end
return
end
@@ -769,14 +868,14 @@ end
function Dongle:InitializeSlashCommand(desc, name, ...)
local reg = lookup[self]
- assert(3, reg, "You must call 'InitializeSlashCommand' from a registered Dongle.")
+ assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "InitializeSlashCommand"))
argcheck(desc, 2, "string")
argcheck(name, 3, "string")
argcheck(select(1, ...), 4, "string")
for i = 2,select("#", ...) do
argcheck(select(i, ...), i+2, "string")
end
-
+
local cmd = {}
cmd.desc = desc
cmd.name = name
@@ -785,7 +884,7 @@ function Dongle:InitializeSlashCommand(desc, name, ...)
for idx,method in pairs(slashCmdMethods) do
cmd[method] = Dongle[method]
end
-
+
local genv = getfenv(0)
for i = 1,select("#", ...) do
@@ -800,7 +899,7 @@ function Dongle:InitializeSlashCommand(desc, name, ...)
end
function Dongle.RegisterSlashHandler(cmd, desc, pattern, handler)
- assert(3, commands[cmd], "You must call 'RegisterSlashHandler' from a Dongle slash command object.")
+ assert(3, commands[cmd], string.format(L["MUST_CALLFROM_SLASH"], "RegisterSlashHandler"))
argcheck(desc, 2, "string")
argcheck(pattern, 3, "string")
@@ -809,70 +908,149 @@ function Dongle.RegisterSlashHandler(cmd, desc, pattern, handler)
if not cmd.patterns then
cmd.patterns = {}
end
- cmd.patterns[pattern] = {
+
+ table.insert(cmd.patterns, {
["desc"] = desc,
["handler"] = handler,
- }
+ ["pattern"] = pattern,
+ })
end
function Dongle.PrintUsage(cmd)
- assert(3, commands[cmd], "You must call 'PrintUsage' from a Dongle slash command object.")
+ assert(3, commands[cmd], string.format(L["MUST_CALLFROM_SLASH"], "PrintUsage"))
local usage = cmd.desc.."\n".."/"..table.concat(cmd.slashes, ", /")..":\n"
if cmd.patterns then
- local descs = {}
- for pattern,tbl in pairs(cmd.patterns) do
- table.insert(descs, tbl.desc)
- end
- table.sort(descs)
- for _,desc in pairs(descs) do
- usage = usage.." - "..desc.."\n"
+ for idx,tbl in ipairs(cmd.patterns) do
+ usage = usage.." - "..tbl.desc.."\n"
end
end
cmd.parent:Print(usage)
end
+local dbcommands = {
+ ["copy"] = {
+ L["DBSLASH_PROFILE_COPY_DESC"],
+ L["DBSLASH_PROFILE_COPY_PATTERN"],
+ "CopyProfile",
+ },
+ ["delete"] = {
+ L["DBSLASH_PROFILE_DELETE_DESC"],
+ L["DBSLASH_PROFILE_DELETE_PATTERN"],
+ "DeleteProfile",
+ },
+ ["list"] = {
+ L["DBSLASH_PROFILE_LIST_DESC"],
+ L["DBSLASH_PROFILE_LIST_PATTERN"],
+ },
+ ["reset"] = {
+ L["DBSLASH_PROFILE_RESET_DESC"],
+ L["DBSLASH_PROFILE_RESET_PATTERN"],
+ "ResetProfile",
+ },
+ ["set"] = {
+ L["DBSLASH_PROFILE_SET_DESC"],
+ L["DBSLASH_PROFILE_SET_PATTERN"],
+ "SetProfile",
+ },
+}
+
+function Dongle.InjectDBCommands(cmd, db, ...)
+ assert(3, commands[cmd], string.format(L["MUST_CALLFROM_SLASH"], "InjectDBCommands"))
+ assert(3, databases[db], string.format(L["BAD_ARGUMENT_DB"], 2, "InjectDBCommands"))
+ local argc = select("#", ...)
+ assert(3, argc > 0, L["INJECTDB_USAGE"])
+
+ for i=1,argc do
+ local cmdname = string.lower(select(i, ...))
+ local entry = dbcommands[cmdname]
+ assert(entry, L["INJECTDB_USAGE"])
+ local func = entry[3]
+
+ local handler
+ if cmdname == "list" then
+ handler = function(...)
+ local profiles = db:GetProfiles()
+ db.parent:Print(L["DBSLASH_PROFILE_LIST_OUT"] .. "\n" .. strjoin("\n", unpack(profiles)))
+ end
+ else
+ handler = function(...) db[entry[3]](db, ...) end
+ end
+
+ cmd:RegisterSlashHandler(entry[1], entry[2], handler)
+ end
+end
+
--[[-------------------------------------------------------------------------
- Begin DongleStub required functions and registration
+ Internal Message/Event Handlers
+---------------------------------------------------------------------------]]
+
+local function PLAYER_LOGOUT(event)
+ Dongle:ClearDBDefaults()
+ for k,v in pairs(registry) do
+ local obj = v.obj
+ if type(obj["Disable"]) == "function" then
+ safecall(obj["Disable"], obj)
+ end
+ end
+end
+
+local function PLAYER_LOGIN()
+ Dongle.initialized = true
+ for i,obj in ipairs(loadorder) do
+ if type(obj.Enable) == "function" then
+ safecall(obj.Enable, obj)
+ end
+ end
+end
+
+local function ADDON_LOADED(event, ...)
+ for i=1, #loadqueue do
+ local obj = loadqueue[i]
+ table.insert(loadorder, obj)
+
+ if type(obj.Initialize) == "function" then
+ safecall(obj.Initialize, obj)
+ end
+
+ if Dongle.initialized and type(obj.Enable) == "function" then
+ safecall(obj.Enable, obj)
+ end
+ loadqueue[i] = nil
+ end
+end
+
+local function DONGLE_PROFILE_CHANGED(msg, db, parent, sv_name, profileKey)
+ local children = db.children
+ if children then
+ for i,namespace in ipairs(children) do
+ namespace:SetProfile(profileKey)
+ end
+ end
+end
+
+--[[-------------------------------------------------------------------------
+ DongleStub required functions and registration
---------------------------------------------------------------------------]]
function Dongle:GetVersion() return major,minor end
local function Activate(self, old)
if old then
- self.registry = old.registry or registry
- self.lookup = old.lookup or lookup
- self.loadqueue = old.loadqueue or loadqueue
- self.loadorder = old.loadorder or loadorder
- self.events = old.events or events
- self.databases = old.databases or databases
- self.commands = old.commands or commands
- self.messages = old.messages or messages
-
- registry = self.registry
- lookup = self.lookup
- loadqueue = self.loadqueue
- loadorder = self.loadorder
- events = self.events
- databases = self.databases
- commands = self.commands
- messages = self.messages
-
- frame = old.frame
+ registry = old.registry or registry
+ lookup = old.lookup or lookup
+ loadqueue = old.loadqueue or loadqueue
+ loadorder = old.loadorder or loadorder
+ events = old.events or events
+ databases = old.databases or databases
+ commands = old.commands or commands
+ messages = old.messages or messages
+ frame = old.frame or CreateFrame("Frame")
local reg = self.registry[major]
reg.obj = self
- lookup[self] = reg
- lookup[major] = reg
else
- self.registry = registry
- self.lookup = lookup
- self.loadqueue = loadqueue
- self.loadorder = loadorder
- self.events = events
- self.databases = databases
- self.commands = commands
- self.messages = messages
+ frame = CreateFrame("Frame")
local reg = {obj = self, name = "Dongle"}
registry[major] = reg
@@ -880,17 +1058,27 @@ local function Activate(self, old)
lookup[major] = reg
end
- if not frame then
- frame = CreateFrame("Frame")
- end
-
+ self.registry = registry
+ self.lookup = lookup
+ self.loadqueue = loadqueue
+ self.loadorder = loadorder
+ self.events = events
+ self.databases = databases
+ self.commands = commands
+ self.messages = messages
self.frame = frame
+
+ local reg = self.registry[major]
+ lookup[self] = reg
+ lookup[major] = reg
+
frame:SetScript("OnEvent", OnEvent)
-- Register for events using Dongle itself
self:RegisterEvent("ADDON_LOADED", ADDON_LOADED)
self:RegisterEvent("PLAYER_LOGIN", PLAYER_LOGIN)
self:RegisterEvent("PLAYER_LOGOUT", PLAYER_LOGOUT)
+ self:RegisterMessage("DONGLE_PROFILE_CHANGED", DONGLE_PROFILE_CHANGED)
-- Convert all the modules handles
for name,obj in pairs(registry) do
@@ -905,7 +1093,7 @@ local function Activate(self, old)
db[method] = self[method]
end
end
-
+
-- Convert all slash command methods
for cmd in pairs(commands) do
for idx,method in ipairs(slashCmdMethods) do
@@ -919,4 +1107,4 @@ local function Deactivate(self, new)
lookup[self] = nil
end
-DongleStub:Register(Dongle, Activate, Deactivate)
+Dongle = DongleStub:Register(Dongle, Activate, Deactivate)