/*
* ====================================================================
* Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://svnkit.com/license.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
* ====================================================================
*/
package org.tmatesoft.svn.core.internal.io.dav.http;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.LinkedList;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.util.SVNDebugLog;
import org.tmatesoft.svn.util.SVNLogType;
/**
* @version 1.3
* @author TMate Software Ltd.
*/
public class SpoolFile {
private static final long LIMIT = 1024*1024*512; // 512MB
private static final long MEMORY_TRESHOLD = 1024*100; // 100KB
private File myDirectory;
private LinkedList<File> myFiles;
private ByteArrayOutputStream myBuffer;
public SpoolFile(File directory) {
myDirectory = directory;
myFiles = new LinkedList<File>();
myBuffer = new ByteArrayOutputStream();
}
public OutputStream openForWriting() {
return new SpoolOutputStream();
}
public InputStream openForReading() {
return new SpoolInputStream();
}
public void delete() throws SVNException {
for (Iterator<File> files = myFiles.iterator(); files.hasNext();) {
final File file = files.next();
SVNFileUtil.deleteFile(file);
}
myBuffer = null;
}
private class SpoolInputStream extends InputStream {
private File myCurrentFile;
private long myCurrentSize;
private int myBufferOffset;
private InputStream myCurrentInput;
public int read() throws IOException {
byte[] buffer = new byte[1];
int read = read(buffer);
if (read != 1) {
return -1;
}
return buffer[0] & 0xFF;
}
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
public int read(byte[] b, int off, int len) throws IOException {
if (myBuffer != null) {
int bufferSize = myBuffer.size() - myBufferOffset;
if (bufferSize <= 0) {
return -1;
}
int toRead = Math.min(bufferSize, len);
byte[] buffer = myBuffer.toByteArray();
System.arraycopy(buffer, myBufferOffset, b, off, toRead);
myBufferOffset += toRead;
return toRead;
}
int read = 0;
while(len - read > 0) {
if (myCurrentFile == null) {
if (myFiles.isEmpty()) {
SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK,
"FAILED TO READ SPOOLED RESPONSE FULLY (no more files): " + (read == 0 ? -1 : read));
return read == 0 ? -1 : read;
}
openNextFile();
}
int toRead = (int) Math.min(len - read, myCurrentSize);
int wasRead = myCurrentInput.read(b, off + read, toRead);
if (wasRead < 0) {
SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK,
"FAILED TO READ SPOOLED RESPONSE FULLY (cannot read more from the current file): " + (read == 0 ? -1 : read));
return read == 0 ? -1 : read;
}
read += wasRead;
myCurrentSize -= wasRead;
if (myCurrentSize == 0) {
SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "SPOOLED RESPONSE FULLY READ");
closeCurrentFile();
}
}
return read;
}
private void openNextFile() throws IOException {
myCurrentFile = (File) myFiles.removeFirst();
SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "READING SPOOLED FILE: " + myCurrentFile);
myCurrentSize = myCurrentFile.length();
SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "ABOUT TO READ: " + myCurrentSize);
try {
myCurrentInput = SVNFileUtil.openFileForReading(myCurrentFile, SVNLogType.NETWORK);
} catch (SVNException e) {
if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
}
throw new IOException(e.getMessage());
}
}
public long skip(long n) throws IOException {
if (myBuffer != null) {
int bufferSize = myBuffer.size() - myBufferOffset;
if (bufferSize <= 0) {
return 0;
}
long toSkip = Math.min(bufferSize, n);
myBufferOffset += (int) toSkip;
return toSkip;
}
int skipped = 0;
while(n - skipped > 0) {
if (myCurrentFile == null) {
if (myFiles.isEmpty()) {
return skipped == 0 ? -1 : skipped;
}
openNextFile();
}
long toSkip = Math.min(n - skipped, myCurrentSize);
long wasSkipped = myCurrentInput.skip(toSkip);
if (wasSkipped < 0) {
return skipped == 0 ? -1 : skipped;
}
skipped += wasSkipped;
myCurrentSize -= wasSkipped;
if (myCurrentSize == 0) {
closeCurrentFile();
}
}
return skipped;
}
private void closeCurrentFile() throws IOException {
try {
myCurrentInput.close();
} finally {
try {
SVNFileUtil.deleteFile(myCurrentFile);
} catch (SVNException e) {
}
myCurrentFile = null;
}
}
public void close() throws IOException {
if (myCurrentFile != null) {
closeCurrentFile();
}
myBuffer = null;
myBufferOffset = 0;
}
}
private class SpoolOutputStream extends OutputStream {
private OutputStream myCurrentOutput;
private long myCurrentSize;
public void write(int b) throws IOException {
write(new byte[] {(byte) (b & 0xFF)});
}
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) throws IOException {
if (myBuffer != null) {
myBuffer.write(b, off, len);
if (myBuffer.size() < MEMORY_TRESHOLD) {
return;
}
}
if (myCurrentOutput == null) {
// open first file.
File file = createNextFile();
SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "SPOOLING RESPONSE TO FILE: " + file);
myFiles.add(file);
try {
myCurrentOutput = SVNFileUtil.openFileForWriting(file);
} catch (SVNException e) {
if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
}
throw new IOException(e.getMessage());
}
}
if (myBuffer != null) {
myBuffer.close();
myBuffer.writeTo(myCurrentOutput);
myCurrentSize += myBuffer.size();
myBuffer = null;
} else {
myCurrentOutput.write(b, off, len);
myCurrentSize += len;
}
if (myCurrentSize >= LIMIT) {
close();
}
}
public void close() throws IOException {
if (myCurrentOutput != null) {
try {
myCurrentOutput.close();
SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "SPOOLED: " + myCurrentSize);
} finally {
myCurrentOutput = null;
}
}
myCurrentSize = 0;
}
public void flush() throws IOException {
if (myCurrentOutput != null) {
myCurrentOutput.flush();
}
}
private File createNextFile() throws IOException {
File file = File.createTempFile("svnkit.", ".spool", myDirectory);
file.createNewFile();
return file;
}
}
}