1 /* 2 * Created on 31-Jan-2005 3 * Created by Paul Gardner 4 * Copyright (C) Azureus Software, Inc, All Rights Reserved. 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License 8 * as published by the Free Software Foundation; either version 2 9 * of the License, or (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 17 * 18 */ 19 20 package com.aelitis.azureus.plugins.tracker.dht; 21 22 23 import java.net.InetSocketAddress; 24 import java.net.URL; 25 import java.net.UnknownHostException; 26 import java.util.*; 27 28 import org.gudy.azureus2.core3.config.COConfigurationManager; 29 import org.gudy.azureus2.core3.internat.MessageText; 30 import org.gudy.azureus2.core3.peer.PEPeerManager; 31 import org.gudy.azureus2.core3.peer.PEPeerSource; 32 import org.gudy.azureus2.core3.tracker.protocol.PRHelpers; 33 import org.gudy.azureus2.core3.util.AEMonitor; 34 import org.gudy.azureus2.core3.util.AENetworkClassifier; 35 import org.gudy.azureus2.core3.util.AESemaphore; 36 import org.gudy.azureus2.core3.util.AEThread2; 37 import org.gudy.azureus2.core3.util.ByteFormatter; 38 import org.gudy.azureus2.core3.util.Constants; 39 import org.gudy.azureus2.core3.util.Debug; 40 import org.gudy.azureus2.core3.util.SystemTime; 41 import org.gudy.azureus2.core3.util.TimeFormatter; 42 import org.gudy.azureus2.core3.util.TorrentUtils; 43 import org.gudy.azureus2.plugins.Plugin; 44 import org.gudy.azureus2.plugins.PluginInterface; 45 import org.gudy.azureus2.plugins.PluginListener; 46 import org.gudy.azureus2.plugins.download.Download; 47 import org.gudy.azureus2.plugins.download.DownloadAnnounceResult; 48 import org.gudy.azureus2.plugins.download.DownloadAnnounceResultPeer; 49 import org.gudy.azureus2.plugins.download.DownloadAttributeListener; 50 import org.gudy.azureus2.plugins.download.DownloadListener; 51 import org.gudy.azureus2.plugins.download.DownloadManagerListener; 52 import org.gudy.azureus2.plugins.download.DownloadScrapeResult; 53 import org.gudy.azureus2.plugins.download.DownloadTrackerListener; 54 import org.gudy.azureus2.plugins.logging.LoggerChannel; 55 import org.gudy.azureus2.plugins.logging.LoggerChannelListener; 56 import org.gudy.azureus2.plugins.peers.Peer; 57 import org.gudy.azureus2.plugins.peers.PeerManager; 58 import org.gudy.azureus2.plugins.torrent.Torrent; 59 import org.gudy.azureus2.plugins.torrent.TorrentAttribute; 60 import org.gudy.azureus2.plugins.ui.UIManager; 61 import org.gudy.azureus2.plugins.ui.config.BooleanParameter; 62 import org.gudy.azureus2.plugins.ui.config.ConfigSection; 63 import org.gudy.azureus2.plugins.ui.config.IntParameter; 64 import org.gudy.azureus2.plugins.ui.config.Parameter; 65 import org.gudy.azureus2.plugins.ui.config.ParameterListener; 66 import org.gudy.azureus2.plugins.ui.model.BasicPluginConfigModel; 67 import org.gudy.azureus2.plugins.ui.model.BasicPluginViewModel; 68 import org.gudy.azureus2.plugins.utils.DelayedTask; 69 import org.gudy.azureus2.plugins.utils.UTTimerEvent; 70 import org.gudy.azureus2.plugins.utils.UTTimerEventPerformer; 71 import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils; 72 73 import com.aelitis.azureus.core.networkmanager.NetworkManager; 74 import com.aelitis.azureus.core.tracker.TrackerPeerSource; 75 import com.aelitis.azureus.core.tracker.TrackerPeerSourceAdapter; 76 import com.aelitis.azureus.plugins.I2PHelpers; 77 import com.aelitis.azureus.plugins.dht.*; 78 79 /** 80 * @author parg 81 * 82 */ 83 84 public class 85 DHTTrackerPlugin 86 implements Plugin, DownloadListener, DownloadAttributeListener, DownloadTrackerListener 87 { 88 public static Object DOWNLOAD_USER_DATA_I2P_SCRAPE_KEY = new Object(); 89 90 private static final String PLUGIN_NAME = "Distributed Tracker"; 91 private static final String PLUGIN_CONFIGSECTION_ID = "plugins.dhttracker"; 92 private static final String PLUGIN_RESOURCE_ID = "ConfigView.section.plugins.dhttracker"; 93 94 private static final int ANNOUNCE_TIMEOUT = 2*60*1000; 95 private static final int ANNOUNCE_DERIVED_TIMEOUT = 60*1000; // spend less time on these 96 private static final int SCRAPE_TIMEOUT = 30*1000; 97 98 private static final int ANNOUNCE_MIN_DEFAULT = 2*60*1000; 99 private static final int ANNOUNCE_MAX = 60*60*1000; 100 private static final int ANNOUNCE_MAX_DERIVED_ONLY = 30*60*1000; 101 102 private static final int INTERESTING_CHECK_PERIOD = 4*60*60*1000; 103 private static final int INTERESTING_INIT_RAND_OURS = 5*60*1000; 104 private static final int INTERESTING_INIT_MIN_OURS = 2*60*1000; 105 private static final int INTERESTING_INIT_RAND_OTHERS = 30*60*1000; 106 private static final int INTERESTING_INIT_MIN_OTHERS = 5*60*1000; 107 108 private static final int INTERESTING_DHT_CHECK_PERIOD = 1*60*60*1000; 109 private static final int INTERESTING_DHT_INIT_RAND = 5*60*1000; 110 private static final int INTERESTING_DHT_INIT_MIN = 2*60*1000; 111 112 113 private static final int INTERESTING_AVAIL_MAX = 8; // won't pub if more 114 private static final int INTERESTING_PUB_MAX_DEFAULT = 30; // limit on pubs 115 116 private static final int REG_TYPE_NONE = 1; 117 private static final int REG_TYPE_FULL = 2; 118 private static final int REG_TYPE_DERIVED = 3; 119 120 private static final int LIMITED_TRACK_SIZE = 16; 121 122 private static final boolean TRACK_NORMAL_DEFAULT = true; 123 private static final boolean TRACK_LIMITED_DEFAULT = true; 124 125 private static final boolean TEST_ALWAYS_TRACK = false; 126 127 public static final int NUM_WANT = 30; // Limit to ensure replies fit in 1 packet 128 129 private static final long start_time = SystemTime.getCurrentTime(); 130 131 private static final Object DL_DERIVED_METRIC_KEY = new Object(); 132 private static final int DL_DERIVED_MIN_TRACK = 5; 133 private static final int DL_DERIVED_MAX_TRACK = 20; 134 private static final int DIRECT_INJECT_PEER_MAX = 5; 135 136 //private static final boolean ADD_ASN_DERIVED_TARGET = false; 137 //private static final boolean ADD_NETPOS_DERIVED_TARGETS = false; 138 139 private static URL DEFAULT_URL; 140 141 static{ 142 try{ 143 DEFAULT_URL = new URL( "dht:" ); 144 145 }catch( Throwable e ){ 146 147 Debug.printStackTrace(e); 148 } 149 } 150 151 private PluginInterface plugin_interface; 152 private BasicPluginViewModel model; 153 private DHTPlugin dht; 154 155 private TorrentAttribute ta_networks; 156 private TorrentAttribute ta_peer_sources; 157 158 private Map<Download,Long> interesting_downloads = new HashMap<Download,Long>(); 159 private int interesting_published = 0; 160 private int interesting_pub_max = INTERESTING_PUB_MAX_DEFAULT; 161 private Map<Download,int[]> running_downloads = new HashMap<Download,int[]>(); 162 private Map<Download,int[]> run_data_cache = new HashMap<Download,int[]>(); 163 private Map<Download,RegistrationDetails> registered_downloads = new HashMap<Download,RegistrationDetails>(); 164 165 private Map<Download,Boolean> limited_online_tracking = new HashMap<Download,Boolean>(); 166 private Map<Download,Long> query_map = new HashMap<Download,Long>(); 167 168 private Map<Download,Integer> in_progress = new HashMap<Download,Integer>(); 169 170 // external config to limit plugin op to pure decentralised only 171 172 private boolean track_only_decentralsed = COConfigurationManager.getBooleanParameter( "dhtplugin.track.only.decentralised", false ); 173 174 private BooleanParameter track_normal_when_offline; 175 private BooleanParameter track_limited_when_online; 176 177 private long current_announce_interval = ANNOUNCE_MIN_DEFAULT; 178 179 private LoggerChannel log; 180 181 private Map<Download,int[]> scrape_injection_map = new WeakHashMap<Download,int[]>(); 182 183 private Random random = new Random(); 184 private boolean is_running; 185 186 private AEMonitor this_mon = new AEMonitor( "DHTTrackerPlugin" ); 187 188 //private DHTNetworkPosition[] current_network_positions; 189 //private long last_net_pos_time; 190 191 private AESemaphore initialised_sem = new AESemaphore( "DHTTrackerPlugin:init" ); 192 193 private DHTTrackerPluginAlt alt_lookup_handler; 194 195 private boolean disable_put; 196 197 { COConfigurationManager.addAndFireParameterListeners( new String[]{ R, R, }, new org.gudy.azureus2.core3.config.ParameterListener() { public void parameterChanged( String parameter_name ) { boolean enable_proxy = COConfigurationManager.getBooleanParameter(R); boolean enable_socks = COConfigurationManager.getBooleanParameter(R); disable_put = enable_proxy && enable_socks; } })198 COConfigurationManager.addAndFireParameterListeners( 199 new String[]{ 200 "Enable.Proxy", 201 "Enable.SOCKS", 202 }, 203 new org.gudy.azureus2.core3.config.ParameterListener() 204 { 205 public void 206 parameterChanged( 207 String parameter_name ) 208 { 209 boolean enable_proxy = COConfigurationManager.getBooleanParameter("Enable.Proxy"); 210 boolean enable_socks = COConfigurationManager.getBooleanParameter("Enable.SOCKS"); 211 212 disable_put = enable_proxy && enable_socks; 213 } 214 }); 215 } 216 217 public static void load( PluginInterface plugin_interface )218 load( 219 PluginInterface plugin_interface ) 220 { 221 plugin_interface.getPluginProperties().setProperty( "plugin.version", "1.0" ); 222 plugin_interface.getPluginProperties().setProperty( "plugin.name", PLUGIN_NAME ); 223 } 224 225 public void initialize( PluginInterface _plugin_interface )226 initialize( 227 PluginInterface _plugin_interface ) 228 { 229 plugin_interface = _plugin_interface; 230 231 log = plugin_interface.getLogger().getTimeStampedChannel(PLUGIN_NAME); 232 233 ta_networks = plugin_interface.getTorrentManager().getAttribute( TorrentAttribute.TA_NETWORKS ); 234 ta_peer_sources = plugin_interface.getTorrentManager().getAttribute( TorrentAttribute.TA_PEER_SOURCES ); 235 236 UIManager ui_manager = plugin_interface.getUIManager(); 237 238 model = 239 ui_manager.createBasicPluginViewModel( PLUGIN_RESOURCE_ID ); 240 241 model.setConfigSectionID(PLUGIN_CONFIGSECTION_ID); 242 243 BasicPluginConfigModel config = 244 ui_manager.createBasicPluginConfigModel( ConfigSection.SECTION_PLUGINS, 245 PLUGIN_CONFIGSECTION_ID); 246 247 track_normal_when_offline = config.addBooleanParameter2( "dhttracker.tracknormalwhenoffline", "dhttracker.tracknormalwhenoffline", TRACK_NORMAL_DEFAULT ); 248 249 track_limited_when_online = config.addBooleanParameter2( "dhttracker.tracklimitedwhenonline", "dhttracker.tracklimitedwhenonline", TRACK_LIMITED_DEFAULT ); 250 251 track_limited_when_online.addListener( 252 new ParameterListener() 253 { 254 public void 255 parameterChanged( 256 Parameter param ) 257 { 258 configChanged(); 259 } 260 }); 261 262 track_normal_when_offline.addListener( 263 new ParameterListener() 264 { 265 public void 266 parameterChanged( 267 Parameter param ) 268 { 269 track_limited_when_online.setEnabled( track_normal_when_offline.getValue()); 270 271 configChanged(); 272 } 273 }); 274 275 if ( !track_normal_when_offline.getValue()){ 276 277 track_limited_when_online.setEnabled( false ); 278 } 279 280 interesting_pub_max = plugin_interface.getPluginconfig().getPluginIntParameter( "dhttracker.presencepubmax", INTERESTING_PUB_MAX_DEFAULT ); 281 282 283 if ( !TRACK_NORMAL_DEFAULT ){ 284 // should be TRUE by default 285 System.out.println( "**** DHT Tracker default set for testing purposes ****" ); 286 } 287 288 BooleanParameter enable_alt = config.addBooleanParameter2( "dhttracker.enable_alt", "dhttracker.enable_alt", true ); 289 290 IntParameter alt_port = config.addIntParameter2( "dhttracker.alt_port", "dhttracker.alt_port", 0, 0, 65535 ); 291 292 enable_alt.addEnabledOnSelection( alt_port ); 293 294 config.createGroup( "dhttracker.alt_group", new Parameter[]{ enable_alt,alt_port }); 295 296 if ( enable_alt.getValue()){ 297 298 alt_lookup_handler = new DHTTrackerPluginAlt( alt_port.getValue()); 299 } 300 301 model.getActivity().setVisible( false ); 302 model.getProgress().setVisible( false ); 303 304 model.getLogArea().setMaximumSize( 80000 ); 305 306 log.addListener( 307 new LoggerChannelListener() 308 { 309 public void 310 messageLogged( 311 int type, 312 String message ) 313 { 314 model.getLogArea().appendText( message+"\n"); 315 } 316 317 public void 318 messageLogged( 319 String str, 320 Throwable error ) 321 { 322 model.getLogArea().appendText( error.toString()+"\n"); 323 } 324 }); 325 326 model.getStatus().setText( MessageText.getString( "ManagerItem.initializing" )); 327 328 log.log( "Waiting for Distributed Database initialisation" ); 329 330 plugin_interface.addListener( 331 new PluginListener() 332 { 333 public void 334 initializationComplete() 335 { 336 boolean release_now = true; 337 338 try{ 339 final PluginInterface dht_pi = 340 plugin_interface.getPluginManager().getPluginInterfaceByClass( 341 DHTPlugin.class ); 342 343 if ( dht_pi != null ){ 344 345 dht = (DHTPlugin)dht_pi.getPlugin(); 346 347 final DelayedTask dt = 348 plugin_interface.getUtilities().createDelayedTask( 349 new Runnable() 350 { 351 352 public void 353 run() 354 { 355 AEThread2 t = 356 new AEThread2( "DHTTrackerPlugin:init", true ) 357 { 358 public void 359 run() 360 { 361 try{ 362 363 if ( dht.isEnabled()){ 364 365 log.log( "DDB Available" ); 366 367 model.getStatus().setText( MessageText.getString( "DHTView.activity.status.false" )); 368 369 initialise(); 370 371 }else{ 372 373 log.log( "DDB Disabled" ); 374 375 model.getStatus().setText( MessageText.getString( "dht.status.disabled" )); 376 377 notRunning(); 378 } 379 }catch( Throwable e ){ 380 381 log.log( "DDB Failed", e ); 382 383 model.getStatus().setText( MessageText.getString( "DHTView.operations.failed" )); 384 385 notRunning(); 386 387 }finally{ 388 389 initialised_sem.releaseForever(); 390 } 391 } 392 }; 393 394 t.start(); 395 } 396 }); 397 398 dt.queue(); 399 400 release_now = false; 401 402 }else{ 403 404 log.log( "DDB Plugin missing" ); 405 406 model.getStatus().setText( MessageText.getString( "DHTView.operations.failed" ) ); 407 408 notRunning(); 409 } 410 }finally{ 411 412 if ( release_now ){ 413 414 initialised_sem.releaseForever(); 415 } 416 } 417 } 418 419 public void 420 closedownInitiated() 421 { 422 423 } 424 425 public void 426 closedownComplete() 427 { 428 429 } 430 }); 431 } 432 433 protected void notRunning()434 notRunning() 435 { 436 plugin_interface.getDownloadManager().addListener( 437 new DownloadManagerListener() 438 { 439 public void 440 downloadAdded( 441 final Download download ) 442 { 443 addDownload( download ); 444 } 445 446 public void 447 downloadRemoved( 448 Download download ) 449 { 450 removeDownload( download ); 451 } 452 }); 453 } 454 455 protected void initialise()456 initialise() 457 { 458 is_running = true; 459 460 plugin_interface.getDownloadManager().addListener( 461 new DownloadManagerListener() 462 { 463 public void 464 downloadAdded( 465 Download download ) 466 { 467 addDownload( download ); 468 } 469 470 public void 471 downloadRemoved( 472 Download download ) 473 { 474 removeDownload( download ); 475 } 476 }); 477 478 plugin_interface.getUtilities().createTimer("DHT Tracker", true ).addPeriodicEvent( 479 15000, 480 new UTTimerEventPerformer() 481 { 482 private int ticks; 483 484 private String prev_alt_status = ""; 485 486 public void 487 perform( 488 UTTimerEvent event) 489 { 490 ticks++; 491 492 processRegistrations( ticks%8==0 ); 493 494 if ( ticks == 2 || ticks%4==0 ){ 495 496 processNonRegistrations(); 497 } 498 499 if ( alt_lookup_handler != null ){ 500 501 if ( ticks % 4 == 0 ){ 502 503 String alt_status = alt_lookup_handler.getString(); 504 505 if ( !alt_status.equals( prev_alt_status )){ 506 507 log.log( "Alternative stats: " + alt_status ); 508 509 prev_alt_status = alt_status; 510 } 511 } 512 } 513 } 514 }); 515 } 516 517 public void waitUntilInitialised()518 waitUntilInitialised() 519 { 520 initialised_sem.reserve(); 521 } 522 523 public boolean isRunning()524 isRunning() 525 { 526 return( is_running ); 527 } 528 529 public void addDownload( final Download download )530 addDownload( 531 final Download download ) 532 { 533 Torrent torrent = download.getTorrent(); 534 535 boolean is_decentralised = false; 536 537 if ( torrent != null ){ 538 539 is_decentralised = TorrentUtils.isDecentralised( torrent.getAnnounceURL()); 540 } 541 542 // bail on our low noise ones, these don't require decentralised tracking unless that's what they are 543 544 if ( download.getFlag( Download.FLAG_LOW_NOISE ) && !is_decentralised ){ 545 546 return; 547 } 548 549 if ( track_only_decentralsed ){ 550 551 if ( torrent != null ){ 552 553 if ( !is_decentralised ){ 554 555 return; 556 } 557 } 558 } 559 560 if ( is_running ){ 561 562 String[] networks = download.getListAttribute( ta_networks ); 563 564 if ( torrent != null && networks != null ){ 565 566 boolean public_net = false; 567 568 for (int i=0;i<networks.length;i++){ 569 570 if ( networks[i].equalsIgnoreCase( "Public" )){ 571 572 public_net = true; 573 574 break; 575 } 576 } 577 578 if ( public_net && !torrent.isPrivate()){ 579 580 boolean our_download = torrent.wasCreatedByUs(); 581 582 long delay; 583 584 if ( our_download ){ 585 586 if ( download.getCreationTime() > start_time ){ 587 588 delay = 0; 589 590 }else{ 591 592 delay = plugin_interface.getUtilities().getCurrentSystemTime() + 593 INTERESTING_INIT_MIN_OURS + 594 random.nextInt( INTERESTING_INIT_RAND_OURS ); 595 596 } 597 }else{ 598 599 int min; 600 int rand; 601 602 if ( TorrentUtils.isDecentralised( torrent.getAnnounceURL())){ 603 604 min = INTERESTING_DHT_INIT_MIN; 605 rand = INTERESTING_DHT_INIT_RAND; 606 607 }else{ 608 609 min = INTERESTING_INIT_MIN_OTHERS; 610 rand = INTERESTING_INIT_RAND_OTHERS; 611 } 612 613 delay = plugin_interface.getUtilities().getCurrentSystemTime() + 614 min + random.nextInt( rand ); 615 } 616 617 try{ 618 this_mon.enter(); 619 620 interesting_downloads.put( download, new Long( delay )); 621 622 }finally{ 623 624 this_mon.exit(); 625 } 626 } 627 } 628 629 download.addAttributeListener(DHTTrackerPlugin.this, ta_networks, DownloadAttributeListener.WRITTEN); 630 download.addAttributeListener(DHTTrackerPlugin.this, ta_peer_sources, DownloadAttributeListener.WRITTEN); 631 632 download.addTrackerListener( DHTTrackerPlugin.this ); 633 634 download.addListener( DHTTrackerPlugin.this ); 635 636 checkDownloadForRegistration( download, true ); 637 638 }else{ 639 640 if ( torrent != null && torrent.isDecentralised()){ 641 642 download.addListener( 643 new DownloadListener() 644 { 645 public void 646 stateChanged( 647 final Download download, 648 int old_state, 649 int new_state ) 650 { 651 int state = download.getState(); 652 653 if ( state == Download.ST_DOWNLOADING || 654 state == Download.ST_SEEDING ){ 655 656 download.setAnnounceResult( 657 new DownloadAnnounceResult() 658 { 659 public Download 660 getDownload() 661 { 662 return( download ); 663 } 664 665 public int 666 getResponseType() 667 { 668 return( DownloadAnnounceResult.RT_ERROR ); 669 } 670 671 public int 672 getReportedPeerCount() 673 { 674 return( 0 ); 675 } 676 677 678 public int 679 getSeedCount() 680 { 681 return( 0 ); 682 } 683 684 public int 685 getNonSeedCount() 686 { 687 return( 0 ); 688 } 689 690 public String 691 getError() 692 { 693 return( "Distributed Database Offline" ); 694 } 695 696 public URL 697 getURL() 698 { 699 return( download.getTorrent().getAnnounceURL()); 700 } 701 702 public DownloadAnnounceResultPeer[] 703 getPeers() 704 { 705 return( new DownloadAnnounceResultPeer[0] ); 706 } 707 708 public long 709 getTimeToWait() 710 { 711 return( 0 ); 712 } 713 714 public Map 715 getExtensions() 716 { 717 return( null ); 718 } 719 }); 720 } 721 } 722 723 public void 724 positionChanged( 725 Download download, 726 int oldPosition, 727 int newPosition ) 728 { 729 730 } 731 }); 732 733 734 download.setScrapeResult( 735 new DownloadScrapeResult() 736 { 737 public Download 738 getDownload() 739 { 740 return( download ); 741 } 742 743 public int 744 getResponseType() 745 { 746 return( RT_ERROR ); 747 } 748 749 public int 750 getSeedCount() 751 { 752 return( -1 ); 753 } 754 755 public int 756 getNonSeedCount() 757 { 758 return( -1 ); 759 } 760 761 public long 762 getScrapeStartTime() 763 { 764 return( SystemTime.getCurrentTime()); 765 } 766 767 public void 768 setNextScrapeStartTime( 769 long nextScrapeStartTime) 770 { 771 } 772 773 public long 774 getNextScrapeStartTime() 775 { 776 return( -1 ); 777 } 778 779 public String 780 getStatus() 781 { 782 return( "Distributed Database Offline" ); 783 } 784 785 public URL 786 getURL() 787 { 788 return( download.getTorrent().getAnnounceURL()); 789 } 790 }); 791 } 792 } 793 } 794 795 public void removeDownload( Download download )796 removeDownload( 797 Download download ) 798 { 799 if ( is_running ){ 800 download.removeTrackerListener( DHTTrackerPlugin.this ); 801 802 download.removeListener( DHTTrackerPlugin.this ); 803 804 try{ 805 this_mon.enter(); 806 807 interesting_downloads.remove( download ); 808 809 running_downloads.remove( download ); 810 811 run_data_cache.remove( download ); 812 813 limited_online_tracking.remove( download ); 814 815 }finally{ 816 817 this_mon.exit(); 818 } 819 }else{ 820 821 } 822 } 823 attributeEventOccurred(Download download, TorrentAttribute attr, int event_type)824 public void attributeEventOccurred(Download download, TorrentAttribute attr, int event_type) { 825 checkDownloadForRegistration(download, false); 826 } 827 828 public void scrapeResult( DownloadScrapeResult result )829 scrapeResult( 830 DownloadScrapeResult result ) 831 { 832 checkDownloadForRegistration( result.getDownload(), false ); 833 } 834 835 public void announceResult( DownloadAnnounceResult result )836 announceResult( 837 DownloadAnnounceResult result ) 838 { 839 checkDownloadForRegistration( result.getDownload(), false ); 840 } 841 842 843 protected void checkDownloadForRegistration( Download download, boolean first_time )844 checkDownloadForRegistration( 845 Download download, 846 boolean first_time ) 847 { 848 if ( download == null ){ 849 850 return; 851 } 852 853 boolean skip_log = false; 854 855 int state = download.getState(); 856 857 int register_type = REG_TYPE_NONE; 858 859 String register_reason; 860 861 Random random = new Random(); 862 /* 863 * Queued downloads are removed from the set to consider as we now have the "presence store" 864 * mechanism to ensure that there are a number of peers out there to provide torrent download 865 * if required. This has been done to avoid the large number of registrations that users with 866 * large numbers of queued torrents were getting. 867 */ 868 869 if ( state == Download.ST_DOWNLOADING || 870 state == Download.ST_SEEDING || 871 // state == Download.ST_QUEUED || 872 download.isPaused()){ // pause is a transitory state, don't dereg 873 874 String[] networks = download.getListAttribute( ta_networks ); 875 876 Torrent torrent = download.getTorrent(); 877 878 if ( torrent != null && networks != null ){ 879 880 boolean public_net = false; 881 882 for (int i=0;i<networks.length;i++){ 883 884 if ( networks[i].equalsIgnoreCase( "Public" )){ 885 886 public_net = true; 887 888 break; 889 } 890 } 891 892 if ( public_net && !torrent.isPrivate()){ 893 894 if ( torrent.isDecentralised()){ 895 896 // peer source not relevant for decentralised torrents 897 898 register_type = REG_TYPE_FULL; 899 900 register_reason = "decentralised"; 901 902 }else{ 903 904 if ( torrent.isDecentralisedBackupEnabled() || TEST_ALWAYS_TRACK ){ 905 906 String[] sources = download.getListAttribute( ta_peer_sources ); 907 908 boolean ok = false; 909 910 if ( sources != null ){ 911 912 for (int i=0;i<sources.length;i++){ 913 914 if ( sources[i].equalsIgnoreCase( "DHT")){ 915 916 ok = true; 917 918 break; 919 } 920 } 921 } 922 923 if ( !( ok || TEST_ALWAYS_TRACK )){ 924 925 register_reason = "decentralised peer source disabled"; 926 927 }else{ 928 // this will always be true since change to exclude queued... 929 930 boolean is_active = 931 state == Download.ST_DOWNLOADING || 932 state == Download.ST_SEEDING || 933 download.isPaused(); 934 935 if ( is_active ){ 936 937 register_type = REG_TYPE_DERIVED; 938 } 939 940 if( torrent.isDecentralisedBackupRequested() || TEST_ALWAYS_TRACK ){ 941 942 register_type = REG_TYPE_FULL; 943 944 register_reason = TEST_ALWAYS_TRACK?"testing always track":"torrent requests decentralised tracking"; 945 946 }else if ( track_normal_when_offline.getValue()){ 947 948 // only track if torrent's tracker is not available 949 950 if ( is_active ){ 951 952 DownloadAnnounceResult result = download.getLastAnnounceResult(); 953 954 if ( result == null || 955 result.getResponseType() == DownloadAnnounceResult.RT_ERROR || 956 TorrentUtils.isDecentralised(result.getURL())){ 957 958 register_type = REG_TYPE_FULL; 959 960 register_reason = "tracker unavailable (announce)"; 961 962 }else{ 963 964 register_reason = "tracker available (announce: " + result.getURL() + ")"; 965 } 966 }else{ 967 968 DownloadScrapeResult result = download.getLastScrapeResult(); 969 970 if ( result == null || 971 result.getResponseType() == DownloadScrapeResult.RT_ERROR || 972 TorrentUtils.isDecentralised(result.getURL())){ 973 974 register_type = REG_TYPE_FULL; 975 976 register_reason = "tracker unavailable (scrape)"; 977 978 }else{ 979 980 register_reason = "tracker available (scrape: " + result.getURL() + ")"; 981 } 982 } 983 984 if ( register_type != REG_TYPE_FULL && track_limited_when_online.getValue()){ 985 986 Boolean existing = (Boolean)limited_online_tracking.get( download ); 987 988 boolean track_it = false; 989 990 if ( existing != null ){ 991 992 track_it = existing.booleanValue(); 993 994 }else{ 995 996 DownloadScrapeResult result = download.getLastScrapeResult(); 997 998 if ( result != null&& 999 result.getResponseType() == DownloadScrapeResult.RT_SUCCESS ){ 1000 1001 int seeds = result.getSeedCount(); 1002 int leechers = result.getNonSeedCount(); 1003 1004 int swarm_size = seeds + leechers; 1005 1006 if ( swarm_size <= LIMITED_TRACK_SIZE ){ 1007 1008 track_it = true; 1009 1010 }else{ 1011 1012 track_it = random.nextInt( swarm_size ) < LIMITED_TRACK_SIZE; 1013 } 1014 1015 if ( track_it ){ 1016 1017 limited_online_tracking.put( download, new Boolean( track_it )); 1018 } 1019 } 1020 } 1021 1022 if( track_it ){ 1023 1024 register_type = REG_TYPE_FULL; 1025 1026 register_reason = "limited online tracking"; 1027 } 1028 } 1029 }else{ 1030 register_type = REG_TYPE_FULL; 1031 1032 register_reason = "peer source enabled"; 1033 } 1034 } 1035 }else{ 1036 1037 register_reason = "decentralised backup disabled for the torrent"; 1038 } 1039 } 1040 }else{ 1041 1042 register_reason = "not public"; 1043 } 1044 }else{ 1045 1046 register_reason = "torrent is broken"; 1047 } 1048 1049 if ( register_type == REG_TYPE_DERIVED ){ 1050 1051 if ( register_reason.length() == 0 ){ 1052 1053 register_reason = "derived"; 1054 1055 }else{ 1056 1057 register_reason = "derived (overriding ' " + register_reason + "')"; 1058 } 1059 } 1060 }else if ( state == Download.ST_STOPPED || 1061 state == Download.ST_ERROR ){ 1062 1063 register_reason = "not running"; 1064 1065 skip_log = true; 1066 1067 }else if ( state == Download.ST_QUEUED ){ 1068 1069 // leave in whatever state it current is (reg or not reg) to avoid thrashing 1070 // registrations when seeding rules are start/queueing downloads 1071 1072 register_reason = ""; 1073 1074 }else{ 1075 1076 register_reason = ""; 1077 } 1078 1079 if ( register_reason.length() > 0 ){ 1080 1081 try{ 1082 this_mon.enter(); 1083 1084 int[] run_data = running_downloads.get( download ); 1085 1086 if ( register_type != REG_TYPE_NONE ){ 1087 1088 if ( run_data == null ){ 1089 1090 log( download, "Monitoring '" + download.getName() + "': " + register_reason); 1091 1092 int[] cache = run_data_cache.remove( download ); 1093 1094 if ( cache == null ){ 1095 1096 running_downloads.put( download, new int[]{ register_type, 0, 0, 0, 0 }); 1097 1098 }else{ 1099 1100 cache[0] = register_type; 1101 1102 running_downloads.put( download, cache ); 1103 } 1104 1105 query_map.put( download, new Long( SystemTime.getCurrentTime())); 1106 1107 }else{ 1108 1109 Integer existing_type = run_data[0]; 1110 1111 if ( existing_type.intValue() == REG_TYPE_DERIVED && 1112 register_type == REG_TYPE_FULL ){ 1113 1114 // upgrade 1115 1116 run_data[0] = register_type; 1117 } 1118 } 1119 }else{ 1120 1121 if ( run_data != null ){ 1122 1123 if ( !skip_log ){ 1124 1125 log( download, "Not monitoring: " + register_reason); 1126 } 1127 1128 running_downloads.remove( download ); 1129 1130 run_data_cache.put( download, run_data ); 1131 1132 // add back to interesting downloads for monitoring 1133 1134 interesting_downloads.put( 1135 download, 1136 new Long( plugin_interface.getUtilities().getCurrentSystemTime() + 1137 INTERESTING_INIT_MIN_OTHERS )); 1138 1139 }else{ 1140 1141 if ( first_time && !skip_log ){ 1142 1143 log( download, "Not monitoring: " + register_reason); 1144 } 1145 } 1146 } 1147 }finally{ 1148 1149 this_mon.exit(); 1150 } 1151 } 1152 } 1153 1154 protected void 1155 processRegistrations( 1156 boolean full_processing ) 1157 { 1158 int tcp_port = plugin_interface.getPluginconfig().getIntParameter( "TCP.Listen.Port" ); 1159 1160 String port_override = COConfigurationManager.getStringParameter("TCP.Listen.Port.Override"); 1161 1162 if( !port_override.equals("")){ 1163 1164 try{ 1165 tcp_port = Integer.parseInt( port_override ); 1166 1167 }catch( Throwable e ){ 1168 } 1169 } 1170 1171 if ( tcp_port == 0 ){ 1172 1173 log.log( "TCP port=0, registration not performed" ); 1174 1175 return; 1176 } 1177 1178 String override_ips = COConfigurationManager.getStringParameter( "Override Ip", "" ); 1179 1180 String override_ip = null; 1181 1182 if ( override_ips.length() > 0 ){ 1183 1184 // gotta select an appropriate override based on network type 1185 1186 StringTokenizer tok = new StringTokenizer( override_ips, ";" ); 1187 1188 while( tok.hasMoreTokens()){ 1189 1190 String this_address = (String)tok.nextToken().trim(); 1191 1192 if ( this_address.length() > 0 ){ 1193 1194 String cat = AENetworkClassifier.categoriseAddress( this_address ); 1195 1196 if ( cat == AENetworkClassifier.AT_PUBLIC ){ 1197 1198 override_ip = this_address; 1199 1200 break; 1201 } 1202 } 1203 } 1204 } 1205 1206 if ( override_ip != null ){ 1207 1208 try{ 1209 override_ip = PRHelpers.DNSToIPAddress( override_ip ); 1210 1211 }catch( UnknownHostException e){ 1212 1213 log.log( " Can't resolve IP override '" + override_ip + "'" ); 1214 1215 override_ip = null; 1216 } 1217 } 1218 1219 ArrayList<Download> rds; 1220 1221 try{ 1222 this_mon.enter(); 1223 1224 rds = new ArrayList<Download>(running_downloads.keySet()); 1225 1226 }finally{ 1227 1228 this_mon.exit(); 1229 } 1230 1231 long now = SystemTime.getCurrentTime(); 1232 1233 1234 if ( full_processing ){ 1235 1236 Iterator<Download> rds_it = rds.iterator(); 1237 1238 List<Object[]> interesting = new ArrayList<Object[]>(); 1239 1240 while( rds_it.hasNext()){ 1241 1242 Download dl = rds_it.next(); 1243 1244 int reg_type = REG_TYPE_NONE; 1245 1246 try{ 1247 this_mon.enter(); 1248 1249 int[] run_data = running_downloads.get( dl ); 1250 1251 if ( run_data != null ){ 1252 1253 reg_type = run_data[0]; 1254 } 1255 }finally{ 1256 1257 this_mon.exit(); 1258 } 1259 1260 if ( reg_type == REG_TYPE_NONE ){ 1261 1262 continue; 1263 } 1264 1265 long metric = getDerivedTrackMetric( dl ); 1266 1267 interesting.add( new Object[]{ dl, new Long( metric )} ); 1268 } 1269 1270 Collections.sort( 1271 interesting, 1272 new Comparator<Object[]>() 1273 { 1274 public int 1275 compare( 1276 Object[] entry1, 1277 Object[] entry2) 1278 { 1279 long res = ((Long)entry2[1]).longValue() - ((Long)entry1[1]).longValue(); 1280 1281 if( res < 0 ){ 1282 1283 return( -1 ); 1284 1285 }else if ( res > 0 ){ 1286 1287 return( 1 ); 1288 1289 }else{ 1290 1291 return( 0 ); 1292 } 1293 } 1294 }); 1295 1296 Iterator<Object[]> it = interesting.iterator(); 1297 1298 int num = 0; 1299 1300 while( it.hasNext()){ 1301 1302 Object[] entry = it.next(); 1303 1304 Download dl = (Download)entry[0]; 1305 long metric = ((Long)entry[1]).longValue(); 1306 1307 num++; 1308 1309 if ( metric > 0 ){ 1310 1311 if ( num <= DL_DERIVED_MIN_TRACK ){ 1312 1313 // leave as is 1314 1315 }else if ( num <= DL_DERIVED_MAX_TRACK ){ 1316 1317 // scale metric between limits 1318 1319 metric = ( metric * ( DL_DERIVED_MAX_TRACK - num )) / ( DL_DERIVED_MAX_TRACK - DL_DERIVED_MIN_TRACK ); 1320 1321 }else{ 1322 1323 metric = 0; 1324 } 1325 } 1326 1327 if ( metric > 0 ){ 1328 1329 dl.setUserData( DL_DERIVED_METRIC_KEY, new Long( metric )); 1330 1331 }else{ 1332 1333 dl.setUserData( DL_DERIVED_METRIC_KEY, null ); 1334 } 1335 } 1336 } 1337 1338 Iterator<Download> rds_it = rds.iterator(); 1339 1340 // first off do any puts 1341 1342 while( rds_it.hasNext()){ 1343 1344 Download dl = rds_it.next(); 1345 1346 int reg_type = REG_TYPE_NONE; 1347 1348 try{ 1349 this_mon.enter(); 1350 1351 int[] run_data = running_downloads.get( dl ); 1352 1353 if ( run_data != null ){ 1354 1355 reg_type = run_data[0]; 1356 } 1357 }finally{ 1358 1359 this_mon.exit(); 1360 } 1361 1362 if ( reg_type == REG_TYPE_NONE ){ 1363 1364 continue; 1365 } 1366 1367 // format is [ip_override:]tcp_port[;CI...][;udp_port] 1368 1369 String value_to_put = override_ip==null?"":(override_ip+":"); 1370 1371 value_to_put += tcp_port; 1372 1373 String put_flags = ";"; 1374 1375 if ( NetworkManager.REQUIRE_CRYPTO_HANDSHAKE ){ 1376 1377 put_flags += "C"; 1378 } 1379 1380 String[] networks = dl.getListAttribute( ta_networks ); 1381 1382 boolean i2p = false; 1383 1384 if ( networks != null ){ 1385 1386 for ( String net: networks ){ 1387 1388 if ( net == AENetworkClassifier.AT_I2P ){ 1389 1390 if ( I2PHelpers.isI2PInstalled()){ 1391 1392 put_flags += "I"; 1393 } 1394 1395 i2p = true; 1396 1397 break; 1398 } 1399 } 1400 } 1401 1402 if ( put_flags.length() > 1 ){ 1403 1404 value_to_put += put_flags; 1405 } 1406 1407 int udp_port = plugin_interface.getPluginconfig().getIntParameter( "UDP.Listen.Port" ); 1408 1409 int dht_port = dht.getLocalAddress().getAddress().getPort(); 1410 1411 if ( udp_port != dht_port ){ 1412 1413 value_to_put += ";" + udp_port; 1414 } 1415 1416 putDetails put_details = new putDetails( value_to_put, override_ip, tcp_port, udp_port, i2p ); 1417 1418 byte dht_flags = isComplete( dl )?DHTPlugin.FLAG_SEEDING:DHTPlugin.FLAG_DOWNLOADING; 1419 1420 RegistrationDetails registration = (RegistrationDetails)registered_downloads.get( dl ); 1421 1422 boolean do_it = false; 1423 1424 if ( registration == null ){ 1425 1426 log( dl, "Registering download as " + (dht_flags == DHTPlugin.FLAG_SEEDING?"Seeding":"Downloading")); 1427 1428 registration = new RegistrationDetails( dl, reg_type, put_details, dht_flags ); 1429 1430 registered_downloads.put( dl, registration ); 1431 1432 do_it = true; 1433 1434 }else{ 1435 1436 boolean targets_changed = false; 1437 1438 if ( full_processing ){ 1439 1440 targets_changed = registration.updateTargets( dl, reg_type ); 1441 } 1442 1443 if ( targets_changed || 1444 registration.getFlags() != dht_flags || 1445 !registration.getPutDetails().sameAs( put_details )){ 1446 1447 log( dl,(registration==null?"Registering":"Re-registering") + " download as " + (dht_flags == DHTPlugin.FLAG_SEEDING?"Seeding":"Downloading")); 1448 1449 registration.update( put_details, dht_flags ); 1450 1451 do_it = true; 1452 } 1453 } 1454 1455 if ( do_it ){ 1456 1457 try{ 1458 this_mon.enter(); 1459 1460 query_map.put( dl, new Long( now )); 1461 1462 }finally{ 1463 1464 this_mon.exit(); 1465 } 1466 1467 trackerPut( dl, registration ); 1468 } 1469 } 1470 1471 // second any removals 1472 1473 Iterator<Map.Entry<Download,RegistrationDetails>> rd_it = registered_downloads.entrySet().iterator(); 1474 1475 while( rd_it.hasNext()){ 1476 1477 Map.Entry<Download,RegistrationDetails> entry = rd_it.next(); 1478 1479 final Download dl = entry.getKey(); 1480 1481 boolean unregister; 1482 1483 try{ 1484 this_mon.enter(); 1485 1486 unregister = !running_downloads.containsKey( dl ); 1487 1488 }finally{ 1489 1490 this_mon.exit(); 1491 } 1492 1493 if ( unregister ){ 1494 1495 log( dl, "Unregistering download" ); 1496 1497 rd_it.remove(); 1498 1499 try{ 1500 this_mon.enter(); 1501 1502 query_map.remove( dl ); 1503 1504 }finally{ 1505 1506 this_mon.exit(); 1507 } 1508 1509 trackerRemove( dl, entry.getValue()); 1510 } 1511 } 1512 1513 // lastly gets 1514 1515 rds_it = rds.iterator(); 1516 1517 while( rds_it.hasNext()){ 1518 1519 final Download dl = (Download)rds_it.next(); 1520 1521 Long next_time; 1522 1523 try{ 1524 this_mon.enter(); 1525 1526 next_time = (Long)query_map.get( dl ); 1527 1528 }finally{ 1529 1530 this_mon.exit(); 1531 } 1532 1533 if ( next_time != null && now >= next_time.longValue()){ 1534 1535 int reg_type = REG_TYPE_NONE; 1536 1537 try{ 1538 this_mon.enter(); 1539 1540 query_map.remove( dl ); 1541 1542 int[] run_data = running_downloads.get( dl ); 1543 1544 if ( run_data != null ){ 1545 1546 reg_type = run_data[0]; 1547 } 1548 }finally{ 1549 1550 this_mon.exit(); 1551 } 1552 1553 final long start = SystemTime.getCurrentTime(); 1554 1555 // if we're already connected to > NUM_WANT peers then don't bother with the main announce 1556 1557 PeerManager pm = dl.getPeerManager(); 1558 1559 // don't query if this download already has an active DHT operation 1560 1561 boolean skip = isActive( dl ) || reg_type == REG_TYPE_NONE; 1562 1563 if ( skip ){ 1564 1565 log( dl, "Deferring announce as activity outstanding" ); 1566 } 1567 1568 RegistrationDetails registration = (RegistrationDetails)registered_downloads.get( dl ); 1569 1570 if ( registration == null ){ 1571 1572 Debug.out( "Inconsistent, registration should be non-null" ); 1573 1574 continue; 1575 } 1576 1577 boolean derived_only = false; 1578 1579 if ( pm != null && !skip ){ 1580 1581 int con = pm.getStats().getConnectedLeechers() + pm.getStats().getConnectedSeeds(); 1582 1583 derived_only = con >= NUM_WANT; 1584 } 1585 1586 if ( !skip ){ 1587 1588 skip = trackerGet( dl, registration, derived_only ) == 0; 1589 1590 } 1591 1592 // if we didn't kick off a get then we have to reschedule here as normally 1593 // the get operation will do the rescheduling when it receives a result 1594 1595 if ( skip ){ 1596 1597 try{ 1598 this_mon.enter(); 1599 1600 if ( running_downloads.containsKey( dl )){ 1601 1602 // use "min" here as we're just deferring it 1603 1604 query_map.put( dl, new Long( start + ANNOUNCE_MIN_DEFAULT )); 1605 } 1606 1607 }finally{ 1608 1609 this_mon.exit(); 1610 } 1611 } 1612 } 1613 } 1614 } 1615 1616 protected long 1617 getDerivedTrackMetric( 1618 Download download ) 1619 { 1620 // metric between -100 and + 100. Note that all -ve mean 'don't do it' 1621 // they're just indicating different reasons 1622 1623 Torrent t = download.getTorrent(); 1624 1625 if ( t == null ){ 1626 1627 return( -100 ); 1628 } 1629 1630 if ( t.getSize() < 10*1024*1024 ){ 1631 1632 return( -99 ); 1633 } 1634 1635 DownloadAnnounceResult announce = download.getLastAnnounceResult(); 1636 1637 if ( announce == null || 1638 announce.getResponseType() != DownloadAnnounceResult.RT_SUCCESS ){ 1639 1640 return( -98 ); 1641 } 1642 1643 DownloadScrapeResult scrape = download.getLastScrapeResult(); 1644 1645 if ( scrape == null || 1646 scrape.getResponseType() != DownloadScrapeResult.RT_SUCCESS ){ 1647 1648 return( -97 ); 1649 } 1650 1651 int leechers = scrape.getNonSeedCount(); 1652 // int seeds = scrape.getSeedCount(); 1653 1654 int total = leechers; // parg - changed to just use leecher count rather than seeds+leechers 1655 1656 if ( total >= 2000 ){ 1657 1658 return( 100 ); 1659 1660 }else if ( total <= 200 ){ 1661 1662 return( 0 ); 1663 1664 }else{ 1665 1666 return( ( total - 200 ) / 4 ); 1667 } 1668 } 1669 1670 protected void 1671 trackerPut( 1672 final Download download, 1673 RegistrationDetails details ) 1674 { 1675 final long start = SystemTime.getCurrentTime(); 1676 1677 trackerTarget[] targets = details.getTargets( true ); 1678 1679 byte flags = details.getFlags(); 1680 1681 for (int i=0;i<targets.length;i++){ 1682 1683 final trackerTarget target = targets[i]; 1684 1685 int target_type = target.getType(); 1686 1687 // don't let a put block an announce as we don't want to be waiting for 1688 // this at start of day to get a torrent running 1689 1690 // increaseActive( dl ); 1691 1692 String encoded = details.getPutDetails().getEncoded(); 1693 1694 byte[] encoded_bytes = encoded.getBytes(); 1695 1696 DHTPluginValue existing = dht.getLocalValue( target.getHash()); 1697 1698 if ( existing != null && 1699 existing.getFlags() == flags && 1700 Arrays.equals( existing.getValue(), encoded_bytes )){ 1701 1702 // already present, no point in updating 1703 1704 continue; 1705 } 1706 1707 if ( disable_put ){ 1708 1709 if ( target_type == REG_TYPE_FULL ){ 1710 1711 log( download, "Registration of '" + target.getDesc() + "' skipped as disabled due to use of SOCKS proxy"); 1712 } 1713 }else if ( download.getFlag( Download.FLAG_METADATA_DOWNLOAD )){ 1714 1715 log( download, "Registration of '" + target.getDesc() + "' skipped as metadata download"); 1716 1717 }else if ( target_type == REG_TYPE_DERIVED && dht.isSleeping()){ 1718 1719 log( download, "Registration of '" + target.getDesc() + "' skipped as sleeping"); 1720 1721 }else{ 1722 1723 dht.put( 1724 target.getHash(), 1725 "Tracker reg of '" + download.getName() + "'" + target.getDesc() + " -> " + encoded, 1726 encoded_bytes, 1727 flags, 1728 false, 1729 new DHTPluginOperationListener() 1730 { 1731 public boolean 1732 diversified() 1733 { 1734 return( true ); 1735 } 1736 1737 public void 1738 starts( 1739 byte[] key ) 1740 { 1741 } 1742 1743 public void 1744 valueRead( 1745 DHTPluginContact originator, 1746 DHTPluginValue value ) 1747 { 1748 } 1749 1750 public void 1751 valueWritten( 1752 DHTPluginContact target, 1753 DHTPluginValue value ) 1754 { 1755 } 1756 1757 public void 1758 complete( 1759 byte[] key, 1760 boolean timeout_occurred ) 1761 { 1762 if ( target.getType() == REG_TYPE_FULL ){ 1763 1764 log( download, 1765 "Registration of '" + target.getDesc() + "' completed (elapsed=" + TimeFormatter.formatColonMillis((SystemTime.getCurrentTime() - start)) + ")"); 1766 } 1767 1768 // decreaseActive( dl ); 1769 } 1770 }); 1771 } 1772 } 1773 } 1774 1775 protected int 1776 trackerGet( 1777 final Download download, 1778 final RegistrationDetails details, 1779 final boolean derived_only ) 1780 { 1781 final long start = SystemTime.getCurrentTime(); 1782 1783 final Torrent torrent = download.getTorrent(); 1784 1785 final URL url_to_report = torrent.isDecentralised()?torrent.getAnnounceURL():DEFAULT_URL; 1786 1787 trackerTarget[] targets = details.getTargets( false ); 1788 1789 final long[] max_retry = { 0 }; 1790 1791 final boolean do_alt = 1792 alt_lookup_handler != null && 1793 (!( download.getFlag( Download.FLAG_LOW_NOISE ) || download.getFlag( Download.FLAG_LIGHT_WEIGHT ))); 1794 1795 int num_done = 0; 1796 1797 for (int i=0;i<targets.length;i++){ 1798 1799 final trackerTarget target = targets[i]; 1800 1801 int target_type = target.getType(); 1802 1803 if ( target_type == REG_TYPE_FULL && derived_only ){ 1804 1805 continue; 1806 1807 }else if ( target_type == REG_TYPE_DERIVED && dht.isSleeping()){ 1808 1809 continue; 1810 } 1811 1812 increaseActive( download ); 1813 1814 num_done++; 1815 1816 final boolean is_complete = isComplete( download ); 1817 1818 dht.get(target.getHash(), 1819 "Tracker announce for '" + download.getName() + "'" + target.getDesc(), 1820 is_complete?DHTPlugin.FLAG_SEEDING:DHTPlugin.FLAG_DOWNLOADING, 1821 NUM_WANT, 1822 target_type==REG_TYPE_FULL?ANNOUNCE_TIMEOUT:ANNOUNCE_DERIVED_TIMEOUT, 1823 false, false, 1824 new DHTPluginOperationListener() 1825 { 1826 List<String> addresses = new ArrayList<String>(); 1827 List<Integer> ports = new ArrayList<Integer>(); 1828 List<Integer> udp_ports = new ArrayList<Integer>(); 1829 List<Boolean> is_seeds = new ArrayList<Boolean>(); 1830 List<String> flags = new ArrayList<String>(); 1831 1832 int seed_count; 1833 int leecher_count; 1834 1835 int i2p_seed_count; 1836 int i2p_leecher_count; 1837 1838 volatile boolean complete; 1839 1840 { 1841 if ( do_alt ){ 1842 1843 alt_lookup_handler.get( 1844 target.getHash(), 1845 is_complete, 1846 new DHTTrackerPluginAlt.LookupListener() 1847 { 1848 public void 1849 foundPeer( 1850 InetSocketAddress address ) 1851 { 1852 alternativePeerRead( address ); 1853 } 1854 1855 public boolean 1856 isComplete() 1857 { 1858 return( complete && addresses.size() > 5 ); 1859 } 1860 1861 public void 1862 completed() 1863 { 1864 } 1865 }); 1866 } 1867 } 1868 1869 public boolean 1870 diversified() 1871 { 1872 return( true ); 1873 } 1874 1875 public void 1876 starts( 1877 byte[] key ) 1878 { 1879 } 1880 1881 private void 1882 alternativePeerRead( 1883 InetSocketAddress peer ) 1884 { 1885 boolean try_injection = false; 1886 1887 synchronized( this ){ 1888 1889 if ( complete ){ 1890 1891 try_injection = addresses.size() < 5; 1892 1893 }else{ 1894 1895 try{ 1896 addresses.add( peer.getAddress().getHostAddress()); 1897 ports.add( peer.getPort()); 1898 udp_ports.add( 0 ); 1899 flags.add( null ); 1900 1901 is_seeds.add( false ); 1902 leecher_count++; 1903 1904 }catch( Throwable e ){ 1905 } 1906 } 1907 } 1908 1909 if ( try_injection ){ 1910 1911 PeerManager pm = download.getPeerManager(); 1912 1913 if ( pm != null ){ 1914 1915 pm.peerDiscovered( 1916 PEPeerSource.PS_DHT, 1917 peer.getAddress().getHostAddress(), 1918 peer.getPort(), 1919 0, 1920 NetworkManager.getCryptoRequired( NetworkManager.CRYPTO_OVERRIDE_NONE )); 1921 } 1922 } 1923 } 1924 1925 public void 1926 valueRead( 1927 DHTPluginContact originator, 1928 DHTPluginValue value ) 1929 { 1930 synchronized( this ){ 1931 1932 if ( complete ){ 1933 1934 return; 1935 } 1936 1937 try{ 1938 String[] tokens = new String(value.getValue()).split(";"); 1939 1940 String tcp_part = tokens[0].trim(); 1941 1942 int sep = tcp_part.indexOf(':'); 1943 1944 String ip_str = null; 1945 String tcp_port_str; 1946 1947 if ( sep == -1 ){ 1948 1949 tcp_port_str = tcp_part; 1950 1951 }else{ 1952 1953 ip_str = tcp_part.substring( 0, sep ); 1954 tcp_port_str = tcp_part.substring( sep+1 ); 1955 } 1956 1957 int tcp_port = Integer.parseInt( tcp_port_str ); 1958 1959 if ( tcp_port > 0 && tcp_port < 65536 ){ 1960 1961 String flag_str = null; 1962 int udp_port = -1; 1963 1964 boolean has_i2p = false; 1965 1966 try{ 1967 for (int i=1;i<tokens.length;i++){ 1968 1969 String token = tokens[i].trim(); 1970 1971 if ( token.length() > 0 ){ 1972 1973 if ( Character.isDigit( token.charAt( 0 ))){ 1974 1975 udp_port = Integer.parseInt( token ); 1976 1977 if ( udp_port <= 0 || udp_port >=65536 ){ 1978 1979 udp_port = -1; 1980 } 1981 }else{ 1982 1983 flag_str = token; 1984 1985 if ( flag_str.contains("I")){ 1986 1987 has_i2p = true; 1988 } 1989 } 1990 } 1991 } 1992 }catch( Throwable e ){ 1993 } 1994 1995 addresses.add( 1996 ip_str==null?originator.getAddress().getAddress().getHostAddress():ip_str); 1997 1998 ports.add( new Integer( tcp_port )); 1999 2000 udp_ports.add( new Integer( udp_port==-1?originator.getAddress().getPort():udp_port)); 2001 2002 flags.add( flag_str ); 2003 2004 if (( value.getFlags() & DHTPlugin.FLAG_DOWNLOADING ) == 1 ){ 2005 2006 leecher_count++; 2007 2008 is_seeds.add( new Boolean( false )); 2009 2010 if ( has_i2p ){ 2011 2012 i2p_leecher_count++; 2013 } 2014 }else{ 2015 2016 is_seeds.add( new Boolean( true )); 2017 2018 seed_count++; 2019 2020 if ( has_i2p ){ 2021 2022 i2p_seed_count++; 2023 } 2024 } 2025 } 2026 2027 }catch( Throwable e ){ 2028 2029 // in case we get crap back (someone spamming the DHT) just 2030 // silently ignore 2031 } 2032 } 2033 } 2034 2035 public void 2036 valueWritten( 2037 DHTPluginContact target, 2038 DHTPluginValue value ) 2039 { 2040 } 2041 2042 public void 2043 complete( 2044 byte[] key, 2045 boolean timeout_occurred ) 2046 { 2047 synchronized( this ){ 2048 2049 if ( complete ){ 2050 2051 return; 2052 } 2053 2054 complete = true; 2055 } 2056 2057 if ( target.getType() == REG_TYPE_FULL || 2058 ( target.getType() == REG_TYPE_DERIVED && 2059 seed_count + leecher_count > 1 )){ 2060 2061 log( download, 2062 "Get of '" + target.getDesc() + "' completed (elapsed=" + TimeFormatter.formatColonMillis(SystemTime.getCurrentTime() - start) 2063 + "), addresses=" + addresses.size() + ", seeds=" 2064 + seed_count + ", leechers=" + leecher_count); 2065 } 2066 2067 decreaseActive(download); 2068 2069 int peers_found = addresses.size(); 2070 2071 List<DownloadAnnounceResultPeer> peers_for_announce = new ArrayList<DownloadAnnounceResultPeer>(); 2072 2073 // scale min and max based on number of active torrents 2074 // we don't want more than a few announces a minute 2075 2076 int announce_per_min = 4; 2077 2078 int num_active = query_map.size(); 2079 2080 int announce_min = Math.max( ANNOUNCE_MIN_DEFAULT, ( num_active / announce_per_min )*60*1000 ); 2081 2082 int announce_max = derived_only?ANNOUNCE_MAX_DERIVED_ONLY:ANNOUNCE_MAX; 2083 2084 announce_min = Math.min( announce_min, announce_max ); 2085 2086 current_announce_interval = announce_min; 2087 2088 final long retry = announce_min + peers_found*(long)(announce_max-announce_min)/NUM_WANT; 2089 2090 int download_state = download.getState(); 2091 2092 boolean we_are_seeding = download_state == Download.ST_SEEDING; 2093 2094 try{ 2095 this_mon.enter(); 2096 2097 int[] run_data = running_downloads.get( download ); 2098 2099 if ( run_data != null ){ 2100 2101 boolean full = target.getType() == REG_TYPE_FULL; 2102 2103 int peer_count = we_are_seeding?leecher_count:(seed_count+leecher_count); 2104 2105 run_data[1] = full?seed_count:Math.max( run_data[1], seed_count); 2106 run_data[2] = full?leecher_count:Math.max( run_data[2], leecher_count); 2107 run_data[3] = full?peer_count:Math.max( run_data[3], peer_count); 2108 2109 run_data[4] = (int)(SystemTime.getCurrentTime()/1000); 2110 2111 long absolute_retry = SystemTime.getCurrentTime() + retry; 2112 2113 if ( absolute_retry > max_retry[0] ){ 2114 2115 // only update next query time if none set yet 2116 // or we appear to have set the existing one. If we 2117 // don't do this then we'll overwrite any rescheduled 2118 // announces 2119 2120 Long existing = (Long)query_map.get( download ); 2121 2122 if ( existing == null || 2123 existing.longValue() == max_retry[0] ){ 2124 2125 max_retry[0] = absolute_retry; 2126 2127 query_map.put( download, new Long( absolute_retry )); 2128 } 2129 } 2130 } 2131 }finally{ 2132 2133 this_mon.exit(); 2134 } 2135 2136 putDetails put_details = details.getPutDetails(); 2137 2138 String ext_address = put_details.getIPOverride(); 2139 2140 if ( ext_address == null ){ 2141 2142 ext_address = dht.getLocalAddress().getAddress().getAddress().getHostAddress(); 2143 } 2144 2145 if ( put_details.hasI2P()){ 2146 2147 if ( we_are_seeding ){ 2148 if ( i2p_seed_count > 0 ){ 2149 i2p_seed_count--; 2150 } 2151 }else{ 2152 if ( i2p_leecher_count > 0 ){ 2153 i2p_leecher_count--; 2154 } 2155 } 2156 } 2157 2158 if ( i2p_seed_count + i2p_leecher_count > 0 ){ 2159 2160 download.setUserData( DOWNLOAD_USER_DATA_I2P_SCRAPE_KEY, new int[]{ i2p_seed_count, i2p_leecher_count }); 2161 2162 }else{ 2163 2164 download.setUserData( DOWNLOAD_USER_DATA_I2P_SCRAPE_KEY, null ); 2165 } 2166 2167 for (int i=0;i<addresses.size();i++){ 2168 2169 // when we are seeding ignore seeds 2170 2171 if ( we_are_seeding && ((Boolean)is_seeds.get(i)).booleanValue()){ 2172 2173 continue; 2174 } 2175 2176 // remove ourselves 2177 2178 String ip = (String)addresses.get(i); 2179 2180 if ( ip.equals( ext_address )){ 2181 2182 if ( ((Integer)ports.get(i)).intValue() == put_details.getTCPPort() && 2183 ((Integer)udp_ports.get(i)).intValue() == put_details.getUDPPort()){ 2184 2185 continue; 2186 } 2187 } 2188 2189 final int f_i = i; 2190 2191 peers_for_announce.add( 2192 new DownloadAnnounceResultPeer() 2193 { 2194 public String 2195 getSource() 2196 { 2197 return( PEPeerSource.PS_DHT ); 2198 } 2199 2200 public String 2201 getAddress() 2202 { 2203 return((String)addresses.get(f_i)); 2204 } 2205 2206 public int 2207 getPort() 2208 { 2209 return(((Integer)ports.get(f_i)).intValue()); 2210 } 2211 2212 public int 2213 getUDPPort() 2214 { 2215 return(((Integer)udp_ports.get(f_i)).intValue()); 2216 } 2217 2218 public byte[] 2219 getPeerID() 2220 { 2221 return( null ); 2222 } 2223 2224 public short 2225 getProtocol() 2226 { 2227 String flag = (String)flags.get(f_i); 2228 2229 short protocol = PROTOCOL_NORMAL; 2230 2231 if ( flag != null ){ 2232 2233 if ( flag.contains("C")){ 2234 2235 protocol = PROTOCOL_CRYPT; 2236 } 2237 } 2238 2239 return( protocol ); 2240 } 2241 }); 2242 2243 } 2244 2245 if ( target.getType() == REG_TYPE_DERIVED && peers_for_announce.size() > 0 ){ 2246 2247 PeerManager pm = download.getPeerManager(); 2248 2249 if ( pm != null ){ 2250 2251 // try some limited direct injection 2252 2253 List<DownloadAnnounceResultPeer> temp = new ArrayList<DownloadAnnounceResultPeer>( peers_for_announce ); 2254 2255 Random rand = new Random(); 2256 2257 for (int i=0;i<DIRECT_INJECT_PEER_MAX && temp.size() > 0; i++ ){ 2258 2259 DownloadAnnounceResultPeer peer = temp.remove( rand.nextInt( temp.size())); 2260 2261 log( download, "Injecting derived peer " + peer.getAddress() + " into " + download.getName()); 2262 2263 Map<Object,Object> user_data = new HashMap<Object,Object>(); 2264 2265 user_data.put( Peer.PR_PRIORITY_CONNECTION, new Boolean( true )); 2266 2267 pm.addPeer( 2268 peer.getAddress(), 2269 peer.getPort(), 2270 peer.getUDPPort(), 2271 peer.getProtocol() == DownloadAnnounceResultPeer.PROTOCOL_CRYPT, 2272 user_data ); 2273 } 2274 } 2275 } 2276 2277 if ( download_state == Download.ST_DOWNLOADING || 2278 download_state == Download.ST_SEEDING ){ 2279 2280 final DownloadAnnounceResultPeer[] peers = new DownloadAnnounceResultPeer[peers_for_announce.size()]; 2281 2282 peers_for_announce.toArray( peers ); 2283 2284 download.setAnnounceResult( 2285 new DownloadAnnounceResult() 2286 { 2287 public Download 2288 getDownload() 2289 { 2290 return( download ); 2291 } 2292 2293 public int 2294 getResponseType() 2295 { 2296 return( DownloadAnnounceResult.RT_SUCCESS ); 2297 } 2298 2299 public int 2300 getReportedPeerCount() 2301 { 2302 return( peers.length); 2303 } 2304 2305 public int 2306 getSeedCount() 2307 { 2308 return( seed_count ); 2309 } 2310 2311 public int 2312 getNonSeedCount() 2313 { 2314 return( leecher_count ); 2315 } 2316 2317 public String 2318 getError() 2319 { 2320 return( null ); 2321 } 2322 2323 public URL 2324 getURL() 2325 { 2326 return( url_to_report ); 2327 } 2328 2329 public DownloadAnnounceResultPeer[] 2330 getPeers() 2331 { 2332 return( peers ); 2333 } 2334 2335 public long 2336 getTimeToWait() 2337 { 2338 return( retry/1000 ); 2339 } 2340 2341 public Map 2342 getExtensions() 2343 { 2344 return( null ); 2345 } 2346 }); 2347 } 2348 2349 // only inject the scrape result if the torrent is decentralised. If we do this for 2350 // "normal" torrents then it can have unwanted side-effects, such as stopping the torrent 2351 // due to ignore rules if there are no downloaders in the DHT - bthub backup, for example, 2352 // isn't scrapable... 2353 2354 // hmm, ok, try being a bit more relaxed about this, inject the scrape if 2355 // we have any peers. 2356 2357 boolean inject_scrape = leecher_count > 0; 2358 2359 DownloadScrapeResult result = download.getLastScrapeResult(); 2360 2361 if ( result == null || 2362 result.getResponseType() == DownloadScrapeResult.RT_ERROR ){ 2363 2364 }else{ 2365 2366 // if the currently reported values are the same as the 2367 // ones we previously injected then overwrite them 2368 // note that we can't test the URL to see if we originated 2369 // the scrape values as this gets replaced when a normal 2370 // scrape fails :( 2371 2372 synchronized( scrape_injection_map ){ 2373 2374 int[] prev = (int[])scrape_injection_map.get( download ); 2375 2376 if ( prev != null && 2377 prev[0] == result.getSeedCount() && 2378 prev[1] == result.getNonSeedCount()){ 2379 2380 inject_scrape = true; 2381 } 2382 } 2383 } 2384 2385 if ( torrent.isDecentralised() || inject_scrape ){ 2386 2387 2388 // make sure that the injected scrape values are consistent 2389 // with our currently connected peers 2390 2391 PeerManager pm = download.getPeerManager(); 2392 2393 int local_seeds = 0; 2394 int local_leechers = 0; 2395 2396 if ( pm != null ){ 2397 2398 Peer[] dl_peers = pm.getPeers(); 2399 2400 for (int i=0;i<dl_peers.length;i++){ 2401 2402 Peer dl_peer = dl_peers[i]; 2403 2404 if ( dl_peer.getPercentDoneInThousandNotation() == 1000 ){ 2405 2406 local_seeds++; 2407 2408 }else{ 2409 local_leechers++; 2410 } 2411 } 2412 } 2413 2414 final int f_adj_seeds = Math.max( seed_count, local_seeds ); 2415 final int f_adj_leechers = Math.max( leecher_count, local_leechers ); 2416 2417 synchronized( scrape_injection_map ){ 2418 2419 scrape_injection_map.put( download, new int[]{ f_adj_seeds, f_adj_leechers }); 2420 } 2421 2422 try{ 2423 this_mon.enter(); 2424 2425 int[] run_data = running_downloads.get( download ); 2426 2427 if ( run_data == null ){ 2428 2429 run_data = run_data_cache.get( download ); 2430 } 2431 2432 if ( run_data != null ){ 2433 2434 run_data[1] = f_adj_seeds; 2435 run_data[2] = f_adj_leechers; 2436 2437 run_data[4] = (int)(SystemTime.getCurrentTime()/1000); 2438 } 2439 }finally{ 2440 2441 this_mon.exit(); 2442 } 2443 2444 download.setScrapeResult( 2445 new DownloadScrapeResult() 2446 { 2447 public Download 2448 getDownload() 2449 { 2450 return( download ); 2451 } 2452 2453 public int 2454 getResponseType() 2455 { 2456 return( RT_SUCCESS ); 2457 } 2458 2459 public int 2460 getSeedCount() 2461 { 2462 return( f_adj_seeds ); 2463 } 2464 2465 public int 2466 getNonSeedCount() 2467 { 2468 return( f_adj_leechers ); 2469 } 2470 2471 public long 2472 getScrapeStartTime() 2473 { 2474 return( start ); 2475 } 2476 2477 public void 2478 setNextScrapeStartTime( 2479 long nextScrapeStartTime) 2480 { 2481 2482 } 2483 public long 2484 getNextScrapeStartTime() 2485 { 2486 return( SystemTime.getCurrentTime() + retry ); 2487 } 2488 2489 public String 2490 getStatus() 2491 { 2492 return( "OK" ); 2493 } 2494 2495 public URL 2496 getURL() 2497 { 2498 return( url_to_report ); 2499 } 2500 }); 2501 } 2502 } 2503 }); 2504 } 2505 2506 return( num_done ); 2507 } 2508 2509 protected boolean 2510 isComplete( 2511 Download download ) 2512 { 2513 if ( Constants.DOWNLOAD_SOURCES_PRETEND_COMPLETE ){ 2514 2515 return( true ); 2516 } 2517 2518 boolean is_complete = download.isComplete(); 2519 2520 if ( is_complete ){ 2521 2522 PeerManager pm = download.getPeerManager(); 2523 2524 if ( pm != null ){ 2525 2526 PEPeerManager core_pm = PluginCoreUtils.unwrap( pm ); 2527 2528 if ( core_pm != null && core_pm.getHiddenBytes() > 0 ){ 2529 2530 is_complete = false; 2531 } 2532 } 2533 } 2534 2535 return( is_complete ); 2536 } 2537 2538 protected void 2539 trackerRemove( 2540 final Download download, 2541 RegistrationDetails details ) 2542 { 2543 if ( disable_put ){ 2544 2545 return; 2546 } 2547 2548 if ( download.getFlag( Download.FLAG_METADATA_DOWNLOAD )){ 2549 2550 return; 2551 } 2552 2553 final long start = SystemTime.getCurrentTime(); 2554 2555 trackerTarget[] targets = details.getTargets( true ); 2556 2557 for (int i=0;i<targets.length;i++){ 2558 2559 final trackerTarget target = targets[i]; 2560 2561 if ( dht.hasLocalKey( target.getHash())){ 2562 2563 increaseActive( download ); 2564 2565 dht.remove( 2566 target.getHash(), 2567 "Tracker dereg of '" + download.getName() + "'" + target.getDesc(), 2568 new DHTPluginOperationListener() 2569 { 2570 public boolean 2571 diversified() 2572 { 2573 return( true ); 2574 } 2575 2576 public void 2577 starts( 2578 byte[] key ) 2579 { 2580 } 2581 2582 public void 2583 valueRead( 2584 DHTPluginContact originator, 2585 DHTPluginValue value ) 2586 { 2587 } 2588 2589 public void 2590 valueWritten( 2591 DHTPluginContact target, 2592 DHTPluginValue value ) 2593 { 2594 } 2595 2596 public void 2597 complete( 2598 byte[] key, 2599 boolean timeout_occurred ) 2600 { 2601 if ( target.getType() == REG_TYPE_FULL ){ 2602 2603 log( download, 2604 "Unregistration of '" + target.getDesc() + "' completed (elapsed=" 2605 + TimeFormatter.formatColonMillis(SystemTime.getCurrentTime() - start) + ")"); 2606 } 2607 2608 decreaseActive( download ); 2609 } 2610 }); 2611 } 2612 } 2613 } 2614 2615 protected void 2616 trackerRemove( 2617 final Download download, 2618 final trackerTarget target ) 2619 { 2620 if ( disable_put ){ 2621 2622 return; 2623 } 2624 2625 if ( download.getFlag( Download.FLAG_METADATA_DOWNLOAD )){ 2626 2627 return; 2628 } 2629 2630 final long start = SystemTime.getCurrentTime(); 2631 2632 if ( dht.hasLocalKey( target.getHash())){ 2633 2634 increaseActive( download ); 2635 2636 dht.remove( 2637 target.getHash(), 2638 "Tracker dereg of '" + download.getName() + "'" + target.getDesc(), 2639 new DHTPluginOperationListener() 2640 { 2641 public boolean 2642 diversified() 2643 { 2644 return( true ); 2645 } 2646 2647 public void 2648 starts( 2649 byte[] key ) 2650 { 2651 } 2652 2653 public void 2654 valueRead( 2655 DHTPluginContact originator, 2656 DHTPluginValue value ) 2657 { 2658 } 2659 2660 public void 2661 valueWritten( 2662 DHTPluginContact target, 2663 DHTPluginValue value ) 2664 { 2665 } 2666 2667 public void 2668 complete( 2669 byte[] key, 2670 boolean timeout_occurred ) 2671 { 2672 if ( target.getType() == REG_TYPE_FULL ){ 2673 2674 log( download, 2675 "Unregistration of '" + target.getDesc() + "' completed (elapsed=" 2676 + TimeFormatter.formatColonMillis(SystemTime.getCurrentTime() - start) + ")"); 2677 } 2678 2679 decreaseActive( download ); 2680 } 2681 }); 2682 } 2683 } 2684 2685 protected void 2686 processNonRegistrations() 2687 { 2688 Download ready_download = null; 2689 long ready_download_next_check = -1; 2690 2691 long now = plugin_interface.getUtilities().getCurrentSystemTime(); 2692 2693 // unfortunately getting scrape results can acquire locks and there is a vague 2694 // possibility of deadlock here, so pre-fetch the scrape results 2695 2696 List<Download> to_scrape = new ArrayList<Download>(); 2697 2698 try{ 2699 this_mon.enter(); 2700 2701 Iterator<Download> it = interesting_downloads.keySet().iterator(); 2702 2703 while( it.hasNext() && ready_download == null ){ 2704 2705 Download download = it.next(); 2706 2707 Torrent torrent = download.getTorrent(); 2708 2709 if ( torrent == null ){ 2710 2711 continue; 2712 } 2713 2714 int[] run_data = running_downloads.get( download ); 2715 2716 if ( run_data == null || run_data[0] == REG_TYPE_DERIVED ){ 2717 2718 // looks like we'll need the scrape below 2719 2720 to_scrape.add( download ); 2721 } 2722 } 2723 }finally{ 2724 2725 this_mon.exit(); 2726 } 2727 2728 Map<Download,DownloadScrapeResult> scrapes = new HashMap<Download,DownloadScrapeResult>(); 2729 2730 for (int i=0;i<to_scrape.size();i++){ 2731 2732 Download download = (Download)to_scrape.get(i); 2733 2734 scrapes.put( download, download.getLastScrapeResult()); 2735 } 2736 2737 try{ 2738 this_mon.enter(); 2739 2740 Iterator<Download> it = interesting_downloads.keySet().iterator(); 2741 2742 while( it.hasNext() && ready_download == null ){ 2743 2744 Download download = it.next(); 2745 2746 Torrent torrent = download.getTorrent(); 2747 2748 if ( torrent == null ){ 2749 2750 continue; 2751 } 2752 2753 int[] run_data = running_downloads.get( download ); 2754 2755 if ( run_data == null || run_data[0] == REG_TYPE_DERIVED ){ 2756 2757 boolean force = torrent.wasCreatedByUs(); 2758 2759 if ( !force ){ 2760 2761 if ( interesting_pub_max > 0 && interesting_published > interesting_pub_max ){ 2762 2763 continue; 2764 } 2765 2766 DownloadScrapeResult scrape = (DownloadScrapeResult)scrapes.get( download ); 2767 2768 if ( scrape == null ){ 2769 2770 // catch it next time round 2771 2772 continue; 2773 } 2774 2775 if ( scrape.getSeedCount() + scrape.getNonSeedCount() > NUM_WANT ){ 2776 2777 continue; 2778 } 2779 } 2780 2781 long target = ((Long)interesting_downloads.get( download )).longValue(); 2782 2783 long check_period = TorrentUtils.isDecentralised( torrent.getAnnounceURL())?INTERESTING_DHT_CHECK_PERIOD:INTERESTING_CHECK_PERIOD; 2784 2785 if ( target <= now ){ 2786 2787 ready_download = download; 2788 ready_download_next_check = now + check_period; 2789 2790 interesting_downloads.put( download, new Long( ready_download_next_check )); 2791 2792 }else if ( target - now > check_period ){ 2793 2794 interesting_downloads.put( download, new Long( now + (target%check_period))); 2795 } 2796 } 2797 } 2798 2799 }finally{ 2800 2801 this_mon.exit(); 2802 } 2803 2804 if ( ready_download != null ){ 2805 2806 final Download f_ready_download = ready_download; 2807 2808 final Torrent torrent = ready_download.getTorrent(); 2809 2810 if ( ready_download.getFlag( Download.FLAG_METADATA_DOWNLOAD )){ 2811 2812 try{ 2813 this_mon.enter(); 2814 2815 interesting_downloads.remove( f_ready_download ); 2816 2817 }finally{ 2818 2819 this_mon.exit(); 2820 } 2821 2822 }else if ( dht.isDiversified( torrent.getHash())){ 2823 2824 // System.out.println( "presence query for " + f_ready_download.getName() + "-> diversified pre start" ); 2825 2826 try{ 2827 this_mon.enter(); 2828 2829 interesting_downloads.remove( f_ready_download ); 2830 2831 }finally{ 2832 2833 this_mon.exit(); 2834 } 2835 }else{ 2836 2837 //System.out.println( "presence query for " + ready_download.getName()); 2838 2839 final long start = now; 2840 final long f_next_check = ready_download_next_check; 2841 2842 dht.get( torrent.getHash(), 2843 "Presence query for '" + ready_download.getName() + "'", 2844 (byte)0, 2845 INTERESTING_AVAIL_MAX, 2846 ANNOUNCE_TIMEOUT, 2847 false, false, 2848 new DHTPluginOperationListener() 2849 { 2850 private boolean diversified; 2851 private int leechers = 0; 2852 private int seeds = 0; 2853 2854 private int i2p_leechers = 0; 2855 private int i2p_seeds = 0; 2856 2857 public boolean 2858 diversified() 2859 { 2860 diversified = true; 2861 2862 return( false ); 2863 } 2864 2865 public void 2866 starts( 2867 byte[] key ) 2868 { 2869 } 2870 2871 public void 2872 valueRead( 2873 DHTPluginContact originator, 2874 DHTPluginValue value ) 2875 { 2876 boolean is_leecher = ( value.getFlags() & DHTPlugin.FLAG_DOWNLOADING ) == 1; 2877 2878 if ( is_leecher ){ 2879 2880 leechers++; 2881 2882 }else{ 2883 2884 seeds++; 2885 } 2886 2887 try{ 2888 String[] tokens = new String(value.getValue()).split(";"); 2889 2890 for (int i=1;i<tokens.length;i++){ 2891 2892 String token = tokens[i].trim(); 2893 2894 if ( token.length() > 0 ){ 2895 2896 if ( !Character.isDigit( token.charAt( 0 ))){ 2897 2898 String flag_str = token; 2899 2900 if ( flag_str.contains("I")){ 2901 2902 if ( is_leecher ){ 2903 2904 i2p_leechers++; 2905 2906 }else{ 2907 2908 i2p_seeds++; 2909 } 2910 } 2911 } 2912 } 2913 2914 } 2915 }catch( Throwable e ){ 2916 2917 } 2918 } 2919 2920 public void 2921 valueWritten( 2922 DHTPluginContact target, 2923 DHTPluginValue value ) 2924 { 2925 } 2926 2927 public void 2928 complete( 2929 byte[] key, 2930 boolean timeout_occurred ) 2931 { 2932 // System.out.println( " presence query for " + f_ready_download.getName() + "->" + total + "/div = " + diversified ); 2933 2934 int total = leechers + seeds; 2935 2936 log( torrent, 2937 "Presence query: availability="+ 2938 (total==INTERESTING_AVAIL_MAX?(INTERESTING_AVAIL_MAX+"+"):(total+"")) + ",div=" + diversified + 2939 " (elapsed=" + TimeFormatter.formatColonMillis(SystemTime.getCurrentTime() - start) + ")"); 2940 2941 if ( diversified ){ 2942 2943 try{ 2944 this_mon.enter(); 2945 2946 interesting_downloads.remove( f_ready_download ); 2947 2948 }finally{ 2949 2950 this_mon.exit(); 2951 } 2952 2953 }else if ( total < INTERESTING_AVAIL_MAX ){ 2954 2955 // once we're registered we don't need to process this download any 2956 // more unless it goes active and then inactive again 2957 2958 try{ 2959 this_mon.enter(); 2960 2961 interesting_downloads.remove( f_ready_download ); 2962 2963 }finally{ 2964 2965 this_mon.exit(); 2966 } 2967 2968 interesting_published++; 2969 2970 if ( !disable_put ){ 2971 2972 dht.put( 2973 torrent.getHash(), 2974 "Presence store '" + f_ready_download.getName() + "'", 2975 "0".getBytes(), // port 0, no connections 2976 (byte)0, 2977 new DHTPluginOperationListener() 2978 { 2979 public boolean 2980 diversified() 2981 { 2982 return( true ); 2983 } 2984 2985 public void 2986 starts( 2987 byte[] key ) 2988 { 2989 } 2990 2991 public void 2992 valueRead( 2993 DHTPluginContact originator, 2994 DHTPluginValue value ) 2995 { 2996 } 2997 2998 public void 2999 valueWritten( 3000 DHTPluginContact target, 3001 DHTPluginValue value ) 3002 { 3003 } 3004 3005 public void 3006 complete( 3007 byte[] key, 3008 boolean timeout_occurred ) 3009 { 3010 } 3011 }); 3012 } 3013 } 3014 3015 3016 try{ 3017 this_mon.enter(); 3018 3019 int[] run_data = running_downloads.get( f_ready_download ); 3020 3021 if ( run_data == null ){ 3022 3023 run_data = run_data_cache.get( f_ready_download ); 3024 } 3025 3026 if ( run_data != null ){ 3027 3028 if ( total < INTERESTING_AVAIL_MAX ){ 3029 3030 run_data[1] = seeds; 3031 run_data[2] = leechers; 3032 run_data[3] = total; 3033 3034 }else{ 3035 3036 run_data[1] = Math.max( run_data[1], seeds ); 3037 run_data[2] = Math.max( run_data[2], leechers ); 3038 } 3039 3040 run_data[4] = (int)(SystemTime.getCurrentTime()/1000); 3041 } 3042 }finally{ 3043 3044 this_mon.exit(); 3045 } 3046 3047 if ( i2p_seeds + i2p_leechers > 0 ){ 3048 3049 int[] details = (int[])f_ready_download.getUserData( DOWNLOAD_USER_DATA_I2P_SCRAPE_KEY ); 3050 3051 if ( details == null ){ 3052 3053 details = new int[]{ i2p_seeds, i2p_leechers }; 3054 3055 f_ready_download.setUserData( DOWNLOAD_USER_DATA_I2P_SCRAPE_KEY,details ); 3056 3057 }else{ 3058 3059 details[0] = Math.max( details[0], i2p_seeds ); 3060 details[1] = Math.max( details[1], i2p_leechers ); 3061 } 3062 } 3063 3064 3065 f_ready_download.setScrapeResult( 3066 new DownloadScrapeResult() 3067 { 3068 public Download 3069 getDownload() 3070 { 3071 return( null ); 3072 } 3073 3074 public int 3075 getResponseType() 3076 { 3077 return( RT_SUCCESS ); 3078 } 3079 3080 public int 3081 getSeedCount() 3082 { 3083 return( seeds ); 3084 } 3085 3086 public int 3087 getNonSeedCount() 3088 { 3089 return( leechers ); 3090 } 3091 3092 public long 3093 getScrapeStartTime() 3094 { 3095 return( SystemTime.getCurrentTime()); 3096 } 3097 3098 public void 3099 setNextScrapeStartTime( 3100 long nextScrapeStartTime) 3101 { 3102 } 3103 3104 public long 3105 getNextScrapeStartTime() 3106 { 3107 return( f_next_check ); 3108 } 3109 3110 public String 3111 getStatus() 3112 { 3113 return( "OK" ); 3114 } 3115 3116 public URL 3117 getURL() 3118 { 3119 URL url_to_report = torrent.isDecentralised()?torrent.getAnnounceURL():DEFAULT_URL; 3120 3121 return( url_to_report ); 3122 } 3123 }); 3124 } 3125 }); 3126 3127 } 3128 } 3129 } 3130 3131 public void 3132 stateChanged( 3133 Download download, 3134 int old_state, 3135 int new_state ) 3136 { 3137 int state = download.getState(); 3138 3139 try{ 3140 this_mon.enter(); 3141 3142 if ( state == Download.ST_DOWNLOADING || 3143 state == Download.ST_SEEDING || 3144 state == Download.ST_QUEUED ){ // included queued here for the mo to avoid lots 3145 // of thrash for torrents that flip a lot 3146 3147 if ( running_downloads.containsKey( download )){ 3148 3149 // force requery 3150 3151 query_map.put( download, new Long( SystemTime.getCurrentTime())); 3152 } 3153 } 3154 }finally{ 3155 3156 this_mon.exit(); 3157 } 3158 3159 // don't do anything if paused as we want things to just continue as they are (we would force an announce here otherwise) 3160 3161 if ( !download.isPaused()){ 3162 3163 checkDownloadForRegistration( download, false ); 3164 } 3165 } 3166 3167 public void 3168 announceAll() 3169 { 3170 log.log( "Announce-all requested" ); 3171 3172 Long now = new Long( SystemTime.getCurrentTime()); 3173 3174 try{ 3175 this_mon.enter(); 3176 3177 Iterator<Map.Entry<Download,Long>> it = query_map.entrySet().iterator(); 3178 3179 while( it.hasNext()){ 3180 3181 Map.Entry<Download,Long> entry = it.next(); 3182 3183 entry.setValue( now ); 3184 } 3185 }finally{ 3186 3187 this_mon.exit(); 3188 } 3189 } 3190 3191 private void 3192 announce( 3193 Download download ) 3194 { 3195 log.log( "Announce requested for " + download.getName()); 3196 3197 try{ 3198 this_mon.enter(); 3199 3200 query_map.put(download, SystemTime.getCurrentTime()); 3201 3202 }finally{ 3203 3204 this_mon.exit(); 3205 } 3206 } 3207 3208 public void 3209 positionChanged( 3210 Download download, 3211 int oldPosition, 3212 int newPosition ) 3213 { 3214 } 3215 3216 protected void 3217 configChanged() 3218 { 3219 Download[] downloads = plugin_interface.getDownloadManager().getDownloads(); 3220 3221 for (int i=0;i<downloads.length;i++){ 3222 3223 checkDownloadForRegistration(downloads[i], false ); 3224 } 3225 } 3226 3227 /** 3228 * This is used by the dhtscraper plugin 3229 */ 3230 3231 public DownloadScrapeResult 3232 scrape( 3233 byte[] hash ) 3234 { 3235 final int[] seeds = {0}; 3236 final int[] leechers = {0}; 3237 3238 final AESemaphore sem = new AESemaphore( "DHTTrackerPlugin:scrape" ); 3239 3240 dht.get(hash, 3241 "Scrape for " + ByteFormatter.encodeString( hash ).substring( 0, 16 ), 3242 DHTPlugin.FLAG_DOWNLOADING, 3243 NUM_WANT, 3244 SCRAPE_TIMEOUT, 3245 false, false, 3246 new DHTPluginOperationListener() 3247 { 3248 public boolean 3249 diversified() 3250 { 3251 return( true ); 3252 } 3253 3254 public void 3255 starts( 3256 byte[] key ) 3257 { 3258 } 3259 3260 public void 3261 valueRead( 3262 DHTPluginContact originator, 3263 DHTPluginValue value ) 3264 { 3265 if (( value.getFlags() & DHTPlugin.FLAG_DOWNLOADING ) == 1 ){ 3266 3267 leechers[0]++; 3268 3269 }else{ 3270 3271 seeds[0]++; 3272 } 3273 } 3274 3275 public void 3276 valueWritten( 3277 DHTPluginContact target, 3278 DHTPluginValue value ) 3279 { 3280 } 3281 3282 public void 3283 complete( 3284 byte[] key, 3285 boolean timeout_occurred ) 3286 { 3287 sem.release(); 3288 } 3289 }); 3290 3291 sem.reserve(); 3292 3293 return( 3294 new DownloadScrapeResult() 3295 { 3296 public Download 3297 getDownload() 3298 { 3299 return( null ); 3300 } 3301 3302 public int 3303 getResponseType() 3304 { 3305 return( RT_SUCCESS ); 3306 } 3307 3308 public int 3309 getSeedCount() 3310 { 3311 return( seeds[0] ); 3312 } 3313 3314 public int 3315 getNonSeedCount() 3316 { 3317 return( leechers[0] ); 3318 } 3319 3320 public long 3321 getScrapeStartTime() 3322 { 3323 return( 0 ); 3324 } 3325 3326 public void 3327 setNextScrapeStartTime( 3328 long nextScrapeStartTime) 3329 { 3330 } 3331 3332 public long 3333 getNextScrapeStartTime() 3334 { 3335 return( 0 ); 3336 } 3337 3338 public String 3339 getStatus() 3340 { 3341 return( "OK" ); 3342 } 3343 3344 public URL 3345 getURL() 3346 { 3347 return( null ); 3348 } 3349 }); 3350 } 3351 3352 protected void 3353 increaseActive( 3354 Download dl ) 3355 { 3356 try{ 3357 this_mon.enter(); 3358 3359 Integer active_i = (Integer)in_progress.get( dl ); 3360 3361 int active = active_i==null?0:active_i.intValue(); 3362 3363 in_progress.put( dl, new Integer( active+1 )); 3364 3365 }finally{ 3366 3367 this_mon.exit(); 3368 } 3369 } 3370 3371 protected void 3372 decreaseActive( 3373 Download dl ) 3374 { 3375 try{ 3376 this_mon.enter(); 3377 3378 Integer active_i = (Integer)in_progress.get( dl ); 3379 3380 if ( active_i == null ){ 3381 3382 Debug.out( "active count inconsistent" ); 3383 3384 }else{ 3385 3386 int active = active_i.intValue()-1; 3387 3388 if ( active == 0 ){ 3389 3390 in_progress.remove( dl ); 3391 3392 }else{ 3393 3394 in_progress.put( dl, new Integer( active )); 3395 } 3396 } 3397 }finally{ 3398 3399 this_mon.exit(); 3400 } 3401 } 3402 3403 protected boolean 3404 isActive( 3405 Download dl ) 3406 { 3407 try{ 3408 this_mon.enter(); 3409 3410 return( in_progress.get(dl) != null ); 3411 3412 }finally{ 3413 3414 this_mon.exit(); 3415 } 3416 } 3417 3418 protected class 3419 RegistrationDetails 3420 { 3421 //private static final int DERIVED_ACTIVE_MIN_MILLIS = 2*60*60*1000; 3422 3423 private putDetails put_details; 3424 private byte flags; 3425 private trackerTarget[] put_targets; 3426 private List<trackerTarget> not_put_targets; 3427 3428 //private long derived_active_start = -1; 3429 //private long previous_metric; 3430 3431 protected 3432 RegistrationDetails( 3433 Download _download, 3434 int _reg_type, 3435 putDetails _put_details, 3436 byte _flags ) 3437 { 3438 put_details = _put_details; 3439 flags = _flags; 3440 3441 getTrackerTargets( _download, _reg_type ); 3442 } 3443 3444 protected void 3445 update( 3446 putDetails _put_details, 3447 byte _flags ) 3448 { 3449 put_details = _put_details; 3450 flags = _flags; 3451 } 3452 3453 protected boolean 3454 updateTargets( 3455 Download _download, 3456 int _reg_type ) 3457 { 3458 trackerTarget[] old_put_targets = put_targets; 3459 3460 getTrackerTargets( _download, _reg_type ); 3461 3462 // first remove any redundant entries 3463 3464 for (int i=0;i<old_put_targets.length;i++){ 3465 3466 boolean found = false; 3467 3468 byte[] old_hash = old_put_targets[i].getHash(); 3469 3470 for (int j=0;j<put_targets.length;j++){ 3471 3472 if ( Arrays.equals( put_targets[j].getHash(), old_hash )){ 3473 3474 found = true; 3475 3476 break; 3477 } 3478 } 3479 3480 if ( !found ){ 3481 3482 trackerRemove( _download, old_put_targets[i] ); 3483 } 3484 } 3485 3486 // now look to see if we have any new stuff 3487 3488 boolean changed = false; 3489 3490 for (int i=0;i<put_targets.length;i++){ 3491 3492 byte[] new_hash = put_targets[i].getHash(); 3493 3494 boolean found = false; 3495 3496 for (int j=0;j<old_put_targets.length;j++){ 3497 3498 if ( Arrays.equals( old_put_targets[j].getHash(), new_hash )){ 3499 3500 found = true; 3501 3502 break; 3503 } 3504 } 3505 3506 if ( !found ){ 3507 3508 changed = true; 3509 } 3510 } 3511 3512 return( changed ); 3513 } 3514 3515 protected putDetails 3516 getPutDetails() 3517 { 3518 return( put_details ); 3519 } 3520 3521 protected byte 3522 getFlags() 3523 { 3524 return( flags ); 3525 } 3526 3527 protected trackerTarget[] 3528 getTargets( 3529 boolean for_put ) 3530 { 3531 if ( for_put || not_put_targets == null ){ 3532 3533 return( put_targets ); 3534 3535 }else{ 3536 3537 List<trackerTarget> result = new ArrayList<trackerTarget>( Arrays.asList( put_targets )); 3538 3539 for (int i=0;i<not_put_targets.size()&& i < 2; i++ ){ 3540 3541 trackerTarget target = (trackerTarget)not_put_targets.remove(0); 3542 3543 not_put_targets.add( target ); 3544 3545 // System.out.println( "Mixing in " + target.getDesc()); 3546 3547 result.add( target ); 3548 } 3549 3550 return( (trackerTarget[])result.toArray( new trackerTarget[result.size()])); 3551 } 3552 } 3553 3554 protected void 3555 getTrackerTargets( 3556 Download download, 3557 int type ) 3558 { 3559 byte[] torrent_hash = download.getTorrent().getHash(); 3560 3561 List<trackerTarget> result = new ArrayList<trackerTarget>(); 3562 3563 if ( type == REG_TYPE_FULL ){ 3564 3565 result.add( new trackerTarget( torrent_hash, REG_TYPE_FULL, "" )); 3566 } 3567 /* 3568 if ( ADD_ASN_DERIVED_TARGET ){ 3569 3570 NetworkAdminASN net_asn = NetworkAdmin.getSingleton().getCurrentASN(); 3571 3572 String as = net_asn.getAS(); 3573 String asn = net_asn.getASName(); 3574 3575 if ( as.length() > 0 && asn.length() > 0 ){ 3576 3577 String key = "azderived:asn:" + as; 3578 3579 try{ 3580 byte[] asn_bytes = key.getBytes( "UTF-8" ); 3581 3582 byte[] key_bytes = new byte[torrent_hash.length + asn_bytes.length]; 3583 3584 System.arraycopy( torrent_hash, 0, key_bytes, 0, torrent_hash.length ); 3585 3586 System.arraycopy( asn_bytes, 0, key_bytes, torrent_hash.length, asn_bytes.length ); 3587 3588 result.add( new trackerTarget( key_bytes, REG_TYPE_DERIVED, asn + "/" + as )); 3589 3590 }catch( Throwable e ){ 3591 3592 Debug.printStackTrace(e); 3593 } 3594 } 3595 } 3596 3597 if ( ADD_NETPOS_DERIVED_TARGETS ){ 3598 3599 long now = SystemTime.getMonotonousTime(); 3600 3601 boolean do_it; 3602 3603 Long metric = (Long)download.getUserData( DL_DERIVED_METRIC_KEY ); 3604 3605 boolean do_it_now = metric != null; 3606 3607 if ( derived_active_start >= 0 && now - derived_active_start <= DERIVED_ACTIVE_MIN_MILLIS ){ 3608 3609 do_it = true; 3610 3611 if ( metric == null ){ 3612 3613 metric = new Long( previous_metric ); 3614 } 3615 }else{ 3616 3617 if ( do_it_now ){ 3618 3619 do_it = true; 3620 3621 }else{ 3622 3623 derived_active_start = -1; 3624 3625 do_it = false; 3626 } 3627 } 3628 3629 boolean newly_active = false; 3630 3631 if ( do_it_now ){ 3632 3633 newly_active = derived_active_start == -1; 3634 3635 derived_active_start = now; 3636 } 3637 3638 List<trackerTarget> skipped_targets = null; 3639 3640 if ( do_it ){ 3641 3642 previous_metric = metric.longValue(); 3643 3644 try{ 3645 DHTNetworkPosition[] positions = getNetworkPositions(); 3646 3647 for (int i=0;i<positions.length;i++){ 3648 3649 DHTNetworkPosition pos = positions[i]; 3650 3651 if ( pos.getPositionType() == DHTNetworkPosition.POSITION_TYPE_VIVALDI_V2 ){ 3652 3653 if ( pos.isValid()){ 3654 3655 List<Object[]> derived_results = getVivaldiTargets( torrent_hash, pos.getLocation()); 3656 3657 int num_to_add = metric.intValue() * derived_results.size() / 100; 3658 3659 // System.out.println( download.getName() + ": metric=" + metric + ", adding=" + num_to_add ); 3660 3661 for (int j=0;j<derived_results.size();j++){ 3662 3663 Object[] entry = derived_results.get(j); 3664 3665 // int distance = ((Integer)entry[0]).intValue(); 3666 3667 trackerTarget target= (trackerTarget)entry[1]; 3668 3669 if ( j < num_to_add ){ 3670 3671 result.add( target ); 3672 3673 }else{ 3674 3675 if ( skipped_targets == null ){ 3676 3677 skipped_targets = new ArrayList<trackerTarget>(); 3678 } 3679 3680 skipped_targets.add( target ); 3681 } 3682 } 3683 } 3684 } 3685 } 3686 }catch( Throwable e ){ 3687 3688 Debug.printStackTrace(e); 3689 } 3690 } 3691 3692 not_put_targets = skipped_targets; 3693 } 3694 */ 3695 3696 put_targets = result.toArray( new trackerTarget[result.size()]); 3697 } 3698 } 3699 3700 /* 3701 private DHTNetworkPosition[] 3702 getNetworkPositions() 3703 { 3704 DHTNetworkPosition[] res = current_network_positions; 3705 3706 long now = SystemTime.getMonotonousTime(); 3707 3708 if ( res == null || 3709 now - last_net_pos_time > 30*60*1000 ){ 3710 3711 res = current_network_positions = DHTNetworkPositionManager.getLocalPositions(); 3712 3713 last_net_pos_time = now; 3714 } 3715 3716 return( res ); 3717 } 3718 */ 3719 3720 private void 3721 log( 3722 Download download, 3723 String str ) 3724 { 3725 log( download.getTorrent(), str ); 3726 } 3727 3728 private void 3729 log( 3730 Torrent torrent, 3731 String str ) 3732 { 3733 log.log( torrent, LoggerChannel.LT_INFORMATION, str ); 3734 } 3735 3736 public TrackerPeerSource 3737 getTrackerPeerSource( 3738 final Download download ) 3739 { 3740 return( 3741 new TrackerPeerSourceAdapter() 3742 { 3743 private long last_fixup; 3744 private boolean updating; 3745 private int status = ST_UNKNOWN; 3746 private long next_time = -1; 3747 private int[] run_data; 3748 3749 private void 3750 fixup() 3751 { 3752 long now = SystemTime.getMonotonousTime(); 3753 3754 if ( now - last_fixup > 5*1000 ){ 3755 3756 try{ 3757 this_mon.enter(); 3758 3759 updating = false; 3760 next_time = -1; 3761 3762 run_data = running_downloads.get( download ); 3763 3764 if ( run_data != null ){ 3765 3766 if ( in_progress.containsKey( download )){ 3767 3768 updating = true; 3769 } 3770 3771 status = initialised_sem.isReleasedForever()?ST_ONLINE:ST_STOPPED; 3772 3773 Long l_next_time = query_map.get( download ); 3774 3775 if ( l_next_time != null ){ 3776 3777 next_time = l_next_time.longValue(); 3778 } 3779 }else if ( interesting_downloads.containsKey( download )){ 3780 3781 status = ST_STOPPED; 3782 3783 }else{ 3784 3785 int dl_state = download.getState(); 3786 3787 if ( dl_state == Download.ST_DOWNLOADING || 3788 dl_state == Download.ST_SEEDING || 3789 dl_state == Download.ST_QUEUED ){ 3790 3791 status = ST_DISABLED; 3792 3793 }else{ 3794 3795 status = ST_STOPPED; 3796 } 3797 } 3798 3799 if ( run_data == null ){ 3800 3801 run_data = run_data_cache.get( download ); 3802 } 3803 }finally{ 3804 3805 this_mon.exit(); 3806 } 3807 3808 String[] sources = download.getListAttribute( ta_peer_sources ); 3809 3810 boolean ok = false; 3811 3812 if ( sources != null ){ 3813 3814 for (int i=0;i<sources.length;i++){ 3815 3816 if ( sources[i].equalsIgnoreCase( "DHT")){ 3817 3818 ok = true; 3819 3820 break; 3821 } 3822 } 3823 } 3824 3825 if ( !ok ){ 3826 3827 status = ST_DISABLED; 3828 } 3829 3830 last_fixup = now; 3831 } 3832 } 3833 3834 public int 3835 getType() 3836 { 3837 return( TP_DHT ); 3838 } 3839 3840 public String 3841 getName() 3842 { 3843 return( "DHT: " + model.getStatus().getText()); 3844 } 3845 3846 public int 3847 getStatus() 3848 { 3849 fixup(); 3850 3851 return( status ); 3852 } 3853 3854 public int 3855 getSeedCount() 3856 { 3857 fixup(); 3858 3859 if ( run_data == null ){ 3860 3861 return( -1 ); 3862 } 3863 3864 return( run_data[1] ); 3865 } 3866 3867 public int 3868 getLeecherCount() 3869 { 3870 fixup(); 3871 3872 if ( run_data == null ){ 3873 3874 return( -1 ); 3875 } 3876 3877 return( run_data[2] ); 3878 } 3879 3880 public int 3881 getPeers() 3882 { 3883 fixup(); 3884 3885 if ( run_data == null ){ 3886 3887 return( -1 ); 3888 } 3889 3890 return( run_data[3] ); 3891 } 3892 3893 public int 3894 getLastUpdate() 3895 { 3896 fixup(); 3897 3898 if ( run_data == null ){ 3899 3900 return( 0 ); 3901 } 3902 3903 return( run_data[4] ); 3904 } 3905 3906 public int 3907 getSecondsToUpdate() 3908 { 3909 fixup(); 3910 3911 if ( next_time < 0 ){ 3912 3913 return( -1 ); 3914 } 3915 3916 return((int)(( next_time - SystemTime.getCurrentTime())/1000 )); 3917 } 3918 3919 public int 3920 getInterval() 3921 { 3922 fixup(); 3923 3924 if ( run_data == null ){ 3925 3926 return( -1 ); 3927 } 3928 3929 return((int)(current_announce_interval/1000)); 3930 } 3931 3932 public int 3933 getMinInterval() 3934 { 3935 fixup(); 3936 3937 if ( run_data == null ){ 3938 3939 return( -1 ); 3940 } 3941 3942 return( ANNOUNCE_MIN_DEFAULT/1000 ); 3943 } 3944 3945 public boolean 3946 isUpdating() 3947 { 3948 return( updating ); 3949 } 3950 3951 public boolean 3952 canManuallyUpdate() 3953 { 3954 fixup(); 3955 3956 return( run_data != null ); 3957 } 3958 3959 public void 3960 manualUpdate() 3961 { 3962 announce( download ); 3963 } 3964 }); 3965 } 3966 3967 3968 public TrackerPeerSource[] 3969 getTrackerPeerSources( 3970 final Torrent torrent ) 3971 { 3972 TrackerPeerSource vuze_dht = 3973 new TrackerPeerSourceAdapter() 3974 { 3975 private volatile boolean query_done; 3976 private volatile int status = ST_INITIALISING; 3977 3978 private volatile int seeds = 0; 3979 private volatile int leechers = 0; 3980 3981 3982 private void 3983 fixup() 3984 { 3985 if ( initialised_sem.isReleasedForever()){ 3986 3987 synchronized( this ){ 3988 3989 if ( query_done ){ 3990 3991 return; 3992 } 3993 3994 query_done = true; 3995 3996 status = ST_UPDATING; 3997 } 3998 3999 dht.get( torrent.getHash(), 4000 "Availability lookup for '" + torrent.getName() + "'", 4001 DHTPlugin.FLAG_DOWNLOADING, 4002 NUM_WANT, 4003 ANNOUNCE_DERIVED_TIMEOUT, 4004 false, true, 4005 new DHTPluginOperationListener() 4006 { 4007 public void 4008 starts( 4009 byte[] key ) 4010 { 4011 } 4012 4013 public boolean 4014 diversified() 4015 { 4016 return( true ); 4017 } 4018 4019 public void 4020 valueRead( 4021 DHTPluginContact originator, 4022 DHTPluginValue value ) 4023 { 4024 if (( value.getFlags() & DHTPlugin.FLAG_DOWNLOADING ) == 1 ){ 4025 4026 seeds++; 4027 4028 }else{ 4029 4030 leechers++; 4031 } 4032 } 4033 4034 public void 4035 valueWritten( 4036 DHTPluginContact target, 4037 DHTPluginValue value ) 4038 { 4039 4040 } 4041 4042 public void 4043 complete( 4044 byte[] key, 4045 boolean timeout_occurred ) 4046 { 4047 status = ST_ONLINE; 4048 } 4049 }); 4050 } 4051 } 4052 4053 public int 4054 getType() 4055 { 4056 return( TP_DHT ); 4057 } 4058 4059 public String 4060 getName() 4061 { 4062 return( "Vuze DHT" ); 4063 } 4064 4065 public int 4066 getStatus() 4067 { 4068 fixup(); 4069 4070 return( status ); 4071 } 4072 4073 public int 4074 getSeedCount() 4075 { 4076 fixup(); 4077 4078 int result = seeds; 4079 4080 if ( result == 0 && status != ST_ONLINE ){ 4081 4082 return( -1 ); 4083 } 4084 4085 return( result ); 4086 } 4087 4088 public int 4089 getLeecherCount() 4090 { 4091 fixup(); 4092 4093 int result = leechers; 4094 4095 if ( result == 0 && status != ST_ONLINE ){ 4096 4097 return( -1 ); 4098 } 4099 4100 return( result ); 4101 } 4102 4103 public int 4104 getPeers() 4105 { 4106 return( -1 ); 4107 } 4108 4109 public boolean 4110 isUpdating() 4111 { 4112 return( status == ST_UPDATING ); 4113 } 4114 4115 }; 4116 4117 if ( alt_lookup_handler != null ){ 4118 4119 TrackerPeerSource alt_dht = 4120 new TrackerPeerSourceAdapter() 4121 { 4122 private volatile int status = ST_UPDATING; 4123 private volatile int peers = 0; 4124 4125 { 4126 alt_lookup_handler.get( 4127 torrent.getHash(), 4128 false, 4129 new DHTTrackerPluginAlt.LookupListener() 4130 { 4131 public void 4132 foundPeer( 4133 InetSocketAddress address ) 4134 { 4135 peers++; 4136 } 4137 4138 public boolean 4139 isComplete() 4140 { 4141 return( false ); 4142 } 4143 4144 public void 4145 completed() 4146 { 4147 status = ST_ONLINE; 4148 } 4149 }); 4150 } 4151 4152 4153 public int 4154 getType() 4155 { 4156 return( TP_DHT ); 4157 } 4158 4159 public String 4160 getName() 4161 { 4162 return( "Mainline DHT" ); 4163 } 4164 4165 public int 4166 getStatus() 4167 { 4168 return( status ); 4169 } 4170 4171 public int 4172 getPeers() 4173 { 4174 int result = peers; 4175 4176 if ( result == 0 && status != ST_ONLINE ){ 4177 4178 return( -1 ); 4179 } 4180 4181 return( result ); 4182 } 4183 4184 public boolean 4185 isUpdating() 4186 { 4187 return( status == ST_UPDATING ); 4188 } 4189 4190 }; 4191 4192 return( new TrackerPeerSource[]{ vuze_dht, alt_dht } ); 4193 4194 }else{ 4195 4196 return( new TrackerPeerSource[]{ vuze_dht } ); 4197 } 4198 } 4199 4200 4201 4202 /* 4203 public static List<Object[]> 4204 getVivaldiTargets( 4205 byte[] torrent_hash, 4206 double[] loc ) 4207 { 4208 List<Object[]> derived_results = new ArrayList<Object[]>(); 4209 4210 String loc_str = ""; 4211 4212 for (int j=0;j<loc.length;j++){ 4213 4214 loc_str += (j==0?"":",") + loc[j]; 4215 } 4216 4217 TriangleSlicer slicer = new TriangleSlicer( 25 ); 4218 4219 double t1_x = loc[0]; 4220 double t1_y = loc[1]; 4221 double t2_x = loc[2]; 4222 double t2_y = loc[3]; 4223 4224 int[] triangle1 = slicer.findVertices( t1_x, t1_y ); 4225 4226 int[] triangle2 = slicer.findVertices( t2_x, t2_y ); 4227 4228 4229 for (int j=0;j<triangle1.length;j+=2 ){ 4230 4231 int t1_vx = triangle1[j]; 4232 int t1_vy = triangle1[j+1]; 4233 4234 double t1_distance = getDistance( t1_x, t1_y, t1_vx, t1_vy ); 4235 4236 for (int k=0;k<triangle2.length;k+=2 ){ 4237 4238 int t2_vx = triangle2[k]; 4239 int t2_vy = triangle2[k+1]; 4240 4241 double t2_distance = getDistance( t2_x, t2_y, t2_vx, t2_vy ); 4242 4243 // these distances are in different dimensions - make up a combined distance 4244 4245 double distance = getDistance( t1_distance, 0, 0, t2_distance ); 4246 4247 4248 String key = "azderived:vivaldi:"; 4249 4250 String v_str = t1_vx + "." + t1_vy + "." + t2_vx + "." + t2_vy; 4251 4252 key += v_str; 4253 4254 try{ 4255 byte[] v_bytes = key.getBytes( "UTF-8" ); 4256 4257 byte[] key_bytes = new byte[torrent_hash.length + v_bytes.length]; 4258 4259 System.arraycopy( torrent_hash, 0, key_bytes, 0, torrent_hash.length ); 4260 4261 System.arraycopy( v_bytes, 0, key_bytes, torrent_hash.length, v_bytes.length ); 4262 4263 derived_results.add( 4264 new Object[]{ 4265 new Integer((int)distance), 4266 new trackerTarget( key_bytes, REG_TYPE_DERIVED, "Vivaldi: " + v_str ) }); 4267 4268 4269 }catch( Throwable e ){ 4270 4271 Debug.printStackTrace(e); 4272 } 4273 } 4274 } 4275 4276 Collections.sort( 4277 derived_results, 4278 new Comparator<Object[]>() 4279 { 4280 public int 4281 compare( 4282 Object[] entry1, 4283 Object[] entry2 ) 4284 { 4285 int d1 = ((Integer)entry1[0]).intValue(); 4286 int d2 = ((Integer)entry2[0]).intValue(); 4287 4288 return( d1 - d2 ); 4289 } 4290 }); 4291 4292 return( derived_results ); 4293 } 4294 4295 protected static double 4296 getDistance( 4297 double x1, 4298 double y1, 4299 double x2, 4300 double y2 ) 4301 { 4302 return(Math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1))); 4303 } 4304 */ 4305 4306 protected static class 4307 putDetails 4308 { 4309 private String encoded; 4310 private String ip_override; 4311 private int tcp_port; 4312 private int udp_port; 4313 private boolean i2p; 4314 4315 private 4316 putDetails( 4317 String _encoded, 4318 String _ip, 4319 int _tcp_port, 4320 int _udp_port, 4321 boolean _i2p ) 4322 { 4323 encoded = _encoded; 4324 ip_override = _ip; 4325 tcp_port = _tcp_port; 4326 udp_port = _udp_port; 4327 i2p = _i2p; 4328 } 4329 4330 protected String 4331 getEncoded() 4332 { 4333 return( encoded ); 4334 } 4335 4336 protected String 4337 getIPOverride() 4338 { 4339 return( ip_override ); 4340 } 4341 4342 protected int 4343 getTCPPort() 4344 { 4345 return( tcp_port ); 4346 } 4347 4348 protected int 4349 getUDPPort() 4350 { 4351 return( udp_port ); 4352 } 4353 4354 private boolean 4355 hasI2P() 4356 { 4357 return( i2p ); 4358 } 4359 4360 protected boolean 4361 sameAs( 4362 putDetails other ) 4363 { 4364 return( getEncoded().equals( other.getEncoded())); 4365 } 4366 } 4367 4368 public static class 4369 trackerTarget 4370 { 4371 private String desc; 4372 private byte[] hash; 4373 private int type; 4374 4375 protected 4376 trackerTarget( 4377 byte[] _hash, 4378 int _type, 4379 String _desc ) 4380 { 4381 hash = _hash; 4382 type = _type; 4383 desc = _desc; 4384 } 4385 4386 public int 4387 getType() 4388 { 4389 return( type ); 4390 } 4391 4392 public byte[] 4393 getHash() 4394 { 4395 return( hash ); 4396 } 4397 4398 public String 4399 getDesc() 4400 { 4401 if ( type != REG_TYPE_FULL ){ 4402 4403 return( "(" + desc + ")" ); 4404 } 4405 4406 return( "" ); 4407 } 4408 } 4409 4410 public static class 4411 TriangleSlicer 4412 { 4413 int width; 4414 4415 private double w; 4416 private double w2; 4417 private double h; 4418 4419 private double tan60; 4420 4421 public TriangleSlicer(int width) { 4422 this.width = width; 4423 4424 this.w = (float) width; 4425 this.w2 = w / 2; 4426 this.h = Math.cos(Math.PI / 6) * w; 4427 4428 this.tan60 = Math.tan(Math.PI / 3); 4429 4430 } 4431 4432 /** 4433 * 4434 * @param x 4435 * @param y 4436 * @return an array of int values being x,y coordinate pairs 4437 */ 4438 public int[] findVertices(double x,double y) { 4439 4440 int yN = (int) Math.floor((y / h)); 4441 int xN = (int) Math.floor((x /w2)); 4442 4443 double v1x,v2x,v3x,v1y,v2y,v3y; 4444 4445 //weither the triangle is like /\ (true) or \/ (false) 4446 boolean upTriangle; 4447 4448 if((xN+yN) % 2 == 0) { 4449 // we have a / separator in the "cell" 4450 if( (y-h*yN) > (x-w2*xN) * tan60 ) { 4451 //we're in the upper part 4452 upTriangle = false; 4453 v1x = w2 * (xN - 1); 4454 v1y = h * (yN + 1) ; 4455 } else { 4456 //we're in the lower part 4457 upTriangle = true; 4458 v1x = w2 * xN; 4459 v1y = h * yN; 4460 } 4461 } else { 4462 // We have a \ separator in the "cell" 4463 if( (y- h*yN) > (w2 - (x-w2*xN)) * tan60 ) { 4464 //we're in the upper part 4465 upTriangle = false; 4466 v1x = w2 * xN; 4467 v1y = h * (yN+1); 4468 } else { 4469 //we're in the lower part 4470 upTriangle = true; 4471 v1x = w2 * (xN - 1); 4472 v1y = h * yN; 4473 } 4474 } 4475 4476 if(upTriangle) { 4477 v2x = v1x + w; 4478 v2y = v1y; 4479 4480 v3x = v1x + w2; 4481 v3y = v1y + h; 4482 } else { 4483 v2x = v1x + w; 4484 v2y = v1y; 4485 4486 v3x = v1x + w2; 4487 v3y = v1y - h; 4488 } 4489 4490 int[] result = new int[6]; 4491 4492 result[0] = (int) v1x; 4493 result[1] = (int) v1y; 4494 4495 result[2] = (int) v2x; 4496 result[3] = (int) v2y; 4497 4498 result[4] = (int) v3x; 4499 result[5] = (int) v3y; 4500 4501 return result; 4502 4503 } 4504 } 4505 } 4506