/*
* Copyright (C) 2008 Josh Guilfoyle <jasta@devtcg.org>
*
* This program 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 2, or (at your option) any
* later version.
*
* This program 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.
*/
package org.devtcg.five.util.streaming;
import java.io.EOFException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import org.devtcg.five.util.streaming.StreamMediaPlayer.RandomAccessStream;
import android.util.Log;
/**
* Simple access stream to "tail" a changing file on disk.
*/
public class TailStream extends RandomAccessStream
{
public static final String TAG = "TailStream";
protected final String mPath;
protected final String mMimeType;
protected long mLength = -1;
private FileChannel mChannel;
private long mRemaining = 0;
protected TailStream(String path, String mimeType)
{
mPath = path;
mMimeType = mimeType;
}
public TailStream(String path, String mimeType, long length)
{
this(path, mimeType);
if (length <= 0)
throw new IllegalArgumentException("Length must be positive");
setLength(length);
}
public RandomAccessStream newInstance()
{
return new TailStream(mPath, mMimeType, mLength);
}
protected void setLength(long length)
{
mLength = length;
mRemaining = length;
}
@Override
public void abort()
{
/* XXX: We need to call abort as well, but there is no way for
* us to get the Thread handle that we're running under. */
try {
mChannel.close();
} catch (IOException e) {
Log.e(TAG, "Error during abort", e);
}
}
@Override
public String getContentType()
{
return mMimeType;
}
@Override
public void open() throws IOException
{
mChannel = (new RandomAccessFile(mPath, "r")).getChannel();
}
@Override
public void close() throws IOException
{
mChannel.close();
mChannel = null;
}
@Override
public void seek(long pos) throws IOException
{
mChannel.position(pos);
mRemaining = mLength - pos;
}
@Override
public long size()
throws IllegalStateException
{
if (mLength < 0)
throw new IllegalStateException("Length not set after open()");
return mLength;
}
@Override
public int read() throws IOException
{
throw new RuntimeException("Don't invoke this method.");
}
private void waitForData() throws IOException
{
long pos = mLength - mRemaining;
// Log.d(TAG, "Waiting for more data from " + mPath);
long now = System.currentTimeMillis();
Thread self = Thread.currentThread();
while (pos >= mChannel.size() && self.isInterrupted() == false)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new EOFException("Aborted stream");
}
}
long elapsed = System.currentTimeMillis() - now;
// Log.d(TAG, "Waited " + elapsed + " milliseconds.");
if (self.isInterrupted() == true)
throw new EOFException("Aborted stream");
}
@Override
public int read(byte[] b, int offs, int len)
throws IOException
{
if (mRemaining == 0)
return -1;
if (len > mRemaining)
len = (int)mRemaining;
ByteBuffer buf = ByteBuffer.wrap(b, offs, len);
int n = mChannel.read(buf);
assert mRemaining >= n;
if (n >= 0)
{
mRemaining -= n;
return n;
}
else
{
waitForData();
/* Next read will find data... */
return 0;
}
}
}