Skip to main content
summaryrefslogblamecommitdiffstats
blob: 722881b06611f4bb9b0ae0377bf67ac1601a1bec (plain) (tree)
1
2
3
4
5
6
7
8
9






                                                   
                                    
                    
                           

                            
                           
                      
                      












                                                                            
                                                                     
                                                       
                                                     
 



















                                                                           
                      


                                                                            
 







                                                                           
                                                                                                         









                                                                                              

                                               
                                        



                                               
                                


                                                  

           

                                                                    
           
                                                                            





                                                                                  






                                                                                    


                                                                                                        


                                                       


















                                                             






                                                                                    

                                                                          


















                                                                                                                 

                        




                                                                                
                                                 


                                                                          
                                     

         





                                                                   

                                                       
                                                                                                       

























                                                                                               



                                                                           
           
                                        





                                          







                                                                                    





















                                                                                       
                                                                     

                                   







                                                                    
                                       






                                                                






                                                                     


                                                                  
                                                            




                                                                         
                                                                     




                                                            




                                                                                 




                                                                       























                                                                                             

                                                                                            


                                                              

         







                                                                                      
                                                               
                                                                       
         
 
           












                                                                                            
           
                                                                                   
                                                                                            


           








                                                                                       
           
                                                                                          

                                                                            
                                                
         



                                                       
                                                                  
                                                                                         
         








                                                                                      
                                                           

         












                                                                                  
                                                                     
                                                                         

         








                                                  
                                                                         
                                                                              

         












                                                                                 
                                                                                  
         
        














                                                                                  
                                                                                         

         








                                                                                      
                                                                
                                                                    


           


















                                                                                          
           
                                                                                           
                                     
                                                                                 
                                                           
                                                                    
                                                                    




                                                                                   
 





                                                                                            

         















                                                                                       
                                                                                                                                      
                                                                                            

































                                                                                                                    
                                                                                                                                     





































                                                                                                             
         
















                                                                                             
                                                                                                                                        
                                                                                            




                                                              
                                                                                               












                                                                                                         

         










































                                                                                                                      
                                                                                                              































                                                                                                                                           
                                                                                                     
                                                                                                                   









                                                                                                     
 



                                                                      



                                       



                                                                          



                               



                                                                                     



                                                        











                                                                                      
                                                     
                                                                                            
         



                                       
 
package org.eclipse.team.internal.ccvs.core.client;

/*
 * (c) Copyright IBM Corp. 2000, 2002.
 * All Rights Reserved.
 */
 
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.team.ccvs.core.ICVSRepositoryLocation;
import org.eclipse.team.internal.ccvs.core.CVSException;
import org.eclipse.team.internal.ccvs.core.Policy;
import org.eclipse.team.internal.ccvs.core.connection.CVSRepositoryLocation;
import org.eclipse.team.internal.ccvs.core.connection.Connection;
import org.eclipse.team.internal.ccvs.core.resources.ICVSFile;
import org.eclipse.team.internal.ccvs.core.resources.ICVSFolder;
import org.eclipse.team.internal.ccvs.core.resources.ICVSResource;
import org.eclipse.team.internal.ccvs.core.resources.LocalFile;
import org.eclipse.team.internal.ccvs.core.resources.LocalFolder;
import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo;
import org.eclipse.team.internal.ccvs.core.util.Assert;
import org.eclipse.team.internal.ccvs.core.util.Util;

/**
 * Maintains CVS communication state for the lifetime of a connection
 * to a remote repository.  This class covers the initialization, use,
 * and eventual shutdown of a dialogue between a CVS client and a
 * remote server.  This dialogue may be monitored through the use of
 * a console.
 * 
 * Initially the Session is in a CLOSED state during which communication
 * with the server cannot take place.  Once OPENED, any number of commands
 * may be issued serially to the server, one at a time.  When finished, the
 * Session MUST be CLOSED once again to prevent eventual local and/or
 * remote resource exhaustion.  The session can either be discarded, or
 * re-opened for use with the same server though no state is persisted from
 * previous connections except for console attributes.
 * 
 * CVSExceptions are thrown only as a result of unrecoverable errors.  Once
 * this happens, commands must no longer be issued to the server.  If the
 * Session is in the OPEN state, it is still the responsibility of the
 * caller to CLOSE it before moving on.
 */
