1 /* 2 * Created on Dec 4, 2009 3 * Created by Paul Gardner 4 * 5 * Copyright (C) Azureus Software, Inc, All Rights Reserved. 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation; either version 2 10 * of the License, or (at your option) any later version. 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 */ 19 20 21 package org.gudy.azureus2.core3.tracker.client.impl; 22 23 import java.net.URL; 24 import java.util.*; 25 26 import org.gudy.azureus2.core3.logging.LogEvent; 27 import org.gudy.azureus2.core3.logging.Logger; 28 import org.gudy.azureus2.core3.peer.PEPeerSource; 29 import org.gudy.azureus2.core3.torrent.TOTorrent; 30 import org.gudy.azureus2.core3.torrent.TOTorrentAnnounceURLSet; 31 import org.gudy.azureus2.core3.torrent.TOTorrentException; 32 import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncer; 33 import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerDataProvider; 34 import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerException; 35 import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerFactory; 36 import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerResponse; 37 import org.gudy.azureus2.core3.tracker.client.impl.bt.TRTrackerBTAnnouncerImpl; 38 import org.gudy.azureus2.core3.tracker.client.impl.dht.TRTrackerDHTAnnouncerImpl; 39 import org.gudy.azureus2.core3.util.AEThread2; 40 import org.gudy.azureus2.core3.util.Debug; 41 import org.gudy.azureus2.core3.util.IndentWriter; 42 import org.gudy.azureus2.core3.util.SimpleTimer; 43 import org.gudy.azureus2.core3.util.SystemTime; 44 import org.gudy.azureus2.core3.util.TimerEvent; 45 import org.gudy.azureus2.core3.util.TimerEventPerformer; 46 import org.gudy.azureus2.core3.util.TorrentUtils; 47 import org.gudy.azureus2.plugins.download.DownloadAnnounceResult; 48 49 import com.aelitis.azureus.core.tracker.TrackerPeerSource; 50 import com.aelitis.azureus.core.util.CopyOnWriteList; 51 52 public class 53 TRTrackerAnnouncerMuxer 54 extends TRTrackerAnnouncerImpl 55 { 56 private static final int ACT_CHECK_INIT_DELAY = 2500; 57 private static final int ACT_CHECK_INTERIM_DELAY = 10*1000; 58 private static final int ACT_CHECK_IDLE_DELAY = 30*1000; 59 private static final int ACT_CHECK_SEEDING_SHORT_DELAY = 60*1000; 60 private static final int ACT_CHECK_SEEDING_LONG_DELAY = 3*60*1000; 61 62 63 private TRTrackerAnnouncerFactory.DataProvider f_provider; 64 private boolean is_manual; 65 66 private long create_time = SystemTime.getMonotonousTime(); 67 68 private CopyOnWriteList<TRTrackerAnnouncerHelper> announcers = new CopyOnWriteList<TRTrackerAnnouncerHelper>(); 69 private Set<TRTrackerAnnouncerHelper> activated = new HashSet<TRTrackerAnnouncerHelper>(); 70 private long last_activation_time; 71 private Set<String> failed_urls = new HashSet<String>(); 72 73 private volatile TimerEvent event; 74 75 private TRTrackerAnnouncerDataProvider provider; 76 private String ip_override; 77 private boolean complete; 78 private boolean stopped; 79 private boolean destroyed; 80 81 private String[] current_networks; 82 83 private TRTrackerAnnouncerHelper last_best_active; 84 private long last_best_active_set_time; 85 86 private Map<String,StatusSummary> recent_responses = new HashMap<String,StatusSummary>(); 87 88 private TRTrackerAnnouncerResponse last_response_informed; 89 90 91 protected TRTrackerAnnouncerMuxer( TOTorrent _torrent, TRTrackerAnnouncerFactory.DataProvider _f_provider, boolean _manual )92 TRTrackerAnnouncerMuxer( 93 TOTorrent _torrent, 94 TRTrackerAnnouncerFactory.DataProvider _f_provider, 95 boolean _manual ) 96 97 throws TRTrackerAnnouncerException 98 { 99 super( _torrent ); 100 101 try{ 102 last_response_informed = new TRTrackerAnnouncerResponseImpl( null, _torrent.getHashWrapper(), TRTrackerAnnouncerResponse.ST_OFFLINE, TRTrackerAnnouncer.REFRESH_MINIMUM_SECS, "Initialising" ); 103 104 }catch( TOTorrentException e ){ 105 106 Logger.log(new LogEvent( _torrent, LOGID, "Torrent hash retrieval fails", e)); 107 108 throw( new TRTrackerAnnouncerException( "TRTrackerAnnouncer: URL encode fails")); 109 } 110 111 is_manual = _manual; 112 f_provider = _f_provider; 113 114 split( true ); 115 } 116 117 protected void split( boolean first_time )118 split( 119 boolean first_time ) 120 121 throws TRTrackerAnnouncerException 122 { 123 String[] networks = f_provider==null?null:f_provider.getNetworks(); 124 125 boolean force_recreate = false; 126 127 if ( !first_time ){ 128 129 if ( current_networks != networks ){ 130 131 if ( current_networks == null || networks == null ){ 132 133 force_recreate = true; 134 135 }else{ 136 137 if ( networks.length != current_networks.length ){ 138 139 force_recreate = true; 140 141 }else{ 142 143 for ( String net1: current_networks ){ 144 145 boolean match = false; 146 147 for ( String net2: networks ){ 148 149 if ( net1 == net2 ){ 150 151 match = true; 152 } 153 } 154 155 if ( !match ){ 156 157 force_recreate = true; 158 159 break; 160 } 161 } 162 } 163 } 164 } 165 } 166 167 current_networks = networks; 168 169 TRTrackerAnnouncerHelper to_activate = null; 170 171 synchronized( this ){ 172 173 if ( stopped || destroyed ){ 174 175 return; 176 } 177 178 TOTorrent torrent = getTorrent(); 179 180 TOTorrentAnnounceURLSet[] sets = torrent.getAnnounceURLGroup().getAnnounceURLSets(); 181 182 // sanitise dht entries 183 184 if ( sets.length == 0 ){ 185 186 sets = new TOTorrentAnnounceURLSet[]{ torrent.getAnnounceURLGroup().createAnnounceURLSet( new URL[]{ torrent.getAnnounceURL()})}; 187 188 }else{ 189 190 boolean found_decentralised = false; 191 boolean modified = false; 192 193 for ( int i=0;i<sets.length;i++ ){ 194 195 TOTorrentAnnounceURLSet set = sets[i]; 196 197 URL[] urls = set.getAnnounceURLs().clone(); 198 199 for (int j=0;j<urls.length;j++){ 200 201 URL u = urls[j]; 202 203 if ( u != null && TorrentUtils.isDecentralised( u )){ 204 205 if ( found_decentralised ){ 206 207 modified = true; 208 209 urls[j] = null; 210 211 }else{ 212 213 found_decentralised = true; 214 } 215 } 216 } 217 } 218 219 if ( modified ){ 220 221 List<TOTorrentAnnounceURLSet> s_list = new ArrayList<TOTorrentAnnounceURLSet>(); 222 223 for ( TOTorrentAnnounceURLSet set: sets ){ 224 225 URL[] urls = set.getAnnounceURLs(); 226 227 List<URL> u_list = new ArrayList<URL>( urls.length ); 228 229 for ( URL u: urls ){ 230 231 if ( u != null ){ 232 233 u_list.add( u ); 234 } 235 } 236 237 if ( u_list.size() > 0 ){ 238 239 s_list.add( torrent.getAnnounceURLGroup().createAnnounceURLSet( u_list.toArray( new URL[ u_list.size() ]))); 240 } 241 } 242 243 sets = s_list.toArray( new TOTorrentAnnounceURLSet[ s_list.size() ]); 244 } 245 } 246 247 List<TOTorrentAnnounceURLSet[]> new_sets = new ArrayList<TOTorrentAnnounceURLSet[]>(); 248 249 if ( is_manual || sets.length < 2 ){ 250 251 new_sets.add( sets ); 252 253 }else{ 254 255 List<TOTorrentAnnounceURLSet> list = new ArrayList<TOTorrentAnnounceURLSet>( Arrays.asList( sets )); 256 257 // often we have http:/xxxx/ and udp:/xxxx/ as separate groups - keep these together 258 259 while( list.size() > 0 ){ 260 261 TOTorrentAnnounceURLSet set1 = list.remove(0); 262 263 boolean done = false; 264 265 URL[] urls1 = set1.getAnnounceURLs(); 266 267 if ( urls1.length == 1 ){ 268 269 URL url1 = urls1[0]; 270 271 String prot1 = url1.getProtocol().toLowerCase(); 272 String host1 = url1.getHost(); 273 274 for (int i=0;i<list.size();i++){ 275 276 TOTorrentAnnounceURLSet set2 = list.get(i); 277 278 URL[] urls2 = set2.getAnnounceURLs(); 279 280 if ( urls2.length == 1 ){ 281 282 URL url2 = urls2[0]; 283 284 String prot2 = url2.getProtocol().toLowerCase(); 285 String host2 = url2.getHost(); 286 287 if ( host1.equals( host2 )){ 288 289 if ( ( prot1.equals( "udp" ) && prot2.startsWith( "http" )) || 290 ( prot2.equals( "udp" ) && prot1.startsWith( "http" ))){ 291 292 list.remove( i ); 293 294 new_sets.add( new TOTorrentAnnounceURLSet[]{ set1, set2 }); 295 296 done = true; 297 } 298 } 299 } 300 } 301 } 302 303 if ( !done ){ 304 305 new_sets.add( new TOTorrentAnnounceURLSet[]{ set1 }); 306 } 307 } 308 } 309 310 // work out the difference 311 312 Iterator<TOTorrentAnnounceURLSet[]> ns_it = new_sets.iterator(); 313 314 // need to copy list as we modify it and returned list ain't thread safe 315 316 List<TRTrackerAnnouncerHelper> existing_announcers = new ArrayList<TRTrackerAnnouncerHelper>( announcers.getList()); 317 318 List<TRTrackerAnnouncerHelper> new_announcers = new ArrayList<TRTrackerAnnouncerHelper>(); 319 320 // first look for unchanged sets 321 322 if ( !force_recreate ){ 323 324 while( ns_it.hasNext()){ 325 326 TOTorrentAnnounceURLSet[] ns = ns_it.next(); 327 328 Iterator<TRTrackerAnnouncerHelper> a_it = existing_announcers.iterator(); 329 330 while( a_it.hasNext()){ 331 332 TRTrackerAnnouncerHelper a = a_it.next(); 333 334 TOTorrentAnnounceURLSet[] os = a.getAnnounceSets(); 335 336 if ( same( ns, os )){ 337 338 ns_it.remove(); 339 a_it.remove(); 340 341 new_announcers.add( a ); 342 343 break; 344 } 345 } 346 } 347 } 348 349 // first remove dht ones from the equation 350 351 TRTrackerAnnouncerHelper existing_dht_announcer = null; 352 TOTorrentAnnounceURLSet[] new_dht_set = null; 353 354 ns_it = new_sets.iterator(); 355 356 while( ns_it.hasNext()){ 357 358 TOTorrentAnnounceURLSet[] x = ns_it.next(); 359 360 if ( TorrentUtils.isDecentralised( x[0].getAnnounceURLs()[0])){ 361 362 new_dht_set = x; 363 364 ns_it.remove(); 365 366 break; 367 } 368 } 369 370 Iterator<TRTrackerAnnouncerHelper> an_it = existing_announcers.iterator(); 371 372 while( an_it.hasNext()){ 373 374 TRTrackerAnnouncerHelper a = an_it.next(); 375 376 TOTorrentAnnounceURLSet[] x = a.getAnnounceSets(); 377 378 if ( TorrentUtils.isDecentralised( x[0].getAnnounceURLs()[0])){ 379 380 existing_dht_announcer = a; 381 382 an_it.remove(); 383 384 break; 385 } 386 } 387 388 if ( existing_dht_announcer != null && new_dht_set != null ){ 389 390 new_announcers.add( existing_dht_announcer ); 391 392 }else if ( existing_dht_announcer != null ){ 393 394 activated.remove( existing_dht_announcer ); 395 396 existing_dht_announcer.destroy(); 397 398 }else if ( new_dht_set != null ){ 399 400 TRTrackerAnnouncerHelper a = create( torrent, networks, new_dht_set ); 401 402 new_announcers.add( a ); 403 } 404 405 // create any new ones required 406 407 ns_it = new_sets.iterator(); 408 409 while( ns_it.hasNext()){ 410 411 TOTorrentAnnounceURLSet[] s = ns_it.next(); 412 413 TRTrackerAnnouncerHelper a = create( torrent, networks, s ); 414 415 new_announcers.add( a ); 416 } 417 418 // finally fix up the announcer list to represent the new state 419 420 Iterator<TRTrackerAnnouncerHelper> a_it = announcers.iterator(); 421 422 while( a_it.hasNext()){ 423 424 TRTrackerAnnouncerHelper a = a_it.next(); 425 426 if ( !new_announcers.contains( a )){ 427 428 a_it.remove(); 429 430 try{ 431 if ( activated.contains( a ) && 432 torrent.getPrivate() && 433 a instanceof TRTrackerBTAnnouncerImpl ){ 434 435 URL url = a.getTrackerURL(); 436 437 if ( url != null ){ 438 439 forceStop((TRTrackerBTAnnouncerImpl)a, networks, url ); 440 } 441 } 442 }finally{ 443 444 if (Logger.isEnabled()) { 445 Logger.log(new LogEvent(getTorrent(), LOGID, "Deactivating " + getString( a.getAnnounceSets()))); 446 } 447 448 activated.remove( a ); 449 450 a.destroy(); 451 } 452 } 453 } 454 455 a_it = new_announcers.iterator(); 456 457 while( a_it.hasNext()){ 458 459 TRTrackerAnnouncerHelper a = a_it.next(); 460 461 if ( !announcers.contains( a )){ 462 463 announcers.add( a ); 464 } 465 } 466 467 if ( !is_manual && announcers.size() > 0 ){ 468 469 if ( activated.size() == 0 ){ 470 471 TRTrackerAnnouncerHelper a = announcers.get(0); 472 473 if (Logger.isEnabled()) { 474 Logger.log(new LogEvent(getTorrent(), LOGID, "Activating " + getString( a.getAnnounceSets()))); 475 } 476 477 activated.add( a ); 478 479 last_activation_time = SystemTime.getMonotonousTime(); 480 481 if ( provider != null ){ 482 483 to_activate = a; 484 } 485 } 486 487 setupActivationCheck( ACT_CHECK_INIT_DELAY ); 488 } 489 } 490 491 if ( to_activate != null ){ 492 493 if ( complete ){ 494 495 to_activate.complete( true ); 496 497 }else{ 498 499 to_activate.update( false ); 500 } 501 } 502 } 503 504 protected void setupActivationCheck( int delay )505 setupActivationCheck( 506 int delay ) 507 { 508 if ( announcers.size() > activated.size()){ 509 510 event = SimpleTimer.addEvent( 511 "TRMuxer:check", 512 SystemTime.getOffsetTime( delay ), 513 new TimerEventPerformer() 514 { 515 public void 516 perform( 517 TimerEvent event ) 518 { 519 checkActivation( false ); 520 } 521 }); 522 } 523 } 524 525 protected void checkActivation( boolean force )526 checkActivation( 527 boolean force ) 528 { 529 synchronized( this ){ 530 531 int next_check_delay; 532 533 if ( destroyed || 534 stopped || 535 announcers.size() <= activated.size()){ 536 537 return; 538 } 539 540 if ( provider == null ){ 541 542 next_check_delay = ACT_CHECK_INIT_DELAY; 543 544 }else{ 545 546 boolean activate = force; 547 548 boolean seeding = provider.getRemaining() == 0; 549 550 if ( seeding && activated.size() > 0 ){ 551 552 // when seeding we only activate on tracker fail or major lack of connections 553 // as normally we rely on downloaders rotating and finding us 554 555 int connected = provider.getConnectedConnectionCount(); 556 557 if ( connected < 1 ){ 558 559 activate = SystemTime.getMonotonousTime() - last_activation_time >= 60*1000; 560 561 next_check_delay = ACT_CHECK_SEEDING_SHORT_DELAY; 562 563 }else if ( connected < 3 ){ 564 565 next_check_delay = ACT_CHECK_SEEDING_LONG_DELAY; 566 567 }else{ 568 569 next_check_delay = 0; 570 } 571 }else{ 572 573 int allowed = provider.getMaxNewConnectionsAllowed(""); // -1 -> unlimited 574 int pending = provider.getPendingConnectionCount(); 575 int connected = provider.getConnectedConnectionCount(); 576 577 int online = 0; 578 579 for ( TRTrackerAnnouncerHelper a: activated ){ 580 581 TRTrackerAnnouncerResponse response = a.getLastResponse(); 582 583 if ( response != null && 584 response.getStatus() == TRTrackerAnnouncerResponse.ST_ONLINE ){ 585 586 online++; 587 } 588 } 589 590 /* 591 System.out.println( 592 "checkActivation: announcers=" + announcers.size() + 593 ", active=" + activated.size() + 594 ", online=" + online + 595 ", allowed=" + allowed + 596 ", pending=" + pending + 597 ", connected=" + connected + 598 ", seeding=" + seeding ); 599 */ 600 601 if ( online == 0 ){ 602 603 activate = true; 604 605 // no trackers online, start next and recheck soon 606 607 next_check_delay = ACT_CHECK_INIT_DELAY; 608 609 }else{ 610 611 int potential = connected + pending; 612 613 if ( potential < 10 ){ 614 615 // minimal connectivity 616 617 activate = true; 618 619 next_check_delay = ACT_CHECK_INIT_DELAY; 620 621 }else if ( allowed < 0 || ( allowed >= 5 && pending < 3*allowed/4 )){ 622 623 // not enough to fulfill our needs 624 625 activate = true; 626 627 next_check_delay = ACT_CHECK_INTERIM_DELAY; 628 629 }else{ 630 // things look good, recheck in a bit 631 632 next_check_delay = ACT_CHECK_IDLE_DELAY; 633 } 634 } 635 } 636 637 if ( activate ){ 638 639 for ( TRTrackerAnnouncerHelper a: announcers ){ 640 641 if ( !activated.contains( a )){ 642 643 if (Logger.isEnabled()) { 644 Logger.log(new LogEvent(getTorrent(), LOGID, "Activating " + getString( a.getAnnounceSets()))); 645 } 646 647 activated.add( a ); 648 649 last_activation_time = SystemTime.getMonotonousTime(); 650 651 if ( complete ){ 652 653 a.complete( true ); 654 655 }else{ 656 657 a.update( false ); 658 } 659 660 break; 661 } 662 } 663 } 664 } 665 666 if ( next_check_delay > 0 ){ 667 668 setupActivationCheck( next_check_delay ); 669 } 670 } 671 } 672 673 private String getString( TOTorrentAnnounceURLSet[] sets )674 getString( 675 TOTorrentAnnounceURLSet[] sets ) 676 { 677 StringBuffer str = new StringBuffer(); 678 679 str.append( "[" ); 680 681 int num1 = 0; 682 683 for ( TOTorrentAnnounceURLSet s: sets ){ 684 685 if ( num1++ > 0 ){ 686 str.append( ", "); 687 } 688 689 str.append( "[" ); 690 691 URL[] urls = s.getAnnounceURLs(); 692 693 int num2 = 0; 694 695 for ( URL u: urls ){ 696 697 if ( num2++ > 0 ){ 698 str.append( ", "); 699 } 700 701 str.append( u.toExternalForm()); 702 } 703 704 str.append( "]" ); 705 } 706 707 str.append( "]" ); 708 709 return( str.toString()); 710 } 711 712 private boolean same( TOTorrentAnnounceURLSet[] s1, TOTorrentAnnounceURLSet[] s2 )713 same( 714 TOTorrentAnnounceURLSet[] s1, 715 TOTorrentAnnounceURLSet[] s2 ) 716 { 717 boolean res = sameSupport( s1, s2 ); 718 719 // System.out.println( "same->" + res + ": " + getString(s1) + "/" + getString(s2)); 720 721 return( res ); 722 } 723 724 private boolean sameSupport( TOTorrentAnnounceURLSet[] s1, TOTorrentAnnounceURLSet[] s2 )725 sameSupport( 726 TOTorrentAnnounceURLSet[] s1, 727 TOTorrentAnnounceURLSet[] s2 ) 728 { 729 if ( s1.length != s2.length ){ 730 731 return( false ); 732 } 733 734 for (int i=0;i<s1.length;i++){ 735 736 URL[] u1 = s1[i].getAnnounceURLs(); 737 URL[] u2 = s2[i].getAnnounceURLs(); 738 739 if ( u1.length != u2.length ){ 740 741 return( false ); 742 } 743 744 if ( u1.length == 1 ){ 745 746 return( u1[0].toExternalForm().equals( u2[0].toExternalForm())); 747 } 748 749 Set<String> set1 = new HashSet<String>(); 750 751 for ( URL u: u1 ){ 752 753 set1.add( u.toExternalForm()); 754 } 755 756 Set<String> set2 = new HashSet<String>(); 757 758 for ( URL u: u2 ){ 759 760 set2.add( u.toExternalForm()); 761 } 762 763 if ( !set1.equals( set2 )){ 764 765 return( false ); 766 } 767 } 768 769 return( true ); 770 } 771 772 protected void forceStop( final TRTrackerBTAnnouncerImpl announcer, final String[] networks, final URL url )773 forceStop( 774 final TRTrackerBTAnnouncerImpl announcer, 775 final String[] networks, 776 final URL url ) 777 { 778 if (Logger.isEnabled()) { 779 Logger.log(new LogEvent(getTorrent(), LOGID, "Force stopping " + url + " as private torrent" )); 780 } 781 782 new AEThread2( "TRMux:fs", true ) 783 { 784 public void 785 run() 786 { 787 try{ 788 TRTrackerBTAnnouncerImpl an = 789 new TRTrackerBTAnnouncerImpl( getTorrent(), new TOTorrentAnnounceURLSet[0], networks, true, getHelper()); 790 791 an.cloneFrom( announcer ); 792 793 an.setTrackerURL( url ); 794 795 an.stop( false ); 796 797 an.destroy(); 798 799 }catch( Throwable e ){ 800 801 } 802 } 803 }.start(); 804 } 805 806 private TRTrackerAnnouncerHelper create( TOTorrent torrent, String[] networks, TOTorrentAnnounceURLSet[] sets )807 create( 808 TOTorrent torrent, 809 String[] networks, 810 TOTorrentAnnounceURLSet[] sets ) 811 812 throws TRTrackerAnnouncerException 813 { 814 TRTrackerAnnouncerHelper announcer; 815 816 boolean decentralised; 817 818 if ( sets.length == 0 ){ 819 820 decentralised = TorrentUtils.isDecentralised( torrent.getAnnounceURL()); 821 822 }else{ 823 824 decentralised = TorrentUtils.isDecentralised( sets[0].getAnnounceURLs()[0]); 825 } 826 827 if ( decentralised ){ 828 829 announcer = new TRTrackerDHTAnnouncerImpl( torrent, networks, is_manual, getHelper()); 830 831 }else{ 832 833 announcer = new TRTrackerBTAnnouncerImpl( torrent, sets, networks, is_manual, getHelper()); 834 } 835 836 for ( TOTorrentAnnounceURLSet set: sets ){ 837 838 URL[] urls = set.getAnnounceURLs(); 839 840 for ( URL u: urls ){ 841 842 String key = u.toExternalForm(); 843 844 StatusSummary summary = recent_responses.get( key ); 845 846 if ( summary == null ){ 847 848 summary = new StatusSummary( announcer, u ); 849 850 recent_responses.put( key, summary ); 851 852 }else{ 853 854 summary.setHelper( announcer ); 855 } 856 } 857 } 858 859 if ( provider != null ){ 860 861 announcer.setAnnounceDataProvider( provider ); 862 } 863 864 if ( ip_override != null ){ 865 866 announcer.setIPOverride( ip_override ); 867 } 868 869 return( announcer ); 870 } 871 872 873 public TRTrackerAnnouncerResponse getLastResponse()874 getLastResponse() 875 { 876 TRTrackerAnnouncerResponse result = null; 877 878 TRTrackerAnnouncerHelper best = getBestActive(); 879 880 if ( best != null ){ 881 882 result = best.getLastResponse(); 883 } 884 885 if ( result == null ){ 886 887 result = last_response_informed; 888 } 889 890 return( result ); 891 } 892 893 894 @Override 895 protected void informResponse( TRTrackerAnnouncerHelper helper, TRTrackerAnnouncerResponse response )896 informResponse( 897 TRTrackerAnnouncerHelper helper, 898 TRTrackerAnnouncerResponse response ) 899 { 900 URL url = response.getURL(); 901 902 // can be null for external plugins (e.g. mldht...) 903 904 if ( url != null ){ 905 906 synchronized( this ){ 907 908 String key = url.toExternalForm(); 909 910 StatusSummary summary = recent_responses.get( key ); 911 912 if ( summary != null ){ 913 914 summary.updateFrom( response ); 915 } 916 } 917 } 918 919 last_response_informed = response; 920 921 // force recalc of best active next time 922 923 last_best_active_set_time = 0; 924 925 super.informResponse( helper, response ); 926 927 if ( response.getStatus() != TRTrackerAnnouncerResponse.ST_ONLINE ){ 928 929 URL u = response.getURL(); 930 931 if ( u != null ){ 932 933 String s = u.toExternalForm(); 934 935 synchronized( failed_urls ){ 936 937 if ( failed_urls.contains( s )){ 938 939 return; 940 } 941 942 failed_urls.add( s ); 943 } 944 } 945 946 checkActivation( true ); 947 } 948 } 949 950 public boolean isManual()951 isManual() 952 { 953 return( is_manual ); 954 } 955 956 public void setAnnounceDataProvider( TRTrackerAnnouncerDataProvider _provider )957 setAnnounceDataProvider( 958 TRTrackerAnnouncerDataProvider _provider ) 959 { 960 List<TRTrackerAnnouncerHelper> to_set; 961 962 synchronized( this ){ 963 964 provider = _provider; 965 966 to_set = announcers.getList(); 967 } 968 969 for ( TRTrackerAnnouncer announcer: to_set ){ 970 971 announcer.setAnnounceDataProvider( provider ); 972 } 973 } 974 975 protected TRTrackerAnnouncerHelper getBestActive()976 getBestActive() 977 { 978 long now = SystemTime.getMonotonousTime(); 979 980 if ( now - last_best_active_set_time < 1000 ){ 981 982 return( last_best_active ); 983 } 984 985 last_best_active = getBestActiveSupport(); 986 987 last_best_active_set_time = now; 988 989 return( last_best_active ); 990 } 991 992 protected TRTrackerAnnouncerHelper getBestActiveSupport()993 getBestActiveSupport() 994 { 995 List<TRTrackerAnnouncerHelper> x = announcers.getList(); 996 997 TRTrackerAnnouncerHelper error_resp = null; 998 999 for ( TRTrackerAnnouncerHelper announcer: x ){ 1000 1001 TRTrackerAnnouncerResponse response = announcer.getLastResponse(); 1002 1003 if ( response != null ){ 1004 1005 int resp_status = response.getStatus(); 1006 1007 if ( resp_status == TRTrackerAnnouncerResponse.ST_ONLINE ){ 1008 1009 return( announcer ); 1010 1011 }else if ( error_resp == null && resp_status == TRTrackerAnnouncerResponse.ST_REPORTED_ERROR ){ 1012 1013 error_resp = announcer; 1014 } 1015 } 1016 } 1017 1018 if ( error_resp != null ){ 1019 1020 return( error_resp ); 1021 } 1022 1023 if ( x.size() > 0 ){ 1024 1025 return( x.get(0)); 1026 } 1027 1028 return( null ); 1029 } 1030 1031 public URL getTrackerURL()1032 getTrackerURL() 1033 { 1034 TRTrackerAnnouncerHelper active = getBestActive(); 1035 1036 if ( active != null ){ 1037 1038 return( active.getTrackerURL()); 1039 } 1040 1041 return( null ); 1042 } 1043 1044 public void setTrackerURL( URL url )1045 setTrackerURL( 1046 URL url ) 1047 { 1048 List<List<String>> groups = new ArrayList<List<String>>(); 1049 1050 List<String> group = new ArrayList<String>(); 1051 1052 group.add( url.toExternalForm()); 1053 1054 groups.add( group ); 1055 1056 TorrentUtils.listToAnnounceGroups( groups, getTorrent()); 1057 1058 resetTrackerUrl( false ); 1059 } 1060 1061 public void resetTrackerUrl( boolean shuffle )1062 resetTrackerUrl( 1063 boolean shuffle ) 1064 { 1065 try{ 1066 split( false ); 1067 1068 }catch( Throwable e ){ 1069 1070 Debug.out( e ); 1071 } 1072 1073 for ( TRTrackerAnnouncer announcer: announcers ){ 1074 1075 announcer.resetTrackerUrl( shuffle ); 1076 } 1077 } 1078 1079 public void setIPOverride( String override )1080 setIPOverride( 1081 String override ) 1082 { 1083 List<TRTrackerAnnouncerHelper> to_set; 1084 1085 synchronized( this ){ 1086 1087 to_set = announcers.getList(); 1088 1089 ip_override = override; 1090 } 1091 1092 for ( TRTrackerAnnouncer announcer: to_set ){ 1093 1094 announcer.setIPOverride( override ); 1095 } 1096 } 1097 1098 public void clearIPOverride()1099 clearIPOverride() 1100 { 1101 List<TRTrackerAnnouncerHelper> to_clear; 1102 1103 synchronized( this ){ 1104 1105 to_clear = announcers.getList(); 1106 1107 ip_override = null; 1108 } 1109 1110 for ( TRTrackerAnnouncer announcer: to_clear ){ 1111 1112 announcer.clearIPOverride(); 1113 } 1114 } 1115 1116 public void setRefreshDelayOverrides( int percentage )1117 setRefreshDelayOverrides( 1118 int percentage ) 1119 { 1120 for ( TRTrackerAnnouncer announcer: announcers ){ 1121 1122 announcer.setRefreshDelayOverrides( percentage ); 1123 } 1124 } 1125 1126 public int getTimeUntilNextUpdate()1127 getTimeUntilNextUpdate() 1128 { 1129 TRTrackerAnnouncerHelper active = getBestActive(); 1130 1131 if ( active != null ){ 1132 1133 return( active.getTimeUntilNextUpdate()); 1134 } 1135 1136 return( Integer.MAX_VALUE ); 1137 } 1138 1139 public int getLastUpdateTime()1140 getLastUpdateTime() 1141 { 1142 TRTrackerAnnouncerHelper active = getBestActive(); 1143 1144 if ( active != null ){ 1145 1146 return( active.getLastUpdateTime()); 1147 } 1148 1149 return( 0 ); 1150 } 1151 1152 public void update( boolean force )1153 update( 1154 boolean force ) 1155 { 1156 List<TRTrackerAnnouncerHelper> to_update; 1157 1158 synchronized( this ){ 1159 1160 to_update = is_manual?announcers.getList():new ArrayList<TRTrackerAnnouncerHelper>( activated ); 1161 } 1162 1163 for ( TRTrackerAnnouncer announcer: to_update ){ 1164 1165 announcer.update(force); 1166 } 1167 } 1168 1169 public void complete( boolean already_reported )1170 complete( 1171 boolean already_reported ) 1172 { 1173 List<TRTrackerAnnouncerHelper> to_complete; 1174 1175 synchronized( this ){ 1176 1177 complete = true; 1178 1179 to_complete = is_manual?announcers.getList():new ArrayList<TRTrackerAnnouncerHelper>( activated ); 1180 } 1181 1182 for ( TRTrackerAnnouncer announcer: to_complete ){ 1183 1184 announcer.complete( already_reported ); 1185 } 1186 } 1187 1188 public void stop( boolean for_queue )1189 stop( 1190 boolean for_queue ) 1191 { 1192 List<TRTrackerAnnouncerHelper> to_stop; 1193 1194 synchronized( this ){ 1195 1196 stopped = true; 1197 1198 to_stop = is_manual?announcers.getList():new ArrayList<TRTrackerAnnouncerHelper>( activated ); 1199 1200 activated.clear(); 1201 } 1202 1203 for ( TRTrackerAnnouncer announcer: to_stop ){ 1204 1205 announcer.stop( for_queue ); 1206 } 1207 } 1208 1209 public void destroy()1210 destroy() 1211 { 1212 TRTrackerAnnouncerFactoryImpl.destroy( this ); 1213 1214 List<TRTrackerAnnouncerHelper> to_destroy; 1215 1216 synchronized( this ){ 1217 1218 destroyed = true; 1219 1220 to_destroy = announcers.getList(); 1221 } 1222 1223 for ( TRTrackerAnnouncer announcer: to_destroy ){ 1224 1225 announcer.destroy(); 1226 } 1227 1228 TimerEvent ev = event; 1229 1230 if ( ev != null ){ 1231 1232 ev.cancel(); 1233 } 1234 } 1235 1236 public int getStatus()1237 getStatus() 1238 { 1239 TRTrackerAnnouncer max_announcer = getBestAnnouncer(); 1240 1241 return( max_announcer==null?-1:max_announcer.getStatus()); 1242 } 1243 1244 public String getStatusString()1245 getStatusString() 1246 { 1247 TRTrackerAnnouncer max_announcer = getBestAnnouncer(); 1248 1249 return( max_announcer==null?"":max_announcer.getStatusString()); 1250 } 1251 1252 public TRTrackerAnnouncer getBestAnnouncer()1253 getBestAnnouncer() 1254 { 1255 int max = -1; 1256 1257 TRTrackerAnnouncer max_announcer = null; 1258 1259 for ( TRTrackerAnnouncer announcer: announcers ){ 1260 1261 int status = announcer.getStatus(); 1262 1263 if ( status > max ){ 1264 1265 max_announcer = announcer; 1266 max = status; 1267 } 1268 } 1269 1270 return( max_announcer==null?this:max_announcer ); 1271 } 1272 1273 public void refreshListeners()1274 refreshListeners() 1275 { 1276 informURLRefresh(); 1277 } 1278 1279 public void setAnnounceResult( DownloadAnnounceResult result )1280 setAnnounceResult( 1281 DownloadAnnounceResult result ) 1282 { 1283 // this is only used for setting DHT results 1284 1285 for ( TRTrackerAnnouncer announcer: announcers ){ 1286 1287 if ( announcer instanceof TRTrackerDHTAnnouncerImpl ){ 1288 1289 announcer.setAnnounceResult( result ); 1290 1291 return; 1292 } 1293 } 1294 1295 // TODO: we should always create a DHT entry and have it denote DHT tracking for all circustances 1296 // have the DHT plugin set it to offline if disabled 1297 1298 List<TRTrackerAnnouncerHelper> x = announcers.getList(); 1299 1300 if ( x.size() > 0 ){ 1301 1302 x.get(0).setAnnounceResult( result ); 1303 } 1304 } 1305 1306 protected int getPeerCacheLimit()1307 getPeerCacheLimit() 1308 { 1309 synchronized( this ){ 1310 1311 if ( activated.size() < announcers.size()){ 1312 1313 return( 0 ); 1314 } 1315 } 1316 1317 if ( SystemTime.getMonotonousTime() - create_time < 15*1000 ){ 1318 1319 return( 0 ); 1320 } 1321 1322 TRTrackerAnnouncer active = getBestActive(); 1323 1324 if ( active != null && provider != null && active.getStatus() == TRTrackerAnnouncerResponse.ST_ONLINE ){ 1325 1326 if ( provider.getMaxNewConnectionsAllowed( "" ) != 0 && 1327 provider.getPendingConnectionCount() == 0 ){ 1328 1329 return( 5 ); 1330 1331 }else{ 1332 1333 return( 0 ); 1334 } 1335 } 1336 1337 return( 10 ); 1338 } 1339 1340 public TrackerPeerSource getTrackerPeerSource( final TOTorrentAnnounceURLSet set )1341 getTrackerPeerSource( 1342 final TOTorrentAnnounceURLSet set ) 1343 { 1344 URL[] urls = set.getAnnounceURLs(); 1345 1346 final String[] url_strs = new String[ urls.length ]; 1347 1348 for ( int i=0;i<urls.length;i++ ){ 1349 1350 url_strs[i] = urls[i].toExternalForm(); 1351 } 1352 1353 return( 1354 new TrackerPeerSource() 1355 { 1356 private StatusSummary _summary; 1357 private boolean enabled; 1358 private long fixup_time; 1359 1360 private StatusSummary 1361 fixup() 1362 { 1363 long now = SystemTime.getMonotonousTime(); 1364 1365 if ( now - fixup_time > 1000 ){ 1366 1367 long most_recent = 0; 1368 StatusSummary summary = null; 1369 1370 synchronized( TRTrackerAnnouncerMuxer.this ){ 1371 1372 for ( String str: url_strs ){ 1373 1374 StatusSummary s = recent_responses.get( str ); 1375 1376 if ( s != null ){ 1377 1378 if ( summary == null || s.getTime() > most_recent ){ 1379 1380 summary = s; 1381 most_recent = s.getTime(); 1382 } 1383 } 1384 } 1385 } 1386 1387 if ( provider != null ){ 1388 1389 enabled = provider.isPeerSourceEnabled( PEPeerSource.PS_BT_TRACKER ); 1390 } 1391 1392 if ( summary != null ){ 1393 1394 _summary = summary; 1395 } 1396 1397 fixup_time = now; 1398 } 1399 1400 return( _summary ); 1401 } 1402 1403 public int 1404 getType() 1405 { 1406 return( TrackerPeerSource.TP_TRACKER ); 1407 } 1408 1409 public String 1410 getName() 1411 { 1412 StatusSummary summary = fixup(); 1413 1414 if ( summary != null ){ 1415 1416 String str =summary.getURL().toExternalForm(); 1417 1418 int pos = str.indexOf( '?' ); 1419 1420 if ( pos != -1 ){ 1421 1422 str = str.substring( 0, pos ); 1423 } 1424 1425 return( str ); 1426 } 1427 1428 return( url_strs[0] ); 1429 } 1430 1431 public int 1432 getStatus() 1433 { 1434 StatusSummary summary = fixup(); 1435 1436 if ( !enabled ){ 1437 1438 return( ST_DISABLED ); 1439 } 1440 1441 if ( summary != null ){ 1442 1443 return( summary.getStatus()); 1444 } 1445 1446 return( ST_QUEUED ); 1447 } 1448 1449 public String 1450 getStatusString() 1451 { 1452 StatusSummary summary = fixup(); 1453 1454 if ( summary != null && enabled ){ 1455 1456 return( summary.getStatusString()); 1457 } 1458 1459 return( null ); 1460 } 1461 1462 public int 1463 getSeedCount() 1464 { 1465 StatusSummary summary = fixup(); 1466 1467 if ( summary != null ){ 1468 1469 return( summary.getSeedCount()); 1470 } 1471 1472 return( -1 ); 1473 } 1474 1475 public int 1476 getLeecherCount() 1477 { 1478 StatusSummary summary = fixup(); 1479 1480 if ( summary != null ){ 1481 1482 return( summary.getLeecherCount()); 1483 } 1484 1485 return( -1 ); 1486 } 1487 1488 public int 1489 getCompletedCount() 1490 { 1491 StatusSummary summary = fixup(); 1492 1493 if ( summary != null ){ 1494 1495 return( summary.getCompletedCount()); 1496 } 1497 1498 return( -1 ); 1499 } 1500 1501 public int 1502 getPeers() 1503 { 1504 StatusSummary summary = fixup(); 1505 1506 if ( summary != null ){ 1507 1508 return( summary.getPeers()); 1509 } 1510 1511 return( -1 ); 1512 } 1513 1514 public int 1515 getLastUpdate() 1516 { 1517 StatusSummary summary = fixup(); 1518 1519 if ( summary != null ){ 1520 1521 long time = summary.getTime(); 1522 1523 if ( time == 0 ){ 1524 1525 return( 0 ); 1526 } 1527 1528 long elapsed = SystemTime.getMonotonousTime() - time; 1529 1530 return((int)( (SystemTime.getCurrentTime() - elapsed ) / 1000 )); 1531 } 1532 1533 return( 0 ); 1534 } 1535 1536 public int 1537 getSecondsToUpdate() 1538 { 1539 StatusSummary summary = fixup(); 1540 1541 if ( summary != null ){ 1542 1543 return( summary.getSecondsToUpdate()); 1544 } 1545 1546 return( -1 ); 1547 } 1548 1549 public int 1550 getInterval() 1551 { 1552 StatusSummary summary = fixup(); 1553 1554 if ( summary != null ){ 1555 1556 return( summary.getInterval()); 1557 } 1558 1559 return( -1 ); 1560 } 1561 1562 public int 1563 getMinInterval() 1564 { 1565 StatusSummary summary = fixup(); 1566 1567 if ( summary != null && enabled ){ 1568 1569 return( summary.getMinInterval()); 1570 } 1571 1572 return( -1 ); 1573 } 1574 1575 public boolean 1576 isUpdating() 1577 { 1578 StatusSummary summary = fixup(); 1579 1580 if ( summary != null && enabled ){ 1581 1582 return( summary.isUpdating()); 1583 } 1584 1585 return( false ); 1586 } 1587 1588 public boolean 1589 canManuallyUpdate() 1590 { 1591 StatusSummary summary = fixup(); 1592 1593 if ( summary == null ){ 1594 1595 return( false ); 1596 } 1597 1598 return( summary.canManuallyUpdate()); 1599 } 1600 1601 public void 1602 manualUpdate() 1603 { 1604 StatusSummary summary = fixup(); 1605 1606 if ( summary != null ){ 1607 1608 summary.manualUpdate(); 1609 } 1610 } 1611 1612 public boolean 1613 canDelete() 1614 { 1615 return( false ); 1616 } 1617 1618 public void 1619 delete() 1620 { 1621 Debug.out( "derp" ); 1622 } 1623 }); 1624 } 1625 1626 public void generateEvidence( IndentWriter writer )1627 generateEvidence( 1628 IndentWriter writer ) 1629 { 1630 for ( TRTrackerAnnouncer announcer: announcers ){ 1631 1632 announcer.generateEvidence(writer); 1633 } 1634 } 1635 1636 private static class 1637 StatusSummary 1638 { 1639 private TRTrackerAnnouncerHelper helper; 1640 1641 private long time; 1642 private URL url; 1643 private int status; 1644 private String status_str; 1645 private int seeds = -1; 1646 private int leechers = -1; 1647 private int peers = -1; 1648 private int completed = -1; 1649 1650 private int interval; 1651 private int min_interval; 1652 1653 protected StatusSummary( TRTrackerAnnouncerHelper _helper, URL _url )1654 StatusSummary( 1655 TRTrackerAnnouncerHelper _helper, 1656 URL _url ) 1657 { 1658 helper = _helper; 1659 url = _url; 1660 1661 status = TrackerPeerSource.ST_QUEUED; 1662 } 1663 1664 protected void setHelper( TRTrackerAnnouncerHelper _helper )1665 setHelper( 1666 TRTrackerAnnouncerHelper _helper ) 1667 { 1668 helper = _helper; 1669 } 1670 1671 protected void updateFrom( TRTrackerAnnouncerResponse response )1672 updateFrom( 1673 TRTrackerAnnouncerResponse response ) 1674 { 1675 time = SystemTime.getMonotonousTime(); 1676 1677 int state = response.getStatus(); 1678 1679 if ( state == TRTrackerAnnouncerResponse.ST_ONLINE ){ 1680 1681 status = TrackerPeerSource.ST_ONLINE; 1682 1683 seeds = response.getScrapeCompleteCount(); 1684 leechers = response.getScrapeIncompleteCount(); 1685 completed = response.getScrapeDownloadedCount(); 1686 peers = response.getPeers().length; 1687 1688 }else{ 1689 1690 status = TrackerPeerSource.ST_ERROR; 1691 } 1692 1693 status_str = response.getStatusString(); 1694 1695 interval = (int)helper.getInterval(); 1696 min_interval = (int)helper.getMinInterval(); 1697 } 1698 1699 public long getTime()1700 getTime() 1701 { 1702 return( time ); 1703 } 1704 1705 public URL getURL()1706 getURL() 1707 { 1708 return( url ); 1709 } 1710 1711 public int getStatus()1712 getStatus() 1713 { 1714 return( status ); 1715 } 1716 1717 public String getStatusString()1718 getStatusString() 1719 { 1720 return( status_str ); 1721 } 1722 1723 public int getSeedCount()1724 getSeedCount() 1725 { 1726 return( seeds ); 1727 } 1728 1729 public int getLeecherCount()1730 getLeecherCount() 1731 { 1732 return( leechers ); 1733 } 1734 1735 public int getCompletedCount()1736 getCompletedCount() 1737 { 1738 return( completed ); 1739 } 1740 1741 public int getPeers()1742 getPeers() 1743 { 1744 return( peers ); 1745 } 1746 1747 public boolean isUpdating()1748 isUpdating() 1749 { 1750 return( helper.isUpdating()); 1751 } 1752 1753 public int getInterval()1754 getInterval() 1755 { 1756 return( interval ); 1757 } 1758 1759 public int getMinInterval()1760 getMinInterval() 1761 { 1762 return( min_interval ); 1763 } 1764 1765 public int getSecondsToUpdate()1766 getSecondsToUpdate() 1767 { 1768 return( helper.getTimeUntilNextUpdate()); 1769 } 1770 1771 public boolean canManuallyUpdate()1772 canManuallyUpdate() 1773 { 1774 return( ((SystemTime.getCurrentTime() / 1000 - helper.getLastUpdateTime() >= TRTrackerAnnouncer.REFRESH_MINIMUM_SECS))); 1775 } 1776 1777 public void manualUpdate()1778 manualUpdate() 1779 { 1780 helper.update( true ); 1781 } 1782 } 1783 } 1784