#!/usr/bin/env python
# Written by Bram Cohen and Myers Carpenter
# see LICENSE.txt for license information
#
#文件名称:btdownloadgui.py
#读码日记:2004-9-2
#笔 者:zfive5(醉马不肖 之 [孤舟蓑笠翁, 独钓寒江雪])
#
# 今天,我决定系统打开种子文件开始对bt客户端进行分析,这
#好理解些,我分析源码是在window2000环境下做的,在资源管理器下双击.torrent
#文件,随后将看到我们大家都很熟悉的bt客户端,让我们选择文件目录。。。。
#知觉告诉我,种子文件名称是通过参数调用传入bt客户端的,通过regedit打开注册
#表我们可以找到如下线索:
#
#Windows Registry Editor Version 5.00
#
#[HKEY_CLASSES_ROOT/.torrent]
#@="bittorrent"
#"Content Type"="application/x-bittorrent"
#
#[HKEY_CLASSES_ROOT/bittorrent]
#@="TORRENT File"
#"EditFlags"=hex:00,00,01,00
#
#[HKEY_CLASSES_ROOT/bittorrent/shell]
#@="open"
#
#[HKEY_CLASSES_ROOT/bittorrent/shell/edit]
#@="Edit Torrent With TorrentEdit"
#
#[HKEY_CLASSES_ROOT/bittorrent/shell/edit/command]
#@="/"C://Program Files//BTDWV//torrentedit.exe/" --torrentfile /"%1/""
#
#[HKEY_CLASSES_ROOT/bittorrent/shell/open]
#
#[HKEY_CLASSES_ROOT/bittorrent/shell/open/command]
#@="/"C://Program Files//BTDWV//btdownloadgui.exe/" --responsefile /"%1/""
#
#可以看到系统等于执行了:
#C:/Program Files/BTDWV/btdownloadgui.exe --responsefile e:/Help.chm.torrent
#
#现在开始分析下面这个文件。。。
#大家可以直接goto 到 class btWxApp(wxApp)这个类的定义部分
#
from sys import argv
from BitTorrent import version
from BitTorrent.download import download
from btdownloadheadless import print_spew
from threading import Event, Thread
from os.path import join, split, exists
from os import getcwd
from wxPython.wx import *
from time import strftime, time
from webbrowser import open_new
from traceback import print_exc
#计算时间函数 秒 -〉(小时:分钟:秒)
def hours(n):
if n == -1:
return ''
if n == 0:
return 'complete!'
n = int(n)
h, r = divmod(n, 60 * 60)
m, sec = divmod(r, 60)
if h > 1000000:
return ''
if h > 0:
return '%d hour %02d min %02d sec' % (h, m, sec)
else:
return '%d min %02d sec' % (m, sec)
wxEVT_INVOKE = wxNewEventType()
#事件之类的东东,我也不在清楚,可以跳过
def EVT_INVOKE(win, func):
win.Connect(-1, -1, wxEVT_INVOKE, func)
#同上
class InvokeEvent(wxPyEvent):
def __init__(self, func, args, kwargs):
wxPyEvent.__init__(self)
self.SetEventType(wxEVT_INVOKE)
self.func = func
self.args = args
self.kwargs = kwargs
#下载主窗体类,这一点,我也是通过知觉,实际上也是正确的
class DownloadInfoFrame:
#这是类的构造函数
def __init__(self, flag):
frame = wxFrame(None, -1, 'BitTorrent ' + version + ' download', size = wxSize(400, 250))
self.frame = frame
self.flag = flag
self.uiflag = Event()
self.fin = False
self.last_update_time = 0
self.showing_error = False
panel = wxPanel(frame, -1)
colSizer = wxFlexGridSizer(cols = 1, vgap = 3)
fnsizer = wxBoxSizer(wxHORIZONTAL)
self.fileNameText = wxStaticText(panel, -1, '', style = wxALIGN_LEFT)
fnsizer.Add(self.fileNameText, 1, wxALIGN_BOTTOM)
self.aboutText = wxStaticText(panel, -1, 'about', style = wxALIGN_RIGHT)
self.aboutText.SetForegroundColour('Blue')
self.aboutText.SetFont(wxFont(14, wxNORMAL, wxNORMAL, wxNORMAL, True))
fnsizer.Add(self.aboutText, 0, wxEXPAND)
colSizer.Add(fnsizer, 0, wxEXPAND)
self.gauge = wxGauge(panel, -1, range = 1000, style = wxGA_SMOOTH)
colSizer.Add(self.gauge, 0, wxEXPAND)
gridSizer = wxFlexGridSizer(cols = 2, vgap = 3, hgap = 8)
gridSizer.Add(wxStaticText(panel, -1, 'Estimated time left:'))
self.timeEstText = wxStaticText(panel, -1, '')
gridSizer.Add(self.timeEstText, 0, wxEXPAND)
gridSizer.Add(wxStaticText(panel, -1, 'Download to:'))
self.fileDestText = wxStaticText(panel, -1, '')
gridSizer.Add(self.fileDestText, 0, wxEXPAND)
gridSizer.AddGrowableCol(1)
rategridSizer = wxFlexGridSizer(cols = 4, vgap = 3, hgap = 8)
rategridSizer.Add(wxStaticText(panel, -1, 'Download rate:'))
self.downRateText = wxStaticText(panel, -1, '')
rategridSizer.Add(self.downRateText, 0, wxEXPAND)
rategridSizer.Add(wxStaticText(panel, -1, 'Downloaded:'))
self.downTotalText = wxStaticText(panel, -1, '')
rategridSizer.Add(self.downTotalText, 0, wxEXPAND)
rategridSizer.Add(wxStaticText(panel, -1, 'Upload rate:'))
self.upRateText = wxStaticText(panel, -1, '')
rategridSizer.Add(self.upRateText, 0, wxEXPAND)
rategridSizer.Add(wxStaticText(panel, -1, 'Uploaded:'))
self.upTotalText = wxStaticText(panel, -1, '')
rategridSizer.Add(self.upTotalText, 0, wxEXPAND)
rategridSizer.AddGrowableCol(1)
rategridSizer.AddGrowableCol(3)
colSizer.Add(gridSizer, 0, wxEXPAND)
colSizer.Add(rategridSizer, 0, wxEXPAND)
colSizer.Add(50, 50, 0, wxEXPAND)
self.cancelButton = wxButton(panel, -1, 'Cancel')
colSizer.Add(self.cancelButton, 0, wxALIGN_CENTER)
colSizer.AddGrowableCol(0)
colSizer.AddGrowableRow(3)
border = wxBoxSizer(wxHORIZONTAL)
border.Add(colSizer, 1, wxEXPAND | wxALL, 4)
panel.SetSizer(border)
panel.SetAutoLayout(True)
EVT_LEFT_DOWN(self.aboutText, self.donate)
EVT_CLOSE(frame, self.done)
EVT_BUTTON(frame, self.cancelButton.GetId(), self.done)
EVT_INVOKE(frame, self.onInvoke)
self.frame.Show()
#下面上一些事件处理函数和辅助函数
def donate(self, event):
Thread(target = self.donate2).start()
def donate2(self):
open_new('http://bitconjurer.org/BitTorrent/donate.html')
def onInvoke(self, event):
if not self.uiflag.isSet():
apply(event.func, event.args, event.kwargs)
def invokeLater(self, func, args = [], kwargs = {}):
if not self.uiflag.isSet():
wxPostEvent(self.frame, InvokeEvent(func, args, kwargs))
def updateStatus(self, d):
if (self.last_update_time + 0.1 < time() and not self.showing_error) or d.get('fractionDone') in (0.0, 1.0) or d.has_key('activity'):
self.invokeLater(self.onUpdateStatus, [d])
def onUpdateStatus(self, d):
try:
if d.has_key('spew'):
print_spew(d['spew'])
activity = d.get('activity')
fractionDone = d.get('fractionDone')
timeEst = d.get('timeEst')
downRate = d.get('downRate')
upRate = d.get('upRate')
downTotal = d.get('downTotal')
upTotal = d.get('upTotal')
if activity is not None and not self.fin:
self.timeEstText.SetLabel(activity)
if fractionDone is not None and not self.fin:
self.gauge.SetValue(int(fractionDone * 1000))
self.frame.SetTitle('%d%% %s - BitTorrent %s' % (int(fractionDone*100), self.filename, version))
if timeEst is not None:
self.timeEstText.SetLabel(hours(timeEst))
if downRate is not None:
self.downRateText.SetLabel('%.0f KiB/s' % (float(downRate) / (1 << 10)))
if upRate is not None:
self.upRateText.SetLabel('%.0f KiB/s' % (float(upRate) / (1 << 10)))
if downTotal is not None:
self.downTotalText.SetLabel('%.1f M' % (downTotal))
if upTotal is not None:
self.upTotalText.SetLabel('%.1f M' % (upTotal))
self.last_update_time = time()
except:
print_ex()
def finished(self):
self.fin = True
self.invokeLater(self.onFinishEvent)
def failed(self):
self.fin = True
self.invokeLater(self.onFailEvent)
def error(self, errormsg):
if not self.showing_error:
self.invokeLater(self.onErrorEvent, [errormsg])
def onFinishEvent(self):
self.timeEstText.SetLabel('Download Succeeded!')
self.cancelButton.SetLabel('Close')
self.gauge.SetValue(1000)
self.frame.SetTitle('%s - Upload - BitTorrent %s' % (self.filename, version))
self.downRateText.SetLabel('')
def onFailEvent(self):
self.timeEstText.SetLabel('Failed!')
self.cancelButton.SetLabel('Close')
self.gauge.SetValue(0)
self.downRateText.SetLabel('')
def onErrorEvent(self, errormsg):
self.showing_error = True
dlg = wxMessageDialog(self.frame, message = errormsg,
caption = 'Download Error', style = wxOK | wxICON_ERROR)
dlg.Fit()
dlg.Center()
dlg.ShowModal()
self.showing_error = False
def chooseFile(self, default, size, saveas, dir):
if saveas:
return saveas
f = Event()
bucket = [None]
self.invokeLater(self.onChooseFile, [default, bucket, f, size, dir])
f.wait()
return bucket[0]
def onChooseFile(self, default, bucket, f, size, dir):
if dir:
dl = wxDirDialog(self.frame, 'Choose a directory to save to, pick a partial download to resume',
join(getcwd(), default), style = wxDD_DEFAULT_STYLE | wxDD_NEW_DIR_BUTTON)
else:
dl = wxFileDialog(self.frame, 'Choose file to save as, pick a partial download to resume', '', default, '*.*', wxSAVE)
if dl.ShowModal() != wxID_OK:
self.done(None)
else:
bucket[0] = dl.GetPath()
self.fileNameText.SetLabel('%s (%.1f MB)' % (default, float(size) / (1 << 20)))
self.timeEstText.SetLabel('Starting up...')
self.fileDestText.SetLabel(dl.GetPath())
self.filename = default
self.frame.SetTitle(default + '- BitTorrent ' + version)
f.set()
def newpath(self, path):
self.fileDestText.SetLabel(path)
def done(self, event):
self.uiflag.set()
self.flag.set()
self.frame.Destroy()
#这是bt的App类,bt的开始位置,params就是命令行参数
class btWxApp(wxApp):
def __init__(self, x, params):
self.params = params
wxApp.__init__(self, x)
#这个成员函数有点像 mfc中的BOOL CtestV1App::InitInstance()成员函数(这是我多年vc使用的结果)
#这里面创建的一个上面的主窗体类和启动了一个线程类
#
def OnInit(self):
doneflag = Event()
d = DownloadInfoFrame(doneflag)
self.SetTopWindow(d.frame)
thread = Thread(target = next, args = [self.params, d, doneflag])
thread.setDaemon(False)
thread.start()
return 1
#这里是bt客户端的入口函数
def run(params):
try:
app = btWxApp(0, params)
app.MainLoop()
except:
print_exc()
#线程处理函数,这也是bt的核心过程
def next(params, d, doneflag):
try:
#这段有点迷惘
p = join(split(argv[0])[0], 'donated')
if not exists(p) and long(time()) % 3 == 0:
open_new('http://bitconjurer.org/BitTorrent/donate.html')
dlg = wxMessageDialog(d.frame, 'BitTorrent is Donation supported software. ' +
'Please go to the donation page (which should be appearing for you now) and make a donation from there. ' +
'Or you can click no and donate later./n/nHave you made a donation yet?',
'Donate!', wxYES_NO | wxICON_INFORMATION | wxNO_DEFAULT)
if dlg.ShowModal() == wxID_YES:
dlg.Destroy()
dlg = wxMessageDialog(d.frame, 'Thanks for your donation! You will no longer be shown donation requests./n/n' +
"If you haven't actually made a donation and are feeling guilty (as you should!) you can always get to " +
"the donation page by clicking the 'about' link in the upper-right corner of the main BitTorrent window and " +
'donating from there.', 'Thanks!', wxOK)
dlg.ShowModal()
dlg.Destroy()
try:
open(p, 'wb').close()
except IOError, e:
dlg = wxMessageDialog(d.frame, "Sorry, but I couldn't set the flag to not ask you for donations in the future - " + str(e),
'Sorry!', wxOK | wxICON_ERROR)
dlg.ShowModal()
dlg.Destroy()
else:
dlg.Destroy()
#这个也是我们下面要说的另一个函数
download(params, d.chooseFile, d.updateStatus, d.finished, d.error, doneflag, 100, d.newpath)
if not d.fin:
d.failed()
except:
print_exc()
#是它调用了run函数,他传入的命令行参数是:--responsefile e:/Help.chm.torrent
if __name__ == '__main__':
run(argv[1:])
#先分析到这里,(待续)