Quantcast

* Dongle-1.0

James Whitehead II [04-14-07 - 01:18]
* Dongle-1.0
Filename
Dongle.lua
diff --git a/Dongle.lua b/Dongle.lua
index aa02beb..ce96b4d 100644
--- a/Dongle.lua
+++ b/Dongle.lua
@@ -28,8 +28,8 @@
   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------]]
-local major = "DongleStub-Beta0"
-local minor = tonumber(string.match("$Revision: 221 $", "(%d+)") or 1)
+local major = "DongleStub"
+local minor = tonumber(string.match("$Revision: 291 $", "(%d+)") or 1)

 local g = getfenv(0)

@@ -46,10 +46,24 @@ if not g.DongleStub or g.DongleStub:IsNewerVersion(major, minor) then

 	function lib:IsNewerVersion(major, minor)
 		local versionData = self.versions and self.versions[major]
-
+
+		-- If DongleStub versions have differing major version names
+		-- such as DongleStub-Beta0 and DongleStub-1.0-RC2 then a second
+		-- instance will be loaded, with older logic.  This code attempts
+		-- to compensate for that by matching the major version against
+		-- "^DongleStub", and handling the version check correctly.
+
+		if major:match("^DongleStub") then
+			local oldmajor,oldminor = self:GetVersion()
+			if self.versions and self.versions[oldmajor] then
+				return minor > oldminor
+			else
+				return true
+			end
+		end
+
 		if not versionData then return true end
 		local oldmajor,oldminor = versionData.instance:GetVersion()
-
 		return minor > oldminor
 	end

@@ -59,7 +73,19 @@ if not g.DongleStub or g.DongleStub:IsNewerVersion(major, minor) then
 	end

 	function lib:Register(newInstance, activate, deactivate)
+		assert(type(newInstance.GetVersion) == "function",
+			"Attempt to register a library with DongleStub that does not have a 'GetVersion' method.")
+
 		local major,minor = newInstance:GetVersion()
+		assert(type(major) == "string",
+			"Attempt to register a library with DongleStub that does not have a proper major version.")
+		assert(type(minor) == "number",
+			"Attempt to register a library with DongleStub that does not have a proper minor version.")
+
+		-- Generate a log of all library registrations
+		if not self.log then self.log = {} end
+		table.insert(self.log, string.format("Register: %s, %s", major, minor))
+
 		if not self:IsNewerVersion(major, minor) then return false end
 		if not self.versions then self.versions = {} end

@@ -73,6 +99,7 @@ if not g.DongleStub or g.DongleStub:IsNewerVersion(major, minor) then

 			self.versions[major] = versionData
 			if type(activate) == "function" then
+				table.insert(self.log, string.format("Activate: %s, %s", major, minor))
 				activate(newInstance)
 			end
 			return newInstance
@@ -85,11 +112,13 @@ if not g.DongleStub or g.DongleStub:IsNewerVersion(major, minor) then

 		local skipCopy
 		if type(activate) == "function" then
-			 skipCopy = activate(newInstance, oldInstance)
+			table.insert(self.log, string.format("Activate: %s, %s", major, minor))
+			skipCopy = activate(newInstance, oldInstance)
 		end

 		-- Deactivate the old libary if necessary
 		if type(oldDeactivate) == "function" then
+			table.insert(self.log, string.format("Deactivate: %s, %s", major, minor))
 			oldDeactivate(oldInstance, newInstance)
 		end

@@ -103,23 +132,29 @@ if not g.DongleStub or g.DongleStub:IsNewerVersion(major, minor) then
 	function lib:GetVersion() return major,minor end

 	local function Activate(new, old)
+		-- This code ensures that we'll move the versions table even
+		-- if the major version names are different, in the case of
+		-- DongleStub
+		if not old then old = g.DongleStub end
+
 		if old then
 			new.versions = old.versions
+			new.log = old.log
 		end
 		g.DongleStub = new
 	end

 	-- Actually trigger libary activation here
 	local stub = g.DongleStub or lib
