package com.twasyl.slideshowfx.hosting.connector; import com.twasyl.slideshowfx.engine.presentation.PresentationEngine; import com.twasyl.slideshowfx.hosting.connector.exceptions.HostingConnectorException; import com.twasyl.slideshowfx.hosting.connector.io.RemoteFile; import com.twasyl.slideshowfx.plugin.AbstractPlugin; import com.twasyl.slideshowfx.utils.DialogHelper; import javafx.scene.control.*; import javafx.scene.layout.VBox; import java.io.FileNotFoundException; import java.net.URI; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; /** * The base class for implementing an {@link IHostingConnector}. * * @author Thierry Wasylczenko * @version 1.1 * @since SlideshowFX 1.0 */ public abstract class AbstractHostingConnector<T extends IHostingConnectorOptions> extends AbstractPlugin<T> implements IHostingConnector<T> { private static final Logger LOGGER = Logger.getLogger(AbstractHostingConnector.class.getName()); /* * Constants for stored properties */ private static final String PROPERTIES_PREFIX = "hosting.connector."; protected static final String CONSUMER_KEY_PROPERTY_SUFFIX = ".consumer.key"; protected static final String CONSUMER_SECRET_PROPERTY_SUFFIX = ".consumer.secret"; protected static final String REDIRECT_URI_PROPERTY_SUFFIX = ".redirecturi"; protected static final String ACCESS_TOKEN_PROPERTY_SUFFIX = ".accesstoken"; private final String configurationBaseName; protected T newOptions; protected final String code; protected String accessToken; protected final RemoteFile rootFolder; protected AbstractHostingConnector(String code, String name, RemoteFile rootFolder) { super(name); this.code = code; this.rootFolder = rootFolder; this.configurationBaseName = PROPERTIES_PREFIX.concat(this.code); } @Override public String getConfigurationBaseName() { return this.configurationBaseName; } @Override public T getNewOptions() { return this.newOptions; } @Override public String getCode() { return this.code; } @Override public boolean isAuthenticated() { boolean authenticated = false; if(this.accessToken != null && !this.accessToken.trim().isEmpty()) { authenticated = checkAccessToken(); } return authenticated; } @Override public String getAccessToken() { return this.accessToken; } @Override public RemoteFile getRootFolder() { return this.rootFolder; } @Override public void upload(PresentationEngine engine) throws HostingConnectorException, FileNotFoundException { this.upload(engine, this.getRootFolder(), false); } @Override public RemoteFile chooseFile(boolean showFolders, boolean showFiles) throws HostingConnectorException { final TreeItem<RemoteFile> rootItem = this.buildCustomTreeItem(this.getRootFolder(), showFolders, showFiles); /** * Get the subfolders of root and populate the root TreeItem */ final List<RemoteFile> subfolders = this.list(this.getRootFolder(), showFolders, showFiles); for(RemoteFile subfolder : subfolders) { rootItem.getChildren().add(this.buildCustomTreeItem(subfolder, showFolders, showFiles)); } final TreeView<RemoteFile> treeView = this.buildCustomTreeView(rootItem); final VBox content = new VBox(5); content.getChildren().addAll(new Label("Choose a destination:"), treeView); RemoteFile destination = null; final ButtonType response = DialogHelper.showCancellableDialog("Choose destination", content); if(response != null && response == ButtonType.OK) { final TreeItem<RemoteFile> selection = treeView.getSelectionModel().getSelectedItem(); if(selection != null) destination = selection.getValue(); } return destination; } /** * Build the {@code TreeView<RemoteFile>} that will host the list of folders available remotely. * If the given {@code root} is null, it will not be added as root of the created TreeView. * * @param root The root that will be added to the created TreeView. * @return The TreeView to host remote folders. */ private TreeView<RemoteFile> buildCustomTreeView(final TreeItem<RemoteFile> root) { final TreeView<RemoteFile> treeView = new TreeView<>(); treeView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); /** * Because the default implementation of a CellFactory calls the toString() method of the value, another * implementation is created in order to simply display the {@link RemoteFile#getName()}. */ treeView.setCellFactory(targetTreeView -> { final TreeCell<RemoteFile> cell = new TreeCell<RemoteFile>() { @Override protected void updateItem(RemoteFile item, boolean empty) { super.updateItem(item, empty); if(!empty && item != null) setText(item.isRoot() ? "/" : item.getName()); else setText(null); } }; return cell; }); treeView.setPrefSize(500, 400); if(root != null) treeView.setRoot(root); return treeView; } /** * This method creates a {@code TreeItem<File>} that loads its children from the service when it is expanded. * * @param value The value of the item. * @return The created TreeItem. */ private TreeItem<RemoteFile> buildCustomTreeItem(RemoteFile value, boolean showFolders, boolean showFiles) throws HostingConnectorException{ final TreeItem<RemoteFile> item = new TreeItem<RemoteFile>(value) { @Override public boolean isLeaf() { // Allows to always display the arrow for expending the folder. return value.isFile(); } }; item.expandedProperty().addListener((expandedValue, oldExpanded, newExpanded) -> { if(newExpanded && item.getChildren().isEmpty()) { try { for(RemoteFile child : this.list(value, showFolders, showFiles)) { item.getChildren().add(this.buildCustomTreeItem(child, showFolders, showFiles)); } } catch (HostingConnectorException e) { LOGGER.log(Level.SEVERE, "Error while building the custom tree view", e); } } }); return item; } /** * Get all parameters present in the query string of the given {@link URI}. * @param uri The URI to extract the parameters from. * @return A map containing the parameters present in the query string of the URI. */ protected Map<String, String> getURIParameters(final URI uri) { final Map<String, String> parameters = new HashMap<>(); final String query = uri.getQuery(); if(query != null && !query.isEmpty()) { final String[] queryParameters = query.split("&"); for(String parameter : queryParameters) { final int equalSign = parameter.indexOf('='); final String name = equalSign == -1 ? parameter : parameter.substring(0, equalSign); final String value = equalSign == -1 ? null : parameter.substring(equalSign + 1); parameters.put(name, value); } } return parameters; } }