/*
* Created on 22 juil. 2003
* Copyright (C) 2003, 2004, 2005, 2006 Aelitis, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* AELITIS, SAS au capital de 46,603.30 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*
*/
package org.gudy.azureus2.core3.tracker.client.impl.bt;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.torrent.TOTorrentAnnounceURLSet;
import org.gudy.azureus2.core3.torrent.TOTorrentException;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncer;
import org.gudy.azureus2.core3.tracker.client.TRTrackerScraperResponse;
import org.gudy.azureus2.core3.tracker.client.impl.TRTrackerScraperResponseImpl;
import org.gudy.azureus2.core3.util.*;
/**
* @author Olivier
*
*/
public class TrackerChecker implements AEDiagnosticsEvidenceGenerator, SystemTime.ChangeListener, TimerEventPerformer {
private final static LogIDs LOGID = LogIDs.TRACKER;
/** List of Trackers.
* key = Tracker URL string
* value = TrackerStatus object
*/
private HashMap trackers;
private AEMonitor trackers_mon = new AEMonitor( "TrackerChecker:trackers" );
/** TRTrackerScraperImpl object associated with this object.
*/
private TRTrackerBTScraperImpl scraper;
private long nextScrapeCheckOn;
/** Initialize TrackerChecker.
*
* @note Since there is only one TRTrackerScraperImpl, there will only be one
* TrackerChecker instance.
*
*/
protected TrackerChecker(TRTrackerBTScraperImpl _scraper) {
scraper = _scraper;
trackers = new HashMap();
if ( !COConfigurationManager.getBooleanParameter("Tracker Client Scrape Total Disable")){
runScrapes();
}
AEDiagnostics.addEvidenceGenerator( this );
SystemTime.registerClockChangeListener( this );
}
/** Retrieves the last cached Scraper Response based on a TRTrackerClient's
* current URL (announce-list entry or announce) and its torrent's hash.
*
* @return The cached scrape response. Can be null.
*/
protected
TRTrackerScraperResponseImpl
getHashData(
TRTrackerAnnouncer tracker_client)
{
try {
return getHashData(tracker_client.getTrackerURL(),
tracker_client.getTorrent().getHashWrapper());
} catch (TOTorrentException e) {
Debug.printStackTrace( e );
return null;
}
}
/** Retrieves the last cached Scraper Response based on a TOTorrent's
* Announce URL (not announce-list) and hash.
*
* @return The cached scrape response. Can be null.
*/
protected TRTrackerScraperResponseImpl
getHashData(
TOTorrent torrent,
URL target_url )
{
try {
return getHashData(target_url==null?torrent.getAnnounceURL():target_url,
torrent.getHashWrapper());
} catch(TOTorrentException e) {
Debug.printStackTrace( e );
return null;
}
}
/** Retrieves the last cached Scraper Response for the supplied tracker URL
* and hash. If no cache has exists for the hash, one is created.
*
* @return The cached scrape response. Can be null.
*/
protected TRTrackerScraperResponseImpl
getHashData(
URL trackerUrl,
final HashWrapper hash)
{
// can be null when first called and url not yet set up...
if ( trackerUrl == null ){
return( null );
}
TRTrackerScraperResponseImpl data = null;
// DON'T USE URL as a key in the trackers map, use the string version. If you
// use a URL then the "containsKey" method does a URL.equals test. This does not
// simply check on str equivalence, it tries to resolve the host name. this can
// result in significant hangs (several seconds....)
String url_str = trackerUrl.toString();
TrackerStatus ts = null;
try{
trackers_mon.enter();
ts = (TrackerStatus) trackers.get(url_str);
if ( ts != null ){
data = ts.getHashData( hash );
}else{
//System.out.println( "adding hash for " + trackerUrl + " : " + ByteFormatter.nicePrint(hashBytes, true));
ts = new TrackerStatus(this, scraper.getScraper(),trackerUrl);
trackers.put(url_str, ts);
if( !ts.isTrackerScrapeUrlValid() ) {
if (Logger.isEnabled()){
Logger.log(new LogEvent(TorrentUtils.getDownloadManager(hash), LOGID,
LogEvent.LT_ERROR, "Can't scrape using url '" + trackerUrl
+ "' as it doesn't end in " + "'/announce', skipping."));
}
}
}
}finally{
trackers_mon.exit();
}
// do outside monitor to avoid deadlock situation as ts.addHash invokes
// listeners....
if ( data == null ){
data = ts.addHash(hash);
}
return data;
}
protected TRTrackerScraperResponseImpl
peekHashData(
TOTorrent torrent,
URL target_url )
{
try{
URL trackerUrl = target_url==null?torrent.getAnnounceURL():target_url;
if ( trackerUrl == null ){
return( null );
}
String url_str = trackerUrl.toString();
try{
trackers_mon.enter();
TrackerStatus ts = (TrackerStatus) trackers.get(url_str);
if ( ts != null ){
return( ts.getHashData( torrent.getHashWrapper()));
}
}finally{
trackers_mon.exit();
}
} catch(TOTorrentException e) {
Debug.printStackTrace( e );
}
return null;
}
/** Removes the scrape task and data associated with the TOTorrent's
* Announce URL, announce-list data and hash.
*/
protected void removeHash(TOTorrent torrent) {
try{
removeHash(torrent.getAnnounceURL().toString(), torrent.getHashWrapper());
TOTorrentAnnounceURLSet[] sets = torrent.getAnnounceURLGroup().getAnnounceURLSets();
for (int i=0;i<sets.length;i++){
URL[] urls = sets[i].getAnnounceURLs();
for (int j=0;j<urls.length;j++){
removeHash(urls[j].toString(), torrent.getHashWrapper());
}
}
} catch (TOTorrentException e) {
Debug.printStackTrace( e );
}
}
/** Removes the scrape task and data associated with the supplied tracker
* URL and torrent hash.
*/
protected void removeHash(String trackerUrl, HashWrapper hash) {
TrackerStatus ts = (TrackerStatus) trackers.get(trackerUrl);
if (ts != null){
//System.out.println( "removing hash for " + trackerUrl );
ts.removeHash(hash);
}
}
/* Forced synchronous scrape of the supplied torrent.
*/
protected void
syncUpdate(
TOTorrent torrent,
URL target_url )
{
if (torrent == null){
return;
}
try {
HashWrapper hash = torrent.getHashWrapper();
TrackerStatus matched_ts = null;
try{
trackers_mon.enter();
Iterator iter = trackers.values().iterator();
while (iter.hasNext()){
TrackerStatus ts = (TrackerStatus) iter.next();
if ( target_url == null ||
target_url.toString().equals( ts.getTrackerURL().toString())){
Map hashmap = ts.getHashes();
try{
ts.getHashesMonitor().enter();
if ( hashmap.get( hash ) != null ){
matched_ts = ts;
break;
}
}finally{
ts.getHashesMonitor().exit();
}
}
}
}finally{
trackers_mon.exit();
}
if ( matched_ts != null ){
matched_ts.updateSingleHash( hash, true, false );
}
}
catch (Throwable e) {
Debug.out( "scrape syncUpdate() exception", e );
}
}
public void perform(TimerEvent event) {
runScrapes();
}
/** Loop indefinitely, waiting for the next scrape, and scraping.
*/
TRTrackerBTScraperResponseImpl oldResponse;
private void
runScrapes()
{
TRTrackerBTScraperResponseImpl nextResponseScraping = checkForNextScrape();
if (Logger.isEnabled() && nextResponseScraping != oldResponse && nextResponseScraping != null ) {
Logger.log(new LogEvent(
TorrentUtils.getDownloadManager(nextResponseScraping.getHash()),
LOGID,
LogEvent.LT_INFORMATION,
"Next scrape will be "
+ nextResponseScraping.getURL()
+ " in "
+ ((nextResponseScraping.getNextScrapeStartTime() - SystemTime.getCurrentTime())/1000)
+ " sec,type="
+ (nextResponseScraping.getTrackerStatus().getSupportsMultipeHashScrapes()
? "multi" : "single")
+ ",active="+nextResponseScraping.getTrackerStatus().getNumActiveScrapes()));
}
long delay;
if (nextResponseScraping == null) {
delay = 60000; // nothing going on, recheck in a min
} else {
long scrape_time = nextResponseScraping.getNextScrapeStartTime();
long time_to_scrape = scrape_time - SystemTime.getCurrentTime()
+ SystemTime.TIME_GRANULARITY_MILLIS;
if (time_to_scrape <= 0) {
if (nextResponseScraping.getTrackerStatus().getNumActiveScrapes() > 0) {
// check if done scraping every 2 seconds, if no other
// scrapes are scheduled. If other scrapes are sceduled,
// we would have got them from checkForNextScrape()
delay = 2000;
} else {
try {
nextResponseScraping.getTrackerStatus().updateSingleHash(
nextResponseScraping.getHash(), false);
delay = 0; // pick up next scrape fairly quickly
} catch (Throwable e) {
Debug.printStackTrace(e);
delay = 30000;
}
}
} else {
delay = time_to_scrape;
if (delay > 30000) {
delay = 30000; // don't sleep too long in case new hashes are added etc.
}
}
}
nextScrapeCheckOn = SystemTime.getCurrentTime() + delay;
oldResponse = nextResponseScraping;
// use tracker timer/thread pool
TRTrackerBTAnnouncerImpl.tracker_timer.addEvent(nextScrapeCheckOn, this);
}
/** Finds the torrent that will be needing a scrape next.
*
*/
private TRTrackerBTScraperResponseImpl
checkForNextScrape()
{
// search for the next scrape
long earliestBlocked = Long.MAX_VALUE;
TRTrackerBTScraperResponseImpl earliestBlockedResponse = null;
long earliestNonBlocked = Long.MAX_VALUE;
TRTrackerBTScraperResponseImpl earliestNonBlockedResponse = null;
try {
trackers_mon.enter();
Iterator iter = trackers.values().iterator();
while (iter.hasNext()) {
TrackerStatus ts = (TrackerStatus) iter.next();
if (!ts.isTrackerScrapeUrlValid()) {
continue;
}
boolean hasActiveScrapes = ts.getNumActiveScrapes() > 0;
Map hashmap = ts.getHashes();
try {
ts.getHashesMonitor().enter();
Iterator iterHashes = hashmap.values().iterator();
while (iterHashes.hasNext()) {
TRTrackerBTScraperResponseImpl response = (TRTrackerBTScraperResponseImpl) iterHashes.next();
if (response.getStatus() != TRTrackerScraperResponse.ST_SCRAPING) {
long nextScrapeStartTime = response.getNextScrapeStartTime();
if (hasActiveScrapes) {
if (nextScrapeStartTime < earliestBlocked) {
earliestBlocked = nextScrapeStartTime;
earliestBlockedResponse = response;
}
} else {
if (nextScrapeStartTime < earliestNonBlocked) {
earliestNonBlocked = nextScrapeStartTime;
earliestNonBlockedResponse = response;
}
}
}
}
} finally {
ts.getHashesMonitor().exit();
}
}
} finally {
trackers_mon.exit();
}
boolean hasEarlierBlockedScrape = earliestBlocked != Long.MAX_VALUE
&& earliestBlocked < earliestNonBlocked;
// If the earlist non-blocked scrape is still 2 seconds away,
// return the blocked scrape with in hopes that it gets unblocked soon
if (hasEarlierBlockedScrape
&& earliestNonBlocked - SystemTime.getCurrentTime() > 2000) {
return earliestBlockedResponse;
} else {
return earliestNonBlockedResponse;
}
}
public void
clockChangeDetected(
long current_time,
long offset )
{
if ( Math.abs( offset ) < 60*1000 ){
return;
}
try{
trackers_mon.enter();
Iterator iter = trackers.values().iterator();
while (iter.hasNext()) {
TrackerStatus ts = (TrackerStatus) iter.next();
Map hashmap = ts.getHashes();
try{
ts.getHashesMonitor().enter();
Iterator iterHashes = hashmap.values().iterator();
while( iterHashes.hasNext() ) {
TRTrackerBTScraperResponseImpl response = (TRTrackerBTScraperResponseImpl)iterHashes.next();
long time = response.getNextScrapeStartTime();
if ( time > 0 ){
response.setNextScrapeStartTime( time + offset );
}
}
}finally{
ts.getHashesMonitor().exit();
}
}
}finally{
trackers_mon.exit();
}
}
public void
clockChangeCompleted(
long current_time,
long offset )
{
}
public void
generate(
IndentWriter writer )
{
writer.println( "BTScraper - now = " + SystemTime.getCurrentTime());
try{
writer.indent();
try{
trackers_mon.enter();
Iterator iter = trackers.entrySet().iterator();
while (iter.hasNext()){
Map.Entry entry = (Map.Entry)iter.next();
TrackerStatus ts = (TrackerStatus)entry.getValue();
writer.println( "Tracker: " + ts.getString());
try{
writer.indent();
ts.getHashesMonitor().enter();
Map hashmap = ts.getHashes();
Iterator iter_hashes = hashmap.entrySet().iterator();
while (iter_hashes.hasNext()){
Map.Entry hash_entry = (Map.Entry)iter_hashes.next();
TRTrackerBTScraperResponseImpl response = (TRTrackerBTScraperResponseImpl)hash_entry.getValue();
writer.println( response.getString());
}
}finally{
ts.getHashesMonitor().exit();
writer.exdent();
}
}
}finally{
trackers_mon.exit();
}
}finally{
writer.exdent();
}
}
public long getNextScrapeCheckOn() {
return nextScrapeCheckOn;
}
}