# Written by Bram Cohen
# see LICENSE.txt for license information
#文件名称:download.py
#读码日记:2004-9-4
#笔 者:zfive5(醉马不肖 之 [孤舟蓑笠翁, 独钓寒江雪])
#
# 接着上节来,我现在理所当然的要分析函数:
#download(params, d.chooseFile, d.updateStatus, d.finished, d.error, doneflag, 100, d.newpath)
#它的定义在download.py。
#
#这里引入库,有点像java里的import,这里可以略过
from zurllib import urlopen
from urlparse import urljoin
from btformats import check_message
from Choker import Choker
from Storage import Storage
from StorageWrapper import StorageWrapper
from Uploader import Upload
from Downloader import Downloader
from Connecter import Connecter
from Encrypter import Encoder
from RawServer import RawServer
from Rerequester import Rerequester
from DownloaderFeedback import DownloaderFeedback
from RateMeasure import RateMeasure
from CurrentRateMeasure import Measure
from PiecePicker import PiecePicker
from bencode import bencode, bdecode
from sha import sha
from os import path, makedirs
from parseargs import parseargs, formatDefinitions
from socket import error as socketerror
from random import seed
from threading import Thread, Event
from time import time
try:
from os import getpid
except ImportError:
def getpid():
return 1
#命令行参数注释列表,当我们直接运行bt客户端时,就弹出一个大的对话框,
#这就是它上面的信息
defaults = [
('max_uploads', 7,
"the maximum number of uploads to allow at once."),
('keepalive_interval', 120.0,
'number of seconds to pause between sending keepalives'),
('download_slice_size', 2 ** 14,
"How many bytes to query for per request."),
('request_backlog', 5,
"how many requests to keep in a single pipe at once."),
('max_message_length', 2 ** 23,
"maximum length prefix encoding you'll accept over the wire - larger values get the connection dropped."),
('ip', '',
"ip to report you have to the tracker."),
('minport', 6881, 'minimum port to listen on, counts up if unavailable'),
('maxport', 6999, 'maximum port to listen on'),
('responsefile', '',
'file the server response was stored in, alternative to url'),
('url', '',
'url to get file from, alternative to responsefile'),
('saveas', '',
'local file name to save the file as, null indicates query user'),
('timeout', 300.0,
'time to wait between closing sockets which nothing has been received on'),
('timeout_check_interval', 60.0,
'time to wait between checking if any connections have timed out'),
('max_slice_length', 2 ** 17,
"maximum length slice to send to peers, larger requests are ignored"),
('max_rate_period', 20.0,
"maximum amount of time to guess the current rate estimate represents"),
('bind', '',
'ip to bind to locally'),
('upload_rate_fudge', 5.0,
'time equivalent of writing to kernel-level TCP buffer, for rate adjustment'),
('display_interval', .5,
'time between updates of displayed information'),
('rerequest_interval', 5 * 60,
'time to wait between requesting more peers'),
('min_peers', 20,
'minimum number of peers to not do rerequesting'),
('http_timeout', 60,
'number of seconds to wait before assuming that an http connection has timed out'),
('max_initiate', 40,
'number of peers at which to stop initiating new connections'),
('check_hashes', 1,
'whether to check hashes on disk'),
('max_upload_rate', 0,
'maximum kB/s to upload at, 0 means no limit'),
('snub_time', 30.0,
"seconds to wait for data to come in over a connection before assuming it's semi-permanently choked"),
('spew', 0,
"whether to display diagnostic info to stdout"),
('rarest_first_cutoff', 4,
"number of downloads at which to switch from random to rarest first"),
('min_uploads', 4,
"the number of uploads to fill out to with extra optimistic unchokes"),
('rarest_first_priority_cutoff', 3,
'the number of peers which need to have a piece before other partials take priority over rarest first'),
('report_hash_failures', 0,
"whether to inform the user that hash failures occur. They're non-fatal."),
]
#功能:bt客户端的核心函数,主要完成下载和上载等功能
#参数:通过上节的download(params, d.chooseFile, d.updateStatus, d.finished, d.error, doneflag, 100, d.newpath)语句,
# 我们可以分析出每一个参数的意义
# params: 命令行参数字典
# filefunc:选择文件路径函数,对应的主窗体的chooseFile成员函数,负责选择文件存放路径的功能。
# statusfunc:状态更新函数,对应的主窗体的updateStatus成员函数,完成bt信息的在窗口上的更新功能。
# finfunc:完成处理函数,对finished成员函数,完成任务后的工作。
# errorfunc:错误处理函数,对error成员函数,出错处理
# doneflag:一个事件,与主窗体关联的
# cols:命令行参数的个数
# pathFunc:?
# paramfunc:?
# spewflag :?
def download(params, filefunc, statusfunc, finfunc, errorfunc, doneflag, cols, pathFunc = None, paramfunc = None, spewflag = Event()):
if len(params) == 0:
#命令行参数错误
errorfunc('arguments are -/n' + formatDefinitions(defaults, cols))
return
try:
#分析命令行参数
config, args = parseargs(params, defaults, 0, 1)
if args:
#得到responsefile参数的值,就是种子文件的全路径名称,其中做了一些错误处理,可以略过
if config.get('responsefile', None) == None:
raise ValueError, 'must have responsefile as arg or parameter, not both'
if path.isfile(args[0]):
config['responsefile'] = args[0]
else:
#这里在得到url,从网络打开的torrent文件
config['url'] = args[0]
if (config['responsefile'] == '') == (config['url'] == ''):
raise ValueError, 'need responsefile or url'
except ValueError, e:
errorfunc('error: ' + str(e) + '/nrun with no args for parameter explanations')
return
#根据不同方式打开种子文件
try:
if config['responsefile'] != '':
h = open(config['responsefile'], 'rb')
else:
h = urlopen(config['url'])
response = h.read()
h.close()
except IOError, e:
errorfunc('problem getting response info - ' + str(e))
return
#分析种子文件
try:
# 函数bdecode(reponse),check_message(response)有点面熟吧,在<<读BitTorrent码日记1>>中说过
response = bdecode(response)
check_message(response)
except ValueError, e:
errorfunc("got bad file info - " + str(e))
return
#选择下载存放文件目录
try:
#定义一个函数,这让我想起了pl/0的语法,有点回想起5年前的编译课程设计.....不说废话
def make(f, forcedir = False):
if not forcedir:
f = path.split(f)[0]
if f != '' and not path.exists(f):
makedirs(f)
#得到种子中的info信息
info = response['info']
if info.has_key('length'):
#对单个文件进行目录存放选择处理
file_length = info['length']
#调用选择下载存放函数,像是vc中save as对话框,语言是相通的....
file = filefunc(info['name'], file_length, config['saveas'], False)
if file is None:
return
#生成文件目录
make(file)
#生成文件与大小对应的列表
files = [(file, file_length)]
else:
#对非单个文件进行目录存放选择处理
file_length = 0
for x in info['files']:
file_length += x['length']
#同上,这里我不想在废话了
file = filefunc(info['name'], file_length, config['saveas'], True)
if file is None:
return
# if this path exists, and no files from the info dict exist, we assume it's a new download and
# the user wants to create a new directory with the default name
#生成相应存放目录
existing = 0
if path.exists(file):
for x in info['files']:
if path.exists(path.join(file, x['path'][0])):
existing = 1
if not existing:
file = path.join(file, info['name'])
make(file, True)
# alert the UI to any possible change in path
if pathFunc != None:
pathFunc(file)
#生成文件与大小对应的列表,生成对应子目录
files = []
for x in info['files']:
n = file
for i in x['path']:
n = path.join(n, i)
files.append((n, x['length']))
make(n)
except OSError, e:
errorfunc("Couldn't allocate dir - " + str(e))
return
#继续
finflag = Event()
ann = [None]
myid = (chr(0) * 12) + sha(repr(time()) + ' ' + str(getpid())).digest()[-8:]
#Initialize the basic random number generator. python infunction
seed(myid)
#得到每个pieces的SHA1摘要,生成一个列表
pieces = [info['pieces'][x:x+20] for x in xrange(0,
len(info['pieces']), 20)]
#定义失败处理函数
def failed(reason, errorfunc = errorfunc, doneflag = doneflag):
doneflag.set()
if reason is not None:
errorfunc(reason)
#定义一个tcp服务器类,这个类的源码在以后再列出来分析
rawserver = RawServer(doneflag, config['timeout_check_interval'], config['timeout'], errorfunc = errorfunc)
try:
try:
#这个我现在也不知道是干什么的,之后再分析
storage = Storage(files, open, path.exists, path.getsize)
except IOError, e:
errorfunc('trouble accessing files - ' + str(e))
return
#定义了一个完成处理函数
def finished(finfunc = finfunc, finflag = finflag,
ann = ann, storage = storage, errorfunc = errorfunc):
finflag.set()
try:
storage.set_readonly()
except (IOError, OSError), e:
errorfunc('trouble setting readonly at end - ' + str(e))
if ann[0] is not None:
ann[0](1)
finfunc()
rm = [None]
#数据失败处理函数
def data_flunked(amount, rm = rm, errorfunc = errorfunc, report_hash_failures = config['report_hash_failures']):
if rm[0] is not None:
rm[0](amount)
if report_hash_failures:
errorfunc('a piece failed hash check, re-downloading it')
#这个我现在也不知道是干什么的,之后再分析
storagewrapper = StorageWrapper(storage,
config['download_slice_size'], pieces,
info['piece length'], finished, failed,
statusfunc, doneflag, config['check_hashes'], data_flunked)
except ValueError, e:
failed('bad data - ' + str(e))
except IOError, e:
failed('IOError - ' + str(e))
if doneflag.isSet():
return
#捆绑服务器端口,minport<=listen_port<=maxport
e = 'maxport less than minport - no ports to check'
for listen_port in xrange(config['minport'], config['maxport'] + 1):
try:
rawserver.bind(listen_port, config['bind'])
break
except socketerror, e:
pass
else:
errorfunc("Couldn't listen - " + str(e))
return
#写到这里我打算止步,现在脑子有太多问号了,像RawServer,Storage这两个类是怎样的?
#我现在也没有心思在看下去了,我打算先看看它们的定义再说。。。。