vanilla-wow-addons – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | --[[ Stubby |
2 | |||
3 | $Id: Stubby.lua 941 2006-07-10 17:34:32Z mentalpower $ |
||
4 | Version: 3.8.0 (Kangaroo) |
||
5 | |||
6 | Stubby is an addon that allows you to register boot code for |
||
7 | your addon. |
||
8 | |||
9 | This bootcode will be run whenever your addon does not demand |
||
10 | load on startup so that you can setup your own conditions for |
||
11 | loading. |
||
12 | |||
13 | A quick example of this is: |
||
14 | ------------------------------------------- |
||
15 | Stubby.RegisterBootCode("myAddOn", "CommandHandler", [[ |
||
16 | local function cmdHandler(msg) |
||
17 | LoadAddOn("myAddOn") |
||
18 | MyAddOn_Command(msg) |
||
19 | end |
||
20 | SLASH_MYADDON1 = "/myaddon" |
||
21 | SlashCmdList['MYADDON'] = cmdHandler |
||
22 | ]]); |
||
23 | ------------------------------------------- |
||
24 | So, what did this just do? It registered some boot code |
||
25 | (called "CommandHandler") with Stubby that Stubby will |
||
26 | (in the case you are not demand loaded) execute on your |
||
27 | behalf. |
||
28 | |||
29 | In the above example, your boot code sets up a command handler |
||
30 | which causes your addon to load and process the command. |
||
31 | |||
32 | Another example: |
||
33 | ------------------------------------------- |
||
34 | Stubby.CreateAddOnLoadBootCode("myAddOn", "Blizzard_AuctionUI") |
||
35 | ------------------------------------------- |
||
36 | Ok, what was that? Well you just setup some boot code |
||
37 | for your addon that will register an addon hook when |
||
38 | Stubby loads and your addon doesn't. This addon hook |
||
39 | will cause your addon to load when the AuctionUI does. |
||
40 | |||
41 | |||
42 | The primary functions that you will be interested in are: |
||
43 | CreateAddOnLoadBootCode(ownerAddOn, triggerAddOn) |
||
44 | CreateEventLoadBootCode(ownerAddOn, triggerEvent) |
||
45 | CreateFunctionLoadBootCode(ownerAddOn, triggerFunction) |
||
46 | And the manual, but vastly more powerful: |
||
47 | RegisterBootCode(ownerAddOn, bootName, bootCode) |
||
48 | |||
49 | |||
50 | Stubby can also save variables for you if you wish to retain |
||
51 | stateful information in your boot code. (maybe you have |
||
52 | recieved notification from your user that they wish always |
||
53 | to have your addon load for the current toon?) |
||
54 | |||
55 | These are the variable functions: |
||
56 | SetConfig(ownerAddOn, variable, value, isGlobal) |
||
57 | GetConfig(ownerAddOn, variable) |
||
58 | ClearConfig(ownerAddOn, variable) |
||
59 | |||
60 | The SetConfig function sets the configuration variable |
||
61 | "variable" for ownerAddOn to value. The variable is |
||
62 | per-toon unless isGlobal is set. |
||
63 | |||
64 | The GetConfig function gets "variable" for ownerAddOn |
||
65 | it will return per-toon values before global ones. |
||
66 | |||
67 | The ClearConfig function clears the toon specific and |
||
68 | global "variable" for ownerAddOn. |
||
69 | |||
70 | |||
71 | The following functions are also available for you to use |
||
72 | if you need to use some manual boot code and want to |
||
73 | hook into some function, addon or event within your boot |
||
74 | code: |
||
75 | Stubby.RegisterFunctionHook(triggerFunction, position, hookFunction, ...) |
||
76 | Stubby.RegisterAddOnHook(triggerAddOn, ownerAddOn, hookFunction, ...) |
||
77 | Stubby.RegisterEventHook(triggerEvent, ownerAddOn, hookFunction, ...) |
||
78 | |||
79 | RegisterFunctionHook allows you to hook into a function. |
||
80 | * The triggerFunction is a string that names the function you |
||
81 | want to hook into. eg: "GameTooltip.SetOwner" |
||
82 | * The position is a negative or positive number that defines |
||
83 | the actual calling order of the addon. The smaller or more |
||
84 | negative the number, the earlier in the call sequence your |
||
85 | hookFunction will be called, the larger the number, the |
||
86 | later your hook will be called. The actual original (hooked) |
||
87 | function is called at position 0, so if your addon is hooked |
||
88 | at a negative position, you will not have access to any |
||
89 | return values. |
||
90 | * You pass (by reference) your function that you wish called |
||
91 | as hookFunction. This function will be called with the |
||
92 | following parameters: |
||
93 | hookFunction(hookParams, returnValue, hook1, hook2 .. hookN) |
||
94 | - hookParams is a table containing the additional parameters |
||
95 | passed to the RegisterFunctionHook function (the "..." params) |
||
96 | - returnValue is an array of the returned values of the function |
||
97 | or nil if none. |
||
98 | - hook1..hookN are the original parameters of the hooked |
||
99 | function in the original order. |
||
100 | |||
101 | RegisterAddOnHook is very much like the register function hook |
||
102 | call except that there is no positioning (you may get notified in |
||
103 | any order with respect to any other addons which may be hooked) |
||
104 | * The triggerAddOn specifies the name of the addon of which you |
||
105 | want to be notified of it's loading. |
||
106 | * The ownerAddOn is your addon's name (used for removing hooks) |
||
107 | * The hookFunction is a function that gets called when the |
||
108 | triggerAddOn loads or if it is already loaded straight away. |
||
109 | This function will be called with the following parameters |
||
110 | hookFunction(hookParams) |
||
111 | - hookParams is a table containing the additional parameters |
||
112 | passed to the RegisterAddOnHook function (the "..." params) |
||
113 | |||
114 | RegisterEventHook allows you to hook an event in much the same |
||
115 | way as the above functions. |
||
116 | * The triggerEvent is an event which causes your hookFunction to |
||
117 | be executed. |
||
118 | * The ownerAddOn is your addon's name (used for removing hooks) |
||
119 | * The hookFunction is a function that gets called whenever the |
||
120 | triggerEvent fires (until canceled with UnregisterEventHook) |
||
121 | This function will be called with the following parameters: |
||
122 | hookFunction(hookParams, event, hook1, hook2 .. hookN) |
||
123 | - hookParams is a table containing the additional parameters |
||
124 | passed to the RegisterEventHook function (the "..." params) |
||
125 | - event is the event string that has just been fired |
||
126 | - hook1..hookN are the original parameters of the event |
||
127 | function in the original order. |
||
128 | |||
129 | Other functions which may be of interest are: |
||
130 | UnregisterFunctionHook(triggerFunction, hookFunc) |
||
131 | UnregisterAddOnHook(triggerAddOn, ownerAddOn) |
||
132 | UnregisterEventHook(triggerEvent, ownerAddOn) |
||
133 | UnregisterBootCode(ownerAddOn, bootName) |
||
134 | |||
135 | There is also a single exposed 'constant' allowing you to do |
||
136 | some basic version checking for compatibility: |
||
137 | Stubby.VERSION (introduced in revision 507) |
||
138 | This constant is Stubby's revision number, a simple positive |
||
139 | integer that will increase by an arbitrary amount with each |
||
140 | new version of Stubby. |
||
141 | Current $Revision: 941 $ |
||
142 | |||
143 | Example: |
||
144 | ------------------------------------------- |
||
145 | if (Stubby.VERSION and Stubby.VERSION >= 507) then |
||
146 | -- Register boot code |
||
147 | else |
||
148 | Stubby.Print("You need to update your version of Stubby!") |
||
149 | end |
||
150 | ------------------------------------------- |
||
151 | |||
152 | License: |
||
153 | This program is free software; you can redistribute it and/or |
||
154 | modify it under the terms of the GNU General Public License |
||
155 | as published by the Free Software Foundation; either version 2 |
||
156 | of the License, or (at your option) any later version. |
||
157 | |||
158 | This program is distributed in the hope that it will be useful, |
||
159 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
160 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
161 | GNU General Public License for more details. |
||
162 | |||
163 | You should have received a copy of the GNU General Public License |
||
164 | along with this program(see GLP.txt); if not, write to the Free Software |
||
165 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
166 | --]] |
||
167 | |||
168 | local cleanList |
||
169 | local config = { |
||
170 | hooks = { functions={}, origFuncs={} }, |
||
171 | calls = { functions={}, callList={} }, |
||
172 | loads = {}, |
||
173 | events = {}, |
||
174 | } |
||
175 | |||
176 | local StubbyConfig = {} |
||
177 | |||
178 | |||
179 | -- Function prototypes |
||
180 | local chatPrint -- chatPrint(...) |
||
181 | local checkAddOns -- checkAddOns() |
||
182 | local clearConfig -- clearConfig(ownerAddOn, variable) |
||
183 | local createAddOnLoadBootCode -- createAddOnLoadBootCode(ownerAddOn, triggerAddOn) |
||
184 | local createEventLoadBootCode -- createEventLoadBootCode(ownerAddOn, triggerEvent) |
||
185 | local createFunctionLoadBootCode -- createFunctionLoadBootCode(ownerAddOn, triggerFunction) |
||
186 | local eventWatcher -- eventWatcher(event) |
||
187 | local events -- events(event, param) |
||
188 | local getConfig -- getConfig(ownerAddOn, variable) |
||
189 | local getOrigFunc -- getOrigFunc(triggerFunction) |
||
190 | local hookCall -- hookCall(funcName, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) |
||
191 | local hookInto -- hookInto(triggerFunction) |
||
192 | local inspectAddOn -- inspectAddOn(addonName, title, info) |
||
193 | local loadWatcher -- loadWatcher(loadedAddOn) |
||
194 | local onLoaded -- onLoaded() |
||
195 | local onWorldStart -- onWorldStart() |
||
196 | local rebuildNotifications -- rebuildNotifications(notifyItems) |
||
197 | local registerAddOnHook -- registerAddOnHook(triggerAddOn, ownerAddOn, hookFunction, ...) |
||
198 | local registerBootCode -- registerBootCode(ownerAddOn, bootName, bootCode) |
||
199 | local registerEventHook -- registerEventHook(triggerEvent, ownerAddOn, hookFunction, ...) |
||
200 | local registerFunctionHook -- registerFunctionHook(triggerFunction, position, hookFunc, ...) |
||
201 | local runBootCodes -- runBootCodes() |
||
202 | local searchForNewAddOns -- searchForNewAddOns() |
||
203 | local cleanUpAddOnData -- cleanUpAddOnData() |
||
204 | local cleanUpAddOnConfigs -- cleanUpAddOnConfigs() |
||
205 | local setConfig -- setConfig(ownerAddOn, variable, value, isGlobal) |
||
206 | local shouldInspectAddOn -- shouldInspectAddOn(addonName) |
||
207 | local unregisterAddOnHook -- unregisterAddOnHook(triggerAddOn, ownerAddOn) |
||
208 | local unregisterBootCode -- unregisterBootCode(ownerAddOn, bootName) |
||
209 | local unregisterEventHook -- unregisterEventHook(triggerEvent, ownerAddOn) |
||
210 | local unregisterFunctionHook -- unregisterFunctionHook(triggerFunction, hookFunc) |
||
211 | |||
212 | |||
213 | -- Function definitions |
||
214 | |||
215 | -- This function takes all the items and their requested orders |
||
216 | -- and assigns an actual ordering to them. |
||
217 | function rebuildNotifications(notifyItems) |
||
218 | local notifyFuncs = {} |
||
219 | for hookType, hData in notifyItems do |
||
220 | notifyFuncs[hookType] = {} |
||
221 | |||
222 | -- Sort all hooks for this type in ascending numerical order. |
||
223 | local sortedPositions = {} |
||
224 | for requestedPos,_ in hData do |
||
225 | table.insert(sortedPositions, requestedPos) |
||
226 | end |
||
227 | table.sort(sortedPositions) |
||
228 | |||
229 | -- Process the sorted request list and insert in correct |
||
230 | -- order into the call list. |
||
231 | for _,requestedPos in sortedPositions do |
||
232 | local func = hData[requestedPos] |
||
233 | table.insert(notifyFuncs[hookType], func) |
||
234 | end |
||
235 | end |
||
236 | return notifyFuncs |
||
237 | end |
||
238 | |||
239 | -- This function's purpose is to execute all the attached |
||
240 | -- functions in order and the original call at just before |
||
241 | -- position 0. |
||
242 | function hookCall(funcName, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) |
||
243 | local result, r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16,r17,r18,r19,r20 |
||
244 | local orig = Stubby.GetOrigFunc(funcName) |
||
245 | if (not orig) then return end |
||
246 | |||
247 | local res; |
||
248 | local retVal = nil |
||
249 | local returns = false |
||
250 | |||
251 | local callees |
||
252 | if config.calls and config.calls.callList and config.calls.callList[funcName] then |
||
253 | callees = config.calls.callList[funcName] |
||
254 | end |
||
255 | |||
256 | if (callees) then |
||
257 | for _,func in ipairs(callees) do |
||
258 | if (orig and func.p >= 0) then |
||
259 | result, r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16,r17,r18,r19,r20 |
||
260 | = pcall(orig, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) |
||
261 | if (result) then |
||
262 | if r1 or r2 or r3 or r4 or r5 or r6 or r7 or r8 or r9 or r10 or r11 or r12 or r13 or r14 or r15 or r16 or r17 or r18 or r19 or r20 then |
||
263 | retVal = { r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16,r17,r18,r19,r20 } |
||
264 | returns = true |
||
265 | end |
||
266 | else |
||
267 | Stubby.Print("Error occured while running hooks for: ", tostring(funcName), "\n", r1, "\nCall Chain:\n", debugstack(2, 3, 6)) |
||
268 | end |
||
269 | orig = nil |
||
270 | end |
||
271 | local result, res, addit = pcall(func.f, func.a, retVal, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20); |
||
272 | if (result) then |
||
273 | if (type(res) == 'string') then |
||
274 | if (res == 'abort') then return end |
||
275 | if (res == 'killorig') then orig = nil end |
||
276 | if (res == 'setreturn') then |
||
277 | retVal = addit |
||
278 | returns = true |
||
279 | end |
||
280 | if (res == 'setparams') then |
||
281 | a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20 = unpack(addit) |
||
282 | end |
||
283 | end |
||
284 | else |
||
285 | Stubby.Print("Error occured while running hooks for: ", tostring(funcName), "\n", res, "\nCall Chain:\n", debugstack(2, 3, 6)) |
||
286 | end |
||
287 | end |
||
288 | end |
||
289 | if (orig) then |
||
290 | result, r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16,r17,r18,r19,r20 |
||
291 | = pcall(orig, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) |
||
292 | if (result) then |
||
293 | if r1 or r2 or r3 or r4 or r5 or r6 or r7 or r8 or r9 or r10 or r11 or r12 or r13 or r14 or r15 or r16 or r17 or r18 or r19 or r20 then |
||
294 | returns = true |
||
295 | end |
||
296 | else |
||
297 | Stubby.Print("Error occured while running hooks for: ", tostring(funcName), "\n", r1, "\nCall Chain:\n", debugstack(2, 3, 6)) |
||
298 | end |
||
299 | end |
||
300 | if (returns) then |
||
301 | return r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16,r17,r18,r19,r20 |
||
302 | end |
||
303 | end |
||
304 | |||
305 | -- This function automatically hooks Stubby in place of the |
||
306 | -- original function, dynamically. |
||
307 | Stubby_OldFunction = nil |
||
308 | Stubby_NewFunction = nil |
||
309 | function hookInto(triggerFunction) |
||
310 | if (config.hooks.origFuncs[triggerFunction]) then return end |
||
311 | RunScript("Stubby_OldFunction = "..triggerFunction) |
||
312 | RunScript("Stubby_NewFunction = function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) return Stubby.HookCall('"..triggerFunction.."', a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) end") |
||
313 | RunScript(triggerFunction.." = Stubby_NewFunction") |
||
314 | config.hooks.functions[triggerFunction] = Stubby_NewFunction; |
||
315 | config.hooks.origFuncs[triggerFunction] = Stubby_OldFunction; |
||
316 | Stubby_NewFunction = nil |
||
317 | Stubby_OldFunction = nil |
||
318 | end |
||
319 | |||
320 | function getOrigFunc(triggerFunction) |
||
321 | if (config.hooks) and (config.hooks.origFuncs) then |
||
322 | return config.hooks.origFuncs[triggerFunction] |
||
323 | end |
||
324 | end |
||
325 | |||
326 | |||
327 | -- This function causes a given function to be hooked by stubby and |
||
328 | -- configures the hook function to be called at the given position. |
||
329 | -- The original function gets executed a position 0. Use a negative |
||
330 | -- number to get called before the original function, and positive |
||
331 | -- number to get called after the original function. Default position |
||
332 | -- is 200. If someone else is already using your number, you will get |
||
333 | -- automatically moved up for after or down for before. Please also |
||
334 | -- leave space for other people who may need to position their hooks |
||
335 | -- in between your hook and the original. |
||
336 | function registerFunctionHook(triggerFunction, position, hookFunc, ...) |
||
337 | local insertPos = tonumber(position) or 200 |
||
338 | local funcObj = { f=hookFunc, a=arg, p=position } |
||
339 | if (table.getn(arg) == 0) then funcObj.a = nil; end |
||
340 | |||
341 | if (not config.calls) then config.calls = {} end |
||
342 | if (not config.calls.functions) then config.calls.functions = {} end |
||
343 | if (config.calls.functions[triggerFunction]) then |
||
344 | while (config.calls.functions[triggerFunction][insertPos]) do |
||
345 | if (position >= 0) then |
||
346 | insertPos = insertPos + 1 |
||
347 | else |
||
348 | insertPos = insertPos - 1 |
||
349 | end |
||
350 | end |
||
351 | config.calls.functions[triggerFunction][insertPos] = funcObj |
||
352 | else |
||
353 | config.calls.functions[triggerFunction] = {} |
||
354 | config.calls.functions[triggerFunction][insertPos] = funcObj |
||
355 | end |
||
356 | config.calls.callList = rebuildNotifications(config.calls.functions); |
||
357 | hookInto(triggerFunction) |
||
358 | end |
||
359 | |||
360 | function unregisterFunctionHook(triggerFunction, hookFunc) |
||
361 | if not (config.calls and config.calls.functions and config.calls.functions[triggerFunction]) then return end |
||
362 | for pos, funcObj in config.calls.functions[triggerFunction] do |
||
363 | if (funcObj and funcObj.f == hookFunc) then |
||
364 | config.calls.functions[triggerFunction][pos] = nil |
||
365 | end |
||
366 | end |
||
367 | end |
||
368 | |||
369 | -- This function registers a given function to be called when a given |
||
370 | -- addon is loaded, or immediatly if it is already loaded (this can be |
||
371 | -- used to setup a hooking function to execute when an addon is loaded |
||
372 | -- but not before) |
||
373 | function registerAddOnHook(triggerAddOn, ownerAddOn, hookFunction, ...) |
||
374 | if (IsAddOnLoaded(triggerAddOn)) then |
||
375 | hookFunction(unpack(arg)) |
||
376 | else |
||
377 | local addon = string.lower(triggerAddOn) |
||
378 | if (not config.loads[addon]) then config.loads[addon] = {} end |
||
379 | config.loads[addon][ownerAddOn] = nil |
||
380 | if (hookFunction) then |
||
381 | config.loads[addon][ownerAddOn] = { f=hookFunction, a=arg } |
||
382 | end |
||
383 | end |
||
384 | end |
||
385 | |||
386 | function unregisterAddOnHook(triggerAddOn, ownerAddOn) |
||
387 | local addon = string.lower(triggerAddOn) |
||
388 | if (config.loads and config.loads[addon] and config.loads[addon][ownerAddOn]) then |
||
389 | config.loads[addon][ownerAddOn] = nil |
||
390 | end |
||
391 | end |
||
392 | |||
393 | |||
394 | function loadWatcher(loadedAddOn) |
||
395 | local addon = string.lower(loadedAddOn) |
||
396 | if (config.loads[addon]) then |
||
397 | local ownerAddOn, hookDetail |
||
398 | for ownerAddOn, hookDetail in config.loads[addon] do |
||
399 | hookDetail.f(hookDetail.a) |
||
400 | end |
||
401 | end |
||
402 | end |
||
403 | |||
404 | -- This function registers a given function to be called when a given |
||
405 | -- event is fired (this can be used to activate an addon upon receipt |
||
406 | -- of a given event etc) |
||
407 | function registerEventHook(triggerEvent, ownerAddOn, hookFunction, ...) |
||
408 | if (not config.events[triggerEvent]) then |
||
409 | config.events[triggerEvent] = {} |
||
410 | StubbyFrame:RegisterEvent(triggerEvent) |
||
411 | end |
||
412 | config.events[triggerEvent][ownerAddOn] = nil |
||
413 | if (hookFunction) then |
||
414 | config.events[triggerEvent][ownerAddOn] = { f=hookFunction, a=arg } |
||
415 | end |
||
416 | end |
||
417 | |||
418 | function unregisterEventHook(triggerEvent, ownerAddOn) |
||
419 | if (config.events and config.events[triggerEvent] and config.events[triggerEvent][ownerAddOn]) then |
||
420 | config.events[triggerEvent][ownerAddOn] = nil |
||
421 | end |
||
422 | end |
||
423 | |||
424 | function eventWatcher(event) |
||
425 | if (config.events[event]) then |
||
426 | local ownerAddOn, hookDetail |
||
427 | for ownerAddOn, hookDetail in config.events[event] do |
||
428 | hookDetail.f(hookDetail.a, event, arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11,arg12,arg13,arg14,arg15,arg16,arg17,arg18,arg19,arg20); |
||
429 | end |
||
430 | end |
||
431 | end |
||
432 | |||
433 | -- This function registers boot code. This is a piece of code |
||
434 | -- specified as a string, which Stubby will execute on your behalf |
||
435 | -- when we are first loaded. This code can do anything a normal |
||
436 | -- lua script can, such as create global functions, register a |
||
437 | -- command handler, hook into functions, load your addon etc. |
||
438 | -- Leaving bootCode nil will remove your boot. |
||
439 | function registerBootCode(ownerAddOn, bootName, bootCode) |
||
440 | local ownerIndex = string.lower(ownerAddOn) |
||
441 | local bootIndex = string.lower(bootName) |
||
442 | if (not StubbyConfig.boots) then StubbyConfig.boots = {} end |
||
443 | if (not StubbyConfig.boots[ownerIndex]) then StubbyConfig.boots[ownerIndex] = {} end |
||
444 | StubbyConfig.boots[ownerIndex][bootIndex] = nil |
||
445 | if (bootCode) then |
||
446 | StubbyConfig.boots[ownerIndex][bootIndex] = bootCode |
||
447 | end |
||
448 | end |
||
449 | |||
450 | function unregisterBootCode(ownerAddOn, bootName) |
||
451 | local ownerIndex = string.lower(ownerAddOn) |
||
452 | local bootIndex = string.lower(bootName) |
||
453 | if not (StubbyConfig.boots) then return end |
||
454 | if not (ownerIndex and StubbyConfig.boots[ownerIndex]) then return end |
||
455 | if (bootIndex == nil) then |
||
456 | StubbyConfig.boots[ownerIndex] = nil |
||
457 | else |
||
458 | StubbyConfig.boots[ownerIndex][bootIndex] = nil |
||
459 | end |
||
460 | end |
||
461 | |||
462 | function createAddOnLoadBootCode(ownerAddOn, triggerAddOn) |
||
463 | registerBootCode(ownerAddOn, triggerAddOn.."AddOnLoader", |
||
464 | 'local function hookFunction() '.. |
||
465 | 'LoadAddOn("'..ownerAddOn..'") '.. |
||
466 | 'Stubby.UnregisterAddOnHook("'..triggerAddOn..'", "'..ownerAddOn..'") '.. |
||
467 | 'end '.. |
||
468 | 'Stubby.RegisterAddOnHook("'..triggerAddOn..'", "'..ownerAddOn..'", hookFunction)' |
||
469 | ); |
||
470 | end |
||
471 | |||
472 | function createFunctionLoadBootCode(ownerAddOn, triggerFunction) |
||
473 | registerBootCode(ownerAddOn, triggerFunction.."FunctionLoader", |
||
474 | 'local function hookFunction() '.. |
||
475 | 'LoadAddOn("'..ownerAddOn..'") '.. |
||
476 | 'Stubby.UnregisterFunctionHook("'..triggerFunction..'", hookFunction) '.. |
||
477 | 'end '.. |
||
478 | 'Stubby.RegisterFunctionHook("'..triggerFunction..'", 200, hookFunction)' |
||
479 | ); |
||
480 | end |
||
481 | |||
482 | function createEventLoadBootCode(ownerAddOn, triggerEvent) |
||
483 | registerBootCode(ownerAddOn, triggerEvent.."FunctionLoader", |
||
484 | 'local function hookFunction() '.. |
||
485 | 'LoadAddOn("'..ownerAddOn..'") '.. |
||
486 | 'Stubby.UnregisterEventHook("'..triggerEvent..'", "'..ownerAddOn..'") '.. |
||
487 | 'end '.. |
||
488 | 'Stubby.RegisterEventHook("'..triggerEvent..'", "'..ownerAddOn..'", hookFunction)' |
||
489 | ); |
||
490 | end |
||
491 | |||
492 | -- Functions to check through all addons for dependants. |
||
493 | -- If any exist that we don't know about, and have a dependancy of us, then we will load them |
||
494 | -- once to give them a chance to register themselves with us. |
||
495 | function checkAddOns() |
||
496 | if not StubbyConfig.inspected then return end |
||
497 | local goodList = {} |
||
498 | local addonCount = GetNumAddOns() |
||
499 | local name, title, notes |
||
500 | for i=1, addonCount do |
||
501 | name, title, notes = GetAddOnInfo(i) |
||
502 | if (StubbyConfig.inspected and StubbyConfig.inspected[name]) then |
||
503 | local infoCompare = title.."|"..(notes or "") |
||
504 | if (infoCompare == StubbyConfig.addinfo[name]) then |
||
505 | goodList[name] = true |
||
506 | end |
||
507 | end |
||
508 | end |
||
509 | for name,_ in StubbyConfig.inspected do |
||
510 | if (not goodList[name]) then |
||
511 | StubbyConfig.inspected[name] = nil |
||
512 | StubbyConfig.addinfo[name] = nil |
||
513 | end |
||
514 | end |
||
515 | end |
||
516 | |||
517 | -- Cleans up boot codes for removed addons and prompts for deletion of their |
||
518 | -- configurations. |
||
519 | function cleanUpAddOnData() |
||
520 | if (not StubbyConfig.boots) then return; end |
||
521 | |||
522 | for b in pairs(StubbyConfig.boots) do |
||
523 | local _,title = GetAddOnInfo(b) |
||
524 | if (not title) then |
||
525 | StubbyConfig.boots[b] = nil |
||
526 | |||
527 | if (StubbyConfig.configs) then |
||
528 | if (cleanList == nil) then cleanList = {}; end |
||
529 | table.insert(cleanList, b) |
||
530 | end |
||
531 | end |
||
532 | end |
||
533 | |||
534 | if (cleanList) then cleanUpAddOnConfigs(); end |
||
535 | end |
||
536 | |||
537 | -- Shows confirmation dialogs to clean configuration for addons that have |
||
538 | -- just been removed. Warning: Calls itself recursively until done. |
||
539 | function cleanUpAddOnConfigs() |
||
540 | if (not cleanList) then return; end |
||
541 | |||
542 | local addonIndex = table.getn(cleanList) |
||
543 | local addonName = cleanList[addonIndex] |
||
544 | |||
545 | if (addonIndex == 1) then |
||
546 | cleanList = nil |
||
547 | else |
||
548 | table.remove(cleanList, addonIndex) |
||
549 | end |
||
550 | |||
551 | StaticPopupDialogs["CLEANUP_STUBBY" .. addonIndex] = { |
||
552 | text = "The AddOn \"" .. addonName .. "\" is no longer available. Do you wish to delete it's loading preferences?", |
||
553 | button1 = "Delete", |
||
554 | button2 = "Keep", |
||
555 | OnAccept = function() |
||
556 | StubbyConfig.configs[addonName] = nil |
||
557 | cleanUpAddOnConfigs(); |
||
558 | end, |
||
559 | OnCancel = function() |
||
560 | cleanUpAddOnConfigs(); |
||
561 | end, |
||
562 | timeout = 0, |
||
563 | whileDead = 1, |
||
564 | }; |
||
565 | StaticPopup_Show("CLEANUP_STUBBY" .. addonIndex, "",""); |
||
566 | end |
||
567 | |||
568 | function shouldInspectAddOn(addonName) |
||
569 | if not StubbyConfig.inspected[addonName] then return true end |
||
570 | return false |
||
571 | end |
||
572 | |||
573 | function inspectAddOn(addonName, title, info) |
||
574 | LoadAddOn(addonName) |
||
575 | StubbyConfig.inspected[addonName] = true |
||
576 | StubbyConfig.addinfo[addonName] = title.."|"..(info or "") |
||
577 | end |
||
578 | |||
579 | function searchForNewAddOns() |
||
580 | local addonCount = GetNumAddOns() |
||
581 | local name, title, notes, enabled, loadable, reason, security, requiresLoad |
||
582 | for i=1, addonCount do |
||
583 | requiresLoad = false |
||
584 | name, title, notes, enabled, loadable, reason, security = GetAddOnInfo(i) |
||
585 | if (IsAddOnLoadOnDemand(i) and shouldInspectAddOn(name) and loadable) then |
||
586 | local addonDeps = { GetAddOnDependencies(i) } |
||
587 | for _, dependancy in pairs(addonDeps) do |
||
588 | if (string.lower(dependancy) == "stubby") then |
||
589 | requiresLoad = true |
||
590 | end |
||
591 | end |
||
592 | end |
||
593 | |||
594 | if (requiresLoad) then inspectAddOn(name, title, notes) end |
||
595 | end |
||
596 | end |
||
597 | |||
598 | -- This function runs through the boot scripts we have, and if the |
||
599 | -- related addon is not loaded yet, runs the boot script. |
||
600 | function runBootCodes() |
||
601 | if (not StubbyConfig.boots) then return end |
||
602 | for addon, boots in StubbyConfig.boots do |
||
603 | if (not IsAddOnLoaded(addon) and IsAddOnLoadOnDemand(addon)) then |
||
604 | local _, _, _, _, loadable = GetAddOnInfo(addon) |
||
605 | if (loadable) then |
||
606 | for bootname, boot in pairs(boots) do |
||
607 | RunScript(boot) |
||
608 | end |
||
609 | end |
||
610 | end |
||
611 | end |
||
612 | end |
||
613 | |||
614 | |||
615 | function onWorldStart() |
||
616 | -- Check for expired or updated addons and remove their boot codes. |
||
617 | checkAddOns() |
||
618 | |||
619 | -- Run all of our boots to setup the respective addons functions. |
||
620 | runBootCodes() |
||
621 | |||
622 | -- The search for new life and new civilizations... or just addons maybe. |
||
623 | searchForNewAddOns() |
||
624 | |||
625 | -- Delete data for removed addons |
||
626 | cleanUpAddOnData() |
||
627 | end |
||
628 | |||
629 | function onLoaded() |
||
630 | if not StubbyConfig.inspected then StubbyConfig.inspected = {} end |
||
631 | if not StubbyConfig.addinfo then StubbyConfig.addinfo = {} end |
||
632 | Stubby.RegisterEventHook("PLAYER_LOGIN", "Stubby", onWorldStart) |
||
633 | end |
||
634 | |||
635 | function events(event, param) |
||
636 | if (not event) then event = "" end |
||
637 | if (not param) then param = "" end |
||
638 | if (event == "ADDON_LOADED") then |
||
639 | if (string.lower(param) == "stubby") then onLoaded() end |
||
640 | Stubby.LoadWatcher(param) |
||
641 | end |
||
642 | Stubby.EventWatcher(event) |
||
643 | end |
||
644 | |||
645 | function chatPrint(...) |
||
646 | if ( DEFAULT_CHAT_FRAME ) then |
||
647 | local msg = "" |
||
648 | for i=1, table.getn(arg) do |
||
649 | if i==1 then msg = arg[i] |
||
650 | else msg = msg.." "..arg[i] |
||
651 | end |
||
652 | end |
||
653 | DEFAULT_CHAT_FRAME:AddMessage(msg, 1.0, 0.35, 0.15) |
||
654 | end |
||
655 | end |
||
656 | |||
657 | -- This function allows boot code to store a configuration variable |
||
658 | -- by default the variable is per character unless isGlobal is set. |
||
659 | function setConfig(ownerAddOn, variable, value, isGlobal) |
||
660 | local ownerIndex = string.lower(ownerAddOn) |
||
661 | local varIndex = string.lower(variable) |
||
662 | if (not isGlobal) then |
||
663 | varIndex = string.lower(UnitName("player")) .. ":" .. varIndex |
||
664 | end |
||
665 | |||
666 | if (not StubbyConfig.configs) then StubbyConfig.configs = {} end |
||
667 | if (not StubbyConfig.configs[ownerIndex]) then StubbyConfig.configs[ownerIndex] = {} end |
||
668 | StubbyConfig.configs[ownerIndex][varIndex] = value |
||
669 | end |
||
670 | |||
671 | -- This function gets a config variable stored by the above function |
||
672 | -- it will prefer a player specific variable over a global with the |
||
673 | -- same name |
||
674 | function getConfig(ownerAddOn, variable) |
||
675 | local ownerIndex = string.lower(ownerAddOn) |
||
676 | local globalIndex = string.lower(variable) |
||
677 | local playerIndex = string.lower(UnitName("player")) .. ":" .. globalIndex |
||
678 | |||
679 | if (not StubbyConfig.configs) then return end |
||
680 | if (not StubbyConfig.configs[ownerIndex]) then return end |
||
681 | local curValue = StubbyConfig.configs[ownerIndex][playerIndex] |
||
682 | if (curValue == nil) then |
||
683 | curValue = StubbyConfig.configs[ownerIndex][globalIndex] |
||
684 | end |
||
685 | return curValue |
||
686 | end |
||
687 | |||
688 | -- This function clears the config variable specified (both the |
||
689 | -- global and player specific) or all config variables for the |
||
690 | -- ownerAddOn if no variable is specified |
||
691 | function clearConfig(ownerAddOn, variable) |
||
692 | local ownerIndex = string.lower(ownerAddOn) |
||
693 | if (not StubbyConfig.configs) then return end |
||
694 | if (not StubbyConfig.configs[ownerIndex]) then return end |
||
695 | if (variable) then |
||
696 | local globalIndex = string.lower(variable) |
||
697 | local playerIndex = string.lower(UnitName("player")) .. ":" .. globalIndex |
||
698 | StubbyConfig.configs[ownerIndex][globalIndex] = nil |
||
699 | StubbyConfig.configs[ownerIndex][playerIndex] = nil |
||
700 | else |
||
701 | StubbyConfig.configs[ownerIndex] = nil |
||
702 | end |
||
703 | end |
||
704 | |||
705 | -- Extract the revision number from SVN keyword string |
||
706 | local function getRevision() |
||
707 | local found, _, rev = string.find("$Revision: 941 $", "(%d+)") |
||
708 | if (found ~= nil) then return tonumber(rev); end |
||
709 | return nil |
||
710 | end |
||
711 | |||
712 | -- Setup our Stubby global object. All interaction is done |
||
713 | -- via the methods exposed here. |
||
714 | Stubby = { |
||
715 | VERSION = getRevision(), |
||
716 | Print = chatPrint, |
||
717 | Events = events, |
||
718 | HookCall = hookCall, |
||
719 | SetConfig = setConfig, |
||
720 | GetConfig = getConfig, |
||
721 | ClearConfig = clearConfig, |
||
722 | GetOrigFunc = getOrigFunc, |
||
723 | LoadWatcher = loadWatcher, |
||
724 | EventWatcher = eventWatcher, |
||
725 | RegisterBootCode = registerBootCode, |
||
726 | RegisterEventHook = registerEventHook, |
||
727 | RegisterAddOnHook = registerAddOnHook, |
||
728 | RegisterFunctionHook = registerFunctionHook, |
||
729 | UnregisterBootCode = unregisterBootCode, |
||
730 | UnregisterEventHook = unregisterEventHook, |
||
731 | UnregisterAddOnHook = unregisterAddOnHook, |
||
732 | UnregisterFunctionHook = unregisterFunctionHook, |
||
733 | CreateAddOnLoadBootCode = createAddOnLoadBootCode, |
||
734 | CreateEventLoadBootCode = createEventLoadBootCode, |
||
735 | CreateFunctionLoadBootCode = createFunctionLoadBootCode, |
||
736 | } |