/*
* Created on 29-Mar-2006
* Created by Paul Gardner
* Copyright (C) 2006 Aelitis, 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; either version 2
* of the License, 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.
* 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.
*
* AELITIS, SAS au capital de 46,603.30 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*
*/
package org.gudy.azureus2.pluginsimpl.local.disk;
import java.util.*;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.disk.DiskManagerFileInfoListener;
import org.gudy.azureus2.core3.download.DownloadManagerPeerListener;
import org.gudy.azureus2.core3.peer.PEPeer;
import org.gudy.azureus2.core3.peer.PEPeerManager;
import org.gudy.azureus2.core3.peer.PEPiece;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.torrent.TOTorrentFile;
import org.gudy.azureus2.core3.util.AESemaphore;
import org.gudy.azureus2.core3.util.Average;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.DirectByteBuffer;
import org.gudy.azureus2.core3.util.SystemTime;
import org.gudy.azureus2.plugins.disk.DiskManagerChannel;
import org.gudy.azureus2.plugins.disk.DiskManagerEvent;
import org.gudy.azureus2.plugins.disk.DiskManagerFileInfo;
import org.gudy.azureus2.plugins.disk.DiskManagerListener;
import org.gudy.azureus2.plugins.disk.DiskManagerRequest;
import org.gudy.azureus2.plugins.download.Download;
import org.gudy.azureus2.plugins.utils.PooledByteBuffer;
import org.gudy.azureus2.pluginsimpl.local.download.DownloadImpl;
import org.gudy.azureus2.pluginsimpl.local.utils.PooledByteBufferImpl;
import com.aelitis.azureus.core.peermanager.piecepicker.PiecePicker;
import com.aelitis.azureus.core.peermanager.piecepicker.PieceRTAProvider;
import com.aelitis.azureus.core.util.CopyOnWriteList;
public class
DiskManagerChannelImpl
implements DiskManagerChannel, DiskManagerFileInfoListener, DownloadManagerPeerListener, PieceRTAProvider
{
private static int BUFFER_MILLIS;
private static int MIN_PIECES_TO_BUFFER;
static{
COConfigurationManager.addAndFireParameterListeners(
new String[]{
"filechannel.rt.buffer.millis",
"filechannel.rt.buffer.pieces",
},
new ParameterListener()
{
public void
parameterChanged(
String parameterName )
{
BUFFER_MILLIS = COConfigurationManager.getIntParameter( "filechannel.rt.buffer.millis" );
MIN_PIECES_TO_BUFFER = COConfigurationManager.getIntParameter( "filechannel.rt.buffer.pieces" );
}
});
}
private static final boolean TRACE = false;
private static final int COMPACT_DELAY = 32;
private static final int MAX_READ_CHUNK = 64*1024;
private static final Comparator comparator = new
Comparator()
{
public int
compare(
Object _o1,
Object _o2)
{
dataEntry o1 = (dataEntry)_o1;
dataEntry o2 = (dataEntry)_o2;
long offset1 = o1.getOffset();
long length1 = o1.getLength();
long offset2 = o2.getOffset();
long length2 = o2.getLength();
long res;
if ( offset1 == offset2 ){
res = length1 - length2;
}else{
res = offset1 - offset2;
}
if ( res == 0 ){
return(0);
}else if ( res < 0 ){
return(-1);
}else{
return(1);
}
}
};
private static final String channel_key = "DiskManagerChannel";
private static int channel_id_next;
// hack to allow other components to be informed when channels are created
private static CopyOnWriteList listeners = new CopyOnWriteList();
public static void
addListener(
channelCreateListener l )
{
listeners.add( l );
}
public static void
removeListener(
channelCreateListener l )
{
listeners.remove( l );
}
protected static void
reportCreated(
DiskManagerChannel channel )
{
Iterator it = listeners.iterator();
while( it.hasNext()){
try{
((channelCreateListener)it.next()).channelCreated( channel );
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
}
public static interface
channelCreateListener
{
public void
channelCreated(
DiskManagerChannel channel );
}
private DownloadImpl download;
private org.gudy.azureus2.pluginsimpl.local.disk.DiskManagerFileInfoImpl plugin_file;
private org.gudy.azureus2.core3.disk.DiskManagerFileInfo core_file;
private Set data_written = new TreeSet( comparator );
private int compact_delay = COMPACT_DELAY;
private List waiters = new ArrayList();
private long file_offset_in_torrent;
private long piece_size;
private Average byte_rate = Average.getInstance( 1000, 20 );
private long start_position;
private long start_time;
private long current_position;
private request current_request;
private long buffer_millis;
private PEPeerManager peer_manager;
private long[] rtas;
private int channel_id;
protected
DiskManagerChannelImpl(
DownloadImpl _download,
org.gudy.azureus2.pluginsimpl.local.disk.DiskManagerFileInfoImpl _plugin_file )
{
download = _download;
plugin_file = _plugin_file;
core_file = plugin_file.getCore();
synchronized( DiskManagerChannelImpl.class ){
channel_id = channel_id_next++;
}
TOTorrentFile tf = core_file.getTorrentFile();
TOTorrent torrent = tf.getTorrent();
TOTorrentFile[] tfs = torrent.getFiles();
rtas = new long[torrent.getNumberOfPieces()];
core_file.getDownloadManager().addPeerListener(this);
for (int i=0;i<core_file.getIndex();i++){
file_offset_in_torrent += tfs[i].getLength();
}
piece_size = tf.getTorrent().getPieceLength();
core_file.addListener( this );
reportCreated( this );
}
public DiskManagerFileInfo
getFile()
{
return( plugin_file );
}
public DiskManagerRequest
createRequest()
{
if ( core_file.getDownloaded() != core_file.getLength()){
if ( core_file.isSkipped()){
core_file.setSkipped( false );
}
boolean force_start = download.isForceStart();
if ( !force_start ){
synchronized( download ){
Map dl_state = (Map)download.getDownload().getData( channel_key );
if ( dl_state == null ){
dl_state = new HashMap();
download.getDownload().setData( channel_key, dl_state );
}
dl_state.put( ""+channel_id, "" );
}
download.setForceStart( true );
}
}
current_request = new request();
return( current_request );
}
public void
dataWritten(
long offset,
long length )
{
if ( TRACE ){
System.out.println( "data written:" + offset + "/" + length );
}
dataEntry entry = new dataEntry( offset, length );
synchronized( data_written ){
data_written.add( entry );
compact_delay--;
if ( compact_delay == 0 ){
compact_delay = COMPACT_DELAY;
Iterator it = data_written.iterator();
dataEntry prev_e = null;
while( it.hasNext()){
dataEntry this_e = (dataEntry)it.next();
if ( prev_e == null ){
prev_e = this_e;
}else{
long prev_offset = prev_e.getOffset();
long prev_length = prev_e.getLength();
long this_offset = this_e.getOffset();
long this_length = this_e.getLength();
if ( this_offset <= prev_offset + prev_length ){
if ( TRACE ){
System.out.println( "merging: " + prev_e.getString() + "/" + this_e.getString());
}
it.remove();
prev_e.setLength( Math.max( prev_offset + prev_length, this_offset + this_length ) - prev_offset );
if ( TRACE ){
System.out.println( " -> " + prev_e.getString());
}
}else{
prev_e = this_e;
}
}
}
}
for (int i=0;i<waiters.size();i++){
((AESemaphore)waiters.get(i)).release();
}
}
}
public void
dataChecked(
long offset,
long length )
{
// System.out.println( "data checked:" + offset + "/" + length );
}
public void
peerManagerWillBeAdded(
PEPeerManager manager )
{
}
public void
peerManagerAdded(
PEPeerManager manager )
{
peer_manager = manager;
manager.getPiecePicker().addRTAProvider( this );
}
public void
peerManagerRemoved(
PEPeerManager manager )
{
peer_manager = null;
manager.getPiecePicker().removeRTAProvider( this );
}
public void
peerAdded(
PEPeer peer )
{
}
public void
peerRemoved(
PEPeer peer )
{
}
public long[]
updateRTAs(
PiecePicker picker )
{
long overall_pos = current_position + file_offset_in_torrent;
int first_piece = (int)( overall_pos / piece_size );
long rate = byte_rate.getAverage();
long buffer_bytes = ( BUFFER_MILLIS * rate ) / 1000;
int pieces_to_buffer = (int)( buffer_bytes / piece_size );
if ( pieces_to_buffer < 1 ){
pieces_to_buffer = 1;
}
int millis_per_piece = BUFFER_MILLIS/pieces_to_buffer;
if ( pieces_to_buffer < MIN_PIECES_TO_BUFFER ){
pieces_to_buffer = MIN_PIECES_TO_BUFFER;
}
// System.out.println( "rate = " + rate + ", buffer_bytes = " + buffer_bytes + ", pieces = " + pieces_to_buffer + ", millis_per_piece = " + millis_per_piece );
Arrays.fill( rtas, 0 );
long now = SystemTime.getCurrentTime();
now += buffer_millis;
for (int i=first_piece;i<first_piece+pieces_to_buffer&&i<rtas.length;i++){
rtas[i] = now + (( i - first_piece ) * millis_per_piece );
}
return( rtas );
}
public long
getStartTime()
{
return( start_time );
}
public long
getStartPosition()
{
return( start_position );
}
public long
getCurrentPosition()
{
return( current_position );
}
public long
getBlockingPosition()
{
request r = current_request;
if ( r == null ){
return( current_position );
}
return( current_position + r.getAvailableBytes());
}
public void
setBufferMillis(
long millis )
{
buffer_millis = millis;
}
public String
getUserAgent()
{
request r = current_request;
if ( r == null ){
return( null );
}
return( r.getUserAgent());
}
public void
destroy()
{
core_file.removeListener( this );
core_file.getDownloadManager().removePeerListener(this);
if ( peer_manager != null ){
peer_manager.getPiecePicker().removeRTAProvider( this );
}
boolean stop_force_start = false;
synchronized( download ){
Map dl_state = (Map)download.getDownload().getData( channel_key );
if ( dl_state != null ){
dl_state.remove( "" + channel_id );
if ( dl_state.size() == 0 ){
stop_force_start = true;
}
}
}
if ( stop_force_start ){
download.setForceStart( false );
}
}
protected class
request
implements DiskManagerRequest
{
private int request_type;
private long request_offset;
private long request_length;
private List listeners = new ArrayList();
private String user_agent;
private volatile boolean cancelled;
AESemaphore wait_sem = new AESemaphore( "DiskManagerChannelImpl:wait" );
protected
request()
{
start_time = SystemTime.getCurrentTime();
}
public void
setType(
int _type )
{
request_type = _type;
}
public void
setOffset(
long _offset )
{
request_offset = _offset;
start_position = request_offset;
}
public void
setLength(
long _length )
{
request_length = _length;
}
public long
getRemaining()
{
synchronized( data_written ){
return( request_length - (current_position - request_offset ));
}
}
public void
setUserAgent(
String str )
{
user_agent = str;
}
protected String
getUserAgent()
{
return( user_agent );
}
public long
getAvailableBytes()
{
if ( plugin_file.getDownloaded() == plugin_file.getLength()){
return( getRemaining());
}
int download_state = download.getState();
// if the file is incomplete and the download isn't running then we don't have a view
// of what's available or not (to do this we'd need to add stuff to access resume data)
if ( download_state != Download.ST_DOWNLOADING &&
download_state != Download.ST_SEEDING ){
return( -1 );
}
synchronized( data_written ){
Iterator it = data_written.iterator();
// may not have been compacted to we need to aggregate contigous entry lengths
dataEntry last_entry = null;
while( it.hasNext()){
dataEntry entry = (dataEntry)it.next();
long entry_offset = entry.getOffset();
long entry_length = entry.getLength();
if ( last_entry == null ){
if ( entry_offset > current_position ){
break;
}
if ( entry_offset <= current_position && current_position < entry_offset + entry_length ){
last_entry = entry;
}
}else{
if ( last_entry.getOffset() + last_entry.getLength() == entry.getOffset()){
last_entry = entry;
}else{
break;
}
}
}
if ( last_entry == null ){
return( 0 );
}else{
return( last_entry.getOffset() + last_entry.getLength() - current_position );
}
}
}
public void
run()
{
long rem = request_length;
long pos = request_offset;
try{
while( rem > 0 && !cancelled ){
int len = 0;
synchronized( data_written ){
current_position = pos;
Iterator it = data_written.iterator();
while( it.hasNext()){
dataEntry entry = (dataEntry)it.next();
long entry_offset = entry.getOffset();
if ( entry_offset > pos ){
break;
}
long entry_length = entry.getLength();
long available = entry_offset + entry_length - pos;
if ( available > 0 ){
len = (int)( available<MAX_READ_CHUNK?available:MAX_READ_CHUNK);
break;
}
}
}
if ( len > 0 ){
DirectByteBuffer buffer = core_file.read( pos, len );
inform( new event( new PooledByteBufferImpl( buffer ), pos, len ));
pos += len;
rem -= len;
synchronized( data_written ){
byte_rate.addValue( len );
current_position = pos;
}
}else{
inform( new event( pos ));
synchronized( data_written ){
waiters.add( wait_sem );
}
try{
wait_sem.reserve();
}finally{
synchronized( data_written ){
waiters.remove( wait_sem );
}
}
}
}
}catch( Throwable e ){
inform( e );
}
}
public void
cancel()
{
cancelled = true;
inform( new Throwable( "Request cancelled" ));
wait_sem.release();
}
protected void
inform(
Throwable e )
{
inform( new event( e ));
}
protected void
inform(
event ev )
{
for (int i=0;i<listeners.size();i++){
try{
((DiskManagerListener)listeners.get(i)).eventOccurred( ev );
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
}
public void
addListener(
DiskManagerListener listener )
{
listeners.add( listener );
}
public void
removeListener(
DiskManagerListener listener )
{
listeners.remove( listener );
}
protected class
event
implements DiskManagerEvent
{
private int event_type;
private Throwable error;
private PooledByteBuffer buffer;
private long event_offset;
private int event_length;
protected
event(
Throwable _error )
{
event_type = DiskManagerEvent.EVENT_TYPE_FAILED;
error = _error;
}
protected
event(
long _offset )
{
event_type = DiskManagerEvent.EVENT_TYPE_BLOCKED;
event_offset = _offset;
}
protected
event(
PooledByteBuffer _buffer,
long _offset,
int _length )
{
event_type = DiskManagerEvent.EVENT_TYPE_SUCCESS;
buffer = _buffer;
event_offset = _offset;
event_length = _length;
}
public int
getType()
{
return( event_type );
}
public DiskManagerRequest
getRequest()
{
return( request.this );
}
public long
getOffset()
{
return( event_offset );
}
public int
getLength()
{
return( event_length );
}
public PooledByteBuffer
getBuffer()
{
return( buffer );
}
public Throwable
getFailure()
{
return( error );
}
}
}
protected static class
dataEntry
{
private long offset;
private long length;
protected
dataEntry(
long _offset,
long _length )
{
offset = _offset;
length = _length;
}
protected long
getOffset()
{
return( offset );
}
protected long
getLength()
{
return( length );
}
protected void
setLength(
long _length )
{
length = _length;
}
protected String
getString()
{
return( "offset=" + offset + ",length=" + length );
}
}
}