1435 lines
39 KiB
Lua
1435 lines
39 KiB
Lua
--[[-------------------------------------------------------------------------
|
|
Copyright (c) 2006-2007, 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 = "DongleStub"
|
|
local minor = tonumber(string.match("$Revision: 313 $", "(%d+)") or 1)
|
|
|
|
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].instance
|
|
else
|
|
error("Cannot find a library with name '"..tostring(k).."'", 2)
|
|
end
|
|
end
|
|
})
|
|
|
|
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
|
|
|
|
local function NilCopyTable(src, dest)
|
|
for k,v in pairs(dest) do dest[k] = nil end
|
|
for k,v in pairs(src) do dest[k] = v end
|
|
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
|
|
|
|
local versionData = self.versions[major]
|
|
if not versionData then
|
|
-- New major version
|
|
versionData = {
|
|
["instance"] = newInstance,
|
|
["deactivate"] = deactivate,
|
|
}
|
|
|
|
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
|
|
end
|
|
|
|
local oldDeactivate = versionData.deactivate
|
|
local oldInstance = versionData.instance
|
|
|
|
versionData.deactivate = deactivate
|
|
|
|
local skipCopy
|
|
if type(activate) == "function" then
|
|
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
|
|
local major, minor = oldInstance:GetVersion()
|
|
table.insert(self.log, string.format("Deactivate: %s, %s", major, minor))
|
|
oldDeactivate(oldInstance, newInstance)
|
|
end
|
|
|
|
-- Re-use the old table, and discard the new one
|
|
if not skipCopy then
|
|
NilCopyTable(newInstance, oldInstance)
|
|
end
|
|
return oldInstance
|
|
end
|
|
|
|
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
|
|
lib = stub:Register(lib, Activate)
|
|
end
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Begin Library Implementation
|
|
---------------------------------------------------------------------------]]
|
|
|
|
local major = "Dongle-1.2"
|
|
local minor = tonumber(string.match("$Revision: 674 $", "(%d+)") or 1)
|
|
|
|
assert(DongleStub, string.format("%s requires DongleStub.", major))
|
|
|
|
if not DongleStub:IsNewerVersion(major, minor) then return end
|
|
|
|
local Dongle = {}
|
|
local methods = {
|
|
"RegisterEvent", "UnregisterEvent", "UnregisterAllEvents", "IsEventRegistered",
|
|
"RegisterMessage", "UnregisterMessage", "UnregisterAllMessages", "TriggerMessage", "IsMessageRegistered",
|
|
"ScheduleTimer", "ScheduleRepeatingTimer", "CancelTimer", "IsTimerScheduled",
|
|
"EnableDebug", "IsDebugEnabled", "Print", "PrintF", "Debug", "DebugF", "Echo", "EchoF",
|
|
"InitializeDB",
|
|
"InitializeSlashCommand",
|
|
"NewModule", "HasModule", "IterateModules",
|
|
}
|
|
|
|
local registry = {}
|
|
local lookup = {}
|
|
local loadqueue = {}
|
|
local loadorder = {}
|
|
local events = {}
|
|
local databases = {}
|
|
local commands = {}
|
|
local messages = {}
|
|
local timers = {}
|
|
local heap = {}
|
|
|
|
local frame
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Message Localization
|
|
---------------------------------------------------------------------------]]
|
|
|
|
local L = {
|
|
["ADDMESSAGE_REQUIRED"] = "The frame you specify must have an 'AddMessage' method.",
|
|
["ALREADY_REGISTERED"] = "A Dongle with the name '%s' is already registered.",
|
|
["BAD_ARGUMENT"] = "bad argument #%d to '%s' (%s expected, got %s)",
|
|
["BAD_ARGUMENT_DB"] = "bad argument #%d to '%s' (DongleDB expected)",
|
|
["CANNOT_DELETE_ACTIVE_PROFILE"] = "You cannot delete your active profile. Change profiles, then attempt to delete.",
|
|
["DELETE_NONEXISTANT_PROFILE"] = "You cannot delete a non-existant profile.",
|
|
["MUST_CALLFROM_DBOBJECT"] = "You must call '%s' from a Dongle database object.",
|
|
["MUST_CALLFROM_REGISTERED"] = "You must call '%s' from a registered Dongle.",
|
|
["MUST_CALLFROM_SLASH"] = "You must call '%s' from a Dongle slash command object.",
|
|
["PROFILE_DOES_NOT_EXIST"] = "Profile '%s' doesn't exist.",
|
|
["REPLACE_DEFAULTS"] = "You are attempting to register defaults with a database that already contains defaults.",
|
|
["SAME_SOURCE_DEST"] = "Source/Destination profile cannot be the same profile.",
|
|
["EVENT_REGISTER_SPECIAL"] = "You cannot register for the '%s' event. Use the '%s' method instead.",
|
|
["Unknown"] = "Unknown",
|
|
["INJECTDB_USAGE"] = "Usage: DongleCmd:InjectDBCommands(db, ['copy', 'delete', 'list', 'reset', 'set'])",
|
|
["DBSLASH_PROFILE_COPY_DESC"] = "profile copy <name> - Copies profile <name> into your current profile.",
|
|
["DBSLASH_PROFILE_COPY_PATTERN"] = "^profile copy (.+)$",
|
|
["DBSLASH_PROFILE_DELETE_DESC"] = "profile delete <name> - Deletes the profile <name>.",
|
|
["DBSLASH_PROFILE_DELETE_PATTERN"] = "^profile delete (.+)$",
|
|
["DBSLASH_PROFILE_LIST_DESC"] = "profile list - Lists all valid profiles.",
|
|
["DBSLASH_PROFILE_LIST_PATTERN"] = "^profile list$",
|
|
["DBSLASH_PROFILE_RESET_DESC"] = "profile reset - Resets the current profile.",
|
|
["DBSLASH_PROFILE_RESET_PATTERN"] = "^profile reset$",
|
|
["DBSLASH_PROFILE_SET_DESC"] = "profile set <name> - Sets the current profile to <name>.",
|
|
["DBSLASH_PROFILE_SET_PATTERN"] = "^profile set (.+)$",
|
|
["DBSLASH_PROFILE_LIST_OUT"] = "Profile List:",
|
|
}
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Utility functions for Dongle use
|
|
---------------------------------------------------------------------------]]
|
|
|
|
local function assert(level,condition,message)
|
|
if not condition then
|
|
error(message,level)
|
|
end
|
|
end
|
|
|
|
local function argcheck(value, num, ...)
|
|
if type(num) ~= "number" then
|
|
error(L["BAD_ARGUMENT"]:format(2, "argcheck", "number", type(num)), 1)
|
|
end
|
|
|
|
for i=1,select("#", ...) do
|
|
if type(value) == select(i, ...) then return end
|
|
end
|
|
|
|
local types = strjoin(", ", ...)
|
|
local name = string.match(debugstack(2,2,0), ": in function [`<](.-)['>]")
|
|
error(L["BAD_ARGUMENT"]:format(num, name, types, type(value)), 3)
|
|
end
|
|
|
|
local function safecall(func,...)
|
|
local success,err = pcall(func,...)
|
|
if not success then
|
|
geterrorhandler()(err)
|
|
end
|
|
end
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Dongle constructor, and DongleModule system
|
|
---------------------------------------------------------------------------]]
|
|
|
|
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(string.format(L["ALREADY_REGISTERED"], name))
|
|
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, string.format(L["MUST_CALLFROM_REGISTERED"], "NewModule"))
|
|
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
|
|
|
|
return obj,name
|
|
end
|
|
|
|
function Dongle:HasModule(module)
|
|
local reg = lookup[self]
|
|
assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "HasModule"))
|
|
argcheck(module, 2, "string", "table")
|
|
|
|
return reg.modules and reg.modules[module]
|
|
end
|
|
|
|
local function ModuleIterator(t, name)
|
|
if not t then return end
|
|
local obj
|
|
repeat
|
|
name,obj = next(t, name)
|
|
until type(name) == "string" or not name
|
|
|
|
return name,obj
|
|
end
|
|
|
|
function Dongle:IterateModules()
|
|
local reg = lookup[self]
|
|
assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "IterateModules"))
|
|
|
|
return ModuleIterator, reg.modules
|
|
end
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Event registration system
|
|
---------------------------------------------------------------------------]]
|
|
|
|
local function 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
|
|
safecall(obj[func], obj, event, ...)
|
|
end
|
|
else
|
|
safecall(func, event, ...)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local specialEvents = {
|
|
["PLAYER_LOGIN"] = "Enable",
|
|
["PLAYER_LOGOUT"] = "Disable",
|
|
}
|
|
|
|
function Dongle:RegisterEvent(event, func)
|
|
local reg = lookup[self]
|
|
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
|
|
|
|
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, string.format(L["MUST_CALLFROM_REGISTERED"], "UnregisterEvent"))
|
|
argcheck(event, 2, "string")
|
|
|
|
local tbl = events[event]
|
|
if tbl then
|
|
tbl[self] = nil
|
|
if not next(tbl) then
|
|
events[event] = nil
|
|
frame:UnregisterEvent(event)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Dongle:UnregisterAllEvents()
|
|
local reg = lookup[self]
|
|
assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "UnregisterAllEvents"))
|
|
|
|
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:IsEventRegistered(event)
|
|
local reg = lookup[self]
|
|
assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "IsEventRegistered"))
|
|
argcheck(event, 2, "string")
|
|
|
|
if events[event] and events[event][self] then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Inter-Addon Messaging System
|
|
---------------------------------------------------------------------------]]
|
|
|
|
function Dongle:RegisterMessage(msg, func)
|
|
argcheck(self, 1, "table")
|
|
argcheck(msg, 2, "string")
|
|
argcheck(func, 3, "string", "function", "nil")
|
|
|
|
-- Name the method the same as the message if necessary
|
|
if not func then func = msg end
|
|
|
|
if not messages[msg] then
|
|
messages[msg] = {}
|
|
end
|
|
messages[msg][self] = func
|
|
end
|
|
|
|
function Dongle:UnregisterMessage(msg)
|
|
argcheck(self, 1, "table")
|
|
argcheck(msg, 2, "string")
|
|
|
|
local tbl = messages[msg]
|
|
if tbl then
|
|
tbl[self] = nil
|
|
if not next(tbl) then
|
|
messages[msg] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
function Dongle:UnregisterAllMessages()
|
|
argcheck(self, 1, "table")
|
|
|
|
for msg,tbl in pairs(messages) do
|
|
tbl[self] = nil
|
|
if not next(tbl) then
|
|
messages[msg] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
function Dongle:TriggerMessage(msg, ...)
|
|
argcheck(self, 1, "table")
|
|
argcheck(msg, 2, "string")
|
|
local msgTbl = messages[msg]
|
|
if not msgTbl then return end
|
|
|
|
for obj,func in pairs(msgTbl) do
|
|
if type(func) == "string" then
|
|
if type(obj[func]) == "function" then
|
|
safecall(obj[func], obj, msg, ...)
|
|
end
|
|
else
|
|
safecall(func, msg, ...)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Dongle:IsMessageRegistered(msg)
|
|
argcheck(self, 1, "table")
|
|
argcheck(msg, 2, "string")
|
|
|
|
local tbl = messages[msg]
|
|
return tbl[self]
|
|
end
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Timer System
|
|
---------------------------------------------------------------------------]]
|
|
|
|
local function HeapSwap(i1, i2)
|
|
heap[i1], heap[i2] = heap[i2], heap[i1]
|
|
end
|
|
|
|
local function HeapBubbleUp(index)
|
|
while index > 1 do
|
|
local parentIndex = math.floor(index / 2)
|
|
if heap[index].timeToFire < heap[parentIndex].timeToFire then
|
|
HeapSwap(index, parentIndex)
|
|
index = parentIndex
|
|
else
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
local function HeapBubbleDown(index)
|
|
while 2 * index <= heap.lastIndex do
|
|
local leftIndex = 2 * index
|
|
local rightIndex = leftIndex + 1
|
|
local current = heap[index]
|
|
local leftChild = heap[leftIndex]
|
|
local rightChild = heap[rightIndex]
|
|
|
|
if not rightChild then
|
|
if leftChild.timeToFire < current.timeToFire then
|
|
HeapSwap(index, leftIndex)
|
|
index = leftIndex
|
|
else
|
|
break
|
|
end
|
|
else
|
|
if leftChild.timeToFire < current.timeToFire or
|
|
rightChild.timeToFire < current.timeToFire then
|
|
if leftChild.timeToFire < rightChild.timeToFire then
|
|
HeapSwap(index, leftIndex)
|
|
index = leftIndex
|
|
else
|
|
HeapSwap(index, rightIndex)
|
|
index = rightIndex
|
|
end
|
|
else
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function OnUpdate(frame, elapsed)
|
|
local schedule = heap[1]
|
|
while schedule and schedule.timeToFire < GetTime() do
|
|
if schedule.cancelled then
|
|
HeapSwap(1, heap.lastIndex)
|
|
heap[heap.lastIndex] = nil
|
|
heap.lastIndex = heap.lastIndex - 1
|
|
HeapBubbleDown(1)
|
|
else
|
|
if schedule.args then
|
|
safecall(schedule.func, schedule.name, unpack(schedule.args))
|
|
else
|
|
safecall(schedule.func, schedule.name)
|
|
end
|
|
|
|
if schedule.repeating then
|
|
schedule.timeToFire = schedule.timeToFire + schedule.repeating
|
|
HeapBubbleDown(1)
|
|
else
|
|
HeapSwap(1, heap.lastIndex)
|
|
heap[heap.lastIndex] = nil
|
|
heap.lastIndex = heap.lastIndex - 1
|
|
HeapBubbleDown(1)
|
|
timers[schedule.name] = nil
|
|
end
|
|
end
|
|
schedule = heap[1]
|
|
end
|
|
if not schedule then frame:Hide() end
|
|
end
|
|
|
|
function Dongle:ScheduleTimer(name, func, delay, ...)
|
|
argcheck(self, 1, "table")
|
|
argcheck(name, 2, "string")
|
|
argcheck(func, 3, "function")
|
|
argcheck(delay, 4, "number")
|
|
|
|
if Dongle:IsTimerScheduled(name) then
|
|
Dongle:CancelTimer(name)
|
|
end
|
|
|
|
local schedule = {}
|
|
timers[name] = schedule
|
|
schedule.timeToFire = GetTime() + delay
|
|
schedule.func = func
|
|
schedule.name = name
|
|
if select('#', ...) ~= 0 then
|
|
schedule.args = { ... }
|
|
end
|
|
|
|
if heap.lastIndex then
|
|
heap.lastIndex = heap.lastIndex + 1
|
|
else
|
|
heap.lastIndex = 1
|
|
end
|
|
heap[heap.lastIndex] = schedule
|
|
HeapBubbleUp(heap.lastIndex)
|
|
if not frame:IsShown() then
|
|
frame:Show()
|
|
end
|
|
end
|
|
|
|
function Dongle:ScheduleRepeatingTimer(name, func, delay, ...)
|
|
Dongle:ScheduleTimer(name, func, delay, ...)
|
|
timers[name].repeating = delay
|
|
end
|
|
|
|
function Dongle:IsTimerScheduled(name)
|
|
argcheck(self, 1, "table")
|
|
argcheck(name, 2, "string")
|
|
local schedule = timers[name]
|
|
if schedule then
|
|
return true, schedule.timeToFire - GetTime()
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
function Dongle:CancelTimer(name)
|
|
argcheck(self, 1, "table")
|
|
argcheck(name, 2, "string")
|
|
local schedule = timers[name]
|
|
if not schedule then return end
|
|
schedule.cancelled = true
|
|
timers[name] = nil
|
|
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
|
|
|
|
function Dongle:IsDebugEnabled()
|
|
local reg = lookup[self]
|
|
assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "EnableDebug"))
|
|
|
|
return reg.debugLevel, reg.debugFrame
|
|
end
|
|
|
|
local function argsToStrings(a1, ...)
|
|
if select("#", ...) > 0 then
|
|
return tostring(a1), argsToStrings(...)
|
|
else
|
|
return tostring(a1)
|
|
end
|
|
end
|
|
|
|
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
|
|
|
|
if header then
|
|
msg = "|cFF33FF99"..name.."|r: "..tostring(msg)
|
|
end
|
|
|
|
if select("#", ...) > 0 then
|
|
msg = string.join(", ", msg, argsToStrings(...))
|
|
end
|
|
|
|
frame:AddMessage(msg)
|
|
end
|
|
|
|
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
|
|
|
|
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
|
|
error(string.gsub(txt, "'%?'", string.format("'%s'", method)), 3)
|
|
end
|
|
end
|
|
|
|
function Dongle:Print(msg, ...)
|
|
local reg = lookup[self]
|
|
assert(1, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "Print"))
|
|
argcheck(msg, 2, "number", "string", "boolean", "table", "function", "thread", "userdata")
|
|
return printHelp(self, "Print", true, DEFAULT_CHAT_FRAME, msg, ...)
|
|
end
|
|
|
|
function Dongle:PrintF(msg, ...)
|
|
local reg = lookup[self]
|
|
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, "EchoF", false, 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", true, reg.debugFrame, ...)
|
|
end
|
|
end
|
|
|
|
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", true, reg.debugFrame, ...)
|
|
end
|
|
end
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Database System
|
|
---------------------------------------------------------------------------]]
|
|
|
|
local dbMethods = {
|
|
"RegisterDefaults", "SetProfile", "GetProfiles", "DeleteProfile", "CopyProfile",
|
|
"GetCurrentProfile", "ResetProfile", "ResetDB",
|
|
"RegisterNamespace",
|
|
}
|
|
|
|
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)
|
|
if not mt then
|
|
mt = {}
|
|
setmetatable(tbl, mt)
|
|
end
|
|
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)
|
|
-- Now need to set the metatable on any child tables
|
|
for dkey,dval in pairs(dest) do
|
|
copyDefaults(dval, v)
|
|
end
|
|
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
|
|
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 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
|
|
-- Now loop through all the actual k,v pairs and remove
|
|
for key,value in pairs(db) do
|
|
removeDefaults(value, v)
|
|
end
|
|
elseif 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
|
|
|
|
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 == "profiles" then
|
|
local sv = rawget(t, "sv")
|
|
if not sv.profiles then sv.profiles = {} end
|
|
rawset(t, "profiles", sv.profiles)
|
|
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
|
|
}
|
|
|
|
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
|
|
|
|
-- Generate the database keys for each section
|
|
local char = string.format("%s - %s", UnitName("player"), GetRealmName())
|
|
local realm = GetRealmName()
|
|
local class = select(2, UnitClass("player"))
|
|
local race = select(2, UnitRace("player"))
|
|
local faction = UnitFactionGroup("player")
|
|
local factionrealm = string.format("%s - %s", faction, realm)
|
|
|
|
-- Make a container for profile keys
|
|
if not sv.profileKeys then sv.profileKeys = {} 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 keyTbl= {
|
|
["char"] = char,
|
|
["realm"] = realm,
|
|
["class"] = class,
|
|
["race"] = race,
|
|
["faction"] = faction,
|
|
["factionrealm"] = factionrealm,
|
|
["global"] = true,
|
|
["profile"] = profileKey,
|
|
["profiles"] = true, -- Don't create until we need
|
|
}
|
|
|
|
-- 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
|
|
|
|
-- 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
|
|
db[method] = Dongle[method]
|
|
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.defaults = defaults
|
|
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", "table")
|
|
argcheck(defaults, 3, "table", "nil")
|
|
argcheck(defaultProfile, 4, "string", "nil")
|
|
|
|
return initdb(self, name, defaults, defaultProfile)
|
|
end
|
|
|
|
-- This function operates on a Dongle DB object
|
|
function Dongle.RegisterDefaults(db, defaults)
|
|
assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "RegisterDefaults"))
|
|
assert(3, db.defaults == nil, L["REPLACE_DEFAUTS"])
|
|
argcheck(defaults, 2, "table")
|
|
|
|
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()
|
|
for db in pairs(databases) do
|
|
local defaults = db.defaults
|
|
local sv = db.sv
|
|
|
|
if db and defaults then
|
|
for section,key in pairs(db.keys) do
|
|
if defaults[section] and rawget(db, section) then
|
|
removeDefaults(db[section], defaults[section])
|
|
end
|
|
end
|
|
|
|
for section,key in pairs(db.keys) do
|
|
local tbl = rawget(db, section)
|
|
if tbl and not next(tbl) then
|
|
if sv[section] then
|
|
if type(key) == "string" then
|
|
sv[section][key] = nil
|
|
else
|
|
sv[section] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Dongle.SetProfile(db, name)
|
|
assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "SetProfile"))
|
|
argcheck(name, 2, "string")
|
|
|
|
local old = db.profile
|
|
local defaults = db.defaults and db.defaults.profile
|
|
|
|
if defaults then
|
|
-- Remove the defaults from the old profile
|
|
removeDefaults(old, defaults)
|
|
end
|
|
|
|
db.profile = nil
|
|
db.keys["profile"] = name
|
|
db.sv.profileKeys[db.keys.char] = name
|
|
|
|
Dongle:TriggerMessage("DONGLE_PROFILE_CHANGED", db, db.parent, db.sv_name, db.keys.profile)
|
|
end
|
|
|
|
function Dongle.GetProfiles(db, tbl)
|
|
assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "GetProfiles"))
|
|
argcheck(tbl, 2, "table", "nil")
|
|
|
|
-- Clear the container table
|
|
if tbl then
|
|
for k,v in pairs(tbl) do tbl[k] = nil end
|
|
else
|
|
tbl = {}
|
|
end
|
|
|
|
local i = 0
|
|
for profileKey in pairs(db.profiles) do
|
|
i = i + 1
|
|
tbl[i] = profileKey
|
|
end
|
|
|
|
-- Add the current profile, if it hasn't been created yet
|
|
if rawget(db, "profile") == nil then
|
|
i = i + 1
|
|
tbl[i] = db.keys.profile
|
|
end
|
|
|
|
return tbl, i
|
|
end
|
|
|
|
function Dongle.GetCurrentProfile(db)
|
|
assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "GetCurrentProfile"))
|
|
return db.keys.profile
|
|
end
|
|
|
|
function Dongle.DeleteProfile(db, name)
|
|
assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "DeleteProfile"))
|
|
argcheck(name, 2, "string")
|
|
|
|
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], string.format(L["MUST_CALLFROM_DBOBJECT"], "CopyProfile"))
|
|
argcheck(name, 2, "string")
|
|
|
|
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.keys.profile)
|
|
end
|
|
|
|
function Dongle.ResetProfile(db)
|
|
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
|
|
|
|
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.keys.profile)
|
|
end
|
|
|
|
|
|
function Dongle.ResetDB(db, defaultProfile)
|
|
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
|
|
|
|
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)
|
|
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", "table")
|
|
|
|
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)
|
|
-- Remove the :SetProfile method from newDB
|
|
newDB.SetProfile = nil
|
|
|
|
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 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
|
|
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
|
|
handler(string.match(cmd_line, pattern))
|
|
end
|
|
return
|
|
end
|
|
end
|
|
end
|
|
cmd:PrintUsage()
|
|
end
|
|
|
|
function Dongle:InitializeSlashCommand(desc, name, ...)
|
|
local reg = lookup[self]
|
|
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
|
|
cmd.parent = self
|
|
cmd.slashes = { ... }
|
|
for idx,method in pairs(slashCmdMethods) do
|
|
cmd[method] = Dongle[method]
|
|
end
|
|
|
|
local genv = getfenv(0)
|
|
|
|
for i = 1,select("#", ...) do
|
|
genv["SLASH_"..name..tostring(i)] = "/"..select(i, ...)
|
|
end
|
|
|
|
genv.SlashCmdList[name] = function(...) OnSlashCommand(cmd, ...) end
|
|
|
|
commands[cmd] = true
|
|
|
|
return cmd
|
|
end
|
|
|
|
function Dongle.RegisterSlashHandler(cmd, desc, pattern, handler)
|
|
assert(3, commands[cmd], string.format(L["MUST_CALLFROM_SLASH"], "RegisterSlashHandler"))
|
|
|
|
argcheck(desc, 2, "string")
|
|
argcheck(pattern, 3, "string")
|
|
argcheck(handler, 4, "function", "string")
|
|
|
|
if not cmd.patterns then
|
|
cmd.patterns = {}
|
|
end
|
|
|
|
table.insert(cmd.patterns, {
|
|
["desc"] = desc,
|
|
["handler"] = handler,
|
|
["pattern"] = pattern,
|
|
})
|
|
end
|
|
|
|
function Dongle.PrintUsage(cmd)
|
|
assert(3, commands[cmd], string.format(L["MUST_CALLFROM_SLASH"], "PrintUsage"))
|
|
local parent = cmd.parent
|
|
|
|
parent:Echo(cmd.desc.."\n".."/"..table.concat(cmd.slashes, ", /")..":\n")
|
|
if cmd.patterns then
|
|
for idx,tbl in ipairs(cmd.patterns) do
|
|
parent:Echo(" - " .. tbl.desc)
|
|
end
|
|
end
|
|
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
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
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 PLAYER_LOGIN
|
|
do
|
|
local lockPlayerLogin = false
|
|
|
|
function PLAYER_LOGIN()
|
|
if lockPlayerLogin then return end
|
|
|
|
lockPlayerLogin = true
|
|
|
|
local obj = table.remove(loadorder, 1)
|
|
while obj do
|
|
if type(obj.Enable) == "function" then
|
|
safecall(obj.Enable, obj)
|
|
end
|
|
obj = table.remove(loadorder, 1)
|
|
end
|
|
|
|
lockPlayerLogin = false
|
|
end
|
|
end
|
|
|
|
local function ADDON_LOADED(event, ...)
|
|
local obj = table.remove(loadqueue, 1)
|
|
while obj do
|
|
table.insert(loadorder, obj)
|
|
|
|
if type(obj.Initialize) == "function" then
|
|
safecall(obj.Initialize, obj)
|
|
end
|
|
|
|
obj = table.remove(loadqueue, 1)
|
|
end
|
|
|
|
if IsLoggedIn() then
|
|
PLAYER_LOGIN()
|
|
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
|
|
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
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
DongleStub required functions and registration
|
|
---------------------------------------------------------------------------]]
|
|
|
|
function Dongle:GetVersion() return major,minor end
|
|
|
|
local function Activate(self, old)
|
|
if old then
|
|
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")
|
|
timers = old.timers or timers
|
|
heap = old.heap or heap
|
|
else
|
|
frame = CreateFrame("Frame")
|
|
local reg = {obj = self, name = "Dongle"}
|
|
registry[major] = reg
|
|
lookup[self] = reg
|
|
lookup[major] = reg
|
|
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
|
|
self.timers = timers
|
|
self.heap = heap
|
|
|
|
frame:SetScript("OnEvent", OnEvent)
|
|
frame:SetScript("OnUpdate", OnUpdate)
|
|
|
|
-- Lets ensure the lookup table has our entry
|
|
-- This fixes an issue with upgrades
|
|
lookup[self] = lookup[major]
|
|
|
|
-- 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
|
|
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
|
|
|
|
-- Convert all slash command methods
|
|
for cmd in pairs(commands) do
|
|
for idx,method in ipairs(slashCmdMethods) do
|
|
cmd[method] = self[method]
|
|
end
|
|
end
|
|
end
|
|
|
|
Dongle = DongleStub:Register(Dongle, Activate)
|