diff options
author | Michael Valenta | 2002-03-19 15:13:17 +0000 |
---|---|---|
committer | Michael Valenta | 2002-03-19 15:13:17 +0000 |
commit | 42f0ba306b8dfa5cf05828f2dad523fd1cd279f5 (patch) | |
tree | b9e758fa0467e83805671fb0edf8edc2d3a209fe | |
parent | 858209ad2172cae7bcd5d05719e5c43ed1457464 (diff) | |
download | eclipse.platform.team-branch-11185.tar.gz eclipse.platform.team-branch-11185.tar.xz eclipse.platform.team-branch-11185.zip |
Initial work for bug 11185branch-11185
6 files changed, 256 insertions, 85 deletions
diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/ICVSFile.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/ICVSFile.java index b6be92849..ec87121e4 100644 --- a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/ICVSFile.java +++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/ICVSFile.java @@ -38,10 +38,15 @@ public interface ICVSFile extends ICVSResource { * It is the responsibility of the caller to close the stream when finished. */ InputStream getInputStream() throws CVSException; - - /** - * Gets an output stream for writing to the file. + + /** + * Gets an appending output stream for writing to the file. * It is the responsibility of the caller to close the stream when finished. + */ + OutputStream getAppendingOutputStream() throws CVSException; + + /** + * Set the contents of the file to the contents of the provided input stream * * @param responseType the type of reponse that was received from the server * @@ -49,14 +54,8 @@ public interface ICVSFile extends ICVSResource { * MERGED - merging remote changes with local changes. Failure could result in loss of local changes * CREATED - contents for a file that doesn't exist locally * UPDATE_EXISTING - Replacing a local file with no local changes with remote changes. - */ - OutputStream getOutputStream(int responseType, boolean keepLocalHistory) throws CVSException; - - /** - * Gets an appending output stream for writing to the file. - * It is the responsibility of the caller to close the stream when finished. - */ - OutputStream getAppendingOutputStream() throws CVSException; + */ + public void setContents(InputStream stream, int responseType, boolean keepLocalHistory, IProgressMonitor monitor) throws CVSException; /** * Sets the file's read-only permission. diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/client/FileInputStreamWrapper.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/client/FileInputStreamWrapper.java new file mode 100644 index 000000000..c7aa32cc9 --- /dev/null +++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/client/FileInputStreamWrapper.java @@ -0,0 +1,188 @@ +package org.eclipse.team.internal.ccvs.core.client; + +/* + * (c) Copyright IBM Corp. 2000, 2002. + * All Rights Reserved. + */ + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.team.internal.ccvs.core.CVSException; +import org.eclipse.team.internal.ccvs.core.Policy; + +/** + * This class can be used to transfer a file from the CVS server to a local IFile + */ +public class FileInputStreamWrapper { + + // 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 int 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 DONT_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 InputStream input; + private long fileSize; + private int totalRead; + private boolean isBinary; + private IProgressMonitor monitor; + private byte[] buffer; + private int nextProgressThresh; + + private static final byte[] BUFFER = new byte[TRANSFER_BUFFER_SIZE / 2]; + private static final byte[] EXPANSION_BUFFER = new byte[TRANSFER_BUFFER_SIZE]; + + private int position; + private int bufferLength; + private String title; + + public FileInputStreamWrapper(InputStream input, long fileSize, boolean isBinary, String title, IProgressMonitor monitor) { + this.input = input; + this.fileSize = fileSize; + this.totalRead = 0; + this.isBinary = isBinary; + this.monitor = monitor; + this.buffer = BUFFER; + this.nextProgressThresh = TRANSFER_PROGRESS_INCREMENT; + this.title = title; + } + + public class InputStreamFromServer extends InputStream { + public int read() throws IOException { + if (position >= bufferLength) { + if (fill() == -1) + return -1; + } + return buffer[position++]; + } + public int read(byte[] bytes) throws IOException { + return read(bytes, 0, bytes.length); + } + public int read(byte[] bytes, int offset, int length) throws IOException { + if (position >= bufferLength) { + if (fill() == -1) + return -1; + } + length = Math.min(bufferLength - position, length); + System.arraycopy(buffer, position, bytes, offset, length); + position += length; + return length; + } + } + + /** + * Return a stream that can be passed to IFile#setContent() + * After the call to setContent, the receiver's input stream will be at the byte + * after the received file. + */ + public InputStream getInputStream() { + return new InputStreamFromServer(); + } + + /* + * 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 int fill() throws IOException { + + // Check if we've read the entire file + if (totalRead == fileSize) { + return -1; + } else if (position < bufferLength) { + return bufferLength - position; + } + + position = 0; + + // If we're not converting, use the big buffer to read + if (isBinary || DONT_CONVERT_NEWLINES) { + buffer = EXPANSION_BUFFER; + } else { + buffer = BUFFER; + } + + bufferLength = input.read(buffer, 0, (int) Math.min(buffer.length, fileSize - totalRead)); + if (bufferLength == -1) { + // Unexpected end of stream + throw new IOException(Policy.bind("Session.readError")); //$NON-NLS-1$ + } + totalRead += bufferLength; + + if (isBinary || DONT_CONVERT_NEWLINES) { + return bufferLength; + } + + bufferLength = convertNewLines(BUFFER, EXPANSION_BUFFER, bufferLength); + buffer = EXPANSION_BUFFER; + + // update progress monitor + if (totalRead > nextProgressThresh) { + monitor.subTask(Policy.bind("Session.transfer", //$NON-NLS-1$ + new Object[] { title, new Long(totalRead / 1024), new Long(fileSize / 1024)})); + nextProgressThresh = totalRead + TRANSFER_PROGRESS_INCREMENT; + } + + return bufferLength; + } + + /* + * Copy the bytes from the source to the target, converting any LF to the platform newline byte. + * + * There is special handling that will skip incoming CRs that precede LF. + */ + private int convertNewLines(byte[] source, byte[] target, int length) { + boolean seenCR = false; + int targetPosition = 0; + for (int sourcePosition = 0; sourcePosition < length; ++sourcePosition) { + final byte b = source[sourcePosition]; + if (b == CARRIAGE_RETURN_BYTE) { + // We keep track of CRs to perform autocorrection for improperly stored text files + seenCR = true; + } else { + if (b == SERVER_NEWLINE_BYTE) { + // if fixCRLF we ignore previous CR (if there was one) + // replace newlineIn with newlineOut + for (int x = 0; x < PLATFORM_NEWLINE_BYTES.length; ++x) target[targetPosition++] = PLATFORM_NEWLINE_BYTES[x]; + } else { + if (seenCR) target[targetPosition++] = CARRIAGE_RETURN_BYTE; // preserve stray CR's + target[targetPosition++] = b; + } + seenCR = false; + } + } + if (seenCR) target[targetPosition++] = CARRIAGE_RETURN_BYTE; + + return targetPosition; + } +} diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/client/Session.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/client/Session.java index 7541bd044..9c2aa41d9 100644 --- a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/client/Session.java +++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/client/Session.java @@ -625,18 +625,9 @@ public class Session { } 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(responseType, true); - 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); - } - } + // Set the contents of the file using the stream wrapper + FileInputStreamWrapper wrapper = new FileInputStreamWrapper(connection.getInputStream(), size, isBinary, title, monitor); + file.setContents(wrapper.getInputStream(), responseType, true, monitor); } /** diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/EclipseFile.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/EclipseFile.java index c541f3538..e89fecbde 100644 --- a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/EclipseFile.java +++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/EclipseFile.java @@ -38,6 +38,8 @@ class EclipseFile extends EclipseResource implements ICVSFile { private static final String TEMP_FILE_EXTENSION = ".tmp";//$NON-NLS-1$ + private static final IPath PROJECT_META_DATA_PATH = new Path(".project"); + /** * Create a handle based on the given local resource. */ @@ -68,40 +70,6 @@ class EclipseFile extends EclipseResource implements ICVSFile { } } - public OutputStream getOutputStream(final int responseType, final boolean keepLocalHistory) throws CVSException { - return new ByteArrayOutputStream() { - public void close() throws IOException { - try { - IFile file = getIFile(); - if (responseType == CREATED || (responseType == UPDATED && ! resource.exists())) { - if (resource.exists()) { - // Special handling for the .project meta-file - // XXX This behavior should be restricted to the meta file! - file.setContents(new ByteArrayInputStream(toByteArray()), true /*force*/, true /*keep history*/, null); - } else { - file.create(new ByteArrayInputStream(toByteArray()), false /*force*/, null); - } - } else if(responseType == UPDATE_EXISTING) { - file.setContents(new ByteArrayInputStream(toByteArray()), false /*force*/, keepLocalHistory /*keep history*/, null); - } else { - - file.setContents(new ByteArrayInputStream(toByteArray()), false /*force*/, keepLocalHistory /*keep history*/, null); - -// // Ensure we don't leave the file in a partially written state -// IFile tempFile = file.getParent().getFile(new Path(file.getName() + TEMP_FILE_EXTENSION)); -// tempFile.create(new ByteArrayInputStream(toByteArray()), true /*force*/, null); -// file.delete(false, true, null); -// tempFile.move(new Path(file.getName()), true, true, null); - } - } catch(CoreException e) { - throw new IOException(Policy.bind("EclipseFile_Problem_creating_resource", e.getMessage())); //$NON-NLS-1$ //$NON-NLS-2$ - } finally { - super.close(); - } - } - }; - } - /* * @see ICVSFile#getAppendingOutputStream() */ @@ -214,7 +182,40 @@ class EclipseFile extends EclipseResource implements ICVSFile { public String getRemoteLocation(ICVSFolder stopSearching) throws CVSException { return getParent().getRemoteLocation(stopSearching) + SEPARATOR + getName(); } + + /* + * @see ICVSFile#setReadOnly() + */ + public void setContents(InputStream stream, int responseType, boolean keepLocalHistory, IProgressMonitor monitor) throws CVSException { + try { + IFile file = getIFile(); + if (responseType == CREATED || (responseType == UPDATED && ! resource.exists())) { + if (resource.exists()) { + if (PROJECT_META_DATA_PATH.equals(file.getFullPath().removeFirstSegments(1))) { + // Special handling for the .project meta-file + file.setContents(stream, true /*force*/, true /*keep history*/, monitor); + } else { + throw new CVSException("File " + file.getFullPath() + " already exists."); + } + } else { + file.create(stream, false /*force*/, null); + } + } else if(responseType == UPDATE_EXISTING) { + file.setContents(stream, false /*force*/, keepLocalHistory /*keep history*/, monitor); + } else { + file.setContents(stream, false /*force*/, keepLocalHistory /*keep history*/, monitor); + // // Ensure we don't leave the file in a partially written state + // IFile tempFile = file.getParent().getFile(new Path(file.getName() + TEMP_FILE_EXTENSION)); + // tempFile.create(new ByteArrayInputStream(toByteArray()), true /*force*/, null); + // file.delete(false, true, null); + // tempFile.move(new Path(file.getName()), true, true, null); + } + } catch(CoreException e) { + throw new CVSException(Policy.bind("EclipseFile_Problem_creating_resource"), e); //$NON-NLS-1$ + } + } + /* * @see ICVSFile#setReadOnly() */ diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/RemoteFile.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/RemoteFile.java index 4eb3b5ed4..094117225 100644 --- a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/RemoteFile.java +++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/resources/RemoteFile.java @@ -89,17 +89,7 @@ public class RemoteFile extends RemoteResource implements ICVSRemoteFile, ICVSFi // use the contents of the file on disk so that the server can calculate the relative // sync state. This is a trick to allow the server to calculate sync state for us. InputStream is = managed.getInputStream(); - OutputStream os = file.getOutputStream(ICVSFile.UPDATED, false); - try { - FileUtil.transfer(is, os); - } catch(IOException e) { - } finally { - try { - os.close(); - is.close(); - } catch(IOException e) { - } - } + file.setContents(is, ICVSFile.UPDATED, false, Policy.monitorFor(null)); parent.setChildren(new ICVSRemoteResource[] {file}); if( ! file.updateRevision(tag, monitor)) { @@ -313,20 +303,24 @@ public class RemoteFile extends RemoteResource implements ICVSRemoteFile, ICVSFi public InputStream getInputStream() throws CVSException { return new ByteArrayInputStream(contents == null ? new byte[0] : contents); } - + /* - * @see ICVSFile#getOutputStream() + * @see ICVSFile#setReadOnly() */ - public OutputStream getOutputStream(int responseType, boolean keepLocalHistory) throws CVSException { - // stores the contents of the file when the stream is closed - // could perhaps be optimized in some manner to avoid excessive array copying - return new ByteArrayOutputStream() { - public void close() throws IOException { - contents = toByteArray(); - super.close(); + public void setContents(InputStream stream, int responseType, boolean keepLocalHistory, IProgressMonitor monitor) throws CVSException { + try { + byte[] buffer = new byte[1024]; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int read; + while ((read = stream.read(buffer)) >= 0) { + Policy.checkCanceled(monitor); + out.write(buffer, 0, read); } - }; - } + contents = out.toByteArray(); + } catch(IOException e) { + throw new CVSException(Policy.bind("")); //$NON-NLS-1$ //$NON-NLS-2$ + } + } public void setReadOnly(boolean readOnly) throws CVSException { } diff --git a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/util/SyncFileWriter.java b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/util/SyncFileWriter.java index 3142fa235..19cb87e24 100644 --- a/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/util/SyncFileWriter.java +++ b/bundles/org.eclipse.team.cvs.core/src/org/eclipse/team/internal/ccvs/core/util/SyncFileWriter.java @@ -7,6 +7,8 @@ package org.eclipse.team.internal.ccvs.core.util; import java.io.BufferedOutputStream; import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; @@ -15,14 +17,9 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; -import org.eclipse.core.resources.IContainer; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Path; import org.eclipse.team.ccvs.core.CVSTag; import org.eclipse.team.ccvs.core.ICVSFile; import org.eclipse.team.ccvs.core.ICVSFolder; -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.resources.CVSEntryLineTag; @@ -283,8 +280,9 @@ public class SyncFileWriter { * compatibility with other CVS clients. */ private static void writeLines(ICVSFile file, String[] contents) throws CVSException { - OutputStream os = new BufferedOutputStream(file.getOutputStream(ICVSFile.UPDATED, false)); + ByteArrayOutputStream os = new ByteArrayOutputStream(); writeLinesToStreamAndClose(os, contents); + file.setContents(new ByteArrayInputStream(os.toByteArray()), ICVSFile.UPDATED, false, Policy.monitorFor(null)); } private static void writeLinesToStreamAndClose(OutputStream os, String[] contents) |