Blame | Last modification | View Log | Download
# Written by John Hoffman# see LICENSE.txt for license informationfrom BitTornado.CurrentRateMeasure import Measurefrom random import randintfrom urlparse import urlparsefrom httplib import HTTPConnectionfrom urllib import quotefrom threading import Threadfrom BitTornado.__init__ import product_name,version_shorttry:Trueexcept:True = 1False = 0EXPIRE_TIME = 60 * 60VERSION = product_name+'/'+version_shortclass haveComplete:def complete(self):return Truedef __getitem__(self, x):return Truehaveall = haveComplete()class SingleDownload:def __init__(self, downloader, url):self.downloader = downloaderself.baseurl = urltry:(scheme, self.netloc, path, pars, query, fragment) = urlparse(url)except:self.downloader.errorfunc('cannot parse http seed address: '+url)returnif scheme != 'http':self.downloader.errorfunc('http seed url not http: '+url)returntry:self.connection = HTTPConnection(self.netloc)except:self.downloader.errorfunc('cannot connect to http seed: '+url)returnself.seedurl = pathif pars:self.seedurl += ';'+parsself.seedurl += '?'if query:self.seedurl += query+'&'self.seedurl += 'info_hash='+quote(self.downloader.infohash)self.measure = Measure(downloader.max_rate_period)self.index = Noneself.url = ''self.requests = []self.request_size = 0self.endflag = Falseself.error = Noneself.retry_period = 30self._retry_period = Noneself.errorcount = 0self.goodseed = Falseself.active = Falseself.cancelled = Falseself.resched(randint(2,10))def resched(self, len = None):if len is None:len = self.retry_periodif 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 = Falseif self.downloader.picker.am_I_complete():self.downloader.downloads.remove(self)returnself.index = self.downloader.picker.next(haveall, self._want)if ( self.index is None and not self.endflagand not self.downloader.peerdownloader.has_downloaders() ):self.endflag = Trueself.index = self.downloader.picker.next(haveall, self._want)if self.index is None:self.endflag = Trueself.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 = Truedef _request(self):import encodings.asciiimport encodings.punycodeimport encodings.idnaself.error = Noneself.received_data = Nonetry:self.connection.request('GET',self.url, None,{'User-Agent': VERSION})r = self.connection.getresponse()self.connection_status = r.statusself.received_data = r.read()except Exception, e:self.error = 'error accessing http seed: '+str(e)try:self.connection.close()except:passtry:self.connection = HTTPConnection(self.netloc)except:self.connection = None # will cause an exception and retry next cycleself.downloader.rawserver.add_task(self.request_finished)def request_finished(self):self.active = Falseif self.error is not None:if self.goodseed:self.downloader.errorfunc(self.error)self.errorcount += 1if self.received_data:self.errorcount = 0if not self._got_data():self.received_data = Noneif 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 = Nonereturnself.resched()def _got_data(self):if self.connection_status == 503: # seed is busytry:self.retry_period = max(int(self.received_data),5)except:passreturn Falseif self.connection_status != 200:self.errorcount += 1return Falseself._retry_period = 1if len(self.received_data) != self.request_size:if self.goodseed:self.downloader.errorfunc('corrupt data from http seed - redownloading')return Falseself.measure.update_rate(len(self.received_data))self.downloader.measurefunc(len(self.received_data))if self.cancelled:return Falseif not self._fulfill_requests():return Falseif not self.goodseed:self.goodseed = Trueself.downloader.seedsfound += 1if 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 Truedef _get_requests(self):self.requests = []self.request_size = 0Lwhile 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 = 0Lsuccess = Truewhile 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 = Falsebreakstart += lengthreturn successdef _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 += length1continueelse:if s:s += ','s += str(begin)+'-'+str(begin+length-1)begin, length = begin1, length1if s:s += ','s += str(begin)+'-'+str(begin+length-1)return sclass HTTPDownloader:def __init__(self, storage, picker, rawserver,finflag, errorfunc, peerdownloader,max_rate_period, infohash, measurefunc, gotpiecefunc):self.storage = storageself.picker = pickerself.rawserver = rawserverself.finflag = finflagself.errorfunc = errorfuncself.peerdownloader = peerdownloaderself.infohash = infohashself.max_rate_period = max_rate_periodself.gotpiecefunc = gotpiecefuncself.measurefunc = measurefuncself.downloads = []self.seedsfound = 0def 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.downloadsdef cancel_piece_download(self, pieces):for d in self.downloads:if d.active and d.index in pieces:d.cancelled = True