-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathannounce.py
More file actions
176 lines (156 loc) · 5.42 KB
/
Copy pathannounce.py
File metadata and controls
176 lines (156 loc) · 5.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#i!/usr/bin/python2.4
"""Announce module
"""
__author__ = 'allen@thebends.org (Allen Porter)'
import base64
import datetime
import os
import wsgiref.handlers
import cgi
import logging
from google.appengine.ext import db
from google.appengine.ext import webapp
from BTL import bencode
import model
import util
def compact_peer_info(ip, port):
try:
s = ( ''.join([chr(int(i)) for i in ip.split('.')])
+ chr((port & 0xFF00) >> 8) + chr(port & 0xFF) )
if len(s) != 6:
s = ''
except:
s = '' # not a valid IP, must be a domain name
return s
class MainPage(webapp.RequestHandler):
def __init__(self):
self._compact = False
self._peers = None
def get(self):
info_hash = util.GetParam('info_hash')
if len(info_hash) != 20:
self.error("Invalid info_hash argument (%d != 20)" % (len(info_hash)))
return
peer_id = self.request.get('peer_id')
if len(peer_id) != 20:
self.error("Invalid peer_id argument")
return
ip = os.environ['REMOTE_ADDR']
# TODO(aporter): Get the optional ip from the client (for proxies)
try:
port = int(self.request.get('port'))
except ValueError:
self.error("Invalid port argument")
return
try:
uploaded = int(self.request.get('uploaded'))
except ValueError:
self.error("Invalid uploaded argument")
return
try:
downloaded = int(self.request.get('downloaded'))
except ValueError:
self.error("Invalid downloaded argument")
return
try:
left = int(self.request.get('left'))
except ValueError:
self.error("Invalid left argument")
return
try:
compact = int(self.request.get('compact'))
except ValueError:
self.error("Invalid compact argument")
return
try:
numwant = int(self.request.get('numwant'))
except ValueError:
self.error("Invalid numwant argument")
return
if compact != 0 and compact != 1:
self.error("Invalid corrupt argument")
return
self._compact = (compact == 1)
no_peer_id = not compact
# TODO(aporter): Event handling
event = self.request.get('event', None)
# TODO(aporter): Used to prove identity to tracker if ip changes
key = self.request.get('key')
torrents = model.Torrent.gql("WHERE info_hash = :1",
base64.b64encode(info_hash))
if torrents.count() == 1:
torrent = torrents[0]
else:
torrent = model.Torrent(info_hash=base64.b64encode(info_hash))
torrent.put()
peers = model.TorrentPeerEntry.gql(
"WHERE torrent = :1 AND peer_id = :2 AND ip = :3 AND port = :4",
torrent, peer_id, ip, port)
if peers.count() == 1:
peer = peers[0]
# TODO(aporter): Should we reject peers with differing IP and port or
# should they be allowed to update? This depends on if this peer id can
# be guessed by others or not.
peer.ip = ip
peer.port = port
peer.last_datetime = datetime.datetime.now()
else:
peer = model.TorrentPeerEntry(torrent=torrent,
ip=ip,
port=port,
peer_id=peer_id,
last_datetime=datetime.datetime.now())
peer.downloaded = downloaded
peer.uploaded = uploaded
peer.put()
self.BuildPeersResult(torrent, peer_id)
self.CleanupOldPeers(torrent)
# TOOD(aporter): Respect maximum number of peers
num_complete = 1
num_incomplete = 0
self.response.headers['Content-Type'] = 'text/plain'
result = {
"interval" : 60,
"complete" : num_complete,
"incomplete" : num_incomplete,
"peers" : self._peers
}
self.response.out.write(bencode.bencode(result))
def error(self, msg):
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write(bencode.bencode({ "failure reason" : msg }))
def BuildPeersResult(self, torrent, current_peer_id):
cutoff_time = datetime.datetime.now() - datetime.timedelta(minutes = 3)
peers = model.TorrentPeerEntry.gql("WHERE torrent = :1 AND " +
"last_datetime >= :2",
torrent, cutoff_time)
if not self._compact:
self._peers = [ ]
for peer_entry in peers:
if peer_entry.peer_id != current_peer_id:
self._peers.append({
"peer id" : str(peer_entry.peer_id),
"ip" : str(peer_entry.ip),
"port" : peer_entry.port,
})
else:
self._peers = ""
for peer_entry in peers:
if peer_entry.peer_id != current_peer_id:
self._peers += compact_peer_info(peer_entry.ip, peer_entry.port)
def CleanupOldPeers(self, torrent):
cutoff_time = datetime.datetime.now() - datetime.timedelta(minutes = 10)
query = db.GqlQuery("SELECT * FROM TorrentPeerEntry " +
"WHERE last_datetime < :1",
cutoff_time)
results = query.fetch(10) # Do some small cleanup
if results:
logging.info("Deleting %d torrent peer entries" % len(results))
for result in results:
result.delete()
application = webapp.WSGIApplication(
[ ( '/announce', MainPage ) ],
debug=True)
# Hack so that we don't try to parse the info_hash as a UTF-8 string
#os.environ['CONTENT_TYPE'] = "%s;charset=" % os.environ['CONTENT_TYPE']
wsgiref.handlers.CGIHandler().run(application)