/*
This file is part of JFLICKS.
JFLICKS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
JFLICKS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with JFLICKS. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jflicks.tv.recorder;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import javax.swing.Timer;
import org.jflicks.job.JobEvent;
import org.jflicks.util.LogUtil;
/**
* This class has code to read from a file and have the notion of recovering
* when we appear to be blocking on a read. What seems to happen when
* reading from a video for linux device (the HD-PVR) it seems we can open
* a stream and then read - but something happens and the read ends up just
* blocking and not getting any data. It might be because the stream to the
* box has been disrupted in some way. One would think that the low level
* driver would not get confused - so we do this to try to work around this
* problem.
*
* @author Doug Barnum
* @version 1.0
*/
public abstract class RecoverJob extends BaseDeviceJob implements
ActionListener {
private static final int MAX_BLOCK_COUNT = 40;
private static final int TIMER_MILLIS = 10000;
private FileInputStream fileInputStream;
private FileChannel fileChannel;
private long currentRead;
private long lastRead;
/**
* Extensions need to do something with the data we have read.
*
* @param buffer An array of bytes.
* @param length The number of valid bytes.
*/
public abstract void process(byte[] buffer, int length);
/**
* Close any resources that we opened so data could have been processed.
*/
public abstract void close();
private int blockCount;
/**
* Simple no argument constructor.
*/
public RecoverJob() {
}
private int getBlockCount() {
return (blockCount);
}
private void setBlockCount(int i) {
blockCount = i;
}
/**
* We listen for our timer event to check to see if our read is blocking.
*
* @param event Time to check.
*/
public void actionPerformed(ActionEvent event) {
if (currentRead != 0L) {
if (lastRead == 0L) {
// First time through...
lastRead = currentRead;
} else if (currentRead == lastRead) {
// We have arrived here with the same read time as last.
// We are probably blocked...
LogUtil.log(LogUtil.DEBUG, "We are probably blocking..." + getDevice());
int bcount = getBlockCount();
bcount++;
setBlockCount(bcount);
LogUtil.log(LogUtil.DEBUG, "Times we failed on a block: " + bcount + " " + getDevice());
// We always want to close...
if (fileChannel != null) {
synchronized (fileChannel) {
try {
LogUtil.log(LogUtil.DEBUG, "Attempting to close! " + getDevice());
fileChannel.close();
LogUtil.log(LogUtil.DEBUG, "Close seemed to work. " + getDevice());
fileChannel = null;
} catch (Exception ex) {
LogUtil.log(LogUtil.WARNING, "exception on interupt close " + getDevice());
fileChannel = null;
}
}
} else {
LogUtil.log(LogUtil.DEBUG, "Can't close, fileChannel is null! " + getDevice());
}
if (bcount > MAX_BLOCK_COUNT) {
LogUtil.log(LogUtil.DEBUG, "Time to give up!! " + getDevice());
stop();
}
} else {
// All good reset our last read...
lastRead = currentRead;
}
}
}
private void reset() {
LogUtil.log(LogUtil.DEBUG, "We are trying to reset! " + getDevice());
try {
currentRead = 0L;
lastRead = 0L;
// Only start a new read and timer if we haven't
// reached our max retry count.
if (getBlockCount() < MAX_BLOCK_COUNT) {
LogUtil.log(LogUtil.DEBUG, "Getting new fileChannel..." + getDevice());
fileInputStream = new FileInputStream(getDevice());
fileChannel = fileInputStream.getChannel();
if (fileChannel != null) {
// Let's up the delay by a second - perhaps
// recovery will happen.
LogUtil.log(LogUtil.DEBUG, "Got new fileChannel." + getDevice());
} else {
// We tried to get a channel, it failed giveup.
LogUtil.log(LogUtil.DEBUG, "Failed new fileChannel." + getDevice());
setTerminate(true);
}
} else {
LogUtil.log(LogUtil.DEBUG, "Looks like time to quit." + getDevice());
setTerminate(true);
}
} catch (IOException ex) {
LogUtil.log(LogUtil.DEBUG, "Failed to re-open, perhaps next time."
+ getDevice());
}
}
/**
* {@inheritDoc}
*/
public void start() {
setTerminate(false);
}
/**
* {@inheritDoc}
*/
public void run() {
Timer timer = null;
ByteBuffer bb = ByteBuffer.allocate(1024);
byte[] buffer = bb.array();
try {
int count = 0;
fileInputStream = new FileInputStream(getDevice());
fileChannel = fileInputStream.getChannel();
currentRead = 0L;
lastRead = 0L;
timer = new Timer(TIMER_MILLIS, this);
timer.start();
while (!isTerminate()) {
try {
bb.rewind();
currentRead = System.currentTimeMillis();
count = fileChannel.read(bb);
} catch (AsynchronousCloseException ex) {
LogUtil.log(LogUtil.DEBUG, "We have been interupted!");
if (fileInputStream != null) {
fileInputStream.close();
}
timer.stop();
count = 0;
if (!isTerminate()) {
reset();
timer.restart();
}
} catch (ClosedChannelException ex) {
timer.stop();
count = 0;
if (!isTerminate()) {
reset();
timer.restart();
}
} catch (Exception ex) {
// Here is a catch-all. Hopefully we can still reset.
timer.stop();
count = 0;
if (!isTerminate()) {
reset();
timer.restart();
}
}
if (count > 0) {
process(buffer, count);
}
}
fileInputStream.close();
timer.stop();
close();
} catch (Exception ex) {
LogUtil.log(LogUtil.WARNING, "Exception RecoverJob: "
+ ex.getClass().getName() + " " + ex.getMessage()
+ " " + getDevice());
ex.printStackTrace();
timer.stop();
close();
}
fireJobEvent(JobEvent.COMPLETE);
}
/**
* {@inheritDoc}
*/
public void stop() {
LogUtil.log(LogUtil.DEBUG, "RecoverJob: We are told to terminate!");
setTerminate(true);
}
/**
* {@inheritDoc}
*/
public void jobUpdate(JobEvent event) {
if (event.getType() == JobEvent.COMPLETE) {
stop();
}
}
}