package com.twasyl.slideshowfx.controllers; import com.twasyl.slideshowfx.io.SlideshowFXExtensionFilter; import com.twasyl.slideshowfx.osgi.OSGiManager; import com.twasyl.slideshowfx.plugin.InstalledPlugin; import com.twasyl.slideshowfx.ui.controls.PluginFileButton; import com.twasyl.slideshowfx.utils.DialogHelper; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Button; import javafx.scene.input.DragEvent; import javafx.scene.input.Dragboard; import javafx.scene.input.TransferMode; import javafx.scene.layout.TilePane; import javafx.stage.FileChooser; import org.osgi.framework.BundleException; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URL; import java.util.ResourceBundle; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.logging.Level; import java.util.logging.Logger; import static com.twasyl.slideshowfx.global.configuration.GlobalConfiguration.PLUGINS_DIRECTORY; /** * Controller of the {@code PluginCenter.fxml} view. * * @author Thierry Wasylczenko * @version 1.1 * @since SlideshowFX 1.1 */ public class PluginCenterController implements Initializable { private static Logger LOGGER = Logger.getLogger(PluginCenterController.class.getName()); @FXML private TilePane plugins; @FXML private Button installPlugin; @FXML private void dragFilesOverPluginButton(final DragEvent event) { final Dragboard dragboard = event.getDragboard(); if(event.getGestureSource() != this.installPlugin && dragboard.hasFiles()) { event.acceptTransferModes(TransferMode.COPY); } event.consume(); } @FXML private void dropFileOverPluginButton(final DragEvent event) { final Dragboard dragboard = event.getDragboard(); boolean allFilesAreValid; if(event.getGestureSource() != this.installPlugin && dragboard.hasFiles()) { allFilesAreValid = true; File pluginFile; int index = 0; while(allFilesAreValid && index < dragboard.getFiles().size()) { pluginFile = dragboard.getFiles().get(index++); allFilesAreValid = this.checkChosenPluginFile(pluginFile); } } else { allFilesAreValid = false; } event.setDropCompleted(allFilesAreValid); event.consume(); } @FXML private void choosePlugin(final ActionEvent event) { FileChooser chooser = new FileChooser(); chooser.getExtensionFilters().add(SlideshowFXExtensionFilter.PLUGIN_FILES); File pluginFile = chooser.showOpenDialog(null); if(pluginFile != null) { this.checkChosenPluginFile(pluginFile); } } /** * Checks if a file chosen by the user is valid or not. In case the file is not a valid plugin, then an error * message is displayed. If it is valid, the plugin file is added to the list of plugins to install and displayed * in the plugins table. * @param pluginFile The plugin file to check. * @return {@code true} if the file is a valid plugin, {@code false} otherwise. */ protected boolean checkChosenPluginFile(final File pluginFile) { boolean valid = false; try { if(fileSeemsValid(pluginFile)) { final PluginFileButton pluginFileButton = new PluginFileButton(pluginFile); pluginFileButton.setSelected(true); this.plugins.getChildren().add(pluginFileButton); valid = true; } else { DialogHelper.showError("Invalid plugin", "The chosen plugin seems invalid"); } } catch (FileNotFoundException e) { LOGGER.log(Level.SEVERE, "Can not determine if the plugin file seems valid", e); } return valid; } /** * Checks if the given {@link File file} is a plugin seems to be a plugin that can be installed in SlideshowFX. * @param file The file to check. * @return {@code true} if the file seems to be plugin, {@code false} otherwise. * @throws NullPointerException If the file is {@code null}. * @throws FileNotFoundException If the file doesn't exist. */ protected boolean fileSeemsValid(final File file) throws FileNotFoundException { if(file == null) throw new NullPointerException("The file to check can not be null"); if(!file.exists()) throw new FileNotFoundException("The file to check must exist"); boolean isValid = false; if(file.getName().endsWith(".jar")) { try(final JarFile jar = new JarFile(file)) { final Manifest manifest = jar.getManifest(); final Attributes attributes = manifest.getMainAttributes(); if(attributes != null) { final String name = attributes.getValue("Bundle-Name"); final String version = attributes.getValue("Bundle-Version"); final String activator = attributes.getValue("Bundle-Activator"); isValid = isManifestAttributeValid(name) && isManifestAttributeValid(version) && isManifestAttributeValid(activator); } } catch (IOException e) { LOGGER.log(Level.SEVERE, "Can not create a JarFile instance for the plugin: " + file.getName(), e); } } return isValid; } /** * Check if a given attribute is considered valid. An attribute is considered valid if is is not null and not * empty. * @param attribute The attribute to check. * @return {@code true} if the attribute is valid, {@code false} otherwise. */ protected boolean isManifestAttributeValid(final String attribute) { return attribute != null && !attribute.trim().isEmpty(); } protected InstalledPlugin createInstalledPlugin(final File pluginFile) { InstalledPlugin plugin = null; try(final JarFile jar = new JarFile(pluginFile)) { final Manifest manifest = jar.getManifest(); final Attributes attributes = manifest.getMainAttributes(); if(attributes != null) { final String name = attributes.getValue("Bundle-Name"); final String version = attributes.getValue("Bundle-Version"); plugin = new InstalledPlugin(name, version); } } catch (IOException e) { LOGGER.log(Level.SEVERE, "Can not create a JarFile instance for the plugin: " + pluginFile.getName(), e); } return plugin; } protected void populatePluginsView() { for(File pluginFile : OSGiManager.getInstance().getActivePlugins()) { if(pluginFile.getName().endsWith(".jar")) { final PluginFileButton button = new PluginFileButton(pluginFile); button.setSelected(true); this.plugins.getChildren().add(button); } } } /** * Validate the user's choices: if already installed plugins are no more selected, uninstall them. If newly chosen * plugins have been added, install them. */ public void validatePluginsConfiguration() { this.plugins.getChildren() .filtered(child -> child instanceof PluginFileButton) .forEach(child -> { final PluginFileButton button = (PluginFileButton) child; if(button.isSelected() && !button.getFile().getParentFile().equals(PLUGINS_DIRECTORY)) { try { OSGiManager.getInstance().deployBundle(button.getFile()); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Can not install plugin: " + button.getFile().getName(), e); } } else if(!button.isSelected() && button.getFile().getParentFile().equals(PLUGINS_DIRECTORY)) { try { OSGiManager.getInstance().uninstallBundle(button.getFile()); } catch (IOException | BundleException e) { LOGGER.log(Level.SEVERE, "Can not uninstall plugin: " + button.getFile().getName(), e); } } }); } @Override public void initialize(URL location, ResourceBundle resources) { this.populatePluginsView(); } }