-	stub:Register(lib, Activate)
+	lib = stub:Register(lib, Activate)
 end

 --[[-------------------------------------------------------------------------
   Begin Library Implementation
 ---------------------------------------------------------------------------]]

-local major = "Dongle-Beta1"
-local minor = tonumber(string.match("$Revision: 260 $", "(%d+)") or 1)
+local major = "Dongle-1.0"
+local minor = tonumber(string.match("$Revision: 303 $", "(%d+)") or 1)

 assert(DongleStub, string.format("Dongle requires DongleStub.", major))

@@ -129,7 +164,7 @@ local Dongle = {}
 local methods = {
 	"RegisterEvent", "UnregisterEvent", "UnregisterAllEvents", "IsEventRegistered",
 	"RegisterMessage", "UnregisterMessage", "UnregisterAllMessages", "TriggerMessage", "IsMessageRegistered",
-	"EnableDebug", "IsDebugEnabled", "Print", "PrintF", "Debug", "DebugF",
+	"EnableDebug", "IsDebugEnabled", "Print", "PrintF", "Debug", "DebugF", "Echo", "EchoF",
 	"InitializeDB",
 	"InitializeSlashCommand",
 	"NewModule", "HasModule", "IterateModules",
@@ -178,7 +213,7 @@ local L = {
 	["DBSLASH_PROFILE_SET_PATTERN"] = "^profile set (.+)$",
 	["DBSLASH_PROFILE_LIST_OUT"] = "Profile List:",
 }
-
+
 --[[-------------------------------------------------------------------------
   Utility functions for Dongle use
 ---------------------------------------------------------------------------]]
@@ -309,7 +344,7 @@ function Dongle:RegisterEvent(event, func)
 	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)
@@ -367,8 +402,7 @@ end
 ---------------------------------------------------------------------------]]

 function Dongle:RegisterMessage(msg, func)
-	local reg = lookup[self]
-	assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "RegisterMessage"))
+	argcheck(self, 1, "table")
 	argcheck(msg, 2, "string")
 	argcheck(func, 3, "string", "function", "nil")

@@ -382,8 +416,7 @@ function Dongle:RegisterMessage(msg, func)
 end

 function Dongle:UnregisterMessage(msg)
-	local reg = lookup[self]
-	assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "UnregisterMessage"))
+	argcheck(self, 1, "table")
 	argcheck(msg, 2, "string")

 	local tbl = messages[msg]
@@ -396,8 +429,7 @@ function Dongle:UnregisterMessage(msg)
 end

 function Dongle:UnregisterAllMessages()
-	local reg = lookup[self]
-	assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "UnregisterAllMessages"))
+	argcheck(self, 1, "table")

 	for msg,tbl in pairs(messages) do
 		tbl[self] = nil
@@ -408,6 +440,7 @@ function Dongle:UnregisterAllMessages()
 end

 function Dongle:TriggerMessage(msg, ...)
+	argcheck(self, 1, "table")
 	argcheck(msg, 2, "string")
 	local msgTbl = messages[msg]
 	if not msgTbl then return end
@@ -424,8 +457,7 @@ function Dongle:TriggerMessage(msg, ...)
 end

 function Dongle:IsMessageRegistered(msg)
-	local reg = lookup[self]
-	assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "IsMessageRegistered"))
+	argcheck(self, 1, "table")
 	argcheck(msg, 2, "string")

 	local tbl = messages[msg]
@@ -462,12 +494,16 @@ local function argsToStrings(a1, ...)
 	end
 end

-local function printHelp(obj, method, frame, msg, ...)
+local function printHelp(obj, method, header, frame, msg, ...)
 	local reg = lookup[obj]
 	assert(4, reg, string.format(L["MUST_CALLFROM_REGISTERED"], method))

 	local name = reg.name
-	msg = "|cFF33FF99"..name.."|r: "..tostring(msg)
+
+	if header then
+		msg = "|cFF33FF99"..name.."|r: "..tostring(msg)
+	end
+
 	if select("#", ...) > 0 then
 		msg = string.join(", ", msg, argsToStrings(...))
 	end
@@ -475,12 +511,20 @@ local function printHelp(obj, method, frame, msg, ...)
 	frame:AddMessage(msg)
 end

