/*
* Created on Oct 10, 2012
* Created by Paul Gardner
*
* Copyright 2012 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 org.gudy.azureus2.pluginsimpl.local.disk;
import java.util.*;
import org.gudy.azureus2.core3.disk.DiskManager;
import org.gudy.azureus2.core3.disk.DiskManagerFileInfoListener;
import org.gudy.azureus2.core3.disk.DiskManagerPiece;
import org.gudy.azureus2.core3.download.DownloadManager;
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.AERunnable;
import org.gudy.azureus2.core3.util.AESemaphore;
import org.gudy.azureus2.core3.util.AsyncDispatcher;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.DirectByteBuffer;
import org.gudy.azureus2.core3.util.SimpleTimer;
import org.gudy.azureus2.core3.util.SystemTime;
import org.gudy.azureus2.core3.util.TimerEvent;
import org.gudy.azureus2.core3.util.TimerEventPerformer;
import org.gudy.azureus2.core3.util.TimerEventPeriodic;
import org.gudy.azureus2.plugins.disk.DiskManagerEvent;
import org.gudy.azureus2.plugins.disk.DiskManagerListener;
import org.gudy.azureus2.plugins.disk.DiskManagerRandomReadRequest;
import org.gudy.azureus2.plugins.download.Download;
import org.gudy.azureus2.plugins.download.DownloadException;
import org.gudy.azureus2.plugins.download.DownloadListener;
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;
public class
DiskManagerRandomReadController
{
private static Map<DownloadImpl, DiskManagerRandomReadController> controller_map = new HashMap<DownloadImpl, DiskManagerRandomReadController>();
public static DiskManagerRandomReadRequest
createRequest(
DownloadImpl download,
DiskManagerFileInfoImpl file,
long file_offset,
long length,
boolean reverse_order,
DiskManagerListener listener )
throws DownloadException
{
if ( file_offset < 0 || file_offset >= file.getLength()){
throw( new DownloadException( "invalid file offset " + file_offset + ", file size=" + file.getLength()));
}
if ( length <= 0 || file_offset + length > file.getLength()){
throw( new DownloadException( "invalid read length " + length + ", offset=" + file_offset + ", file size=" + file.getLength()));
}
DiskManagerRandomReadController controller;
synchronized( controller_map ){
controller = controller_map.get( download );
if ( controller == null ){
controller = new DiskManagerRandomReadController( download );
controller_map.put( download, controller );
}
return( controller.addRequest( file, file_offset, length, reverse_order, listener ));
}
}
private DownloadImpl download;
private List<DiskManagerRandomReadRequestImpl> requests = new ArrayList<DiskManagerRandomReadRequestImpl>();
private AsyncDispatcher dispatcher = new AsyncDispatcher( "dm_rand_reads");
private boolean set_force_start;
private TimerEventPeriodic timer_event;
private volatile boolean busy;
private volatile long last_busy_time;
private
DiskManagerRandomReadController(
DownloadImpl _download )
{
download = _download;
timer_event =
SimpleTimer.addPeriodicEvent(
"dmrr:timer",
5000,
new TimerEventPerformer()
{
public void
perform(
TimerEvent event)
{
if ( busy || SystemTime.getMonotonousTime() - last_busy_time < 5000 ){
return;
}
synchronized( controller_map ){
synchronized( requests ){
if ( requests.size() > 0 ){
return;
}
}
controller_map.remove( download );
if ( set_force_start ){
download.setForceStart( false );
}
}
timer_event.cancel();
}
});
}
private DiskManagerRandomReadRequest
addRequest(
DiskManagerFileInfoImpl file,
long file_offset,
long length,
boolean reverse_order,
DiskManagerListener listener )
{
DiskManagerRandomReadRequestImpl request = new DiskManagerRandomReadRequestImpl( file, file_offset, length, reverse_order, listener );
long file_length = file.getLength();
if ( file_offset >= file_length ){
Debug.out( "Invalid request offset: " + file_offset + ", file length=" + file_length );
return( null );
}
if ( file_offset + length > file_length ){
Debug.out( "Invalid request length: " + file_offset + "/" + length + ", file length=" + file_length );
return( null );
}
synchronized( requests ){
requests.add( request );
}
dispatcher.dispatch(
new AERunnable()
{
public void
runSupport()
{
try{
busy = true;
executeRequest();
}finally{
busy = false;
last_busy_time = SystemTime.getMonotonousTime();
}
}
});
return( request );
}
private void
executeRequest()
{
DiskManagerRandomReadRequestImpl request;
synchronized( requests ){
if ( requests.isEmpty()){
return;
}
request = requests.remove( 0 );
}
if ( request.isCancelled()){
return;
}
DiskManagerFileInfoListener info_listener = null;
org.gudy.azureus2.core3.disk.DiskManagerFileInfo core_file = request.getFile().getCore();
DownloadManager core_download = core_file.getDownloadManager();
int prev_hint_piece = -1;
int curr_hint_piece = -1;
try{
if ( core_download.getTorrent() == null ){
throw( new DownloadException( "Torrent invalid" ));
}
if ( core_download.isDestroyed()){
Debug.out( "Download has been removed" );
throw( new DownloadException( "Download has been removed" ));
}
TOTorrentFile tf = core_file.getTorrentFile();
TOTorrent torrent = tf.getTorrent();
TOTorrentFile[] tfs = torrent.getFiles();
long core_file_start_byte = 0;
for (int i=0;i<core_file.getIndex();i++){
core_file_start_byte += tfs[i].getLength();
}
long download_byte_start = core_file_start_byte + request.getOffset();
long download_byte_end = download_byte_start + request.getLength();
int piece_size = (int)tf.getTorrent().getPieceLength();
if ( core_file.getDownloaded() != core_file.getLength()){
if ( core_file.isSkipped()){
core_file.setSkipped( false );
}
boolean force_start = download.isForceStart();
if ( !force_start ){
download.setForceStart( true );
set_force_start = true;
final AESemaphore running_sem = new AESemaphore( "rs" );
DownloadListener dl_listener =
new DownloadListener()
{
public void
stateChanged(
Download download,
int old_state,
int new_state )
{
if ( new_state == Download.ST_DOWNLOADING || new_state == Download.ST_SEEDING ){
running_sem.release();
}
}
public void
positionChanged(
Download download,
int oldPosition,
int newPosition )
{
}
};
download.addListener( dl_listener );
try{
if ( download.getState() != Download.ST_DOWNLOADING && download.getState() != Download.ST_SEEDING ){
if ( !running_sem.reserve( 10*1000 )){
throw( new DownloadException( "timeout waiting for download to start" ));
}
}
}finally{
download.removeListener( dl_listener );
}
}
}
boolean is_reverse = request.isReverse();
final AESemaphore wait_sem = new AESemaphore( "rr:waiter" );
info_listener = new
DiskManagerFileInfoListener()
{
public void
dataWritten(
long offset,
long length )
{
wait_sem.release();
}
public void
dataChecked(
long offset,
long length )
{
}
};
long start_time = SystemTime.getMonotonousTime();
boolean has_started = false;
core_file.addListener( info_listener );
//System.out.println( "Request starts" );
while( download_byte_start < download_byte_end ){
if ( request.isCancelled()){
throw( new Exception( "request cancelled" ));
}
//System.out.println( "Request current: " + download_byte_start + " -> " + download_byte_end );
long now = SystemTime.getMonotonousTime();
int piece_start = (int)( download_byte_start / piece_size );
int piece_start_offset = (int)( download_byte_start % piece_size );
int piece_end = (int)( ( download_byte_end - 1 ) / piece_size );
int piece_end_offset = (int)( ( download_byte_end - 1 ) % piece_size ) + 1;
//System.out.println( " piece details: " + piece_start + "/" + piece_start_offset + " -> " + piece_end + "/" + piece_end_offset );
DiskManagerPiece[] pieces = null;
DiskManager disk_manager = core_download.getDiskManager();
if ( disk_manager != null ){
pieces = disk_manager.getPieces();
}
long avail_start;
long avail_end;
if ( pieces == null ){
if ( core_file.getDownloaded() == core_file.getLength()){
avail_start = download_byte_start;
avail_end = download_byte_end;
}else{
if ( now - start_time < 10000 && !has_started ){
wait_sem.reserve( 250 );
continue;
}
throw( new Exception( "download stopped" ));
}
}else{
has_started = true;
if ( is_reverse ){
long min_done = download_byte_end;
for ( int i=piece_end; i>= piece_start; i-- ){
int p_start = i==piece_start?piece_start_offset:0;
int p_end = i==piece_end?piece_end_offset:piece_size;
DiskManagerPiece piece = pieces[i];
boolean[] done = piece.getWritten();
if ( done == null ){
if ( piece.isDone()){
min_done = i*(long)piece_size;
continue;
}else{
break;
}
}
int block_size = piece.getBlockSize( 0 );
int first_block = p_start/block_size;
int last_block = (p_end-1)/block_size;
for ( int j=last_block;j>=first_block;j--){
if ( done[j] ){
min_done = i*(long)piece_size + j*block_size;
}else{
break;
}
}
}
avail_start = Math.max( download_byte_start, min_done );
avail_end = download_byte_end;
}else{
long max_done = download_byte_start;
for ( int i=piece_start; i <= piece_end; i++ ){
int p_start = i==piece_start?piece_start_offset:0;
int p_end = i==piece_end?piece_end_offset:piece_size;
DiskManagerPiece piece = pieces[i];
boolean[] done = piece.getWritten();
if ( done == null ){
if ( piece.isDone()){
max_done = (i+1)*(long)piece_size;
continue;
}else{
break;
}
}
int block_size = piece.getBlockSize( 0 );
int first_block = p_start/block_size;
int last_block = (p_end-1)/block_size;
for ( int j=first_block;j<=last_block;j++){
if ( done[j] ){
max_done = i*(long)piece_size + (j+1)*block_size;
}else{
break;
}
}
}
avail_start = download_byte_start;
avail_end = Math.min( download_byte_end, max_done );
}
}
//System.out.println( " avail: " + avail_start + " -> " + avail_end );
int max_chunk = 128*1024;
if ( avail_end > avail_start ){
long length = avail_end - avail_start;
if ( length > max_chunk ){
if ( is_reverse ){
avail_start = avail_end - max_chunk;
}else{
avail_end = avail_start + max_chunk;
}
}
//System.out.println( "got data: " + avail_start + " -> " + avail_end );
long read_offset = avail_start - core_file_start_byte;
int read_length = (int)( avail_end - avail_start );
DirectByteBuffer buffer = core_file.read( read_offset, read_length );
request.dataAvailable( buffer, read_offset, read_length );
if ( is_reverse ){
download_byte_end = avail_start;
}else{
download_byte_start = avail_end;
}
continue;
}
PEPeerManager pm = core_download.getPeerManager();
if ( pm == null ){
if ( now - start_time < 10000 && !has_started ){
wait_sem.reserve( 250 );
continue;
}
throw( new Exception( "download stopped" ));
}else{
has_started = true;
}
PiecePicker picker = pm.getPiecePicker();
picker.setReverseBlockOrder( is_reverse );
int hint_piece;
int hint_offset;
int hint_length;
if ( piece_start == piece_end ){
hint_piece = piece_start;
hint_offset = piece_start_offset;
hint_length = piece_end_offset - piece_start_offset;
}else{
if ( is_reverse ){
hint_piece = piece_end;
hint_offset = 0;
hint_length = piece_end_offset;
}else{
hint_piece = piece_start;
hint_offset = piece_start_offset;
hint_length = piece_size - piece_start_offset;
}
}
if ( curr_hint_piece == -1 ){
int[] existing = picker.getGlobalRequestHint();
if ( existing != null ){
curr_hint_piece = existing[0];
}
}
//System.out.println( "hint: " + hint_piece + "/" + hint_offset + "/" + hint_length + ": curr=" + curr_hint_piece + ", prev=" + prev_hint_piece );
picker.setGlobalRequestHint( hint_piece, hint_offset, hint_length );
if ( hint_piece != curr_hint_piece ){
prev_hint_piece = curr_hint_piece;
curr_hint_piece = hint_piece;
}
if ( prev_hint_piece != -1 ){
clearHint( pm, prev_hint_piece );
}
wait_sem.reserve( 250 );
}
}catch( Throwable e ){
request.failed( e );
}finally{
PEPeerManager pm = core_download.getPeerManager();
if ( pm != null ){
PiecePicker picker = pm.getPiecePicker();
if ( picker != null ){
picker.setReverseBlockOrder( false );
picker.setGlobalRequestHint( -1, 0, 0 );
if ( curr_hint_piece != -1 ){
clearHint( pm, curr_hint_piece );
}
}
}
if ( info_listener != null ){
core_file.removeListener( info_listener );
}
}
}
private void
clearHint(
PEPeerManager pm,
int hint_piece )
{
PEPiece piece = pm.getPiece( hint_piece );
if ( piece != null && piece.getReservedBy() != null ){
piece.setReservedBy( null );
//System.out.println( "clearing res by for " + hint_piece );
}
List<PEPeer> peers = pm.getPeers();
for ( PEPeer peer: peers ){
int[] res = peer.getReservedPieceNumbers();
if ( res != null ){
for ( int i: res ){
if ( i == hint_piece ){
peer.removeReservedPieceNumber( hint_piece );
//System.out.println( "removing res by on " + peer.getIp() + " for " + hint_piece );
}
}
}
}
}
private class
DiskManagerRandomReadRequestImpl
implements DiskManagerRandomReadRequest
{
private DiskManagerFileInfoImpl file;
private long file_offset;
private long length;
private boolean reverse_order;
private DiskManagerListener listener;
private volatile boolean cancelled;
private boolean failed;
private
DiskManagerRandomReadRequestImpl(
DiskManagerFileInfoImpl _file,
long _file_offset,
long _length,
boolean _reverse_order,
DiskManagerListener _listener )
{
file = _file;
file_offset = _file_offset;
length = _length;
reverse_order = _reverse_order;
listener = _listener;
}
public DiskManagerFileInfoImpl
getFile()
{
return( file );
}
public long
getOffset()
{
return( file_offset );
}
public long
getLength()
{
return( length );
}
public boolean
isReverse()
{
return( reverse_order );
}
private boolean
isCancelled()
{
return( cancelled );
}
public void
cancel()
{
synchronized( requests ){
requests.remove( this );
cancelled = true;
}
failed( new Exception( "request cancelled" ));
}
private void
dataAvailable(
DirectByteBuffer buffer,
final long offset,
final int length )
{
final PooledByteBuffer p_buffer = new PooledByteBufferImpl( buffer );
listener.eventOccurred(
new DiskManagerEvent()
{
public int
getType()
{
return( EVENT_TYPE_SUCCESS );
}
public long
getOffset()
{
return( offset );
}
public int
getLength()
{
return( length );
}
public PooledByteBuffer
getBuffer()
{
return( p_buffer );
}
public Throwable
getFailure()
{
return( null );
}
});
}
private void
failed(
final Throwable e )
{
Debug.out(e );
synchronized( requests ){
if ( failed ){
return;
}
failed = true;
}
listener.eventOccurred(
new DiskManagerEvent()
{
public int
getType()
{
return( EVENT_TYPE_FAILED );
}
public long
getOffset()
{
return( -1 );
}
public int
getLength()
{
return( -1 );
}
public PooledByteBuffer
getBuffer()
{
return( null );
}
public Throwable
getFailure()
{
return( e );
}
});
}
}
}