/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo 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. * * OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.netbeans.lib.cvsclient.commandLine; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.text.MessageFormat; import java.util.Arrays; import java.util.Comparator; import java.util.ResourceBundle; import org.netbeans.lib.cvsclient.CVSRoot; import org.netbeans.lib.cvsclient.Client; import org.netbeans.lib.cvsclient.admin.StandardAdminHandler; import org.netbeans.lib.cvsclient.command.BasicCommand; import org.netbeans.lib.cvsclient.command.Command; import org.netbeans.lib.cvsclient.command.CommandAbortedException; import org.netbeans.lib.cvsclient.command.CommandException; import org.netbeans.lib.cvsclient.command.GlobalOptions; import org.netbeans.lib.cvsclient.commandLine.command.CommandProvider; import org.netbeans.lib.cvsclient.connection.AuthenticationException; import org.netbeans.lib.cvsclient.connection.Connection; import org.netbeans.lib.cvsclient.connection.ConnectionFactory; import org.netbeans.lib.cvsclient.connection.PServerConnection; import org.netbeans.lib.cvsclient.connection.PasswordsFile; import org.netbeans.lib.cvsclient.connection.StandardScrambler; import org.netbeans.lib.cvsclient.event.CVSListener; /** * An implementation of the standard CVS client utility (command line tool) in Java * * @author Robert Greig */ public class CVSCommand { private static final String HELP_OPTIONS = "--help-options"; // NOI18N private static final String HELP_COMMANDS = "--help-commands"; // NOI18N private static final String HELP_SYNONYMS = "--help-synonyms"; // NOI18N /** * The path to the repository on the server */ private String repository; /** * The local path to use to perform operations (the top level) */ private String localPath; /** * The connection to the server */ private Connection connection; /** * The client that manages interactions with the server */ private Client client; /** * The global options being used. GlobalOptions are only global for a particular command. */ private GlobalOptions globalOptions; /** * The port number that is used to connect to the remote server. It is taken into account only when it's value is greater then zero. */ private int port = 0; /** * Execute a configured CVS command * * @param command * the command to execute * @throws CommandException * if there is an error running the command */ public boolean executeCommand(Command command, PrintStream stderr) throws CommandException, AuthenticationException { client.setErrorStream(stderr); return client.executeCommand(command, globalOptions); } public void setRepository(String repository) { this.repository = repository; } public void setLocalPath(String localPath) { this.localPath = localPath; } public void setGlobalOptions(GlobalOptions globalOptions) { this.globalOptions = globalOptions; } /** * Creates the connection and the client and connects. */ private void connect(CVSRoot root, String password) throws IllegalArgumentException, AuthenticationException, CommandAbortedException { connection = ConnectionFactory.getConnection(root); if (CVSRoot.METHOD_PSERVER.equals(root.getMethod())) { ((PServerConnection) connection).setEncodedPassword(password); if (port > 0) { ((PServerConnection) connection).setPort(port); } } connection.open(); client = new Client(connection, new StandardAdminHandler()); client.setLocalPath(localPath); } private void addListener(CVSListener listener) { if (client != null) { // add a listener to the client client.getEventManager().addCVSListener(listener); } } private void close(PrintStream stderr) { try { connection.close(); } catch (IOException e) { stderr.println("Unable to close connection: " + e); // e.printStackTrace(); } } /** * Obtain the CVS root, either from the -D option cvs.root or from the CVS directory * * @return the CVSRoot string */ private static String getCVSRoot(String workingDir) { String root = null; BufferedReader r = null; if (workingDir == null) { workingDir = System.getProperty("user.dir"); } try { File f = new File(workingDir); File rootFile = new File(f, "CVS/Root"); if (rootFile.exists()) { r = new BufferedReader(new FileReader(rootFile)); root = r.readLine(); } } catch (IOException e) { // ignore } finally { try { if (r != null) { r.close(); } } catch (IOException e) { System.err.println("Warning: could not close CVS/Root file!"); } } if (root == null) { root = System.getProperty("cvs.root"); } return root; } /** * Process global options passed into the application * * @param args * the argument list, complete * @param globalOptions * the global options structure that will be passed to the command */ private static int processGlobalOptions(String[] args, GlobalOptions globalOptions, PrintStream stderr) { final String getOptString = globalOptions.getOptString(); GetOpt go = new GetOpt(args, getOptString); int ch = -1; boolean usagePrint = false; while ((ch = go.getopt()) != GetOpt.optEOF) { // System.out.println("Global option '"+((char) ch)+"', '"+go.optArgGet()+"'"); boolean success = globalOptions.setCVSCommand((char) ch, go.optArgGet()); if (!success) { usagePrint = true; } } if (usagePrint) { showUsage(stderr); return -10; } return go.optIndexGet(); } private static void showUsage(PrintStream stderr) { String usageStr = ResourceBundle.getBundle(CVSCommand.class.getPackage().getName() + ".Bundle").getString("MSG_HelpUsage"); // NOI18N stderr.println(MessageFormat.format(usageStr, new Object[] { HELP_OPTIONS, HELP_COMMANDS, HELP_SYNONYMS })); // stderr.println("Usage: cvs [global options] command [command-options-and-arguments]"); // stderr.println(" specify "+HELP_OPTIONS+" for a list of options"); // stderr.println(" specify "+HELP_COMMANDS+" for a list of commands"); // stderr.println(" specify "+HELP_SYNONYMS+" for a list of command synonyms"); } /** * Perform the 'login' command, asking the user for a password. If the login is successful, the password is written to a file. The * file's location is user.home, unless the cvs.passfile option is set. * * @param userName * the userName * @param hostName * the host */ private static boolean performLogin(String userName, String hostName, String repository, int port, GlobalOptions globalOptions) { PServerConnection c = new PServerConnection(); c.setUserName(userName); String password = null; try { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Enter password: "); password = in.readLine(); } catch (IOException e) { System.err.println("Could not read password: " + e); return false; } String encodedPassword = StandardScrambler.getInstance().scramble(password); c.setEncodedPassword(encodedPassword); c.setHostName(hostName); c.setRepository(repository); c.setPort(port); try { c.verify(); } catch (AuthenticationException e) { System.err.println("Could not login to host " + hostName); return false; } // was successful, so write the appropriate file out // we look for cvs.passfile being set, but if not use user.dir // as the default String root = globalOptions.getCVSRoot(); try { PasswordsFile.storePassword(root, encodedPassword); System.err.println("Logged in successfully to " + repository + " on host " + hostName); return true; } catch (IOException e) { System.err.println("Error: could not write password file."); return false; } } /** * Lookup the password in the .cvspass file. This file is looked for in the user.home directory if the option cvs.passfile is not set * * @param CVSRoot * the CVS root for which the password is being searched * @return the password, scrambled */ private static String lookupPassword(String CVSRoot, String CVSRootWithPort, PrintStream stderr) { File passFile = new File(System.getProperty("cvs.passfile", System.getProperty("user.home") + "/.cvspass")); BufferedReader reader = null; String password = null; try { reader = new BufferedReader(new FileReader(passFile)); String line; while ((line = reader.readLine()) != null) { if (line.startsWith("/1 ")) { line = line.substring("/1 ".length()); } if (line.startsWith(CVSRoot + " ")) { password = line.substring(CVSRoot.length() + 1); break; } else if (line.startsWith(CVSRootWithPort + " ")) { password = line.substring(CVSRootWithPort.length() + 1); break; } } } catch (IOException e) { stderr.println("Could not read password for host: " + e); return null; } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { stderr.println("Warning: could not close password file."); } } } if (password == null) { stderr.println("Didn't find password for CVSROOT '" + CVSRoot + "'."); } return password; } /** * Execute the CVS command and exit JVM. */ public static void main(String[] args) { if (processCommand(args, null, System.getProperty("user.dir"), System.out, System.err)) { System.exit(0); } else { System.exit(1); } } /** * Process the CVS command passed in args[] array with all necessary options. The only difference from main() method is, that this * method does not exit the JVM and provides command output. * * @param args * The command with options * @param files * The files to execute the command on. * @param localPath * The local working directory * @param stdout * The standard output of the command * @param stderr * The error output of the command. */ public static boolean processCommand(String[] args, File[] files, String localPath, PrintStream stdout, PrintStream stderr) { return processCommand(args, files, localPath, 0, stdout, stderr); } /** * Process the CVS command passed in args[] array with all necessary options. The only difference from main() method is, that this * method does not exit the JVM and provides command output. * * @param args * The command with options * @param files * The files to execute the command on. * @param localPath * The local working directory * @param port * The port number that is used to connect to the remote server. It is taken into account only when it's value is greater * then zero. * @param stdout * The standard output of the command * @param stderr * The error output of the command. * @return whether the command was processed successfully */ public static boolean processCommand(String[] args, File[] files, String localPath, int port, PrintStream stdout, PrintStream stderr) { assert stdout != null : "The output stream must be defined."; // NOI18N assert stderr != null : "The error stream must be defined."; // NOI18N // Provide help if requested if (args.length > 0) { if (HELP_OPTIONS.equals(args[0])) { printHelpOptions(stdout); return true; } else if (HELP_COMMANDS.equals(args[0])) { printHelpCommands(stdout); return true; } else if (HELP_SYNONYMS.equals(args[0])) { printHelpSynonyms(stdout); return true; } } try { // Adjust the local path localPath = new File(localPath).getCanonicalPath(); } catch (IOException ioex) { } // Set up the CVSRoot. Note that it might still be null after this // call if the user has decided to set it with the -d command line // global option GlobalOptions globalOptions = new GlobalOptions(); globalOptions.setCVSRoot(getCVSRoot(localPath)); // Set up any global options specified. These occur before the // name of the command to run int commandIndex = -1; try { commandIndex = processGlobalOptions(args, globalOptions, stderr); if (commandIndex == -10) { return true; } } catch (IllegalArgumentException e) { stderr.println("Invalid argument: " + e); return false; } if (globalOptions.isShowHelp()) { printHelp(commandIndex, args, stdout, stderr); return true; } if (globalOptions.isShowVersion()) { printVersion(stdout, stderr); return true; } // if we don't have a CVS root by now, the user has messed up if (globalOptions.getCVSRoot() == null) { stderr.println("No CVS root is set. Use the cvs.root " + "property, e.g. java -Dcvs.root=\":pserver:user@host:/usr/cvs\"" + " or start the application in a directory containing a CVS subdirectory" + " or use the -d command switch."); return false; } // parse the CVS root into its constituent parts CVSRoot root = null; final String cvsRoot = globalOptions.getCVSRoot(); try { root = CVSRoot.parse(cvsRoot); } catch (IllegalArgumentException e) { stderr.println("Incorrect format for CVSRoot: " + cvsRoot + "\nThe correct format is: " + "[:method:][[user][:password]@][hostname:[port]]/path/to/repository" + "\nwhere \"method\" is pserver."); return false; } // if we had some options without any command, then the user messed up if (commandIndex >= args.length) { showUsage(stderr); return false; } final String command = args[commandIndex]; if (command.equals("login")) { if (CVSRoot.METHOD_PSERVER.equals(root.getMethod())) { return performLogin(root.getUserName(), root.getHostName(), root.getRepository(), root.getPort(), globalOptions); } else { stderr.println("login does not apply for connection type " + "\'" + root.getMethod() + "\'"); return false; } } // this is not login, but a 'real' cvs command, so construct it, // set the options, and then connect to the server and execute it Command c = null; try { c = CommandFactory.getDefault().createCommand(command, args, ++commandIndex, globalOptions, localPath); } catch (IllegalArgumentException e) { stderr.println("Illegal argument: " + e.getMessage()); return false; } if (files != null && c instanceof BasicCommand) { ((BasicCommand) c).setFiles(files); } String password = null; if (CVSRoot.METHOD_PSERVER.equals(root.getMethod())) { password = root.getPassword(); if (password != null) { password = StandardScrambler.getInstance().scramble(password); } else { if (port > 0) { root.setPort(port); } password = lookupPassword(cvsRoot, root.toString(), stderr); if (password == null) { password = StandardScrambler.getInstance().scramble(""); // an empty password } } } CVSCommand cvsCommand = new CVSCommand(); cvsCommand.setGlobalOptions(globalOptions); cvsCommand.setRepository(root.getRepository()); if (port > 0) { cvsCommand.port = port; } // the local path is just the path where we executed the // command. This is the case for command-line CVS but not // usually for GUI front-ends cvsCommand.setLocalPath(localPath); try { cvsCommand.connect(root, password); CVSListener list; if (c instanceof ListenerProvider) { list = ((ListenerProvider) c).createCVSListener(stdout, stderr); } else { list = new BasicListener(stdout, stderr); } cvsCommand.addListener(list); boolean status = cvsCommand.executeCommand(c, stderr); return status; } catch (AuthenticationException aex) { stderr.println(aex.getLocalizedMessage()); return false; } catch (CommandAbortedException caex) { stderr.println("Error: " + caex); Thread.currentThread().interrupt(); return false; } catch (Exception t) { stderr.println("Error: " + t); t.printStackTrace(stderr); return false; } finally { if (cvsCommand != null) { cvsCommand.close(stderr); } } } private static void printHelpOptions(PrintStream stdout) { String options = ResourceBundle.getBundle(CVSCommand.class.getPackage().getName() + ".Bundle").getString("MSG_HelpOptions"); // NOI18N stdout.println(options); } private static void printHelpCommands(PrintStream stdout) { String msg = ResourceBundle.getBundle(CVSCommand.class.getPackage().getName() + ".Bundle").getString("MSG_CVSCommands"); // NOI18N stdout.println(msg); CommandProvider[] providers = CommandFactory.getDefault().getCommandProviders(); Arrays.sort(providers, new CommandProvidersComparator()); int maxNameLength = 0; for (int i = 0; i < providers.length; i++) { int l = providers[i].getName().length(); if (maxNameLength < l) { maxNameLength = l; } } maxNameLength += 2; // Two spaces from the longest name for (int i = 0; i < providers.length; i++) { stdout.print("\t" + providers[i].getName()); char spaces[] = new char[maxNameLength - providers[i].getName().length()]; Arrays.fill(spaces, ' '); stdout.print(new String(spaces)); providers[i].printShortDescription(stdout); stdout.println(); } } private static void printHelpSynonyms(PrintStream stdout) { String msg = ResourceBundle.getBundle(CVSCommand.class.getPackage().getName() + ".Bundle").getString("MSG_CVSSynonyms"); // NOI18N stdout.println(msg); CommandProvider[] providers = CommandFactory.getDefault().getCommandProviders(); Arrays.sort(providers, new CommandProvidersComparator()); int maxNameLength = 0; for (int i = 0; i < providers.length; i++) { int l = providers[i].getName().length(); if (maxNameLength < l) { maxNameLength = l; } } maxNameLength += 2; // Two spaces from the longest name for (int i = 0; i < providers.length; i++) { String[] synonyms = providers[i].getSynonyms(); if (synonyms.length > 0) { stdout.print("\t" + providers[i].getName()); char spaces[] = new char[maxNameLength - providers[i].getName().length()]; Arrays.fill(spaces, ' '); stdout.print(new String(spaces)); for (int j = 0; j < synonyms.length; j++) { stdout.print(synonyms[j] + " "); } stdout.println(); } } } private static void printHelp(int commandIndex, String[] args, PrintStream stdout, PrintStream stderr) { if (commandIndex >= args.length) { showUsage(stdout); } else { String cmdName = args[commandIndex]; CommandProvider provider = CommandFactory.getDefault().getCommandProvider(cmdName); if (provider == null) { printUnknownCommand(cmdName, stderr); } else { provider.printLongDescription(stdout); } } } private static void printVersion(PrintStream stdout, PrintStream stderr) { String version = CVSCommand.class.getPackage().getSpecificationVersion(); stdout.println("Java Concurrent Versions System (JavaCVS) " + version + " (client)"); } private static void printUnknownCommand(String commandName, PrintStream out) { String msg = ResourceBundle.getBundle(CVSCommand.class.getPackage().getName() + ".Bundle").getString("MSG_UnknownCommand"); // NOI18N out.println(MessageFormat.format(msg, new Object[] { commandName })); printHelpCommands(out); } private static final class CommandProvidersComparator implements Comparator { @Override public int compare(Object o1, Object o2) { if (!(o1 instanceof CommandProvider) || !(o2 instanceof CommandProvider)) { throw new IllegalArgumentException("Can not compare objects " + o1 + " and " + o2); } return ((CommandProvider) o1).getName().compareTo(((CommandProvider) o2).getName()); } } }