-local function printFHelp(obj, method, frame, msg, ...)
+local function printFHelp(obj, method, header, 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, ...)
+	local success,txt
+
+	if header then
+		msg = "|cFF33FF99%s|r: " .. msg
+		success,txt = pcall(string.format, msg, name, ...)
+	else
+		success,txt = pcall(string.format, msg, ...)
+	end
+
 	if success then
 		frame:AddMessage(txt)
 	else
@@ -490,16 +534,30 @@ end

 function Dongle:Print(msg, ...)
 	local reg = lookup[self]
-	assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "Print"))
+	assert(1, 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, ...)
+	return printHelp(self, "Print", true, DEFAULT_CHAT_FRAME, msg, ...)
 end

 function Dongle:PrintF(msg, ...)
 	local reg = lookup[self]
-	assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "PrintF"))
+	assert(1, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "PrintF"))
+	argcheck(msg, 2, "number", "string", "boolean", "table", "function", "thread", "userdata")
+	return printFHelp(self, "PrintF", true, DEFAULT_CHAT_FRAME, msg, ...)
+end
+
+function Dongle:Echo(msg, ...)
+	local reg = lookup[self]
+	assert(1, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "Echo"))
+	argcheck(msg, 2, "number", "string", "boolean", "table", "function", "thread", "userdata")
+	return printHelp(self, "Echo", false, DEFAULT_CHAT_FRAME, msg, ...)
+end
+
+function Dongle:EchoF(msg, ...)
+	local reg = lookup[self]
+	assert(1, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "EchoF"))
 	argcheck(msg, 2, "number", "string", "boolean", "table", "function", "thread", "userdata")
-	return printFHelp(self, "PrintF", DEFAULT_CHAT_FRAME, msg, ...)
+	return printFHelp(self, "EchoF", false, DEFAULT_CHAT_FRAME, msg, ...)
 end

 function Dongle:Debug(level, ...)
@@ -508,7 +566,7 @@ function Dongle:Debug(level, ...)
 	argcheck(level, 2, "number")

 	if reg.debugLevel and level <= reg.debugLevel then
-		printHelp(self, "Debug", reg.debugFrame, ...)
+		printHelp(self, "Debug", true, reg.debugFrame, ...)
 	end
 end

@@ -518,7 +576,7 @@ function Dongle:DebugF(level, ...)
 	argcheck(level, 2, "number")

 	if reg.debugLevel and level <= reg.debugLevel then
-		printFHelp(self, "DebugF", reg.debugFrame, ...)
+		printFHelp(self, "DebugF", true, reg.debugFrame, ...)
 	end
 end

@@ -532,9 +590,53 @@ local dbMethods = {
 	"RegisterNamespace",
 }

-local function copyDefaults(dest, src, force)
+local function copyTable(src)
+	local dest = {}
 	for k,v in pairs(src) do
+		if type(k) == "table" then
+			k = copyTable(k)
+		end
 		if type(v) == "table" then
+			v = copyTable(v)
+		end
+		dest[k] = v
+	end
+	return dest
+end
+
+local function copyDefaults(dest, src, force)
+	for k,v in pairs(src) do
+		if k == "*" then
+			if type(v) == "table" then
+				-- Values are tables, need some magic here
+				local mt = {
+					__cache = {},
+					__index = function(t,k)
+						local mt = getmetatable(dest)
+						local cache = rawget(mt, "__cache")
+						local tbl = rawget(cache, k)
+						if not tbl then
+							local parent = t
+							local parentkey = k
+							tbl = copyTable(v)
+							rawset(cache, k, tbl)
+							local mt = getmetatable(tbl)
+							local newindex = function(t,k,v)
+								rawset(parent, parentkey, t)
+								rawset(t, k, v)
+							end
+							rawset(mt, "__newindex", newindex)
+						end
+						return tbl
+					end,
+				}
+				setmetatable(dest, mt)
+			else
+				-- Values are not tables, so this is just a simple return
+				local mt = {__index = function() return v end}
+				setmetatable(dest, mt)
+			end
+		elseif type(v) == "table" then
 			if not dest[k] then dest[k] = {} end
 			copyDefaults(dest[k], v, force)
 		else
