/* * Syncany, www.syncany.org * Copyright (C) 2011-2015 Philipp C. Heckel <philipp.heckel@gmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.syncany.cli; import static java.util.Arrays.asList; import java.util.ArrayList; import java.util.List; import org.syncany.cli.util.CliTableUtil; import org.syncany.operations.OperationResult; import org.syncany.operations.daemon.messages.ConnectToHostExternalEvent; import org.syncany.operations.daemon.messages.PluginInstallExternalEvent; import org.syncany.operations.plugin.ExtendedPluginInfo; import org.syncany.operations.plugin.PluginInfo; import org.syncany.operations.plugin.PluginOperation; import org.syncany.operations.plugin.PluginOperationAction; import org.syncany.operations.plugin.PluginOperationOptions; import org.syncany.operations.plugin.PluginOperationOptions.PluginListMode; import org.syncany.operations.plugin.PluginOperationResult; import org.syncany.operations.plugin.PluginOperationResult.PluginResultCode; import org.syncany.util.StringUtil; import com.google.common.collect.Iterables; import com.google.common.eventbus.Subscribe; import joptsimple.OptionParser; import joptsimple.OptionSet; import joptsimple.OptionSpec; public class PluginCommand extends Command { private boolean minimalOutput = false; @Override public CommandScope getRequiredCommandScope() { return CommandScope.ANY; } @Override public boolean canExecuteInDaemonScope() { return false; // TODO [low] Doesn't have an impact if command scope is ANY } @Override public int execute(String[] operationArgs) throws Exception { PluginOperationOptions operationOptions = parseOptions(operationArgs); PluginOperationResult operationResult = new PluginOperation(config, operationOptions).execute(); printResults(operationResult); return 0; } @Override public PluginOperationOptions parseOptions(String[] operationArgs) throws Exception { PluginOperationOptions operationOptions = new PluginOperationOptions(); OptionParser parser = new OptionParser(); OptionSpec<Void> optionLocal = parser.acceptsAll(asList("L", "local-only")); OptionSpec<Void> optionRemote = parser.acceptsAll(asList("R", "remote-only")); OptionSpec<Void> optionSnapshots = parser.acceptsAll(asList("s", "snapshot", "snapshots")); OptionSpec<Void> optionMinimalOutput = parser.acceptsAll(asList("m", "minimal-output")); OptionSpec<String> optionApiEndpoint = parser.acceptsAll(asList("a", "api-endpoint")).withRequiredArg(); OptionSet options = parser.parse(operationArgs); // Files List<?> nonOptionArgs = options.nonOptionArguments(); if (nonOptionArgs.size() == 0) { throw new Exception("Invalid syntax, please specify an action (list, install, remove, update)."); } // <action> String actionStr = nonOptionArgs.get(0).toString(); PluginOperationAction action = parsePluginAction(actionStr); operationOptions.setAction(action); // --minimal-output minimalOutput = options.has(optionMinimalOutput); // --snapshots operationOptions.setSnapshots(options.has(optionSnapshots)); // --api-endpoint if (options.has(optionApiEndpoint)) { operationOptions.setApiEndpoint(options.valueOf(optionApiEndpoint)); } // install|remove <plugin-id> if (action == PluginOperationAction.INSTALL || action == PluginOperationAction.REMOVE) { if (nonOptionArgs.size() != 2) { throw new Exception("Invalid syntax, please specify a plugin ID."); } // <plugin-id> String pluginId = nonOptionArgs.get(1).toString(); operationOptions.setPluginId(pluginId); } // --local-only, --remote-only else if (action == PluginOperationAction.LIST) { if (options.has(optionLocal)) { operationOptions.setListMode(PluginListMode.LOCAL); } else if (options.has(optionRemote)) { operationOptions.setListMode(PluginListMode.REMOTE); } else { operationOptions.setListMode(PluginListMode.ALL); } // <plugin-id> (optional in 'list' or 'update') if (nonOptionArgs.size() == 2) { String pluginId = nonOptionArgs.get(1).toString(); operationOptions.setPluginId(pluginId); } } else if (action == PluginOperationAction.UPDATE && nonOptionArgs.size() == 2) { String pluginId = nonOptionArgs.get(1).toString(); operationOptions.setPluginId(pluginId); } return operationOptions; } private PluginOperationAction parsePluginAction(String actionStr) throws Exception { try { return PluginOperationAction.valueOf(actionStr.toUpperCase()); } catch (Exception e) { throw new Exception("Invalid syntax, unknown action '" + actionStr + "'"); } } @Override public void printResults(OperationResult operationResult) { PluginOperationResult concreteOperationResult = (PluginOperationResult) operationResult; switch (concreteOperationResult.getAction()) { case LIST: printResultList(concreteOperationResult); return; case INSTALL: printResultInstall(concreteOperationResult); return; case REMOVE: printResultRemove(concreteOperationResult); return; case UPDATE: printResultUpdate(concreteOperationResult); return; default: out.println("Unknown action: " + concreteOperationResult.getAction()); } } private void printResultList(PluginOperationResult operationResult) { if (operationResult.getResultCode() == PluginResultCode.OK) { List<String[]> tableValues = new ArrayList<String[]>(); tableValues.add(new String[]{"Id", "Name", "Local Version", "Type", "Remote Version", "Updatable", "Provided By"}); int outdatedCount = 0; int updatableCount = 0; int thirdPartyCount = 0; for (ExtendedPluginInfo extPluginInfo : operationResult.getPluginList()) { PluginInfo pluginInfo = (extPluginInfo.isInstalled()) ? extPluginInfo.getLocalPluginInfo() : extPluginInfo.getRemotePluginInfo(); String localVersionStr = (extPluginInfo.isInstalled()) ? extPluginInfo.getLocalPluginInfo().getPluginVersion() : ""; String installedStr = extPluginInfo.isInstalled() ? (extPluginInfo.canUninstall() ? "User" : "Global") : ""; String remoteVersionStr = (extPluginInfo.isRemoteAvailable()) ? extPluginInfo.getRemotePluginInfo().getPluginVersion() : ""; String thirdPartyStr = (pluginInfo.isPluginThirdParty()) ? "Third Party" : "Syncany Team"; String updatableStr = ""; if (extPluginInfo.isInstalled() && extPluginInfo.isOutdated()) { if (extPluginInfo.canUninstall()) { updatableStr = "Auto"; updatableCount++; } else { updatableStr = "Manual"; } outdatedCount++; } if (pluginInfo.isPluginThirdParty()) { thirdPartyCount++; } tableValues.add(new String[]{pluginInfo.getPluginId(), pluginInfo.getPluginName(), localVersionStr, installedStr, remoteVersionStr, updatableStr, thirdPartyStr}); } CliTableUtil.printTable(out, tableValues, "No plugins found."); if (outdatedCount > 0) { String isAre = (outdatedCount == 1) ? "is" : "are"; String pluginPlugins = (outdatedCount == 1) ? "plugin" : "plugins"; out.printf("\nUpdates:\nThere %s %d outdated %s, %d of them %s automatically updatable.\n", isAre, outdatedCount, pluginPlugins, updatableCount, isAre); } if (thirdPartyCount > 0) { String pluginPlugins = (thirdPartyCount == 1) ? "plugin" : "plugins"; out.printf("\nThird party plugins:\nPlease note that the Syncany Team does not take review or maintain the third-party %s\nlisted above. Please report issues to the corresponding plugin site.\n", pluginPlugins); } } else { out.printf("Listing plugins failed. No connection? Try -d to get more details.\n"); out.println(); } } private void printResultInstall(PluginOperationResult operationResult) { // Print minimal result if (minimalOutput) { if (operationResult.getResultCode() == PluginResultCode.OK) { out.println("OK"); } else { out.println("NOK"); } } // Print regular result else { if (operationResult.getResultCode() == PluginResultCode.OK) { out.printf("Plugin successfully installed from %s\n", operationResult.getSourcePluginPath()); out.printf("Install location: %s\n", operationResult.getTargetPluginPath()); out.println(); printPluginDetails(operationResult.getAffectedPluginInfo()); printPluginConflictWarning(operationResult); } else { out.println("Plugin installation failed. Try -d to get more details."); out.println(); } } } private void printResultUpdate(final PluginOperationResult operationResult) { // Print regular result if (operationResult.getResultCode() == PluginResultCode.OK) { if (operationResult.getUpdatedPluginIds().size() == 0) { out.println("All plugins are up to date."); } else { Iterables.removeAll(operationResult.getUpdatedPluginIds(), operationResult.getErroneousPluginIds()); Iterables.removeAll(operationResult.getUpdatedPluginIds(), operationResult.getDelayedPluginIds()); if (operationResult.getDelayedPluginIds().size() > 0) { out.printf("Plugins to be updated: %s\n", StringUtil.join(operationResult.getDelayedPluginIds(), ", ")); } if (operationResult.getUpdatedPluginIds().size() > 0) { out.printf("Plugins successfully updated: %s\n", StringUtil.join(operationResult.getUpdatedPluginIds(), ", ")); } if (operationResult.getErroneousPluginIds().size() > 0) { out.printf("Failed to update %s. Try -d to get more details\n", StringUtil.join(operationResult.getErroneousPluginIds(), ", ")); } out.println(); } } else { out.println("Plugin update failed. Try -d to get more details."); out.println(); } } private void printPluginConflictWarning(PluginOperationResult operationResult) { List<String> conflictingPluginIds = operationResult.getConflictingPluginIds(); if (conflictingPluginIds != null && conflictingPluginIds.size() > 0) { out.println("---------------------------------------------------------------------------"); out.printf(" WARNING: The installed plugin '%s' conflicts with other installed:\n", operationResult.getAffectedPluginInfo().getPluginId()); out.printf(" plugin(s): %s\n", StringUtil.join(conflictingPluginIds, ", ")); out.println(); out.println(" If you'd like to use these plugins in the daemon, it is VERY likely"); out.println(" that parts of the application WILL CRASH. Data corruption might occur!"); out.println(); out.println(" Using the plugins outside of the daemon (sy <command> ...) might also"); out.println(" be an issue. Details about this in issue #154."); out.println("---------------------------------------------------------------------------"); out.println(); } } private void printResultRemove(PluginOperationResult operationResult) { // Print minimal result if (minimalOutput) { if (operationResult.getResultCode() == PluginResultCode.OK) { out.println("OK"); } else { out.println("NOK"); } } // Print regular result else { if (operationResult.getResultCode() == PluginResultCode.OK) { out.printf("Plugin successfully removed.\n"); out.printf("Original local was %s\n", operationResult.getSourcePluginPath()); out.println(); } else { out.println("Plugin removal failed."); out.println(); out.println("Note: Plugins shipped with the application or additional packages"); out.println(" cannot be removed. These plugin are marked 'Global' in the list."); out.println(); } } } private void printPluginDetails(PluginInfo pluginInfo) { out.println("Plugin details:"); out.println("- ID: " + pluginInfo.getPluginId()); out.println("- Name: " + pluginInfo.getPluginName()); out.println("- Version: " + pluginInfo.getPluginVersion()); out.println(); } @Subscribe public void onConnectToHostEventReceived(ConnectToHostExternalEvent event) { if (!minimalOutput) { out.printr("Connecting to " + event.getHost() + " ..."); } } @Subscribe public void onPluginInstallEventReceived(PluginInstallExternalEvent event) { if (!minimalOutput) { out.printr("Installing plugin from " + event.getSource() + " ..."); } } }