Quantcast

* Updated to Dongle-Beta1

James Whitehead II [03-18-07 - 04:29]
* Updated to Dongle-Beta1
Filename
Dongle.lua
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)