public class Session {
	public static final String CURRENT_LOCAL_FOLDER = "."; //$NON-NLS-1$
	public static final String CURRENT_REMOTE_FOLDER = ""; //$NON-NLS-1$
	public static final String SERVER_SEPARATOR = "/"; //$NON-NLS-1$

	// default file transfer buffer size (in bytes)
	private static int TRANSFER_BUFFER_SIZE = 8192;
	// update progress bar in increments of this size (in bytes)
	//   no incremental progress shown for files smaller than this size
	private static long TRANSFER_PROGRESS_INCREMENT = 32768;

	// the platform's line termination sequence
	private static final byte[] PLATFORM_NEWLINE_BYTES =
		System.getProperty("line.separator").getBytes();  //$NON-NLS-1$ // at least one byte long
	// the server's line termination sequence
	private static final int SERVER_NEWLINE_BYTE = 0x0a; // exactly one byte long
	private static final byte[] SERVER_NEWLINE_BYTES = new byte[] { SERVER_NEWLINE_BYTE };
	// true iff newlines must be converted between platform and server formats
	private static boolean MUST_CONVERT_NEWLINES = PLATFORM_NEWLINE_BYTES.length != 1
		&& PLATFORM_NEWLINE_BYTES[0] != SERVER_NEWLINE_BYTE;
		
	// VCM 1.0 comitted files using CR/LF as a delimiter
	private static final int CARRIAGE_RETURN_BYTE = 0x0d;

	private CVSRepositoryLocation location;
	private ICVSFolder localRoot;
	private boolean outputToConsole;
	private Connection connection = null;
	private String validRequests = null;
	private Date modTime = null;
	private boolean noLocalChanges = false;
	private List expansions;

	// a shared buffer used for file transfers
	private byte[] transferBuffer = null;

	/**
	 * Creates a new CVS session, initially in the CLOSED state.
	 * By default, command output is directed to the console.
	 * 
	 * @param location the CVS repository location used for this session
	 * @param localRoot represents the current working directory of the client
	 */
	public Session(ICVSRepositoryLocation location, ICVSFolder localRoot) {
		this(location, localRoot, true);
	}
	
	/**
	 * Creates a new CVS session, initially in the CLOSED state.
	 * 
	 * @param location the CVS repository location used for this session
	 * @param localRoot represents the current working directory of the client
	 * @param outputToConsole if true, command output is directed to the console
	 */
	public Session(ICVSRepositoryLocation location, ICVSFolder localRoot, boolean outputToConsole) {
		this.location = (CVSRepositoryLocation) location;
		this.localRoot = localRoot;
		this.outputToConsole = outputToConsole;
	}
	
	/*
	 * Add a module expansion receivered from the server.
	 * This is only used by the ModuleExpansionsHandler
	 */
	protected void addModuleExpansion(String expansion) {
		expansions.add(expansion);
	}
	
	/*
	 * Add a module expansion receivered from the server.
	 * This is only used by the ExpandModules command
	 */
	protected void resetModuleExpansion() {
		if (expansions == null) 
			expansions = new ArrayList();
		else
			expansions.clear();
	}
	
	/**
	 * Opens, authenticates and initializes a connection to the server specified
	 * for the remote location.
	 *
	 * @param monitor the progress monitor
	 * @throws IllegalStateException if the Session is not in the CLOSED state
	 */
	public void open(IProgressMonitor monitor) throws CVSException {
		if (connection != null) throw new IllegalStateException();
		monitor.beginTask(null, 100);
		try {
			connection = location.openConnection(Policy.subMonitorFor(monitor, 50));
			
			// tell the server the names of the responses we can handle
			connection.writeLine("Valid-responses " + Command.makeResponseList()); //$NON-NLS-1$
	
			// ask for the set of valid requests
			boolean saveOutputToConsole = outputToConsole;
			outputToConsole = false;
			Command.VALID_REQUESTS.execute(this, Command.NO_GLOBAL_OPTIONS, Command.NO_LOCAL_OPTIONS,
				Command.NO_ARGUMENTS, null, Policy.subMonitorFor(monitor, 50));
			outputToConsole = saveOutputToConsole;
	
			// set the root directory on the server for this connection
			connection.writeLine("Root " + getRepositoryRoot()); //$NON-NLS-1$
		} finally {
			monitor.done();
		}
	}		
	
