instrumentation: add next-share/
[cs-p2p-next.git] / instrumentation / next-share / BaseLib / TrackerChecking / TrackerChecking.py
1 # written by Yuan Yuan
2 # see LICENSE.txt for license information
3
4 # single torrent checking without Thread
5 import sys
6 from BaseLib.Core.BitTornado.bencode import bdecode
7 from random import shuffle
8 import urllib
9 import socket
10 import BaseLib.Core.Utilities.timeouturlopen as timeouturlopen
11 from time import time
12 from traceback import print_exc
13
14 HTTP_TIMEOUT = 30 # seconds
15
16 DEBUG = False
17
18 def trackerChecking(torrent):    
19     single_no_thread(torrent)              
20         
21 def single_no_thread(torrent):
22     
23     (seeder, leecher) = (-2, -2)        # default dead
24     if ( torrent["info"].get("announce-list", "") == "" ):        # no announce-list
25         try:
26             announce = torrent["info"]["announce"]                    # get the single tracker
27             (s, l) = singleTrackerStatus(torrent, announce)
28             seeder = max(seeder, s)
29             leecher = max(leecher, l)
30         except:
31             pass
32     else:                                                # have announce-list
33         for announces in torrent["info"]["announce-list"]:
34             a_len = len(announces)
35             if (a_len == 0):                            # length = 0
36                 continue
37             if (a_len == 1):                            # length = 1
38                 announce = announces[0]
39                 (s, l) = singleTrackerStatus(torrent, announce)
40                 seeder = max(seeder, s)
41                 leecher = max(leecher, l)
42             else:                                        # length > 1
43                 aindex = torrent["info"]["announce-list"].index(announces)                                    
44                 shuffle(announces)
45                 # Arno: protect agaist DoS torrents with many trackers in announce list. 
46                 announces = announces[:16]
47                 for announce in announces:                # for eache announce
48                     (s, l) = singleTrackerStatus(torrent, announce)
49                     seeder = max(seeder, s)
50                     leecher = max(leecher, l)
51                     if seeder > 0:  # good
52                         break
53                 if (seeder > 0 or leecher > 0):        # put the announce\
54                     announces.remove(announce)            # in front of the tier
55                     announces.insert(0, announce)                    
56                     torrent["info"]["announce-list"][aindex] = announces
57 #                    print "one changed"
58             if (seeder > 0):
59                 break
60     if (seeder == -3 and leecher == -3):
61         pass        # if interval problem, just keep the last status
62     else:
63         torrent["seeder"] = seeder
64         torrent["leecher"] = leecher
65         if (torrent["seeder"] > 0 or torrent["leecher"] > 0):
66             torrent["status"] = "good"
67         elif (torrent["seeder"] == 0 and torrent["leecher"] == 0):
68             torrent["status"] = "unknown"
69 #            torrent["seeder"] = 0
70 #            torrent["leecher"] = 0
71         elif (torrent["seeder"] == -1 and torrent["leecher"] == -1):    # unknown
72             torrent["status"] = "unknown"
73 #            torrent["seeder"] = -1
74 #            torrent["leecher"] = -1
75         else:        # if seeder == -2 and leecher == -2, dead
76             torrent["status"] = "dead"
77             torrent["seeder"] = -2
78             torrent["leecher"] = -2
79     torrent["last_check_time"] = long(time())
80     return torrent
81
82
83 def singleTrackerStatus(torrent, announce):
84     # return (-1, -1) means the status of torrent is unknown
85     # return (-2. -2) means the status of torrent is dead
86     # return (-3, -3) means the interval problem 
87     info_hash = torrent["infohash"]
88     
89     if DEBUG:
90         print >>sys.stderr,"TrackerChecking: Checking",announce,"for",`info_hash`
91     
92     url = getUrl(announce, info_hash)            # whether scrape support
93     if (url == None):                            # tracker url error
94         return (-2, -2)                            # use announce instead
95     try:
96         #print 'Checking url: %s' % url
97         (seeder, leecher) = getStatus(url, info_hash)
98         
99         if DEBUG:
100             print >>sys.stderr,"TrackerChecking: Result",(seeder,leecher)
101     except:
102         (seeder, leecher) = (-2, -2)
103     return (seeder, leecher)
104
105 # generate the query URL
106 def getUrl(announce, info_hash):
107     if (announce == -1):                        # tracker url error
108         return None                                # return None
109     announce_index = announce.rfind("announce")
110     last_index = announce.rfind("/")    
111     
112     url = announce    
113     if (last_index +1 == announce_index):        # srape supprot
114         url = url.replace("announce","scrape")
115     url += "?info_hash=" + urllib.quote(info_hash)
116 #    print url
117     return url
118
119
120             
121 def getStatus(url, info_hash):
122     try:
123         resp = timeouturlopen.urlOpenTimeout(url,timeout=HTTP_TIMEOUT)
124         response = resp.read()
125         
126     except IOError:
127 #        print "IOError"
128         return (-1, -1)                    # unknown
129     except AttributeError:
130 #        print "AttributeError"
131         return (-2, -2)                    # dead
132     
133     try:
134         response_dict = bdecode(response)
135
136     except:
137 #        print "DeCode Error "  + response
138         return (-2, -2)                    # dead
139     
140     try:
141         status = response_dict["files"][info_hash]
142         seeder = status["complete"]
143         if seeder < 0:
144             seeder = 0
145         leecher = status["incomplete"]
146         if leecher < 0:
147             leecher = 0
148         
149     except KeyError:
150 #        print "KeyError "  + info_hash + str(response_dict)
151         try:
152             if response_dict.has_key("flags"): # may be interval problem        
153                 if response_dict["flags"].has_key("min_request_interval"):
154 #                    print "interval problem"
155                     return (-3 ,-3)
156         except:
157             pass
158 #        print "KeyError "  + info_hash + str(response_dict)
159         return (-2, -2)                    # dead
160     
161     return (seeder, leecher)