Plugins.pm.svn-base
上传用户:market2
上传日期:2018-11-18
资源大小:18786k
文件大小:12k
源码类别:

外挂编程

开发平台:

Windows_Unix

  1. #########################################################################
  2. #  OpenKore - Plugin system
  3. #
  4. #  This software is open source, licensed under the GNU General Public
  5. #  License, version 2.
  6. #  Basically, this means that you're allowed to modify and distribute
  7. #  this software. However, if you distribute modified versions, you MUST
  8. #  also distribute the source code.
  9. #  See http://www.gnu.org/licenses/gpl.html for the full license.
  10. #
  11. #  $Revision$
  12. #  $Id$
  13. #
  14. #########################################################################
  15. ##
  16. # MODULE DESCRIPTION: Plugin system
  17. #
  18. # This module provides an interface for handling plugins.
  19. # See the <a href="http://www.openkore.com/wiki/index.php/How_to_write_plugins_for_OpenKore">Plugin
  20. # Writing Tutorial</a> for more information about plugins.
  21. #
  22. # NOTE: Do not confuse plugins with modules! See Modules.pm for more information.
  23. # TODO: use events instead of printing log information directly.
  24. package Plugins;
  25. use strict;
  26. use warnings;
  27. use Time::HiRes qw(time sleep);
  28. use Exception::Class ('Plugin::LoadException', 'Plugin::DeniedException');
  29. use UNIVERSAL qw(isa);
  30. use Modules 'register';
  31. use Globals;
  32. use Utils qw(stringToQuark quarkToString);
  33. use Utils::DataStructures qw(binAdd existsInList);
  34. use Utils::ObjectList;
  35. use Utils::Exceptions;
  36. use Log qw(message);
  37. use Translation qw(T TF);
  38. use Settings qw(%sys);
  39. #############################
  40. ### CATEGORY: Variables
  41. #############################
  42. ##
  43. # String $Plugins::current_plugin
  44. #
  45. # When a plugin is being (re)loaded, the filename of the plugin is set in this variable.
  46. our $current_plugin;
  47. ##
  48. # String $Plugins::current_plugin_folder
  49. #
  50. # When a plugin is being (re)loaded, the the plugin's folder is set in this variable.
  51. our $current_plugin_folder;
  52. our @plugins;
  53. our %hooks;
  54. use enum qw(HOOKNAME INDEX);
  55. use enum qw(CALLBACK USER_DATA);
  56. #############################
  57. ### CATEGORY: Functions
  58. #############################
  59. ##
  60. # void Plugins::loadAll()
  61. #
  62. # Loads all plugins from the plugins folder, and all plugins that are one subfolder below
  63. # the plugins folder. Plugins must have the .pl extension.
  64. #
  65. # Throws Plugin::LoadException if a plugin failed to load.
  66. # Throws Plugin::DeniedException if the plugin system refused to load a plugin. This can
  67. # happen, for example, if it detects that a plugin is incompatible.
  68. sub loadAll {
  69. if (!exists $sys{'loadPlugins'}) {
  70. message T("Loading all plugins (by default)...n", 'plugins');
  71. } elsif (!$sys{'loadPlugins'}) {
  72. message T("Automatic loading of plugins disabledn", 'plugins');
  73. return;
  74. } elsif ($sys{'loadPlugins'} eq '1') {
  75. message T("Loading all plugins...n", 'plugins');
  76. } elsif ($sys{'loadPlugins'} eq '2') {
  77. message T("Selectively loading plugins...n", 'plugins');
  78. } elsif ($sys{'loadPlugins'} eq '3') {
  79. message T("Selectively skipping plugins...n", 'plugins');
  80. }
  81. my (@plugins, @subdirs, @names);
  82. my @pluginsFolders;
  83. @pluginsFolders = Settings::getPluginsFolders() if (defined &Settings::getPluginsFolders);
  84. foreach my $dir (@pluginsFolders) {
  85. my @items;
  86. next if (!opendir(DIR, $dir));
  87. @items = readdir DIR;
  88. closedir DIR;
  89. foreach my $file (@items) {
  90. if (-f "$dir/$file" && $file =~ /.(pl|lp)$/) {
  91. next if (exists $sys{'loadPlugins'} && $sys{'loadPlugins'} eq '3' && existsInList($sys{'skipPlugins_list'}, substr($file, 0, -3)));
  92. push @plugins, "$dir/$file";
  93. push @names, substr($file, 0, -3) if (exists $sys{'loadPlugins'} && $sys{'loadPlugins'} eq '2');
  94. } elsif (-d "$dir/$file" && $file !~ /^(.|CVS$)/i) {
  95. push @subdirs, "$dir/$file";
  96. }
  97. }
  98. }
  99. foreach my $dir (@subdirs) {
  100. my @items;
  101. next if (!opendir(DIR, $dir));
  102. @items = readdir DIR;
  103. closedir DIR;
  104. foreach my $file (@items) {
  105. if (-f "$dir/$file" && $file =~ /.(pl|lp)$/) {
  106. push @plugins, "$dir/$file";
  107. push @names, substr($file, 0, -3) if (exists $sys{'loadPlugins'} && $sys{'loadPlugins'} eq '2');
  108. }
  109. }
  110. }
  111. while (@plugins) {
  112. my $plugin = shift(@plugins);
  113. if (exists $sys{'loadPlugins'} && $sys{'loadPlugins'} eq '2') {
  114. my $file = shift(@names);
  115. next if (exists $sys{'loadPlugins_list'} && !existsInList($sys{'loadPlugins_list'}, $file));
  116. }
  117. load($plugin);
  118. }
  119. }
  120. ##
  121. # void Plugins::load(String file)
  122. # file: The filename of a plugin.
  123. #
  124. # Loads a plugin.
  125. #
  126. # Throws Plugin::LoadException if it failed to load.
  127. # Throws Plugin::DeniedException if the plugin system refused to load this plugin. This can
  128. # happen, for example, if it detects that a plugin is incompatible.
  129. sub load {
  130. my $file = shift;
  131. message(TF("Loading plugin %s...n", $file), "plugins");
  132. $current_plugin = $file;
  133. $current_plugin_folder = $file;
  134. $current_plugin_folder =~ s/(.*)[/\].*/$1/;
  135. if (! -f $file) {
  136. Plugin::LoadException->throw(TF("File %s does not exist.", $file));
  137. } elsif ($file =~ /(^|/)ropp.pl$/i) {
  138. Plugin::DeniedException->throw(TF("The ROPP plugin (ropp.pl) is obsolete and is " .
  139. "no longer necessary. Please remove it, or %s will not work correctly.",
  140. $Settings::NAME || "OpenKore"));
  141. }
  142. undef $!;
  143. undef $@;
  144. if (!defined(do $file)) {
  145. if ($@) {
  146. Plugin::LoadException->throw(TF("Plugin contains syntax errors:n%s", $@));
  147. } else {
  148. Plugin::LoadException->throw("$!");
  149. }
  150. }
  151. }
  152. ##
  153. # boolean Plugins::unload(name)
  154. # name: The name of the plugin to unload.
  155. # Returns: 1 if the plugin has been successfully unloaded, 0 if the plugin isn't registered.
  156. #
  157. # Unloads a registered plugin.
  158. sub unload {
  159. my $name = shift;
  160. my $i = 0;
  161. foreach my $plugin (@plugins) {
  162. if ($plugin && $plugin->{name} eq $name) {
  163. $plugin->{unload_callback}->() if (defined $plugin->{unload_callback});
  164. delete $plugins[$i];
  165. return 1;
  166. }
  167. $i++;
  168. }
  169. return 0;
  170. }
  171. ##
  172. # void Plugins::unloadAll()
  173. #
  174. # Unloads all registered plugins.
  175. sub unloadAll {
  176. my $name = shift;
  177. foreach my $plugin (@plugins) {
  178. next if (!$plugin);
  179. $plugin->{unload_callback}->() if (defined $plugin->{unload_callback});
  180. }
  181. @plugins = ();
  182. }
  183. ##
  184. # boolean Plugins::reload(String name)
  185. # name: The name of the plugin to reload.
  186. # Returns: 1 on success, 0 if the plugin isn't registered.
  187. #
  188. # Reload a plugin.
  189. #
  190. # Throws Plugin::LoadException if it failed to load.
  191. sub reload {
  192. my $name = shift;
  193. my $i = 0;
  194. foreach my $plugin (@plugins) {
  195. if ($plugin && $plugin->{name} eq $name) {
  196. my $filename = $plugin->{filename};
  197. if (defined $plugin->{reload_callback}) {
  198. $plugin->{reload_callback}->()
  199. } elsif (defined $plugin->{unload_callback}) {
  200. $plugin->{unload_callback}->();
  201. }
  202. undef %{$plugin};
  203. delete $plugins[$i];
  204. load($filename);
  205. return 1;
  206. }
  207. $i++;
  208. }
  209. return 0;
  210. }
  211. ##
  212. # void Plugins::register(String name, String description, [unload_callback, reload_callback])
  213. # name: The plugin's name.
  214. # description: A short one-line description of the plugin.
  215. # unload_callback: Reference to a function that will be called when the plugin is being unloaded.
  216. # reload_callback: Reference to a function that will be called when the plugin is being reloaded.
  217. # Returns: 1 if the plugin has been successfully registered, 0 if a plugin with the same name is already registered.
  218. #
  219. # Plugins should call this function when they are loaded. This function registers
  220. # the plugin in the plugin database. Registered plugins can be unloaded by the user.
  221. #
  222. # In the unload/reload callback functions, plugins should delete any hook functions they added.
  223. # See also: Plugins::addHook(), Plugins::delHook()
  224. sub register {
  225. my $name = shift;
  226. return 0 if registered($name);
  227. my %plugin_info = (
  228. name => $name,
  229. description => shift,
  230. unload_callback => shift,
  231. reload_callback => shift,
  232. filename => $current_plugin
  233. );
  234. binAdd(@plugins, %plugin_info);
  235. return 1;
  236. }
  237. ##
  238. # boolean Plugins::registered(String name)
  239. # name: The plugin's name.
  240. # Returns: 1 if the plugin's registered, 0 if it isn't.
  241. #
  242. # Checks whether a plugin is registered.
  243. sub registered {
  244. my $name = shift;
  245. foreach (@plugins) {
  246. return 1 if ($_ && $_->{name} eq $name);
  247. }
  248. return 0;
  249. }
  250. ##
  251. # Plugins::addHook(String hookname, callback, [user_data])
  252. # hookname: Name of a hook.
  253. # callback: Reference to the function to call.
  254. # user_data: Additional data to pass to callback.
  255. # Returns: A handle which can be used to remove this hook.
  256. #
  257. # Add a hook for $hookname. Whenever Kore calls Plugins::callHook('foo'),
  258. # callback is also called.
  259. #
  260. # See also Plugins::callHook() for information about how callback is called.
  261. #
  262. # Example:
  263. # # Somewhere in your plugin:
  264. # use Plugins;
  265. # use Log;
  266. #
  267. # my $hook = Plugins::addHook('AI_pre', &ai_called);
  268. #
  269. # sub ai_called {
  270. #     Log::message("Kore's AI() function has been called.n");
  271. # }
  272. #
  273. # # Somewhere in the Kore source code:
  274. # sub AI {
  275. #     ...
  276. #     Plugins::callHook('AI_pre');   # <-- ai_called() is now also called.
  277. #     ...
  278. # }
  279. sub addHook {
  280. my ($hookName, $callback, $user_data) = @_;
  281. my $hookList = $hooks{$hookName} ||= new ObjectList();
  282. my @entry;
  283. $entry[CALLBACK] = $callback;
  284. $entry[USER_DATA] = $user_data if defined($user_data);
  285. my @handle;
  286. $handle[HOOKNAME] = stringToQuark($hookName);
  287. $handle[INDEX] = $hookList->add(bless(@entry, "Plugins::HookEntry"));
  288. return bless(@handle, 'Plugins::HookHandle');
  289. }
  290. ##
  291. # Plugins::addHooks( [hookName, callback, user_data], ... )
  292. # Returns: A handle, which can be used with Plugins::delHook()
  293. #
  294. # A convenience function for adding many hooks with one function.
  295. #
  296. # See also: Plugins::addHook(), Plugins::delHook()
  297. #
  298. # Example:
  299. # $hooks = Plugins::addHooks(
  300. #  ['AI_pre',       &onAI_pre],
  301. #  ['mainLoop_pre', &onMainLoop_pre, $some_user_data]
  302. # );
  303. # Plugins::delHook($hooks);
  304. #
  305. # # The above is the same as:
  306. # $hook1 = Plugins::addHook('AI_pre', &onAI_pre);
  307. # $hook2 = Plugins::addHook('mainLoop_pre', &onMainLoop_pre);
  308. # Plugins::delHook($hook1);
  309. # Plugins::delHook($hook2);
  310. sub addHooks {
  311. my @hooks;
  312. foreach my $params (@_) {
  313. push @hooks, addHook(@{$params});
  314. }
  315. return bless(@hooks, "Plugins::HookHandles");
  316. }
  317. ##
  318. # Plugins::delHook(hookname, handle)
  319. # hookname: Name of a hook.
  320. # handle: A hook handle, as returned by Plugins::addHook()
  321. #
  322. # Removes a registered hook. $callback will not be called anymore.
  323. #
  324. # See also: Plugins::addHook()
  325. #
  326. # Example:
  327. # Plugins::register('example', 'Example Plugin', &on_unload, &on_reload);
  328. # my $hook = Plugins::addHook('AI_pre', &ai_called);
  329. #
  330. # sub on_unload {
  331. #     Plugins::delHook($hook);
  332. #     Log::message "Example plugin unloaded.n";
  333. # }
  334. sub delHook {
  335. my ($handle) = @_;
  336. if (@_ > 1) {
  337. # More than one parameter was passed. This means that the plugin
  338. # is still using the old API. Make sure things are backwards
  339. # compatible.
  340. shift;
  341. ($handle) = @_;
  342. }
  343. if (isa($handle, 'Plugins::HookHandles')) {
  344. foreach my $singleHandle (@{$handle}) {
  345. delHook($singleHandle);
  346. }
  347. } elsif (isa($handle, 'Plugins::HookHandle') && defined $handle->[HOOKNAME]) {
  348. my $hookName = quarkToString($handle->[HOOKNAME]);
  349. my $hookList = $hooks{$hookName};
  350. if ($hookList) {
  351. my $entry = $hookList->get($handle->[INDEX]);
  352. $hookList->remove($entry);
  353. }
  354. delete $handle->[HOOKNAME];
  355. delete $handle->[INDEX];
  356. if ($hookList && $hookList->size() == 0) {
  357. delete $hooks{$hookName};
  358. }
  359. } else {
  360. ArgumentException->throw("Invalid hook handle passed to Plugins::delHook().");
  361. }
  362. }
  363. ##
  364. # Plugins::delHooks(hooks)
  365. #
  366. # An alias for Plugins::delHook(), for backwards compatibility reasons.
  367. sub delHooks {
  368. &delHook;
  369. }
  370. ##
  371. # void Plugins::callHook(String hookName, [argument])
  372. # hookName: Name of the hook.
  373. # argument: An argument to pass to the hook's callback functions.
  374. #
  375. # Call all callback functions which are associated with the hook $hookName.
  376. #
  377. # The hook's callback function is called as follows:
  378. # <pre class="example">
  379. # $callback->($hookName, $argument, userdata as passed to addHook);
  380. # </pre>
  381. #
  382. # See also: Plugins::addHook()
  383. sub callHook {
  384. my ($hookName, $argument) = @_;
  385. my $hookList = $hooks{$hookName};
  386. if ($hookList) {
  387. my $items = $hookList->getItems();
  388. foreach my $entry (@{$items}) {
  389. $entry->[CALLBACK]->($hookName, $argument, $entry->[USER_DATA]);
  390. }
  391. }
  392. }
  393. ##
  394. # boolean Plugins::hasHook(String hookName)
  395. #
  396. # Check whether there are any hooks registered for the specified hook name.
  397. sub hasHook {
  398. my ($hookName) = @_;
  399. return defined $hooks{$hookName};
  400. }
  401. 1;