001/*
002 * Syncany, www.syncany.org
003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com>
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation, either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018package org.syncany.cli;
019
020import java.util.List;
021
022import joptsimple.OptionParser;
023import joptsimple.OptionSet;
024import joptsimple.OptionSpec;
025
026import org.syncany.config.to.ConfigTO;
027import org.syncany.operations.OperationResult;
028import org.syncany.operations.init.ConnectOperation;
029import org.syncany.operations.init.ConnectOperationOptions;
030import org.syncany.operations.init.ConnectOperationOptions.ConnectOptionsStrategy;
031import org.syncany.operations.init.ConnectOperationResult;
032import org.syncany.operations.init.ConnectOperationResult.ConnectResultCode;
033import org.syncany.plugins.transfer.StorageTestResult;
034import org.syncany.plugins.transfer.TransferSettings;
035
036import static java.util.Arrays.asList;
037
038public class ConnectCommand extends AbstractInitCommand {
039        public ConnectCommand() {
040                super();
041        }
042
043        @Override
044        public CommandScope getRequiredCommandScope() {
045                return CommandScope.UNINITIALIZED_LOCALDIR;
046        }
047
048        @Override
049        public boolean canExecuteInDaemonScope() {
050                return false;
051        }
052
053        @Override
054        public int execute(String[] operationArgs) throws Exception {
055                boolean retryNeeded = true;
056                boolean performOperation = true;
057
058                ConnectOperationOptions operationOptions = parseOptions(operationArgs);
059
060                while (retryNeeded && performOperation) {
061                        ConnectOperationResult operationResult = new ConnectOperation(operationOptions, this).execute();
062                        printResults(operationResult);
063
064                        retryNeeded = operationResult.getResultCode() != ConnectResultCode.OK
065                                        && operationResult.getResultCode() != ConnectResultCode.NOK_DECRYPT_ERROR;
066
067                        if (retryNeeded) {
068                                performOperation = isInteractive && askRetryConnection();
069
070                                if (performOperation) {
071                                        updateTransferSettings(operationOptions.getConfigTO().getTransferSettings());
072                                }
073                        }
074                }
075
076                return 0;
077        }
078
079        @Override
080        public ConnectOperationOptions parseOptions(String[] operationArgs) throws Exception {
081                ConnectOperationOptions operationOptions = new ConnectOperationOptions();
082
083                OptionParser parser = new OptionParser();
084                OptionSpec<String> optionPlugin = parser.acceptsAll(asList("P", "plugin")).withRequiredArg();
085                OptionSpec<String> optionPluginOpts = parser.acceptsAll(asList("o", "plugin-option")).withRequiredArg();
086                OptionSpec<Void> optionAddDaemon = parser.acceptsAll(asList("n", "add-daemon"));
087                OptionSpec<String> optionPassword = parser.acceptsAll(asList("password")).withRequiredArg();
088
089                OptionSet options = parser.parse(operationArgs);
090                List<?> nonOptionArgs = options.nonOptionArguments();
091
092                // Set interactivity mode  
093                isInteractive = !options.has(optionPlugin) && !options.has(optionPassword);
094                
095                // Plugin
096                TransferSettings transferSettings = null;
097
098                if (nonOptionArgs.size() == 1) {
099                        String connectLink = (String) nonOptionArgs.get(0);
100
101                        operationOptions.setStrategy(ConnectOptionsStrategy.CONNECTION_LINK);
102                        operationOptions.setConnectLink(connectLink);
103
104                        transferSettings = null;
105                }
106                else if (nonOptionArgs.size() == 0) {
107                        operationOptions.setStrategy(ConnectOptionsStrategy.CONNECTION_TO);
108                        operationOptions.setConnectLink(null);
109
110                        transferSettings = createTransferSettingsFromOptions(options, optionPlugin, optionPluginOpts);
111                }
112                else {
113                        throw new Exception("Invalid syntax.");
114                }
115
116                ConfigTO configTO = createConfigTO(transferSettings);
117
118                operationOptions.setLocalDir(localDir);
119                operationOptions.setConfigTO(configTO);
120                operationOptions.setDaemon(options.has(optionAddDaemon));
121                operationOptions.setPassword(validateAndGetPassword(options, optionPassword));
122
123                return operationOptions;
124        }
125
126        private String validateAndGetPassword(OptionSet options, OptionSpec<String> optionPassword) {           
127                if (!isInteractive) {
128                        if (options.has(optionPassword)) {
129                                return options.valueOf(optionPassword);
130                        }                       
131                        else {
132                                return null; // No encryption, no password.
133                        }
134                }       
135                else {
136                        return null; // Will be set in callback!
137                }
138        }
139        
140        @Override
141        public void printResults(OperationResult operationResult) {
142                ConnectOperationResult concreteOperationResult = (ConnectOperationResult) operationResult;
143
144                if (concreteOperationResult.getResultCode() == ConnectResultCode.OK) {
145                        out.println();
146                        out.println("Repository connected, and local folder initialized.");
147                        out.println("You can now use 'sy up' and 'sy down' to sync your files.");
148                        out.println();
149
150                        if (concreteOperationResult.isAddedToDaemon()) {
151                                out.println("To automatically sync this folder, simply restart the daemon with 'sy daemon restart'.");
152                                out.println();
153                        }
154                }
155                else if (concreteOperationResult.getResultCode() == ConnectResultCode.NOK_TEST_FAILED) {
156                        StorageTestResult testResult = concreteOperationResult.getTestResult();
157                        out.println();
158
159                        if (!testResult.isTargetCanConnect()) {
160                                out.println("ERROR: Cannot connect to the repository, because the connection to the storage backend failed.");
161                                out.println("       Possible reasons for this could be connectivity issues (are you connect to the Internet?),");
162                                out.println("       or invalid user credentials (are username/password valid?).");
163                        }
164                        else if (!testResult.isTargetExists()) {
165                                out.println("ERROR: Cannot connect to the repository, because the target does not exist.");
166                                out.println("       Please check if it really exists and if you can read from it / write to it.");
167                        }
168                        else if (!testResult.isTargetCanWrite()) {
169                                out.println("ERROR: Cannot connect to the repository, because the target is not writable. This is probably");
170                                out.println("       a permission issue (does the user have write permissions to the target?).");
171                        }
172                        else if (!testResult.isRepoFileExists()) {
173                                out.println("ERROR: Cannot connect to the repository, because no repo file was found ('syncany' file).");
174                                out.println("       Are you sure that this is a valid Syncany repository? Use 'sy init' to create a new one.");
175                        }
176                        else {
177                                out.println("ERROR: Cannot connect to the repository.");
178                        }
179
180                        out.println();
181                        printTestResult(testResult);
182                }
183                else if (concreteOperationResult.getResultCode() == ConnectResultCode.NOK_DECRYPT_ERROR) {
184                        out.println();
185                        out.println("ERROR: Invalid password or corrupt ciphertext.");
186                        out.println();
187                        out.println("The reason for this might be an invalid password, or that the");
188                        out.println("link/files have been tampered with.");
189                        out.println();
190                }
191                else {
192                        out.println();
193                        out.println("ERROR: Cannot connect to repository. Unknown error code: " + operationResult);
194                        out.println();
195                }
196        }
197}