| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- # Written by John Hoffman
- # see LICENSE.txt for license information
- from BitTornado.CurrentRateMeasure import Measure
- from random import randint
- from urlparse import urlparse
- from httplib import HTTPConnection
- from urllib import quote
- from threading import Thread
- from BitTornado.__init__ import product_name,version_short
- try:
- True
- except:
- True = 1
- False = 0
- EXPIRE_TIME = 60 * 60
- VERSION = product_name+'/'+version_short
- class haveComplete:
- def complete(self):
- return True
- def __getitem__(self, x):
- return True
- haveall = haveComplete()
- class SingleDownload:
- def __init__(self, downloader, url):
- self.downloader = downloader
- self.baseurl = url
- try:
- (scheme, self.netloc, path, pars, query, fragment) = urlparse(url)
- except:
- self.downloader.errorfunc('cannot parse http seed address: '+url)
- return
- if scheme != 'http':
- self.downloader.errorfunc('http seed url not http: '+url)
- return
- try:
- self.connection = HTTPConnection(self.netloc)
- except:
- self.downloader.errorfunc('cannot connect to http seed: '+url)
- return
- self.seedurl = path
- if pars:
- self.seedurl += ';'+pars
- self.seedurl += '?'
- if query:
- self.seedurl += query+'&'
- self.seedurl += 'info_hash='+quote(self.downloader.infohash)
- self.measure = Measure(downloader.max_rate_period)
- self.index = None
- self.url = ''
- self.requests = []
- self.request_size = 0
- self.endflag = False
- self.error = None
- self.retry_period = 30
- self._retry_period = None
- self.errorcount = 0
- self.goodseed = False
- self.active = False
- self.cancelled = False
- self.resched(randint(2,10))
- def resched(self, len = None):
- if len is None:
- len = self.retry_period
- if self.errorcount > 3:
- len = len * (self.errorcount - 2)
- self.downloader.rawserver.add_task(self.download, len)
- def _want(self, index):
- if self.endflag:
- return self.downloader.storage.do_I_have_requests(index)
- else:
- return self.downloader.storage.is_unstarted(index)
- def download(self):
- self.cancelled = False
- if self.downloader.picker.am_I_complete():
- self.downloader.downloads.remove(self)
- return
- self.index = self.downloader.picker.next(haveall, self._want)
- if ( self.index is None and not self.endflag
- and not self.downloader.peerdownloader.has_downloaders() ):
- self.endflag = True
- self.index = self.downloader.picker.next(haveall, self._want)
- if self.index is None:
- self.endflag = True
- self.resched()
- else:
- self.url = ( self.seedurl+'&piece='+str(self.index) )
- self._get_requests()
- if self.request_size < self.downloader.storage._piecelen(self.index):
- self.url += '&ranges='+self._request_ranges()
- rq = Thread(target = self._request)
- rq.setDaemon(False)
- rq.start()
- self.active = True
- def _request(self):
- import encodings.ascii
- import encodings.punycode
- import encodings.idna
-
- self.error = None
- self.received_data = None
- try:
- self.connection.request('GET',self.url, None,
- {'User-Agent': VERSION})
- r = self.connection.getresponse()
- self.connection_status = r.status
- self.received_data = r.read()
- except Exception, e:
- self.error = 'error accessing http seed: '+str(e)
- try:
- self.connection.close()
- except:
- pass
- try:
- self.connection = HTTPConnection(self.netloc)
- except:
- self.connection = None # will cause an exception and retry next cycle
- self.downloader.rawserver.add_task(self.request_finished)
- def request_finished(self):
- self.active = False
- if self.error is not None:
- if self.goodseed:
- self.downloader.errorfunc(self.error)
- self.errorcount += 1
- if self.received_data:
- self.errorcount = 0
- if not self._got_data():
- self.received_data = None
- if not self.received_data:
- self._release_requests()
- self.downloader.peerdownloader.piece_flunked(self.index)
- if self._retry_period:
- self.resched(self._retry_period)
- self._retry_period = None
- return
- self.resched()
- def _got_data(self):
- if self.connection_status == 503: # seed is busy
- try:
- self.retry_period = max(int(self.received_data),5)
- except:
- pass
- return False
- if self.connection_status != 200:
- self.errorcount += 1
- return False
- self._retry_period = 1
- if len(self.received_data) != self.request_size:
- if self.goodseed:
- self.downloader.errorfunc('corrupt data from http seed - redownloading')
- return False
- self.measure.update_rate(len(self.received_data))
- self.downloader.measurefunc(len(self.received_data))
- if self.cancelled:
- return False
- if not self._fulfill_requests():
- return False
- if not self.goodseed:
- self.goodseed = True
- self.downloader.seedsfound += 1
- if self.downloader.storage.do_I_have(self.index):
- self.downloader.picker.complete(self.index)
- self.downloader.peerdownloader.check_complete(self.index)
- self.downloader.gotpiecefunc(self.index)
- return True
-
- def _get_requests(self):
- self.requests = []
- self.request_size = 0L
- while self.downloader.storage.do_I_have_requests(self.index):
- r = self.downloader.storage.new_request(self.index)
- self.requests.append(r)
- self.request_size += r[1]
- self.requests.sort()
- def _fulfill_requests(self):
- start = 0L
- success = True
- while self.requests:
- begin, length = self.requests.pop(0)
- if not self.downloader.storage.piece_came_in(self.index, begin,
- self.received_data[start:start+length]):
- success = False
- break
- start += length
- return success
- def _release_requests(self):
- for begin, length in self.requests:
- self.downloader.storage.request_lost(self.index, begin, length)
- self.requests = []
- def _request_ranges(self):
- s = ''
- begin, length = self.requests[0]
- for begin1, length1 in self.requests[1:]:
- if begin + length == begin1:
- length += length1
- continue
- else:
- if s:
- s += ','
- s += str(begin)+'-'+str(begin+length-1)
- begin, length = begin1, length1
- if s:
- s += ','
- s += str(begin)+'-'+str(begin+length-1)
- return s
-
-
- class HTTPDownloader:
- def __init__(self, storage, picker, rawserver,
- finflag, errorfunc, peerdownloader,
- max_rate_period, infohash, measurefunc, gotpiecefunc):
- self.storage = storage
- self.picker = picker
- self.rawserver = rawserver
- self.finflag = finflag
- self.errorfunc = errorfunc
- self.peerdownloader = peerdownloader
- self.infohash = infohash
- self.max_rate_period = max_rate_period
- self.gotpiecefunc = gotpiecefunc
- self.measurefunc = measurefunc
- self.downloads = []
- self.seedsfound = 0
- def make_download(self, url):
- self.downloads.append(SingleDownload(self, url))
- return self.downloads[-1]
- def get_downloads(self):
- if self.finflag.isSet():
- return []
- return self.downloads
- def cancel_piece_download(self, pieces):
- for d in self.downloads:
- if d.active and d.index in pieces:
- d.cancelled = True
|