blob: f63b5fbe34b700963ea628f28c2b40ec1089dc80 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2007 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Red Hat Incorporated - is/setExecutable() code
*******************************************************************************/
package org.eclipse.team.internal.ccvs.core.client;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.*;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.client.Command.GlobalOption;
import org.eclipse.team.internal.ccvs.core.client.Command.QuietOption;
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.NotifyInfo;
import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo;
import org.eclipse.team.internal.ccvs.core.util.Util;
import org.eclipse.team.internal.core.streams.*;
/**
* 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 final 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 final int TRANSFER_PROGRESS_INCREMENT = 32768;
public static final boolean IS_CRLF_PLATFORM = Arrays.equals(
System.getProperty("line.separator").getBytes(), new byte[] { '\r', '\n' }); //$NON-NLS-1$
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 boolean createBackups = true;
private int compressionLevel = 0;
private List expansions;
private Collection /* of ICVSFile */ textTransferOverrideSet = null;
// state need to indicate whether
private boolean ignoringLocalChanges = false;
// The resource bundle key that provides the file sending message
private String sendFileTitleMessage;
private Map responseHandlers;
// List of errors accumulated while the command is executing
private List errors = new ArrayList();
private Command currentCommand;
/**
* 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 {
open(monitor, true /* write access*/);
}
public void open(IProgressMonitor monitor, boolean writeAccess) throws CVSException {
if (connection != null) throw new IllegalStateException();
monitor = Policy.subMonitorFor(monitor, 100);
boolean opened = false;
try {
connection = getLocationForConnection(writeAccess).openConnection(Policy.subMonitorFor(monitor, 50));
// If we're connected to a CVSNT server or we don't know the platform,
// accept MT. Otherwise don't.
boolean useMT = ! (location.getServerPlatform() == CVSRepositoryLocation.CVS_SERVER);
if ( ! useMT) {
removeResponseHandler("MT"); //$NON-NLS-1$
}
// tell the server the names of the responses we can handle
connection.writeLine("Valid-responses " + makeResponseList()); //$NON-NLS-1$
// Flush in order to recieve the valid requests
connection.flush();
// ask for the set of valid requests
IStatus status = Request.VALID_REQUESTS.execute(this, Policy.subMonitorFor(monitor, 40));
if (!status.isOK()) {
throw new CVSException(status);
}
// set the root directory on the server for this connection
connection.writeLine("Root " + getRepositoryRoot()); //$NON-NLS-1$
// enable compression
compressionLevel = CVSProviderPlugin.getPlugin().getCompressionLevel();
if (compressionLevel != 0 && isValidRequest("gzip-file-contents")) { //$NON-NLS-1$
// Enable the use of CVS 1.8 per-file compression mechanism.
// The newer Gzip-stream request seems to be problematic due to Java's
// GZIPInputStream tendency to block on read() rather than to return a
// partially filled buffer. The latter option would be better since it
// can make more effective use of the code dictionary, if it can be made
// to work...
connection.writeLine("gzip-file-contents " + Integer.toString(compressionLevel)); //$NON-NLS-1$
} else {
compressionLevel = 0;
}
// get the server platform if it is unknown
if (CVSProviderPlugin.getPlugin().isDetermineVersionEnabled() && location.getServerPlatform() == CVSRepositoryLocation.UNDETERMINED_PLATFORM) {
Command.VERSION.execute(this, location, Policy.subMonitorFor(monitor, 10));
}
opened = true;
} finally {
if (connection != null && ! opened) {
close();
}
monitor.done();
}
}
/*
* Return the location to be used for this connection
*/
private CVSRepositoryLocation getLocationForConnection(boolean writeAccess) {
return location;
}
/**
* Closes a connection to the server.
*
* @throws IllegalStateException if the Session is not in the OPEN state
*/
public void close() {
if (connection != null) {
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$
}
public boolean isCVSNT() {
if (location.getServerPlatform() == CVSRepositoryLocation.UNDETERMINED_PLATFORM) {
return location.getRootDirectory().indexOf(':') == 1;
} else {
return location.getServerPlatform() == CVSRepositoryLocation.CVSNT_SERVER;
}
}
/**
* 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(stripTrainingCR(arg.substring(oldPos, pos)));
connection.write("Argumentx "); //$NON-NLS-1$
oldPos = pos + 1;
}
connection.writeLine(stripTrainingCR(arg.substring(oldPos)));
}
/*
* Remove any trailing CR from the string
*/
private String stripTrainingCR(String string) {
if (string.endsWith("\r")) { //$NON-NLS-1$
return string.substring(0, string.length() - 1);
}
return string;
}
/**
* Sends a request to the server and flushes any output buffers.
*
* @param requestId the string associated with the request to be executed
*/
public void sendRequest(String requestId) throws CVSException {
connection.writeLine(requestId);
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 {
String path = Util.appendPath(getRepositoryRoot(), localDir);
sendDirectory(localDir, path);
}
/**
* 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 = CURRENT_LOCAL_FOLDER;
connection.writeLine("Directory " + localDir); //$NON-NLS-1$
connection.writeLine(remoteDir);
}
/**
* Sends a Directory request for the localRoot.
*/
public void sendLocalRootDirectory() throws CVSException {
sendDirectory(CURRENT_LOCAL_FOLDER, localRoot.getRemoteLocation(localRoot));
}
/**
* 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(byte[] syncBytes, String serverTimestamp) throws CVSException {
connection.write("Entry "); //$NON-NLS-1$
if (serverTimestamp == null) {
serverTimestamp = ""; //$NON-NLS-1$
}
int start = Util.getOffsetOfDelimeter(syncBytes, (byte)'/', 0, 3);
if (start == -1) {
// something is wrong with the entry line so just send it as is
// and let the server report the error.
connection.writeLine(new String(syncBytes));
return;
}
int end = Util.getOffsetOfDelimeter(syncBytes, (byte)'/', start + 1, 1);
if (end == -1) {
// something is wrong with the entry line so just send it as is
// and let the server report the error.
connection.writeLine(new String(syncBytes));
return;
}
connection.write(new String(syncBytes, 0, start + 1));
connection.write(serverTimestamp);
connection.writeLine(new String(syncBytes, end, syncBytes.length - end));
}
/**
* 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 the Notify request to the server
*/
public void sendNotify(ICVSFolder parent, NotifyInfo info)
throws CVSException {
String filename = info.getName();
connection.writeLine("Notify " + filename); //$NON-NLS-1$
connection.writeLine(info.getServerLine(parent));
}
/**
* 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 {
sendModified(file, isBinary, true, monitor);
}
public void sendModified(ICVSFile file, boolean isBinary, boolean sendBinary, IProgressMonitor monitor)
throws CVSException {
String filename = file.getName();
connection.writeLine("Modified " + filename); //$NON-NLS-1$
// send the default permissions for now
if (file.isExecutable()) {
connection.writeLine(ResourceSyncInfo.getDefaultExecutablePermissions());
} else {
connection.writeLine(ResourceSyncInfo.getDefaultPermissions());
}
sendFile(file, isBinary, sendBinary, monitor);
}
/**
* 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(ICVSStorage file, boolean isBinary, boolean sendBinary, IProgressMonitor monitor) throws CVSException {
// check overrides
if (textTransferOverrideSet != null &&
textTransferOverrideSet.contains(file)) isBinary = false;
// update progress monitor
final String title = NLS.bind(getSendFileTitleMessage(), (new Object[]{ Util.toTruncatedPath(file, localRoot, 3) }));
monitor.subTask(NLS.bind(CVSMessages.Session_transferNoSize, new String[] { title }));
try {
InputStream in = null;
long length;
try {
if (isBinary && !sendBinary) {
byte[] bytes = "hello".getBytes(); //$NON-NLS-1$
sendUncompressedBytes(new ByteArrayInputStream(bytes), bytes.length);
return;
}
if (compressionLevel == 0) {
in = file.getContents();
if (!isBinary && IS_CRLF_PLATFORM){
// uncompressed text
byte[] buffer = new byte[TRANSFER_BUFFER_SIZE];
in = new CRLFtoLFInputStream(in);
ByteCountOutputStream counter = new ByteCountOutputStream();
try {
for (int count; (count = in.read(buffer)) != -1;) counter.write(buffer, 0, count);
} finally {
counter.close();
}
in.close();
length = counter.getSize();
in = new CRLFtoLFInputStream(file.getContents());
} else {
// uncompressed binary
length = file.getSize();
}
in = new ProgressMonitorInputStream(in, length, TRANSFER_PROGRESS_INCREMENT, monitor) {
protected void updateMonitor(long bytesRead, long bytesTotal, IProgressMonitor monitor) {
if (bytesRead == 0) return;
Assert.isTrue(bytesRead <= bytesTotal);
monitor.subTask(NLS.bind(CVSMessages.Session_transfer, (new Object[] { title, Long.toString(bytesRead >> 10), Long.toString(bytesTotal >> 10) })));
}
};
sendUncompressedBytes(in, length);
} else {
monitor.subTask(NLS.bind(CVSMessages.Session_calculatingCompressedSize, new String[] { Util.toTruncatedPath(file, localRoot, 3) }));
in = file.getContents();
byte[] buffer = new byte[TRANSFER_BUFFER_SIZE];
ByteCountOutputStream counter = new ByteCountOutputStream();
OutputStream zout = new GZIPOutputStream(counter);
if (!isBinary && IS_CRLF_PLATFORM) in = new CRLFtoLFInputStream(in);
try {
for (int count; (count = in.read(buffer)) != -1;) zout.write(buffer, 0, count);
} finally {
zout.close();
}
in.close();
in = file.getContents();
in = new ProgressMonitorInputStream(in, file.getSize(), TRANSFER_PROGRESS_INCREMENT, monitor) {
protected void updateMonitor(long bytesRead, long bytesTotal, IProgressMonitor monitor) {
if (bytesRead == 0) return;
Assert.isTrue(bytesRead <= bytesTotal);
monitor.subTask(NLS.bind(CVSMessages.Session_transfer, (new Object[] { title, Long.toString(bytesRead >> 10), Long.toString(bytesTotal >> 10) })));
}
};
if (!isBinary && IS_CRLF_PLATFORM) in = new CRLFtoLFInputStream(in);
sendCompressedBytes(in, counter.getSize());
}
} finally {
if (in != null) in.close();
}
} catch (IOException e) {
throw CVSException.wrapException(e);
}
}
/*
* Send the contents of the input stream to CVS.
* Length must equal the number of bytes that will be transferred
* across the wire, that is, the compressed file size.
*/
private void sendCompressedBytes(InputStream in, long length) throws IOException, CVSException {
String sizeLine = "z" + Long.toString(length); //$NON-NLS-1$
writeLine(sizeLine);
OutputStream out = connection.getOutputStream();
GZIPOutputStream zo = new GZIPOutputStream(out);
byte[] buffer = new byte[TRANSFER_BUFFER_SIZE];
for (int count;
(count = in.read(buffer)) != -1;)
zo.write(buffer, 0, count);
zo.finish();
}
/*
* Send the contents of the input stream to CVS.
* Length must equal the number of bytes that will be transferred
* across the wire.
*/
private void sendUncompressedBytes(InputStream in, long length) throws IOException, CVSException {
OutputStream out = connection.getOutputStream();
String sizeLine = Long.toString(length);
writeLine(sizeLine);
byte[] buffer = new byte[TRANSFER_BUFFER_SIZE];
for (int count; (count = in.read(buffer)) != -1;) out.write(buffer, 0, count);
}
/**
* 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 responseType one of the ICVSFile updated types (UPDATED, CREATED, MERGED, UPDATE_EXISTING)
* indicating what repsonse type provided the file contents
* @param monitor the progress monitor
*/
public void receiveFile(ICVSStorage file, boolean isBinary, int responseType, IProgressMonitor monitor)
throws CVSException {
// check overrides
if (textTransferOverrideSet != null &&
textTransferOverrideSet.contains(file)) isBinary = false;
// update progress monitor
final String title = NLS.bind(CVSMessages.Session_receiving, (new Object[]{ Util.toTruncatedPath(file, localRoot, 3) }));
monitor.subTask(NLS.bind(CVSMessages.Session_transferNoSize, new String[] { title }));
// get the file size from the server
long size;
boolean compressed = false;
String sizeLine = null;
try {
sizeLine = readLine();
if (sizeLine.charAt(0) == 'z') {
compressed = true;
sizeLine = sizeLine.substring(1);
}
size = Long.parseLong(sizeLine, 10);
} catch (NumberFormatException e) {
// In some cases, the server will give us an error line here
if (sizeLine != null && sizeLine.startsWith("E")) { //$NON-NLS-1$
handleErrorLine(sizeLine.substring(1).trim(), org.eclipse.core.runtime.Status.OK_STATUS);
return;
} else {
IStatus status = new CVSStatus(IStatus.ERROR,CVSStatus.ERROR,CVSMessages.Session_badInt, e, localRoot);
throw new CVSException(status);
}
}
// create an input stream that spans the next 'size' bytes from the connection
InputStream in = new SizeConstrainedInputStream(connection.getInputStream(), size, true /*discardOnClose*/);
// setup progress monitoring
in = new ProgressMonitorInputStream(in, size, TRANSFER_PROGRESS_INCREMENT, monitor) {
protected void updateMonitor(long bytesRead, long bytesTotal, IProgressMonitor monitor) {
if (bytesRead == 0) return;
monitor.subTask(NLS.bind(CVSMessages.Session_transfer, (new Object[] { title, Long.toString(bytesRead >> 10), Long.toString(bytesTotal >> 10) })));
}
};
// if compression enabled, decompress on the fly
if (compressed) {
try {
in = new GZIPInputStream(in);
} catch (IOException e) {
try {
in.close();
} catch (IOException e2) {
// Don't care. We're right about to report this IOException
}
throw CVSException.wrapException(e);
}
}
// if not binary, translate line delimiters on the fly
if (! isBinary) {
// switch from LF to CRLF if appropriate
if (IS_CRLF_PLATFORM && CVSProviderPlugin.getPlugin().isUsePlatformLineend()) {
// auto-correct for CRLF line-ends that come from the server
in = new CRLFtoLFInputStream(in);
// convert LF to CRLF
in = new LFtoCRLFInputStream(in);
} else {
// be nice and warn about text files that contain CRLF
in = new CRLFDetectInputStream(in, file);
}
}
// write the file locally
file.setContents(in, responseType, true, new NullProgressMonitor());
}
/**
* 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$
}
public boolean isOutputToConsole() {
return outputToConsole;
}
/**
* Stores a flag as to whether .# files will be created. (Default is true)
* @param createBackups if true, creates .# files at the server's request
*/
void setCreateBackups(boolean createBackups) {
this.createBackups = createBackups;
}
/**
* Returns a flag as to whether .# files will be created.
*/
boolean isCreateBackups() {
return createBackups;
}
/**
* Gets the sendFileTitleKey.
* @return Returns a String
*/
String getSendFileTitleMessage() {
if (sendFileTitleMessage == null)
return CVSMessages.Session_sending;
return sendFileTitleMessage;
}
/**
* Sets the sendFileTitleKey.
* @param sendFileTitleKey The sendFileTitleKey to set
*/
public void setSendFileTitleKey(String sendFileTitleMessage) {
this.sendFileTitleMessage = sendFileTitleMessage;
}
/**
* Remembers a set of files that must be transferred as 'text'
* regardless of what the isBinary parameter to sendFile() is.
*
* @param textTransferOverrideSet the set of ICVSFiles to override, or null if none
*/
public void setTextTransferOverride(Collection textTransferOverrideSet) {
this.textTransferOverrideSet = textTransferOverrideSet;
}
/**
* Filter the provided global options using parameters set on this session
* or globally. The session may add global options that correspond to user
* preferences or remove those that contradict requirements for this
* particular session.
*
* @param globalOptions the global options, read-only
* @return the filtered global options
*/
protected GlobalOption[] filterGlobalOptions(GlobalOption[] globalOptions) {
if (! Command.DO_NOT_CHANGE.isElementOf(globalOptions)) {
// Get the user preference for verbosity
QuietOption quietOption = CVSProviderPlugin.getPlugin().getQuietness();
if (quietOption != null) {
globalOptions = quietOption.addToEnd(globalOptions);
}
// Get the user preference for read-only
if (isWatchEditEnabled()) {
if (!Command.MAKE_READ_ONLY.isElementOf(globalOptions)) {
globalOptions = Command.MAKE_READ_ONLY.addToEnd(globalOptions);
}
}
}
return globalOptions;
}
private boolean isWatchEditEnabled() {
// First, look at the global preference
if (CVSProviderPlugin.getPlugin().getPluginPreferences().getBoolean(CVSProviderPlugin.READ_ONLY)) {
return true;
}
// If there is a provider, use the providers setting for watch/edit
try {
IResource resource = getLocalRoot().getIResource();
if (resource != null && resource.getType() != IResource.ROOT) {
RepositoryProvider provider = RepositoryProvider.getProvider(resource.getProject(), CVSProviderPlugin.getTypeId());
if (provider != null) {
return ((CVSTeamProvider) provider).isWatchEditEnabled();
}
}
} catch (CVSException e) {
CVSProviderPlugin.log(e);
}
return false;
}
/**
* Method setIgnoringLocalChanges.
* @param b
*/
protected void setIgnoringLocalChanges(boolean b) {
ignoringLocalChanges = b;
}
/**
* Returns the ignoringLocalChanges.
* @return boolean
*/
protected boolean isIgnoringLocalChanges() {
return ignoringLocalChanges;
}
/*
* Get the response handler map to be used for this session. The map is created by making a copy of the global
* reponse handler map.
*/
protected Map getReponseHandlers() {
if (responseHandlers == null) {
responseHandlers = Request.getReponseHandlerMap();
}
return responseHandlers;
}
/*
* Makes a list of all valid responses; for initializing a session.
* @return a space-delimited list of all valid response strings
*/
private String makeResponseList() {
StringBuffer result = new StringBuffer("ok error M E"); //$NON-NLS-1$
Iterator elements = getReponseHandlers().keySet().iterator();
while (elements.hasNext()) {
result.append(' ');
result.append((String) elements.next());
}
return result.toString();
}
public void registerResponseHandler(ResponseHandler handler) {
getReponseHandlers().put(handler.getResponseID(), handler);
}
public void removeResponseHandler(String responseID) {
getReponseHandlers().remove(responseID);
}
public ResponseHandler getResponseHandler(String responseID) {
return (ResponseHandler)getReponseHandlers().get(responseID);
}
/**
* Accumulate the added errors so they can be included in the status returned
* when the command execution is finished. OK status are ignored.
* @param status the status to be accumulated
*/
public void addError(IStatus status) {
if (!status.isOK())
errors.add(status);
}
public boolean hasErrors() {
return !errors.isEmpty();
}
public IStatus[] getErrors() {
return (IStatus[]) errors.toArray(new IStatus[errors.size()]);
}
public void clearErrors() {
errors.clear();
}
public void setCurrentCommand(Command c) {
currentCommand = c;
}
public Command getCurrentCommand() {
return currentCommand;
}
/**
* Report the given error line to any listeners
* @param line the error line
* @param status the status that indicates any problems encountered parsing the line
*/
public void handleErrorLine(String line, IStatus status) {
ConsoleListeners.getInstance().errorLineReceived(this, line, status);
}
/**
* An error has occurred while processing responses from the
* server. Place this error is the status that will be returned
* from the command and show the error in the console
* @param status the status that descibes the error
*/
public void handleResponseError(IStatus status) {
addError(status);
handleErrorLine(NLS.bind(CVSMessages.Session_0, new String[] { status.getMessage() }), status);
}
}