You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PluginManager.java 12KB

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