package org.freeplane.main.addons; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.StringReader; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.swing.JOptionPane; import org.apache.commons.lang.StringEscapeUtils; import org.freeplane.core.resources.ResourceController; import org.freeplane.core.ui.components.UITools; import org.freeplane.core.util.FileUtils; import org.freeplane.core.util.LogUtils; import org.freeplane.core.util.TextUtils; import org.freeplane.features.mode.Controller; import org.freeplane.features.mode.ModeController; import org.freeplane.features.mode.mindmapmode.MModeController; import org.freeplane.features.url.UrlManager; import org.freeplane.main.addons.AddOnProperties.AddOnType; import org.freeplane.n3.nanoxml.IXMLParser; import org.freeplane.n3.nanoxml.IXMLReader; import org.freeplane.n3.nanoxml.StdXMLReader; import org.freeplane.n3.nanoxml.XMLElement; import org.freeplane.n3.nanoxml.XMLParserFactory; public class AddOnsController { private static final String ADDONS_DIR = "addons"; private static AddOnsController addOnsController; private List<AddOnProperties> installedAddOns = new ArrayList<AddOnProperties>(); private boolean autoInstall; public AddOnsController() { createAddOnsDirIfNecessary(); registerPlugins(); autoInstall = true; } private void createAddOnsDirIfNecessary() { final File addOnsDir = getAddOnsDir(); // in applets the addOnsDir will be null if (addOnsDir != null && !addOnsDir.exists()) { LogUtils.info("creating user add-ons directory " + addOnsDir); addOnsDir.mkdirs(); } } private void registerPlugins() { final File addOnsDir = getAddOnsDir(); // in applets the addOnsDir will be null if (addOnsDir == null) return; File[] addonXmlFiles = addOnsDir.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(".plugin.xml"); } }); if(addonXmlFiles == null) { LogUtils.warn("unable to list AddOns directory: "+ addOnsDir); } else { final IXMLParser parser = XMLParserFactory.createDefaultXMLParser(); for (File file : addonXmlFiles) { BufferedInputStream inputStream = null; try { inputStream = new BufferedInputStream(new FileInputStream(file)); final IXMLReader reader = new StdXMLReader(inputStream); parser.setReader(reader); registerInstalledAddOn(new AddOnProperties(AddOnType.PLUGIN, (XMLElement) parser.parse())); } catch (final Exception e) { LogUtils.warn("error parsing " + file, e); } finally { FileUtils.silentlyClose(inputStream); } } } } public static AddOnsController getController() { if (addOnsController == null) addOnsController = new AddOnsController(); return addOnsController; } public List<AddOnProperties> getInstalledAddOns() { return Collections.unmodifiableList(installedAddOns); } public void registerInstalledAddOn(final AddOnProperties addOn) { installedAddOns.add(addOn); final ResourceController resourceController = ResourceController.getResourceController(); if (addOn.getDefaultProperties() != null) resourceController.addDefaults(addOn.getDefaultProperties()); if (addOn.getPreferencesXml() != null) { final ModeController modeController = Controller.getCurrentModeController(); if (modeController instanceof MModeController) { ((MModeController)modeController).getOptionPanelBuilder().load(new StringReader(addOn.getPreferencesXml())); } } if (addOn.getTranslations() != null) registerAddOnResources(addOn, resourceController); } /** make the translations of this add-on known system-wide. */ public static void registerAddOnResources(final AddOnProperties addOn, final ResourceController resourceController) { HashSet<String> languages = new HashSet<String>(); languages.add(resourceController.getLanguageCode()); languages.add(resourceController.getDefaultLanguageCode()); for (String language : languages) { final Map<String, String> resources = addOn.getTranslations().get(language); if (resources != null) { for (Entry<String, String> entry : resources.entrySet()) { if (entry.getValue().indexOf('\\') != -1) { // convert \uFFFF sequences entry.setValue(StringEscapeUtils.unescapeJava(entry.getValue())); } } resourceController.addLanguageResources(language, addOptionPanelPrefix(resources, addOn.getName())); resourceController.addLanguageResources(language, resources); } } } /** if the add-on is configurable it's a burden for the add-on-writer that the keys in the configuration are * prepended by "OptionPanel.". This code relieves the developer from taking care of that. */ private static Map<String, String> addOptionPanelPrefix(final Map<String, String> resources, final String addOnName) { final HashMap<String, String> result = new HashMap<String, String>(resources.size()); for (Entry<String, String> entry : resources.entrySet()) { result.put("OptionPanel." + entry.getKey(), entry.getValue()); } final String nameKey = "addons." + addOnName; result.put("OptionPanel.separator." + nameKey, resources.get(nameKey)); return result; } public File getAddOnsDir() { // in applets the userDir will be null final String userDir = ResourceController.getResourceController().getFreeplaneUserDirectory(); return userDir == null ? null : new File(userDir, ADDONS_DIR); } public void save(final AddOnProperties addOn) throws IOException { final File addOnsDir = getAddOnsDir(); if (addOnsDir != null) { File file = addOn.getAddOnPropertiesFile(); if (file == null) { file = new File(addOnsDir, addOn.getName() + "." + addOn.getAddOnType().name().toLowerCase() + ".xml"); } FileUtils.dumpStringToFile(addOn.toXmlString(), file, "UTF-8"); } } public void deinstall(AddOnProperties addOn) { LogUtils.info("deinstalling " + addOn); for (String[] rule : addOn.getDeinstallationRules()) { if (rule[0].equals("delete")) { final File file = new File(expandVariables(rule)); if (!file.exists()) { LogUtils.warn("file " + expandVariables(rule) + " should be deleted but does not exist"); } else { if (file.delete()) LogUtils.info("deleted " + expandVariables(rule)); else LogUtils.warn("could not delete file " + expandVariables(rule)); } } } installedAddOns.remove(addOn); } private String expandVariables(String[] rule) { return rule[1].replace("${installationbase}", ResourceController.getResourceController() .getFreeplaneUserDirectory()); } /** returns true if the url is an add-on package and the user decided to install it. */ public boolean installIfAppropriate(final URL url) { if(! autoInstall) return false; if (url.getFile().endsWith(UrlManager.FREEPLANE_ADD_ON_FILE_EXTENSION)) { AddOnInstaller installer = (AddOnInstaller) Controller.getCurrentModeController().getExtension( AddOnInstaller.class); if (installer == null) { LogUtils.warn("no AddOnInstaller registered. Cannot install " + url); return false; } UITools.backOtherWindows(); final int selection = UITools.showConfirmDialog(null, TextUtils.format("newmap.install.addon.question", new File(url.getFile()).getName()), TextUtils.getText("newmap.install.addon.title"), JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE); if (selection == JOptionPane.OK_OPTION) { installer.install(url); return true; } } return false; } public void setAutoInstallEnabled(boolean autoInstall) { this.autoInstall = autoInstall; } public boolean isAutoInstallEnabled() { return autoInstall; } public AddOnProperties getInstalledAddOn(final String name) { // Performance consideration: list is small -> iteration over list is OK. for (AddOnProperties addOn : installedAddOns) { if (addOn.getName().equals(name)) return addOn; } return null; } }