1 --[[-------------------------------------------------------------------------
2 Copyright (c) 2006-2007, Dongle Development Team
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are
9 * Redistributions of source code must retain the above copyright
10 notice, this list of conditions and the following disclaimer.
11 * Redistributions in binary form must reproduce the above
12 copyright notice, this list of conditions and the following
13 disclaimer in the documentation and/or other materials provided
14 with the distribution.
15 * Neither the name of the Dongle Development Team nor the names of
16 its contributors may be used to endorse or promote products derived
17 from this software without specific prior written permission.
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 ---------------------------------------------------------------------------]]
31 local major
= "DongleStub"
32 local minor
= tonumber(string.match("$Revision: 313 $", "(%d+)") or 1)
36 if not g
.DongleStub
or g
.DongleStub
:IsNewerVersion(major
, minor
) then
37 local lib
= setmetatable({}, {
38 __call
= function(t
,k
)
39 if type(t
.versions
) == "table" and t
.versions
[k
] then
40 return t
.versions
[k
].instance
42 error("Cannot find a library with name '"..tostring(k
).."'", 2)
47 function lib
:IsNewerVersion(major
, minor
)
48 local versionData
= self
.versions
and self
.versions
[major
]
50 -- If DongleStub versions have differing major version names
51 -- such as DongleStub-Beta0 and DongleStub-1.0-RC2 then a second
52 -- instance will be loaded, with older logic. This code attempts
53 -- to compensate for that by matching the major version against
54 -- "^DongleStub", and handling the version check correctly.
56 if major
:match("^DongleStub") then
57 local oldmajor
,oldminor
= self
:GetVersion()
58 if self
.versions
and self
.versions
[oldmajor
] then
59 return minor
> oldminor
65 if not versionData
then return true end
66 local oldmajor
,oldminor
= versionData
.instance
:GetVersion()
67 return minor
> oldminor
70 local function NilCopyTable(src
, dest
)
71 for k
,v
in pairs(dest
) do dest
[k
] = nil end
72 for k
,v
in pairs(src
) do dest
[k
] = v
end
75 function lib
:Register(newInstance
, activate
, deactivate
)
76 assert(type(newInstance
.GetVersion
) == "function",
77 "Attempt to register a library with DongleStub that does not have a 'GetVersion' method.")
79 local major
,minor
= newInstance
:GetVersion()
80 assert(type(major
) == "string",
81 "Attempt to register a library with DongleStub that does not have a proper major version.")
82 assert(type(minor
) == "number",
83 "Attempt to register a library with DongleStub that does not have a proper minor version.")
85 -- Generate a log of all library registrations
86 if not self
.log then self
.log = {} end
87 table.insert(self
.log, string.format("Register: %s, %s", major
, minor
))
89 if not self
:IsNewerVersion(major
, minor
) then return false end
90 if not self
.versions
then self
.versions
= {} end
92 local versionData
= self
.versions
[major
]
93 if not versionData
then
96 ["instance"] = newInstance
,
97 ["deactivate"] = deactivate
,
100 self
.versions
[major
] = versionData
101 if type(activate
) == "function" then
102 table.insert(self
.log, string.format("Activate: %s, %s", major
, minor
))
103 activate(newInstance
)
108 local oldDeactivate
= versionData
.deactivate
109 local oldInstance
= versionData
.instance
111 versionData
.deactivate
= deactivate
114 if type(activate
) == "function" then
115 table.insert(self
.log, string.format("Activate: %s, %s", major
, minor
))
116 skipCopy
= activate(newInstance
, oldInstance
)
119 -- Deactivate the old libary if necessary
120 if type(oldDeactivate
) == "function" then
121 local major
, minor
= oldInstance
:GetVersion()
122 table.insert(self
.log, string.format("Deactivate: %s, %s", major
, minor
))
123 oldDeactivate(oldInstance
, newInstance
)
126 -- Re-use the old table, and discard the new one
128 NilCopyTable(newInstance
, oldInstance
)
133 function lib
:GetVersion() return major
,minor
end
135 local function Activate(new
, old
)
136 -- This code ensures that we'll move the versions table even
137 -- if the major version names are different, in the case of
139 if not old
then old
= g
.DongleStub
end
142 new
.versions
= old
.versions
148 -- Actually trigger libary activation here
149 local stub
= g
.DongleStub
or lib
150 lib
= stub
:Register(lib
, Activate
)
153 --[[-------------------------------------------------------------------------
154 Begin Library Implementation
155 ---------------------------------------------------------------------------]]
157 local major
= "Dongle-1.0"
158 local minor
= tonumber(string.match("$Revision: 371 $", "(%d+)") or 1) + 500
159 -- ** IMPORTANT NOTE **
160 -- Due to some issues we had previously with Dongle revision numbers
161 -- we need to artificially inflate the minor revision number, to ensure
162 -- we load sequentially.
164 assert(DongleStub
, string.format("%s requires DongleStub.", major
))
166 if not DongleStub
:IsNewerVersion(major
, minor
) then return end
170 "RegisterEvent", "UnregisterEvent", "UnregisterAllEvents", "IsEventRegistered",
171 "RegisterMessage", "UnregisterMessage", "UnregisterAllMessages", "TriggerMessage", "IsMessageRegistered",
172 "EnableDebug", "IsDebugEnabled", "Print", "PrintF", "Debug", "DebugF", "Echo", "EchoF",
174 "InitializeSlashCommand",
175 "NewModule", "HasModule", "IterateModules",
189 --[[-------------------------------------------------------------------------
191 ---------------------------------------------------------------------------]]
194 ["ADDMESSAGE_REQUIRED"] = "The frame you specify must have an 'AddMessage' method.",
195 ["ALREADY_REGISTERED"] = "A Dongle with the name '%s' is already registered.",
196 ["BAD_ARGUMENT"] = "bad argument #%d to '%s' (%s expected, got %s)",
197 ["BAD_ARGUMENT_DB"] = "bad argument #%d to '%s' (DongleDB expected)",
198 ["CANNOT_DELETE_ACTIVE_PROFILE"] = "You cannot delete your active profile. Change profiles, then attempt to delete.",
199 ["DELETE_NONEXISTANT_PROFILE"] = "You cannot delete a non-existant profile.",
200 ["MUST_CALLFROM_DBOBJECT"] = "You must call '%s' from a Dongle database object.",
201 ["MUST_CALLFROM_REGISTERED"] = "You must call '%s' from a registered Dongle.",
202 ["MUST_CALLFROM_SLASH"] = "You must call '%s' from a Dongle slash command object.",
203 ["PROFILE_DOES_NOT_EXIST"] = "Profile '%s' doesn't exist.",
204 ["REPLACE_DEFAULTS"] = "You are attempting to register defaults with a database that already contains defaults.",
205 ["SAME_SOURCE_DEST"] = "Source/Destination profile cannot be the same profile.",
206 ["EVENT_REGISTER_SPECIAL"] = "You cannot register for the '%s' event. Use the '%s' method instead.",
207 ["Unknown"] = "Unknown",
208 ["INJECTDB_USAGE"] = "Usage: DongleCmd:InjectDBCommands(db, ['copy', 'delete', 'list', 'reset', 'set'])",
209 ["DBSLASH_PROFILE_COPY_DESC"] = "profile copy <name> - Copies profile <name> into your current profile.",
210 ["DBSLASH_PROFILE_COPY_PATTERN"] = "^profile copy (.+)$",
211 ["DBSLASH_PROFILE_DELETE_DESC"] = "profile delete <name> - Deletes the profile <name>.",
212 ["DBSLASH_PROFILE_DELETE_PATTERN"] = "^profile delete (.+)$",
213 ["DBSLASH_PROFILE_LIST_DESC"] = "profile list - Lists all valid profiles.",
214 ["DBSLASH_PROFILE_LIST_PATTERN"] = "^profile list$",
215 ["DBSLASH_PROFILE_RESET_DESC"] = "profile reset - Resets the current profile.",
216 ["DBSLASH_PROFILE_RESET_PATTERN"] = "^profile reset$",
217 ["DBSLASH_PROFILE_SET_DESC"] = "profile set <name> - Sets the current profile to <name>.",
218 ["DBSLASH_PROFILE_SET_PATTERN"] = "^profile set (.+)$",
219 ["DBSLASH_PROFILE_LIST_OUT"] = "Profile List:",
222 --[[-------------------------------------------------------------------------
223 Utility functions for Dongle use
224 ---------------------------------------------------------------------------]]
226 local function assert(level
,condition
,message
)
227 if not condition
then
232 local function argcheck(value
, num
, ...)
233 if type(num
) ~= "number" then
234 error(L
["BAD_ARGUMENT"]:format(2, "argcheck", "number", type(num
)), 1)
237 for i
=1,select("#", ...) do
238 if type(value
) == select(i
, ...) then return end
241 local types
= strjoin(", ", ...)
242 local name
= string.match(debugstack(2,2,0), ": in function [`<](.-)['>]")
243 error(L
["BAD_ARGUMENT"]:format(num
, name
, types
, type(value
)), 3)
246 local function safecall(func
,...)
247 local success
,err
= pcall(func
,...)
249 geterrorhandler()(err
)
253 --[[-------------------------------------------------------------------------
254 Dongle constructor, and DongleModule system
255 ---------------------------------------------------------------------------]]
257 function Dongle
:New(name
, obj
)
258 argcheck(name
, 2, "string")
259 argcheck(obj
, 3, "table", "nil")
265 if registry
[name
] then
266 error(string.format(L
["ALREADY_REGISTERED"], name
))
269 local reg
= {["obj"] = obj
, ["name"] = name
}
275 for k
,v
in pairs(methods
) do
279 -- Add this Dongle to the end of the queue
280 table.insert(loadqueue
, obj
)
284 function Dongle
:NewModule(name
, obj
)
285 local reg
= lookup
[self
]
286 assert(3, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "NewModule"))
287 argcheck(name
, 2, "string")
288 argcheck(obj
, 3, "table", "nil")
290 obj
,name
= Dongle
:New(name
, obj
)
292 if not reg
.modules
then reg
.modules
= {} end
293 reg
.modules
[obj
] = obj
294 reg
.modules
[name
] = obj
299 function Dongle
:HasModule(module
)
300 local reg
= lookup
[self
]
301 assert(3, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "HasModule"))
302 argcheck(module
, 2, "string", "table")
304 return reg
.modules
and reg
.modules
[module
]
307 local function ModuleIterator(t
, name
)
308 if not t
then return end
311 name
,obj
= next(t
, name
)
312 until type(name
) == "string" or not name
317 function Dongle
:IterateModules()
318 local reg
= lookup
[self
]
319 assert(3, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "IterateModules"))
321 return ModuleIterator
, reg
.modules
324 --[[-------------------------------------------------------------------------
325 Event registration system
326 ---------------------------------------------------------------------------]]
328 local function OnEvent(frame
, event
, ...)
329 local eventTbl
= events
[event
]
331 for obj
,func
in pairs(eventTbl
) do
332 if type(func
) == "string" then
333 if type(obj
[func
]) == "function" then
334 safecall(obj
[func
], obj
, event
, ...)
337 safecall(func
, event
, ...)
343 local specialEvents
= {
344 ["PLAYER_LOGIN"] = "Enable",
345 ["PLAYER_LOGOUT"] = "Disable",
348 function Dongle
:RegisterEvent(event
, func
)
349 local reg
= lookup
[self
]
350 assert(3, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "RegisterEvent"))
351 argcheck(event
, 2, "string")
352 argcheck(func
, 3, "string", "function", "nil")
354 local special
= (self
~= Dongle
) and specialEvents
[event
]
356 error(string.format(L
["EVENT_REGISTER_SPECIAL"], event
, special
), 3)
359 -- Name the method the same as the event if necessary
360 if not func
then func
= event
end
362 if not events
[event
] then
364 frame
:RegisterEvent(event
)
366 events
[event
][self
] = func
369 function Dongle
:UnregisterEvent(event
)
370 local reg
= lookup
[self
]
371 assert(3, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "UnregisterEvent"))
372 argcheck(event
, 2, "string")
374 local tbl
= events
[event
]
377 if not next(tbl
) then
379 frame
:UnregisterEvent(event
)
384 function Dongle
:UnregisterAllEvents()
385 local reg
= lookup
[self
]
386 assert(3, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "UnregisterAllEvents"))
388 for event
,tbl
in pairs(events
) do
390 if not next(tbl
) then
392 frame
:UnregisterEvent(event
)
397 function Dongle
:IsEventRegistered(event
)
398 local reg
= lookup
[self
]
399 assert(3, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "IsEventRegistered"))
400 argcheck(event
, 2, "string")
402 local tbl
= events
[event
]
406 --[[-------------------------------------------------------------------------
407 Inter-Addon Messaging System
408 ---------------------------------------------------------------------------]]
410 function Dongle
:RegisterMessage(msg
, func
)
411 argcheck(self
, 1, "table")
412 argcheck(msg
, 2, "string")
413 argcheck(func
, 3, "string", "function", "nil")
415 -- Name the method the same as the message if necessary
416 if not func
then func
= msg
end
418 if not messages
[msg
] then
421 messages
[msg
][self
] = func
424 function Dongle
:UnregisterMessage(msg
)
425 argcheck(self
, 1, "table")
426 argcheck(msg
, 2, "string")
428 local tbl
= messages
[msg
]
431 if not next(tbl
) then
437 function Dongle
:UnregisterAllMessages()
438 argcheck(self
, 1, "table")
440 for msg
,tbl
in pairs(messages
) do
442 if not next(tbl
) then
448 function Dongle
:TriggerMessage(msg
, ...)
449 argcheck(self
, 1, "table")
450 argcheck(msg
, 2, "string")
451 local msgTbl
= messages
[msg
]
452 if not msgTbl
then return end
454 for obj
,func
in pairs(msgTbl
) do
455 if type(func
) == "string" then
456 if type(obj
[func
]) == "function" then
457 safecall(obj
[func
], obj
, msg
, ...)
460 safecall(func
, msg
, ...)
465 function Dongle
:IsMessageRegistered(msg
)
466 argcheck(self
, 1, "table")
467 argcheck(msg
, 2, "string")
469 local tbl
= messages
[msg
]
473 --[[-------------------------------------------------------------------------
474 Debug and Print utility functions
475 ---------------------------------------------------------------------------]]
477 function Dongle
:EnableDebug(level
, frame
)
478 local reg
= lookup
[self
]
479 assert(3, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "EnableDebug"))
480 argcheck(level
, 2, "number", "nil")
481 argcheck(frame
, 3, "table", "nil")
483 assert(3, type(frame
) == "nil" or type(frame
.AddMessage
) == "function", L
["ADDMESSAGE_REQUIRED"])
484 reg
.debugFrame
= frame
or ChatFrame1
485 reg
.debugLevel
= level
488 function Dongle
:IsDebugEnabled()
489 local reg
= lookup
[self
]
490 assert(3, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "EnableDebug"))
492 return reg
.debugLevel
, reg
.debugFrame
495 local function argsToStrings(a1
, ...)
496 if select("#", ...) > 0 then
497 return tostring(a1
), argsToStrings(...)
503 local function printHelp(obj
, method
, header
, frame
, msg
, ...)
504 local reg
= lookup
[obj
]
505 assert(4, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], method
))
507 local name
= reg
.name
510 msg
= "|cFF33FF99"..name
.."|r: "..tostring(msg
)
513 if select("#", ...) > 0 then
514 msg
= string.join(", ", msg
, argsToStrings(...))
517 frame
:AddMessage(msg
)
520 local function printFHelp(obj
, method
, header
, frame
, msg
, ...)
521 local reg
= lookup
[obj
]
522 assert(4, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], method
))
524 local name
= reg
.name
528 msg
= "|cFF33FF99%s|r: " .. msg
529 success
,txt
= pcall(string.format, msg
, name
, ...)
531 success
,txt
= pcall(string.format, msg
, ...)
535 frame
:AddMessage(txt
)
537 error(string.gsub(txt
, "'%?'", string.format("'%s'", method
)), 3)
541 function Dongle
:Print(msg
, ...)
542 local reg
= lookup
[self
]
543 assert(1, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "Print"))
544 argcheck(msg
, 2, "number", "string", "boolean", "table", "function", "thread", "userdata")
545 return printHelp(self
, "Print", true, DEFAULT_CHAT_FRAME
, msg
, ...)
548 function Dongle
:PrintF(msg
, ...)
549 local reg
= lookup
[self
]
550 assert(1, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "PrintF"))
551 argcheck(msg
, 2, "number", "string", "boolean", "table", "function", "thread", "userdata")
552 return printFHelp(self
, "PrintF", true, DEFAULT_CHAT_FRAME
, msg
, ...)
555 function Dongle
:Echo(msg
, ...)
556 local reg
= lookup
[self
]
557 assert(1, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "Echo"))
558 argcheck(msg
, 2, "number", "string", "boolean", "table", "function", "thread", "userdata")
559 return printHelp(self
, "Echo", false, DEFAULT_CHAT_FRAME
, msg
, ...)
562 function Dongle
:EchoF(msg
, ...)
563 local reg
= lookup
[self
]
564 assert(1, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "EchoF"))
565 argcheck(msg
, 2, "number", "string", "boolean", "table", "function", "thread", "userdata")
566 return printFHelp(self
, "EchoF", false, DEFAULT_CHAT_FRAME
, msg
, ...)
569 function Dongle
:Debug(level
, ...)
570 local reg
= lookup
[self
]
571 assert(3, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "Debug"))
572 argcheck(level
, 2, "number")
574 if reg
.debugLevel
and level
<= reg
.debugLevel
then
575 printHelp(self
, "Debug", true, reg
.debugFrame
, ...)
579 function Dongle
:DebugF(level
, ...)
580 local reg
= lookup
[self
]
581 assert(3, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "DebugF"))
582 argcheck(level
, 2, "number")
584 if reg
.debugLevel
and level
<= reg
.debugLevel
then
585 printFHelp(self
, "DebugF", true, reg
.debugFrame
, ...)
589 --[[-------------------------------------------------------------------------
591 ---------------------------------------------------------------------------]]
594 "RegisterDefaults", "SetProfile", "GetProfiles", "DeleteProfile", "CopyProfile",
595 "GetCurrentProfile", "ResetProfile", "ResetDB",
599 local function copyTable(src
)
601 for k
,v
in pairs(src
) do
602 if type(k
) == "table" then
605 if type(v
) == "table" then
613 local function copyDefaults(dest
, src
, force
)
614 for k
,v
in pairs(src
) do
616 if type(v
) == "table" then
617 -- Values are tables, need some magic here
620 __index
= function(t
,k
)
621 local mt
= getmetatable(dest
)
622 local cache
= rawget(mt
, "__cache")
623 local tbl
= rawget(cache
, k
)
628 rawset(cache
, k
, tbl
)
629 local mt
= getmetatable(tbl
)
632 setmetatable(tbl
, mt
)
634 local newindex
= function(t
,k
,v
)
635 rawset(parent
, parentkey
, t
)
638 rawset(mt
, "__newindex", newindex
)
643 setmetatable(dest
, mt
)
644 -- Now need to set the metatable on any child tables
645 for dkey
,dval
in pairs(dest
) do
646 copyDefaults(dval
, v
)
649 -- Values are not tables, so this is just a simple return
650 local mt
= {__index
= function() return v
end}
651 setmetatable(dest
, mt
)
653 elseif type(v
) == "table" then
654 if not dest
[k
] then dest
[k
] = {} end
655 copyDefaults(dest
[k
], v
, force
)
657 if (dest
[k
] == nil) or force
then
664 local function removeDefaults(db
, defaults
)
665 if not db
then return end
666 for k
,v
in pairs(defaults
) do
667 if k
== "*" and type(v
) == "table" then
668 -- check for any defaults that have been changed
669 local mt
= getmetatable(db
)
670 local cache
= rawget(mt
, "__cache")
672 for cacheKey
,cacheValue
in pairs(cache
) do
673 removeDefaults(cacheValue
, v
)
674 if next(cacheValue
) ~= nil then
675 -- Something's changed
676 rawset(db
, cacheKey
, cacheValue
)
679 -- Now loop through all the actual k,v pairs and remove
680 for key
,value
in pairs(db
) do
681 removeDefaults(value
, v
)
683 elseif type(v
) == "table" and db
[k
] then
684 removeDefaults(db
[k
], v
)
685 if not next(db
[k
]) then
689 if db
[k
] == defaults
[k
] then
696 local function initSection(db
, section
, svstore
, key
, defaults
)
697 local sv
= rawget(db
, "sv")
700 if not sv
[svstore
] then sv
[svstore
] = {} end
701 if not sv
[svstore
][key
] then
702 sv
[svstore
][key
] = {}
706 local tbl
= sv
[svstore
][key
]
709 copyDefaults(tbl
, defaults
)
711 rawset(db
, section
, tbl
)
713 return tableCreated
, tbl
717 __index
= function(t
, section
)
718 local keys
= rawget(t
, "keys")
719 local key
= keys
[section
]
721 local defaultTbl
= rawget(t
, "defaults")
722 local defaults
= defaultTbl
and defaultTbl
[section
]
724 if section
== "profile" then
725 local new
= initSection(t
, section
, "profiles", key
, defaults
)
727 Dongle
:TriggerMessage("DONGLE_PROFILE_CREATED", t
, rawget(t
, "parent"), rawget(t
, "sv_name"), key
)
729 elseif section
== "profiles" then
730 local sv
= rawget(t
, "sv")
731 if not sv
.profiles
then sv
.profiles
= {} end
732 rawset(t
, "profiles", sv
.profiles
)
733 elseif section
== "global" then
734 local sv
= rawget(t
, "sv")
735 if not sv
.global
then sv
.global
= {} end
737 copyDefaults(sv
.global
, defaults
)
739 rawset(t
, section
, sv
.global
)
741 initSection(t
, section
, section
, key
, defaults
)
745 return rawget(t
, section
)
749 local function initdb(parent
, name
, defaults
, defaultProfile
, olddb
)
750 -- This allows us to use an arbitrary table as base instead of saved variable name
752 if type(name
) == "string" then
758 elseif type(name
) == "table" then
762 -- Generate the database keys for each section
763 local char
= string.format("%s - %s", UnitName("player"), GetRealmName())
764 local realm
= GetRealmName()
765 local class
= select(2, UnitClass("player"))
766 local race
= select(2, UnitRace("player"))
767 local faction
= UnitFactionGroup("player")
768 local factionrealm
= string.format("%s - %s", faction
, realm
)
770 -- Make a container for profile keys
771 if not sv
.profileKeys
then sv
.profileKeys
= {} end
773 -- Try to get the profile selected from the char db
774 local profileKey
= sv
.profileKeys
[char
] or defaultProfile
or char
775 sv
.profileKeys
[char
] = profileKey
782 ["faction"] = faction
,
783 ["factionrealm"] = factionrealm
,
785 ["profile"] = profileKey
,
786 ["profiles"] = true, -- Don't create until we need
789 -- If we've been passed an old database, clear it out
791 for k
,v
in pairs(olddb
) do olddb
[k
] = nil end
794 -- Give this database the metatable so it initializes dynamically
795 local db
= setmetatable(olddb
or {}, dbmt
)
797 -- Copy methods locally
798 for idx
,method
in pairs(dbMethods
) do
799 db
[method
] = Dongle
[method
]
802 -- Set some properties in the object we're returning
803 db
.profiles
= sv
.profiles
807 db
.defaults
= defaults
815 function Dongle
:InitializeDB(name
, defaults
, defaultProfile
)
816 local reg
= lookup
[self
]
817 assert(3, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "InitializeDB"))
818 argcheck(name
, 2, "string", "table")
819 argcheck(defaults
, 3, "table", "nil")
820 argcheck(defaultProfile
, 4, "string", "nil")
822 return initdb(self
, name
, defaults
, defaultProfile
)
825 -- This function operates on a Dongle DB object
826 function Dongle
.RegisterDefaults(db
, defaults
)
827 assert(3, databases
[db
], string.format(L
["MUST_CALLFROM_DBOBJECT"], "RegisterDefaults"))
828 assert(3, db
.defaults
== nil, L
["REPLACE_DEFAUTS"])
829 argcheck(defaults
, 2, "table")
831 for section
,key
in pairs(db
.keys
) do
832 if defaults
[section
] and rawget(db
, section
) then
833 copyDefaults(db
[section
], defaults
[section
])
837 db
.defaults
= defaults
840 function Dongle
:ClearDBDefaults()
841 for db
in pairs(databases
) do
842 local defaults
= db
.defaults
845 if db
and defaults
then
846 for section
,key
in pairs(db
.keys
) do
847 if defaults
[section
] and rawget(db
, section
) then
848 removeDefaults(db
[section
], defaults
[section
])
852 for section
,key
in pairs(db
.keys
) do
853 local tbl
= rawget(db
, section
)
854 if tbl
and not next(tbl
) then
856 if type(key
) == "string" then
857 sv
[section
][key
] = nil
868 function Dongle
.SetProfile(db
, name
)
869 assert(3, databases
[db
], string.format(L
["MUST_CALLFROM_DBOBJECT"], "SetProfile"))
870 argcheck(name
, 2, "string")
872 local old
= db
.profile
873 local defaults
= db
.defaults
and db
.defaults
.profile
876 -- Remove the defaults from the old profile
877 removeDefaults(old
, defaults
)
881 db
.keys
["profile"] = name
882 db
.sv
.profileKeys
[db
.keys
.char
] = name
884 Dongle
:TriggerMessage("DONGLE_PROFILE_CHANGED", db
, db
.parent
, db
.sv_name
, db
.keys
.profile
)
887 function Dongle
.GetProfiles(db
, t
)
888 assert(3, databases
[db
], string.format(L
["MUST_CALLFROM_DBOBJECT"], "GetProfiles"))
889 argcheck(t
, 2, "table", "nil")
893 for profileKey
in pairs(db
.sv
.profiles
) do
900 function Dongle
.GetCurrentProfile(db
)
901 assert(e
, databases
[db
], string.format(L
["MUST_CALLFROM_DBOBJECT"], "GetCurrentProfile"))
902 return db
.keys
.profile
905 function Dongle
.DeleteProfile(db
, name
)
906 assert(3, databases
[db
], string.format(L
["MUST_CALLFROM_DBOBJECT"], "DeleteProfile"))
907 argcheck(name
, 2, "string")
909 if db
.keys
.profile
== name
then
910 error(L
["CANNOT_DELETE_ACTIVE_PROFILE"], 2)
913 assert(type(db
.sv
.profiles
[name
]) == "table", L
["DELETE_NONEXISTANT_PROFILE"])
915 db
.sv
.profiles
[name
] = nil
916 Dongle
:TriggerMessage("DONGLE_PROFILE_DELETED", db
, db
.parent
, db
.sv_name
, name
)
919 function Dongle
.CopyProfile(db
, name
)
920 assert(3, databases
[db
], string.format(L
["MUST_CALLFROM_DBOBJECT"], "CopyProfile"))
921 argcheck(name
, 2, "string")
923 assert(3, db
.keys
.profile
~= name
, L
["SAME_SOURCE_DEST"])
924 assert(3, type(db
.sv
.profiles
[name
]) == "table", string.format(L
["PROFILE_DOES_NOT_EXIST"], name
))
926 local profile
= db
.profile
927 local source
= db
.sv
.profiles
[name
]
929 copyDefaults(profile
, source
, true)
930 Dongle
:TriggerMessage("DONGLE_PROFILE_COPIED", db
, db
.parent
, db
.sv_name
, name
, db
.keys
.profile
)
933 function Dongle
.ResetProfile(db
)
934 assert(3, databases
[db
], string.format(L
["MUST_CALLFROM_DBOBJECT"], "ResetProfile"))
936 local profile
= db
.profile
938 for k
,v
in pairs(profile
) do
942 local defaults
= db
.defaults
and db
.defaults
.profile
944 copyDefaults(profile
, defaults
)
946 Dongle
:TriggerMessage("DONGLE_PROFILE_RESET", db
, db
.parent
, db
.sv_name
, db
.keys
.profile
)
950 function Dongle
.ResetDB(db
, defaultProfile
)
951 assert(3, databases
[db
], string.format(L
["MUST_CALLFROM_DBOBJECT"], "ResetDB"))
952 argcheck(defaultProfile
, 2, "nil", "string")
955 for k
,v
in pairs(sv
) do
959 local parent
= db
.parent
961 initdb(parent
, db
.sv_name
, db
.defaults
, defaultProfile
, db
)
962 Dongle
:TriggerMessage("DONGLE_DATABASE_RESET", db
, parent
, db
.sv_name
, db
.keys
.profile
)
963 Dongle
:TriggerMessage("DONGLE_PROFILE_CHANGED", db
, db
.parent
, db
.sv_name
, db
.keys
.profile
)
967 function Dongle
.RegisterNamespace(db
, name
, defaults
)
968 assert(3, databases
[db
], string.format(L
["MUST_CALLFROM_DBOBJECT"], "RegisterNamespace"))
969 argcheck(name
, 2, "string")
970 argcheck(defaults
, 3, "nil", "string")
973 if not sv
.namespaces
then sv
.namespaces
= {} end
974 if not sv
.namespaces
[name
] then
975 sv
.namespaces
[name
] = {}
978 local newDB
= initdb(db
, sv
.namespaces
[name
], defaults
, db
.keys
.profile
)
979 -- Remove the :SetProfile method from newDB
980 newDB
.SetProfile
= nil
982 if not db
.children
then db
.children
= {} end
983 table.insert(db
.children
, newDB
)
987 --[[-------------------------------------------------------------------------
989 ---------------------------------------------------------------------------]]
991 local slashCmdMethods
= {
993 "RegisterSlashHandler",
997 local function OnSlashCommand(cmd
, cmd_line
)
999 for idx
,tbl
in pairs(cmd
.patterns
) do
1000 local pattern
= tbl
.pattern
1001 if string.match(cmd_line
, pattern
) then
1002 local handler
= tbl
.handler
1003 if type(tbl
.handler
) == "string" then
1005 -- Look in the command object before we look at the parent object
1006 if cmd
[handler
] then obj
= cmd
end
1007 if cmd
.parent
[handler
] then obj
= cmd
.parent
end
1009 obj
[handler
](obj
, string.match(cmd_line
, pattern
))
1012 handler(string.match(cmd_line
, pattern
))
1021 function Dongle
:InitializeSlashCommand(desc
, name
, ...)
1022 local reg
= lookup
[self
]
1023 assert(3, reg
, string.format(L
["MUST_CALLFROM_REGISTERED"], "InitializeSlashCommand"))
1024 argcheck(desc
, 2, "string")
1025 argcheck(name
, 3, "string")
1026 argcheck(select(1, ...), 4, "string")
1027 for i
= 2,select("#", ...) do
1028 argcheck(select(i
, ...), i
+2, "string")
1035 cmd
.slashes
= { ... }
1036 for idx
,method
in pairs(slashCmdMethods
) do
1037 cmd
[method
] = Dongle
[method
]
1040 local genv
= getfenv(0)
1042 for i
= 1,select("#", ...) do
1043 genv
["SLASH_"..name
..tostring(i
)] = "/"..select(i
, ...)
1046 genv
.SlashCmdList
[name
] = function(...) OnSlashCommand(cmd
, ...) end
1048 commands
[cmd
] = true
1053 function Dongle
.RegisterSlashHandler(cmd
, desc
, pattern
, handler
)
1054 assert(3, commands
[cmd
], string.format(L
["MUST_CALLFROM_SLASH"], "RegisterSlashHandler"))
1056 argcheck(desc
, 2, "string")
1057 argcheck(pattern
, 3, "string")
1058 argcheck(handler
, 4, "function", "string")
1060 if not cmd
.patterns
then
1064 table.insert(cmd
.patterns
, {
1066 ["handler"] = handler
,
1067 ["pattern"] = pattern
,
1071 function Dongle
.PrintUsage(cmd
)
1072 assert(3, commands
[cmd
], string.format(L
["MUST_CALLFROM_SLASH"], "PrintUsage"))
1073 local parent
= cmd
.parent
1075 parent
:Echo(cmd
.desc
.."\n".."/"..table.concat(cmd
.slashes
, ", /")..":\n")
1076 if cmd
.patterns
then
1077 for idx
,tbl
in ipairs(cmd
.patterns
) do
1078 parent
:Echo(" - " .. tbl
.desc
)
1083 local dbcommands
= {
1085 L
["DBSLASH_PROFILE_COPY_DESC"],
1086 L
["DBSLASH_PROFILE_COPY_PATTERN"],
1090 L
["DBSLASH_PROFILE_DELETE_DESC"],
1091 L
["DBSLASH_PROFILE_DELETE_PATTERN"],
1095 L
["DBSLASH_PROFILE_LIST_DESC"],
1096 L
["DBSLASH_PROFILE_LIST_PATTERN"],
1099 L
["DBSLASH_PROFILE_RESET_DESC"],
1100 L
["DBSLASH_PROFILE_RESET_PATTERN"],
1104 L
["DBSLASH_PROFILE_SET_DESC"],
1105 L
["DBSLASH_PROFILE_SET_PATTERN"],
1110 function Dongle
.InjectDBCommands(cmd
, db
, ...)
1111 assert(3, commands
[cmd
], string.format(L
["MUST_CALLFROM_SLASH"], "InjectDBCommands"))
1112 assert(3, databases
[db
], string.format(L
["BAD_ARGUMENT_DB"], 2, "InjectDBCommands"))
1113 local argc
= select("#", ...)
1114 assert(3, argc
> 0, L
["INJECTDB_USAGE"])
1117 local cmdname
= string.lower(select(i
, ...))
1118 local entry
= dbcommands
[cmdname
]
1119 assert(entry
, L
["INJECTDB_USAGE"])
1120 local func
= entry
[3]
1123 if cmdname
== "list" then
1124 handler
= function(...)
1125 local profiles
= db
:GetProfiles()
1126 db
.parent
:Print(L
["DBSLASH_PROFILE_LIST_OUT"] .. "\n" .. strjoin("\n", unpack(profiles
)))
1129 handler
= function(...) db
[entry
[3]]
(db
, ...) end
1132 cmd
:RegisterSlashHandler(entry
[1], entry
[2], handler
)
1136 --[[-------------------------------------------------------------------------
1137 Internal Message/Event Handlers
1138 ---------------------------------------------------------------------------]]
1140 local function PLAYER_LOGOUT(event
)
1141 Dongle
:ClearDBDefaults()
1142 for k
,v
in pairs(registry
) do
1144 if type(obj
["Disable"]) == "function" then
1145 safecall(obj
["Disable"], obj
)
1150 local function PLAYER_LOGIN()
1151 Dongle
.initialized
= true
1152 for i
=1, #loadorder
do
1153 local obj
= loadorder
[i
]
1154 if type(obj
.Enable
) == "function" then
1155 safecall(obj
.Enable
, obj
)
1161 local function ADDON_LOADED(event
, ...)
1162 for i
=1, #loadqueue
do
1163 local obj
= loadqueue
[i
]
1164 table.insert(loadorder
, obj
)
1166 if type(obj
.Initialize
) == "function" then
1167 safecall(obj
.Initialize
, obj
)
1172 if not Dongle
.initialized
then
1173 if type(IsLoggedIn
) == "function" then
1174 Dongle
.initialized
= IsLoggedIn()
1176 Dongle
.initialized
= ChatFrame1
.defaultLanguage
1180 if Dongle
.initialized
then
1181 for i
=1, #loadorder
do
1182 local obj
= loadorder
[i
]
1183 if type(obj
.Enable
) == "function" then
1184 safecall(obj
.Enable
, obj
)
1191 local function DONGLE_PROFILE_CHANGED(msg
, db
, parent
, sv_name
, profileKey
)
1192 local children
= db
.children
1194 for i
,namespace
in ipairs(children
) do
1195 local old
= namespace
.profile
1196 local defaults
= namespace
.defaults
and namespace
.defaults
.profile
1199 -- Remove the defaults from the old profile
1200 removeDefaults(old
, defaults
)
1203 namespace
.profile
= nil
1204 namespace
.keys
["profile"] = profileKey
1209 --[[-------------------------------------------------------------------------
1210 DongleStub required functions and registration
1211 ---------------------------------------------------------------------------]]
1213 function Dongle
:GetVersion() return major
,minor
end
1215 local function Activate(self
, old
)
1217 registry
= old
.registry
or registry
1218 lookup
= old
.lookup
or lookup
1219 loadqueue
= old
.loadqueue
or loadqueue
1220 loadorder
= old
.loadorder
or loadorder
1221 events
= old
.events
or events
1222 databases
= old
.databases
or databases
1223 commands
= old
.commands
or commands
1224 messages
= old
.messages
or messages
1225 frame
= old
.frame
or CreateFrame("Frame")
1227 frame
= CreateFrame("Frame")
1228 local reg
= {obj
= self
, name
= "Dongle"}
1229 registry
[major
] = reg
1234 self
.registry
= registry
1235 self
.lookup
= lookup
1236 self
.loadqueue
= loadqueue
1237 self
.loadorder
= loadorder
1238 self
.events
= events
1239 self
.databases
= databases
1240 self
.commands
= commands
1241 self
.messages
= messages
1244 frame
:SetScript("OnEvent", OnEvent
)
1246 local lib
= old
or self
1248 -- Lets make sure the lookup table has us.
1249 lookup
[lib
] = lookup
[major
]
1251 -- Register for events using Dongle itself
1252 lib
:RegisterEvent("ADDON_LOADED", ADDON_LOADED
)
1253 lib
:RegisterEvent("PLAYER_LOGIN", PLAYER_LOGIN
)
1254 lib
:RegisterEvent("PLAYER_LOGOUT", PLAYER_LOGOUT
)
1255 lib
:RegisterMessage("DONGLE_PROFILE_CHANGED", DONGLE_PROFILE_CHANGED
)
1257 -- Convert all the modules handles
1258 for name
,obj
in pairs(registry
) do
1259 for k
,v
in ipairs(methods
) do
1264 -- Convert all database methods
1265 for db
in pairs(databases
) do
1266 for idx
,method
in ipairs(dbMethods
) do
1267 db
[method
] = self
[method
]
1271 -- Convert all slash command methods
1272 for cmd
in pairs(commands
) do
1273 for idx
,method
in ipairs(slashCmdMethods
) do
1274 cmd
[method
] = self
[method
]
1279 -- Lets nuke any Dongle deactivate functions, please
1280 -- I hate nasty hacks.
1281 if DongleStub
.versions
and DongleStub
.versions
[major
] then
1282 local reg
= DongleStub
.versions
[major
]
1283 reg
.deactivate
= nil
1286 Dongle
= DongleStub
:Register(Dongle
, Activate
)