/*
* Created on Feb 12, 2009
* Created by Paul Gardner
*
* Copyright 2009 Vuze, Inc. All rights reserved.
*
* 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; version 2 of the License only.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
package com.aelitis.azureus.core.devices.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.gudy.azureus2.core3.util.AEThread2;
import org.gudy.azureus2.core3.util.Average;
import org.gudy.azureus2.core3.util.Debug;
public abstract class
TranscodePipe
{
private final int BUFFER_SIZE = 128*1024;
private final int BUFFER_CACHE_SIZE = BUFFER_SIZE*3;
protected volatile boolean paused;
protected volatile boolean destroyed;
protected volatile int bytes_available;
protected volatile int max_bytes_per_sec;
protected List<Socket> sockets = new ArrayList<Socket>();
private ServerSocket server_socket;
private AEThread2 refiller;
private LinkedList<bufferCache> buffer_cache = new LinkedList<bufferCache>();
private int buffer_cache_size;
private Average connection_speed = Average.getInstance(1000, 10); //average over 10s, update every 1000ms
private Average write_speed = Average.getInstance(1000, 10); //average over 10s, update every 1000ms
private errorListener error_listener;
protected
TranscodePipe(
errorListener _error_listener )
throws IOException
{
error_listener = _error_listener;
server_socket = new ServerSocket( 0, 50, InetAddress.getByName( "127.0.0.1" ));
new AEThread2( "TranscodePipe", true )
{
public void
run()
{
while( !destroyed ){
try{
final Socket socket = server_socket.accept();
connection_speed.addValue( 1 );
new AEThread2( "TranscodePipe", true )
{
public void
run()
{
handleSocket( socket );
}
}.start();
}catch( Throwable e ){
if ( !destroyed ){
destroy();
}
break;
}
}
}
}.start();
}
public long
getConnectionRate()
{
return( connection_speed.getAverage());
}
public long
getWriteSpeed()
{
return(write_speed.getAverage());
}
protected abstract void
handleSocket(
Socket socket );
protected void
handlePipe(
final InputStream is,
final OutputStream os )
{
new AEThread2( "TranscodePipe:c", true )
{
public void
run()
{
final int BUFFER_SIZE = 128*1024;
byte[] buffer = new byte[ BUFFER_SIZE ];
while( !destroyed ){
try{
int limit;
if ( paused ){
Thread.sleep(250);
limit = 1;
}else{
if ( max_bytes_per_sec > 0 ){
limit = bytes_available;
if ( limit <= 0 ){
Thread.sleep( 25 );
continue;
}
limit = Math.min( BUFFER_SIZE, limit );
}else{
limit = BUFFER_SIZE;
}
}
int len = is.read( buffer, 0, limit );
if ( len <= 0 ){
break;
}
if ( max_bytes_per_sec > 0 ){
bytes_available -= len;
}
os.write( buffer, 0, len );
write_speed.addValue( len );
}catch( Throwable e ){
break;
}
}
try{
os.flush();
}catch( Throwable e ){
}
try{
is.close();
}catch( Throwable e ){
}
try{
os.close();
}catch( Throwable e ){
}
}
}.start();
}
protected RandomAccessFile
reserveRAF()
throws IOException
{
throw( new IOException( "Not implemented" ));
}
protected void
releaseRAF(
RandomAccessFile raf )
{
}
protected void
handleRAF(
final OutputStream os,
final long position,
final long length )
{
new AEThread2( "TranscodePipe:c", true )
{
public void
run()
{
RandomAccessFile raf = null;
try{
raf = reserveRAF();
long pos = position;
long rem = length;
while( !destroyed && rem > 0){
int limit;
if ( paused ){
Thread.sleep(250);
limit = 1;
}else{
if ( max_bytes_per_sec > 0 ){
limit = bytes_available;
if ( limit <= 0 ){
Thread.sleep( 25 );
continue;
}
limit = Math.min( BUFFER_SIZE, limit );
}else{
limit = BUFFER_SIZE;
}
limit = (int)Math.min( rem, limit );
}
int read_length = 0;
int buffer_start = 0;
byte[] buffer = null;
synchronized( TranscodePipe.this ){
int c_num = 0;
Iterator<bufferCache> it = buffer_cache.iterator();
while( it.hasNext()){
bufferCache b = it.next();
long rel_offset = pos - b.offset;
if ( rel_offset >= 0 ){
byte[] data = b.data;
long avail = data.length - rel_offset;
if ( avail > 0 ){
read_length = (int)Math.min( avail, limit );
buffer = data;
buffer_start = (int)rel_offset;
//System.out.println( "using cache entry: o_offset=" + pos + ",r_offset=" + rel_offset+",len=" + read_length );
if ( c_num > 0 ){
it.remove();
buffer_cache.addFirst( b );
}
break;
}
}
c_num++;
}
if ( buffer == null ){
buffer = new byte[ limit ];
raf.seek( pos );
read_length = raf.read( buffer );
if ( read_length != limit ){
Debug.out( "eh?");
throw( new IOException( "Inconsistent" ));
}
bufferCache b = new bufferCache( pos, buffer );
// System.out.println( "adding to cache: o_offset=" + pos + ", size=" + limit );
buffer_cache.addFirst( b );
buffer_cache_size += limit;
while( buffer_cache_size > BUFFER_CACHE_SIZE ){
b = buffer_cache.removeLast();
buffer_cache_size -= b.data.length;
}
}
}
if ( read_length <= 0 ){
break;
}
rem -= read_length;
pos += read_length;
if ( max_bytes_per_sec > 0 ){
bytes_available -= read_length;
}
os.write( buffer, buffer_start, read_length );
write_speed.addValue( read_length );
}
os.flush();
}catch( Throwable e ){
if ( raf != null ){
try{
synchronized( TranscodePipe.this ){
raf.seek( 0 );
raf.read( new byte[1] );
}
}catch( Throwable f ){
reportError( e );
}
}
}finally{
try{
os.close();
}catch( Throwable e ){
}
if ( raf != null ){
releaseRAF( raf );
}
}
}
}.start();
}
protected void
pause()
{
paused = true;
}
protected void
resume()
{
paused = false;
}
public void
setMaxBytesPerSecond(
int max )
{
if ( max == max_bytes_per_sec ){
return;
}
max_bytes_per_sec = max;
synchronized( this ){
if ( refiller == null ){
refiller =
new AEThread2( "refiller", true )
{
public void
run()
{
int count = 0;
while( !destroyed ){
if ( max_bytes_per_sec == 0 ){
synchronized( this ){
if ( max_bytes_per_sec == 0 ){
refiller = null;
break;
}
}
}
count++;
bytes_available += max_bytes_per_sec/10;
if ( count%10 == 0 ){
bytes_available += max_bytes_per_sec%10;
}
try{
Thread.sleep(100);
}catch( Throwable e ){
Debug.printStackTrace(e);
break;
}
}
}
};
refiller.start();
}
}
}
protected int
getPort()
{
return( server_socket.getLocalPort());
}
protected boolean
destroy()
{
synchronized( this ){
if ( destroyed ){
return( false );
}
destroyed = true;
}
for (Socket s: sockets ){
try{
s.close();
}catch( Throwable e ){
}
}
sockets.clear();
try{
server_socket.close();
}catch( Throwable e ){
Debug.printStackTrace(e);
}
return( true );
}
protected void
reportError(
Throwable error )
{
if ( error_listener != null ){
error_listener.error( error );
}else{
Debug.out( error );
}
}
private class
bufferCache
{
private long offset;
private byte[] data;
protected
bufferCache(
long _offset,
byte[] _data )
{
offset = _offset;
data = _data;
}
}
protected interface
errorListener
{
public void
error(
Throwable e );
}
}