@@ -544,11 +646,23 @@ local function copyDefaults(dest, src, force)
 		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
+		if k == "*" and type(v) == "table" then
+			-- check for any defaults that have been changed
+			local mt = getmetatable(db)
+			local cache = rawget(mt, "__cache")
+
+			for cacheKey,cacheValue in pairs(cache) do
+				removeDefaults(cacheValue, v)
+				if next(cacheValue) ~= nil then
+					-- Something's changed
+					rawset(db, cacheKey, cacheValue)
+				end
+			end
+		elseif type(v) == "table" and db[k] then
 			removeDefaults(db[k], v)
 			if not next(db[k]) then
 				db[k] = nil
@@ -563,14 +677,14 @@ end

 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] = {}
+	if not sv[svstore][key] then
+		sv[svstore][key] = {}
 		tableCreated = true
 	end
-
+
 	local tbl = sv[svstore][key]

 	if defaults then
@@ -601,11 +715,11 @@ local dbmt = {
 					copyDefaults(sv.global, defaults)
 				end
 				rawset(t, section, sv.global)
-			else
+			else
 				initSection(t, section, section, key, defaults)
 			end
 		end
-
+
 		return rawget(t, section)
 	end
 }
@@ -624,9 +738,9 @@ local function initdb(parent, name, defaults, defaultProfile, olddb)
 	end

 	-- Generate the database keys for each section
-	local char = string.format("%s of %s", UnitName("player"), GetRealmName())
+	local char = string.format("%s - %s", UnitName("player"), GetRealmName())
 	local realm = GetRealmName()
-	local class = UnitClass("player")
+	local class = select(2, UnitClass("player"))
 	local race = select(2, UnitRace("player"))
 	local faction = UnitFactionGroup("player")
 	local factionrealm = string.format("%s - %s", faction, realm)
@@ -671,14 +785,14 @@ local function initdb(parent, name, defaults, defaultProfile, olddb)
 	db.parent = parent

 	databases[db] = true
-
+
 	return db
 end

 function Dongle:InitializeDB(name, defaults, defaultProfile)
 	local reg = lookup[self]
 	assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "InitializeDB"))
-	argcheck(name, 2, "string")
+	argcheck(name, 2, "string", "table")
 	argcheck(defaults, 3, "table", "nil")
 	argcheck(defaultProfile, 4, "string", "nil")

@@ -696,7 +810,7 @@ function Dongle.RegisterDefaults(db, defaults)
 			copyDefaults(db[section], defaults[section])
 		end
 	end
-
+
 	db.defaults = defaults
 end

@@ -733,10 +847,10 @@ function Dongle.SetProfile(db, name)
 		-- Remove the defaults from the old profile
 		removeDefaults(old, defaults)
 	end
-
+
 	db.profile = nil
 	db.keys["profile"] = name
-
+
 	Dongle:TriggerMessage("DONGLE_PROFILE_CHANGED", db, db.parent, db.sv_name, db.keys.profile)
 end

@@ -760,7 +874,7 @@ function Dongle.DeleteProfile(db, name)
 	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
@@ -789,7 +903,7 @@ function Dongle.ResetProfile(db)
 	for k,v in pairs(profile) do
 		profile[k] = nil
 	end
-
+
 	local defaults = db.defaults and db.defaults.profile
 	if defaults then
 		copyDefaults(profile, defaults)
@@ -808,7 +922,7 @@ function Dongle.ResetDB(db, defaultProfile)
 	end

 	local parent = db.parent
-
+
 	initdb(parent, db.sv_name, db.defaults, defaultProfile, db)
 	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)
@@ -822,11 +936,14 @@ function Dongle.RegisterNamespace(db, name, defaults)

 	local sv = db.sv
 	if not sv.namespaces then sv.namespaces = {} end
-	if not sv.namespaces[name] then
+	if not sv.namespaces[name] then
 		sv.namespaces[name] = {}
 	end
