/*
* Created on May 27, 2008
* Created by Paul Gardner
*
* Copyright 2008 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.plugins.net.buddy.tracker;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.*;
import org.gudy.azureus2.core3.global.GlobalManager;
import org.gudy.azureus2.core3.global.GlobalManagerAdapter;
import org.gudy.azureus2.core3.peer.PEPeerManager;
import org.gudy.azureus2.core3.util.AddressUtils;
import org.gudy.azureus2.core3.util.Average;
import org.gudy.azureus2.core3.util.Constants;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.HashWrapper;
import org.gudy.azureus2.core3.util.LightHashMap;
import org.gudy.azureus2.core3.util.SHA1;
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.download.Download;
import org.gudy.azureus2.plugins.download.DownloadAnnounceResult;
import org.gudy.azureus2.plugins.download.DownloadListener;
import org.gudy.azureus2.plugins.download.DownloadManagerListener;
import org.gudy.azureus2.plugins.download.DownloadPeerListener;
import org.gudy.azureus2.plugins.download.DownloadScrapeResult;
import org.gudy.azureus2.plugins.download.DownloadTrackerListener;
import org.gudy.azureus2.plugins.peers.Peer;
import org.gudy.azureus2.plugins.peers.PeerEvent;
import org.gudy.azureus2.plugins.peers.PeerListener2;
import org.gudy.azureus2.plugins.peers.PeerManager;
import org.gudy.azureus2.plugins.peers.PeerManagerListener;
import org.gudy.azureus2.plugins.peers.PeerStats;
import org.gudy.azureus2.plugins.torrent.Torrent;
import org.gudy.azureus2.plugins.ui.config.BooleanParameter;
import org.gudy.azureus2.plugins.ui.config.Parameter;
import org.gudy.azureus2.plugins.ui.config.ParameterListener;
import org.gudy.azureus2.plugins.ui.model.BasicPluginConfigModel;
import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils;
import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.core.util.CopyOnWriteList;
import com.aelitis.azureus.core.util.CopyOnWriteSet;
import com.aelitis.azureus.plugins.net.buddy.*;
public class
BuddyPluginTracker
implements BuddyPluginListener, DownloadManagerListener, BuddyPluginAZ2TrackerListener, DownloadPeerListener
{
private static final Object PEER_KEY = new Object(); // maps to Download object
private static final Object PEER_STATS_KEY = new Object();
public static final int BUDDY_NETWORK_IDLE = 1;
public static final int BUDDY_NETWORK_OUTBOUND = 2;
public static final int BUDDY_NETWORK_INBOUND = 3;
private static final int TRACK_CHECK_PERIOD = 15*1000;
private static final int TRACK_CHECK_TICKS = TRACK_CHECK_PERIOD/BuddyPlugin.TIMER_PERIOD;
private static final int PEER_CHECK_PERIOD = 60*1000;
private static final int PEER_CHECK_TICKS = PEER_CHECK_PERIOD/BuddyPlugin.TIMER_PERIOD;
private static final int PEER_RECHECK_PERIOD = 120*1000;
private static final int PEER_RECHECK_TICKS = PEER_RECHECK_PERIOD/BuddyPlugin.TIMER_PERIOD;
private static final int TRACK_INTERVAL = 10*60*1000;
private static final int SHORT_ID_SIZE = 4;
private static final int FULL_ID_SIZE = 20;
private static final int REQUEST_TRACKER_SUMMARY = 1;
private static final int REPLY_TRACKER_SUMMARY = 2;
private static final int REQUEST_TRACKER_STATUS = 3;
private static final int REPLY_TRACKER_STATUS = 4;
private static final int REQUEST_TRACKER_CHANGE = 5;
private static final int REPLY_TRACKER_CHANGE = 6;
private static final int REQUEST_TRACKER_ADD = 7;
private static final int REPLY_TRACKER_ADD = 8;
private static final int RETRY_SEND_MIN = 5*60*1000;
private static final int RETRY_SEND_MAX = 60*60*1000;
private static final int BUDDY_NO = 0;
private static final int BUDDY_MAYBE = 1;
private static final int BUDDY_YES = 2;
private BuddyPlugin plugin;
private boolean plugin_enabled;
private boolean tracker_enabled;
private boolean seeding_only;
private boolean old_plugin_enabled;
private boolean old_tracker_enabled;
private boolean old_seeding_only;
private int network_status = BUDDY_NETWORK_IDLE;
private Set online_buddies = new HashSet();
private Map online_buddy_ips = new HashMap();
private Set tracked_downloads = new HashSet();
private int download_set_id;
private Set last_processed_download_set;
private int last_processed_download_set_id;
private Map short_id_map = new HashMap();
private Map full_id_map = new HashMap();
private Set actively_tracking = new HashSet();
private CopyOnWriteSet buddy_peers = new CopyOnWriteSet();
private CopyOnWriteList listeners = new CopyOnWriteList();
private TimerEventPeriodic buddy_stats_timer;
private Average buddy_receive_speed = Average.getInstance(1000, 10);
private Average buddy_send_speed = Average.getInstance(1000, 10);
public
BuddyPluginTracker(
BuddyPlugin _plugin,
BasicPluginConfigModel _config )
{
plugin = _plugin;
final BooleanParameter te = _config.addBooleanParameter2("azbuddy.tracker.enabled", "azbuddy.tracker.enabled", true );
tracker_enabled = te.getValue();
te.addListener(
new ParameterListener()
{
public void
parameterChanged(
Parameter param )
{
tracker_enabled = te.getValue();
checkEnabledState();
}
});
// Assumed if we already have a plugin reference, that the
// Azureus Core is available
GlobalManager gm = AzureusCoreFactory.getSingleton().getGlobalManager();
gm.addListener(
new GlobalManagerAdapter()
{
public void
seedingStatusChanged(
boolean seeding_only_mode,
boolean potentially_seeding_only )
{
seeding_only = potentially_seeding_only;
checkEnabledState();
}
}, false );
seeding_only = gm.isPotentiallySeedingOnly();
checkEnabledState();
}
public void
initialise()
{
plugin_enabled = plugin.isEnabled();
checkEnabledState();
List buddies = plugin.getBuddies();
for (int i=0;i<buddies.size();i++){
buddyAdded((BuddyPluginBuddy)buddies.get(i));
}
plugin.addListener( this );
plugin.getAZ2Handler().addTrackerListener( this );
plugin.getPluginInterface().getDownloadManager().addListener( this, true );
}
public void
tick(
int tick_count )
{
if ( tick_count % TRACK_CHECK_TICKS == 0 ){
checkTracking();
}
if ( ( tick_count-1 ) % TRACK_CHECK_TICKS == 0 ){
doTracking();
}
if ( tick_count % PEER_CHECK_TICKS == 0 ){
checkPeers();
}
if ( tick_count % PEER_RECHECK_TICKS == 0 ){
recheckPeers();
}
}
public int
getNetworkStatus()
{
return( network_status );
}
public long
getNetworkReceiveBytesPerSecond()
{
return( buddy_receive_speed.getAverage());
}
public long
getNetworkSendBytesPerSecond()
{
return( buddy_send_speed.getAverage());
}
protected void
doTracking()
{
if ( !( plugin_enabled && tracker_enabled )){
return;
}
Map to_do = new HashMap();
Set active_set = new HashSet();
synchronized( online_buddies ){
Iterator it = online_buddies.iterator();
while( it.hasNext()){
BuddyPluginBuddy buddy = (BuddyPluginBuddy)it.next();
buddyData buddy_data = getBuddyData( buddy );
Map active = buddy_data.getDownloadsToTrack();
if ( active.size() > 0 ){
Iterator it2 = active.entrySet().iterator();
List track_now = new ArrayList();
while( it2.hasNext()){
Map.Entry entry = (Map.Entry)it2.next();
Download dl = (Download)entry.getKey();
boolean now = ((Boolean)entry.getValue()).booleanValue();
if ( now ){
track_now.add( dl );
}
active_set.add( dl );
}
if( track_now.size() > 0 ){
to_do.put( buddy, track_now );
}
}
}
}
synchronized( actively_tracking ){
Iterator it = active_set.iterator();
while( it.hasNext()){
Download dl = (Download)it.next();
if ( !actively_tracking.contains( dl )){
actively_tracking.add( dl );
trackPeers( dl );
}
}
it = actively_tracking.iterator();
while( it.hasNext()){
Download dl = (Download)it.next();
if ( !active_set.contains( dl )){
it.remove();
untrackPeers( dl );
}
}
}
Iterator it = to_do.entrySet().iterator();
while( it.hasNext()){
Map.Entry entry = (Map.Entry)it.next();
BuddyPluginBuddy buddy = (BuddyPluginBuddy)entry.getKey();
if ( !buddy.isOnline( false )){
continue;
}
InetAddress ip = buddy.getAdjustedIP();
if ( ip == null ){
continue;
}
int tcp_port = buddy.getTCPPort();
int udp_port = buddy.getUDPPort();
List downloads = (List)entry.getValue();
for (int i=0;i<downloads.size();i++){
Download download = (Download)downloads.get(i);
PeerManager pm = download.getPeerManager();
if ( pm == null ){
continue;
}
Peer[] existing_peers = pm.getPeers( ip.getHostAddress());
boolean connected = false;
for (int j=0;j<existing_peers.length;j++){
Peer peer = existing_peers[j];
if ( peer.getTCPListenPort() == tcp_port ||
peer.getUDPListenPort() == udp_port ){
connected = true;
break;
}
}
if ( connected ){
log( download.getName() + " - peer " + ip.getHostAddress() + " already connected" );
continue;
}
log( download.getName() + " - connecting to peer " + ip.getHostAddress());
PEPeerManager c_pm = PluginCoreUtils.unwrap( pm );
Map user_data = new LightHashMap();
user_data.put( PEER_KEY, download );
user_data.put( Peer.PR_PRIORITY_CONNECTION, new Boolean( true ));
c_pm.addPeer( ip.getHostAddress(), tcp_port, udp_port, true, user_data );
}
}
}
protected void
checkTracking()
{
if ( !( plugin_enabled && tracker_enabled )){
return;
}
List online;
synchronized( online_buddies ){
online = new ArrayList( online_buddies );
}
Set downloads;
int downloads_id;
synchronized( tracked_downloads ){
boolean downloads_changed = last_processed_download_set_id != download_set_id;
if ( downloads_changed ){
last_processed_download_set = new HashSet( tracked_downloads );
last_processed_download_set_id = download_set_id;
}
downloads = last_processed_download_set;
downloads_id = last_processed_download_set_id;
}
Map diff_map = new HashMap();
for (int i=0;i<online.size();i++){
BuddyPluginBuddy buddy = (BuddyPluginBuddy)online.get(i);
buddyData buddy_data = getBuddyData( buddy );
buddy_data.updateLocal( downloads, downloads_id, diff_map );
}
}
public void
initialised(
boolean available )
{
}
public void
buddyAdded(
BuddyPluginBuddy buddy )
{
buddyChanged( buddy );
}
public void
buddyRemoved(
BuddyPluginBuddy buddy )
{
buddyChanged( buddy );
}
public void
buddyChanged(
BuddyPluginBuddy buddy )
{
if ( buddy.isOnline( false )){
addBuddy( buddy );
}else{
removeBuddy( buddy );
}
}
protected buddyData
getBuddyData(
BuddyPluginBuddy buddy )
{
synchronized( online_buddies ){
buddyData buddy_data = (buddyData)buddy.getUserData( BuddyPluginTracker.class );
if ( buddy_data == null ){
buddy_data = new buddyData( buddy );
buddy.setUserData( BuddyPluginTracker.class, buddy_data );
}
return( buddy_data );
}
}
protected buddyData
addBuddy(
BuddyPluginBuddy buddy )
{
synchronized( online_buddies ){
if ( !online_buddies.contains( buddy )){
online_buddies.add( buddy );
}
buddyData bd = getBuddyData( buddy );
if ( bd.hasIPChanged()){
String ip = bd.getIP();
if ( ip != null ){
List l = (List)online_buddy_ips.get( ip );
if ( l != null ){
l.remove( buddy );
if ( l.size() == 0 ){
online_buddy_ips.remove( ip );
}
}
}
bd.updateIP();
ip = bd.getIP();
if ( ip != null ){
List l = (List)online_buddy_ips.get( ip );
if ( l == null ){
l = new ArrayList();
online_buddy_ips.put( ip, l );
}
l.add( buddy );
}
}
return( bd );
}
}
protected void
removeBuddy(
BuddyPluginBuddy buddy )
{
synchronized( online_buddies ){
if ( online_buddies.contains( buddy )){
buddyData bd = getBuddyData( buddy );
online_buddies.remove( buddy );
String ip = bd.getIP();
if ( ip != null ){
List l = (List)online_buddy_ips.get( ip );
if ( l != null ){
l.remove( buddy );
if ( l.size() == 0 ){
online_buddy_ips.remove( ip );
}
}
}
}
}
}
protected int
isBuddy(
Peer peer )
{
String peer_ip = peer.getIp();
List ips = AddressUtils.getLANAddresses( peer_ip );
synchronized( online_buddies ){
int result = BUDDY_NO;
String tested = "";
outer:
for (int i=0;i<ips.size();i++){
String ip = (String)ips.get(i);
tested += ip;
List buddies =(List)online_buddy_ips.get( ip );
if ( buddies != null ){
if ( peer.getTCPListenPort() == 0 && peer.getUDPListenPort() == 0 ){
result = BUDDY_MAYBE;
}else{
for (int j=0;j<buddies.size();j++){
BuddyPluginBuddy buddy = (BuddyPluginBuddy)buddies.get(j);
if ( buddy.getTCPPort() == peer.getTCPListenPort() &&
buddy.getTCPPort() != 0 ){
result = BUDDY_YES;
break outer;
}
if ( buddy.getUDPPort() == peer.getUDPListenPort() &&
buddy.getUDPPort() != 0 ){
result = BUDDY_YES;
break outer;
}
}
}
}
}
// log( "isBuddy: " + peer_ip + " -> " + result + ",tested=" + tested );
return( result );
}
}
public void
messageLogged(
String str,
boolean error )
{
}
public void
enabledStateChanged(
boolean _enabled )
{
plugin_enabled = _enabled;
checkEnabledState();
}
public boolean
isEnabled()
{
synchronized( this ){
return( plugin_enabled && tracker_enabled );
}
}
protected void
checkEnabledState()
{
boolean seeding_change = false;
boolean enabled_change = false;
synchronized( this ){
boolean old_enabled = old_plugin_enabled && old_tracker_enabled;
if ( plugin_enabled != old_plugin_enabled ){
log( "Plugin enabled state changed to " + plugin_enabled );
old_plugin_enabled = plugin_enabled;
}
if ( tracker_enabled != old_tracker_enabled ){
log( "Tracker enabled state changed to " + tracker_enabled );
old_tracker_enabled = tracker_enabled;
}
if ( seeding_only != old_seeding_only ){
log( "Seeding-only state changed to " + seeding_only );
old_seeding_only = seeding_only;
seeding_change = true;
}
enabled_change = old_enabled != ( plugin_enabled && tracker_enabled );
}
if ( seeding_change ){
updateSeedingMode();
}
if ( enabled_change ){
fireEnabledChanged( isEnabled());
}
}
protected void
updateSeedingMode()
{
updateNetworkStatus();
List online;
synchronized( online_buddies ){
online = new ArrayList( online_buddies );
}
for (int i=0;i<online.size();i++){
buddyData buddy_data = getBuddyData((BuddyPluginBuddy)online.get(i));
if ( buddy_data.hasDownloadsInCommon()){
buddy_data.updateStatus();
}
}
}
public void
downloadAdded(
final Download download )
{
Torrent t = download.getTorrent();
if ( t == null ){
return;
}
if ( t.isPrivate()){
download.addTrackerListener(
new DownloadTrackerListener()
{
public void
scrapeResult(
DownloadScrapeResult result )
{
}
public void
announceResult(
DownloadAnnounceResult result)
{
if ( okToTrack( download )){
trackDownload( download );
}else{
untrackDownload( download );
}
}
},
false );
}
if ( okToTrack( download )){
trackDownload( download );
}
download.addListener(
new DownloadListener()
{
public void
stateChanged(
Download download,
int old_state,
int new_state )
{
if ( okToTrack( download )){
trackDownload( download );
}else{
untrackDownload( download );
}
}
public void
positionChanged(
Download download,
int oldPosition,
int newPosition )
{
}
});
}
public void
downloadRemoved(
Download download )
{
untrackDownload( download );
}
protected void
trackDownload(
Download download )
{
synchronized( tracked_downloads ){
if ( tracked_downloads.contains( download )){
return;
}
downloadData download_data = new downloadData( download );
download.setUserData( BuddyPluginTracker.class, download_data );
HashWrapper full_id = download_data.getID();
HashWrapper short_id = new HashWrapper( full_id.getHash(), 0, 4 );
full_id_map.put( full_id, download );
List dls = (List)short_id_map.get( short_id );
if ( dls == null ){
dls = new ArrayList();
short_id_map.put( short_id, dls );
}
dls.add( download );
tracked_downloads.add( download );
download_set_id++;
}
}
protected void
untrackDownload(
Download download )
{
synchronized( tracked_downloads ){
if ( tracked_downloads.remove( download )){
download_set_id++;
downloadData download_data = (downloadData)download.getUserData( BuddyPluginTracker.class );
download.setUserData( BuddyPluginTracker.class, null );
HashWrapper full_id = download_data.getID();
full_id_map.remove( full_id );
HashWrapper short_id = new HashWrapper( full_id.getHash(), 0, SHORT_ID_SIZE );
List dls = (List)short_id_map.get( short_id );
if ( dls != null ){
dls.remove( download );
if ( dls.size() == 0 ){
short_id_map.remove( short_id );
}
}
}
}
synchronized( online_buddies ){
Iterator it = online_buddies.iterator();
while( it.hasNext()){
BuddyPluginBuddy buddy = (BuddyPluginBuddy)it.next();
buddyData buddy_data = getBuddyData( buddy );
buddy_data.resetTracking( download );
}
}
synchronized( actively_tracking ){
actively_tracking.remove( download );
}
}
protected void
trackPeers(
final Download download )
{
PeerManager pm = download.getPeerManager();
// not running
if ( pm == null ){
synchronized( actively_tracking ){
actively_tracking.remove( download );
}
}else{
log( "Tracking peers for " + download.getName());
download.addPeerListener( this );
}
}
public void
peerManagerAdded(
Download download,
PeerManager peer_manager )
{
trackPeers( download, peer_manager );
}
public void
peerManagerRemoved(
Download download,
PeerManager peer_manager )
{
synchronized( actively_tracking ){
actively_tracking.remove( download );
}
download.removePeerListener( this );
}
protected void
trackPeers(
final Download download,
PeerManager pm )
{
pm.addListener(
new PeerManagerListener()
{
public void
peerAdded(
PeerManager manager,
Peer peer )
{
synchronized( actively_tracking ){
if ( !actively_tracking.contains( download )){
manager.removeListener( this );
return;
}
}
trackPeer( download, peer );
}
public void
peerRemoved(
PeerManager manager,
Peer peer )
{
}
});
Peer[] peers = pm.getPeers();
for (int i=0;i<peers.length;i++){
trackPeer( download, peers[i] );
}
}
protected void
trackPeer(
final Download download,
final Peer peer )
{
int type = isBuddy( peer );
if ( type == BUDDY_YES ){
markBuddyPeer( download, peer );
}else if ( type == BUDDY_MAYBE ){
// mark as peer early so that we get optimistic disconnect if needed
markBuddyPeer( download, peer );
PeerListener2 listener =
new PeerListener2()
{
public void
eventOccurred(
PeerEvent event )
{
if ( event.getType() == PeerEvent.ET_STATE_CHANGED ){
if (((Integer)event.getData()).intValue() == Peer.TRANSFERING ){
peer.removeListener( this );
// withdraw buddy marker if it turns out our earlier optimism
// was misplaced
if ( isBuddy( peer ) != BUDDY_YES ){
unmarkBuddyPeer( peer );
}
}
}
}
};
peer.addListener( listener );
if ( peer.getState() == Peer.TRANSFERING ){
peer.removeListener( listener );
// withdraw buddy marker if it turns out our earlier optimism
// was misplaced
if ( isBuddy( peer ) != BUDDY_YES ){
unmarkBuddyPeer( peer );
}
}
}
}
protected void
untrackPeers(
Download download )
{
log( "Not tracking peers for " + download.getName());
download.removePeerListener( this );
PeerManager pm = download.getPeerManager();
if ( pm != null ){
Peer[] peers = pm.getPeers();
for (int i=0;i<peers.length;i++){
Peer peer = peers[i];
unmarkBuddyPeer( peer );
}
}
}
protected void
markBuddyPeer(
final Download download,
final Peer peer )
{
boolean state_changed = false;
synchronized( buddy_peers ){
if ( !buddy_peers.contains( peer )){
log( "Adding buddy peer " + peer.getIp());
if ( buddy_peers.size() == 0 ){
if ( buddy_stats_timer == null ){
buddy_stats_timer =
SimpleTimer.addPeriodicEvent(
"BuddyTracker:stats",
1000,
new TimerEventPerformer()
{
public void
perform(
TimerEvent event )
{
Iterator it = buddy_peers.iterator();
long total_sent = 0;
long total_received = 0;
while( it.hasNext()){
Peer p = (Peer)it.next();
PeerStats ps = p.getStats();
long sent = ps.getTotalSent();
long received = ps.getTotalReceived();
long[] last = (long[])p.getUserData( PEER_STATS_KEY );
if ( last != null ){
total_sent += sent - last[0];
total_received += received - last[1];
}
p.setUserData( PEER_STATS_KEY, new long[]{ sent, received });
}
buddy_receive_speed.addValue( total_received );
buddy_send_speed.addValue( total_sent );
}
});
}
state_changed = true;
}
buddy_peers.add( peer );
peer.setUserData( PEER_KEY, download );
peer.setPriorityConnection( true );
log( download.getName() + ": adding buddy peer " + peer.getIp());
peer.addListener(
new PeerListener2()
{
public void
eventOccurred(
PeerEvent event )
{
if ( event.getType() == PeerEvent.ET_STATE_CHANGED ){
int state = ((Integer)event.getData()).intValue();
if ( state == Peer.CLOSING || state == Peer.DISCONNECTED ){
peer.removeListener( this );
unmarkBuddyPeer( peer );
}
}
}
});
}
}
if ( peer.getState() == Peer.CLOSING || peer.getState() == Peer.DISCONNECTED ){
unmarkBuddyPeer( peer );
}
if ( state_changed ){
updateNetworkStatus();
}
}
protected void
unmarkBuddyPeer(
Peer peer )
{
boolean state_changed = false;
synchronized( buddy_peers ){
Download download = (Download)peer.getUserData( PEER_KEY );
if ( download == null ){
return;
}
if ( buddy_peers.remove( peer )){
if ( buddy_peers.size() == 0 ){
state_changed = true;
if ( buddy_stats_timer != null ){
buddy_stats_timer.cancel();
buddy_stats_timer = null;
}
}
log( download.getName() + ": removing buddy peer " + peer.getIp());
}
peer.setUserData( PEER_KEY, null );
peer.setPriorityConnection( false );
}
if ( state_changed ){
updateNetworkStatus();
}
}
protected void
checkPeers()
{
List to_unmark = new ArrayList();
synchronized( buddy_peers ){
Iterator it = buddy_peers.iterator();
while( it.hasNext()){
Peer peer = (Peer)it.next();
if ( peer.getState() == Peer.CLOSING || peer.getState() == Peer.DISCONNECTED ){
to_unmark.add( peer );
}
}
}
for (int i=0;i<to_unmark.size();i++){
unmarkBuddyPeer((Peer)to_unmark.get(i));
}
}
protected void
recheckPeers()
{
// go over peers for active torrents to see if we've missed and. can really only
// happen with multi-homed LAN setups where a new (and utilised) route is found
// after we start tracking
synchronized( actively_tracking ){
Iterator it = actively_tracking.iterator();
while( it.hasNext()){
Download download = (Download)it.next();
PeerManager pm = download.getPeerManager();
if ( pm != null ){
Peer[] peers = pm.getPeers();
for (int i=0;i<peers.length;i++){
trackPeer( download, peers[i] );
}
}
}
}
}
protected void
updateNetworkStatus()
{
int new_status;
boolean changed = false;
synchronized( buddy_peers ){
if ( buddy_peers.size() == 0 ){
new_status = BUDDY_NETWORK_IDLE;
}else{
new_status = seeding_only?BUDDY_NETWORK_OUTBOUND:BUDDY_NETWORK_INBOUND;
}
if ( new_status != network_status ){
network_status = new_status;
changed = true;
}
}
if ( changed ){
fireStateChange( new_status );
}
}
public void
addListener(
BuddyPluginTrackerListener l )
{
listeners.add( l );
}
public void
removeListener(
BuddyPluginTrackerListener l )
{
listeners.remove( l );
}
protected void
fireStateChange(
int state )
{
Iterator it = listeners.iterator();
while( it.hasNext()){
try{
((BuddyPluginTrackerListener)it.next()).networkStatusChanged( this, state );
}catch( Throwable e ){
Debug.out( e );
}
}
}
protected void
fireEnabledChanged(
boolean enabled )
{
Iterator it = listeners.iterator();
while( it.hasNext()){
try{
((BuddyPluginTrackerListener)it.next()).enabledStateChanged( this, enabled );
}catch( Throwable e ){
Debug.out( e );
}
}
}
protected void
sendMessage(
BuddyPluginBuddy buddy,
int type,
Map body )
{
Map msg = new HashMap();
msg.put( "type", new Long( type ));
msg.put( "msg", body );
plugin.getAZ2Handler().sendAZ2TrackerMessage(
buddy,
msg,
BuddyPluginTracker.this );
}
public Map
messageReceived(
BuddyPluginBuddy buddy,
Map message )
{
buddyData buddy_data = buddyAlive( buddy );
int type = ((Long)message.get( "type" )).intValue();
Map msg = (Map)message.get( "msg" );
return( buddy_data.receiveMessage( type, msg ));
}
public void
messageFailed(
BuddyPluginBuddy buddy,
Throwable cause )
{
log( "Failed to send message to " + buddy.getName(), cause );
buddyDead( buddy );
}
protected buddyData
buddyAlive(
BuddyPluginBuddy buddy )
{
buddyData buddy_data = addBuddy( buddy );
buddy_data.setAlive( true );
return( buddy_data );
}
protected void
buddyDead(
BuddyPluginBuddy buddy )
{
buddyData buddy_data = getBuddyData( buddy );
if ( buddy_data != null ){
buddy_data.setAlive( false );
}
}
protected boolean
okToTrack(
Download d )
{
Torrent t = d.getTorrent();
if ( t == null ){
return( false );
}
// only track private torrents if we have successfully received peers from tracker
// which means we have the torrent legitimately. As this rule is enforced by both
// ends of the tracking operation it means we will only track between peers that
// both have a legitimate copy of the torrent.
if ( t.isPrivate()){
DownloadAnnounceResult announce = d.getLastAnnounceResult();
if ( announce == null ||
announce.getResponseType() != DownloadAnnounceResult.RT_SUCCESS ||
announce.getPeers().length < 2 ){
return( false );
}
}
int state = d.getState();
return( state != Download.ST_ERROR &&
state != Download.ST_STOPPING &&
state != Download.ST_STOPPED );
}
protected void
log(
String str )
{
plugin.log( "Tracker: " + str );
}
protected void
log(
String str,
boolean verbose )
{
if ( verbose ){
if ( Constants.isCVSVersion()){
log( str );
}
}else{
log( str );
}
}
protected void
log(
String str,
Throwable e )
{
plugin.log( "Tracker: " + str, e );
}
private class
buddyData
{
private BuddyPluginBuddy buddy;
private Set downloads_sent;
private int downloads_sent_id;
private Map downloads_in_common;
private boolean buddy_seeding_only;
private int consecutive_fails;
private long last_fail;
private String current_ip;
protected
buddyData(
BuddyPluginBuddy _buddy )
{
buddy = _buddy;
}
protected void
updateIP()
{
current_ip = buddy.getAdjustedIP().getHostAddress();
log( "IP set to " + current_ip );
}
protected boolean
hasIPChanged()
{
InetAddress latest_ip = buddy.getAdjustedIP();
if ( latest_ip == null && current_ip == null ){
return( false );
}else if ( latest_ip == null || current_ip == null ){
return( true );
}else{
return( !current_ip.equals( latest_ip.getHostAddress()));
}
}
protected String
getIP()
{
return( current_ip );
}
protected boolean
hasDownloadsInCommon()
{
synchronized( this ){
return( downloads_in_common != null );
}
}
protected void
setAlive(
boolean alive )
{
synchronized( this ){
if ( alive ){
consecutive_fails = 0;
last_fail = 0;
}else{
consecutive_fails++;
last_fail = SystemTime.getMonotonousTime();
}
}
}
protected void
updateLocal(
Set downloads,
int id,
Map diff_map )
{
if ( consecutive_fails > 0 ){
long retry_millis = RETRY_SEND_MIN;
for (int i=0;i<consecutive_fails-1;i++){
retry_millis <<= 2;
if ( retry_millis > RETRY_SEND_MAX ){
retry_millis = RETRY_SEND_MAX;
break;
}
}
long now = SystemTime.getMonotonousTime();
if ( now - last_fail >= retry_millis ){
last_fail = now; // assume we're going to fail so we avoid
// falling through here multiple times before
// actuallt failing again
downloads_sent = null;
downloads_sent_id = 0;
}
}
// first check to see if completion state changed for any common downloads
List comp_changed = new ArrayList();
synchronized( this ){
if ( downloads_in_common != null ){
Iterator it = downloads_in_common.entrySet().iterator();
while( it.hasNext()){
Map.Entry entry = (Map.Entry)it.next();
Download d = (Download)entry.getKey();
buddyDownloadData bdd = (buddyDownloadData)entry.getValue();
boolean local_complete = d.isComplete( false );
if ( local_complete != bdd.isLocalComplete()){
bdd.setLocalComplete( local_complete );
comp_changed.add( d );
}
}
}
}
if ( comp_changed.size() > 0 ){
byte[][] change_details = exportFullIDs( comp_changed );
if( change_details[0].length > 0 ){
Map msg = new HashMap();
msg.put( "seeding", new Long( seeding_only?1:0 ));
msg.put( "change", change_details[0] );
msg.put( "change_s", change_details[1] );
sendMessage( buddy, REQUEST_TRACKER_CHANGE, msg );
}
}
if ( id == downloads_sent_id ){
return;
}
Long key = new Long(((long)id) << 32 | (long)downloads_sent_id);
Object[] diffs = (Object[])diff_map.get( key );
boolean incremental = downloads_sent != null;
byte[] added_bytes;
byte[] removed_bytes;
if ( diffs == null ){
List added;
List removed = new ArrayList();
if ( downloads_sent == null ){
added = new ArrayList( downloads );
}else{
added = new ArrayList();
Iterator it1 = downloads.iterator();
while( it1.hasNext()){
Download download = (Download)it1.next();
if ( okToTrack( download )){
if ( !downloads_sent.contains( download )){
added.add( download );
}
}
}
Iterator it2 = downloads_sent.iterator();
while( it2.hasNext()){
Download download = (Download)it2.next();
if ( !downloads.contains( download )){
removed.add( download );
}
}
}
added_bytes = exportShortIDs( added );
removed_bytes = exportFullIDs( removed )[0];
diff_map.put( key, new Object[]{ added_bytes, removed_bytes });
}else{
added_bytes = (byte[])diffs[0];
removed_bytes = (byte[])diffs[1];
}
downloads_sent = downloads;
downloads_sent_id = id;
if ( added_bytes.length == 0 && removed_bytes.length == 0 ){
return;
}
Map msg = new HashMap();
if ( added_bytes.length > 0 ){
msg.put( "added", added_bytes );
}
if ( removed_bytes.length > 0 ){
msg.put( "removed", removed_bytes );
}
msg.put( "inc", new Long( incremental?1:0 ));
msg.put( "seeding", new Long( seeding_only?1:0 ));
sendMessage( buddy, REQUEST_TRACKER_SUMMARY, msg );
}
protected Map
updateRemote(
Map msg )
{
List added = importShortIDs((byte[])msg.get( "added" ));
Map reply = new HashMap();
byte[][] add_details = exportFullIDs( added );
if( add_details[0].length > 0 ){
reply.put( "added", add_details[0] );
reply.put( "added_s", add_details[1] );
}
synchronized( this ){
if ( downloads_in_common != null ){
Map removed = importFullIDs( (byte[])msg.get( "removed" ), null );
Iterator it = removed.keySet().iterator();
while( it.hasNext()){
Download d = (Download)it.next();
if ( downloads_in_common.remove( d ) != null ){
log( "Removed " + d.getName() + " common download", false, true );
}
}
if ( downloads_in_common.size() == 0 ){
downloads_in_common = null;
}
}
}
return( reply );
}
protected void
updateCommonDownloads(
Map downloads,
boolean incremental )
{
synchronized( this ){
if ( downloads_in_common == null ){
downloads_in_common = new HashMap();
}else{
// if not incremental then remove any downloads that no longer
// are in common
if ( !incremental ){
Iterator it = downloads_in_common.keySet().iterator();
while( it.hasNext()){
Download download = (Download)it.next();
if ( !downloads.containsKey( download )){
log( "Removing " + download.getName() + " from common downloads", false, true );
it.remove();
}
}
}
}
Iterator it = downloads.entrySet().iterator();
while( it.hasNext()){
Map.Entry entry = (Map.Entry)it.next();
Download d = (Download)entry.getKey();
buddyDownloadData bdd = (buddyDownloadData)entry.getValue();
buddyDownloadData existing = (buddyDownloadData)downloads_in_common.get( d );
if ( existing == null ){
log( "Adding " + d.getName() + " to common downloads (bdd=" + bdd.getString() + ")", false, true );
downloads_in_common.put( d, bdd );
}else{
boolean old_rc = existing.isRemoteComplete();
boolean new_rc = bdd.isRemoteComplete();
if ( old_rc != new_rc ){
existing.setRemoteComplete( new_rc );
log( "Changing " + d.getName() + " common downloads (bdd=" + existing.getString() + ")", false, true );
}
}
}
if ( downloads_in_common.size() == 0 ){
downloads_in_common = null;
}
}
}
protected void
updateStatus()
{
Map msg = new HashMap();
msg.put( "seeding", new Long( seeding_only?1:0 ));
sendMessage( buddy, REQUEST_TRACKER_STATUS, msg );
}
protected Map
receiveMessage(
int type,
Map msg_in )
{
int reply_type = -1;
Map msg_out = null;
Long l_seeding = (Long)msg_in.get( "seeding" );
if( l_seeding != null ){
boolean old = buddy_seeding_only;
buddy_seeding_only = l_seeding.intValue() == 1;
if ( old != buddy_seeding_only ){
log( "Seeding only changed to " + buddy_seeding_only );
}
}
if ( type == REQUEST_TRACKER_SUMMARY ){
reply_type = REPLY_TRACKER_SUMMARY;
msg_out = updateRemote( msg_in );
msg_out.put( "inc", msg_in.get( "inc" ));
}else if ( type == REQUEST_TRACKER_STATUS ){
reply_type = REPLY_TRACKER_STATUS;
}else if ( type == REQUEST_TRACKER_CHANGE ){
reply_type = REPLY_TRACKER_STATUS;
Map downloads = importFullIDs( (byte[])msg_in.get( "changed" ), (byte[])msg_in.get( "changed_s" ) );
updateCommonDownloads( downloads, true );
}else if ( type == REQUEST_TRACKER_ADD ){
reply_type = REPLY_TRACKER_ADD;
Map downloads = importFullIDs( (byte[])msg_in.get( "added" ), (byte[])msg_in.get( "added_s" ) );
updateCommonDownloads( downloads, true );
}else if ( type == REPLY_TRACKER_SUMMARY ){
// full hashes on reply
byte[] possible_matches = (byte[])msg_in.get( "added" );
byte[] possible_match_states = (byte[])msg_in.get( "added_s" );
boolean incremental = ((Long)msg_in.get( "inc" )).intValue() == 1;
if ( possible_matches != null && possible_match_states != null ){
Map downloads = importFullIDs( possible_matches, possible_match_states );
if ( downloads.size() > 0 ){
updateCommonDownloads( downloads, incremental );
byte[][] common_details = exportFullIDs( new ArrayList( downloads.keySet()));
if( common_details[0].length > 0 ){
Map msg = new HashMap();
msg.put( "seeding", new Long( seeding_only?1:0 ));
msg.put( "added", common_details[0] );
msg.put( "added_s", common_details[1] );
sendMessage( buddy, REQUEST_TRACKER_ADD, msg );
}
}
}
}else if ( type == REPLY_TRACKER_CHANGE ||
type == REPLY_TRACKER_STATUS ||
type == REPLY_TRACKER_ADD ){
// nothing interesting in reply for these
}else{
log( "Unrecognised type " + type );
}
if ( reply_type != -1 ){
Map reply = new HashMap();
reply.put( "type", new Long( reply_type ));
if ( msg_out == null ){
msg_out = new HashMap();
}
msg_out.put( "seeding", new Long( seeding_only?1:0 ));
reply.put( "msg", msg_out );
return( reply );
}
return( null );
}
protected byte[]
exportShortIDs(
List downloads )
{
byte[] res = new byte[ SHORT_ID_SIZE * downloads.size() ];
for (int i=0;i<downloads.size();i++ ){
Download download = (Download)downloads.get(i);
downloadData download_data = (downloadData)download.getUserData( BuddyPluginTracker.class );
if ( download_data != null ){
System.arraycopy(
download_data.getID().getBytes(),
0,
res,
i * SHORT_ID_SIZE,
SHORT_ID_SIZE );
}
}
return( res );
}
protected List
importShortIDs(
byte[] ids )
{
List res = new ArrayList();
if ( ids != null ){
synchronized( tracked_downloads ){
for (int i=0;i<ids.length;i+= SHORT_ID_SIZE ){
List dls = (List)short_id_map.get( new HashWrapper( ids, i, SHORT_ID_SIZE ));
if ( dls != null ){
res.addAll( dls );
}
}
}
}
return( res );
}
protected byte[][]
exportFullIDs(
List downloads )
{
byte[] hashes = new byte[ FULL_ID_SIZE * downloads.size() ];
byte[] states = new byte[ downloads.size()];
for (int i=0;i<downloads.size();i++ ){
Download download = (Download)downloads.get(i);
downloadData download_data = (downloadData)download.getUserData( BuddyPluginTracker.class );
if ( download_data != null ){
System.arraycopy(
download_data.getID().getBytes(),
0,
hashes,
i * FULL_ID_SIZE,
FULL_ID_SIZE );
states[i] = download.isComplete( false )?(byte)0x01:(byte)0x00;
}
}
return( new byte[][]{ hashes, states });
}
protected Map
importFullIDs(
byte[] ids,
byte[] states )
{
Map res = new HashMap();
if ( ids != null ){
synchronized( tracked_downloads ){
for (int i=0;i<ids.length;i+= FULL_ID_SIZE ){
Download dl = (Download)full_id_map.get( new HashWrapper( ids, i, FULL_ID_SIZE ));
if ( dl != null ){
buddyDownloadData bdd = new buddyDownloadData( dl );
if ( states != null ){
bdd.setRemoteComplete(( states[i/FULL_ID_SIZE] & 0x01 ) != 0 );
}
res.put( dl, bdd );
}
}
}
}
return( res );
}
protected Map
getDownloadsToTrack()
{
Map res = new HashMap();
if ( seeding_only == buddy_seeding_only ){
log( "Not tracking, buddy and me both " + (seeding_only?"seeding":"downloading"), true, false );
return( res );
}
long now = SystemTime.getMonotonousTime();
synchronized( this ){
if ( downloads_in_common == null ){
log( "Not tracking, buddy has nothing in common", true, false );
return( res );
}
Iterator it = downloads_in_common.entrySet().iterator();
while( it.hasNext()){
Map.Entry entry = (Map.Entry)it.next();
Download d = (Download)entry.getKey();
buddyDownloadData bdd = (buddyDownloadData)entry.getValue();
if ( d.isComplete( false ) && bdd.isRemoteComplete()){
// both complete, nothing to do!
log( d.getName() + " - not tracking, both complete", true, true );
}else{
long last_track = bdd.getTrackTime();
if ( last_track == 0 ||
now - last_track >= TRACK_INTERVAL ){
log( d.getName() + " - tracking", false, true );
bdd.setTrackTime( now );
res.put( d, new Boolean( true ));
}else{
res.put( d, new Boolean( false ));
}
}
}
}
return( res );
}
protected void
resetTracking(
Download download )
{
synchronized( this ){
if ( downloads_in_common == null ){
return;
}
buddyDownloadData bdd = (buddyDownloadData)downloads_in_common.get( download );
if ( bdd != null ){
bdd.resetTrackTime();
}
}
}
protected void
log(
String str )
{
BuddyPluginTracker.this.log( buddy.getName() + ": " + str );
}
protected void
log(
String str,
boolean verbose,
boolean no_buddy )
{
BuddyPluginTracker.this.log( (no_buddy?"":( buddy.getName() + ": ")) + str, verbose );
}
}
private static class
buddyDownloadData
{
private boolean local_is_complete;
private boolean remote_is_complete;
private long last_track;
protected
buddyDownloadData(
Download download )
{
local_is_complete = download.isComplete( false );
}
protected void
setLocalComplete(
boolean b )
{
local_is_complete = b;
}
protected boolean
isLocalComplete()
{
return( local_is_complete );
}
protected void
setRemoteComplete(
boolean b )
{
remote_is_complete = b;
}
protected boolean
isRemoteComplete()
{
return( remote_is_complete );
}
protected void
setTrackTime(
long time )
{
last_track = time;
}
protected long
getTrackTime()
{
return( last_track );
}
protected void
resetTrackTime()
{
last_track = 0;
}
protected String
getString()
{
return( "lic=" + local_is_complete + ",ric=" + remote_is_complete + ",lt=" + last_track );
}
}
private static class
downloadData
{
private static final byte[] IV = {(byte)0x7A, (byte)0x7A, (byte)0xAD, (byte)0xAB, (byte)0x8E, (byte)0xBF, (byte)0xCD, (byte)0x39, (byte)0x87, (byte)0x0, (byte)0xA4, (byte)0xB8, (byte)0xFE, (byte)0x40, (byte)0xA2, (byte)0xE8 };
private HashWrapper id;
protected
downloadData(
Download download )
{
Torrent t = download.getTorrent();
if ( t != null ){
byte[] hash = t.getHash();
SHA1 sha1 = new SHA1();
sha1.update( ByteBuffer.wrap( IV ));
sha1.update( ByteBuffer.wrap( hash ));
id = new HashWrapper( sha1.digest() );
}
}
protected HashWrapper
getID()
{
return( id );
}
}
}