Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

PluginManager.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. /*
  2. * Copyright (c) 2006-2017 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5. * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7. * permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8. *
  9. * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10. * Software.
  11. *
  12. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13. * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
  14. * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16. */
  17. package com.dmdirc.plugins;
  18. import com.dmdirc.events.PluginRefreshEvent;
  19. import com.dmdirc.events.eventbus.EventBus;
  20. import com.dmdirc.interfaces.config.IdentityController;
  21. import com.dmdirc.updater.components.PluginComponent;
  22. import com.dmdirc.updater.manager.UpdateManager;
  23. import com.google.common.collect.Sets;
  24. import java.io.File;
  25. import java.nio.file.Paths;
  26. import java.util.ArrayList;
  27. import java.util.Collection;
  28. import java.util.HashMap;
  29. import java.util.HashSet;
  30. import java.util.List;
  31. import java.util.Map;
  32. import java.util.Set;
  33. import org.slf4j.Logger;
  34. import org.slf4j.LoggerFactory;
  35. import dagger.ObjectGraph;
  36. import static com.dmdirc.util.LogUtils.USER_ERROR;
  37. /**
  38. * Searches for and manages plugins and services.
  39. */
  40. public class PluginManager {
  41. private static final Logger LOG = LoggerFactory.getLogger(PluginManager.class);
  42. /** List of known plugins' file names to their corresponding {@link PluginInfo} objects. */
  43. private final Map<String, PluginInfo> knownPlugins = new HashMap<>();
  44. /** Set of known plugins' metadata. */
  45. private final Set<PluginMetaData> plugins = new HashSet<>();
  46. /** File handler to use for disk-based functions. */
  47. private final PluginFileHandler fileHandler;
  48. /** Directory where plugins are stored. */
  49. private final String directory;
  50. /** The identity controller to use to find configuration options. */
  51. private final IdentityController identityController;
  52. /** The update manager to inform about plugins. */
  53. private final UpdateManager updateManager;
  54. /** Global ClassLoader used by plugins from this manager. */
  55. private final GlobalClassLoader globalClassLoader;
  56. /** The graph to pass to plugins for DI purposes. */
  57. private final ObjectGraph objectGraph;
  58. /** Event bus to pass to plugin info for plugin loaded events. */
  59. private final EventBus eventBus;
  60. /** The service manager to use. */
  61. private final ServiceManager serviceManager;
  62. /**
  63. * Creates a new instance of PluginManager.
  64. *
  65. * @param eventBus The event bus to subscribe to events on
  66. * @param identityController The identity controller to use for configuration options.
  67. * @param updateManager The update manager to inform about plugins.
  68. * @param objectGraph The graph to pass to plugins for DI purposes.
  69. * @param directory The directory to load plugins from.
  70. */
  71. public PluginManager(
  72. final EventBus eventBus,
  73. final ServiceManager serviceManager,
  74. final IdentityController identityController,
  75. final UpdateManager updateManager,
  76. final ObjectGraph objectGraph,
  77. final PluginFileHandler fileHandler,
  78. final String directory) {
  79. this.identityController = identityController;
  80. this.serviceManager = serviceManager;
  81. this.updateManager = updateManager;
  82. this.fileHandler = fileHandler;
  83. this.directory = directory;
  84. this.globalClassLoader = new GlobalClassLoader(this);
  85. this.objectGraph = objectGraph;
  86. this.eventBus = eventBus;
  87. }
  88. /**
  89. * Get the global class loader in use for this plugin manager.
  90. *
  91. * @return Global Class Loader
  92. */
  93. public GlobalClassLoader getGlobalClassLoader() {
  94. return globalClassLoader;
  95. }
  96. /**
  97. * Autoloads plugins.
  98. */
  99. public void doAutoLoad() {
  100. for (String plugin : identityController.getGlobalConfiguration().getOptionList("plugins",
  101. "autoload")) {
  102. plugin = plugin.trim();
  103. if (!plugin.isEmpty() && plugin.charAt(0) != '#' && getPluginInfo(plugin) != null) {
  104. getPluginInfo(plugin).loadPlugin();
  105. }
  106. }
  107. }
  108. /**
  109. * Tests and adds the specified plugin to the known plugins list. Plugins will only be added if:
  110. * <ul><li>The file exists,<li>No other plugin with the same name is known,<li>All requirements
  111. * are met for the plugin,
  112. * <li>The plugin has a valid config file that can be read</ul>.
  113. *
  114. * @param filename Filename of Plugin jar
  115. *
  116. * @return True if the plugin is in the known plugins list (either before this invocation or as
  117. * a result of it), false if it was not added for one of the reasons outlined above.
  118. */
  119. public boolean addPlugin(final String filename) {
  120. if (knownPlugins.containsKey(filename.toLowerCase())) {
  121. return true;
  122. }
  123. if (!new File(directory, filename).exists()) {
  124. LOG.warn(USER_ERROR, "Error loading plugin {}: File does not exist", filename);
  125. return false;
  126. }
  127. try {
  128. final PluginMetaData metadata = new PluginMetaData(this,
  129. Paths.get(directory, filename));
  130. metadata.load();
  131. final PluginInfo pluginInfo = new PluginInfo(this, serviceManager, metadata,
  132. eventBus, identityController, objectGraph);
  133. final PluginInfo existing = getPluginInfoByName(metadata.getName());
  134. if (existing != null) {
  135. LOG.warn(USER_ERROR, "Duplicate plugin detected, ignoring. ({} is the same as {})",
  136. filename, existing.getFilename());
  137. return false;
  138. }
  139. if (metadata.getUpdaterId() > 0 && metadata.getVersion().isValid()
  140. || identityController.getGlobalConfiguration()
  141. .hasOptionInt("plugin-addonid", metadata.getName())) {
  142. updateManager.addComponent(new PluginComponent(
  143. identityController.getGlobalConfiguration(), pluginInfo));
  144. }
  145. knownPlugins.put(filename.toLowerCase(), pluginInfo);
  146. eventBus.publishAsync(new PluginRefreshEvent());
  147. return true;
  148. } catch (PluginException e) {
  149. LOG.warn(USER_ERROR, "Error loading plugin {}: {}", filename, e.getMessage(), e);
  150. }
  151. return false;
  152. }
  153. /**
  154. * Remove a plugin.
  155. *
  156. * @param filename Filename of Plugin jar
  157. *
  158. * @return True if removed.
  159. */
  160. public boolean delPlugin(final String filename) {
  161. if (!knownPlugins.containsKey(filename.toLowerCase())) {
  162. return false;
  163. }
  164. final PluginInfo pluginInfo = getPluginInfo(filename);
  165. final boolean wasLoaded = pluginInfo.isLoaded();
  166. if (wasLoaded && !pluginInfo.isUnloadable()) {
  167. return false;
  168. }
  169. pluginInfo.unloadPlugin();
  170. knownPlugins.remove(filename.toLowerCase());
  171. return true;
  172. }
  173. /**
  174. * Reload a plugin.
  175. *
  176. * @param filename Filename of Plugin jar
  177. *
  178. * @return True if reloaded.
  179. */
  180. public boolean reloadPlugin(final String filename) {
  181. if (!knownPlugins.containsKey(filename.toLowerCase())) {
  182. return false;
  183. }
  184. final PluginInfo pluginInfo = getPluginInfo(filename);
  185. final boolean wasLoaded = pluginInfo.isLoaded();
  186. if (wasLoaded && !pluginInfo.isUnloadable()) {
  187. return false;
  188. }
  189. delPlugin(filename);
  190. boolean result = addPlugin(filename);
  191. if (wasLoaded && result) {
  192. getPluginInfo(filename).loadPlugin();
  193. result = getPluginInfo(filename).isLoaded();
  194. }
  195. return result;
  196. }
  197. /**
  198. * Get a plugin instance.
  199. *
  200. * @param filename File name of plugin jar
  201. *
  202. * @return PluginInfo instance, or null
  203. */
  204. public PluginInfo getPluginInfo(final String filename) {
  205. return knownPlugins.get(filename.toLowerCase());
  206. }
  207. /**
  208. * Get a plugin instance by plugin name.
  209. *
  210. * @param name Name of plugin to find.
  211. *
  212. * @return PluginInfo instance, or null
  213. */
  214. public PluginInfo getPluginInfoByName(final String name) {
  215. for (PluginInfo pluginInfo : getPluginInfos()) {
  216. if (pluginInfo.getMetaData().getName().equalsIgnoreCase(name)) {
  217. return pluginInfo;
  218. }
  219. }
  220. return null;
  221. }
  222. /**
  223. * Get directory where plugins are stored.
  224. *
  225. * @return Directory where plugins are stored.
  226. *
  227. * @deprecated Should be injected.
  228. */
  229. @Deprecated
  230. public String getDirectory() {
  231. return directory;
  232. }
  233. /**
  234. * Get directory where plugin files are stored.
  235. *
  236. * @return Directory where plugin files are stored.
  237. */
  238. public String getFilesDirectory() {
  239. final String fs = System.getProperty("file.separator");
  240. String filesDir = directory + "files" + fs;
  241. if (identityController.getGlobalConfiguration().hasOptionString("plugins", "filesdir")) {
  242. final String fdopt = identityController.getGlobalConfiguration()
  243. .getOptionString("plugins", "filesdir");
  244. if (fdopt != null && !fdopt.isEmpty() && new File(fdopt).exists()) {
  245. filesDir = fdopt;
  246. }
  247. }
  248. return filesDir;
  249. }
  250. /**
  251. * Refreshes the list of known plugins.
  252. */
  253. public void refreshPlugins() {
  254. final Set<PluginMetaData> newPlugins = fileHandler.refresh(this);
  255. Sets.difference(plugins, newPlugins).forEach(this::handlePluginDeleted);
  256. Sets.difference(newPlugins, plugins).forEach(this::handleNewPluginFound);
  257. eventBus.publishAsync(new PluginRefreshEvent());
  258. }
  259. /**
  260. * Called when a new plugin has been located on disk.
  261. *
  262. * @param metaData The metadata of the new plugin.
  263. */
  264. private void handleNewPluginFound(final PluginMetaData metaData) {
  265. plugins.add(metaData);
  266. addPlugin(metaData.getRelativeFilename());
  267. }
  268. /**
  269. * Called when an existing plugin has gone missing from disk.
  270. *
  271. * @param metaData The metadata of the deleted plugin.
  272. */
  273. private void handlePluginDeleted(final PluginMetaData metaData) {
  274. plugins.remove(metaData);
  275. delPlugin(metaData.getRelativeFilename());
  276. }
  277. /**
  278. * Retrieves a list of all installed plugins. Any file under the main plugin directory
  279. * (~/.DMDirc/plugins or similar) that matches *.jar is deemed to be a valid plugin.
  280. *
  281. * @return A list of all installed or known plugins
  282. */
  283. public Collection<PluginMetaData> getAllPlugins() {
  284. return fileHandler.getKnownPlugins();
  285. }
  286. /**
  287. * Update the autoLoadList
  288. *
  289. * @param plugin to add/remove (Decided automatically based on isLoaded())
  290. */
  291. public void updateAutoLoad(final PluginInfo plugin) {
  292. final List<String> list = identityController.getGlobalConfiguration()
  293. .getOptionList("plugins", "autoload");
  294. final String path = plugin.getMetaData().getRelativeFilename();
  295. if (plugin.isLoaded() && !list.contains(path)) {
  296. list.add(path);
  297. } else if (!plugin.isLoaded() && list.contains(path)) {
  298. list.remove(path);
  299. }
  300. identityController.getUserSettings().setOption("plugins", "autoload", list);
  301. }
  302. /**
  303. * Get Collection&lt;PluginInf&gt; of known plugins.
  304. *
  305. * @return Collection&lt;PluginInfo&gt; of known plugins.
  306. */
  307. public Collection<PluginInfo> getPluginInfos() {
  308. return new ArrayList<>(knownPlugins.values());
  309. }
  310. }