diff --git a/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java b/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java new file mode 100644 index 000000000..b22649b00 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java @@ -0,0 +1,41 @@ +package ch.eitchnet.utils.io; + +/** + *

+ * This interface defines an API for use in situations where long running jobs notify observers of the jobs status. The + * jobs has a size which is a primitive long value e.g. the number of bytes parsed/ to be parsed in a file + *

+ * + * @author Robert von Burg + */ +public interface FileProgressListener { + + /** + * Notify the listener that the progress has begun + * + * @param size + * the size of the job which is to be accomplished + */ + public void begin(long size); + + /** + * Notifies the listener of incremental progress + * + * @param percent + * percent completed + * @param position + * the position relative to the job size + */ + public void progress(int percent, long position); + + /** + * Notifies the listener that the progress is completed + * + * @param percent + * the percent completed. Ideally the value would be 100, but in cases of errors it can be less + * @param position + * the position where the job finished. Ideally the value would be the same as the size given at + * {@link #begin(long)} but in case of errors it can be different + */ + public void end(int percent, long position); +} diff --git a/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java b/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java new file mode 100644 index 000000000..9ffc13a46 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java @@ -0,0 +1,81 @@ +package ch.eitchnet.utils.io; + +import java.text.MessageFormat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * File stream progress monitoring thread + * + * @author Robert von Burg + */ +public class FileStreamProgressWatcher implements Runnable { + + private static final Logger logger = LoggerFactory.getLogger(FileStreamProgressWatcher.class); + private ProgressableFileInputStream inputStream; + private boolean run = false; + private FileProgressListener progressListener; + private long millis; + + /** + * @param millis + * @param progressListener + * @param inputStream + */ + public FileStreamProgressWatcher(long millis, FileProgressListener progressListener, + ProgressableFileInputStream inputStream) { + this.millis = millis; + this.progressListener = progressListener; + this.inputStream = inputStream; + } + + @Override + public void run() { + this.run = true; + + this.progressListener.begin(this.inputStream.getFileSize()); + + while (this.run) { + try { + + int percentComplete = this.inputStream.getPercentComplete(); + + if (this.inputStream.isClosed()) { + logger.info(MessageFormat.format("Input Stream is closed at: {0}%", percentComplete)); //$NON-NLS-1$ + + this.run = false; + this.progressListener.end(percentComplete, this.inputStream.getBytesRead()); + + } else if (percentComplete < 100) { + + this.progressListener.progress(percentComplete, this.inputStream.getBytesRead()); + + } else if (percentComplete >= 100) { + + this.run = false; + this.progressListener.end(percentComplete, this.inputStream.getBytesRead()); + + } + + if (this.run) { + Thread.sleep(this.millis); + } + + } catch (InterruptedException e) { + + logger.info(MessageFormat.format("Work stopped: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + this.run = false; + int percentComplete = this.inputStream.getPercentComplete(); + this.progressListener.end(percentComplete, this.inputStream.getBytesRead()); + + } catch (Exception e) { + + logger.error(e.getMessage(), e); + this.run = false; + int percentComplete = this.inputStream.getPercentComplete(); + this.progressListener.end(percentComplete, Long.MAX_VALUE); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java b/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java new file mode 100644 index 000000000..4cafa25fd --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java @@ -0,0 +1,43 @@ +package ch.eitchnet.utils.io; + +import java.text.MessageFormat; + +import org.slf4j.Logger; + +import ch.eitchnet.utils.helper.FileHelper; + +/** + * @author Robert von Burg + */ +public class LoggingFileProgressListener implements FileProgressListener { + + private final Logger logger; + private final String name; + + /** + * @param logger + * @param name + */ + public LoggingFileProgressListener(Logger logger, String name) { + this.logger = logger; + this.name = name; + } + + @Override + public void begin(long size) { + String msg = "Starting to read {0} with a size of {1}"; //$NON-NLS-1$ + this.logger.info(MessageFormat.format(msg, this.name, FileHelper.humanizeFileSize(size))); + } + + @Override + public void progress(int percent, long position) { + String msg = "Read {0}% of {1} at position {2}"; //$NON-NLS-1$ + this.logger.info(MessageFormat.format(msg, percent, this.name, FileHelper.humanizeFileSize(position))); + } + + @Override + public void end(int percent, long position) { + String msg = "Finished reading {0} at position {1} ({2}%)"; //$NON-NLS-1$ + this.logger.info(MessageFormat.format(msg, this.name, percent, FileHelper.humanizeFileSize(position))); + } +} diff --git a/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java b/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java new file mode 100644 index 000000000..94890cc72 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java @@ -0,0 +1,146 @@ +package ch.eitchnet.utils.io; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +/** + *

+ * This sub class of {@link FileInputStream} allows to follow the currently read bytes of a {@link File}. In conjunction + * with a {@link Thread} and a {@link FileProgressListener} it is possible to track the progress of a long running job on + * bigger files + *

+ * + * @author Robert von Burg + */ +public class ProgressableFileInputStream extends FileInputStream { + + private long fileSize; + private long bytesRead; + private boolean closed; + + /** + * Constructs a normal {@link FileInputStream} with the given {@link File} + * + * @param file + * the file to read + * @throws FileNotFoundException + * thrown if the {@link File} does not exist + */ + public ProgressableFileInputStream(File file) throws FileNotFoundException { + super(file); + this.fileSize = file.length(); + } + + /** + * @see java.io.FileInputStream#read() + */ + @Override + public int read() throws IOException { + synchronized (this) { + this.bytesRead++; + } + return super.read(); + } + + /** + * @see java.io.FileInputStream#read(byte[], int, int) + */ + @Override + public int read(byte[] b, int off, int len) throws IOException { + int read = super.read(b, off, len); + if (read != -1) { + synchronized (this) { + this.bytesRead += read; + } + } + return read; + } + + /** + * @see java.io.FileInputStream#read(byte[]) + */ + @Override + public int read(byte[] b) throws IOException { + int read = super.read(b); + if (read != -1) { + synchronized (this) { + this.bytesRead += read; + } + } + return read; + } + + /** + * @see java.io.FileInputStream#skip(long) + */ + @Override + public long skip(long n) throws IOException { + long skip = super.skip(n); + if (skip != -1) { + synchronized (this) { + this.bytesRead += skip; + } + } + return skip; + } + + /** + * @see java.io.FileInputStream#close() + */ + @Override + public void close() throws IOException { + this.closed = true; + super.close(); + } + + /** + * Returns the size of the file being read + * + * @return the size of the file being read + */ + public long getFileSize() { + return this.fileSize; + } + + /** + * Returns the number of bytes already read + * + * @return the number of bytes already read + */ + public long getBytesRead() { + synchronized (this) { + if (this.bytesRead > this.fileSize) + this.bytesRead = this.fileSize; + return this.bytesRead; + } + } + + /** + * Returns the percent read of the file + * + * @return the percentage complete of the process + */ + public int getPercentComplete() { + + long currentRead; + synchronized (this) { + if (this.bytesRead > this.fileSize) + this.bytesRead = this.fileSize; + currentRead = this.bytesRead; + } + + double read = (100.0d / this.fileSize * currentRead); + return (int) read; + } + + /** + * Returns true if {@link #close()} was called, false otherwise + * + * @return the closed + */ + public boolean isClosed() { + return this.closed; + } +}