Quantcast

* Added Dongle to version control

James Whitehead II [12-22-06 - 06:18]
* Added Dongle to version control
Filename
Dongle.lua
DongleUtils.lua
diff --git a/Dongle.lua b/Dongle.lua
new file mode 100644
index 0000000..66ce40a
--- /dev/null
+++ b/Dongle.lua
@@ -0,0 +1,759 @@
+--[[-------------------------------------------------------------------------
+  Copyright (c) 2006, Dongle Development Team
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are
+  met:
+
+      * Redistributions of source code must retain the above copyright
+        notice, this list of conditions and the following disclaimer.
+      * Redistributions in binary form must reproduce the above
+        copyright notice, this list of conditions and the following
+        disclaimer in the documentation and/or other materials provided
+        with the distribution.
+      * Neither the name of the Dongle Development Team nor the names of
+        its contributors may be used to endorse or promote products derived
+        from this software without specific prior written permission.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+  (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,minor = "DongleStub", 20061205.3
+local g = getfenv(0)
+
+if not g.DongleStub or g.DongleStub:IsNewerVersion(major, minor) then
+	local lib = setmetatable({}, {
+		__call = function(t,k)
+			if type(t.versions) == "table" and t.versions[k] then
+				return t.versions[k]
+			else
+				error("Cannot find a library with name '"..tostring(k).."'", 2)
+			end
+		end
+	})
+
+	function lib:IsNewerVersion(major, minor)
+		local entry = self.versions and self.versions[major]
+
+		if not entry then return true end
+		local oldmajor,oldminor = entry:GetVersion()
+
+		return minor > oldminor
+	end
+
+	function lib:Register(new)
+		local major,minor = new:GetVersion()
+		if not self:IsNewerVersion(major, minor) then return false end
+		local old = self.versions and self.versions[major]
+		-- Run the new libraries activation
+		if type(new.Activate) == "function" then
+			new:Activate(old)
+		end
+
+		-- Deactivate the old libary if necessary
+		if old and type(old.Deactivate) == "function" then
+			old:Deactivate(new)
+		end
+
+		self.versions[major] = new
+	end
+
+	function lib:GetVersion() return major,minor end
+
+	function lib:Activate(old)
+		if old then
+			self.versions = old.versions
+		else
+			self.versions = {}
+		end
+		g.DongleStub = self
+	end
+
+	-- Actually trigger libary activation here
+	local stub = g.DongleStub or lib
+	stub:Register(lib)
+end
+
+--[[-------------------------------------------------------------------------
+Begin Library Implementation
+---------------------------------------------------------------------------]]
+
+local major = "Dongle"
+local minor = tonumber(select(3,string.find("$Revision: 92 $", "(%d+)")) or 1)
+
+assert(DongleStub, string.format("%s requires DongleStub.", major))
+if not DongleStub:IsNewerVersion(major, minor) then return end
+
+Dongle = {}
+local methods = {
+	"RegisterEvent", "UnregisterEvent", "UnregisterAllEvents", "TriggerEvent",
+	"EnableDebug", "Print", "PrintF", "Debug", "DebugF",
+	"InitializeDB",
+	"NewModule", "HasModule", "IterateModules",
+}
+
+local registry = {}
+local lookup = {}
+local loadqueue = {}
+local loadorder = {}
+local events = {}
+local databases = {}
+local commands = {}
+
+local function assert(level,condition,message)
+	if not condition then
+		error(message,level)
+	end
+end
+
+local function argcheck(value, num, ...)
+	assert(1, type(num) == "number",
+		"Bad argument #2 to 'argcheck' (number expected, got " .. 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)
+end
+
+local function safecall(func,...)
+	local success,err = pcall(func,...)
+	if not success then
+		geterrorhandler()(err)
+	end
+end
+
+function Dongle:New(name, obj)
+	argcheck(name, 2, "string")
+	argcheck(obj, 3, "table", "nil")
+
+	if not obj then
+		obj = {}
+	end
+
+	if registry[name] then
+		error("A Dongle with the name '"..name.."' is already registered.")
+	end
+
+	local reg = {["obj"] = obj, ["name"] = name}
+
+	registry[name] = reg
+	lookup[obj] = reg
+	lookup[name] = reg
+
+	for k,v in pairs(methods) do
+		obj[v] = self[v]
+	end
+
+	-- Add this Dongle to the end of the queue
+	table.insert(loadqueue, obj)
+	return obj,name
+end
+
+function Dongle:NewModule(name, obj)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'NewModule' from a registered Dongle.")
+	argcheck(name, 2, "string")
+	argcheck(obj, 3, "table", "nil")
+
+	obj,name = Dongle:New(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.")
+	argcheck(module, 2, "string", "table")
+
+	return reg.modules[module]
+end
+
+local EMPTY_TABLE = {}
+
+function Dongle:IterateModules()
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'IterateModules' from a registered Dongle.")
+
+	return ipairs(reg.modules or EMPTY_TABLE)
+end
+
+function Dongle:ADDON_LOADED(frame, 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 self.initialized and type(obj.Enable) == "function" then
+			safecall(obj.Enable, obj)
+		end
+		loadqueue[i] = nil
+	end
+end
+
+function Dongle:PLAYER_LOGIN()
+	self.initialized = true
+	for i,obj in ipairs(loadorder) do
+		if type(obj.Enable) == "function" then
+			safecall(obj.Enable, obj)
+		end
+	end
+end
+
+function Dongle:TriggerEvent(event, ...)
+	argcheck(event, 2, "string")
+	local eventTbl = events[event]
+	if eventTbl then
+		for obj,func in pairs(eventTbl) do
+			if type(func) == "string" then
+				if type(obj[func]) == "function" then
+					safecall(obj[func], obj, event, ...)
+				end
+			else
+				safecall(func,event,...)
+			end
+		end
+	end
+end
+
+function Dongle:OnEvent(frame, event, ...)
+	local eventTbl = events[event]
+	if eventTbl then
+		for obj,func in pairs(eventTbl) do
+			if type(func) == "string" then
+				if type(obj[func]) == "function" then
+					obj[func](obj, event, ...)
+				end
+			else
+				func(event, ...)
+			end
+		end
+	end
+end
+
+function Dongle:RegisterEvent(event, func)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'RegisterEvent' from a registered Dongle.")
+	argcheck(event, 2, "string")
+	argcheck(func, 3, "string", "function", "nil")
+
+	-- Name the method the same as the event if necessary
+	if not func then func = event end
+
+	if not events[event] then
+		events[event] = {}
+		frame:RegisterEvent(event)
+	end
+	events[event][self] = func
+end
+
+function Dongle:UnregisterEvent(event)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'UnregisterEvent' from a registered Dongle.")
+	argcheck(event, 2, "string")
+
+	if events[event] then
+		events[event][self] = nil
+		if not next(events[event]) then
+			events[event] = nil
+			frame:UnregisterEvent(event)
+		end
+	end
+end
+
+function Dongle:UnregisterAllEvents()
+	assert(3, lookup[self], "You must call 'UnregisterAllEvents' from a registered Dongle.")
+
+	for event,tbl in pairs(events) do
+		tbl[self] = nil
+		if not next(tbl) then
+			events[event] = nil
+			frame:UnregisterEvent(event)
+		end
+	end
+end
+
+function Dongle:AdminEvents(event)
+	local method
+	if event == "PLAYER_LOGOUT" then
+		Dongle:ClearDBDefaults()
+		method = "Disable"
+	elseif event == "PLAYER_REGEN_DISABLED" then
+		method = "CombatLockdown"
+	elseif event == "PLAYER_REGEN_ENABLED" then
+		method = "CombatUnlock"
+	end
+
+	if method then
+		for k,v in pairs(registry) do
+			local obj = v.obj
+			if obj[method] then obj[method](obj) end
+		end
+	end
+end
+
+function Dongle:EnableDebug(level)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'EnableDebug' from a registered Dongle.")
+	argcheck(level, 2, "number", "nil")
+
+	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.")
+
+		local name = reg.name
+		msg = "|cFF33FF99"..name.."|r: "..tostring(msg)
+		if select("#", ...) > 0 then
+			msg = string.join(", ", msg, argsToStrings(...))
+		end
+
+		ChatFrame1:AddMessage(msg)
+	end
+
+	local function printFHelp(obj, method, msg, ...)
+		local reg = lookup[obj]
+		assert(4, reg, "You must call '"..method.."' from a registered Dongle.")
+
+		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
+	end
+
+	function Dongle:Print(...)
+		return printHelp(self, "Print", ...)
+	end
+
+	function Dongle:PrintF(...)
+		return printFHelp(self, "PrintF", ...)
+	end
+
+	function Dongle:Debug(level, ...)
+		local reg = lookup[self]
+		assert(3, reg, "You must call 'Debug' from a registered Dongle.")
+		argcheck(level, 2, "number")
+
+		if reg.debugLevel and level >= reg.debugLevel then
+			printHelp(self, "Debug", ...)
+		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")
+
+		if reg.debugLevel and level >= reg.debugLevel then
+			printFHelp(self, "DebugF", ...)
+		end
+	end
+end
+
+local dbMethods = {
+	"RegisterDefaults", "SetProfile", "GetProfiles", "DeleteProfile", "CopyProfile",
+	"ResetProfile", "ResetDB",
+}
+
+function Dongle:InitializeDB(name, defaults, defaultProfile)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'InitializeDB' from a registered Dongle.")
+	argcheck(name, 2, "string")
+	argcheck(defaults, 3, "table", "nil")
+	argcheck(defaultProfile, 4, "string", "nil")
+
+	local sv = getglobal(name)
+
+	if not sv then
+		sv = {}
+		setglobal(name, sv)
+
+		-- Lets do the initial setup
+		sv.char = {}
+		sv.faction = {}
+		sv.realm = {}
+		sv.class = {}
+		sv.global = {}
+		sv.profiles = {}
+	end
+
+	-- Initialize the specific databases
+	local char = string.format("%s of %s", UnitName("player"), GetRealmName())
+	local realm = string.format("%s", GetRealmName())
+	local class = UnitClass("player")
+	local race = select(2, UnitRace("player"))
+	local faction = UnitFactionGroup("player")
+
+	-- 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
+	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
+
+	if not sv.profiles[profileKey] then sv.profiles[profileKey] = {} end
+
+	local db = {
+		["char"] = sv.char[char],
+		["realm"] = sv.realm[realm],
+		["class"] = sv.class[class],
+		["faction"] = sv.faction[faction],
+		["profile"] = sv.profiles[profileKey],
+		["global"] = sv.global,
+		["profiles"] = sv.profiles,
+	}
+
+	-- Copy methods locally
+	for idx,method in pairs(dbMethods) do
+		db[method] = Dongle[method]
+	end
+
+	-- Set some properties in the object we're returning
+	db.sv = sv
+	db.sv_name = name
+	db.profileKey = profileKey
+	db.parent = reg.name
+	db.charKey = char
+	db.realmKey = realm
+	db.classKey = class
+	db.factionKey = faction
+
+	databases[db] = true
+
+	if defaults then
+		db:RegisterDefaults(defaults)
+	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 not dest[k] or force then
+				dest[k] = v
+			end
+		end
+	end
+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.")
+	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
+		end
+	end
+end
+
+function Dongle:ClearDBDefaults()
+	for db in pairs(databases) do
+		local defaults = db.defaults
+		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)
+				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
+		end
+	end
+end
+
+function Dongle.SetProfile(db, name)
+	assert(3, databases[db], "You must call 'SetProfile' from a Dongle database object.")
+	argcheck(name, 2, "string")
+
+	local sv = db.sv
+	local old = sv.profiles[db.profileKey]
+	local new = sv.profiles[name]
+
+	if not new then
+		sv.profiles[name] = {}
+		new = sv.profiles[name]
+	end
+
+	if db.defaults and db.defaults.profile 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
+
+	-- FIRE: DONGLE_PROFILE_CHANGED, "DongleName", "SVName", "ProfileName"
+	local parent = lookup[db.parent].obj
+	parent:TriggerEvent("DONGLE_PROFILE_CHANGED", db.parent, db.sv_name, name)
+end
+
+function Dongle.GetProfiles(db, t)
+	assert(3, databases[db], "You must call 'GetProfiles' from a Dongle database object.")
+	argcheck(t, 2, "table", "nil")
+
+	t = t or {}
+	local i = 1
+	for profileKey in pairs(db.profiles) do
+		t[i] = profileKey
+		i = i + 1
+	end
+	return t, i - 1
+end
+
+function Dongle.DeleteProfile(db, name)
+	assert(3, databases[db], "You must call 'DeleteProfile' from a Dongle database object.")
+	argcheck(name, 2, "string")
+
+	if db.profileKey == name then
+		error("You cannot delete your active profile.  Change profiles, then attempt to delete.", 2)
+	end
+
+	db.sv.profiles[name] = nil
+	local parent = lookup[db.parent].obj
+	parent:TriggerEvent("DONGLE_PROFILE_DELETED", 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.")
+	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.")
+
+	local profile = db.profile
+	local source = db.sv.profiles[name]
+
+	-- Don't do a destructive copy, just do what we're told
+	copyDefaults(profile, source, true)
+	-- FIRE: DONGLE_PROFILE_COPIED, "DongleName", "SVName", "SourceProfile", "DestProfile"
+	local parent = lookup[db.parent].obj
+	parent:TriggerEvent("DONGLE_PROFILE_COPIED", db.parent, db.sv_name, name, db.profileKey)
+end
+
+function Dongle.ResetProfile(db)
+	assert(3, databases[db], "You must call 'ResetProfile' from a Dongle database object.")
+
+	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)
+	end
+	-- FIRE: DONGLE_PROFILE_RESET, "DongleName", "SVName", "ProfileName"
+	local parent = lookup[db.parent].obj
+	parent:TriggerEvent("DONGLE_PROFILE_RESET", db.parent, db.sv_name, db.profileKey)
+end
+
+
+function Dongle.ResetDB(db, defaultProfile)
+	assert(3, databases[db], "You must call 'ResetDB' from a Dongle database object.")
+    argcheck(defaultProfile, 2, "nil", "string")
+
+	local sv = db.sv
+	for k,v in pairs(sv) do
+		sv[k] = nil
+	end
+
+	local parent = lookup[db.parent].obj
+
+	local newdb = parent:InitializeDB(db.sv_name, db.defaults, defaultProfile)
+	newdb:SetProfile(newdb.profileKey)
+	local parent = lookup[db.parent].obj
+	parent:TriggerEvent("DONGLE_DATABASE_RESET", newdb.parent, newdb.sv_name, newdb.profileKey)
+
+	-- Remove the old database from the lookup table
+	databases[db] = nil
+	return newdb
+end
+
+function Dongle:RegisterSlashCommand(command, prefix, pattern, validator)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'RegisterSlashCommand' from a registered Dongle.")
+	argcheck(prefix, 2, "string")
+	argcheck(pattern, 3, "string", "nil")
+	argcheck(validator, 4, "function", "nil")
+
+	if not reg.cmd then
+		reg.cmd = {}
+	end
+	reg.cmd[prefix] = {
+		["pattern"] = pattern,
+		["validator"] = validator,
+	}
+
+	-- Register the slash command here
+
+
+end
+
+--[[-------------------------------------------------------------------------
+  Begin DongleStub required functions and registration
+---------------------------------------------------------------------------]]
+
+function Dongle:GetVersion() return major,minor end
+
+function Dongle:Activate(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
+
+		registry = self.registry
+		lookup = self.lookup
+		loadqueue = self.loadqueue
+		loadorder = self.loadorder
+		events = self.events
+		databases = self.databases
+		commands = self.commands
+
+		frame = old.frame
+		self.registry[major].obj = self
+	else
+		self.registry = registry
+		self.lookup = lookup
+		self.loadqueue = loadqueue
+		self.loadorder = loadorder
+		self.events = events
+		self.databases = databases
+		self.commands = commands
+
+		local reg = {obj = self, name = "Dongle"}
+		registry[major] = reg
+		lookup[self] = reg
+		lookup[major] = reg
+	end
+
+	if not frame then
+		frame = CreateFrame("Frame")
+	end
+
+	self.frame = frame
+	frame:SetScript("OnEvent", function(...) self:OnEvent(...) end)
+
+	-- Register for events using Dongle itself
+	self:RegisterEvent("ADDON_LOADED")
+	self:RegisterEvent("PLAYER_LOGIN")
+	self:RegisterEvent("PLAYER_LOGOUT", "AdminEvents")
+	self:RegisterEvent("PLAYER_REGEN_ENABLED", "AdminEvents")
+	self:RegisterEvent("PLAYER_REGEN_DISABLED", "AdminEvents")
+
+	-- Convert all the modules handles
+	for name,obj in pairs(registry) do
+		for k,v in ipairs(methods) do
+			obj[k] = self[v]
+		end
+	end
+
+	-- Convert all database methods
+	for db in pairs(databases) do
+		for idx,method in ipairs(dbMethods) do
+			db[method] = self[method]
+		end
+	end
+end
+
+function Dongle:Deactivate(new)
+	lookup[self] = nil
+	self:UnregisterAllEvents()
+end
+
+DongleStub:Register(Dongle)
diff --git a/DongleUtils.lua b/DongleUtils.lua
new file mode 100644
index 0000000..d1c2c26
--- /dev/null
+++ b/DongleUtils.lua
@@ -0,0 +1,479 @@
+--[[-------------------------------------------------------------------------
+  Copyright (c) 2006, Dongle Development Team
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are
+  met:
+
+      * Redistributions of source code must retain the above copyright
+        notice, this list of conditions and the following disclaimer.
+      * Redistributions in binary form must reproduce the above
+        copyright notice, this list of conditions and the following
+        disclaimer in the documentation and/or other materials provided
+        with the distribution.
+      * Neither the name of the Dongle Development Team nor the names of
+        its contributors may be used to endorse or promote products derived
+        from this software without specific prior written permission.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+  (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,minor = "DongleStub", 20061205.3
+local g = getfenv(0)
+
+if not g.DongleStub or g.DongleStub:IsNewerVersion(major, minor) then
+	local lib = setmetatable({}, {
+		__call = function(t,k)
+			if type(t.versions) == "table" and t.versions[k] then
+				return t.versions[k]
+			else
+				error("Cannot find a library with name '"..tostring(k).."'", 2)
+			end
+		end
+	})
+
+	function lib:IsNewerVersion(major, minor)
+		local entry = self.versions and self.versions[major]
+
+		if not entry then return true end
+		local oldmajor,oldminor = entry:GetVersion()
+
+		return minor > oldminor
+	end
+
+	function lib:Register(new)
+		local major,minor = new:GetVersion()
+		if not self:IsNewerVersion(major, minor) then return false end
+		local old = self.versions and self.versions[major]
+		-- Run the new libraries activation
+		if type(new.Activate) == "function" then
+			new:Activate(old)
+		end
+
+		-- Deactivate the old libary if necessary
+		if old and type(old.Deactivate) == "function" then
+			old:Deactivate(new)
+		end
+
+		self.versions[major] = new
+	end
+
+	function lib:GetVersion() return major,minor end
+
+	function lib:Activate(old)
+		if old then
+			self.versions = old.versions
+		else
+			self.versions = {}
+		end
+		g.DongleStub = self
+	end
+
+	-- Actually trigger libary activation here
+	local stub = g.DongleStub or lib
+	stub:Register(lib)
+end
+
+--[[-------------------------------------------------------------------------
+Begin Library Implementation
+---------------------------------------------------------------------------]]
+
+local majorUtil, majorGrat, majorMetro = "DongleUtils", "GratuityMini", "MetrognomeNano"
+local minor = tonumber(select(3,string.find("$Revision: 139 $", "(%d+)"))) or 1
+if not DongleStub:IsNewerVersion(majorUtil, minor) then return end
+
+
+--------------------------------
+--        DongleUtils         --
+--      Misc handy utils      --
+--------------------------------
+
+local DongleUtils = {}
+
+
+function DongleUtils:GetVersion()
+	return majorUtil, minor
+end
+
+
+function DongleUtils:Activate(old)
+
+end
+
+
+function DongleUtils:Deactivate(new)
+	DongleUtils = nil
+end
+
+
+---------------------------
+-- Common locale strings --
+---------------------------
+
+local locale = GetLocale()
+-- Localized class names.  Index == enUS, value == localized
+DongleUtils.classnames = locale == "deDE" and {
+	["Warlock"] = "Hexenmeister",
+	["Warrior"] = "Krieger",
+	["Hunter"] = "Jger",
+	["Mage"] = "Magier",
+	["Priest"] = "Priester",
+	["Druid"] = "Druide",
+	["Paladin"] = "Paladin",
+	["Shaman"] = "Schamane",
+	["Rogue"] = "Schurke",
+} or locale == "frFR" and {
+	["Warlock"] = "D\195\169moniste",
+	["Warrior"] = "Guerrier",
+	["Hunter"] = "Chasseur",
+	["Mage"] = "Mage",
+	["Priest"] = "Pr\195\170tre",
+	["Druid"] = "Druide",
+	["Paladin"] = "Paladin",
+	["Shaman"] = "Chaman",
+	["Rogue"] = "Voleur",
+} or {
+	["Warlock"] = "Warlock",
+	["Warrior"] = "Warrior",
+	["Hunter"] = "Hunter",
+	["Mage"] = "Mage",
+	["Priest"] = "Priest",
+	["Druid"] = "Druid",
+	["Paladin"] = "Paladin",
+	["Shaman"] = "Shaman",
+	["Rogue"] = "Rogue",
+}
+
+-- Reversed version of .classnames, for locale -> enUS translation
+DongleUtils.classnamesreverse = {}
+for i,v in pairs(DongleUtils.classnames) do DongleUtils.classnamesreverse[v] = i end
+
+
+-- Handy method to attach to secure frames, to aid in setting attributes quickly
+-- Example: someframe:SetManyAttributes("type1", "spell", "spell", "Innervate", "unit1", "player")
+function DongleUtils.SetManyAttributes(self, ...)
+	for i=1,select("#", ...),2 do
+		local att,val = select(i, ...)
+		if not att then return end
+		self:SetAttribute(att,val)
+	end
+end
+
+
+function DongleUtils.RGBToHex(r, g, b)
+	return string.format("%02x%02x%02x", r, g, b)
+end
+
+
+function DongleUtils.RGBPercToHex(r, g, b)
+	return string.format("%02x%02x%02x", r*255, g*255, b*255)
+end
+
+
+function DongleUtils.HexToRGB(hex)
+	local rhex, ghex, bhex = string.sub(hex, 1, 2), string.sub(hex, 3, 4), string.sub(hex, 5, 6)
+	return tonumber(rhex, 16), tonumber(ghex, 16), tonumber(bhex, 16)
+end
+
+
+function DongleUtils.HexToRGBPerc(hex)
+	local rhex, ghex, bhex = string.sub(hex, 1, 2), string.sub(hex, 3, 4), string.sub(hex, 5, 6)
+	return tonumber(rhex, 16)/255, tonumber(ghex, 16)/255, tonumber(bhex, 16)/255
+end
+
+
+function DongleUtils.ColorGradient(perc, ...)
+	local num = select("#", ...)
+	local hexes = type(select(1, ...)) == "string"
+
+	if perc == 1 then
+		if hexes then return select(num, ...)
+		else return select(num-2, ...), select(num-1, ...), select(num, ...) end
+	end
+
+	if not hexes then num = num / 3 end
+
+	local segment, relperc = math.modf(perc*(num-1))
+	local r1, g1, b1, r2, g2, b2
+	if hexes then
+		r1, g1, b1 = DongleUtils.HexToRGBPerc(select(segment+1, ...))
+		r2, g2, b2 = DongleUtils.HexToRGBPerc(select(segment+2, ...))
+	else
+		r1, g1, b1 = select((segment*3)+1, ...), select((segment*3)+2, ...), select((segment*3)+3, ...)
+		r2, g2, b2 = select((segment*3)+4, ...), select((segment*3)+5, ...), select((segment*3)+6, ...)
+	end
+
+	if hexes then
+		return DongleUtils.RGBToHex(r1 + (r2-r1)*relperc,
+			g1 + (g2-g1)*relperc,
+			b1 + (b2-b1)*relperc)
+	else
+		return r1 + (r2-r1)*relperc,
+			g1 + (g2-g1)*relperc,
+			b1 + (b2-b1)*relperc
+	end
+end
+
+
+function DongleUtils.GetHPSeverity(perc, class)
+	if not class then return DongleUtils.ColorGradient(perc, 1,0,0, 1,1,0, 0,1,0)
+	else
+		local c = RAID_CLASS_COLORS[class]
+		return DongleUtils.ColorGradient(perc, 1,0,0, 1,1,0, c.r,c.g,c.b)
+	end
+end
+
+
+DongleStub:Register(DongleUtils)
+
+
+---------------------------------------
+--           GratuityMini            --
+--      Tooltip parsing library      --
+---------------------------------------
+
+local GratuityMini = {}
+local CreateTooltip
+
+function GratuityMini:GetVersion()
+	return majorGrat, minor
+end
+
+
+function GratuityMini:Activate(old)
+	self.tooltip = old and old.tooltip or CreateTooltip()
+	CreateTooltip = nil
+end
+
+
+function GratuityMini:Deactivate(new)
+	GratuityMini = nil
+end
+
+
+function GratuityMini:GetTooltip()
+	return self.tooltip
+end
+
+
+CreateTooltip = function()
+	local tip = CreateFrame("GameTooltip")
+	tip:SetOwner(tip, "ANCHOR_NONE")
+	tip.Llines, tip.Rlines = {}, {}
+	for i=1,30 do
+		tip.Llines[i], tip.Rlines[i] = tip:CreateFontString(), tip:CreateFontString()
+		tip.Llines[i]:SetFontObject(GameFontNormal); tip.Rlines[i]:SetFontObject(GameFontNormal)
+		tip:AddFontStrings(tip.Llines[i], tip.Rlines[i])
+	end
+
+	tip.Erase = function(self)
+		self:ClearLines() -- Ensures tooltip's NumLines is reset
+		for i=1,30 do
+			self.Rlines[i]:SetText() -- Clear text from right side (ClearLines only hides them)
+			self.L[i], self.R[i] = nil, nil -- Flush the metatable cache
+		end
+		if not self:IsOwned(self) then self:SetOwner(self, "ANCHOR_NONE") end
+	end
+
+	local methods = {"SetMerchantCostItem", "SetBagItem", "SetAction", "SetAuctionItem", "SetAuctionSellItem", "SetBuybackItem",
+		"SetCraftItem", "SetCraftSpell", "SetHyperlink", "SetInboxItem", "SetInventoryItem", "SetLootItem", "SetLootRollItem",
+		"SetMerchantItem", "SetPetAction", "SetPlayerBuff", "SetQuestItem", "SetQuestLogItem", "SetQuestRewardSpell",
+		"SetSendMailItem", "SetShapeshift", "SetSpell", "SetTalent", "SetTrackingSpell", "SetTradePlayerItem", "SetTradeSkillItem",
+		"SetTradeTargetItem", "SetTrainerService", "SetUnit", "SetUnitBuff", "SetUnitDebuff"}
+	for _,m in pairs(methods) do
+		local orig = tip[m]
+		tip[m] = function(self, ...)
+			self:Erase()
+			return orig(self, ...)
+		end
+	end
+
+	tip.L, tip.R = {}, {}
+	setmetatable(tip.L, {
+		__index = function(t, key)
+			if tip:NumLines() >= key and tip.Llines[key] then
+				local v = tip.Llines[key]:GetText()
+				t[key] = v
+				return v
+			end
+			return nil
+		end,
+	})
+	setmetatable(tip.R, {
+		__index = function(t, key)
+			if tip:NumLines() >= key and tip.Rlines[key] then
+				local v = tip.Rlines[key]:GetText()
+				t[key] = v
+				return v
+			end
+			return nil
+		end,
+	})
+
+	return tip
+end
+
+
+DongleStub:Register(GratuityMini)
+
+
+--------------------------------------------------
+--                MetrognomeNano                --
+--      OnUpdate and delayed event manager      --
+--------------------------------------------------
+
+local Metrognome = {}
+local frame, handlers, eventargs
+
+
+function Metrognome:GetVersion()
+	return majorMetro, minor
+end
+
+
+function Metrognome:Activate(old)
+	self.eventargs = old and old.eventargs or {}
+	self.handlers = old and old.handlers or {}
+	self.frame = old and old.frame or CreateFrame("Frame")
+	handlers, frame, eventargs = self.handlers, self.frame, self.eventargs
+	if not old then frame:Hide() end
+	frame.name = "MetrognomeNano Frame"
+	frame:SetScript("OnUpdate", self.OnUpdate)
+
+	if old then
+		self.olds = old.olds or {}
+		if not getmetatable(self.olds) then setmetatable(self.olds, {__mode = "k"}) end
+		self.olds[old] = true
+
+		for o in pairs(self.olds) do
+			for i,v in pairs(self) do
+				if i ~= "GetVersion" and i ~= "Activate" and i ~= "Deactivate" then o[i] = v end
+			end
+		end
+	end
+	self.Activate = nil
+end
+
+
+function Metrognome:Deactivate(new)
+	Metrognome, frame, handlers, eventargs = nil, nil, nil, nil
+	self.Deactivate = nil
+end
+
+
+function Metrognome:FireDelayedEvent(event, delay, ...)
+	local id = event..GetTime()
+
+	self:Register(self, id, "DelayedEventHandler", rate, id, event, ...)
+	self:Start(id, 1)
+
+	return id
+end
+
+
+function Metrognome:DelayedEventHandler(id, event, ...)
+	self:Unregister(id)
+
+	if Dongle then Dongle:FireEvent(event, ...) end
+end
+
+
+function Metrognome:Register(addon, name, func, rate, ...)
+--~ 	self:argCheck(name, 2, "string")
+--~ 	self:argCheck(func, 3, "function")
+--~ 	self:assert(not handlers[name], "A timer with the name "..name.." is already registered")
+
+	handlers[name] = {
+		handler = type(func) == "string" and addon,
+		name = name,
+		func = func,
+		rate = rate or 0,
+		...
+	}
+
+	return true
+end
+
+
+function Metrognome:Unregister(name)
+--~ 	self:argCheck(name, 2, "string")
+
+	if not handlers[name] then return end
+	handlers[name] = nil
+	if not next(handlers) then frame:Hide() end
+	return true
+end
+
+
+function Metrognome:Start(name, numexec)
+--~ 	self:argCheck(name, 2, "string")
+
+	if not handlers[name] then return end
+	handlers[name].limit = numexec
+	handlers[name].elapsed = 0
+	handlers[name].running = true
+	frame:Show()
+	return true
+end
+
+
+function Metrognome:Stop(name)
+--~ 	self:argCheck(name, 2, "string")
+
+	if not handlers[name] then return end
+	handlers[name].running = nil
+	handlers[name].limit = nil
+	if not next(handlers) then frame:Hide() end
+	return true
+end
+
+
+function Metrognome:ChangeRate(name, newrate)
+--~ 	self:argCheck(name, 2, "string")
+
+	if not handlers[name] then return end
+
+	local t = handlers[name]
+	t.elapsed = 0
+	t.rate = newrate or 0
+	return true
+end
+
+
+function Metrognome:GetHandlerTable(name)
+--~ 	self:argCheck(name, 2, "string")
+
+	return handlers[name]
+end
+
+
+function Metrognome.OnUpdate(frame, elapsed)
+	for i,v in pairs(handlers) do
+		if v.running then
+			v.elapsed = v.elapsed + elapsed
+			if v.elapsed >= v.rate then
+				if v.handler then v.handler[v.func](v.handler, v.elapsed, unpack(v))
+				else v.func(v.elapsed, unpack(v)) end
+				v.elapsed = 0
+				if v.limit then
+					v.limit = v.limit - 1
+					if v.limit <= 0 then Metrognome:Stop(i) end
+				end
+			end
+		end
+	end
+end
+
+
+DongleStub:Register(Metrognome)