| package org.eclipse.team.internal.ccvs.core.client; |
| |
| /* |
| * (c) Copyright IBM Corp. 2000, 2002. |
| * All Rights Reserved. |
| */ |
| |
| import java.io.ByteArrayInputStream; |
| 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.runtime.IProgressMonitor; |
| import org.eclipse.team.ccvs.core.ICVSFile; |
| import org.eclipse.team.ccvs.core.ICVSFolder; |
| import org.eclipse.team.ccvs.core.ICVSRepositoryLocation; |
| import org.eclipse.team.ccvs.core.ICVSResource; |
| 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.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$ |
| } |
| |
| /** |
| * 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. |
| * |
| * The modules expansions are typically a directory path of length 1 |
| * but can be of greater length on occasion. |
| */ |
| 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; |
| } |
| |
| |
| } |