	/**
	 * Closes a connection to the server.
	 *
	 * @throws IllegalStateException if the Session is not in the OPEN state
	 */
	public void close() throws CVSException {
		if (connection == null) throw new IllegalStateException();
		connection.close();
		connection = null;
		validRequests = null;
	}
	
	/**
	 * Determines if the server supports the specified request.
	 * 
	 * @param request the request string to verify
	 * @return true iff the request is supported
	 */
	public boolean isValidRequest(String request) {
		return (validRequests == null) ||
			(validRequests.indexOf(" " + request + " ") != -1); //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * Gives you an LocalFolder for a absolute path in
	 * platform dependend style.
	 * 
	 * @throws CVSException on path.indexOf("CVS") != -1
	 * @throws CVSException on internal IOExeption
	 */
	public static ICVSFolder getManagedFolder(File folder) throws CVSException {
		return new LocalFolder(folder);
	}
	public static ICVSFile getManagedFile(File file) throws CVSException {
		return new LocalFile(file);
	}
	
	public static ICVSResource getManagedResource(IResource resource) throws CVSException {
		File file = resource.getLocation().toFile();
		if (resource.getType() == IResource.FILE)
			return getManagedFile(file);
		else
			return getManagedFolder(file);
	}

	/**
	 * Returns the local root folder for this session.
	 * <p>
	 * Generally speaking, specifies the "current working directory" at
	 * the time of invocation of an equivalent CVS command-line client.
	 * </p>
	 * 
	 * @return the local root folder
	 */
	public ICVSFolder getLocalRoot() {
		return localRoot;
	}

	/**
	 * Return the list of module expansions communicated from the server
	 */
	public String[] getModuleExpansions() {
		if (expansions == null) return new String[0];
		return (String[]) expansions.toArray(new String[expansions.size()]);
	}
	
	/**
	 * Returns the repository root folder for this session.
	 * <p>
	 * Specifies the unqualified path to the CVS repository root folder
	 * on the server.
	 * </p>
	 * 
	 * @return the repository root folder
	 */
	public String getRepositoryRoot() {
		return location.getRootDirectory();
	}
	
	/**
	 * Returns an object representing the CVS repository location for this session.
	 * 
	 * @return the CVS repository location
	 */
	public ICVSRepositoryLocation getCVSRepositoryLocation() {
		return location;
	}

	/**
	 * Receives a line of text minus the newline from the server.
	 * 
	 * @return the line of text
	 */
	public String readLine() throws CVSException {
		return connection.readLine();
	}

	/**
	 * Sends a line of text followed by a newline to the server.
	 * 
	 * @param line the line of text
	 */
	public void writeLine(String line) throws CVSException {
		connection.writeLine(line);
	}

	/**
	 * Sends an argument to the server.
	 * <p>e.g. sendArgument("Hello\nWorld\n  Hello World") sends:
	 * <pre>
	 *   Argument Hello \n
	 *   Argumentx World \n
	 *   Argumentx Hello World \n
	 * </pre></p>
	 *
	 * @param arg the argument to send
	 */
	public void sendArgument(String arg) throws CVSException {
		connection.write("Argument "); //$NON-NLS-1$
		int oldPos = 0;
		for (;;) {
			int pos = arg.indexOf('\n', oldPos);
			if (pos == -1) break;
			connection.writeLine(arg.substring(oldPos, pos));
			connection.write("Argumentx "); //$NON-NLS-1$
			oldPos = pos + 1;
		}
		connection.writeLine(arg.substring(oldPos));
	}

	/**
	 * Sends a command to the server and flushes any output buffers.
	 * 
	 * @param commandId the string associated with the command to be executed
	 */
	public void sendCommand(String commandId) throws CVSException {
		connection.writeLine(commandId);
		connection.flush();
	}

	/**
	 * Sends an Is-modified request to the server without the file contents.
	 * <p>e.g. if a file called "local_file" was modified, sends:
	 * <pre>
	 *   Is-modified local_file \n
	 * </pre></p><p>
	 * This request is an optimized form of the Modified request and may not
	 * be supported by all servers.  Hence, if it is not supported, a Modified
	 * request is sent instead along with the file's contents.  According to
	 * the CVS protocol specification, this request is only safe for use with
	 * some forms of: admin, annotate, diff, editors, log, watch-add, watch-off,
	 * watch-on, watch-remove, and watchers.<br>
	 * It may be possible to use this for: add, export, remove and status.<br>
	 * Do not use with co, ci, history, init, import, release, rdiff, rtag, or update.
	 * </p><p>
	 * Note: The most recent Directory request must have specified the file's
	 *       parent folder.
	 * </p>
	 * 
	 * @param file the file that was modified
	 * @see #sendModified
	 */
	public void sendIsModified(ICVSFile file, boolean isBinary, IProgressMonitor monitor)
		throws CVSException {
		if (isValidRequest("Is-modified")) { //$NON-NLS-1$
			connection.writeLine("Is-modified " + file.getName()); //$NON-NLS-1$
		} else {
			sendModified(file, isBinary, monitor);
		}
	}

	/**
	 * Sends a Static-directory request to the server.
	 * <p>
	 * Indicates that the directory specified in the most recent Directory request
	 * is static.  No new files will be checked out into this directory unless
	 * explicitly requested.
	 * </p>
	 */
	public void sendStaticDirectory() throws CVSException {
		connection.writeLine("Static-directory"); //$NON-NLS-1$
	}

	/**
	 * Sends a Directory request to the server with a constructed path.
	 * <p>
	 * It may be necessary at times to guess the remote path of a directory since
	 * it does not exist yet.  In this case we construct a remote path based on the
	 * local path by prepending the local path with the repository root.  This may
	 * not work in the presence of modules, so only use it for creating new projects.
	 * </p><p>
	 * Note: A CVS repository root can end with a trailing slash. The CVS server
	 *       expects that the repository root sent contain this extra slash. Including
	 *       the foward slash in addition to the absolute remote path makes for a string
	 *       containing two consecutive slashes (e.g. /home/cvs/repo//projecta/a.txt).
	 *       This is valid in the CVS protocol.
	 * </p>
	 */
	public void sendConstructedDirectory(String localDir) throws CVSException {
		sendDirectory(localDir, getRepositoryRoot() + "/" + localDir); //$NON-NLS-1$
	}

	/**
	 * Sends a Directory request to the server.
	 * <p>e.g. sendDirectory("local_dir", "remote_dir") sends:
	 * <pre>
	 *   Directory local_dir
	 *   repository_root/remote_dir
	 * </pre></p>
	 * 
	 * @param localDir the path of the local directory relative to localRoot
	 * @param remoteDir the path of the remote directory relative to repositoryRoot
	 */
	public void sendDirectory(String localDir, String remoteDir) throws CVSException {
		if (localDir.length() == 0) localDir = "."; //$NON-NLS-1$
		connection.writeLine("Directory " + localDir); //$NON-NLS-1$
		connection.writeLine(remoteDir);
	}

	/**
	 * Sends a Directory request for the localRoot.
	 */
	public void sendLocalRootDirectory() throws CVSException {
		sendDirectory(".", localRoot.getRemoteLocation(localRoot)); //$NON-NLS-1$
	}

	/**
	 * Sends a Directory request for the localRoot with a constructed path.
	 * <p>
	 * Use this when creating a new project that does not exist in the repository.
	 * </p>
	 * @see #sendConstructedDirectory
	 */
	public void sendConstructedRootDirectory() throws CVSException {
		sendConstructedDirectory(""); //$NON-NLS-1$
	}

	/**
	 * Sends an Entry request to the server.
	 * <p>
	 * Indicates that a file is managed (but it may not exist locally).  Sends
	 * the file's entry line to the server to indicate the version that was
	 * previously checked out.
	 * </p><p>
	 * Note: The most recent Directory request must have specified the file's
	 *       parent folder.
	 * </p>
	 * 
	 * @param entryLine the formatted entry line of the managed file.
	 */
	public void sendEntry(String entryLine) throws CVSException {
		connection.writeLine("Entry " + entryLine); //$NON-NLS-1$
	}

	/**
	 * Sends a global options to the server.
	 * <p>e.g. sendGlobalOption("-n") sends:
	 * <pre>
	 *   Global_option -n \n
	 * </pre></p>
	 * 
	 * @param option the global option to send
	 */
	public void sendGlobalOption(String option) throws CVSException {
		connection.writeLine("Global_option " + option); //$NON-NLS-1$
	}

	/**
	 * Sends an Unchanged request to the server.
	 * <p>e.g. if a file called "local_file" was not modified, sends:
	 * <pre>
	 *   Unchanged local_file \n
	 * </pre></p><p>
	 * Note: The most recent Directory request must have specified the file's
	 *       parent folder.
	 * </p>
	 * 
	 * @param file the file that was not modified
	 */
	public void sendUnchanged(ICVSFile file) throws CVSException {
		connection.writeLine("Unchanged " + file.getName()); //$NON-NLS-1$
	}
	
	/**
	 * Sends a Questionable request to the server.
	 * <p>
	 * Indicates that a file exists locally but is unmanaged.  Asks the server
	 * whether or not the file should be ignored in subsequent CVS operations.
	 * The reply to the request occurs in the form of special M-type message
	 * responses prefixed with '?' when the next command is executed.
	 * </p><p>
	 * Note: The most recent Directory request must have specified the file's
	 *       parent folder.
	 * </p>
	 * 
	 * @param resource the local file or folder
	 */
	public void sendQuestionable(ICVSResource resource) throws CVSException {
		connection.writeLine("Questionable " + resource.getName()); //$NON-NLS-1$
	}

	/**
	 * Sends a Sticky tag request to the server.
	 * <p>
	 * Indicates that the directory specified in the most recent Directory request
	 * has a sticky tag or date, and sends the tag's contents.
	 * </p>
	 * 
	 * @param tag the sticky tag associated with the directory
	 */
	public void sendSticky(String tag) throws CVSException {
		connection.writeLine("Sticky " + tag); //$NON-NLS-1$
	}

	/**
	 * Sends a Modified request to the server along with the file contents.
	 * <p>e.g. if a file called "local_file" was modified, sends:
	 * <pre>
	 *   Modified local_file \n
	 *   file_permissions \n
	 *   file_size \n
	 *   [... file_contents ...]
	 * </pre></p><p>
	 * Under some circumstances, Is-modified may be used in place of this request.<br>
	 * Do not use with history, init, import, rdiff, release, rtag, or update.
	 * </p><p>
	 * Note: The most recent Directory request must have specified the file's
	 *       parent folder.
	 * </p>
	 * 
	 * @param file the file that was modified
	 * @param isBinary if true the file is sent without translating line delimiters
	 * @param monitor the progress monitor
	 * @see #sendIsModified
	 */
	public void sendModified(ICVSFile file, boolean isBinary, IProgressMonitor monitor)
		throws CVSException {
		connection.writeLine("Modified " + file.getName()); //$NON-NLS-1$
		ResourceSyncInfo info = file.getSyncInfo();
		if (info != null && info.getPermissions() != null) {
			connection.writeLine(info.getPermissions());
		} else {
			connection.writeLine(ResourceSyncInfo.DEFAULT_PERMISSIONS);
		}
		sendFile(file, isBinary, monitor);
	}

	/**
	 * Gets the shared file transfer buffer.
	 */
	private byte[] getTransferBuffer() {
		if (transferBuffer == null) transferBuffer = new byte[TRANSFER_BUFFER_SIZE];
		return transferBuffer;
	}
	
	/**
	 * Sends a file to the remote CVS server, possibly translating line delimiters.
	 * <p>
	 * Line termination sequences are automatically converted to linefeeds only
	 * (required by the CVS specification) when sending non-binary files.  This
	 * may alter the actual size and contents of the file that is sent.
	 * </p><p>
	 * Note: Non-binary files must be small enough to fit in available memory.
	 * </p>
	 * @param file the file to be sent
	 * @param isBinary is true if the file should be sent without translation
	 * @param monitor the progress monitor
	 */
	public void sendFile(ICVSFile file, boolean isBinary, IProgressMonitor monitor)
		throws CVSException {
		// update progress monitor
		String title = Policy.bind("Session.sending", new Object[]{ Util.toTruncatedPath(file, localRoot, 3) }); //$NON-NLS-1$
		monitor.subTask(Policy.bind("Session.transferNoSize", title)); //$NON-NLS-1$
		// obtain an input stream for the file and its size
		long size = file.getSize();
		InputStream in = file.getInputStream();
		OutputStream out = connection.getOutputStream();
		try {
			if (isBinary || PLATFORM_NEWLINE_BYTES.length == 1) {
				writeLine(Long.toString(size));
				if (! isBinary && MUST_CONVERT_NEWLINES) {
					/*** convert newlines on-the-fly ***/
					transferWithProgress(in, out, size,
						PLATFORM_NEWLINE_BYTES[0], SERVER_NEWLINE_BYTES, monitor, title);
				} else {
					/*** perform no conversion ***/
					transferWithProgress(in, out, size, 0, null, monitor, title);
				}
			} else {
				// implies file is text, and we must convert newlines since size of platform newline
				// sequence is not 1, but the server's is
				/*** convert newlines in memory, since file size may change ***/
				Assert.isTrue(size < Integer.MAX_VALUE);
				int fsize = (int) size;
				byte[] fileContents;
				if (fsize <= TRANSFER_BUFFER_SIZE) fileContents = getTransferBuffer();
				else fileContents = new byte[fsize];
				// translate the file from non-LF delimiters in memory and
				// compute its reduced size
				try {
					// read exactly _size_ bytes
					try {
						for (int pos = 0, read; pos < fsize; pos += read) {
							Policy.checkCanceled(monitor);
							read = in.read(fileContents, pos, fsize - pos);
							if (read == -1) {
								// file ended prematurely
								throw new IOException(Policy.bind("Session.readError"));//$NON-NLS-1$
							}
						}
					} finally {
						in.close(); // remember to close the source file
						in = null;
					}
				} catch (IOException e) {
					throw CVSException.wrapException(e);
				}
				// convert platform line termination sequences
				// conservative since it leaves any partial sequences alone (like stray CR's)
				// assumes no prefix of a sequence
				int cur = 0, match = 0;
				for (int pos = 0; pos < fsize; ++pos) {
					byte b = fileContents[pos];
					if (PLATFORM_NEWLINE_BYTES[match] == b) {
						if (match == PLATFORM_NEWLINE_BYTES.length - 1) {
							b = SERVER_NEWLINE_BYTE;
							cur -= match;
							match = 0;
						} else match += 1;
					} else {
						match = 0;
					}
					fileContents[cur++] = b;
				}
				// send file
				writeLine(Integer.toString(cur));
				in = new ByteArrayInputStream(fileContents, 0, cur);
				transferWithProgress(in, out, cur, 0, null, monitor, title);
			}
		} finally {
			try {
				if (in != null) in.close();
			} catch (IOException e) {
				throw CVSException.wrapException(e);
			}
		}
	}

	/**
	 * Receives a file from the remote CVS server, possibly translating line delimiters.
	 * <p>
	 * Line termination sequences are automatically converted to platform format
	 * only when receiving non-binary files.  This may alter the actual size and
	 * contents of the file that is received.
	 * </p><p>
	 * Translation is performed on-the-fly, so the file need not fit in available memory.
	 * </p>
	 * @param file the file to be received
	 * @param isBinary is true if the file should be received without translation
	 * @param monitor the progress monitor
	 */
	public void receiveFile(ICVSFile file, boolean isBinary, IProgressMonitor monitor)
	throws CVSException {
		// update progress monitor
		String title = Policy.bind("Session.receiving", new Object[]{ Util.toTruncatedPath(file, localRoot, 3) }); //$NON-NLS-1$
		monitor.subTask(Policy.bind("Session.transferNoSize", title)); //$NON-NLS-1$
		// get the file size from the server
		long size;
		try {
			size = Long.parseLong(readLine(), 10);
		} catch (NumberFormatException e) {
			throw new CVSException(Policy.bind("Session.badInt"), e); //$NON-NLS-1$
		}
		// obtain an output stream for the file
		OutputStream out = file.getOutputStream();
		try {
			transferWithProgress(connection.getInputStream(), out, size, SERVER_NEWLINE_BYTE,
				isBinary ? null : PLATFORM_NEWLINE_BYTES, monitor, title);
		} finally {
			try {
				out.close();
			} catch (IOException e) {
				throw CVSException.wrapException(e);
			}
		}
	}
	
	/**
	 * Transfers a file to or from the remove CVS server, possibly expanding line delimiters.
	 * <p>
	 * Line termination sequences are only converted upon request by specifying an
	 * array containing the expected sequence of bytes representing an outbound newline,
	 * and a single byte representing an inbound newline.  If null is passed for the
	 * former, the file is assumed to have binary contents, hence no translation is
	 * performed.
	 * </p><p>
	 * Translation is performed on-the-fly, so the file need not fit in available memory.
	 * </p>
	 * @param in the input stream
	 * @param out the output stream
	 * @param size the source file size
	 * @param newlineIn the single byte for a received newline, ignored if binary
	 * @param newlineOut the sequence of bytes for sent newline, or null if binary
	 * @param monitor the progress monitor
	 * @param title the name of the file being received (as shown in the monitor)
	 */
	private void transferWithProgress(InputStream in, OutputStream out,
		long size, int newlineIn, byte[] newlineOut, IProgressMonitor monitor, String title)
		throws CVSException {
		long nextProgressThresh = TRANSFER_PROGRESS_INCREMENT;
		Long ksize = new Long(size / 1024);
		try {
			byte[] buffer = getTransferBuffer();
			final int wfirst, wlast;
			if (newlineOut != null) {
				wfirst = buffer.length / 2;
				wlast = buffer.length - newlineOut.length - 1; // reserve space for newline & stray CR
			} else {
				wfirst = buffer.length;
				wlast = wfirst;
			}
			int wpos = wfirst;
			// read exactly _size_ bytes
			boolean fixCRLF = (newlineIn == SERVER_NEWLINE_BYTE);
			boolean seenCR = false; // only true if fixCRLF and last byte was a CR
			for (long totalRead = 0; totalRead < size;) {
				Policy.checkCanceled(monitor);
				int read = in.read(buffer, 0, (int) Math.min(wfirst, size - totalRead));
				if (read == -1) {
					// file ended prematurely
					throw new IOException(Policy.bind("Session.readError")); //$NON-NLS-1$
				}
				totalRead += read;
				if (newlineOut == null) {
					// dump binary data
					out.write(buffer, 0, read);
				} else {
					// filter newline sequences in memory from first half of buffer into second half
					// then dump to output stream
					for (int p = 0; p < read; ++p) {
						final byte b = buffer[p];
						if (b == CARRIAGE_RETURN_BYTE && fixCRLF) {
							seenCR = true;
						} else {
							if (b == newlineIn) {
								// if fixCRLF we ignore previous CR (if there was one)
								// replace newlineIn with newlineOut
								for (int x = 0; x < newlineOut.length; ++x) buffer[wpos++] = newlineOut[x];
							} else {
								if (seenCR) buffer[wpos++] = CARRIAGE_RETURN_BYTE; // preserve stray CR's
								buffer[wpos++] = b;
							}
							seenCR = false;
						}
						if (wpos >= wlast) {
							// flush output buffer
							out.write(buffer, wfirst, wpos - wfirst);
							wpos = wfirst;
						}
					}
				}
				// update progress monitor
				if (totalRead > nextProgressThresh) {
					monitor.subTask(Policy.bind("Session.transfer", //$NON-NLS-1$
							new Object[] { title, new Long(totalRead / 1024), ksize}));
					nextProgressThresh = totalRead + TRANSFER_PROGRESS_INCREMENT;
				}
			}
			// flush pending buffered output
			if (seenCR) buffer[wpos++] = CARRIAGE_RETURN_BYTE; // preserve stray CR's
			if (wpos != wfirst) out.write(buffer, wfirst, wpos - wfirst);
		} catch (IOException e) {
			throw CVSException.wrapException(e);
		}
	}	 

	/**
	 * Stores the value of the last Mod-time response encountered.
	 * Valid only for the duration of a single CVS command.
	 */
	void setModTime(Date modTime) {
		this.modTime = modTime;
	}
	
	/**
	 * Returns the stored value of the last Mod-time response,
	 * or null if there was none while processing the current command.
	 */
	Date getModTime() {
		return modTime;
	}
	
	/**
	 * Stores true if the -n global option was specified for the current command.
	 * Valid only for the duration of a single CVS command.
	 */
	void setNoLocalChanges(boolean noLocalChanges) {
		this.noLocalChanges = noLocalChanges;
	}
	
	/**
	 * Returns true if the -n global option was specified for the current command,
	 * false otherwise.
	 */
	boolean isNoLocalChanges() {
		return noLocalChanges;
	}
	
	/**
	 * Callback hook for the ValidRequestsHandler to specify the set of valid
	 * requests for this session.
	 */
	void setValidRequests(String validRequests) {
		this.validRequests = " " + validRequests + " "; //$NON-NLS-1$  //$NON-NLS-2$
	}

	boolean isOutputToConsole() {
		return outputToConsole;
	}
}

Back to the top