-
+
 	local newDB = initdb(db, sv.namespaces[name], defaults, db.keys.profile)
+	-- Remove the :SetProfile method from newDB
+	newDB.SetProfile = nil
+
 	if not db.children then db.children = {} end
 	table.insert(db.children, newDB)
 	return newDB
@@ -918,14 +1035,14 @@ end

 function Dongle.PrintUsage(cmd)
 	assert(3, commands[cmd], string.format(L["MUST_CALLFROM_SLASH"], "PrintUsage"))
+	local parent = cmd.parent

-	local usage = cmd.desc.."\n".."/"..table.concat(cmd.slashes, ", /")..":\n"
+	parent:Echo(cmd.desc.."\n".."/"..table.concat(cmd.slashes, ", /")..":\n")
 	if cmd.patterns then
 		for idx,tbl in ipairs(cmd.patterns) do
-			usage = usage.." - "..tbl.desc.."\n"
+			parent:Echo(" - " .. tbl.desc)
 		end
 	end
-	cmd.parent:Print(usage)
 end

 local dbcommands = {
@@ -937,7 +1054,7 @@ local dbcommands = {
 	["delete"] = {
 		L["DBSLASH_PROFILE_DELETE_DESC"],
 		L["DBSLASH_PROFILE_DELETE_PATTERN"],
-		"DeleteProfile",
+		"DeleteProfile",
 	},
 	["list"] = {
 		L["DBSLASH_PROFILE_LIST_DESC"],
@@ -960,7 +1077,7 @@ function Dongle.InjectDBCommands(cmd, db, ...)
 	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]
@@ -969,7 +1086,7 @@ function Dongle.InjectDBCommands(cmd, db, ...)

 		local handler
 		if cmdname == "list" then
-			handler = function(...)
+			handler = function(...)
 				local profiles = db:GetProfiles()
 				db.parent:Print(L["DBSLASH_PROFILE_LIST_OUT"] .. "\n" .. strjoin("\n", unpack(profiles)))
 			end
@@ -997,10 +1114,12 @@ end

 local function PLAYER_LOGIN()
 	Dongle.initialized = true
-	for i,obj in ipairs(loadorder) do
+	for i=1, #loadorder do
+		local obj = loadorder[i]
 		if type(obj.Enable) == "function" then
 			safecall(obj.Enable, obj)
 		end
+		loadorder[i] = nil
 	end
 end

@@ -1012,11 +1131,25 @@ local function ADDON_LOADED(event, ...)
 		if type(obj.Initialize) == "function" then
 			safecall(obj.Initialize, obj)
 		end
+		loadqueue[i] = nil
+	end
+
+	if not Dongle.initialized then
+		if type(IsLoggedIn) == "function" then
+			Dongle.initialized = IsLoggedIn()
+		else
+			Dongle.initialized = ChatFrame1.defaultLanguage
+		end
+	end

-		if Dongle.initialized and type(obj.Enable) == "function" then
-			safecall(obj.Enable, obj)
+	if Dongle.initialized then
+		for i=1, #loadorder do
+			local obj = loadorder[i]
+			if type(obj.Enable) == "function" then
+				safecall(obj.Enable, obj)
+			end
+			loadorder[i] = nil
 		end
-		loadqueue[i] = nil
 	end
 end

@@ -1024,7 +1157,16 @@ 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)
+			local old = namespace.profile
+			local defaults = namespace.defaults and namespace.defaults.profile
+
+			if defaults then
+				-- Remove the defaults from the old profile
+				removeDefaults(old, defaults)
+			end
+
+			namespace.profile = nil
+			namespace.keys["profile"] = profileKey
 		end
 	end
 end
@@ -1047,11 +1189,9 @@ local function Activate(self, old)
 		messages = old.messages or messages
 		frame = old.frame or CreateFrame("Frame")

-		local reg = self.registry[major]
-		reg.obj = self
+		registry[major].obj = self
 	else
-	  frame = CreateFrame("Frame")
-
+		frame = CreateFrame("Frame")
 		local reg = {obj = self, name = "Dongle"}
 		registry[major] = reg
 		lookup[self] = reg