From e0f99d24b7e63d400b2814d7b5721a2f9090178c Mon Sep 17 00:00:00 2001 From: James Whitehead II Date: Sun, 18 Mar 2007 04:29:48 +0000 Subject: [PATCH] * Updated to Dongle-Beta1 --- Dongle.lua | 836 +++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 512 insertions(+), 324 deletions(-) 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 - Copies profile into your current profile.", + ["DBSLASH_PROFILE_COPY_PATTERN"] = "^profile copy (.+)$", + ["DBSLASH_PROFILE_DELETE_DESC"] = "profile delete - Deletes the profile .", + ["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 - Sets the current profile to .", + ["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) -- 1.7.9.5