/* * (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; import java.io.EOFException; import java.io.File; import java.io.IOException; import java.io.InterruptedIOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import org.netbeans.lib.cvsclient.admin.AdminHandler; import org.netbeans.lib.cvsclient.admin.Entry; 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.command.KeywordSubstitutionOptions; import org.netbeans.lib.cvsclient.connection.AuthenticationException; import org.netbeans.lib.cvsclient.connection.Connection; import org.netbeans.lib.cvsclient.event.CVSEvent; import org.netbeans.lib.cvsclient.event.EnhancedMessageEvent; import org.netbeans.lib.cvsclient.event.EventManager; import org.netbeans.lib.cvsclient.file.DefaultFileHandler; import org.netbeans.lib.cvsclient.file.FileDetails; import org.netbeans.lib.cvsclient.file.FileHandler; import org.netbeans.lib.cvsclient.file.GzippedFileHandler; import org.netbeans.lib.cvsclient.request.ExpandModulesRequest; import org.netbeans.lib.cvsclient.request.GzipFileContentsRequest; import org.netbeans.lib.cvsclient.request.Request; import org.netbeans.lib.cvsclient.request.RootRequest; import org.netbeans.lib.cvsclient.request.UnconfiguredRequestException; import org.netbeans.lib.cvsclient.request.UseUnchangedRequest; import org.netbeans.lib.cvsclient.request.ValidRequestsRequest; import org.netbeans.lib.cvsclient.request.ValidResponsesRequest; import org.netbeans.lib.cvsclient.request.WrapperSendRequest; import org.netbeans.lib.cvsclient.response.ErrorMessageResponse; import org.netbeans.lib.cvsclient.response.Response; import org.netbeans.lib.cvsclient.response.ResponseException; import org.netbeans.lib.cvsclient.response.ResponseFactory; import org.netbeans.lib.cvsclient.response.ResponseServices; import org.netbeans.lib.cvsclient.util.BugLog; import org.netbeans.lib.cvsclient.util.DefaultIgnoreFileFilter; import org.netbeans.lib.cvsclient.util.IgnoreFileFilter; import org.netbeans.lib.cvsclient.util.LoggedDataInputStream; import org.netbeans.lib.cvsclient.util.LoggedDataOutputStream; import org.netbeans.lib.cvsclient.util.Logger; import org.netbeans.lib.cvsclient.util.StringPattern; /** * The main way of communication with a server using the CVS Protocol. The client is not responsible for setting up the connection with the * server, only interacting with it. * * @see org.netbeans.lib.cvsclient.connection.Connection * @author Robert Greig */ public class Client implements ClientServices, ResponseServices { /** * The connection used to interact with the server. */ private Connection connection; /** * The file handler to use. */ private FileHandler transmitFileHandler; private FileHandler gzipFileHandler = new GzippedFileHandler(); private FileHandler uncompFileHandler = new DefaultFileHandler(); private boolean dontUseGzipFileHandler; /** * The modified date. */ private Date modifiedDate; /** * The admin handler to use. */ private AdminHandler adminHandler; /** * The local path, ie the path to the directory in which this command was executed. */ private String localPath; /** * Whether any commands have been executed so far. This allows us to send some initialisation requests before the first command. */ private boolean isFirstCommand = true; /** * The event manager. */ private final EventManager eventManager = new EventManager(this); /** * The global options for the executing command. */ private GlobalOptions globalOptions; private PrintStream stderr = System.err; /** * This is set to true if we should abort the current command. */ private boolean abort; private ResponseFactory responseFactory; private IgnoreFileFilter ignoreFileFilter; /* * The valid list of requests that is valid for the CVS server * corresponding to this client */ private Map validRequests = new HashMap(); /** * A map of file patterns and keyword substitution options */ private Map wrappersMap = null; /** * This will be set to true after initialization requests are sent to the server. The initialization requests setup valid requests and * send RootRequest to the server. */ private boolean initialRequestsSent = false; private boolean printConnectionReuseWarning = false; private static final Set ALLOWED_CONNECTION_REUSE_REQUESTS = new HashSet(Arrays.asList(new Class[] { ExpandModulesRequest.class, WrapperSendRequest.class })); // processRequests & getCounter private LoggedDataInputStream loggedDataInputStream; private LoggedDataOutputStream loggedDataOutputStream; private boolean warned; /** * Construct a Client using a given connection and file handler. You must initialize the connection and adminHandler first. <code> * // establish connection to the given CVS pserver * PServerConnection connection = new PServerConnection(); * connection.setUserName(userName); * connection.setHostName(hostName); * connection.setEncodedPassword(StandardScrambler.getInstance().scramble(password)); * connection.setRepository(repository); * // test the connection * try { * connection.open(); * } catch (AuthenticationException e) { * // do something * } * * // create a CVS client * Client cvsClient = new Client(connection,new StandardAdminHandler()); * * // set the directory in which we work * cvsClient.setLocalPath(localPath); * </code> * * @param connection * the connection to the cvs server * @param adminHandler * the admin handler to use */ public Client(Connection connection, AdminHandler adminHandler) { setConnection(connection); setAdminHandler(adminHandler); ignoreFileFilter = new DefaultIgnoreFileFilter(); dontUseGzipFileHandler = false; } public void setErrorStream(PrintStream stderr) { this.stderr = stderr; } /** * Get the connection used for communicating with the server. Connection. * * @return the connection */ public Connection getConnection() { return connection; } /** * Set the connection used for communicating with the server. * * @param c * the connection to use for all communication with the server */ public void setConnection(Connection connection) { this.connection = connection; initialRequestsSent = false; setIsFirstCommand(true); } /** * Get the admin handler uesd to read and write administrative information about files on the local machine. * * @return the admin handler */ public AdminHandler getAdminHandler() { return adminHandler; } /** * Set the admin handler used to read and write administrative information about files on the local machine. */ public void setAdminHandler(AdminHandler adminHandler) { this.adminHandler = adminHandler; } /** * Get the local path; that is, the path to the directory in which the currently executing command is referring. */ @Override public String getLocalPath() { return localPath; } /** * Set the local path, i.e. the path to the directory in which all commands are given (top level). */ public void setLocalPath(String localPath) { // remove trailing (back-) slash localPath = localPath.replace('\\', '/'); while (localPath.endsWith("/")) { // NOI18N localPath = localPath.substring(0, localPath.length() - 1); } this.localPath = localPath; } /** * Returns true if no previous command was executed using thiz. */ @Override public boolean isFirstCommand() { return isFirstCommand; } /** * Set whether this is the first command. Normally you do not need to set this yourself - after execution the first command will have * set this to false. */ @Override public void setIsFirstCommand(boolean isFirstCommand) { this.isFirstCommand = isFirstCommand; } /** * Return the uncompressed file handler. */ @Override public FileHandler getUncompressedFileHandler() { return uncompFileHandler; } /** * Set the uncompressed file handler. */ @Override public void setUncompressedFileHandler(FileHandler uncompFileHandler) { this.uncompFileHandler = uncompFileHandler; } /** * Return the Gzip stream handler. */ @Override public FileHandler getGzipFileHandler() { return gzipFileHandler; } /** * Set the handler for Gzip data. */ @Override public void setGzipFileHandler(FileHandler gzipFileHandler) { this.gzipFileHandler = gzipFileHandler; } /** * ReSet the filehandler for Gzip compressed data. Makes sure the requests for sending gzipped data are not sent.. */ @Override public void dontUseGzipFileHandler() { setGzipFileHandler(new DefaultFileHandler()); dontUseGzipFileHandler = true; } @Override public boolean isAborted() { return abort; } /** * Ensures, that the connection is open. * * @throws AuthenticationException * if it wasn't possible to connect */ @Override public void ensureConnection() throws AuthenticationException { BugLog.getInstance().assertNotNull(getConnection()); if (getConnection().isOpen()) { return; } // #69689 detect silent servers, possibly caused by proxy errors final Throwable ex[] = new Throwable[1]; final boolean opened[] = new boolean[] { false }; Runnable probe = new Runnable() { @Override public void run() { try { getConnection().open(); synchronized (opened) { opened[0] = true; } } catch (Throwable e) { synchronized (ex) { ex[0] = e; } } } }; Thread probeThread = new Thread(probe, "CVS Server Probe"); // NOI18N probeThread.start(); try { probeThread.join(60 * 1000); // 1 min Throwable wasEx; synchronized (ex) { wasEx = ex[0]; } if (wasEx != null) { if (wasEx instanceof CommandAbortedException) { // User cancelled abort(); return; } else if (wasEx instanceof AuthenticationException) { throw (AuthenticationException) wasEx; } else if (wasEx instanceof RuntimeException) { throw (RuntimeException) wasEx; } else if (wasEx instanceof Error) { throw (Error) wasEx; } else { assert false : wasEx; } } boolean wasOpened; synchronized (opened) { wasOpened = opened[0]; } if (wasOpened == false) { probeThread.interrupt(); throw new AuthenticationException("Timeout, no response from server.", "Timeout, no response from server."); } } catch (InterruptedException e) { // User cancelled probeThread.interrupt(); abort(); } } /** * Process all the requests. The connection must have been opened and set first. * * @param requests * the requets to process */ @Override public void processRequests(List requests) throws IOException, UnconfiguredRequestException, ResponseException, CommandAbortedException { if (requests == null || requests.size() == 0) { throw new IllegalArgumentException("[processRequests] requests " + // NOI18N "was either null or empty."); // NOI18N } if (abort) { throw new CommandAbortedException("Aborted during request processing", // NOI18N CommandException.getLocalMessage("Client.commandAborted", null)); // NOI18N } loggedDataInputStream = null; loggedDataOutputStream = null; // send the initialisation requests if we are handling the first // command boolean filterRootRequest = true; if (isFirstCommand()) { setIsFirstCommand(false); int pos = 0; if (!initialRequestsSent) { pos = fillInitialRequests(requests); initialRequestsSent = true; filterRootRequest = false; } if (globalOptions != null) { // sends the global options that are to be sent to server (-q, -Q, -t, -n, l) for (Iterator it = globalOptions.createRequestList().iterator(); it.hasNext();) { Request request = (Request) it.next(); requests.add(pos++, request); } if (globalOptions.isUseGzip() && globalOptions.getCompressionLevel() != 0) { requests.add(pos++, new GzipFileContentsRequest(globalOptions.getCompressionLevel())); } } } else if (printConnectionReuseWarning) { if (System.getProperty("javacvs.multiple_commands_warning") == null) { // NOI18N System.err.println("WARNING TO DEVELOPERS:"); // NOI18N System.err .println("Please be warned that attempting to reuse one open connection for more commands is not supported by cvs servers very well."); // NOI18N System.err.println("You are advised to open a new Connection each time."); // NOI18N System.err .println("If you still want to proceed, please do: System.setProperty(\"javacvs.multiple_commands_warning\", \"false\")"); // NOI18N System.err.println("That will disable this message."); // NOI18N } } if (!ALLOWED_CONNECTION_REUSE_REQUESTS.contains(requests.get(requests.size() - 1).getClass())) { printConnectionReuseWarning = true; } final boolean fireEnhancedEvents = getEventManager().isFireEnhancedEventSet(); int fileDetailRequestCount = 0; if (fireEnhancedEvents) { for (Iterator it = requests.iterator(); it.hasNext();) { Request request = (Request) it.next(); FileDetails fileDetails = request.getFileForTransmission(); if (fileDetails != null && fileDetails.getFile().exists()) { fileDetailRequestCount++; } } CVSEvent event = new EnhancedMessageEvent(this, EnhancedMessageEvent.REQUESTS_COUNT, new Integer(fileDetailRequestCount)); getEventManager().fireCVSEvent(event); } LoggedDataOutputStream dos = connection.getOutputStream(); loggedDataOutputStream = dos; // this list stores stream modification requests, each to be called // to modify the input stream the next time we need to process a // response List streamModifierRequests = new LinkedList(); // sending files does not seem to allow compression transmitFileHandler = getUncompressedFileHandler(); for (Iterator it = requests.iterator(); it.hasNext();) { if (abort) { throw new CommandAbortedException("Aborted during request processing", // NOI18N CommandException.getLocalMessage("Client.commandAborted", null)); // NOI18N } final Request request = (Request) it.next(); if (request instanceof GzipFileContentsRequest) { if (dontUseGzipFileHandler) { stderr.println("Warning: The server is not supporting gzip-file-contents request, no compression is used."); continue; } } // skip the root request if already sent if (request instanceof RootRequest) { if (filterRootRequest) { continue; } else { // Even if we should not filter the RootRequest now, we must filter all successive RootRequests filterRootRequest = true; } } // send request to server String requestString = request.getRequestString(); dos.writeBytes(requestString); // we must modify the outputstream now, but defer modification // of the inputstream until we are about to read a response. // This is because some modifiers (e.g. gzip) read the header // on construction, and obviously no header is present when // no response has been sent request.modifyOutputStream(connection); if (request.modifiesInputStream()) { streamModifierRequests.add(request); } dos = connection.getOutputStream(); FileDetails fileDetails = request.getFileForTransmission(); if (fileDetails != null) { final File file = fileDetails.getFile(); // only transmit the file if it exists! When committing // a remove request you cannot transmit the file if (file.exists()) { Logger.logOutput(new String("<Sending file: " + // NOI18N file.getAbsolutePath() + ">\n").getBytes("utf8")); // NOI18N if (fireEnhancedEvents) { CVSEvent event = new EnhancedMessageEvent(this, EnhancedMessageEvent.FILE_SENDING, file); getEventManager().fireCVSEvent(event); fileDetailRequestCount--; } if (fileDetails.isBinary()) { transmitFileHandler.transmitBinaryFile(file, dos); } else { transmitFileHandler.transmitTextFile(file, dos); } if (fireEnhancedEvents && fileDetailRequestCount == 0) { CVSEvent event = new EnhancedMessageEvent(this, EnhancedMessageEvent.REQUESTS_SENT, "Ok"); // NOI18N getEventManager().fireCVSEvent(event); } } } if (request.isResponseExpected()) { dos.flush(); // now perform the deferred modification of the input stream Iterator modifiers = streamModifierRequests.iterator(); while (modifiers.hasNext()) { System.err.println("Modifying the inputstream..."); // NOI18N final Request smRequest = (Request) modifiers.next(); System.err.println("Request is a: " + // NOI18N smRequest.getClass().getName()); smRequest.modifyInputStream(connection); } streamModifierRequests.clear(); handleResponse(); } } dos.flush(); transmitFileHandler = null; } private ResponseFactory getResponseFactory() { if (responseFactory == null) { responseFactory = new ResponseFactory(); } return responseFactory; } /** * Handle the response from a request. * * @throws ResponseException * if there is a problem reading the response */ private void handleResponse() throws ResponseException, CommandAbortedException { try { LoggedDataInputStream dis = connection.getInputStream(); loggedDataInputStream = dis; int ch = -1; try { ch = dis.read(); } catch (InterruptedIOException ex) { abort(); } while (!abort && ch != -1) { StringBuffer responseNameBuffer = new StringBuffer(); // read in the response name while (ch != -1 && (char) ch != '\n' && (char) ch != ' ') { responseNameBuffer.append((char) ch); try { ch = dis.read(); } catch (InterruptedIOException ex) { abort(); break; } } String responseString = responseNameBuffer.toString(); Response response = getResponseFactory().createResponse(responseString); // Logger.logInput(new String("<" + responseString + " processing start>\n").getBytes()); // NOI18N response.process(dis, this); boolean terminal = response.isTerminalResponse(); // handle SpecialResponses if (terminal && response instanceof ErrorMessageResponse) { ErrorMessageResponse errorResponce = (ErrorMessageResponse) response; String errMsg = errorResponce.getMessage(); throw new CommandAbortedException(errMsg, errMsg); } // Logger.logInput(new String("<" + responseString + " processed " + terminal + ">\n").getBytes()); // NOI18N if (terminal || abort) { break; } try { ch = dis.read(); } catch (InterruptedIOException ex) { abort(); break; } } if (abort) { String localMsg = CommandException.getLocalMessage("Client.commandAborted", null); // NOI18N throw new CommandAbortedException("Aborted during request processing", localMsg); // NOI18N } } catch (EOFException ex) { throw new ResponseException(ex, CommandException.getLocalMessage("CommandException.EndOfFile", null)); // NOI18N } catch (IOException ex) { throw new ResponseException(ex); } } /** * Execute a command. Do not forget to initialize the CVS Root on globalOptions first! Example: <code> * GlobalOptions options = new GlobalOptions(); * options.setCVSRoot(":pserver:"+userName+"@"+hostName+":"+cvsRoot); * </code> * * @param command * the command to execute * @param options * the global options to use for executing the command * @throws CommandException * if an error occurs when executing the command * @throws CommandAbortedException * if the command is aborted */ public boolean executeCommand(Command command, GlobalOptions globalOptions) throws CommandException, CommandAbortedException, AuthenticationException { BugLog.getInstance().assertNotNull(command); BugLog.getInstance().assertNotNull(globalOptions); this.globalOptions = globalOptions; getUncompressedFileHandler().setGlobalOptions(globalOptions); getGzipFileHandler().setGlobalOptions(globalOptions); try { eventManager.addCVSListener(command); command.execute(this, eventManager); } finally { eventManager.removeCVSListener(command); } return !command.hasFailed(); } /** * Counts {@link #processRequests(java.util.List)}. send and received bytes. * * @thread it assumes that client is not run in paralel. */ public long getCounter() { long ret = 0; if (loggedDataInputStream != null) { ret += loggedDataInputStream.getCounter(); } if (loggedDataOutputStream != null) { ret += loggedDataOutputStream.getCounter(); } return ret; } /** * Convert a <i>pathname</i> in the CVS sense (see 5.10 in the protocol document) into a local absolute pathname for the file. * * @param localDirectory * the name of the local directory, relative to the directory in which the command was given * @param repository * the full repository name for the file */ @Override public String convertPathname(String localDirectory, String repository) { int lastIndexOfSlash = repository.lastIndexOf('/'); String filename = repository.substring(lastIndexOfSlash + 1); if (localDirectory.startsWith("./")) { // NOI18N // remove the dot localDirectory = localDirectory.substring(1); } if (localDirectory.startsWith("/")) { // NOI18N // remove the leading slash localDirectory = localDirectory.substring(1); } // note that the localDirectory ends in a slash return getLocalPath() + '/' + localDirectory + filename; } /** * Get the repository path from the connection. * * @return the repository path, e.g. /home/bob/cvs. Delegated to the Connection in this case * @see Connection#getRepository() */ @Override public String getRepository() { return connection.getRepository(); } /** * Create or update the administration files for a particular file. This will create the CVS directory if necessary, and the Root and * Repository files if necessary. It will also update the Entries file with the new entry * * @param localDirectory * the local directory, relative to the directory in which the command was given, where the file in question lives * @param repositoryPath * the path of the file in the repository, in absolute form. * @param entry * the entry object for that file */ @Override public void updateAdminData(String localDirectory, String repositoryPath, Entry entry) throws IOException { final String absolutePath = localPath + '/' + localDirectory; if (repositoryPath.startsWith(getRepository())) { repositoryPath = repositoryPath.substring(getRepository().length() + 1); } else { if (warned == false) { String warning = "#65188 warning C/S protocol error (section 5.10). It's regurarly observed with cvs 1.12.xx servers.\n"; // NOI18N warning += " unexpected pathname=" + repositoryPath + " missing root prefix=" + getRepository() + "\n"; // NOI18N warning += " relaxing, but who knows all consequences...."; // NOI18N System.err.println(warning); warned = true; } } adminHandler.updateAdminData(absolutePath, repositoryPath, entry, globalOptions); } /** * Set the modified date of the next file to be written. The next call to write<Type>File will use this date. * * @param modifiedDate * the date the file should be marked as modified */ @Override public void setNextFileDate(Date modifiedDate) { this.modifiedDate = modifiedDate; } /** * Get the modified date for the next file. * * @return the date and then null the instance variable. */ @Override public Date getNextFileDate() { // // We null the instance variable so that future calls will not // retrieve a date specified for a previous file // Date copy = modifiedDate; modifiedDate = null; return copy; } /** * Get the Entry for the specified file, if one exists. * * @param f * the file * @throws IOException * if the Entries file cannot be read */ @Override public Entry getEntry(File f) throws IOException { return adminHandler.getEntry(f); } /** * Get the entries for a specified directory. * * @param directory * the directory for which to get the entries * @return an iterator of Entry objects */ @Override public Iterator getEntries(File directory) throws IOException { return adminHandler.getEntries(directory); } @Override public boolean exists(File file) { return adminHandler.exists(file); } /** * Get the repository path for a given directory, for example in the directory /home/project/foo/bar, the repository directory might be * /usr/cvs/foo/bar. The repository directory is commonly stored in the file * * <pre> * Repository * </pre> * * in the CVS directory on the client (this is the case in the standard CVS command-line tool). * * If no * * <pre> * CVS / Repository * </pre> * * file was found, the specified directory, the localpath are used to "guess" the repository path. * * @param directory * the directory */ @Override public String getRepositoryForDirectory(String directory) throws IOException { try { String repository = adminHandler.getRepositoryForDirectory(directory, getRepository()); return repository; } catch (IOException ex) { // an IOException is thrown, if the adminHandler can't detect the repository // by reading the CVS/Repository file, e.g. when checking out into a new directory try { directory = new File(directory).getCanonicalPath(); } catch (IOException ioex) { } directory = directory.replace('\\', '/'); while (directory.endsWith("/")) { // NOI18N directory = directory.substring(0, directory.length() - 1); } // must also canonicalize 'localPath' to be in sync with 'directory' String localPathCanonical = getLocalPath(); try { localPathCanonical = new File(getLocalPath()).getCanonicalPath(); } catch (IOException ioex) { } localPathCanonical = localPathCanonical.replace('\\', '/'); while (localPathCanonical.endsWith("/")) { // NOI18N localPathCanonical = localPathCanonical.substring(0, localPathCanonical.length() - 1); } int localPathLength = localPathCanonical.length(); String repository; if (directory.length() >= localPathLength) { repository = getRepository() + directory.substring(localPathLength); } else { // Asking for some folder upon the local working path repository = getRepository(); } return repository; } } @Override public String getRepositoryForDirectory(File directory) throws IOException { return adminHandler.getRepositoryForDirectory(directory.getAbsolutePath(), getRepository()); } /** * Set the Entry for the specified file. * * @param file * the file * @param entry * the new entry * @throws IOException * if an error occurs writing the details */ @Override public void setEntry(File file, Entry entry) throws IOException { adminHandler.setEntry(file, entry); } /** * Remove the Entry for the specified file. * * @param file * the file whose entry is to be removed * @throws IOException * if an error occurs writing the Entries file */ @Override public void removeEntry(File file) throws IOException { adminHandler.removeEntry(file); } /** * Remove the specified file from the local disk. If the file does not exist, the operation does nothing. * * @param pathname * the full path to the file to remove * @throws IOException * if an IO error occurs while removing the file */ @Override public void removeLocalFile(String pathname) throws IOException { transmitFileHandler.removeLocalFile(pathname); } /** * Removes the specified file determined by pathName and repositoryName. In this implementation the filename from repositoryPath is * added to the localpath (which doesn't have the filename in it) and that file is deleted. */ @Override public void removeLocalFile(String pathName, String repositoryName) throws IOException { int ind = repositoryName.lastIndexOf('/'); if (ind <= 0) { return; } String fileName = repositoryName.substring(ind + 1); String localFile = pathName + fileName; File fileToDelete = new File(getLocalPath(), localFile); removeLocalFile(fileToDelete.getAbsolutePath()); removeEntry(fileToDelete); } /** * Rename the local file. * * @param pathname * the full path to the file to rename * @param newName * the new name of the file (not the full path) * @throws IOException * if an IO error occurs while renaming the file */ @Override public void renameLocalFile(String pathname, String newName) throws IOException { transmitFileHandler.renameLocalFile(pathname, newName); } /** * Get the CVS event manager. This is generally called by response handlers that want to fire events. * * @return the eventManager */ @Override public EventManager getEventManager() { return eventManager; } /** * Get the global options that are set to this client. Individual commands can get the global options via this method. */ @Override public GlobalOptions getGlobalOptions() { return globalOptions; } /** * Call this method to abort the current command. The command will be aborted at the next suitable time */ public synchronized void abort() { abort = true; } /** * Get all the files contained within a given directory that are <b>known to CVS</b>. * * @param directory * the directory to look in * @return a set of all files. */ @Override public Set getAllFiles(File directory) throws IOException { return adminHandler.getAllFiles(directory); } @Override public void setIgnoreFileFilter(IgnoreFileFilter ignoreFileFilter) { this.ignoreFileFilter = ignoreFileFilter; } @Override public IgnoreFileFilter getIgnoreFileFilter() { return ignoreFileFilter; } @Override public boolean shouldBeIgnored(File directory, String noneCvsFile) { if (ignoreFileFilter != null) { return ignoreFileFilter.shouldBeIgnored(directory, noneCvsFile); } return false; } /** * Checks for presence of CVS/Tag file and returns it's value. * * @return the value of CVS/Tag file for the specified directory null if file doesn't exist */ @Override public String getStickyTagForDirectory(File directory) { return adminHandler.getStickyTagForDirectory(directory); } /** * This method is called when a response for the ValidRequests request is received. * * @param requests * A List of requests that is valid for this CVS server separated by spaces. */ @Override public void setValidRequests(String requests) { // We need to tokenize the requests and add it to our map StringTokenizer tokenizer = new StringTokenizer(requests); String token; while (tokenizer.hasMoreTokens()) { token = tokenizer.nextToken(); // we just add an object with the corresponding request // as the key. validRequests.put(token, this); } } private int fillInitialRequests(List requests) { int pos = 0; requests.add(pos++, new RootRequest(getRepository())); requests.add(pos++, new UseUnchangedRequest()); requests.add(pos++, new ValidRequestsRequest()); requests.add(pos++, new ValidResponsesRequest()); return pos; } /** * This method is called by WrapperSendResponse for each wrapper setting sent back by the CVS server * * @param pattern * A StringPattern indicating the pattern for which the wrapper applies * @param option * A KeywordSubstituionOption corresponding to the setting */ @Override public void addWrapper(StringPattern pattern, KeywordSubstitutionOptions option) { if (wrappersMap == null) { throw new IllegalArgumentException("This method should be called " + "by WrapperSendResponse only."); } wrappersMap.put(pattern, option); } /** * Returns the wrappers map associated with the CVS server The map is valid only after the connection is established */ @Override public Map getWrappersMap() throws CommandException { if (wrappersMap == null) { wrappersMap = new HashMap(); ArrayList requests = new ArrayList(); requests.add(new WrapperSendRequest()); boolean isFirst = isFirstCommand(); try { processRequests(requests); } catch (Exception ex) { throw new CommandException(ex, "An error during obtaining server wrappers."); } finally { // Do not alter isFirstCommand property setIsFirstCommand(isFirst); } wrappersMap = Collections.unmodifiableMap(wrappersMap); } return wrappersMap; } /** * Factory for creating clients. */ public static interface Factory { /** * Creates new client instance. Never null. It uses fresh connection. */ Client createClient(); } }