Merge lp:~rcart/ubuntu/natty/bittornado/fix-420387 into lp:ubuntu/natty/bittornado
- Natty (11.04)
- fix-420387
- Merge into natty
Status: | Merged |
---|---|
Merge reported by: | Daniel Holbach |
Merged at revision: | not available |
Proposed branch: | lp:~rcart/ubuntu/natty/bittornado/fix-420387 |
Merge into: | lp:ubuntu/natty/bittornado |
Diff against target: |
28069 lines (+1407/-24161) 80 files modified
.pc/01_MANIFEST.in_remove_broken_cruft.dpatch/setup.py (+0/-28) .pc/02_btdownloadcurses_increase_significant_digit.dpatch/btdownloadcurses.py (+0/-407) .pc/05_bttrack_connerr_fix.dpatch/BitTornado/BT1/track.py (+0/-1137) .pc/06_README_portchange.dpatch/README.txt (+0/-110) .pc/07_change_report_address.dpatch/BitTornado/__init__.py (+0/-63) .pc/08_btdownloadcurses_indent.dpatch/btdownloadcurses.py (+0/-407) .pc/09_timtuckerfixes.dpatch/BitTornado/BT1/Connecter.py (+0/-328) .pc/09_timtuckerfixes.dpatch/BitTornado/BT1/Encrypter.py (+0/-657) .pc/09_timtuckerfixes.dpatch/BitTornado/BT1/Storage.py (+0/-584) .pc/09_timtuckerfixes.dpatch/BitTornado/BT1/StreamCheck.py (+0/-135) .pc/09_timtuckerfixes.dpatch/BitTornado/ConfigDir.py (+0/-401) .pc/09_timtuckerfixes.dpatch/BitTornado/RawServer.py (+0/-195) .pc/09_timtuckerfixes.dpatch/BitTornado/clock.py (+0/-27) .pc/09_timtuckerfixes.dpatch/BitTornado/download_bt1.py (+0/-877) .pc/09_timtuckerfixes.dpatch/BitTornado/launchmanycore.py (+0/-381) .pc/10_removeCVScrud.dpatch/.cvsignore (+0/-4) .pc/10_removeCVScrud.dpatch/BitTornado/.cvsignore (+0/-4) .pc/10_removeCVScrud.dpatch/BitTornado/BT1/.cvsignore (+0/-4) .pc/11_sorthashcheck.dpatch/BitTornado/launchmanycore.py (+0/-389) .pc/12_fix_guis_for_2.6.dpatch/btcompletedirgui.py (+0/-192) .pc/12_fix_guis_for_2.6.dpatch/btmaketorrentgui.py (+0/-353) .pc/13_fix_btcompletedirgui_bug.dpatch/btcompletedirgui.py (+0/-192) .pc/15_fix_unicode_in_makemetafile.py.dpatch/BitTornado/BT1/makemetafile.py (+0/-263) .pc/16_fix_ipv6_in_SocketHandler.dpatch/BitTornado/SocketHandler.py (+0/-375) .pc/17_fix_NatCheck_bufferlen_error.dpatch/BitTornado/BT1/NatCheck.py (+0/-219) .pc/18_fix_launchmany_encrypter.dpatch/BitTornado/BT1/Encrypter.py (+0/-646) .pc/19_fix_tracker_return_all.dpatch/BitTornado/BT1/track.py (+0/-1137) .pc/20_tracker_cache_minor_fix.dpatch/BitTornado/BT1/track.py (+0/-1138) .pc/21_remove_deprecated_wxPython_usage.dpatch/BitTornado/ConfigReader.py (+0/-1195) .pc/21_remove_deprecated_wxPython_usage.dpatch/bt-t-make.py (+0/-1063) .pc/21_remove_deprecated_wxPython_usage.dpatch/btcompletedirgui.py (+0/-192) .pc/21_remove_deprecated_wxPython_usage.dpatch/btdownloadgui.py (+0/-2373) .pc/21_remove_deprecated_wxPython_usage.dpatch/btmaketorrentgui.py (+0/-353) .pc/22_fix_makemetafile_error-handling.dpatch/BitTornado/BT1/makemetafile.py (+0/-264) .pc/23_remove_UPnP_options.dpatch/BitTornado/download_bt1.py (+0/-871) .pc/23_remove_UPnP_options.dpatch/BitTornado/launchmanycore.py (+0/-390) .pc/23_remove_UPnP_options.dpatch/btdownloadcurses.py (+0/-408) .pc/23_remove_UPnP_options.dpatch/btdownloadgui.py (+0/-2368) .pc/23_remove_UPnP_options.dpatch/btdownloadheadless.py (+0/-244) .pc/24_clarify_ip_parameter.dpatch/README.txt (+0/-110) .pc/25_errors_in_error_handling.dpatch/btdownloadcurses.py (+0/-408) .pc/25_errors_in_error_handling.dpatch/btdownloadheadless.py (+0/-244) .pc/27_remove_btdownloadheadless_curses_dependency.dpatch/btdownloadheadless.py (+0/-246) .pc/28_float_mod_time_fix.dpatch/BitTornado/parsedir.py (+0/-150) .pc/29_fix_urandom_error.dpatch/BitTornado/BTcrypto.py (+0/-103) .pc/30_announce_list_only_torrents.dpatch/BitTornado/BT1/btformats.py (+0/-100) .pc/30_announce_list_only_torrents.dpatch/btshowmetainfo.py (+0/-78) .pc/31_fix_for_compact_reqd_off.dpatch/BitTornado/BT1/track.py (+0/-1143) .pc/applied-patches (+0/-27) BitTornado/BT1/Connecter.py (+0/-1) BitTornado/BT1/Encrypter.py (+13/-4) BitTornado/BT1/NatCheck.py (+3/-0) BitTornado/BT1/Storage.py (+14/-18) BitTornado/BT1/StreamCheck.py (+16/-1) BitTornado/BT1/btformats.py (+3/-17) BitTornado/BT1/makemetafile.py (+2/-3) BitTornado/BT1/track.py (+13/-22) BitTornado/BTcrypto.py (+0/-1) BitTornado/ConfigDir.py (+17/-2) BitTornado/ConfigReader.py (+231/-226) BitTornado/RawServer.py (+2/-4) BitTornado/SocketHandler.py (+1/-1) BitTornado/__init__.py (+1/-1) BitTornado/clock.py (+2/-5) BitTornado/download_bt1.py (+13/-4) BitTornado/launchmanycore.py (+5/-14) BitTornado/parsedir.py (+1/-2) README.txt (+4/-4) bt-t-make.py (+255/-255) btcompletedirgui.py (+56/-56) btdownloadcurses.py (+6/-11) btdownloadgui.py (+356/-351) btdownloadheadless.py (+10/-4) btmaketorrentgui.py (+106/-106) btshowmetainfo.py (+54/-63) debian/changelog (+8/-0) debian/control (+2/-1) debian/patches/32_use_hashlib_for_sha.patch (+210/-0) debian/patches/series (+1/-0) setup.py (+2/-1) |
To merge this branch: | bzr merge lp:~rcart/ubuntu/natty/bittornado/fix-420387 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Holbach (community) | Approve | ||
Artur Rona (community) | Approve | ||
Ubuntu branches | Pending | ||
Review via email: mp+46878@code.launchpad.net |
Commit message
Description of the change
* debian/
- Updated use of deprecated sha module to hashlib. (LP: #420387, Closes: #593653)
Scott Moser (smoser) wrote : | # |
Artur Rona (ari-tczew) wrote : | # |
1) Please don't change Standards-Version and other lintian warnings if it's not necessary. This is the place for Debian.
2) According to above, don't mention about fixing lintian warnings in debian/changelog.
3) We don't mention about update-maintainer field in debian/changelog.
4) Please add DEP3 tags to your patch. https:/
Artur Rona (ari-tczew) wrote : | # |
5) Package is in quilt 3.0 source format, so please change your patch filename to *.patch, not *.dpatch and update series file. Debian should update their files.
6) I would like to see following entry in debian/changelog:
* debian/
- Updated use of deprecated sha module to hashlib. (LP: #420387)
* Removed old CVS dir so it can use .bzr dir for Bazaar.
7) What about forwarding this patch to Debian?
Thank you for your contribution!
Artur Rona (ari-tczew) wrote : | # |
There are files patched directly. Could you try to clean up? also .pc files should be removed.
Ronny Cardona (rcart) wrote : | # |
Thanks for the corrections and time, Artur. I've updated the branch right now. All the patches are directly applied by default in the original branch, so i've cleaned it up.
Artur Rona (ari-tczew) wrote : | # |
Now looks better, but still some issues:
1) Please use revision 0.3.18-10ubuntu1 and below (LP: #420387, Closes: #593653)
2) Improve DEP3 tags:
Bug-Debian: http://
Add dot at the end of sentence in Description ;)
About Origin: I saw on the Debian bug that patch comes from Git - could you research the git address and get a link to this revision?
Artur Rona (ari-tczew) wrote : | # |
BTW package built fine.
Ronny Cardona (rcart) wrote : | # |
> BTW package built fine.
Great. Now I’m looking for the git link, hopefully get it right away to update the branch ^^
Ronny Cardona (rcart) wrote : | # |
Branch updated.
About the git address; I didn't find it. It seems like Debian uses svn and upstream cvs in the project.
Looks like the patch author worked in a local git branch to generate the patch, not sure about that :/
By the way, why use that Bittornado revision on Ubuntu? It's due to the actual status of developement cycle?
Thanks in advance
Artur Rona (ari-tczew) wrote : | # |
Please add DEP3 tag to 32*.patch:
Bug-Ubuntu: https:/
If you have this done, I'll approve.
Artur Rona (ari-tczew) wrote : | # |
Sorry, I was wrong: https:/
- 7. By Ronny Cardona
-
* debian/
patches/ 32_use_ hashlib_ for_sha. patch:
- Updated use of deprecated sha module to hashlib. (LP: #420387,
Closes: #593653)
Ronny Cardona (rcart) wrote : | # |
Branch updated.I hope that it's ready.
Thanks for all your corrections.
Artur Rona (ari-tczew) wrote : | # |
OK, now core-dev turn.
Daniel Holbach (dholbach) wrote : | # |
I'm not quite sure what happened in this branch, but I uploaded what I extracted as the minimal diff.
--- bittornado-
+++ bittornado/
@@ -1,3 +1,11 @@
+bittornado (0.3.18-10ubuntu1) natty; urgency=low
+
+ * debian/
+ - Updated use of deprecated sha module to hashlib. (LP: #420387,
+ Closes: #593653)
+
+ -- Ronny Cardona (Rcart) <email address hidden> Mon, 24 Jan 2011 17:27:47 -0600
+
bittornado (0.3.18-10) unstable; urgency=low
* New patch from upstream's CVS to allow torrents that only have an
--- bittornado-
+++ bittornado/
@@ -1,7 +1,8 @@
Source: bittornado
Section: net
Priority: optional
-Maintainer: Cameron Dale <email address hidden>
+Maintainer: Ubuntu Developers <email address hidden>
+XSBC-Original-
Build-Depends: debhelper (>= 5.0.37.2)
Build-
Standards-Version: 3.8.4
--- bittornado-
+++ bittornado/
@@ -0,0 +1,210 @@
+From: Ronny Cardona (Rcart) <email address hidden>
+Description: Updated use of deprecated sha module to hashlib.
+Origin: http://
+Bug-Debian: http://
+Bug-Ubuntu: https:/
+
+Index: bittornado.
+======
+--- bittornado.
++++ bittornado.
+@@ -4,7 +4,10 @@
+
+ from os.path import getsize, split, join, abspath, isdir
+ from os import listdir
+-from sha import sha
++try:
++ from hashlib import sha1 as sha
++except ImportError:
++ from sha import sha
+ from copy import copy
+ from string import strip
+ from BitTornado.bencode import bencode
+Index: bittornado.
+======
+--- bittornado.
++++ bittornado.
+@@ -12,7 +12,10 @@
+ from traceback import print_exc
+ from socket import error, gethostbyname
+ from random import shuffle
+-from sha import sha
++try:
++ from hashlib import sha1 as sha
++except ImportError:
++ from sha import sha
+ from time import time
+ try:
+ from os import getpid
+Index: bittornado.
+======
+--- bittornado.
++++ bittornado.
Preview Diff
1 | === removed directory '.pc/01_MANIFEST.in_remove_broken_cruft.dpatch' | |||
2 | === removed file '.pc/01_MANIFEST.in_remove_broken_cruft.dpatch/setup.py' | |||
3 | --- .pc/01_MANIFEST.in_remove_broken_cruft.dpatch/setup.py 2010-03-21 14:36:30 +0000 | |||
4 | +++ .pc/01_MANIFEST.in_remove_broken_cruft.dpatch/setup.py 1970-01-01 00:00:00 +0000 | |||
5 | @@ -1,28 +0,0 @@ | |||
6 | 1 | #!/usr/bin/env python | ||
7 | 2 | |||
8 | 3 | # Written by Bram Cohen | ||
9 | 4 | # see LICENSE.txt for license information | ||
10 | 5 | |||
11 | 6 | import sys | ||
12 | 7 | assert sys.version >= '2', "Install Python 2.0 or greater" | ||
13 | 8 | from distutils.core import setup, Extension | ||
14 | 9 | import BitTornado | ||
15 | 10 | |||
16 | 11 | setup( | ||
17 | 12 | name = "BitTornado", | ||
18 | 13 | version = BitTornado.version, | ||
19 | 14 | author = "Bram Cohen, John Hoffman, Uoti Arpala et. al.", | ||
20 | 15 | author_email = "<theshadow@degreez.net>", | ||
21 | 16 | url = "http://www.bittornado.com", | ||
22 | 17 | license = "MIT", | ||
23 | 18 | |||
24 | 19 | packages = ["BitTornado","BitTornado.BT1"], | ||
25 | 20 | |||
26 | 21 | scripts = ["btdownloadgui.py", "btdownloadheadless.py", | ||
27 | 22 | "bttrack.py", "btmakemetafile.py", "btlaunchmany.py", "btcompletedir.py", | ||
28 | 23 | "btdownloadcurses.py", "btcompletedirgui.py", "btlaunchmanycurses.py", | ||
29 | 24 | "btmakemetafile.py", "btreannounce.py", "btrename.py", "btshowmetainfo.py", | ||
30 | 25 | 'btmaketorrentgui.py', 'btcopyannounce.py', 'btsethttpseeds.py', | ||
31 | 26 | 'bt-t-make.py', | ||
32 | 27 | ] | ||
33 | 28 | ) | ||
34 | 29 | 0 | ||
35 | === removed directory '.pc/02_btdownloadcurses_increase_significant_digit.dpatch' | |||
36 | === removed file '.pc/02_btdownloadcurses_increase_significant_digit.dpatch/btdownloadcurses.py' | |||
37 | --- .pc/02_btdownloadcurses_increase_significant_digit.dpatch/btdownloadcurses.py 2010-03-21 14:36:30 +0000 | |||
38 | +++ .pc/02_btdownloadcurses_increase_significant_digit.dpatch/btdownloadcurses.py 1970-01-01 00:00:00 +0000 | |||
39 | @@ -1,407 +0,0 @@ | |||
40 | 1 | #!/usr/bin/env python | ||
41 | 2 | |||
42 | 3 | # Written by Henry 'Pi' James | ||
43 | 4 | # see LICENSE.txt for license information | ||
44 | 5 | |||
45 | 6 | SPEW_SCROLL_RATE = 1 | ||
46 | 7 | |||
47 | 8 | from BitTornado import PSYCO | ||
48 | 9 | if PSYCO.psyco: | ||
49 | 10 | try: | ||
50 | 11 | import psyco | ||
51 | 12 | assert psyco.__version__ >= 0x010100f0 | ||
52 | 13 | psyco.full() | ||
53 | 14 | except: | ||
54 | 15 | pass | ||
55 | 16 | |||
56 | 17 | from BitTornado.download_bt1 import BT1Download, defaults, parse_params, get_usage, get_response | ||
57 | 18 | from BitTornado.RawServer import RawServer, UPnP_ERROR | ||
58 | 19 | from random import seed | ||
59 | 20 | from socket import error as socketerror | ||
60 | 21 | from BitTornado.bencode import bencode | ||
61 | 22 | from BitTornado.natpunch import UPnP_test | ||
62 | 23 | from threading import Event | ||
63 | 24 | from os.path import abspath | ||
64 | 25 | from signal import signal, SIGWINCH | ||
65 | 26 | from sha import sha | ||
66 | 27 | from sys import argv, exit | ||
67 | 28 | import sys | ||
68 | 29 | from time import time, strftime | ||
69 | 30 | from BitTornado.clock import clock | ||
70 | 31 | from BitTornado import createPeerID, version | ||
71 | 32 | from BitTornado.ConfigDir import ConfigDir | ||
72 | 33 | |||
73 | 34 | try: | ||
74 | 35 | import curses | ||
75 | 36 | import curses.panel | ||
76 | 37 | from curses.wrapper import wrapper as curses_wrapper | ||
77 | 38 | from signal import signal, SIGWINCH | ||
78 | 39 | except: | ||
79 | 40 | print 'Textmode GUI initialization failed, cannot proceed.' | ||
80 | 41 | |||
81 | 42 | print 'This download interface requires the standard Python module ' \ | ||
82 | 43 | '"curses", which is unfortunately not available for the native ' \ | ||
83 | 44 | 'Windows port of Python. It is however available for the Cygwin ' \ | ||
84 | 45 | 'port of Python, running on all Win32 systems (www.cygwin.com).' | ||
85 | 46 | |||
86 | 47 | print 'You may still use "btdownloadheadless.py" to download.' | ||
87 | 48 | sys.exit(1) | ||
88 | 49 | |||
89 | 50 | assert sys.version >= '2', "Install Python 2.0 or greater" | ||
90 | 51 | try: | ||
91 | 52 | True | ||
92 | 53 | except: | ||
93 | 54 | True = 1 | ||
94 | 55 | False = 0 | ||
95 | 56 | |||
96 | 57 | def fmttime(n): | ||
97 | 58 | if n == 0: | ||
98 | 59 | return 'download complete!' | ||
99 | 60 | try: | ||
100 | 61 | n = int(n) | ||
101 | 62 | assert n >= 0 and n < 5184000 # 60 days | ||
102 | 63 | except: | ||
103 | 64 | return '<unknown>' | ||
104 | 65 | m, s = divmod(n, 60) | ||
105 | 66 | h, m = divmod(m, 60) | ||
106 | 67 | return 'finishing in %d:%02d:%02d' % (h, m, s) | ||
107 | 68 | |||
108 | 69 | def fmtsize(n): | ||
109 | 70 | s = str(n) | ||
110 | 71 | size = s[-3:] | ||
111 | 72 | while len(s) > 3: | ||
112 | 73 | s = s[:-3] | ||
113 | 74 | size = '%s,%s' % (s[-3:], size) | ||
114 | 75 | if n > 999: | ||
115 | 76 | unit = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] | ||
116 | 77 | i = 1 | ||
117 | 78 | while i + 1 < len(unit) and (n >> 10) >= 999: | ||
118 | 79 | i += 1 | ||
119 | 80 | n >>= 10 | ||
120 | 81 | n = float(n) / (1 << 10) | ||
121 | 82 | size = '%s (%.0f %s)' % (size, n, unit[i]) | ||
122 | 83 | return size | ||
123 | 84 | |||
124 | 85 | |||
125 | 86 | class CursesDisplayer: | ||
126 | 87 | def __init__(self, scrwin, errlist, doneflag): | ||
127 | 88 | self.scrwin = scrwin | ||
128 | 89 | self.errlist = errlist | ||
129 | 90 | self.doneflag = doneflag | ||
130 | 91 | |||
131 | 92 | signal(SIGWINCH, self.winch_handler) | ||
132 | 93 | self.changeflag = Event() | ||
133 | 94 | |||
134 | 95 | self.done = 0 | ||
135 | 96 | self.file = '' | ||
136 | 97 | self.fileSize = '' | ||
137 | 98 | self.activity = '' | ||
138 | 99 | self.status = '' | ||
139 | 100 | self.progress = '' | ||
140 | 101 | self.downloadTo = '' | ||
141 | 102 | self.downRate = '---' | ||
142 | 103 | self.upRate = '---' | ||
143 | 104 | self.shareRating = '' | ||
144 | 105 | self.seedStatus = '' | ||
145 | 106 | self.peerStatus = '' | ||
146 | 107 | self.errors = [] | ||
147 | 108 | self.last_update_time = 0 | ||
148 | 109 | self.spew_scroll_time = 0 | ||
149 | 110 | self.spew_scroll_pos = 0 | ||
150 | 111 | |||
151 | 112 | self._remake_window() | ||
152 | 113 | |||
153 | 114 | def winch_handler(self, signum, stackframe): | ||
154 | 115 | self.changeflag.set() | ||
155 | 116 | curses.endwin() | ||
156 | 117 | self.scrwin.refresh() | ||
157 | 118 | self.scrwin = curses.newwin(0, 0, 0, 0) | ||
158 | 119 | self._remake_window() | ||
159 | 120 | |||
160 | 121 | def _remake_window(self): | ||
161 | 122 | self.scrh, self.scrw = self.scrwin.getmaxyx() | ||
162 | 123 | self.scrpan = curses.panel.new_panel(self.scrwin) | ||
163 | 124 | self.labelh, self.labelw, self.labely, self.labelx = 11, 9, 1, 2 | ||
164 | 125 | self.labelwin = curses.newwin(self.labelh, self.labelw, | ||
165 | 126 | self.labely, self.labelx) | ||
166 | 127 | self.labelpan = curses.panel.new_panel(self.labelwin) | ||
167 | 128 | self.fieldh, self.fieldw, self.fieldy, self.fieldx = ( | ||
168 | 129 | self.labelh, self.scrw-2 - self.labelw-3, | ||
169 | 130 | 1, self.labelw+3) | ||
170 | 131 | self.fieldwin = curses.newwin(self.fieldh, self.fieldw, | ||
171 | 132 | self.fieldy, self.fieldx) | ||
172 | 133 | self.fieldwin.nodelay(1) | ||
173 | 134 | self.fieldpan = curses.panel.new_panel(self.fieldwin) | ||
174 | 135 | self.spewh, self.speww, self.spewy, self.spewx = ( | ||
175 | 136 | self.scrh - self.labelh - 2, self.scrw - 3, 1 + self.labelh, 2) | ||
176 | 137 | self.spewwin = curses.newwin(self.spewh, self.speww, | ||
177 | 138 | self.spewy, self.spewx) | ||
178 | 139 | self.spewpan = curses.panel.new_panel(self.spewwin) | ||
179 | 140 | try: | ||
180 | 141 | self.scrwin.border(ord('|'),ord('|'),ord('-'),ord('-'),ord(' '),ord(' '),ord(' '),ord(' ')) | ||
181 | 142 | except: | ||
182 | 143 | pass | ||
183 | 144 | self.labelwin.addstr(0, 0, 'file:') | ||
184 | 145 | self.labelwin.addstr(1, 0, 'size:') | ||
185 | 146 | self.labelwin.addstr(2, 0, 'dest:') | ||
186 | 147 | self.labelwin.addstr(3, 0, 'progress:') | ||
187 | 148 | self.labelwin.addstr(4, 0, 'status:') | ||
188 | 149 | self.labelwin.addstr(5, 0, 'dl speed:') | ||
189 | 150 | self.labelwin.addstr(6, 0, 'ul speed:') | ||
190 | 151 | self.labelwin.addstr(7, 0, 'sharing:') | ||
191 | 152 | self.labelwin.addstr(8, 0, 'seeds:') | ||
192 | 153 | self.labelwin.addstr(9, 0, 'peers:') | ||
193 | 154 | curses.panel.update_panels() | ||
194 | 155 | curses.doupdate() | ||
195 | 156 | self.changeflag.clear() | ||
196 | 157 | |||
197 | 158 | |||
198 | 159 | def finished(self): | ||
199 | 160 | self.done = 1 | ||
200 | 161 | self.activity = 'download succeeded!' | ||
201 | 162 | self.downRate = '---' | ||
202 | 163 | self.display(fractionDone = 1) | ||
203 | 164 | |||
204 | 165 | def failed(self): | ||
205 | 166 | self.done = 1 | ||
206 | 167 | self.activity = 'download failed!' | ||
207 | 168 | self.downRate = '---' | ||
208 | 169 | self.display() | ||
209 | 170 | |||
210 | 171 | def error(self, errormsg): | ||
211 | 172 | newerrmsg = strftime('[%H:%M:%S] ') + errormsg | ||
212 | 173 | self.errors.append(newerrmsg) | ||
213 | 174 | self.errlist.append(newerrmsg) | ||
214 | 175 | self.display() | ||
215 | 176 | |||
216 | 177 | def display(self, dpflag = Event(), fractionDone = None, timeEst = None, | ||
217 | 178 | downRate = None, upRate = None, activity = None, | ||
218 | 179 | statistics = None, spew = None, **kws): | ||
219 | 180 | |||
220 | 181 | inchar = self.fieldwin.getch() | ||
221 | 182 | if inchar == 12: # ^L | ||
222 | 183 | self._remake_window() | ||
223 | 184 | elif inchar in (ord('q'),ord('Q')): | ||
224 | 185 | self.doneflag.set() | ||
225 | 186 | |||
226 | 187 | if activity is not None and not self.done: | ||
227 | 188 | self.activity = activity | ||
228 | 189 | elif timeEst is not None: | ||
229 | 190 | self.activity = fmttime(timeEst) | ||
230 | 191 | if self.changeflag.isSet(): | ||
231 | 192 | return | ||
232 | 193 | if self.last_update_time + 0.1 > clock() and fractionDone not in (0.0, 1.0) and activity is not None: | ||
233 | 194 | return | ||
234 | 195 | self.last_update_time = clock() | ||
235 | 196 | if fractionDone is not None: | ||
236 | 197 | blocknum = int(self.fieldw * fractionDone) | ||
237 | 198 | self.progress = blocknum * '#' + (self.fieldw - blocknum) * '_' | ||
238 | 199 | self.status = '%s (%.1f%%)' % (self.activity, fractionDone * 100) | ||
239 | 200 | else: | ||
240 | 201 | self.status = self.activity | ||
241 | 202 | if downRate is not None: | ||
242 | 203 | self.downRate = '%.1f KB/s' % (float(downRate) / (1 << 10)) | ||
243 | 204 | if upRate is not None: | ||
244 | 205 | self.upRate = '%.1f KB/s' % (float(upRate) / (1 << 10)) | ||
245 | 206 | if statistics is not None: | ||
246 | 207 | if (statistics.shareRating < 0) or (statistics.shareRating > 100): | ||
247 | 208 | self.shareRating = 'oo (%.1f MB up / %.1f MB down)' % (float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20)) | ||
248 | 209 | else: | ||
249 | 210 | self.shareRating = '%.3f (%.1f MB up / %.1f MB down)' % (statistics.shareRating, float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20)) | ||
250 | 211 | if not self.done: | ||
251 | 212 | self.seedStatus = '%d seen now, plus %.3f distributed copies' % (statistics.numSeeds,0.001*int(1000*statistics.numCopies2)) | ||
252 | 213 | else: | ||
253 | 214 | self.seedStatus = '%d seen recently, plus %.3f distributed copies' % (statistics.numOldSeeds,0.001*int(1000*statistics.numCopies)) | ||
254 | 215 | self.peerStatus = '%d seen now, %.1f%% done at %.1f kB/s' % (statistics.numPeers,statistics.percentDone,float(statistics.torrentRate) / (1 << 10)) | ||
255 | 216 | |||
256 | 217 | self.fieldwin.erase() | ||
257 | 218 | self.fieldwin.addnstr(0, 0, self.file, self.fieldw, curses.A_BOLD) | ||
258 | 219 | self.fieldwin.addnstr(1, 0, self.fileSize, self.fieldw) | ||
259 | 220 | self.fieldwin.addnstr(2, 0, self.downloadTo, self.fieldw) | ||
260 | 221 | if self.progress: | ||
261 | 222 | self.fieldwin.addnstr(3, 0, self.progress, self.fieldw, curses.A_BOLD) | ||
262 | 223 | self.fieldwin.addnstr(4, 0, self.status, self.fieldw) | ||
263 | 224 | self.fieldwin.addnstr(5, 0, self.downRate, self.fieldw) | ||
264 | 225 | self.fieldwin.addnstr(6, 0, self.upRate, self.fieldw) | ||
265 | 226 | self.fieldwin.addnstr(7, 0, self.shareRating, self.fieldw) | ||
266 | 227 | self.fieldwin.addnstr(8, 0, self.seedStatus, self.fieldw) | ||
267 | 228 | self.fieldwin.addnstr(9, 0, self.peerStatus, self.fieldw) | ||
268 | 229 | |||
269 | 230 | self.spewwin.erase() | ||
270 | 231 | |||
271 | 232 | if not spew: | ||
272 | 233 | errsize = self.spewh | ||
273 | 234 | if self.errors: | ||
274 | 235 | self.spewwin.addnstr(0, 0, "error(s):", self.speww, curses.A_BOLD) | ||
275 | 236 | errsize = len(self.errors) | ||
276 | 237 | displaysize = min(errsize, self.spewh) | ||
277 | 238 | displaytop = errsize - displaysize | ||
278 | 239 | for i in range(displaysize): | ||
279 | 240 | self.spewwin.addnstr(i, self.labelw, self.errors[displaytop + i], | ||
280 | 241 | self.speww-self.labelw-1, curses.A_BOLD) | ||
281 | 242 | else: | ||
282 | 243 | if self.errors: | ||
283 | 244 | self.spewwin.addnstr(0, 0, "error:", self.speww, curses.A_BOLD) | ||
284 | 245 | self.spewwin.addnstr(0, self.labelw, self.errors[-1], | ||
285 | 246 | self.speww-self.labelw-1, curses.A_BOLD) | ||
286 | 247 | self.spewwin.addnstr(2, 0, " # IP Upload Download Completed Speed", self.speww, curses.A_BOLD) | ||
287 | 248 | |||
288 | 249 | |||
289 | 250 | if self.spew_scroll_time + SPEW_SCROLL_RATE < clock(): | ||
290 | 251 | self.spew_scroll_time = clock() | ||
291 | 252 | if len(spew) > self.spewh-5 or self.spew_scroll_pos > 0: | ||
292 | 253 | self.spew_scroll_pos += 1 | ||
293 | 254 | if self.spew_scroll_pos > len(spew): | ||
294 | 255 | self.spew_scroll_pos = 0 | ||
295 | 256 | |||
296 | 257 | for i in range(len(spew)): | ||
297 | 258 | spew[i]['lineno'] = i+1 | ||
298 | 259 | spew.append({'lineno': None}) | ||
299 | 260 | spew = spew[self.spew_scroll_pos:] + spew[:self.spew_scroll_pos] | ||
300 | 261 | |||
301 | 262 | for i in range(min(self.spewh - 5, len(spew))): | ||
302 | 263 | if not spew[i]['lineno']: | ||
303 | 264 | continue | ||
304 | 265 | self.spewwin.addnstr(i+3, 0, '%3d' % spew[i]['lineno'], 3) | ||
305 | 266 | self.spewwin.addnstr(i+3, 4, spew[i]['ip']+spew[i]['direction'], 16) | ||
306 | 267 | if spew[i]['uprate'] > 100: | ||
307 | 268 | self.spewwin.addnstr(i+3, 20, '%6.0f KB/s' % (float(spew[i]['uprate']) / 1000), 11) | ||
308 | 269 | self.spewwin.addnstr(i+3, 32, '-----', 5) | ||
309 | 270 | if spew[i]['uinterested'] == 1: | ||
310 | 271 | self.spewwin.addnstr(i+3, 33, 'I', 1) | ||
311 | 272 | if spew[i]['uchoked'] == 1: | ||
312 | 273 | self.spewwin.addnstr(i+3, 35, 'C', 1) | ||
313 | 274 | if spew[i]['downrate'] > 100: | ||
314 | 275 | self.spewwin.addnstr(i+3, 38, '%6.0f KB/s' % (float(spew[i]['downrate']) / 1000), 11) | ||
315 | 276 | self.spewwin.addnstr(i+3, 50, '-------', 7) | ||
316 | 277 | if spew[i]['dinterested'] == 1: | ||
317 | 278 | self.spewwin.addnstr(i+3, 51, 'I', 1) | ||
318 | 279 | if spew[i]['dchoked'] == 1: | ||
319 | 280 | self.spewwin.addnstr(i+3, 53, 'C', 1) | ||
320 | 281 | if spew[i]['snubbed'] == 1: | ||
321 | 282 | self.spewwin.addnstr(i+3, 55, 'S', 1) | ||
322 | 283 | self.spewwin.addnstr(i+3, 58, '%5.1f%%' % (float(int(spew[i]['completed']*1000))/10), 6) | ||
323 | 284 | if spew[i]['speed'] is not None: | ||
324 | 285 | self.spewwin.addnstr(i+3, 64, '%5.0f KB/s' % (float(spew[i]['speed'])/1000), 10) | ||
325 | 286 | |||
326 | 287 | if statistics is not None: | ||
327 | 288 | self.spewwin.addnstr(self.spewh-1, 0, | ||
328 | 289 | 'downloading %d pieces, have %d fragments, %d of %d pieces completed' | ||
329 | 290 | % ( statistics.storage_active, statistics.storage_dirty, | ||
330 | 291 | statistics.storage_numcomplete, | ||
331 | 292 | statistics.storage_totalpieces ), self.speww-1 ) | ||
332 | 293 | |||
333 | 294 | curses.panel.update_panels() | ||
334 | 295 | curses.doupdate() | ||
335 | 296 | dpflag.set() | ||
336 | 297 | |||
337 | 298 | def chooseFile(self, default, size, saveas, dir): | ||
338 | 299 | self.file = default | ||
339 | 300 | self.fileSize = fmtsize(size) | ||
340 | 301 | if saveas == '': | ||
341 | 302 | saveas = default | ||
342 | 303 | self.downloadTo = abspath(saveas) | ||
343 | 304 | return saveas | ||
344 | 305 | |||
345 | 306 | def run(scrwin, errlist, params): | ||
346 | 307 | doneflag = Event() | ||
347 | 308 | d = CursesDisplayer(scrwin, errlist, doneflag) | ||
348 | 309 | try: | ||
349 | 310 | while 1: | ||
350 | 311 | configdir = ConfigDir('downloadcurses') | ||
351 | 312 | defaultsToIgnore = ['responsefile', 'url', 'priority'] | ||
352 | 313 | configdir.setDefaults(defaults,defaultsToIgnore) | ||
353 | 314 | configdefaults = configdir.loadConfig() | ||
354 | 315 | defaults.append(('save_options',0, | ||
355 | 316 | "whether to save the current options as the new default configuration " + | ||
356 | 317 | "(only for btdownloadcurses.py)")) | ||
357 | 318 | try: | ||
358 | 319 | config = parse_params(params, configdefaults) | ||
359 | 320 | except ValueError, e: | ||
360 | 321 | d.error('error: ' + str(e) + '\nrun with no args for parameter explanations') | ||
361 | 322 | break | ||
362 | 323 | if not config: | ||
363 | 324 | d.error(get_usage(defaults, d.fieldw, configdefaults)) | ||
364 | 325 | break | ||
365 | 326 | if config['save_options']: | ||
366 | 327 | configdir.saveConfig(config) | ||
367 | 328 | configdir.deleteOldCacheData(config['expire_cache_data']) | ||
368 | 329 | |||
369 | 330 | myid = createPeerID() | ||
370 | 331 | seed(myid) | ||
371 | 332 | |||
372 | 333 | rawserver = RawServer(doneflag, config['timeout_check_interval'], | ||
373 | 334 | config['timeout'], ipv6_enable = config['ipv6_enabled'], | ||
374 | 335 | failfunc = d.failed, errorfunc = d.error) | ||
375 | 336 | |||
376 | 337 | upnp_type = UPnP_test(config['upnp_nat_access']) | ||
377 | 338 | while True: | ||
378 | 339 | try: | ||
379 | 340 | listen_port = rawserver.find_and_bind(config['minport'], config['maxport'], | ||
380 | 341 | config['bind'], ipv6_socket_style = config['ipv6_binds_v4'], | ||
381 | 342 | upnp = upnp_type, randomizer = config['random_port']) | ||
382 | 343 | break | ||
383 | 344 | except socketerror, e: | ||
384 | 345 | if upnp_type and e == UPnP_ERROR: | ||
385 | 346 | d.error('WARNING: COULD NOT FORWARD VIA UPnP') | ||
386 | 347 | upnp_type = 0 | ||
387 | 348 | continue | ||
388 | 349 | d.error("Couldn't listen - " + str(e)) | ||
389 | 350 | d.failed() | ||
390 | 351 | return | ||
391 | 352 | |||
392 | 353 | response = get_response(config['responsefile'], config['url'], d.error) | ||
393 | 354 | if not response: | ||
394 | 355 | break | ||
395 | 356 | |||
396 | 357 | infohash = sha(bencode(response['info'])).digest() | ||
397 | 358 | |||
398 | 359 | dow = BT1Download(d.display, d.finished, d.error, d.error, doneflag, | ||
399 | 360 | config, response, infohash, myid, rawserver, listen_port, | ||
400 | 361 | configdir) | ||
401 | 362 | |||
402 | 363 | if not dow.saveAs(d.chooseFile): | ||
403 | 364 | break | ||
404 | 365 | |||
405 | 366 | if not dow.initFiles(old_style = True): | ||
406 | 367 | break | ||
407 | 368 | if not dow.startEngine(): | ||
408 | 369 | dow.shutdown() | ||
409 | 370 | break | ||
410 | 371 | dow.startRerequester() | ||
411 | 372 | dow.autoStats() | ||
412 | 373 | |||
413 | 374 | if not dow.am_I_finished(): | ||
414 | 375 | d.display(activity = 'connecting to peers') | ||
415 | 376 | rawserver.listen_forever(dow.getPortHandler()) | ||
416 | 377 | d.display(activity = 'shutting down') | ||
417 | 378 | dow.shutdown() | ||
418 | 379 | break | ||
419 | 380 | |||
420 | 381 | except KeyboardInterrupt: | ||
421 | 382 | # ^C to exit.. | ||
422 | 383 | pass | ||
423 | 384 | try: | ||
424 | 385 | rawserver.shutdown() | ||
425 | 386 | except: | ||
426 | 387 | pass | ||
427 | 388 | if not d.done: | ||
428 | 389 | d.failed() | ||
429 | 390 | |||
430 | 391 | |||
431 | 392 | if __name__ == '__main__': | ||
432 | 393 | if argv[1:] == ['--version']: | ||
433 | 394 | print version | ||
434 | 395 | exit(0) | ||
435 | 396 | if len(argv) <= 1: | ||
436 | 397 | print "Usage: btdownloadcurses.py <global options>\n" | ||
437 | 398 | print get_usage(defaults) | ||
438 | 399 | exit(1) | ||
439 | 400 | |||
440 | 401 | errlist = [] | ||
441 | 402 | curses_wrapper(run, errlist, argv[1:]) | ||
442 | 403 | |||
443 | 404 | if errlist: | ||
444 | 405 | print "These errors occurred during execution:" | ||
445 | 406 | for error in errlist: | ||
446 | 407 | print error | ||
447 | 408 | \ No newline at end of file | 0 | \ No newline at end of file |
448 | 409 | 1 | ||
449 | === removed directory '.pc/05_bttrack_connerr_fix.dpatch' | |||
450 | === removed directory '.pc/05_bttrack_connerr_fix.dpatch/BitTornado' | |||
451 | === removed directory '.pc/05_bttrack_connerr_fix.dpatch/BitTornado/BT1' | |||
452 | === removed file '.pc/05_bttrack_connerr_fix.dpatch/BitTornado/BT1/track.py' | |||
453 | --- .pc/05_bttrack_connerr_fix.dpatch/BitTornado/BT1/track.py 2010-03-21 14:36:30 +0000 | |||
454 | +++ .pc/05_bttrack_connerr_fix.dpatch/BitTornado/BT1/track.py 1970-01-01 00:00:00 +0000 | |||
455 | @@ -1,1137 +0,0 @@ | |||
456 | 1 | # Written by Bram Cohen | ||
457 | 2 | # see LICENSE.txt for license information | ||
458 | 3 | |||
459 | 4 | from BitTornado.parseargs import parseargs, formatDefinitions | ||
460 | 5 | from BitTornado.RawServer import RawServer, autodetect_ipv6, autodetect_socket_style | ||
461 | 6 | from BitTornado.HTTPHandler import HTTPHandler, months, weekdays | ||
462 | 7 | from BitTornado.parsedir import parsedir | ||
463 | 8 | from NatCheck import NatCheck, CHECK_PEER_ID_ENCRYPTED | ||
464 | 9 | from BitTornado.BTcrypto import CRYPTO_OK | ||
465 | 10 | from T2T import T2TList | ||
466 | 11 | from BitTornado.subnetparse import IP_List, ipv6_to_ipv4, to_ipv4, is_valid_ip, is_ipv4 | ||
467 | 12 | from BitTornado.iprangeparse import IP_List as IP_Range_List | ||
468 | 13 | from BitTornado.torrentlistparse import parsetorrentlist | ||
469 | 14 | from threading import Event, Thread | ||
470 | 15 | from BitTornado.bencode import bencode, bdecode, Bencached | ||
471 | 16 | from BitTornado.zurllib import urlopen, quote, unquote | ||
472 | 17 | from Filter import Filter | ||
473 | 18 | from urlparse import urlparse | ||
474 | 19 | from os import rename, getpid | ||
475 | 20 | from os.path import exists, isfile | ||
476 | 21 | from cStringIO import StringIO | ||
477 | 22 | from traceback import print_exc | ||
478 | 23 | from time import time, gmtime, strftime, localtime | ||
479 | 24 | from BitTornado.clock import clock | ||
480 | 25 | from random import shuffle, seed, randrange | ||
481 | 26 | from sha import sha | ||
482 | 27 | from types import StringType, IntType, LongType, ListType, DictType | ||
483 | 28 | from binascii import b2a_hex, a2b_hex, a2b_base64 | ||
484 | 29 | from string import lower | ||
485 | 30 | import sys, os | ||
486 | 31 | import signal | ||
487 | 32 | import re | ||
488 | 33 | import BitTornado.__init__ | ||
489 | 34 | from BitTornado.__init__ import version, createPeerID | ||
490 | 35 | try: | ||
491 | 36 | True | ||
492 | 37 | except: | ||
493 | 38 | True = 1 | ||
494 | 39 | False = 0 | ||
495 | 40 | bool = lambda x: not not x | ||
496 | 41 | |||
497 | 42 | defaults = [ | ||
498 | 43 | ('port', 80, "Port to listen on."), | ||
499 | 44 | ('dfile', None, 'file to store recent downloader info in'), | ||
500 | 45 | ('bind', '', 'comma-separated list of ips/hostnames to bind to locally'), | ||
501 | 46 | # ('ipv6_enabled', autodetect_ipv6(), | ||
502 | 47 | ('ipv6_enabled', 0, | ||
503 | 48 | 'allow the client to connect to peers via IPv6'), | ||
504 | 49 | ('ipv6_binds_v4', autodetect_socket_style(), | ||
505 | 50 | 'set if an IPv6 server socket will also field IPv4 connections'), | ||
506 | 51 | ('socket_timeout', 15, 'timeout for closing connections'), | ||
507 | 52 | ('save_dfile_interval', 5 * 60, 'seconds between saving dfile'), | ||
508 | 53 | ('timeout_downloaders_interval', 45 * 60, 'seconds between expiring downloaders'), | ||
509 | 54 | ('reannounce_interval', 30 * 60, 'seconds downloaders should wait between reannouncements'), | ||
510 | 55 | ('response_size', 50, 'number of peers to send in an info message'), | ||
511 | 56 | ('timeout_check_interval', 5, | ||
512 | 57 | 'time to wait between checking if any connections have timed out'), | ||
513 | 58 | ('nat_check', 3, | ||
514 | 59 | "how many times to check if a downloader is behind a NAT (0 = don't check)"), | ||
515 | 60 | ('log_nat_checks', 0, | ||
516 | 61 | "whether to add entries to the log for nat-check results"), | ||
517 | 62 | ('min_time_between_log_flushes', 3.0, | ||
518 | 63 | 'minimum time it must have been since the last flush to do another one'), | ||
519 | 64 | ('min_time_between_cache_refreshes', 600.0, | ||
520 | 65 | 'minimum time in seconds before a cache is considered stale and is flushed'), | ||
521 | 66 | ('allowed_dir', '', 'only allow downloads for .torrents in this dir'), | ||
522 | 67 | ('allowed_list', '', 'only allow downloads for hashes in this list (hex format, one per line)'), | ||
523 | 68 | ('allowed_controls', 0, 'allow special keys in torrents in the allowed_dir to affect tracker access'), | ||
524 | 69 | ('multitracker_enabled', 0, 'whether to enable multitracker operation'), | ||
525 | 70 | ('multitracker_allowed', 'autodetect', 'whether to allow incoming tracker announces (can be none, autodetect or all)'), | ||
526 | 71 | ('multitracker_reannounce_interval', 2 * 60, 'seconds between outgoing tracker announces'), | ||
527 | 72 | ('multitracker_maxpeers', 20, 'number of peers to get in a tracker announce'), | ||
528 | 73 | ('aggregate_forward', '', 'format: <url>[,<password>] - if set, forwards all non-multitracker to this url with this optional password'), | ||
529 | 74 | ('aggregator', '0', 'whether to act as a data aggregator rather than a tracker. If enabled, may be 1, or <password>; ' + | ||
530 | 75 | 'if password is set, then an incoming password is required for access'), | ||
531 | 76 | ('hupmonitor', 0, 'whether to reopen the log file upon receipt of HUP signal'), | ||
532 | 77 | ('http_timeout', 60, | ||
533 | 78 | 'number of seconds to wait before assuming that an http connection has timed out'), | ||
534 | 79 | ('parse_dir_interval', 60, 'seconds between reloading of allowed_dir or allowed_file ' + | ||
535 | 80 | 'and allowed_ips and banned_ips lists'), | ||
536 | 81 | ('show_infopage', 1, "whether to display an info page when the tracker's root dir is loaded"), | ||
537 | 82 | ('infopage_redirect', '', 'a URL to redirect the info page to'), | ||
538 | 83 | ('show_names', 1, 'whether to display names from allowed dir'), | ||
539 | 84 | ('favicon', '', 'file containing x-icon data to return when browser requests favicon.ico'), | ||
540 | 85 | ('allowed_ips', '', 'only allow connections from IPs specified in the given file; '+ | ||
541 | 86 | 'file contains subnet data in the format: aa.bb.cc.dd/len'), | ||
542 | 87 | ('banned_ips', '', "don't allow connections from IPs specified in the given file; "+ | ||
543 | 88 | 'file contains IP range data in the format: xxx:xxx:ip1-ip2'), | ||
544 | 89 | ('only_local_override_ip', 2, "ignore the ip GET parameter from machines which aren't on local network IPs " + | ||
545 | 90 | "(0 = never, 1 = always, 2 = ignore if NAT checking is not enabled)"), | ||
546 | 91 | ('logfile', '', 'file to write the tracker logs, use - for stdout (default)'), | ||
547 | 92 | ('allow_get', 0, 'use with allowed_dir; adds a /file?hash={hash} url that allows users to download the torrent file'), | ||
548 | 93 | ('keep_dead', 0, 'keep dead torrents after they expire (so they still show up on your /scrape and web page)'), | ||
549 | 94 | ('scrape_allowed', 'full', 'scrape access allowed (can be none, specific or full)'), | ||
550 | 95 | ('dedicated_seed_id', '', 'allows tracker to monitor dedicated seed(s) and flag torrents as seeded'), | ||
551 | 96 | ('compact_reqd', 1, "only allow peers that accept a compact response"), | ||
552 | 97 | ] | ||
553 | 98 | |||
554 | 99 | def statefiletemplate(x): | ||
555 | 100 | if type(x) != DictType: | ||
556 | 101 | raise ValueError | ||
557 | 102 | for cname, cinfo in x.items(): | ||
558 | 103 | if cname == 'peers': | ||
559 | 104 | for y in cinfo.values(): # The 'peers' key is a dictionary of SHA hashes (torrent ids) | ||
560 | 105 | if type(y) != DictType: # ... for the active torrents, and each is a dictionary | ||
561 | 106 | raise ValueError | ||
562 | 107 | for id, info in y.items(): # ... of client ids interested in that torrent | ||
563 | 108 | if (len(id) != 20): | ||
564 | 109 | raise ValueError | ||
565 | 110 | if type(info) != DictType: # ... each of which is also a dictionary | ||
566 | 111 | raise ValueError # ... which has an IP, a Port, and a Bytes Left count for that client for that torrent | ||
567 | 112 | if type(info.get('ip', '')) != StringType: | ||
568 | 113 | raise ValueError | ||
569 | 114 | port = info.get('port') | ||
570 | 115 | if type(port) not in (IntType,LongType) or port < 0: | ||
571 | 116 | raise ValueError | ||
572 | 117 | left = info.get('left') | ||
573 | 118 | if type(left) not in (IntType,LongType) or left < 0: | ||
574 | 119 | raise ValueError | ||
575 | 120 | if type(info.get('supportcrypto')) not in (IntType,LongType): | ||
576 | 121 | raise ValueError | ||
577 | 122 | if type(info.get('requirecrypto')) not in (IntType,LongType): | ||
578 | 123 | raise ValueError | ||
579 | 124 | elif cname == 'completed': | ||
580 | 125 | if (type(cinfo) != DictType): # The 'completed' key is a dictionary of SHA hashes (torrent ids) | ||
581 | 126 | raise ValueError # ... for keeping track of the total completions per torrent | ||
582 | 127 | for y in cinfo.values(): # ... each torrent has an integer value | ||
583 | 128 | if type(y) not in (IntType,LongType): | ||
584 | 129 | raise ValueError # ... for the number of reported completions for that torrent | ||
585 | 130 | elif cname == 'allowed': | ||
586 | 131 | if (type(cinfo) != DictType): # a list of info_hashes and included data | ||
587 | 132 | raise ValueError | ||
588 | 133 | if x.has_key('allowed_dir_files'): | ||
589 | 134 | adlist = [z[1] for z in x['allowed_dir_files'].values()] | ||
590 | 135 | for y in cinfo.keys(): # and each should have a corresponding key here | ||
591 | 136 | if not y in adlist: | ||
592 | 137 | raise ValueError | ||
593 | 138 | elif cname == 'allowed_dir_files': | ||
594 | 139 | if (type(cinfo) != DictType): # a list of files, their attributes and info hashes | ||
595 | 140 | raise ValueError | ||
596 | 141 | dirkeys = {} | ||
597 | 142 | for y in cinfo.values(): # each entry should have a corresponding info_hash | ||
598 | 143 | if not y[1]: | ||
599 | 144 | continue | ||
600 | 145 | if not x['allowed'].has_key(y[1]): | ||
601 | 146 | raise ValueError | ||
602 | 147 | if dirkeys.has_key(y[1]): # and each should have a unique info_hash | ||
603 | 148 | raise ValueError | ||
604 | 149 | dirkeys[y[1]] = 1 | ||
605 | 150 | |||
606 | 151 | |||
607 | 152 | alas = 'your file may exist elsewhere in the universe\nbut alas, not here\n' | ||
608 | 153 | |||
609 | 154 | local_IPs = IP_List() | ||
610 | 155 | local_IPs.set_intranet_addresses() | ||
611 | 156 | |||
612 | 157 | |||
613 | 158 | def isotime(secs = None): | ||
614 | 159 | if secs == None: | ||
615 | 160 | secs = time() | ||
616 | 161 | return strftime('%Y-%m-%d %H:%M UTC', gmtime(secs)) | ||
617 | 162 | |||
618 | 163 | http_via_filter = re.compile(' for ([0-9.]+)\Z') | ||
619 | 164 | |||
620 | 165 | def _get_forwarded_ip(headers): | ||
621 | 166 | header = headers.get('x-forwarded-for') | ||
622 | 167 | if header: | ||
623 | 168 | try: | ||
624 | 169 | x,y = header.split(',') | ||
625 | 170 | except: | ||
626 | 171 | return header | ||
627 | 172 | if is_valid_ip(x) and not local_IPs.includes(x): | ||
628 | 173 | return x | ||
629 | 174 | return y | ||
630 | 175 | header = headers.get('client-ip') | ||
631 | 176 | if header: | ||
632 | 177 | return header | ||
633 | 178 | header = headers.get('via') | ||
634 | 179 | if header: | ||
635 | 180 | x = http_via_filter.search(header) | ||
636 | 181 | try: | ||
637 | 182 | return x.group(1) | ||
638 | 183 | except: | ||
639 | 184 | pass | ||
640 | 185 | header = headers.get('from') | ||
641 | 186 | #if header: | ||
642 | 187 | # return header | ||
643 | 188 | #return None | ||
644 | 189 | return header | ||
645 | 190 | |||
646 | 191 | def get_forwarded_ip(headers): | ||
647 | 192 | x = _get_forwarded_ip(headers) | ||
648 | 193 | if not is_valid_ip(x) or local_IPs.includes(x): | ||
649 | 194 | return None | ||
650 | 195 | return x | ||
651 | 196 | |||
652 | 197 | def compact_peer_info(ip, port): | ||
653 | 198 | try: | ||
654 | 199 | s = ( ''.join([chr(int(i)) for i in ip.split('.')]) | ||
655 | 200 | + chr((port & 0xFF00) >> 8) + chr(port & 0xFF) ) | ||
656 | 201 | if len(s) != 6: | ||
657 | 202 | raise ValueError | ||
658 | 203 | except: | ||
659 | 204 | s = '' # not a valid IP, must be a domain name | ||
660 | 205 | return s | ||
661 | 206 | |||
662 | 207 | class Tracker: | ||
663 | 208 | def __init__(self, config, rawserver): | ||
664 | 209 | self.config = config | ||
665 | 210 | self.response_size = config['response_size'] | ||
666 | 211 | self.dfile = config['dfile'] | ||
667 | 212 | self.natcheck = config['nat_check'] | ||
668 | 213 | favicon = config['favicon'] | ||
669 | 214 | self.parse_dir_interval = config['parse_dir_interval'] | ||
670 | 215 | self.favicon = None | ||
671 | 216 | if favicon: | ||
672 | 217 | try: | ||
673 | 218 | h = open(favicon,'r') | ||
674 | 219 | self.favicon = h.read() | ||
675 | 220 | h.close() | ||
676 | 221 | except: | ||
677 | 222 | print "**warning** specified favicon file -- %s -- does not exist." % favicon | ||
678 | 223 | self.rawserver = rawserver | ||
679 | 224 | self.cached = {} # format: infohash: [[time1, l1, s1], [time2, l2, s2], ...] | ||
680 | 225 | self.cached_t = {} # format: infohash: [time, cache] | ||
681 | 226 | self.times = {} | ||
682 | 227 | self.state = {} | ||
683 | 228 | self.seedcount = {} | ||
684 | 229 | |||
685 | 230 | self.allowed_IPs = None | ||
686 | 231 | self.banned_IPs = None | ||
687 | 232 | if config['allowed_ips'] or config['banned_ips']: | ||
688 | 233 | self.allowed_ip_mtime = 0 | ||
689 | 234 | self.banned_ip_mtime = 0 | ||
690 | 235 | self.read_ip_lists() | ||
691 | 236 | |||
692 | 237 | self.only_local_override_ip = config['only_local_override_ip'] | ||
693 | 238 | if self.only_local_override_ip == 2: | ||
694 | 239 | self.only_local_override_ip = not config['nat_check'] | ||
695 | 240 | |||
696 | 241 | if CHECK_PEER_ID_ENCRYPTED and not CRYPTO_OK: | ||
697 | 242 | print ('**warning** crypto library not installed,' + | ||
698 | 243 | ' cannot completely verify encrypted peers') | ||
699 | 244 | |||
700 | 245 | if exists(self.dfile): | ||
701 | 246 | try: | ||
702 | 247 | h = open(self.dfile, 'rb') | ||
703 | 248 | ds = h.read() | ||
704 | 249 | h.close() | ||
705 | 250 | tempstate = bdecode(ds) | ||
706 | 251 | if not tempstate.has_key('peers'): | ||
707 | 252 | tempstate = {'peers': tempstate} | ||
708 | 253 | statefiletemplate(tempstate) | ||
709 | 254 | self.state = tempstate | ||
710 | 255 | except: | ||
711 | 256 | print '**warning** statefile '+self.dfile+' corrupt; resetting' | ||
712 | 257 | self.downloads = self.state.setdefault('peers', {}) | ||
713 | 258 | self.completed = self.state.setdefault('completed', {}) | ||
714 | 259 | |||
715 | 260 | self.becache = {} | ||
716 | 261 | ''' format: infohash: [[l0, s0], [l1, s1], ...] | ||
717 | 262 | l0,s0 = compact, not requirecrypto=1 | ||
718 | 263 | l1,s1 = compact, only supportcrypto=1 | ||
719 | 264 | l2,s2 = [compact, crypto_flag], all peers | ||
720 | 265 | if --compact_reqd 0: | ||
721 | 266 | l3,s3 = [ip,port,id] | ||
722 | 267 | l4,l4 = [ip,port] nopeerid | ||
723 | 268 | ''' | ||
724 | 269 | if config['compact_reqd']: | ||
725 | 270 | x = 3 | ||
726 | 271 | else: | ||
727 | 272 | x = 5 | ||
728 | 273 | self.cache_default = [({},{}) for i in xrange(x)] | ||
729 | 274 | for infohash, ds in self.downloads.items(): | ||
730 | 275 | self.seedcount[infohash] = 0 | ||
731 | 276 | for x,y in ds.items(): | ||
732 | 277 | ip = y['ip'] | ||
733 | 278 | if ( (self.allowed_IPs and not self.allowed_IPs.includes(ip)) | ||
734 | 279 | or (self.banned_IPs and self.banned_IPs.includes(ip)) ): | ||
735 | 280 | del ds[x] | ||
736 | 281 | continue | ||
737 | 282 | if not y['left']: | ||
738 | 283 | self.seedcount[infohash] += 1 | ||
739 | 284 | if y.get('nat',-1): | ||
740 | 285 | continue | ||
741 | 286 | gip = y.get('given_ip') | ||
742 | 287 | if is_valid_ip(gip) and ( | ||
743 | 288 | not self.only_local_override_ip or local_IPs.includes(ip) ): | ||
744 | 289 | ip = gip | ||
745 | 290 | self.natcheckOK(infohash,x,ip,y['port'],y) | ||
746 | 291 | |||
747 | 292 | for x in self.downloads.keys(): | ||
748 | 293 | self.times[x] = {} | ||
749 | 294 | for y in self.downloads[x].keys(): | ||
750 | 295 | self.times[x][y] = 0 | ||
751 | 296 | |||
752 | 297 | self.trackerid = createPeerID('-T-') | ||
753 | 298 | seed(self.trackerid) | ||
754 | 299 | |||
755 | 300 | self.reannounce_interval = config['reannounce_interval'] | ||
756 | 301 | self.save_dfile_interval = config['save_dfile_interval'] | ||
757 | 302 | self.show_names = config['show_names'] | ||
758 | 303 | rawserver.add_task(self.save_state, self.save_dfile_interval) | ||
759 | 304 | self.prevtime = clock() | ||
760 | 305 | self.timeout_downloaders_interval = config['timeout_downloaders_interval'] | ||
761 | 306 | rawserver.add_task(self.expire_downloaders, self.timeout_downloaders_interval) | ||
762 | 307 | self.logfile = None | ||
763 | 308 | self.log = None | ||
764 | 309 | if (config['logfile']) and (config['logfile'] != '-'): | ||
765 | 310 | try: | ||
766 | 311 | self.logfile = config['logfile'] | ||
767 | 312 | self.log = open(self.logfile,'a') | ||
768 | 313 | sys.stdout = self.log | ||
769 | 314 | print "# Log Started: ", isotime() | ||
770 | 315 | except: | ||
771 | 316 | print "**warning** could not redirect stdout to log file: ", sys.exc_info()[0] | ||
772 | 317 | |||
773 | 318 | if config['hupmonitor']: | ||
774 | 319 | def huphandler(signum, frame, self = self): | ||
775 | 320 | try: | ||
776 | 321 | self.log.close () | ||
777 | 322 | self.log = open(self.logfile,'a') | ||
778 | 323 | sys.stdout = self.log | ||
779 | 324 | print "# Log reopened: ", isotime() | ||
780 | 325 | except: | ||
781 | 326 | print "**warning** could not reopen logfile" | ||
782 | 327 | |||
783 | 328 | signal.signal(signal.SIGHUP, huphandler) | ||
784 | 329 | |||
785 | 330 | self.allow_get = config['allow_get'] | ||
786 | 331 | |||
787 | 332 | self.t2tlist = T2TList(config['multitracker_enabled'], self.trackerid, | ||
788 | 333 | config['multitracker_reannounce_interval'], | ||
789 | 334 | config['multitracker_maxpeers'], config['http_timeout'], | ||
790 | 335 | self.rawserver) | ||
791 | 336 | |||
792 | 337 | if config['allowed_list']: | ||
793 | 338 | if config['allowed_dir']: | ||
794 | 339 | print '**warning** allowed_dir and allowed_list options cannot be used together' | ||
795 | 340 | print '**warning** disregarding allowed_dir' | ||
796 | 341 | config['allowed_dir'] = '' | ||
797 | 342 | self.allowed = self.state.setdefault('allowed_list',{}) | ||
798 | 343 | self.allowed_list_mtime = 0 | ||
799 | 344 | self.parse_allowed() | ||
800 | 345 | self.remove_from_state('allowed','allowed_dir_files') | ||
801 | 346 | if config['multitracker_allowed'] == 'autodetect': | ||
802 | 347 | config['multitracker_allowed'] = 'none' | ||
803 | 348 | config['allowed_controls'] = 0 | ||
804 | 349 | |||
805 | 350 | elif config['allowed_dir']: | ||
806 | 351 | self.allowed = self.state.setdefault('allowed',{}) | ||
807 | 352 | self.allowed_dir_files = self.state.setdefault('allowed_dir_files',{}) | ||
808 | 353 | self.allowed_dir_blocked = {} | ||
809 | 354 | self.parse_allowed() | ||
810 | 355 | self.remove_from_state('allowed_list') | ||
811 | 356 | |||
812 | 357 | else: | ||
813 | 358 | self.allowed = None | ||
814 | 359 | self.remove_from_state('allowed','allowed_dir_files', 'allowed_list') | ||
815 | 360 | if config['multitracker_allowed'] == 'autodetect': | ||
816 | 361 | config['multitracker_allowed'] = 'none' | ||
817 | 362 | config['allowed_controls'] = 0 | ||
818 | 363 | |||
819 | 364 | self.uq_broken = unquote('+') != ' ' | ||
820 | 365 | self.keep_dead = config['keep_dead'] | ||
821 | 366 | self.Filter = Filter(rawserver.add_task) | ||
822 | 367 | |||
823 | 368 | aggregator = config['aggregator'] | ||
824 | 369 | if aggregator == '0': | ||
825 | 370 | self.is_aggregator = False | ||
826 | 371 | self.aggregator_key = None | ||
827 | 372 | else: | ||
828 | 373 | self.is_aggregator = True | ||
829 | 374 | if aggregator == '1': | ||
830 | 375 | self.aggregator_key = None | ||
831 | 376 | else: | ||
832 | 377 | self.aggregator_key = aggregator | ||
833 | 378 | self.natcheck = False | ||
834 | 379 | |||
835 | 380 | send = config['aggregate_forward'] | ||
836 | 381 | if not send: | ||
837 | 382 | self.aggregate_forward = None | ||
838 | 383 | else: | ||
839 | 384 | try: | ||
840 | 385 | self.aggregate_forward, self.aggregate_password = send.split(',') | ||
841 | 386 | except: | ||
842 | 387 | self.aggregate_forward = send | ||
843 | 388 | self.aggregate_password = None | ||
844 | 389 | |||
845 | 390 | self.dedicated_seed_id = config['dedicated_seed_id'] | ||
846 | 391 | self.is_seeded = {} | ||
847 | 392 | |||
848 | 393 | self.cachetime = 0 | ||
849 | 394 | self.cachetimeupdate() | ||
850 | 395 | |||
851 | 396 | def cachetimeupdate(self): | ||
852 | 397 | self.cachetime += 1 # raw clock, but more efficient for cache | ||
853 | 398 | self.rawserver.add_task(self.cachetimeupdate,1) | ||
854 | 399 | |||
855 | 400 | def aggregate_senddata(self, query): | ||
856 | 401 | url = self.aggregate_forward+'?'+query | ||
857 | 402 | if self.aggregate_password is not None: | ||
858 | 403 | url += '&password='+self.aggregate_password | ||
859 | 404 | rq = Thread(target = self._aggregate_senddata, args = [url]) | ||
860 | 405 | rq.setDaemon(False) | ||
861 | 406 | rq.start() | ||
862 | 407 | |||
863 | 408 | def _aggregate_senddata(self, url): # just send, don't attempt to error check, | ||
864 | 409 | try: # discard any returned data | ||
865 | 410 | h = urlopen(url) | ||
866 | 411 | h.read() | ||
867 | 412 | h.close() | ||
868 | 413 | except: | ||
869 | 414 | return | ||
870 | 415 | |||
871 | 416 | |||
872 | 417 | def get_infopage(self): | ||
873 | 418 | try: | ||
874 | 419 | if not self.config['show_infopage']: | ||
875 | 420 | return (404, 'Not Found', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, alas) | ||
876 | 421 | red = self.config['infopage_redirect'] | ||
877 | 422 | if red: | ||
878 | 423 | return (302, 'Found', {'Content-Type': 'text/html', 'Location': red}, | ||
879 | 424 | '<A HREF="'+red+'">Click Here</A>') | ||
880 | 425 | |||
881 | 426 | s = StringIO() | ||
882 | 427 | s.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' \ | ||
883 | 428 | '<html><head><title>BitTorrent download info</title>\n') | ||
884 | 429 | if self.favicon is not None: | ||
885 | 430 | s.write('<link rel="shortcut icon" href="/favicon.ico">\n') | ||
886 | 431 | s.write('</head>\n<body>\n' \ | ||
887 | 432 | '<h3>BitTorrent download info</h3>\n'\ | ||
888 | 433 | '<ul>\n' | ||
889 | 434 | '<li><strong>tracker version:</strong> %s</li>\n' \ | ||
890 | 435 | '<li><strong>server time:</strong> %s</li>\n' \ | ||
891 | 436 | '</ul>\n' % (version, isotime())) | ||
892 | 437 | if self.config['allowed_dir']: | ||
893 | 438 | if self.show_names: | ||
894 | 439 | names = [ (self.allowed[hash]['name'],hash) | ||
895 | 440 | for hash in self.allowed.keys() ] | ||
896 | 441 | else: | ||
897 | 442 | names = [ (None,hash) | ||
898 | 443 | for hash in self.allowed.keys() ] | ||
899 | 444 | else: | ||
900 | 445 | names = [ (None,hash) for hash in self.downloads.keys() ] | ||
901 | 446 | if not names: | ||
902 | 447 | s.write('<p>not tracking any files yet...</p>\n') | ||
903 | 448 | else: | ||
904 | 449 | names.sort() | ||
905 | 450 | tn = 0 | ||
906 | 451 | tc = 0 | ||
907 | 452 | td = 0 | ||
908 | 453 | tt = 0 # Total transferred | ||
909 | 454 | ts = 0 # Total size | ||
910 | 455 | nf = 0 # Number of files displayed | ||
911 | 456 | if self.config['allowed_dir'] and self.show_names: | ||
912 | 457 | s.write('<table summary="files" border="1">\n' \ | ||
913 | 458 | '<tr><th>info hash</th><th>torrent name</th><th align="right">size</th><th align="right">complete</th><th align="right">downloading</th><th align="right">downloaded</th><th align="right">transferred</th></tr>\n') | ||
914 | 459 | else: | ||
915 | 460 | s.write('<table summary="files">\n' \ | ||
916 | 461 | '<tr><th>info hash</th><th align="right">complete</th><th align="right">downloading</th><th align="right">downloaded</th></tr>\n') | ||
917 | 462 | for name,hash in names: | ||
918 | 463 | l = self.downloads[hash] | ||
919 | 464 | n = self.completed.get(hash, 0) | ||
920 | 465 | tn = tn + n | ||
921 | 466 | c = self.seedcount[hash] | ||
922 | 467 | tc = tc + c | ||
923 | 468 | d = len(l) - c | ||
924 | 469 | td = td + d | ||
925 | 470 | if self.config['allowed_dir'] and self.show_names: | ||
926 | 471 | if self.allowed.has_key(hash): | ||
927 | 472 | nf = nf + 1 | ||
928 | 473 | sz = self.allowed[hash]['length'] # size | ||
929 | 474 | ts = ts + sz | ||
930 | 475 | szt = sz * n # Transferred for this torrent | ||
931 | 476 | tt = tt + szt | ||
932 | 477 | if self.allow_get == 1: | ||
933 | 478 | linkname = '<a href="/file?info_hash=' + quote(hash) + '">' + name + '</a>' | ||
934 | 479 | else: | ||
935 | 480 | linkname = name | ||
936 | 481 | s.write('<tr><td><code>%s</code></td><td>%s</td><td align="right">%s</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i</td><td align="right">%s</td></tr>\n' \ | ||
937 | 482 | % (b2a_hex(hash), linkname, size_format(sz), c, d, n, size_format(szt))) | ||
938 | 483 | else: | ||
939 | 484 | s.write('<tr><td><code>%s</code></td><td align="right"><code>%i</code></td><td align="right"><code>%i</code></td><td align="right"><code>%i</code></td></tr>\n' \ | ||
940 | 485 | % (b2a_hex(hash), c, d, n)) | ||
941 | 486 | if self.config['allowed_dir'] and self.show_names: | ||
942 | 487 | s.write('<tr><td align="right" colspan="2">%i files</td><td align="right">%s</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i</td><td align="right">%s</td></tr>\n' | ||
943 | 488 | % (nf, size_format(ts), tc, td, tn, size_format(tt))) | ||
944 | 489 | else: | ||
945 | 490 | s.write('<tr><td align="right">%i files</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i</td></tr>\n' | ||
946 | 491 | % (nf, tc, td, tn)) | ||
947 | 492 | s.write('</table>\n' \ | ||
948 | 493 | '<ul>\n' \ | ||
949 | 494 | '<li><em>info hash:</em> SHA1 hash of the "info" section of the metainfo (*.torrent)</li>\n' \ | ||
950 | 495 | '<li><em>complete:</em> number of connected clients with the complete file</li>\n' \ | ||
951 | 496 | '<li><em>downloading:</em> number of connected clients still downloading</li>\n' \ | ||
952 | 497 | '<li><em>downloaded:</em> reported complete downloads</li>\n' \ | ||
953 | 498 | '<li><em>transferred:</em> torrent size * total downloaded (does not include partial transfers)</li>\n' \ | ||
954 | 499 | '</ul>\n') | ||
955 | 500 | |||
956 | 501 | s.write('</body>\n' \ | ||
957 | 502 | '</html>\n') | ||
958 | 503 | return (200, 'OK', {'Content-Type': 'text/html; charset=iso-8859-1'}, s.getvalue()) | ||
959 | 504 | except: | ||
960 | 505 | print_exc() | ||
961 | 506 | return (500, 'Internal Server Error', {'Content-Type': 'text/html; charset=iso-8859-1'}, 'Server Error') | ||
962 | 507 | |||
963 | 508 | |||
964 | 509 | def scrapedata(self, hash, return_name = True): | ||
965 | 510 | l = self.downloads[hash] | ||
966 | 511 | n = self.completed.get(hash, 0) | ||
967 | 512 | c = self.seedcount[hash] | ||
968 | 513 | d = len(l) - c | ||
969 | 514 | f = {'complete': c, 'incomplete': d, 'downloaded': n} | ||
970 | 515 | if return_name and self.show_names and self.config['allowed_dir']: | ||
971 | 516 | f['name'] = self.allowed[hash]['name'] | ||
972 | 517 | return (f) | ||
973 | 518 | |||
974 | 519 | def get_scrape(self, paramslist): | ||
975 | 520 | fs = {} | ||
976 | 521 | if paramslist.has_key('info_hash'): | ||
977 | 522 | if self.config['scrape_allowed'] not in ['specific', 'full']: | ||
978 | 523 | return (400, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, | ||
979 | 524 | bencode({'failure reason': | ||
980 | 525 | 'specific scrape function is not available with this tracker.'})) | ||
981 | 526 | for hash in paramslist['info_hash']: | ||
982 | 527 | if self.allowed is not None: | ||
983 | 528 | if self.allowed.has_key(hash): | ||
984 | 529 | fs[hash] = self.scrapedata(hash) | ||
985 | 530 | else: | ||
986 | 531 | if self.downloads.has_key(hash): | ||
987 | 532 | fs[hash] = self.scrapedata(hash) | ||
988 | 533 | else: | ||
989 | 534 | if self.config['scrape_allowed'] != 'full': | ||
990 | 535 | return (400, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, | ||
991 | 536 | bencode({'failure reason': | ||
992 | 537 | 'full scrape function is not available with this tracker.'})) | ||
993 | 538 | if self.allowed is not None: | ||
994 | 539 | keys = self.allowed.keys() | ||
995 | 540 | else: | ||
996 | 541 | keys = self.downloads.keys() | ||
997 | 542 | for hash in keys: | ||
998 | 543 | fs[hash] = self.scrapedata(hash) | ||
999 | 544 | |||
1000 | 545 | return (200, 'OK', {'Content-Type': 'text/plain'}, bencode({'files': fs})) | ||
1001 | 546 | |||
1002 | 547 | |||
1003 | 548 | def get_file(self, hash): | ||
1004 | 549 | if not self.allow_get: | ||
1005 | 550 | return (400, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, | ||
1006 | 551 | 'get function is not available with this tracker.') | ||
1007 | 552 | if not self.allowed.has_key(hash): | ||
1008 | 553 | return (404, 'Not Found', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, alas) | ||
1009 | 554 | fname = self.allowed[hash]['file'] | ||
1010 | 555 | fpath = self.allowed[hash]['path'] | ||
1011 | 556 | return (200, 'OK', {'Content-Type': 'application/x-bittorrent', | ||
1012 | 557 | 'Content-Disposition': 'attachment; filename=' + fname}, | ||
1013 | 558 | open(fpath, 'rb').read()) | ||
1014 | 559 | |||
1015 | 560 | |||
1016 | 561 | def check_allowed(self, infohash, paramslist): | ||
1017 | 562 | if ( self.aggregator_key is not None | ||
1018 | 563 | and not ( paramslist.has_key('password') | ||
1019 | 564 | and paramslist['password'][0] == self.aggregator_key ) ): | ||
1020 | 565 | return (200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, | ||
1021 | 566 | bencode({'failure reason': | ||
1022 | 567 | 'Requested download is not authorized for use with this tracker.'})) | ||
1023 | 568 | |||
1024 | 569 | if self.allowed is not None: | ||
1025 | 570 | if not self.allowed.has_key(infohash): | ||
1026 | 571 | return (200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, | ||
1027 | 572 | bencode({'failure reason': | ||
1028 | 573 | 'Requested download is not authorized for use with this tracker.'})) | ||
1029 | 574 | if self.config['allowed_controls']: | ||
1030 | 575 | if self.allowed[infohash].has_key('failure reason'): | ||
1031 | 576 | return (200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, | ||
1032 | 577 | bencode({'failure reason': self.allowed[infohash]['failure reason']})) | ||
1033 | 578 | |||
1034 | 579 | if paramslist.has_key('tracker'): | ||
1035 | 580 | if ( self.config['multitracker_allowed'] == 'none' or # turned off | ||
1036 | 581 | paramslist['peer_id'][0] == self.trackerid ): # oops! contacted myself | ||
1037 | 582 | return (200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, | ||
1038 | 583 | bencode({'failure reason': 'disallowed'})) | ||
1039 | 584 | |||
1040 | 585 | if ( self.config['multitracker_allowed'] == 'autodetect' | ||
1041 | 586 | and not self.allowed[infohash].has_key('announce-list') ): | ||
1042 | 587 | return (200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, | ||
1043 | 588 | bencode({'failure reason': | ||
1044 | 589 | 'Requested download is not authorized for multitracker use.'})) | ||
1045 | 590 | |||
1046 | 591 | return None | ||
1047 | 592 | |||
1048 | 593 | |||
1049 | 594 | def add_data(self, infohash, event, ip, paramslist): | ||
1050 | 595 | peers = self.downloads.setdefault(infohash, {}) | ||
1051 | 596 | ts = self.times.setdefault(infohash, {}) | ||
1052 | 597 | self.completed.setdefault(infohash, 0) | ||
1053 | 598 | self.seedcount.setdefault(infohash, 0) | ||
1054 | 599 | |||
1055 | 600 | def params(key, default = None, l = paramslist): | ||
1056 | 601 | if l.has_key(key): | ||
1057 | 602 | return l[key][0] | ||
1058 | 603 | return default | ||
1059 | 604 | |||
1060 | 605 | myid = params('peer_id','') | ||
1061 | 606 | if len(myid) != 20: | ||
1062 | 607 | raise ValueError, 'id not of length 20' | ||
1063 | 608 | if event not in ['started', 'completed', 'stopped', 'snooped', None]: | ||
1064 | 609 | raise ValueError, 'invalid event' | ||
1065 | 610 | port = params('cryptoport') | ||
1066 | 611 | if port is None: | ||
1067 | 612 | port = params('port','') | ||
1068 | 613 | port = long(port) | ||
1069 | 614 | if port < 0 or port > 65535: | ||
1070 | 615 | raise ValueError, 'invalid port' | ||
1071 | 616 | left = long(params('left','')) | ||
1072 | 617 | if left < 0: | ||
1073 | 618 | raise ValueError, 'invalid amount left' | ||
1074 | 619 | uploaded = long(params('uploaded','')) | ||
1075 | 620 | downloaded = long(params('downloaded','')) | ||
1076 | 621 | if params('supportcrypto'): | ||
1077 | 622 | supportcrypto = 1 | ||
1078 | 623 | try: | ||
1079 | 624 | s = int(params['requirecrypto']) | ||
1080 | 625 | chr(s) | ||
1081 | 626 | except: | ||
1082 | 627 | s = 0 | ||
1083 | 628 | requirecrypto = s | ||
1084 | 629 | else: | ||
1085 | 630 | supportcrypto = 0 | ||
1086 | 631 | requirecrypto = 0 | ||
1087 | 632 | |||
1088 | 633 | peer = peers.get(myid) | ||
1089 | 634 | islocal = local_IPs.includes(ip) | ||
1090 | 635 | mykey = params('key') | ||
1091 | 636 | if peer: | ||
1092 | 637 | auth = peer.get('key',-1) == mykey or peer.get('ip') == ip | ||
1093 | 638 | |||
1094 | 639 | gip = params('ip') | ||
1095 | 640 | if is_valid_ip(gip) and (islocal or not self.only_local_override_ip): | ||
1096 | 641 | ip1 = gip | ||
1097 | 642 | else: | ||
1098 | 643 | ip1 = ip | ||
1099 | 644 | |||
1100 | 645 | if params('numwant') is not None: | ||
1101 | 646 | rsize = min(int(params('numwant')),self.response_size) | ||
1102 | 647 | else: | ||
1103 | 648 | rsize = self.response_size | ||
1104 | 649 | |||
1105 | 650 | if event == 'stopped': | ||
1106 | 651 | if peer: | ||
1107 | 652 | if auth: | ||
1108 | 653 | self.delete_peer(infohash,myid) | ||
1109 | 654 | |||
1110 | 655 | elif not peer: | ||
1111 | 656 | ts[myid] = clock() | ||
1112 | 657 | peer = { 'ip': ip, 'port': port, 'left': left, | ||
1113 | 658 | 'supportcrypto': supportcrypto, | ||
1114 | 659 | 'requirecrypto': requirecrypto } | ||
1115 | 660 | if mykey: | ||
1116 | 661 | peer['key'] = mykey | ||
1117 | 662 | if gip: | ||
1118 | 663 | peer['given ip'] = gip | ||
1119 | 664 | if port: | ||
1120 | 665 | if not self.natcheck or islocal: | ||
1121 | 666 | peer['nat'] = 0 | ||
1122 | 667 | self.natcheckOK(infohash,myid,ip1,port,peer) | ||
1123 | 668 | else: | ||
1124 | 669 | NatCheck(self.connectback_result,infohash,myid,ip1,port, | ||
1125 | 670 | self.rawserver,encrypted=requirecrypto) | ||
1126 | 671 | else: | ||
1127 | 672 | peer['nat'] = 2**30 | ||
1128 | 673 | if event == 'completed': | ||
1129 | 674 | self.completed[infohash] += 1 | ||
1130 | 675 | if not left: | ||
1131 | 676 | self.seedcount[infohash] += 1 | ||
1132 | 677 | |||
1133 | 678 | peers[myid] = peer | ||
1134 | 679 | |||
1135 | 680 | else: | ||
1136 | 681 | if not auth: | ||
1137 | 682 | return rsize # return w/o changing stats | ||
1138 | 683 | |||
1139 | 684 | ts[myid] = clock() | ||
1140 | 685 | if not left and peer['left']: | ||
1141 | 686 | self.completed[infohash] += 1 | ||
1142 | 687 | self.seedcount[infohash] += 1 | ||
1143 | 688 | if not peer.get('nat', -1): | ||
1144 | 689 | for bc in self.becache[infohash]: | ||
1145 | 690 | bc[1][myid] = bc[0][myid] | ||
1146 | 691 | del bc[0][myid] | ||
1147 | 692 | elif left and not peer['left']: | ||
1148 | 693 | self.completed[infohash] -= 1 | ||
1149 | 694 | self.seedcount[infohash] -= 1 | ||
1150 | 695 | if not peer.get('nat', -1): | ||
1151 | 696 | for bc in self.becache[infohash]: | ||
1152 | 697 | bc[0][myid] = bc[1][myid] | ||
1153 | 698 | del bc[1][myid] | ||
1154 | 699 | peer['left'] = left | ||
1155 | 700 | |||
1156 | 701 | if port: | ||
1157 | 702 | recheck = False | ||
1158 | 703 | if ip != peer['ip']: | ||
1159 | 704 | peer['ip'] = ip | ||
1160 | 705 | recheck = True | ||
1161 | 706 | if gip != peer.get('given ip'): | ||
1162 | 707 | if gip: | ||
1163 | 708 | peer['given ip'] = gip | ||
1164 | 709 | elif peer.has_key('given ip'): | ||
1165 | 710 | del peer['given ip'] | ||
1166 | 711 | recheck = True | ||
1167 | 712 | |||
1168 | 713 | natted = peer.get('nat', -1) | ||
1169 | 714 | if recheck: | ||
1170 | 715 | if natted == 0: | ||
1171 | 716 | l = self.becache[infohash] | ||
1172 | 717 | y = not peer['left'] | ||
1173 | 718 | for x in l: | ||
1174 | 719 | del x[y][myid] | ||
1175 | 720 | if natted >= 0: | ||
1176 | 721 | del peer['nat'] # restart NAT testing | ||
1177 | 722 | if natted and natted < self.natcheck: | ||
1178 | 723 | recheck = True | ||
1179 | 724 | |||
1180 | 725 | if recheck: | ||
1181 | 726 | if not self.natcheck or islocal: | ||
1182 | 727 | peer['nat'] = 0 | ||
1183 | 728 | self.natcheckOK(infohash,myid,ip1,port,peer) | ||
1184 | 729 | else: | ||
1185 | 730 | NatCheck(self.connectback_result,infohash,myid,ip1,port, | ||
1186 | 731 | self.rawserver,encrypted=requirecrypto) | ||
1187 | 732 | |||
1188 | 733 | return rsize | ||
1189 | 734 | |||
1190 | 735 | |||
1191 | 736 | def peerlist(self, infohash, stopped, tracker, is_seed, | ||
1192 | 737 | return_type, rsize, supportcrypto): | ||
1193 | 738 | data = {} # return data | ||
1194 | 739 | seeds = self.seedcount[infohash] | ||
1195 | 740 | data['complete'] = seeds | ||
1196 | 741 | data['incomplete'] = len(self.downloads[infohash]) - seeds | ||
1197 | 742 | |||
1198 | 743 | if ( self.config['allowed_controls'] | ||
1199 | 744 | and self.allowed[infohash].has_key('warning message') ): | ||
1200 | 745 | data['warning message'] = self.allowed[infohash]['warning message'] | ||
1201 | 746 | |||
1202 | 747 | if tracker: | ||
1203 | 748 | data['interval'] = self.config['multitracker_reannounce_interval'] | ||
1204 | 749 | if not rsize: | ||
1205 | 750 | return data | ||
1206 | 751 | cache = self.cached_t.setdefault(infohash, None) | ||
1207 | 752 | if ( not cache or len(cache[1]) < rsize | ||
1208 | 753 | or cache[0] + self.config['min_time_between_cache_refreshes'] < clock() ): | ||
1209 | 754 | bc = self.becache.setdefault(infohash,self.cache_default) | ||
1210 | 755 | cache = [ clock(), bc[0][0].values() + bc[0][1].values() ] | ||
1211 | 756 | self.cached_t[infohash] = cache | ||
1212 | 757 | shuffle(cache[1]) | ||
1213 | 758 | cache = cache[1] | ||
1214 | 759 | |||
1215 | 760 | data['peers'] = cache[-rsize:] | ||
1216 | 761 | del cache[-rsize:] | ||
1217 | 762 | return data | ||
1218 | 763 | |||
1219 | 764 | data['interval'] = self.reannounce_interval | ||
1220 | 765 | if stopped or not rsize: # save some bandwidth | ||
1221 | 766 | data['peers'] = [] | ||
1222 | 767 | return data | ||
1223 | 768 | |||
1224 | 769 | bc = self.becache.setdefault(infohash,self.cache_default) | ||
1225 | 770 | len_l = len(bc[2][0]) | ||
1226 | 771 | len_s = len(bc[2][1]) | ||
1227 | 772 | if not (len_l+len_s): # caches are empty! | ||
1228 | 773 | data['peers'] = [] | ||
1229 | 774 | return data | ||
1230 | 775 | l_get_size = int(float(rsize)*(len_l)/(len_l+len_s)) | ||
1231 | 776 | cache = self.cached.setdefault(infohash,[None,None,None])[return_type] | ||
1232 | 777 | if cache and ( not cache[1] | ||
1233 | 778 | or (is_seed and len(cache[1]) < rsize) | ||
1234 | 779 | or len(cache[1]) < l_get_size | ||
1235 | 780 | or cache[0]+self.config['min_time_between_cache_refreshes'] < self.cachetime ): | ||
1236 | 781 | cache = None | ||
1237 | 782 | if not cache: | ||
1238 | 783 | peers = self.downloads[infohash] | ||
1239 | 784 | if self.config['compact_reqd']: | ||
1240 | 785 | vv = ([],[],[]) | ||
1241 | 786 | else: | ||
1242 | 787 | vv = ([],[],[],[],[]) | ||
1243 | 788 | for key, ip, port in self.t2tlist.harvest(infohash): # empty if disabled | ||
1244 | 789 | if not peers.has_key(key): | ||
1245 | 790 | cp = compact_peer_info(ip, port) | ||
1246 | 791 | vv[0].append(cp) | ||
1247 | 792 | vv[2].append((cp,'\x00')) | ||
1248 | 793 | if not self.config['compact_reqd']: | ||
1249 | 794 | vv[3].append({'ip': ip, 'port': port, 'peer id': key}) | ||
1250 | 795 | vv[4].append({'ip': ip, 'port': port}) | ||
1251 | 796 | cache = [ self.cachetime, | ||
1252 | 797 | bc[return_type][0].values()+vv[return_type], | ||
1253 | 798 | bc[return_type][1].values() ] | ||
1254 | 799 | shuffle(cache[1]) | ||
1255 | 800 | shuffle(cache[2]) | ||
1256 | 801 | self.cached[infohash][return_type] = cache | ||
1257 | 802 | for rr in xrange(len(self.cached[infohash])): | ||
1258 | 803 | if rr != return_type: | ||
1259 | 804 | try: | ||
1260 | 805 | self.cached[infohash][rr][1].extend(vv[rr]) | ||
1261 | 806 | except: | ||
1262 | 807 | pass | ||
1263 | 808 | if len(cache[1]) < l_get_size: | ||
1264 | 809 | peerdata = cache[1] | ||
1265 | 810 | if not is_seed: | ||
1266 | 811 | peerdata.extend(cache[2]) | ||
1267 | 812 | cache[1] = [] | ||
1268 | 813 | cache[2] = [] | ||
1269 | 814 | else: | ||
1270 | 815 | if not is_seed: | ||
1271 | 816 | peerdata = cache[2][l_get_size-rsize:] | ||
1272 | 817 | del cache[2][l_get_size-rsize:] | ||
1273 | 818 | rsize -= len(peerdata) | ||
1274 | 819 | else: | ||
1275 | 820 | peerdata = [] | ||
1276 | 821 | if rsize: | ||
1277 | 822 | peerdata.extend(cache[1][-rsize:]) | ||
1278 | 823 | del cache[1][-rsize:] | ||
1279 | 824 | if return_type == 0: | ||
1280 | 825 | data['peers'] = ''.join(peerdata) | ||
1281 | 826 | elif return_type == 1: | ||
1282 | 827 | data['crypto_flags'] = "0x01"*len(peerdata) | ||
1283 | 828 | data['peers'] = ''.join(peerdata) | ||
1284 | 829 | elif return_type == 2: | ||
1285 | 830 | data['crypto_flags'] = ''.join([p[1] for p in peerdata]) | ||
1286 | 831 | data['peers'] = ''.join([p[0] for p in peerdata]) | ||
1287 | 832 | else: | ||
1288 | 833 | data['peers'] = peerdata | ||
1289 | 834 | return data | ||
1290 | 835 | |||
1291 | 836 | |||
1292 | 837 | def get(self, connection, path, headers): | ||
1293 | 838 | real_ip = connection.get_ip() | ||
1294 | 839 | ip = real_ip | ||
1295 | 840 | if is_ipv4(ip): | ||
1296 | 841 | ipv4 = True | ||
1297 | 842 | else: | ||
1298 | 843 | try: | ||
1299 | 844 | ip = ipv6_to_ipv4(ip) | ||
1300 | 845 | ipv4 = True | ||
1301 | 846 | except ValueError: | ||
1302 | 847 | ipv4 = False | ||
1303 | 848 | |||
1304 | 849 | if ( (self.allowed_IPs and not self.allowed_IPs.includes(ip)) | ||
1305 | 850 | or (self.banned_IPs and self.banned_IPs.includes(ip)) ): | ||
1306 | 851 | return (400, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, | ||
1307 | 852 | bencode({'failure reason': | ||
1308 | 853 | 'your IP is not allowed on this tracker'})) | ||
1309 | 854 | |||
1310 | 855 | nip = get_forwarded_ip(headers) | ||
1311 | 856 | if nip and not self.only_local_override_ip: | ||
1312 | 857 | ip = nip | ||
1313 | 858 | try: | ||
1314 | 859 | ip = to_ipv4(ip) | ||
1315 | 860 | ipv4 = True | ||
1316 | 861 | except ValueError: | ||
1317 | 862 | ipv4 = False | ||
1318 | 863 | |||
1319 | 864 | paramslist = {} | ||
1320 | 865 | def params(key, default = None, l = paramslist): | ||
1321 | 866 | if l.has_key(key): | ||
1322 | 867 | return l[key][0] | ||
1323 | 868 | return default | ||
1324 | 869 | |||
1325 | 870 | try: | ||
1326 | 871 | (scheme, netloc, path, pars, query, fragment) = urlparse(path) | ||
1327 | 872 | if self.uq_broken == 1: | ||
1328 | 873 | path = path.replace('+',' ') | ||
1329 | 874 | query = query.replace('+',' ') | ||
1330 | 875 | path = unquote(path)[1:] | ||
1331 | 876 | for s in query.split('&'): | ||
1332 | 877 | if s: | ||
1333 | 878 | i = s.index('=') | ||
1334 | 879 | kw = unquote(s[:i]) | ||
1335 | 880 | paramslist.setdefault(kw, []) | ||
1336 | 881 | paramslist[kw] += [unquote(s[i+1:])] | ||
1337 | 882 | |||
1338 | 883 | if path == '' or path == 'index.html': | ||
1339 | 884 | return self.get_infopage() | ||
1340 | 885 | if (path == 'file'): | ||
1341 | 886 | return self.get_file(params('info_hash')) | ||
1342 | 887 | if path == 'favicon.ico' and self.favicon is not None: | ||
1343 | 888 | return (200, 'OK', {'Content-Type' : 'image/x-icon'}, self.favicon) | ||
1344 | 889 | |||
1345 | 890 | # automated access from here on | ||
1346 | 891 | |||
1347 | 892 | if path in ('scrape', 'scrape.php', 'tracker.php/scrape'): | ||
1348 | 893 | return self.get_scrape(paramslist) | ||
1349 | 894 | |||
1350 | 895 | if not path in ('announce', 'announce.php', 'tracker.php/announce'): | ||
1351 | 896 | return (404, 'Not Found', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, alas) | ||
1352 | 897 | |||
1353 | 898 | # main tracker function | ||
1354 | 899 | |||
1355 | 900 | filtered = self.Filter.check(real_ip, paramslist, headers) | ||
1356 | 901 | if filtered: | ||
1357 | 902 | return (400, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, | ||
1358 | 903 | bencode({'failure reason': filtered})) | ||
1359 | 904 | |||
1360 | 905 | infohash = params('info_hash') | ||
1361 | 906 | if not infohash: | ||
1362 | 907 | raise ValueError, 'no info hash' | ||
1363 | 908 | |||
1364 | 909 | notallowed = self.check_allowed(infohash, paramslist) | ||
1365 | 910 | if notallowed: | ||
1366 | 911 | return notallowed | ||
1367 | 912 | |||
1368 | 913 | event = params('event') | ||
1369 | 914 | |||
1370 | 915 | rsize = self.add_data(infohash, event, ip, paramslist) | ||
1371 | 916 | |||
1372 | 917 | except ValueError, e: | ||
1373 | 918 | return (400, 'Bad Request', {'Content-Type': 'text/plain'}, | ||
1374 | 919 | 'you sent me garbage - ' + str(e)) | ||
1375 | 920 | |||
1376 | 921 | if self.aggregate_forward and not paramslist.has_key('tracker'): | ||
1377 | 922 | self.aggregate_senddata(query) | ||
1378 | 923 | |||
1379 | 924 | if self.is_aggregator: # don't return peer data here | ||
1380 | 925 | return (200, 'OK', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, | ||
1381 | 926 | bencode({'response': 'OK'})) | ||
1382 | 927 | |||
1383 | 928 | if params('compact') and ipv4: | ||
1384 | 929 | if params('requirecrypto'): | ||
1385 | 930 | return_type = 1 | ||
1386 | 931 | elif params('supportcrypto'): | ||
1387 | 932 | return_type = 2 | ||
1388 | 933 | else: | ||
1389 | 934 | return_type = 0 | ||
1390 | 935 | elif self.config['compact_reqd'] and ipv4: | ||
1391 | 936 | return (400, 'Bad Request', {'Content-Type': 'text/plain'}, | ||
1392 | 937 | 'your client is outdated, please upgrade') | ||
1393 | 938 | elif params('no_peer_id'): | ||
1394 | 939 | return_type = 4 | ||
1395 | 940 | else: | ||
1396 | 941 | return_type = 3 | ||
1397 | 942 | |||
1398 | 943 | data = self.peerlist(infohash, event=='stopped', | ||
1399 | 944 | params('tracker'), not params('left'), | ||
1400 | 945 | return_type, rsize, params('supportcrypto')) | ||
1401 | 946 | |||
1402 | 947 | if paramslist.has_key('scrape'): # deprecated | ||
1403 | 948 | data['scrape'] = self.scrapedata(infohash, False) | ||
1404 | 949 | |||
1405 | 950 | if self.dedicated_seed_id: | ||
1406 | 951 | if params('seed_id') == self.dedicated_seed_id and params('left') == 0: | ||
1407 | 952 | self.is_seeded[infohash] = True | ||
1408 | 953 | if params('check_seeded') and self.is_seeded.get(infohash): | ||
1409 | 954 | data['seeded'] = 1 | ||
1410 | 955 | |||
1411 | 956 | return (200, 'OK', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode(data)) | ||
1412 | 957 | |||
1413 | 958 | |||
1414 | 959 | def natcheckOK(self, infohash, peerid, ip, port, peer): | ||
1415 | 960 | seed = not peer['left'] | ||
1416 | 961 | bc = self.becache.setdefault(infohash,self.cache_default) | ||
1417 | 962 | cp = compact_peer_info(ip, port) | ||
1418 | 963 | reqc = peer['requirecrypto'] | ||
1419 | 964 | bc[2][seed][peerid] = (cp,chr(reqc)) | ||
1420 | 965 | if peer['supportcrypto']: | ||
1421 | 966 | bc[1][seed][peerid] = cp | ||
1422 | 967 | if not reqc: | ||
1423 | 968 | bc[0][seed][peerid] = cp | ||
1424 | 969 | if not self.config['compact_reqd']: | ||
1425 | 970 | bc[3][seed][peerid] = Bencached(bencode({'ip': ip, 'port': port, | ||
1426 | 971 | 'peer id': peerid})) | ||
1427 | 972 | bc[4][seed][peerid] = Bencached(bencode({'ip': ip, 'port': port})) | ||
1428 | 973 | |||
1429 | 974 | |||
1430 | 975 | def natchecklog(self, peerid, ip, port, result): | ||
1431 | 976 | year, month, day, hour, minute, second, a, b, c = localtime(time()) | ||
1432 | 977 | print '%s - %s [%02d/%3s/%04d:%02d:%02d:%02d] "!natcheck-%s:%i" %i 0 - -' % ( | ||
1433 | 978 | ip, quote(peerid), day, months[month], year, hour, minute, second, | ||
1434 | 979 | ip, port, result) | ||
1435 | 980 | |||
1436 | 981 | def connectback_result(self, result, downloadid, peerid, ip, port): | ||
1437 | 982 | record = self.downloads.get(downloadid,{}).get(peerid) | ||
1438 | 983 | if ( record is None | ||
1439 | 984 | or (record['ip'] != ip and record.get('given ip') != ip) | ||
1440 | 985 | or record['port'] != port ): | ||
1441 | 986 | if self.config['log_nat_checks']: | ||
1442 | 987 | self.natchecklog(peerid, ip, port, 404) | ||
1443 | 988 | return | ||
1444 | 989 | if self.config['log_nat_checks']: | ||
1445 | 990 | if result: | ||
1446 | 991 | x = 200 | ||
1447 | 992 | else: | ||
1448 | 993 | x = 503 | ||
1449 | 994 | self.natchecklog(peerid, ip, port, x) | ||
1450 | 995 | if not record.has_key('nat'): | ||
1451 | 996 | record['nat'] = int(not result) | ||
1452 | 997 | if result: | ||
1453 | 998 | self.natcheckOK(downloadid,peerid,ip,port,record) | ||
1454 | 999 | elif result and record['nat']: | ||
1455 | 1000 | record['nat'] = 0 | ||
1456 | 1001 | self.natcheckOK(downloadid,peerid,ip,port,record) | ||
1457 | 1002 | elif not result: | ||
1458 | 1003 | record['nat'] += 1 | ||
1459 | 1004 | |||
1460 | 1005 | |||
1461 | 1006 | def remove_from_state(self, *l): | ||
1462 | 1007 | for s in l: | ||
1463 | 1008 | try: | ||
1464 | 1009 | del self.state[s] | ||
1465 | 1010 | except: | ||
1466 | 1011 | pass | ||
1467 | 1012 | |||
1468 | 1013 | def save_state(self): | ||
1469 | 1014 | self.rawserver.add_task(self.save_state, self.save_dfile_interval) | ||
1470 | 1015 | h = open(self.dfile, 'wb') | ||
1471 | 1016 | h.write(bencode(self.state)) | ||
1472 | 1017 | h.close() | ||
1473 | 1018 | |||
1474 | 1019 | |||
1475 | 1020 | def parse_allowed(self): | ||
1476 | 1021 | self.rawserver.add_task(self.parse_allowed, self.parse_dir_interval) | ||
1477 | 1022 | |||
1478 | 1023 | if self.config['allowed_dir']: | ||
1479 | 1024 | r = parsedir( self.config['allowed_dir'], self.allowed, | ||
1480 | 1025 | self.allowed_dir_files, self.allowed_dir_blocked, | ||
1481 | 1026 | [".torrent"] ) | ||
1482 | 1027 | ( self.allowed, self.allowed_dir_files, self.allowed_dir_blocked, | ||
1483 | 1028 | added, garbage2 ) = r | ||
1484 | 1029 | |||
1485 | 1030 | self.state['allowed'] = self.allowed | ||
1486 | 1031 | self.state['allowed_dir_files'] = self.allowed_dir_files | ||
1487 | 1032 | |||
1488 | 1033 | self.t2tlist.parse(self.allowed) | ||
1489 | 1034 | |||
1490 | 1035 | else: | ||
1491 | 1036 | f = self.config['allowed_list'] | ||
1492 | 1037 | if self.allowed_list_mtime == os.path.getmtime(f): | ||
1493 | 1038 | return | ||
1494 | 1039 | try: | ||
1495 | 1040 | r = parsetorrentlist(f, self.allowed) | ||
1496 | 1041 | (self.allowed, added, garbage2) = r | ||
1497 | 1042 | self.state['allowed_list'] = self.allowed | ||
1498 | 1043 | except (IOError, OSError): | ||
1499 | 1044 | print '**warning** unable to read allowed torrent list' | ||
1500 | 1045 | return | ||
1501 | 1046 | self.allowed_list_mtime = os.path.getmtime(f) | ||
1502 | 1047 | |||
1503 | 1048 | for infohash in added.keys(): | ||
1504 | 1049 | self.downloads.setdefault(infohash, {}) | ||
1505 | 1050 | self.completed.setdefault(infohash, 0) | ||
1506 | 1051 | self.seedcount.setdefault(infohash, 0) | ||
1507 | 1052 | |||
1508 | 1053 | |||
1509 | 1054 | def read_ip_lists(self): | ||
1510 | 1055 | self.rawserver.add_task(self.read_ip_lists,self.parse_dir_interval) | ||
1511 | 1056 | |||
1512 | 1057 | f = self.config['allowed_ips'] | ||
1513 | 1058 | if f and self.allowed_ip_mtime != os.path.getmtime(f): | ||
1514 | 1059 | self.allowed_IPs = IP_List() | ||
1515 | 1060 | try: | ||
1516 | 1061 | self.allowed_IPs.read_fieldlist(f) | ||
1517 | 1062 | self.allowed_ip_mtime = os.path.getmtime(f) | ||
1518 | 1063 | except (IOError, OSError): | ||
1519 | 1064 | print '**warning** unable to read allowed_IP list' | ||
1520 | 1065 | |||
1521 | 1066 | f = self.config['banned_ips'] | ||
1522 | 1067 | if f and self.banned_ip_mtime != os.path.getmtime(f): | ||
1523 | 1068 | self.banned_IPs = IP_Range_List() | ||
1524 | 1069 | try: | ||
1525 | 1070 | self.banned_IPs.read_rangelist(f) | ||
1526 | 1071 | self.banned_ip_mtime = os.path.getmtime(f) | ||
1527 | 1072 | except (IOError, OSError): | ||
1528 | 1073 | print '**warning** unable to read banned_IP list' | ||
1529 | 1074 | |||
1530 | 1075 | |||
1531 | 1076 | def delete_peer(self, infohash, peerid): | ||
1532 | 1077 | dls = self.downloads[infohash] | ||
1533 | 1078 | peer = dls[peerid] | ||
1534 | 1079 | if not peer['left']: | ||
1535 | 1080 | self.seedcount[infohash] -= 1 | ||
1536 | 1081 | if not peer.get('nat',-1): | ||
1537 | 1082 | l = self.becache[infohash] | ||
1538 | 1083 | y = not peer['left'] | ||
1539 | 1084 | for x in l: | ||
1540 | 1085 | if x[y].has_key(peerid): | ||
1541 | 1086 | del x[y][peerid] | ||
1542 | 1087 | del self.times[infohash][peerid] | ||
1543 | 1088 | del dls[peerid] | ||
1544 | 1089 | |||
1545 | 1090 | def expire_downloaders(self): | ||
1546 | 1091 | for x in self.times.keys(): | ||
1547 | 1092 | for myid, t in self.times[x].items(): | ||
1548 | 1093 | if t < self.prevtime: | ||
1549 | 1094 | self.delete_peer(x,myid) | ||
1550 | 1095 | self.prevtime = clock() | ||
1551 | 1096 | if (self.keep_dead != 1): | ||
1552 | 1097 | for key, value in self.downloads.items(): | ||
1553 | 1098 | if len(value) == 0 and ( | ||
1554 | 1099 | self.allowed is None or not self.allowed.has_key(key) ): | ||
1555 | 1100 | del self.times[key] | ||
1556 | 1101 | del self.downloads[key] | ||
1557 | 1102 | del self.seedcount[key] | ||
1558 | 1103 | self.rawserver.add_task(self.expire_downloaders, self.timeout_downloaders_interval) | ||
1559 | 1104 | |||
1560 | 1105 | |||
1561 | 1106 | def track(args): | ||
1562 | 1107 | if len(args) == 0: | ||
1563 | 1108 | print formatDefinitions(defaults, 80) | ||
1564 | 1109 | return | ||
1565 | 1110 | try: | ||
1566 | 1111 | config, files = parseargs(args, defaults, 0, 0) | ||
1567 | 1112 | except ValueError, e: | ||
1568 | 1113 | print 'error: ' + str(e) | ||
1569 | 1114 | print 'run with no arguments for parameter explanations' | ||
1570 | 1115 | return | ||
1571 | 1116 | r = RawServer(Event(), config['timeout_check_interval'], | ||
1572 | 1117 | config['socket_timeout'], ipv6_enable = config['ipv6_enabled']) | ||
1573 | 1118 | t = Tracker(config, r) | ||
1574 | 1119 | r.bind(config['port'], config['bind'], | ||
1575 | 1120 | reuse = True, ipv6_socket_style = config['ipv6_binds_v4']) | ||
1576 | 1121 | r.listen_forever(HTTPHandler(t.get, config['min_time_between_log_flushes'])) | ||
1577 | 1122 | t.save_state() | ||
1578 | 1123 | print '# Shutting down: ' + isotime() | ||
1579 | 1124 | |||
1580 | 1125 | def size_format(s): | ||
1581 | 1126 | if (s < 1024): | ||
1582 | 1127 | r = str(s) + 'B' | ||
1583 | 1128 | elif (s < 1048576): | ||
1584 | 1129 | r = str(int(s/1024)) + 'KiB' | ||
1585 | 1130 | elif (s < 1073741824L): | ||
1586 | 1131 | r = str(int(s/1048576)) + 'MiB' | ||
1587 | 1132 | elif (s < 1099511627776L): | ||
1588 | 1133 | r = str(int((s/1073741824.0)*100.0)/100.0) + 'GiB' | ||
1589 | 1134 | else: | ||
1590 | 1135 | r = str(int((s/1099511627776.0)*100.0)/100.0) + 'TiB' | ||
1591 | 1136 | return(r) | ||
1592 | 1137 | |||
1593 | 1138 | 0 | ||
1594 | === removed directory '.pc/06_README_portchange.dpatch' | |||
1595 | === removed file '.pc/06_README_portchange.dpatch/README.txt' | |||
1596 | --- .pc/06_README_portchange.dpatch/README.txt 2010-03-21 14:36:30 +0000 | |||
1597 | +++ .pc/06_README_portchange.dpatch/README.txt 1970-01-01 00:00:00 +0000 | |||
1598 | @@ -1,110 +0,0 @@ | |||
1599 | 1 | BitTorrent is a tool for distributing files. It's extremely | ||
1600 | 2 | easy to use - downloads are started by clicking on hyperlinks. | ||
1601 | 3 | Whenever more than one person is downloading at once | ||
1602 | 4 | they send pieces of the file(s) to each other, thus relieving | ||
1603 | 5 | the central server's bandwidth burden. Even with many | ||
1604 | 6 | simultaneous downloads, the upload burden on the central server | ||
1605 | 7 | remains quite small, since each new downloader introduces new | ||
1606 | 8 | upload capacity. | ||
1607 | 9 | |||
1608 | 10 | Windows web browser support is added by running an installer. | ||
1609 | 11 | A prebuilt one is available, but instructions for building it | ||
1610 | 12 | yourself are in BUILD.windows.txt | ||
1611 | 13 | |||
1612 | 14 | Instructions for Unix installation are in INSTALL.unix.txt | ||
1613 | 15 | |||
1614 | 16 | To start hosting - | ||
1615 | 17 | |||
1616 | 18 | 1) start running a tracker | ||
1617 | 19 | |||
1618 | 20 | First, you need a tracker. If you're on a dynamic IP or otherwise | ||
1619 | 21 | unreliable connection, you should find someone else's tracker and | ||
1620 | 22 | use that. Otherwise, follow the rest of this step. | ||
1621 | 23 | |||
1622 | 24 | Trackers refer downloaders to each other. The load on the tracker | ||
1623 | 25 | is very small, so you only need one for all your files. | ||
1624 | 26 | |||
1625 | 27 | To run a tracker, execute the command bttrack.py Here is an example - | ||
1626 | 28 | |||
1627 | 29 | ./bttrack.py --port 6969 --dfile dstate | ||
1628 | 30 | |||
1629 | 31 | --dfile is where persistent information is kept on the tracker across | ||
1630 | 32 | invocations. It makes everything start working again immediately if | ||
1631 | 33 | you restart the tracker. A new one will be created if it doesn't exist | ||
1632 | 34 | already. | ||
1633 | 35 | |||
1634 | 36 | The tracker must be on a net-addressible box, and you must know the | ||
1635 | 37 | ip number or dns name of it. | ||
1636 | 38 | |||
1637 | 39 | The tracker outputs web logs to standard out. You can get information | ||
1638 | 40 | about the files it's currently serving by getting its index page. | ||
1639 | 41 | |||
1640 | 42 | 2) create a metainfo file using btmakemetafile.py | ||
1641 | 43 | |||
1642 | 44 | To generate a metainfo file, run the publish btmakemetafile and give | ||
1643 | 45 | it the file you want metainfo for and the url of the tracker | ||
1644 | 46 | |||
1645 | 47 | ./btmakemetafile.py http://my.tracker:6969/announce myfile.ext | ||
1646 | 48 | |||
1647 | 49 | This will generate a file called myfile.ext.torrent | ||
1648 | 50 | |||
1649 | 51 | Make sure to include the port number in the tracker url if it isn't 80. | ||
1650 | 52 | |||
1651 | 53 | This command may take a while to scan over the whole file hashing it. | ||
1652 | 54 | |||
1653 | 55 | The /announce path is special and hard-coded into the tracker. | ||
1654 | 56 | Make sure to give the domain or ip your tracker is on instead of | ||
1655 | 57 | my.tracker. | ||
1656 | 58 | |||
1657 | 59 | You can use either a dns name or an IP address in the tracker url. | ||
1658 | 60 | |||
1659 | 61 | 3) associate .torrent with application/x-bittorrent on your web server | ||
1660 | 62 | |||
1661 | 63 | The way you do this is dependent on the particular web server you're using. | ||
1662 | 64 | |||
1663 | 65 | You must have a web server which can serve ordinary static files and is | ||
1664 | 66 | addressable from the internet at large. | ||
1665 | 67 | |||
1666 | 68 | 4) put the newly made .torrent file on your web server | ||
1667 | 69 | |||
1668 | 70 | Note that the file name you choose on the server must end in .torrent, so | ||
1669 | 71 | it gets associated with the right mimetype. | ||
1670 | 72 | |||
1671 | 73 | 5) put up a static page which links to the location you uploaded to in step 4 | ||
1672 | 74 | |||
1673 | 75 | The file you uploaded in step 4 is linked to using an ordinary url. | ||
1674 | 76 | |||
1675 | 77 | 6) start a downloader as a resume on the complete file | ||
1676 | 78 | |||
1677 | 79 | You have to run a downloader which already has the complete file, | ||
1678 | 80 | so new downloaders have a place to get it from. Here's an example - | ||
1679 | 81 | |||
1680 | 82 | ./btdownloadheadless.py --url http://my.server/myfile.torrent --saveas myfile.ext | ||
1681 | 83 | |||
1682 | 84 | Make sure the saveas argument points to the already complete file. | ||
1683 | 85 | |||
1684 | 86 | If you're running the complete downloader on the same machine or LAN as | ||
1685 | 87 | the tracker, give a --ip parameter to the complete downloader. The --ip | ||
1686 | 88 | parameter can be either an IP address or DNS name. | ||
1687 | 89 | |||
1688 | 90 | BitTorrent defaults to port 6881. If it can't use 6881, (probably because | ||
1689 | 91 | another download is happening) it tries 6882, then 6883, etc. It gives up | ||
1690 | 92 | after 6889. | ||
1691 | 93 | |||
1692 | 94 | 7) you're done! | ||
1693 | 95 | |||
1694 | 96 | Now you just have to get people downloading! Refer them to the page you | ||
1695 | 97 | created in step 5. | ||
1696 | 98 | |||
1697 | 99 | BitTorrent can also publish whole directories - simply point | ||
1698 | 100 | btmakemetafile.py at the directory with files in it, they'll be published | ||
1699 | 101 | as one unit. All files in subdirectories will be included, although files | ||
1700 | 102 | and directories named 'CVS' and 'core' are ignored. | ||
1701 | 103 | |||
1702 | 104 | If you have any questions, try the web site or mailing list - | ||
1703 | 105 | |||
1704 | 106 | http://bitconjurer.org/BitTorrent/ | ||
1705 | 107 | |||
1706 | 108 | http://groups.yahoo.com/group/BitTorrent | ||
1707 | 109 | |||
1708 | 110 | You can also often find me, Bram, in #bittorrent of irc.freenode.net | ||
1709 | 111 | 0 | ||
1710 | === removed directory '.pc/07_change_report_address.dpatch' | |||
1711 | === removed directory '.pc/07_change_report_address.dpatch/BitTornado' | |||
1712 | === removed file '.pc/07_change_report_address.dpatch/BitTornado/__init__.py' | |||
1713 | --- .pc/07_change_report_address.dpatch/BitTornado/__init__.py 2010-03-21 14:36:30 +0000 | |||
1714 | +++ .pc/07_change_report_address.dpatch/BitTornado/__init__.py 1970-01-01 00:00:00 +0000 | |||
1715 | @@ -1,63 +0,0 @@ | |||
1716 | 1 | product_name = 'BitTornado' | ||
1717 | 2 | version_short = 'T-0.3.18' | ||
1718 | 3 | |||
1719 | 4 | version = version_short+' ('+product_name+')' | ||
1720 | 5 | report_email = version_short+'@degreez.net' | ||
1721 | 6 | |||
1722 | 7 | from types import StringType | ||
1723 | 8 | from sha import sha | ||
1724 | 9 | from time import time, clock | ||
1725 | 10 | try: | ||
1726 | 11 | from os import getpid | ||
1727 | 12 | except ImportError: | ||
1728 | 13 | def getpid(): | ||
1729 | 14 | return 1 | ||
1730 | 15 | |||
1731 | 16 | mapbase64 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-' | ||
1732 | 17 | |||
1733 | 18 | _idprefix = version_short[0] | ||
1734 | 19 | for subver in version_short[2:].split('.'): | ||
1735 | 20 | try: | ||
1736 | 21 | subver = int(subver) | ||
1737 | 22 | except: | ||
1738 | 23 | subver = 0 | ||
1739 | 24 | _idprefix += mapbase64[subver] | ||
1740 | 25 | _idprefix += ('-' * (6-len(_idprefix))) | ||
1741 | 26 | _idrandom = [None] | ||
1742 | 27 | |||
1743 | 28 | def resetPeerIDs(): | ||
1744 | 29 | try: | ||
1745 | 30 | f = open('/dev/urandom','rb') | ||
1746 | 31 | x = f.read(20) | ||
1747 | 32 | f.close() | ||
1748 | 33 | except: | ||
1749 | 34 | x = '' | ||
1750 | 35 | |||
1751 | 36 | l1 = 0 | ||
1752 | 37 | t = clock() | ||
1753 | 38 | while t == clock(): | ||
1754 | 39 | l1 += 1 | ||
1755 | 40 | l2 = 0 | ||
1756 | 41 | t = long(time()*100) | ||
1757 | 42 | while t == long(time()*100): | ||
1758 | 43 | l2 += 1 | ||
1759 | 44 | l3 = 0 | ||
1760 | 45 | if l2 < 1000: | ||
1761 | 46 | t = long(time()*10) | ||
1762 | 47 | while t == long(clock()*10): | ||
1763 | 48 | l3 += 1 | ||
1764 | 49 | x += ( repr(time()) + '/' + str(time()) + '/' | ||
1765 | 50 | + str(l1) + '/' + str(l2) + '/' + str(l3) + '/' | ||
1766 | 51 | + str(getpid()) ) | ||
1767 | 52 | |||
1768 | 53 | s = '' | ||
1769 | 54 | for i in sha(x).digest()[-11:]: | ||
1770 | 55 | s += mapbase64[ord(i) & 0x3F] | ||
1771 | 56 | _idrandom[0] = s | ||
1772 | 57 | |||
1773 | 58 | resetPeerIDs() | ||
1774 | 59 | |||
1775 | 60 | def createPeerID(ins = '---'): | ||
1776 | 61 | assert type(ins) is StringType | ||
1777 | 62 | assert len(ins) == 3 | ||
1778 | 63 | return _idprefix + ins + _idrandom[0] | ||
1779 | 64 | 0 | ||
1780 | === removed directory '.pc/08_btdownloadcurses_indent.dpatch' | |||
1781 | === removed file '.pc/08_btdownloadcurses_indent.dpatch/btdownloadcurses.py' | |||
1782 | --- .pc/08_btdownloadcurses_indent.dpatch/btdownloadcurses.py 2010-03-21 14:36:30 +0000 | |||
1783 | +++ .pc/08_btdownloadcurses_indent.dpatch/btdownloadcurses.py 1970-01-01 00:00:00 +0000 | |||
1784 | @@ -1,407 +0,0 @@ | |||
1785 | 1 | #!/usr/bin/env python | ||
1786 | 2 | |||
1787 | 3 | # Written by Henry 'Pi' James | ||
1788 | 4 | # see LICENSE.txt for license information | ||
1789 | 5 | |||
1790 | 6 | SPEW_SCROLL_RATE = 1 | ||
1791 | 7 | |||
1792 | 8 | from BitTornado import PSYCO | ||
1793 | 9 | if PSYCO.psyco: | ||
1794 | 10 | try: | ||
1795 | 11 | import psyco | ||
1796 | 12 | assert psyco.__version__ >= 0x010100f0 | ||
1797 | 13 | psyco.full() | ||
1798 | 14 | except: | ||
1799 | 15 | pass | ||
1800 | 16 | |||
1801 | 17 | from BitTornado.download_bt1 import BT1Download, defaults, parse_params, get_usage, get_response | ||
1802 | 18 | from BitTornado.RawServer import RawServer, UPnP_ERROR | ||
1803 | 19 | from random import seed | ||
1804 | 20 | from socket import error as socketerror | ||
1805 | 21 | from BitTornado.bencode import bencode | ||
1806 | 22 | from BitTornado.natpunch import UPnP_test | ||
1807 | 23 | from threading import Event | ||
1808 | 24 | from os.path import abspath | ||
1809 | 25 | from signal import signal, SIGWINCH | ||
1810 | 26 | from sha import sha | ||
1811 | 27 | from sys import argv, exit | ||
1812 | 28 | import sys | ||
1813 | 29 | from time import time, strftime | ||
1814 | 30 | from BitTornado.clock import clock | ||
1815 | 31 | from BitTornado import createPeerID, version | ||
1816 | 32 | from BitTornado.ConfigDir import ConfigDir | ||
1817 | 33 | |||
1818 | 34 | try: | ||
1819 | 35 | import curses | ||
1820 | 36 | import curses.panel | ||
1821 | 37 | from curses.wrapper import wrapper as curses_wrapper | ||
1822 | 38 | from signal import signal, SIGWINCH | ||
1823 | 39 | except: | ||
1824 | 40 | print 'Textmode GUI initialization failed, cannot proceed.' | ||
1825 | 41 | |||
1826 | 42 | print 'This download interface requires the standard Python module ' \ | ||
1827 | 43 | '"curses", which is unfortunately not available for the native ' \ | ||
1828 | 44 | 'Windows port of Python. It is however available for the Cygwin ' \ | ||
1829 | 45 | 'port of Python, running on all Win32 systems (www.cygwin.com).' | ||
1830 | 46 | |||
1831 | 47 | print 'You may still use "btdownloadheadless.py" to download.' | ||
1832 | 48 | sys.exit(1) | ||
1833 | 49 | |||
1834 | 50 | assert sys.version >= '2', "Install Python 2.0 or greater" | ||
1835 | 51 | try: | ||
1836 | 52 | True | ||
1837 | 53 | except: | ||
1838 | 54 | True = 1 | ||
1839 | 55 | False = 0 | ||
1840 | 56 | |||
1841 | 57 | def fmttime(n): | ||
1842 | 58 | if n == 0: | ||
1843 | 59 | return 'download complete!' | ||
1844 | 60 | try: | ||
1845 | 61 | n = int(n) | ||
1846 | 62 | assert n >= 0 and n < 5184000 # 60 days | ||
1847 | 63 | except: | ||
1848 | 64 | return '<unknown>' | ||
1849 | 65 | m, s = divmod(n, 60) | ||
1850 | 66 | h, m = divmod(m, 60) | ||
1851 | 67 | return 'finishing in %d:%02d:%02d' % (h, m, s) | ||
1852 | 68 | |||
1853 | 69 | def fmtsize(n): | ||
1854 | 70 | s = str(n) | ||
1855 | 71 | size = s[-3:] | ||
1856 | 72 | while len(s) > 3: | ||
1857 | 73 | s = s[:-3] | ||
1858 | 74 | size = '%s,%s' % (s[-3:], size) | ||
1859 | 75 | if n > 999: | ||
1860 | 76 | unit = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] | ||
1861 | 77 | i = 1 | ||
1862 | 78 | while i + 1 < len(unit) and (n >> 10) >= 999: | ||
1863 | 79 | i += 1 | ||
1864 | 80 | n >>= 10 | ||
1865 | 81 | n = float(n) / (1 << 10) | ||
1866 | 82 | size = '%s (%.2f %s)' % (size, n, unit[i]) | ||
1867 | 83 | return size | ||
1868 | 84 | |||
1869 | 85 | |||
1870 | 86 | class CursesDisplayer: | ||
1871 | 87 | def __init__(self, scrwin, errlist, doneflag): | ||
1872 | 88 | self.scrwin = scrwin | ||
1873 | 89 | self.errlist = errlist | ||
1874 | 90 | self.doneflag = doneflag | ||
1875 | 91 | |||
1876 | 92 | signal(SIGWINCH, self.winch_handler) | ||
1877 | 93 | self.changeflag = Event() | ||
1878 | 94 | |||
1879 | 95 | self.done = 0 | ||
1880 | 96 | self.file = '' | ||
1881 | 97 | self.fileSize = '' | ||
1882 | 98 | self.activity = '' | ||
1883 | 99 | self.status = '' | ||
1884 | 100 | self.progress = '' | ||
1885 | 101 | self.downloadTo = '' | ||
1886 | 102 | self.downRate = '---' | ||
1887 | 103 | self.upRate = '---' | ||
1888 | 104 | self.shareRating = '' | ||
1889 | 105 | self.seedStatus = '' | ||
1890 | 106 | self.peerStatus = '' | ||
1891 | 107 | self.errors = [] | ||
1892 | 108 | self.last_update_time = 0 | ||
1893 | 109 | self.spew_scroll_time = 0 | ||
1894 | 110 | self.spew_scroll_pos = 0 | ||
1895 | 111 | |||
1896 | 112 | self._remake_window() | ||
1897 | 113 | |||
1898 | 114 | def winch_handler(self, signum, stackframe): | ||
1899 | 115 | self.changeflag.set() | ||
1900 | 116 | curses.endwin() | ||
1901 | 117 | self.scrwin.refresh() | ||
1902 | 118 | self.scrwin = curses.newwin(0, 0, 0, 0) | ||
1903 | 119 | self._remake_window() | ||
1904 | 120 | |||
1905 | 121 | def _remake_window(self): | ||
1906 | 122 | self.scrh, self.scrw = self.scrwin.getmaxyx() | ||
1907 | 123 | self.scrpan = curses.panel.new_panel(self.scrwin) | ||
1908 | 124 | self.labelh, self.labelw, self.labely, self.labelx = 11, 9, 1, 2 | ||
1909 | 125 | self.labelwin = curses.newwin(self.labelh, self.labelw, | ||
1910 | 126 | self.labely, self.labelx) | ||
1911 | 127 | self.labelpan = curses.panel.new_panel(self.labelwin) | ||
1912 | 128 | self.fieldh, self.fieldw, self.fieldy, self.fieldx = ( | ||
1913 | 129 | self.labelh, self.scrw-2 - self.labelw-3, | ||
1914 | 130 | 1, self.labelw+3) | ||
1915 | 131 | self.fieldwin = curses.newwin(self.fieldh, self.fieldw, | ||
1916 | 132 | self.fieldy, self.fieldx) | ||
1917 | 133 | self.fieldwin.nodelay(1) | ||
1918 | 134 | self.fieldpan = curses.panel.new_panel(self.fieldwin) | ||
1919 | 135 | self.spewh, self.speww, self.spewy, self.spewx = ( | ||
1920 | 136 | self.scrh - self.labelh - 2, self.scrw - 3, 1 + self.labelh, 2) | ||
1921 | 137 | self.spewwin = curses.newwin(self.spewh, self.speww, | ||
1922 | 138 | self.spewy, self.spewx) | ||
1923 | 139 | self.spewpan = curses.panel.new_panel(self.spewwin) | ||
1924 | 140 | try: | ||
1925 | 141 | self.scrwin.border(ord('|'),ord('|'),ord('-'),ord('-'),ord(' '),ord(' '),ord(' '),ord(' ')) | ||
1926 | 142 | except: | ||
1927 | 143 | pass | ||
1928 | 144 | self.labelwin.addstr(0, 0, 'file:') | ||
1929 | 145 | self.labelwin.addstr(1, 0, 'size:') | ||
1930 | 146 | self.labelwin.addstr(2, 0, 'dest:') | ||
1931 | 147 | self.labelwin.addstr(3, 0, 'progress:') | ||
1932 | 148 | self.labelwin.addstr(4, 0, 'status:') | ||
1933 | 149 | self.labelwin.addstr(5, 0, 'dl speed:') | ||
1934 | 150 | self.labelwin.addstr(6, 0, 'ul speed:') | ||
1935 | 151 | self.labelwin.addstr(7, 0, 'sharing:') | ||
1936 | 152 | self.labelwin.addstr(8, 0, 'seeds:') | ||
1937 | 153 | self.labelwin.addstr(9, 0, 'peers:') | ||
1938 | 154 | curses.panel.update_panels() | ||
1939 | 155 | curses.doupdate() | ||
1940 | 156 | self.changeflag.clear() | ||
1941 | 157 | |||
1942 | 158 | |||
1943 | 159 | def finished(self): | ||
1944 | 160 | self.done = 1 | ||
1945 | 161 | self.activity = 'download succeeded!' | ||
1946 | 162 | self.downRate = '---' | ||
1947 | 163 | self.display(fractionDone = 1) | ||
1948 | 164 | |||
1949 | 165 | def failed(self): | ||
1950 | 166 | self.done = 1 | ||
1951 | 167 | self.activity = 'download failed!' | ||
1952 | 168 | self.downRate = '---' | ||
1953 | 169 | self.display() | ||
1954 | 170 | |||
1955 | 171 | def error(self, errormsg): | ||
1956 | 172 | newerrmsg = strftime('[%H:%M:%S] ') + errormsg | ||
1957 | 173 | self.errors.append(newerrmsg) | ||
1958 | 174 | self.errlist.append(newerrmsg) | ||
1959 | 175 | self.display() | ||
1960 | 176 | |||
1961 | 177 | def display(self, dpflag = Event(), fractionDone = None, timeEst = None, | ||
1962 | 178 | downRate = None, upRate = None, activity = None, | ||
1963 | 179 | statistics = None, spew = None, **kws): | ||
1964 | 180 | |||
1965 | 181 | inchar = self.fieldwin.getch() | ||
1966 | 182 | if inchar == 12: # ^L | ||
1967 | 183 | self._remake_window() | ||
1968 | 184 | elif inchar in (ord('q'),ord('Q')): | ||
1969 | 185 | self.doneflag.set() | ||
1970 | 186 | |||
1971 | 187 | if activity is not None and not self.done: | ||
1972 | 188 | self.activity = activity | ||
1973 | 189 | elif timeEst is not None: | ||
1974 | 190 | self.activity = fmttime(timeEst) | ||
1975 | 191 | if self.changeflag.isSet(): | ||
1976 | 192 | return | ||
1977 | 193 | if self.last_update_time + 0.1 > clock() and fractionDone not in (0.0, 1.0) and activity is not None: | ||
1978 | 194 | return | ||
1979 | 195 | self.last_update_time = clock() | ||
1980 | 196 | if fractionDone is not None: | ||
1981 | 197 | blocknum = int(self.fieldw * fractionDone) | ||
1982 | 198 | self.progress = blocknum * '#' + (self.fieldw - blocknum) * '_' | ||
1983 | 199 | self.status = '%s (%.1f%%)' % (self.activity, fractionDone * 100) | ||
1984 | 200 | else: | ||
1985 | 201 | self.status = self.activity | ||
1986 | 202 | if downRate is not None: | ||
1987 | 203 | self.downRate = '%.1f KB/s' % (float(downRate) / (1 << 10)) | ||
1988 | 204 | if upRate is not None: | ||
1989 | 205 | self.upRate = '%.1f KB/s' % (float(upRate) / (1 << 10)) | ||
1990 | 206 | if statistics is not None: | ||
1991 | 207 | if (statistics.shareRating < 0) or (statistics.shareRating > 100): | ||
1992 | 208 | self.shareRating = 'oo (%.1f MB up / %.1f MB down)' % (float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20)) | ||
1993 | 209 | else: | ||
1994 | 210 | self.shareRating = '%.3f (%.1f MB up / %.1f MB down)' % (statistics.shareRating, float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20)) | ||
1995 | 211 | if not self.done: | ||
1996 | 212 | self.seedStatus = '%d seen now, plus %.3f distributed copies' % (statistics.numSeeds,0.001*int(1000*statistics.numCopies2)) | ||
1997 | 213 | else: | ||
1998 | 214 | self.seedStatus = '%d seen recently, plus %.3f distributed copies' % (statistics.numOldSeeds,0.001*int(1000*statistics.numCopies)) | ||
1999 | 215 | self.peerStatus = '%d seen now, %.1f%% done at %.1f kB/s' % (statistics.numPeers,statistics.percentDone,float(statistics.torrentRate) / (1 << 10)) | ||
2000 | 216 | |||
2001 | 217 | self.fieldwin.erase() | ||
2002 | 218 | self.fieldwin.addnstr(0, 0, self.file, self.fieldw, curses.A_BOLD) | ||
2003 | 219 | self.fieldwin.addnstr(1, 0, self.fileSize, self.fieldw) | ||
2004 | 220 | self.fieldwin.addnstr(2, 0, self.downloadTo, self.fieldw) | ||
2005 | 221 | if self.progress: | ||
2006 | 222 | self.fieldwin.addnstr(3, 0, self.progress, self.fieldw, curses.A_BOLD) | ||
2007 | 223 | self.fieldwin.addnstr(4, 0, self.status, self.fieldw) | ||
2008 | 224 | self.fieldwin.addnstr(5, 0, self.downRate, self.fieldw) | ||
2009 | 225 | self.fieldwin.addnstr(6, 0, self.upRate, self.fieldw) | ||
2010 | 226 | self.fieldwin.addnstr(7, 0, self.shareRating, self.fieldw) | ||
2011 | 227 | self.fieldwin.addnstr(8, 0, self.seedStatus, self.fieldw) | ||
2012 | 228 | self.fieldwin.addnstr(9, 0, self.peerStatus, self.fieldw) | ||
2013 | 229 | |||
2014 | 230 | self.spewwin.erase() | ||
2015 | 231 | |||
2016 | 232 | if not spew: | ||
2017 | 233 | errsize = self.spewh | ||
2018 | 234 | if self.errors: | ||
2019 | 235 | self.spewwin.addnstr(0, 0, "error(s):", self.speww, curses.A_BOLD) | ||
2020 | 236 | errsize = len(self.errors) | ||
2021 | 237 | displaysize = min(errsize, self.spewh) | ||
2022 | 238 | displaytop = errsize - displaysize | ||
2023 | 239 | for i in range(displaysize): | ||
2024 | 240 | self.spewwin.addnstr(i, self.labelw, self.errors[displaytop + i], | ||
2025 | 241 | self.speww-self.labelw-1, curses.A_BOLD) | ||
2026 | 242 | else: | ||
2027 | 243 | if self.errors: | ||
2028 | 244 | self.spewwin.addnstr(0, 0, "error:", self.speww, curses.A_BOLD) | ||
2029 | 245 | self.spewwin.addnstr(0, self.labelw, self.errors[-1], | ||
2030 | 246 | self.speww-self.labelw-1, curses.A_BOLD) | ||
2031 | 247 | self.spewwin.addnstr(2, 0, " # IP Upload Download Completed Speed", self.speww, curses.A_BOLD) | ||
2032 | 248 | |||
2033 | 249 | |||
2034 | 250 | if self.spew_scroll_time + SPEW_SCROLL_RATE < clock(): | ||
2035 | 251 | self.spew_scroll_time = clock() | ||
2036 | 252 | if len(spew) > self.spewh-5 or self.spew_scroll_pos > 0: | ||
2037 | 253 | self.spew_scroll_pos += 1 | ||
2038 | 254 | if self.spew_scroll_pos > len(spew): | ||
2039 | 255 | self.spew_scroll_pos = 0 | ||
2040 | 256 | |||
2041 | 257 | for i in range(len(spew)): | ||
2042 | 258 | spew[i]['lineno'] = i+1 | ||
2043 | 259 | spew.append({'lineno': None}) | ||
2044 | 260 | spew = spew[self.spew_scroll_pos:] + spew[:self.spew_scroll_pos] | ||
2045 | 261 | |||
2046 | 262 | for i in range(min(self.spewh - 5, len(spew))): | ||
2047 | 263 | if not spew[i]['lineno']: | ||
2048 | 264 | continue | ||
2049 | 265 | self.spewwin.addnstr(i+3, 0, '%3d' % spew[i]['lineno'], 3) | ||
2050 | 266 | self.spewwin.addnstr(i+3, 4, spew[i]['ip']+spew[i]['direction'], 16) | ||
2051 | 267 | if spew[i]['uprate'] > 100: | ||
2052 | 268 | self.spewwin.addnstr(i+3, 20, '%6.0f KB/s' % (float(spew[i]['uprate']) / 1000), 11) | ||
2053 | 269 | self.spewwin.addnstr(i+3, 32, '-----', 5) | ||
2054 | 270 | if spew[i]['uinterested'] == 1: | ||
2055 | 271 | self.spewwin.addnstr(i+3, 33, 'I', 1) | ||
2056 | 272 | if spew[i]['uchoked'] == 1: | ||
2057 | 273 | self.spewwin.addnstr(i+3, 35, 'C', 1) | ||
2058 | 274 | if spew[i]['downrate'] > 100: | ||
2059 | 275 | self.spewwin.addnstr(i+3, 38, '%6.0f KB/s' % (float(spew[i]['downrate']) / 1000), 11) | ||
2060 | 276 | self.spewwin.addnstr(i+3, 50, '-------', 7) | ||
2061 | 277 | if spew[i]['dinterested'] == 1: | ||
2062 | 278 | self.spewwin.addnstr(i+3, 51, 'I', 1) | ||
2063 | 279 | if spew[i]['dchoked'] == 1: | ||
2064 | 280 | self.spewwin.addnstr(i+3, 53, 'C', 1) | ||
2065 | 281 | if spew[i]['snubbed'] == 1: | ||
2066 | 282 | self.spewwin.addnstr(i+3, 55, 'S', 1) | ||
2067 | 283 | self.spewwin.addnstr(i+3, 58, '%5.1f%%' % (float(int(spew[i]['completed']*1000))/10), 6) | ||
2068 | 284 | if spew[i]['speed'] is not None: | ||
2069 | 285 | self.spewwin.addnstr(i+3, 64, '%5.0f KB/s' % (float(spew[i]['speed'])/1000), 10) | ||
2070 | 286 | |||
2071 | 287 | if statistics is not None: | ||
2072 | 288 | self.spewwin.addnstr(self.spewh-1, 0, | ||
2073 | 289 | 'downloading %d pieces, have %d fragments, %d of %d pieces completed' | ||
2074 | 290 | % ( statistics.storage_active, statistics.storage_dirty, | ||
2075 | 291 | statistics.storage_numcomplete, | ||
2076 | 292 | statistics.storage_totalpieces ), self.speww-1 ) | ||
2077 | 293 | |||
2078 | 294 | curses.panel.update_panels() | ||
2079 | 295 | curses.doupdate() | ||
2080 | 296 | dpflag.set() | ||
2081 | 297 | |||
2082 | 298 | def chooseFile(self, default, size, saveas, dir): | ||
2083 | 299 | self.file = default | ||
2084 | 300 | self.fileSize = fmtsize(size) | ||
2085 | 301 | if saveas == '': | ||
2086 | 302 | saveas = default | ||
2087 | 303 | self.downloadTo = abspath(saveas) | ||
2088 | 304 | return saveas | ||
2089 | 305 | |||
2090 | 306 | def run(scrwin, errlist, params): | ||
2091 | 307 | doneflag = Event() | ||
2092 | 308 | d = CursesDisplayer(scrwin, errlist, doneflag) | ||
2093 | 309 | try: | ||
2094 | 310 | while 1: | ||
2095 | 311 | configdir = ConfigDir('downloadcurses') | ||
2096 | 312 | defaultsToIgnore = ['responsefile', 'url', 'priority'] | ||
2097 | 313 | configdir.setDefaults(defaults,defaultsToIgnore) | ||
2098 | 314 | configdefaults = configdir.loadConfig() | ||
2099 | 315 | defaults.append(('save_options',0, | ||
2100 | 316 | "whether to save the current options as the new default configuration " + | ||
2101 | 317 | "(only for btdownloadcurses.py)")) | ||
2102 | 318 | try: | ||
2103 | 319 | config = parse_params(params, configdefaults) | ||
2104 | 320 | except ValueError, e: | ||
2105 | 321 | d.error('error: ' + str(e) + '\nrun with no args for parameter explanations') | ||
2106 | 322 | break | ||
2107 | 323 | if not config: | ||
2108 | 324 | d.error(get_usage(defaults, d.fieldw, configdefaults)) | ||
2109 | 325 | break | ||
2110 | 326 | if config['save_options']: | ||
2111 | 327 | configdir.saveConfig(config) | ||
2112 | 328 | configdir.deleteOldCacheData(config['expire_cache_data']) | ||
2113 | 329 | |||
2114 | 330 | myid = createPeerID() | ||
2115 | 331 | seed(myid) | ||
2116 | 332 | |||
2117 | 333 | rawserver = RawServer(doneflag, config['timeout_check_interval'], | ||
2118 | 334 | config['timeout'], ipv6_enable = config['ipv6_enabled'], | ||
2119 | 335 | failfunc = d.failed, errorfunc = d.error) | ||
2120 | 336 | |||
2121 | 337 | upnp_type = UPnP_test(config['upnp_nat_access']) | ||
2122 | 338 | while True: | ||
2123 | 339 | try: | ||
2124 | 340 | listen_port = rawserver.find_and_bind(config['minport'], config['maxport'], | ||
2125 | 341 | config['bind'], ipv6_socket_style = config['ipv6_binds_v4'], | ||
2126 | 342 | upnp = upnp_type, randomizer = config['random_port']) | ||
2127 | 343 | break | ||
2128 | 344 | except socketerror, e: | ||
2129 | 345 | if upnp_type and e == UPnP_ERROR: | ||
2130 | 346 | d.error('WARNING: COULD NOT FORWARD VIA UPnP') | ||
2131 | 347 | upnp_type = 0 | ||
2132 | 348 | continue | ||
2133 | 349 | d.error("Couldn't listen - " + str(e)) | ||
2134 | 350 | d.failed() | ||
2135 | 351 | return | ||
2136 | 352 | |||
2137 | 353 | response = get_response(config['responsefile'], config['url'], d.error) | ||
2138 | 354 | if not response: | ||
2139 | 355 | break | ||
2140 | 356 | |||
2141 | 357 | infohash = sha(bencode(response['info'])).digest() | ||
2142 | 358 | |||
2143 | 359 | dow = BT1Download(d.display, d.finished, d.error, d.error, doneflag, | ||
2144 | 360 | config, response, infohash, myid, rawserver, listen_port, | ||
2145 | 361 | configdir) | ||
2146 | 362 | |||
2147 | 363 | if not dow.saveAs(d.chooseFile): | ||
2148 | 364 | break | ||
2149 | 365 | |||
2150 | 366 | if not dow.initFiles(old_style = True): | ||
2151 | 367 | break | ||
2152 | 368 | if not dow.startEngine(): | ||
2153 | 369 | dow.shutdown() | ||
2154 | 370 | break | ||
2155 | 371 | dow.startRerequester() | ||
2156 | 372 | dow.autoStats() | ||
2157 | 373 | |||
2158 | 374 | if not dow.am_I_finished(): | ||
2159 | 375 | d.display(activity = 'connecting to peers') | ||
2160 | 376 | rawserver.listen_forever(dow.getPortHandler()) | ||
2161 | 377 | d.display(activity = 'shutting down') | ||
2162 | 378 | dow.shutdown() | ||
2163 | 379 | break | ||
2164 | 380 | |||
2165 | 381 | except KeyboardInterrupt: | ||
2166 | 382 | # ^C to exit.. | ||
2167 | 383 | pass | ||
2168 | 384 | try: | ||
2169 | 385 | rawserver.shutdown() | ||
2170 | 386 | except: | ||
2171 | 387 | pass | ||
2172 | 388 | if not d.done: | ||
2173 | 389 | d.failed() | ||
2174 | 390 | |||
2175 | 391 | |||
2176 | 392 | if __name__ == '__main__': | ||
2177 | 393 | if argv[1:] == ['--version']: | ||
2178 | 394 | print version | ||
2179 | 395 | exit(0) | ||
2180 | 396 | if len(argv) <= 1: | ||
2181 | 397 | print "Usage: btdownloadcurses.py <global options>\n" | ||
2182 | 398 | print get_usage(defaults) | ||
2183 | 399 | exit(1) | ||
2184 | 400 | |||
2185 | 401 | errlist = [] | ||
2186 | 402 | curses_wrapper(run, errlist, argv[1:]) | ||
2187 | 403 | |||
2188 | 404 | if errlist: | ||
2189 | 405 | print "These errors occurred during execution:" | ||
2190 | 406 | for error in errlist: | ||
2191 | 407 | print error | ||
2192 | 408 | \ No newline at end of file | 0 | \ No newline at end of file |
2193 | 409 | 1 | ||
2194 | === removed directory '.pc/09_timtuckerfixes.dpatch' | |||
2195 | === removed directory '.pc/09_timtuckerfixes.dpatch/BitTornado' | |||
2196 | === removed directory '.pc/09_timtuckerfixes.dpatch/BitTornado/BT1' | |||
2197 | === removed file '.pc/09_timtuckerfixes.dpatch/BitTornado/BT1/Connecter.py' | |||
2198 | --- .pc/09_timtuckerfixes.dpatch/BitTornado/BT1/Connecter.py 2010-03-21 14:36:30 +0000 | |||
2199 | +++ .pc/09_timtuckerfixes.dpatch/BitTornado/BT1/Connecter.py 1970-01-01 00:00:00 +0000 | |||
2200 | @@ -1,328 +0,0 @@ | |||
2201 | 1 | # Written by Bram Cohen | ||
2202 | 2 | # see LICENSE.txt for license information | ||
2203 | 3 | |||
2204 | 4 | from BitTornado.bitfield import Bitfield | ||
2205 | 5 | from BitTornado.clock import clock | ||
2206 | 6 | from binascii import b2a_hex | ||
2207 | 7 | |||
2208 | 8 | try: | ||
2209 | 9 | True | ||
2210 | 10 | except: | ||
2211 | 11 | True = 1 | ||
2212 | 12 | False = 0 | ||
2213 | 13 | |||
2214 | 14 | DEBUG1 = False | ||
2215 | 15 | DEBUG2 = False | ||
2216 | 16 | |||
2217 | 17 | def toint(s): | ||
2218 | 18 | return long(b2a_hex(s), 16) | ||
2219 | 19 | |||
2220 | 20 | def tobinary(i): | ||
2221 | 21 | return (chr(i >> 24) + chr((i >> 16) & 0xFF) + | ||
2222 | 22 | chr((i >> 8) & 0xFF) + chr(i & 0xFF)) | ||
2223 | 23 | |||
2224 | 24 | CHOKE = chr(0) | ||
2225 | 25 | UNCHOKE = chr(1) | ||
2226 | 26 | INTERESTED = chr(2) | ||
2227 | 27 | NOT_INTERESTED = chr(3) | ||
2228 | 28 | # index | ||
2229 | 29 | HAVE = chr(4) | ||
2230 | 30 | # index, bitfield | ||
2231 | 31 | BITFIELD = chr(5) | ||
2232 | 32 | # index, begin, length | ||
2233 | 33 | REQUEST = chr(6) | ||
2234 | 34 | # index, begin, piece | ||
2235 | 35 | PIECE = chr(7) | ||
2236 | 36 | # index, begin, piece | ||
2237 | 37 | CANCEL = chr(8) | ||
2238 | 38 | |||
2239 | 39 | class Connection: | ||
2240 | 40 | def __init__(self, connection, connecter, ccount): | ||
2241 | 41 | self.connection = connection | ||
2242 | 42 | self.connecter = connecter | ||
2243 | 43 | self.ccount = ccount | ||
2244 | 44 | self.got_anything = False | ||
2245 | 45 | self.next_upload = None | ||
2246 | 46 | self.outqueue = [] | ||
2247 | 47 | self.partial_message = None | ||
2248 | 48 | self.download = None | ||
2249 | 49 | self.send_choke_queued = False | ||
2250 | 50 | self.just_unchoked = None | ||
2251 | 51 | |||
2252 | 52 | def get_ip(self, real=False): | ||
2253 | 53 | return self.connection.get_ip(real) | ||
2254 | 54 | |||
2255 | 55 | def get_id(self): | ||
2256 | 56 | return self.connection.get_id() | ||
2257 | 57 | |||
2258 | 58 | def get_readable_id(self): | ||
2259 | 59 | return self.connection.get_readable_id() | ||
2260 | 60 | |||
2261 | 61 | def close(self): | ||
2262 | 62 | if DEBUG1: | ||
2263 | 63 | print (self.ccount,'connection closed') | ||
2264 | 64 | self.connection.close() | ||
2265 | 65 | |||
2266 | 66 | def is_locally_initiated(self): | ||
2267 | 67 | return self.connection.is_locally_initiated() | ||
2268 | 68 | |||
2269 | 69 | def is_encrypted(self): | ||
2270 | 70 | return self.connection.is_encrypted() | ||
2271 | 71 | |||
2272 | 72 | def send_interested(self): | ||
2273 | 73 | self._send_message(INTERESTED) | ||
2274 | 74 | |||
2275 | 75 | def send_not_interested(self): | ||
2276 | 76 | self._send_message(NOT_INTERESTED) | ||
2277 | 77 | |||
2278 | 78 | def send_choke(self): | ||
2279 | 79 | if self.partial_message: | ||
2280 | 80 | self.send_choke_queued = True | ||
2281 | 81 | else: | ||
2282 | 82 | self._send_message(CHOKE) | ||
2283 | 83 | self.upload.choke_sent() | ||
2284 | 84 | self.just_unchoked = 0 | ||
2285 | 85 | |||
2286 | 86 | def send_unchoke(self): | ||
2287 | 87 | if self.send_choke_queued: | ||
2288 | 88 | self.send_choke_queued = False | ||
2289 | 89 | if DEBUG1: | ||
2290 | 90 | print (self.ccount,'CHOKE SUPPRESSED') | ||
2291 | 91 | else: | ||
2292 | 92 | self._send_message(UNCHOKE) | ||
2293 | 93 | if ( self.partial_message or self.just_unchoked is None | ||
2294 | 94 | or not self.upload.interested or self.download.active_requests ): | ||
2295 | 95 | self.just_unchoked = 0 | ||
2296 | 96 | else: | ||
2297 | 97 | self.just_unchoked = clock() | ||
2298 | 98 | |||
2299 | 99 | def send_request(self, index, begin, length): | ||
2300 | 100 | self._send_message(REQUEST + tobinary(index) + | ||
2301 | 101 | tobinary(begin) + tobinary(length)) | ||
2302 | 102 | if DEBUG1: | ||
2303 | 103 | print (self.ccount,'sent request',index,begin,begin+length) | ||
2304 | 104 | |||
2305 | 105 | def send_cancel(self, index, begin, length): | ||
2306 | 106 | self._send_message(CANCEL + tobinary(index) + | ||
2307 | 107 | tobinary(begin) + tobinary(length)) | ||
2308 | 108 | if DEBUG1: | ||
2309 | 109 | print (self.ccount,'sent cancel',index,begin,begin+length) | ||
2310 | 110 | |||
2311 | 111 | def send_bitfield(self, bitfield): | ||
2312 | 112 | self._send_message(BITFIELD + bitfield) | ||
2313 | 113 | |||
2314 | 114 | def send_have(self, index): | ||
2315 | 115 | self._send_message(HAVE + tobinary(index)) | ||
2316 | 116 | |||
2317 | 117 | def send_keepalive(self): | ||
2318 | 118 | self._send_message('') | ||
2319 | 119 | |||
2320 | 120 | def _send_message(self, s): | ||
2321 | 121 | if DEBUG2: | ||
2322 | 122 | if s: | ||
2323 | 123 | print (self.ccount,'SENDING MESSAGE',ord(s[0]),len(s)) | ||
2324 | 124 | else: | ||
2325 | 125 | print (self.ccount,'SENDING MESSAGE',-1,0) | ||
2326 | 126 | s = tobinary(len(s))+s | ||
2327 | 127 | if self.partial_message: | ||
2328 | 128 | self.outqueue.append(s) | ||
2329 | 129 | else: | ||
2330 | 130 | self.connection.send_message_raw(s) | ||
2331 | 131 | |||
2332 | 132 | def send_partial(self, bytes): | ||
2333 | 133 | if self.connection.closed: | ||
2334 | 134 | return 0 | ||
2335 | 135 | if self.partial_message is None: | ||
2336 | 136 | s = self.upload.get_upload_chunk() | ||
2337 | 137 | if s is None: | ||
2338 | 138 | return 0 | ||
2339 | 139 | index, begin, piece = s | ||
2340 | 140 | self.partial_message = ''.join(( | ||
2341 | 141 | tobinary(len(piece) + 9), PIECE, | ||
2342 | 142 | tobinary(index), tobinary(begin), piece.tostring() )) | ||
2343 | 143 | if DEBUG1: | ||
2344 | 144 | print (self.ccount,'sending chunk',index,begin,begin+len(piece)) | ||
2345 | 145 | |||
2346 | 146 | if bytes < len(self.partial_message): | ||
2347 | 147 | self.connection.send_message_raw(self.partial_message[:bytes]) | ||
2348 | 148 | self.partial_message = self.partial_message[bytes:] | ||
2349 | 149 | return bytes | ||
2350 | 150 | |||
2351 | 151 | q = [self.partial_message] | ||
2352 | 152 | self.partial_message = None | ||
2353 | 153 | if self.send_choke_queued: | ||
2354 | 154 | self.send_choke_queued = False | ||
2355 | 155 | self.outqueue.append(tobinary(1)+CHOKE) | ||
2356 | 156 | self.upload.choke_sent() | ||
2357 | 157 | self.just_unchoked = 0 | ||
2358 | 158 | q.extend(self.outqueue) | ||
2359 | 159 | self.outqueue = [] | ||
2360 | 160 | q = ''.join(q) | ||
2361 | 161 | self.connection.send_message_raw(q) | ||
2362 | 162 | return len(q) | ||
2363 | 163 | |||
2364 | 164 | def get_upload(self): | ||
2365 | 165 | return self.upload | ||
2366 | 166 | |||
2367 | 167 | def get_download(self): | ||
2368 | 168 | return self.download | ||
2369 | 169 | |||
2370 | 170 | def set_download(self, download): | ||
2371 | 171 | self.download = download | ||
2372 | 172 | |||
2373 | 173 | def backlogged(self): | ||
2374 | 174 | return not self.connection.is_flushed() | ||
2375 | 175 | |||
2376 | 176 | def got_request(self, i, p, l): | ||
2377 | 177 | self.upload.got_request(i, p, l) | ||
2378 | 178 | if self.just_unchoked: | ||
2379 | 179 | self.connecter.ratelimiter.ping(clock() - self.just_unchoked) | ||
2380 | 180 | self.just_unchoked = 0 | ||
2381 | 181 | |||
2382 | 182 | |||
2383 | 183 | |||
2384 | 184 | |||
2385 | 185 | class Connecter: | ||
2386 | 186 | def __init__(self, make_upload, downloader, choker, numpieces, | ||
2387 | 187 | totalup, config, ratelimiter, sched = None): | ||
2388 | 188 | self.downloader = downloader | ||
2389 | 189 | self.make_upload = make_upload | ||
2390 | 190 | self.choker = choker | ||
2391 | 191 | self.numpieces = numpieces | ||
2392 | 192 | self.config = config | ||
2393 | 193 | self.ratelimiter = ratelimiter | ||
2394 | 194 | self.rate_capped = False | ||
2395 | 195 | self.sched = sched | ||
2396 | 196 | self.totalup = totalup | ||
2397 | 197 | self.rate_capped = False | ||
2398 | 198 | self.connections = {} | ||
2399 | 199 | self.external_connection_made = 0 | ||
2400 | 200 | self.ccount = 0 | ||
2401 | 201 | |||
2402 | 202 | def how_many_connections(self): | ||
2403 | 203 | return len(self.connections) | ||
2404 | 204 | |||
2405 | 205 | def connection_made(self, connection): | ||
2406 | 206 | self.ccount += 1 | ||
2407 | 207 | c = Connection(connection, self, self.ccount) | ||
2408 | 208 | if DEBUG2: | ||
2409 | 209 | print (c.ccount,'connection made') | ||
2410 | 210 | self.connections[connection] = c | ||
2411 | 211 | c.upload = self.make_upload(c, self.ratelimiter, self.totalup) | ||
2412 | 212 | c.download = self.downloader.make_download(c) | ||
2413 | 213 | self.choker.connection_made(c) | ||
2414 | 214 | return c | ||
2415 | 215 | |||
2416 | 216 | def connection_lost(self, connection): | ||
2417 | 217 | c = self.connections[connection] | ||
2418 | 218 | if DEBUG2: | ||
2419 | 219 | print (c.ccount,'connection closed') | ||
2420 | 220 | del self.connections[connection] | ||
2421 | 221 | if c.download: | ||
2422 | 222 | c.download.disconnected() | ||
2423 | 223 | self.choker.connection_lost(c) | ||
2424 | 224 | |||
2425 | 225 | def connection_flushed(self, connection): | ||
2426 | 226 | conn = self.connections[connection] | ||
2427 | 227 | if conn.next_upload is None and (conn.partial_message is not None | ||
2428 | 228 | or len(conn.upload.buffer) > 0): | ||
2429 | 229 | self.ratelimiter.queue(conn) | ||
2430 | 230 | |||
2431 | 231 | def got_piece(self, i): | ||
2432 | 232 | for co in self.connections.values(): | ||
2433 | 233 | co.send_have(i) | ||
2434 | 234 | |||
2435 | 235 | def got_message(self, connection, message): | ||
2436 | 236 | c = self.connections[connection] | ||
2437 | 237 | t = message[0] | ||
2438 | 238 | if DEBUG2: | ||
2439 | 239 | print (c.ccount,'message received',ord(t)) | ||
2440 | 240 | if t == BITFIELD and c.got_anything: | ||
2441 | 241 | if DEBUG2: | ||
2442 | 242 | print (c.ccount,'misplaced bitfield') | ||
2443 | 243 | connection.close() | ||
2444 | 244 | return | ||
2445 | 245 | c.got_anything = True | ||
2446 | 246 | if (t in [CHOKE, UNCHOKE, INTERESTED, NOT_INTERESTED] and | ||
2447 | 247 | len(message) != 1): | ||
2448 | 248 | if DEBUG2: | ||
2449 | 249 | print (c.ccount,'bad message length') | ||
2450 | 250 | connection.close() | ||
2451 | 251 | return | ||
2452 | 252 | if t == CHOKE: | ||
2453 | 253 | c.download.got_choke() | ||
2454 | 254 | elif t == UNCHOKE: | ||
2455 | 255 | c.download.got_unchoke() | ||
2456 | 256 | elif t == INTERESTED: | ||
2457 | 257 | if not c.download.have.complete(): | ||
2458 | 258 | c.upload.got_interested() | ||
2459 | 259 | elif t == NOT_INTERESTED: | ||
2460 | 260 | c.upload.got_not_interested() | ||
2461 | 261 | elif t == HAVE: | ||
2462 | 262 | if len(message) != 5: | ||
2463 | 263 | if DEBUG2: | ||
2464 | 264 | print (c.ccount,'bad message length') | ||
2465 | 265 | connection.close() | ||
2466 | 266 | return | ||
2467 | 267 | i = toint(message[1:]) | ||
2468 | 268 | if i >= self.numpieces: | ||
2469 | 269 | if DEBUG2: | ||
2470 | 270 | print (c.ccount,'bad piece number') | ||
2471 | 271 | connection.close() | ||
2472 | 272 | return | ||
2473 | 273 | if c.download.got_have(i): | ||
2474 | 274 | c.upload.got_not_interested() | ||
2475 | 275 | elif t == BITFIELD: | ||
2476 | 276 | try: | ||
2477 | 277 | b = Bitfield(self.numpieces, message[1:]) | ||
2478 | 278 | except ValueError: | ||
2479 | 279 | if DEBUG2: | ||
2480 | 280 | print (c.ccount,'bad bitfield') | ||
2481 | 281 | connection.close() | ||
2482 | 282 | return | ||
2483 | 283 | if c.download.got_have_bitfield(b): | ||
2484 | 284 | c.upload.got_not_interested() | ||
2485 | 285 | elif t == REQUEST: | ||
2486 | 286 | if len(message) != 13: | ||
2487 | 287 | if DEBUG2: | ||
2488 | 288 | print (c.ccount,'bad message length') | ||
2489 | 289 | connection.close() | ||
2490 | 290 | return | ||
2491 | 291 | i = toint(message[1:5]) | ||
2492 | 292 | if i >= self.numpieces: | ||
2493 | 293 | if DEBUG2: | ||
2494 | 294 | print (c.ccount,'bad piece number') | ||
2495 | 295 | connection.close() | ||
2496 | 296 | return | ||
2497 | 297 | c.got_request(i, toint(message[5:9]), | ||
2498 | 298 | toint(message[9:])) | ||
2499 | 299 | elif t == CANCEL: | ||
2500 | 300 | if len(message) != 13: | ||
2501 | 301 | if DEBUG2: | ||
2502 | 302 | print (c.ccount,'bad message length') | ||
2503 | 303 | connection.close() | ||
2504 | 304 | return | ||
2505 | 305 | i = toint(message[1:5]) | ||
2506 | 306 | if i >= self.numpieces: | ||
2507 | 307 | if DEBUG2: | ||
2508 | 308 | print (c.ccount,'bad piece number') | ||
2509 | 309 | connection.close() | ||
2510 | 310 | return | ||
2511 | 311 | c.upload.got_cancel(i, toint(message[5:9]), | ||
2512 | 312 | toint(message[9:])) | ||
2513 | 313 | elif t == PIECE: | ||
2514 | 314 | if len(message) <= 9: | ||
2515 | 315 | if DEBUG2: | ||
2516 | 316 | print (c.ccount,'bad message length') | ||
2517 | 317 | connection.close() | ||
2518 | 318 | return | ||
2519 | 319 | i = toint(message[1:5]) | ||
2520 | 320 | if i >= self.numpieces: | ||
2521 | 321 | if DEBUG2: | ||
2522 | 322 | print (c.ccount,'bad piece number') | ||
2523 | 323 | connection.close() | ||
2524 | 324 | return | ||
2525 | 325 | if c.download.got_piece(i, toint(message[5:9]), message[9:]): | ||
2526 | 326 | self.got_piece(i) | ||
2527 | 327 | else: | ||
2528 | 328 | connection.close() | ||
2529 | 329 | 0 | ||
2530 | === removed file '.pc/09_timtuckerfixes.dpatch/BitTornado/BT1/Encrypter.py' | |||
2531 | --- .pc/09_timtuckerfixes.dpatch/BitTornado/BT1/Encrypter.py 2010-03-21 14:36:30 +0000 | |||
2532 | +++ .pc/09_timtuckerfixes.dpatch/BitTornado/BT1/Encrypter.py 1970-01-01 00:00:00 +0000 | |||
2533 | @@ -1,657 +0,0 @@ | |||
2534 | 1 | # Written by Bram Cohen | ||
2535 | 2 | # see LICENSE.txt for license information | ||
2536 | 3 | |||
2537 | 4 | from cStringIO import StringIO | ||
2538 | 5 | from binascii import b2a_hex | ||
2539 | 6 | from socket import error as socketerror | ||
2540 | 7 | from urllib import quote | ||
2541 | 8 | from traceback import print_exc | ||
2542 | 9 | from BitTornado.BTcrypto import Crypto | ||
2543 | 10 | |||
2544 | 11 | try: | ||
2545 | 12 | True | ||
2546 | 13 | except: | ||
2547 | 14 | True = 1 | ||
2548 | 15 | False = 0 | ||
2549 | 16 | bool = lambda x: not not x | ||
2550 | 17 | |||
2551 | 18 | DEBUG = False | ||
2552 | 19 | |||
2553 | 20 | MAX_INCOMPLETE = 8 | ||
2554 | 21 | |||
2555 | 22 | protocol_name = 'BitTorrent protocol' | ||
2556 | 23 | option_pattern = chr(0)*8 | ||
2557 | 24 | |||
2558 | 25 | def toint(s): | ||
2559 | 26 | return long(b2a_hex(s), 16) | ||
2560 | 27 | |||
2561 | 28 | def tobinary16(i): | ||
2562 | 29 | return chr((i >> 8) & 0xFF) + chr(i & 0xFF) | ||
2563 | 30 | |||
2564 | 31 | hexchars = '0123456789ABCDEF' | ||
2565 | 32 | hexmap = [] | ||
2566 | 33 | for i in xrange(256): | ||
2567 | 34 | hexmap.append(hexchars[(i&0xF0)/16]+hexchars[i&0x0F]) | ||
2568 | 35 | |||
2569 | 36 | def tohex(s): | ||
2570 | 37 | r = [] | ||
2571 | 38 | for c in s: | ||
2572 | 39 | r.append(hexmap[ord(c)]) | ||
2573 | 40 | return ''.join(r) | ||
2574 | 41 | |||
2575 | 42 | def make_readable(s): | ||
2576 | 43 | if not s: | ||
2577 | 44 | return '' | ||
2578 | 45 | if quote(s).find('%') >= 0: | ||
2579 | 46 | return tohex(s) | ||
2580 | 47 | return '"'+s+'"' | ||
2581 | 48 | |||
2582 | 49 | |||
2583 | 50 | class IncompleteCounter: | ||
2584 | 51 | def __init__(self): | ||
2585 | 52 | self.c = 0 | ||
2586 | 53 | def increment(self): | ||
2587 | 54 | self.c += 1 | ||
2588 | 55 | def decrement(self): | ||
2589 | 56 | self.c -= 1 | ||
2590 | 57 | def toomany(self): | ||
2591 | 58 | return self.c >= MAX_INCOMPLETE | ||
2592 | 59 | |||
2593 | 60 | incompletecounter = IncompleteCounter() | ||
2594 | 61 | |||
2595 | 62 | |||
2596 | 63 | # header, options, download id, my id, [length, message] | ||
2597 | 64 | |||
2598 | 65 | class Connection: | ||
2599 | 66 | def __init__(self, Encoder, connection, id, | ||
2600 | 67 | ext_handshake=False, encrypted = None, options = None): | ||
2601 | 68 | self.Encoder = Encoder | ||
2602 | 69 | self.connection = connection | ||
2603 | 70 | self.connecter = Encoder.connecter | ||
2604 | 71 | self.id = id | ||
2605 | 72 | self.locally_initiated = (id != None) | ||
2606 | 73 | self.readable_id = make_readable(id) | ||
2607 | 74 | self.complete = False | ||
2608 | 75 | self.keepalive = lambda: None | ||
2609 | 76 | self.closed = False | ||
2610 | 77 | self.buffer = '' | ||
2611 | 78 | self.bufferlen = None | ||
2612 | 79 | self.log = None | ||
2613 | 80 | self.read = self._read | ||
2614 | 81 | self.write = self._write | ||
2615 | 82 | self.cryptmode = 0 | ||
2616 | 83 | self.encrypter = None | ||
2617 | 84 | if self.locally_initiated: | ||
2618 | 85 | incompletecounter.increment() | ||
2619 | 86 | if encrypted: | ||
2620 | 87 | self.encrypted = True | ||
2621 | 88 | self.encrypter = Crypto(True) | ||
2622 | 89 | self.write(self.encrypter.pubkey+self.encrypter.padding()) | ||
2623 | 90 | else: | ||
2624 | 91 | self.encrypted = False | ||
2625 | 92 | self.write(chr(len(protocol_name)) + protocol_name + | ||
2626 | 93 | option_pattern + self.Encoder.download_id ) | ||
2627 | 94 | self.next_len, self.next_func = 1+len(protocol_name), self.read_header | ||
2628 | 95 | elif ext_handshake: | ||
2629 | 96 | self.Encoder.connecter.external_connection_made += 1 | ||
2630 | 97 | if encrypted: # passed an already running encrypter | ||
2631 | 98 | self.encrypter = encrypted | ||
2632 | 99 | self.encrypted = True | ||
2633 | 100 | self._start_crypto() | ||
2634 | 101 | self.next_len, self.next_func = 14, self.read_crypto_block3c | ||
2635 | 102 | else: | ||
2636 | 103 | self.encrypted = False | ||
2637 | 104 | self.options = options | ||
2638 | 105 | self.write(self.Encoder.my_id) | ||
2639 | 106 | self.next_len, self.next_func = 20, self.read_peer_id | ||
2640 | 107 | else: | ||
2641 | 108 | self.encrypted = None # don't know yet | ||
2642 | 109 | self.next_len, self.next_func = 1+len(protocol_name), self.read_header | ||
2643 | 110 | self.Encoder.raw_server.add_task(self._auto_close, 30) | ||
2644 | 111 | |||
2645 | 112 | |||
2646 | 113 | def _log_start(self): # only called with DEBUG = True | ||
2647 | 114 | self.log = open('peerlog.'+self.get_ip()+'.txt','a') | ||
2648 | 115 | self.log.write('connected - ') | ||
2649 | 116 | if self.locally_initiated: | ||
2650 | 117 | self.log.write('outgoing\n') | ||
2651 | 118 | else: | ||
2652 | 119 | self.log.write('incoming\n') | ||
2653 | 120 | self._logwritefunc = self.write | ||
2654 | 121 | self.write = self._log_write | ||
2655 | 122 | |||
2656 | 123 | def _log_write(self, s): | ||
2657 | 124 | self.log.write('w:'+b2a_hex(s)+'\n') | ||
2658 | 125 | self._logwritefunc(s) | ||
2659 | 126 | |||
2660 | 127 | |||
2661 | 128 | def get_ip(self, real=False): | ||
2662 | 129 | return self.connection.get_ip(real) | ||
2663 | 130 | |||
2664 | 131 | def get_id(self): | ||
2665 | 132 | return self.id | ||
2666 | 133 | |||
2667 | 134 | def get_readable_id(self): | ||
2668 | 135 | return self.readable_id | ||
2669 | 136 | |||
2670 | 137 | def is_locally_initiated(self): | ||
2671 | 138 | return self.locally_initiated | ||
2672 | 139 | |||
2673 | 140 | def is_encrypted(self): | ||
2674 | 141 | return bool(self.encrypted) | ||
2675 | 142 | |||
2676 | 143 | def is_flushed(self): | ||
2677 | 144 | return self.connection.is_flushed() | ||
2678 | 145 | |||
2679 | 146 | def _read_header(self, s): | ||
2680 | 147 | if s == chr(len(protocol_name))+protocol_name: | ||
2681 | 148 | return 8, self.read_options | ||
2682 | 149 | return None | ||
2683 | 150 | |||
2684 | 151 | def read_header(self, s): | ||
2685 | 152 | if self._read_header(s): | ||
2686 | 153 | if self.encrypted or self.Encoder.config['crypto_stealth']: | ||
2687 | 154 | return None | ||
2688 | 155 | return 8, self.read_options | ||
2689 | 156 | if self.locally_initiated and not self.encrypted: | ||
2690 | 157 | return None | ||
2691 | 158 | elif not self.Encoder.config['crypto_allowed']: | ||
2692 | 159 | return None | ||
2693 | 160 | if not self.encrypted: | ||
2694 | 161 | self.encrypted = True | ||
2695 | 162 | self.encrypter = Crypto(self.locally_initiated) | ||
2696 | 163 | self._write_buffer(s) | ||
2697 | 164 | return self.encrypter.keylength, self.read_crypto_header | ||
2698 | 165 | |||
2699 | 166 | ################## ENCRYPTION SUPPORT ###################### | ||
2700 | 167 | |||
2701 | 168 | def _start_crypto(self): | ||
2702 | 169 | self.encrypter.setrawaccess(self._read,self._write) | ||
2703 | 170 | self.write = self.encrypter.write | ||
2704 | 171 | self.read = self.encrypter.read | ||
2705 | 172 | if self.buffer: | ||
2706 | 173 | self.buffer = self.encrypter.decrypt(self.buffer) | ||
2707 | 174 | |||
2708 | 175 | def _end_crypto(self): | ||
2709 | 176 | self.read = self._read | ||
2710 | 177 | self.write = self._write | ||
2711 | 178 | self.encrypter = None | ||
2712 | 179 | |||
2713 | 180 | def read_crypto_header(self, s): | ||
2714 | 181 | self.encrypter.received_key(s) | ||
2715 | 182 | self.encrypter.set_skey(self.Encoder.download_id) | ||
2716 | 183 | if self.locally_initiated: | ||
2717 | 184 | if self.Encoder.config['crypto_only']: | ||
2718 | 185 | cryptmode = '\x00\x00\x00\x02' # full stream encryption | ||
2719 | 186 | else: | ||
2720 | 187 | cryptmode = '\x00\x00\x00\x03' # header or full stream | ||
2721 | 188 | padc = self.encrypter.padding() | ||
2722 | 189 | self.write( self.encrypter.block3a | ||
2723 | 190 | + self.encrypter.block3b | ||
2724 | 191 | + self.encrypter.encrypt( | ||
2725 | 192 | ('\x00'*8) # VC | ||
2726 | 193 | + cryptmode # acceptable crypto modes | ||
2727 | 194 | + tobinary16(len(padc)) | ||
2728 | 195 | + padc # PadC | ||
2729 | 196 | + '\x00\x00' ) ) # no initial payload data | ||
2730 | 197 | self._max_search = 520 | ||
2731 | 198 | return 1, self.read_crypto_block4a | ||
2732 | 199 | self.write(self.encrypter.pubkey+self.encrypter.padding()) | ||
2733 | 200 | self._max_search = 520 | ||
2734 | 201 | return 0, self.read_crypto_block3a | ||
2735 | 202 | |||
2736 | 203 | def _search_for_pattern(self, s, pat): | ||
2737 | 204 | p = s.find(pat) | ||
2738 | 205 | if p < 0: | ||
2739 | 206 | if len(s) >= len(pat): | ||
2740 | 207 | self._max_search -= len(s)+1-len(pat) | ||
2741 | 208 | if self._max_search < 0: | ||
2742 | 209 | self.close() | ||
2743 | 210 | return False | ||
2744 | 211 | self._write_buffer(s[1-len(pat):]) | ||
2745 | 212 | return False | ||
2746 | 213 | self._write_buffer(s[p+len(pat):]) | ||
2747 | 214 | return True | ||
2748 | 215 | |||
2749 | 216 | ### INCOMING CONNECTION ### | ||
2750 | 217 | |||
2751 | 218 | def read_crypto_block3a(self, s): | ||
2752 | 219 | if not self._search_for_pattern(s,self.encrypter.block3a): | ||
2753 | 220 | return -1, self.read_crypto_block3a # wait for more data | ||
2754 | 221 | return len(self.encrypter.block3b), self.read_crypto_block3b | ||
2755 | 222 | |||
2756 | 223 | def read_crypto_block3b(self, s): | ||
2757 | 224 | if s != self.encrypter.block3b: | ||
2758 | 225 | return None | ||
2759 | 226 | self.Encoder.connecter.external_connection_made += 1 | ||
2760 | 227 | self._start_crypto() | ||
2761 | 228 | return 14, self.read_crypto_block3c | ||
2762 | 229 | |||
2763 | 230 | def read_crypto_block3c(self, s): | ||
2764 | 231 | if s[:8] != ('\x00'*8): # check VC | ||
2765 | 232 | return None | ||
2766 | 233 | self.cryptmode = toint(s[8:12]) % 4 | ||
2767 | 234 | if self.cryptmode == 0: | ||
2768 | 235 | return None # no encryption selected | ||
2769 | 236 | if ( self.cryptmode == 1 # only header encryption | ||
2770 | 237 | and self.Encoder.config['crypto_only'] ): | ||
2771 | 238 | return None | ||
2772 | 239 | padlen = (ord(s[12])<<8)+ord(s[13]) | ||
2773 | 240 | if padlen > 512: | ||
2774 | 241 | return None | ||
2775 | 242 | return padlen+2, self.read_crypto_pad3 | ||
2776 | 243 | |||
2777 | 244 | def read_crypto_pad3(self, s): | ||
2778 | 245 | s = s[-2:] | ||
2779 | 246 | ialen = (ord(s[0])<<8)+ord(s[1]) | ||
2780 | 247 | if ialen > 65535: | ||
2781 | 248 | return None | ||
2782 | 249 | if self.cryptmode == 1: | ||
2783 | 250 | cryptmode = '\x00\x00\x00\x01' # header only encryption | ||
2784 | 251 | else: | ||
2785 | 252 | cryptmode = '\x00\x00\x00\x02' # full stream encryption | ||
2786 | 253 | padd = self.encrypter.padding() | ||
2787 | 254 | self.write( ('\x00'*8) # VC | ||
2788 | 255 | + cryptmode # encryption mode | ||
2789 | 256 | + tobinary16(len(padd)) | ||
2790 | 257 | + padd ) # PadD | ||
2791 | 258 | if ialen: | ||
2792 | 259 | return ialen, self.read_crypto_ia | ||
2793 | 260 | return self.read_crypto_block3done() | ||
2794 | 261 | |||
2795 | 262 | def read_crypto_ia(self, s): | ||
2796 | 263 | if DEBUG: | ||
2797 | 264 | self._log_start() | ||
2798 | 265 | self.log.write('r:'+b2a_hex(s)+'(ia)\n') | ||
2799 | 266 | if self.buffer: | ||
2800 | 267 | self.log.write('r:'+b2a_hex(self.buffer)+'(buffer)\n') | ||
2801 | 268 | return self.read_crypto_block3done(s) | ||
2802 | 269 | |||
2803 | 270 | def read_crypto_block3done(self, ia=''): | ||
2804 | 271 | if DEBUG: | ||
2805 | 272 | if not self.log: | ||
2806 | 273 | self._log_start() | ||
2807 | 274 | if self.cryptmode == 1: # only handshake encryption | ||
2808 | 275 | assert not self.buffer # oops; check for exceptions to this | ||
2809 | 276 | self._end_crypto() | ||
2810 | 277 | if ia: | ||
2811 | 278 | self._write_buffer(ia) | ||
2812 | 279 | return 1+len(protocol_name), self.read_encrypted_header | ||
2813 | 280 | |||
2814 | 281 | ### OUTGOING CONNECTION ### | ||
2815 | 282 | |||
2816 | 283 | def read_crypto_block4a(self, s): | ||
2817 | 284 | if not self._search_for_pattern(s,self.encrypter.VC_pattern()): | ||
2818 | 285 | return -1, self.read_crypto_block4a # wait for more data | ||
2819 | 286 | self._start_crypto() | ||
2820 | 287 | return 6, self.read_crypto_block4b | ||
2821 | 288 | |||
2822 | 289 | def read_crypto_block4b(self, s): | ||
2823 | 290 | self.cryptmode = toint(s[:4]) % 4 | ||
2824 | 291 | if self.cryptmode == 1: # only header encryption | ||
2825 | 292 | if self.Encoder.config['crypto_only']: | ||
2826 | 293 | return None | ||
2827 | 294 | elif self.cryptmode != 2: | ||
2828 | 295 | return None # unknown encryption | ||
2829 | 296 | padlen = (ord(s[4])<<8)+ord(s[5]) | ||
2830 | 297 | if padlen > 512: | ||
2831 | 298 | return None | ||
2832 | 299 | if padlen: | ||
2833 | 300 | return padlen, self.read_crypto_pad4 | ||
2834 | 301 | return self.read_crypto_block4done() | ||
2835 | 302 | |||
2836 | 303 | def read_crypto_pad4(self, s): | ||
2837 | 304 | # discard data | ||
2838 | 305 | return self.read_crypto_block4done() | ||
2839 | 306 | |||
2840 | 307 | def read_crypto_block4done(self): | ||
2841 | 308 | if DEBUG: | ||
2842 | 309 | self._log_start() | ||
2843 | 310 | if self.cryptmode == 1: # only handshake encryption | ||
2844 | 311 | if not self.buffer: # oops; check for exceptions to this | ||
2845 | 312 | return None | ||
2846 | 313 | self._end_crypto() | ||
2847 | 314 | self.write(chr(len(protocol_name)) + protocol_name + | ||
2848 | 315 | option_pattern + self.Encoder.download_id) | ||
2849 | 316 | return 1+len(protocol_name), self.read_encrypted_header | ||
2850 | 317 | |||
2851 | 318 | ### START PROTOCOL OVER ENCRYPTED CONNECTION ### | ||
2852 | 319 | |||
2853 | 320 | def read_encrypted_header(self, s): | ||
2854 | 321 | return self._read_header(s) | ||
2855 | 322 | |||
2856 | 323 | ################################################ | ||
2857 | 324 | |||
2858 | 325 | def read_options(self, s): | ||
2859 | 326 | self.options = s | ||
2860 | 327 | return 20, self.read_download_id | ||
2861 | 328 | |||
2862 | 329 | def read_download_id(self, s): | ||
2863 | 330 | if ( s != self.Encoder.download_id | ||
2864 | 331 | or not self.Encoder.check_ip(ip=self.get_ip()) ): | ||
2865 | 332 | return None | ||
2866 | 333 | if not self.locally_initiated: | ||
2867 | 334 | if not self.encrypted: | ||
2868 | 335 | self.Encoder.connecter.external_connection_made += 1 | ||
2869 | 336 | self.write(chr(len(protocol_name)) + protocol_name + | ||
2870 | 337 | option_pattern + self.Encoder.download_id + self.Encoder.my_id) | ||
2871 | 338 | return 20, self.read_peer_id | ||
2872 | 339 | |||
2873 | 340 | def read_peer_id(self, s): | ||
2874 | 341 | if not self.encrypted and self.Encoder.config['crypto_only']: | ||
2875 | 342 | return None # allows older trackers to ping, | ||
2876 | 343 | # but won't proceed w/ connections | ||
2877 | 344 | if not self.id: | ||
2878 | 345 | self.id = s | ||
2879 | 346 | self.readable_id = make_readable(s) | ||
2880 | 347 | else: | ||
2881 | 348 | if s != self.id: | ||
2882 | 349 | return None | ||
2883 | 350 | self.complete = self.Encoder.got_id(self) | ||
2884 | 351 | if not self.complete: | ||
2885 | 352 | return None | ||
2886 | 353 | if self.locally_initiated: | ||
2887 | 354 | self.write(self.Encoder.my_id) | ||
2888 | 355 | incompletecounter.decrement() | ||
2889 | 356 | self._switch_to_read2() | ||
2890 | 357 | c = self.Encoder.connecter.connection_made(self) | ||
2891 | 358 | self.keepalive = c.send_keepalive | ||
2892 | 359 | return 4, self.read_len | ||
2893 | 360 | |||
2894 | 361 | def read_len(self, s): | ||
2895 | 362 | l = toint(s) | ||
2896 | 363 | if l > self.Encoder.max_len: | ||
2897 | 364 | return None | ||
2898 | 365 | return l, self.read_message | ||
2899 | 366 | |||
2900 | 367 | def read_message(self, s): | ||
2901 | 368 | if s != '': | ||
2902 | 369 | self.connecter.got_message(self, s) | ||
2903 | 370 | return 4, self.read_len | ||
2904 | 371 | |||
2905 | 372 | def read_dead(self, s): | ||
2906 | 373 | return None | ||
2907 | 374 | |||
2908 | 375 | def _auto_close(self): | ||
2909 | 376 | if not self.complete: | ||
2910 | 377 | self.close() | ||
2911 | 378 | |||
2912 | 379 | def close(self): | ||
2913 | 380 | if not self.closed: | ||
2914 | 381 | self.connection.close() | ||
2915 | 382 | self.sever() | ||
2916 | 383 | |||
2917 | 384 | def sever(self): | ||
2918 | 385 | if self.log: | ||
2919 | 386 | self.log.write('closed\n') | ||
2920 | 387 | self.log.close() | ||
2921 | 388 | self.closed = True | ||
2922 | 389 | del self.Encoder.connections[self.connection] | ||
2923 | 390 | if self.complete: | ||
2924 | 391 | self.connecter.connection_lost(self) | ||
2925 | 392 | elif self.locally_initiated: | ||
2926 | 393 | incompletecounter.decrement() | ||
2927 | 394 | |||
2928 | 395 | def send_message_raw(self, message): | ||
2929 | 396 | self.write(message) | ||
2930 | 397 | |||
2931 | 398 | def _write(self, message): | ||
2932 | 399 | if not self.closed: | ||
2933 | 400 | self.connection.write(message) | ||
2934 | 401 | |||
2935 | 402 | def data_came_in(self, connection, s): | ||
2936 | 403 | self.read(s) | ||
2937 | 404 | |||
2938 | 405 | def _write_buffer(self, s): | ||
2939 | 406 | self.buffer = s+self.buffer | ||
2940 | 407 | |||
2941 | 408 | def _read(self, s): | ||
2942 | 409 | if self.log: | ||
2943 | 410 | self.log.write('r:'+b2a_hex(s)+'\n') | ||
2944 | 411 | self.Encoder.measurefunc(len(s)) | ||
2945 | 412 | self.buffer += s | ||
2946 | 413 | while True: | ||
2947 | 414 | if self.closed: | ||
2948 | 415 | return | ||
2949 | 416 | # self.next_len = # of characters function expects | ||
2950 | 417 | # or 0 = all characters in the buffer | ||
2951 | 418 | # or -1 = wait for next read, then all characters in the buffer | ||
2952 | 419 | # not compatible w/ keepalives, switch out after all negotiation complete | ||
2953 | 420 | if self.next_len <= 0: | ||
2954 | 421 | m = self.buffer | ||
2955 | 422 | self.buffer = '' | ||
2956 | 423 | elif len(self.buffer) >= self.next_len: | ||
2957 | 424 | m = self.buffer[:self.next_len] | ||
2958 | 425 | self.buffer = self.buffer[self.next_len:] | ||
2959 | 426 | else: | ||
2960 | 427 | return | ||
2961 | 428 | try: | ||
2962 | 429 | x = self.next_func(m) | ||
2963 | 430 | except: | ||
2964 | 431 | self.next_len, self.next_func = 1, self.read_dead | ||
2965 | 432 | raise | ||
2966 | 433 | if x is None: | ||
2967 | 434 | self.close() | ||
2968 | 435 | return | ||
2969 | 436 | self.next_len, self.next_func = x | ||
2970 | 437 | if self.next_len < 0: # already checked buffer | ||
2971 | 438 | return # wait for additional data | ||
2972 | 439 | if self.bufferlen is not None: | ||
2973 | 440 | self._read2('') | ||
2974 | 441 | return | ||
2975 | 442 | |||
2976 | 443 | def _switch_to_read2(self): | ||
2977 | 444 | self._write_buffer = None | ||
2978 | 445 | if self.encrypter: | ||
2979 | 446 | self.encrypter.setrawaccess(self._read2,self._write) | ||
2980 | 447 | else: | ||
2981 | 448 | self.read = self._read2 | ||
2982 | 449 | self.bufferlen = len(self.buffer) | ||
2983 | 450 | self.buffer = [self.buffer] | ||
2984 | 451 | |||
2985 | 452 | def _read2(self, s): # more efficient, requires buffer['',''] & bufferlen | ||
2986 | 453 | if self.log: | ||
2987 | 454 | self.log.write('r:'+b2a_hex(s)+'\n') | ||
2988 | 455 | self.Encoder.measurefunc(len(s)) | ||
2989 | 456 | while True: | ||
2990 | 457 | if self.closed: | ||
2991 | 458 | return | ||
2992 | 459 | p = self.next_len-self.bufferlen | ||
2993 | 460 | if self.next_len == 0: | ||
2994 | 461 | m = '' | ||
2995 | 462 | elif s: | ||
2996 | 463 | if p > len(s): | ||
2997 | 464 | self.buffer.append(s) | ||
2998 | 465 | self.bufferlen += len(s) | ||
2999 | 466 | return | ||
3000 | 467 | self.bufferlen = len(s)-p | ||
3001 | 468 | self.buffer.append(s[:p]) | ||
3002 | 469 | m = ''.join(self.buffer) | ||
3003 | 470 | if p == len(s): | ||
3004 | 471 | self.buffer = [] | ||
3005 | 472 | else: | ||
3006 | 473 | self.buffer=[s[p:]] | ||
3007 | 474 | s = '' | ||
3008 | 475 | elif p <= 0: | ||
3009 | 476 | # assert len(self.buffer) == 1 | ||
3010 | 477 | s = self.buffer[0] | ||
3011 | 478 | self.bufferlen = len(s)-self.next_len | ||
3012 | 479 | m = s[:self.next_len] | ||
3013 | 480 | if p == 0: | ||
3014 | 481 | self.buffer = [] | ||
3015 | 482 | else: | ||
3016 | 483 | self.buffer = [s[self.next_len:]] | ||
3017 | 484 | s = '' | ||
3018 | 485 | else: | ||
3019 | 486 | return | ||
3020 | 487 | try: | ||
3021 | 488 | x = self.next_func(m) | ||
3022 | 489 | except: | ||
3023 | 490 | self.next_len, self.next_func = 1, self.read_dead | ||
3024 | 491 | raise | ||
3025 | 492 | if x is None: | ||
3026 | 493 | self.close() | ||
3027 | 494 | return | ||
3028 | 495 | self.next_len, self.next_func = x | ||
3029 | 496 | if self.next_len < 0: # already checked buffer | ||
3030 | 497 | return # wait for additional data | ||
3031 | 498 | |||
3032 | 499 | |||
3033 | 500 | def connection_flushed(self, connection): | ||
3034 | 501 | if self.complete: | ||
3035 | 502 | self.connecter.connection_flushed(self) | ||
3036 | 503 | |||
3037 | 504 | def connection_lost(self, connection): | ||
3038 | 505 | if self.Encoder.connections.has_key(connection): | ||
3039 | 506 | self.sever() | ||
3040 | 507 | |||
3041 | 508 | |||
3042 | 509 | class _dummy_banlist: | ||
3043 | 510 | def includes(self, x): | ||
3044 | 511 | return False | ||
3045 | 512 | |||
3046 | 513 | class Encoder: | ||
3047 | 514 | def __init__(self, connecter, raw_server, my_id, max_len, | ||
3048 | 515 | schedulefunc, keepalive_delay, download_id, | ||
3049 | 516 | measurefunc, config, bans=_dummy_banlist() ): | ||
3050 | 517 | self.raw_server = raw_server | ||
3051 | 518 | self.connecter = connecter | ||
3052 | 519 | self.my_id = my_id | ||
3053 | 520 | self.max_len = max_len | ||
3054 | 521 | self.schedulefunc = schedulefunc | ||
3055 | 522 | self.keepalive_delay = keepalive_delay | ||
3056 | 523 | self.download_id = download_id | ||
3057 | 524 | self.measurefunc = measurefunc | ||
3058 | 525 | self.config = config | ||
3059 | 526 | self.connections = {} | ||
3060 | 527 | self.banned = {} | ||
3061 | 528 | self.external_bans = bans | ||
3062 | 529 | self.to_connect = [] | ||
3063 | 530 | self.paused = False | ||
3064 | 531 | if self.config['max_connections'] == 0: | ||
3065 | 532 | self.max_connections = 2 ** 30 | ||
3066 | 533 | else: | ||
3067 | 534 | self.max_connections = self.config['max_connections'] | ||
3068 | 535 | schedulefunc(self.send_keepalives, keepalive_delay) | ||
3069 | 536 | |||
3070 | 537 | def send_keepalives(self): | ||
3071 | 538 | self.schedulefunc(self.send_keepalives, self.keepalive_delay) | ||
3072 | 539 | if self.paused: | ||
3073 | 540 | return | ||
3074 | 541 | for c in self.connections.values(): | ||
3075 | 542 | c.keepalive() | ||
3076 | 543 | |||
3077 | 544 | def start_connections(self, list): | ||
3078 | 545 | if not self.to_connect: | ||
3079 | 546 | self.raw_server.add_task(self._start_connection_from_queue) | ||
3080 | 547 | self.to_connect = list | ||
3081 | 548 | |||
3082 | 549 | def _start_connection_from_queue(self): | ||
3083 | 550 | if self.connecter.external_connection_made: | ||
3084 | 551 | max_initiate = self.config['max_initiate'] | ||
3085 | 552 | else: | ||
3086 | 553 | max_initiate = int(self.config['max_initiate']*1.5) | ||
3087 | 554 | cons = len(self.connections) | ||
3088 | 555 | if cons >= self.max_connections or cons >= max_initiate: | ||
3089 | 556 | delay = 60 | ||
3090 | 557 | elif self.paused or incompletecounter.toomany(): | ||
3091 | 558 | delay = 1 | ||
3092 | 559 | else: | ||
3093 | 560 | delay = 0 | ||
3094 | 561 | dns, id, encrypted = self.to_connect.pop(0) | ||
3095 | 562 | self.start_connection(dns, id, encrypted) | ||
3096 | 563 | if self.to_connect: | ||
3097 | 564 | self.raw_server.add_task(self._start_connection_from_queue, delay) | ||
3098 | 565 | |||
3099 | 566 | def start_connection(self, dns, id, encrypted = None): | ||
3100 | 567 | if ( self.paused | ||
3101 | 568 | or len(self.connections) >= self.max_connections | ||
3102 | 569 | or id == self.my_id | ||
3103 | 570 | or not self.check_ip(ip=dns[0]) ): | ||
3104 | 571 | return True | ||
3105 | 572 | if self.config['crypto_only']: | ||
3106 | 573 | if encrypted is None or encrypted: # fails on encrypted = 0 | ||
3107 | 574 | encrypted = True | ||
3108 | 575 | else: | ||
3109 | 576 | return True | ||
3110 | 577 | for v in self.connections.values(): | ||
3111 | 578 | if v is None: | ||
3112 | 579 | continue | ||
3113 | 580 | if id and v.id == id: | ||
3114 | 581 | return True | ||
3115 | 582 | ip = v.get_ip(True) | ||
3116 | 583 | if self.config['security'] and ip != 'unknown' and ip == dns[0]: | ||
3117 | 584 | return True | ||
3118 | 585 | try: | ||
3119 | 586 | c = self.raw_server.start_connection(dns) | ||
3120 | 587 | con = Connection(self, c, id, encrypted = encrypted) | ||
3121 | 588 | self.connections[c] = con | ||
3122 | 589 | c.set_handler(con) | ||
3123 | 590 | except socketerror: | ||
3124 | 591 | return False | ||
3125 | 592 | return True | ||
3126 | 593 | |||
3127 | 594 | def _start_connection(self, dns, id, encrypted = None): | ||
3128 | 595 | def foo(self=self, dns=dns, id=id, encrypted=encrypted): | ||
3129 | 596 | self.start_connection(dns, id, encrypted) | ||
3130 | 597 | self.schedulefunc(foo, 0) | ||
3131 | 598 | |||
3132 | 599 | def check_ip(self, connection=None, ip=None): | ||
3133 | 600 | if not ip: | ||
3134 | 601 | ip = connection.get_ip(True) | ||
3135 | 602 | if self.config['security'] and self.banned.has_key(ip): | ||
3136 | 603 | return False | ||
3137 | 604 | if self.external_bans.includes(ip): | ||
3138 | 605 | return False | ||
3139 | 606 | return True | ||
3140 | 607 | |||
3141 | 608 | def got_id(self, connection): | ||
3142 | 609 | if connection.id == self.my_id: | ||
3143 | 610 | self.connecter.external_connection_made -= 1 | ||
3144 | 611 | return False | ||
3145 | 612 | ip = connection.get_ip(True) | ||
3146 | 613 | for v in self.connections.values(): | ||
3147 | 614 | if connection is not v: | ||
3148 | 615 | if connection.id == v.id: | ||
3149 | 616 | if ip == v.get_ip(True): | ||
3150 | 617 | v.close() | ||
3151 | 618 | else: | ||
3152 | 619 | return False | ||
3153 | 620 | if self.config['security'] and ip != 'unknown' and ip == v.get_ip(True): | ||
3154 | 621 | v.close() | ||
3155 | 622 | return True | ||
3156 | 623 | |||
3157 | 624 | def external_connection_made(self, connection): | ||
3158 | 625 | if self.paused or len(self.connections) >= self.max_connections: | ||
3159 | 626 | connection.close() | ||
3160 | 627 | return False | ||
3161 | 628 | con = Connection(self, connection, None) | ||
3162 | 629 | self.connections[connection] = con | ||
3163 | 630 | connection.set_handler(con) | ||
3164 | 631 | return True | ||
3165 | 632 | |||
3166 | 633 | def externally_handshaked_connection_made(self, connection, options, | ||
3167 | 634 | already_read, encrypted = None): | ||
3168 | 635 | if ( self.paused | ||
3169 | 636 | or len(self.connections) >= self.max_connections | ||
3170 | 637 | or not self.check_ip(connection=connection) ): | ||
3171 | 638 | connection.close() | ||
3172 | 639 | return False | ||
3173 | 640 | con = Connection(self, connection, None, | ||
3174 | 641 | ext_handshake = True, encrypted = encrypted, options = options) | ||
3175 | 642 | self.connections[connection] = con | ||
3176 | 643 | connection.set_handler(con) | ||
3177 | 644 | if already_read: | ||
3178 | 645 | con.data_came_in(con, already_read) | ||
3179 | 646 | return True | ||
3180 | 647 | |||
3181 | 648 | def close_all(self): | ||
3182 | 649 | for c in self.connections.values(): | ||
3183 | 650 | c.close() | ||
3184 | 651 | self.connections = {} | ||
3185 | 652 | |||
3186 | 653 | def ban(self, ip): | ||
3187 | 654 | self.banned[ip] = 1 | ||
3188 | 655 | |||
3189 | 656 | def pause(self, flag): | ||
3190 | 657 | self.paused = flag | ||
3191 | 658 | 0 | ||
3192 | === removed file '.pc/09_timtuckerfixes.dpatch/BitTornado/BT1/Storage.py' | |||
3193 | --- .pc/09_timtuckerfixes.dpatch/BitTornado/BT1/Storage.py 2010-03-21 14:36:30 +0000 | |||
3194 | +++ .pc/09_timtuckerfixes.dpatch/BitTornado/BT1/Storage.py 1970-01-01 00:00:00 +0000 | |||
3195 | @@ -1,584 +0,0 @@ | |||
3196 | 1 | # Written by Bram Cohen | ||
3197 | 2 | # see LICENSE.txt for license information | ||
3198 | 3 | |||
3199 | 4 | from BitTornado.piecebuffer import BufferPool | ||
3200 | 5 | from threading import Lock | ||
3201 | 6 | from time import time, strftime, localtime | ||
3202 | 7 | import os | ||
3203 | 8 | from os.path import exists, getsize, getmtime, basename | ||
3204 | 9 | from traceback import print_exc | ||
3205 | 10 | try: | ||
3206 | 11 | from os import fsync | ||
3207 | 12 | except ImportError: | ||
3208 | 13 | fsync = lambda x: None | ||
3209 | 14 | from bisect import bisect | ||
3210 | 15 | |||
3211 | 16 | try: | ||
3212 | 17 | True | ||
3213 | 18 | except: | ||
3214 | 19 | True = 1 | ||
3215 | 20 | False = 0 | ||
3216 | 21 | |||
3217 | 22 | DEBUG = False | ||
3218 | 23 | |||
3219 | 24 | MAXREADSIZE = 32768 | ||
3220 | 25 | MAXLOCKSIZE = 1000000000L | ||
3221 | 26 | MAXLOCKRANGE = 3999999999L # only lock first 4 gig of file | ||
3222 | 27 | |||
3223 | 28 | _pool = BufferPool() | ||
3224 | 29 | PieceBuffer = _pool.new | ||
3225 | 30 | |||
3226 | 31 | def dummy_status(fractionDone = None, activity = None): | ||
3227 | 32 | pass | ||
3228 | 33 | |||
3229 | 34 | class Storage: | ||
3230 | 35 | def __init__(self, files, piece_length, doneflag, config, | ||
3231 | 36 | disabled_files = None): | ||
3232 | 37 | # can raise IOError and ValueError | ||
3233 | 38 | self.files = files | ||
3234 | 39 | self.piece_length = piece_length | ||
3235 | 40 | self.doneflag = doneflag | ||
3236 | 41 | self.disabled = [False] * len(files) | ||
3237 | 42 | self.file_ranges = [] | ||
3238 | 43 | self.disabled_ranges = [] | ||
3239 | 44 | self.working_ranges = [] | ||
3240 | 45 | numfiles = 0 | ||
3241 | 46 | total = 0l | ||
3242 | 47 | so_far = 0l | ||
3243 | 48 | self.handles = {} | ||
3244 | 49 | self.whandles = {} | ||
3245 | 50 | self.tops = {} | ||
3246 | 51 | self.sizes = {} | ||
3247 | 52 | self.mtimes = {} | ||
3248 | 53 | if config.get('lock_files', True): | ||
3249 | 54 | self.lock_file, self.unlock_file = self._lock_file, self._unlock_file | ||
3250 | 55 | else: | ||
3251 | 56 | self.lock_file, self.unlock_file = lambda x1,x2: None, lambda x1,x2: None | ||
3252 | 57 | self.lock_while_reading = config.get('lock_while_reading', False) | ||
3253 | 58 | self.lock = Lock() | ||
3254 | 59 | |||
3255 | 60 | if not disabled_files: | ||
3256 | 61 | disabled_files = [False] * len(files) | ||
3257 | 62 | |||
3258 | 63 | for i in xrange(len(files)): | ||
3259 | 64 | file, length = files[i] | ||
3260 | 65 | if doneflag.isSet(): # bail out if doneflag is set | ||
3261 | 66 | return | ||
3262 | 67 | self.disabled_ranges.append(None) | ||
3263 | 68 | if length == 0: | ||
3264 | 69 | self.file_ranges.append(None) | ||
3265 | 70 | self.working_ranges.append([]) | ||
3266 | 71 | else: | ||
3267 | 72 | range = (total, total + length, 0, file) | ||
3268 | 73 | self.file_ranges.append(range) | ||
3269 | 74 | self.working_ranges.append([range]) | ||
3270 | 75 | numfiles += 1 | ||
3271 | 76 | total += length | ||
3272 | 77 | if disabled_files[i]: | ||
3273 | 78 | l = 0 | ||
3274 | 79 | else: | ||
3275 | 80 | if exists(file): | ||
3276 | 81 | l = getsize(file) | ||
3277 | 82 | if l > length: | ||
3278 | 83 | h = open(file, 'rb+') | ||
3279 | 84 | h.truncate(length) | ||
3280 | 85 | h.flush() | ||
3281 | 86 | h.close() | ||
3282 | 87 | l = length | ||
3283 | 88 | else: | ||
3284 | 89 | l = 0 | ||
3285 | 90 | h = open(file, 'wb+') | ||
3286 | 91 | h.flush() | ||
3287 | 92 | h.close() | ||
3288 | 93 | self.mtimes[file] = getmtime(file) | ||
3289 | 94 | self.tops[file] = l | ||
3290 | 95 | self.sizes[file] = length | ||
3291 | 96 | so_far += l | ||
3292 | 97 | |||
3293 | 98 | self.total_length = total | ||
3294 | 99 | self._reset_ranges() | ||
3295 | 100 | |||
3296 | 101 | self.max_files_open = config['max_files_open'] | ||
3297 | 102 | if self.max_files_open > 0 and numfiles > self.max_files_open: | ||
3298 | 103 | self.handlebuffer = [] | ||
3299 | 104 | else: | ||
3300 | 105 | self.handlebuffer = None | ||
3301 | 106 | |||
3302 | 107 | |||
3303 | 108 | if os.name == 'nt': | ||
3304 | 109 | def _lock_file(self, name, f): | ||
3305 | 110 | import msvcrt | ||
3306 | 111 | for p in range(0, min(self.sizes[name],MAXLOCKRANGE), MAXLOCKSIZE): | ||
3307 | 112 | f.seek(p) | ||
3308 | 113 | msvcrt.locking(f.fileno(), msvcrt.LK_LOCK, | ||
3309 | 114 | min(MAXLOCKSIZE,self.sizes[name]-p)) | ||
3310 | 115 | |||
3311 | 116 | def _unlock_file(self, name, f): | ||
3312 | 117 | import msvcrt | ||
3313 | 118 | for p in range(0, min(self.sizes[name],MAXLOCKRANGE), MAXLOCKSIZE): | ||
3314 | 119 | f.seek(p) | ||
3315 | 120 | msvcrt.locking(f.fileno(), msvcrt.LK_UNLCK, | ||
3316 | 121 | min(MAXLOCKSIZE,self.sizes[name]-p)) | ||
3317 | 122 | |||
3318 | 123 | elif os.name == 'posix': | ||
3319 | 124 | def _lock_file(self, name, f): | ||
3320 | 125 | import fcntl | ||
3321 | 126 | fcntl.flock(f.fileno(), fcntl.LOCK_EX) | ||
3322 | 127 | |||
3323 | 128 | def _unlock_file(self, name, f): | ||
3324 | 129 | import fcntl | ||
3325 | 130 | fcntl.flock(f.fileno(), fcntl.LOCK_UN) | ||
3326 | 131 | |||
3327 | 132 | else: | ||
3328 | 133 | def _lock_file(self, name, f): | ||
3329 | 134 | pass | ||
3330 | 135 | def _unlock_file(self, name, f): | ||
3331 | 136 | pass | ||
3332 | 137 | |||
3333 | 138 | |||
3334 | 139 | def was_preallocated(self, pos, length): | ||
3335 | 140 | for file, begin, end in self._intervals(pos, length): | ||
3336 | 141 | if self.tops.get(file, 0) < end: | ||
3337 | 142 | return False | ||
3338 | 143 | return True | ||
3339 | 144 | |||
3340 | 145 | |||
3341 | 146 | def _sync(self, file): | ||
3342 | 147 | self._close(file) | ||
3343 | 148 | if self.handlebuffer: | ||
3344 | 149 | self.handlebuffer.remove(file) | ||
3345 | 150 | |||
3346 | 151 | def sync(self): | ||
3347 | 152 | # may raise IOError or OSError | ||
3348 | 153 | for file in self.whandles.keys(): | ||
3349 | 154 | self._sync(file) | ||
3350 | 155 | |||
3351 | 156 | |||
3352 | 157 | def set_readonly(self, f=None): | ||
3353 | 158 | if f is None: | ||
3354 | 159 | self.sync() | ||
3355 | 160 | return | ||
3356 | 161 | file = self.files[f][0] | ||
3357 | 162 | if self.whandles.has_key(file): | ||
3358 | 163 | self._sync(file) | ||
3359 | 164 | |||
3360 | 165 | |||
3361 | 166 | def get_total_length(self): | ||
3362 | 167 | return self.total_length | ||
3363 | 168 | |||
3364 | 169 | |||
3365 | 170 | def _open(self, file, mode): | ||
3366 | 171 | if self.mtimes.has_key(file): | ||
3367 | 172 | try: | ||
3368 | 173 | if self.handlebuffer is not None: | ||
3369 | 174 | assert getsize(file) == self.tops[file] | ||
3370 | 175 | newmtime = getmtime(file) | ||
3371 | 176 | oldmtime = self.mtimes[file] | ||
3372 | 177 | assert newmtime <= oldmtime+1 | ||
3373 | 178 | assert newmtime >= oldmtime-1 | ||
3374 | 179 | except: | ||
3375 | 180 | if DEBUG: | ||
3376 | 181 | print ( file+' modified: ' | ||
3377 | 182 | +strftime('(%x %X)',localtime(self.mtimes[file])) | ||
3378 | 183 | +strftime(' != (%x %X) ?',localtime(getmtime(file))) ) | ||
3379 | 184 | raise IOError('modified during download') | ||
3380 | 185 | try: | ||
3381 | 186 | return open(file, mode) | ||
3382 | 187 | except: | ||
3383 | 188 | if DEBUG: | ||
3384 | 189 | print_exc() | ||
3385 | 190 | raise | ||
3386 | 191 | |||
3387 | 192 | |||
3388 | 193 | def _close(self, file): | ||
3389 | 194 | f = self.handles[file] | ||
3390 | 195 | del self.handles[file] | ||
3391 | 196 | if self.whandles.has_key(file): | ||
3392 | 197 | del self.whandles[file] | ||
3393 | 198 | f.flush() | ||
3394 | 199 | self.unlock_file(file, f) | ||
3395 | 200 | f.close() | ||
3396 | 201 | self.tops[file] = getsize(file) | ||
3397 | 202 | self.mtimes[file] = getmtime(file) | ||
3398 | 203 | else: | ||
3399 | 204 | if self.lock_while_reading: | ||
3400 | 205 | self.unlock_file(file, f) | ||
3401 | 206 | f.close() | ||
3402 | 207 | |||
3403 | 208 | |||
3404 | 209 | def _close_file(self, file): | ||
3405 | 210 | if not self.handles.has_key(file): | ||
3406 | 211 | return | ||
3407 | 212 | self._close(file) | ||
3408 | 213 | if self.handlebuffer: | ||
3409 | 214 | self.handlebuffer.remove(file) | ||
3410 | 215 | |||
3411 | 216 | |||
3412 | 217 | def _get_file_handle(self, file, for_write): | ||
3413 | 218 | if self.handles.has_key(file): | ||
3414 | 219 | if for_write and not self.whandles.has_key(file): | ||
3415 | 220 | self._close(file) | ||
3416 | 221 | try: | ||
3417 | 222 | f = self._open(file, 'rb+') | ||
3418 | 223 | self.handles[file] = f | ||
3419 | 224 | self.whandles[file] = 1 | ||
3420 | 225 | self.lock_file(file, f) | ||
3421 | 226 | except (IOError, OSError), e: | ||
3422 | 227 | if DEBUG: | ||
3423 | 228 | print_exc() | ||
3424 | 229 | raise IOError('unable to reopen '+file+': '+str(e)) | ||
3425 | 230 | |||
3426 | 231 | if self.handlebuffer: | ||
3427 | 232 | if self.handlebuffer[-1] != file: | ||
3428 | 233 | self.handlebuffer.remove(file) | ||
3429 | 234 | self.handlebuffer.append(file) | ||
3430 | 235 | elif self.handlebuffer is not None: | ||
3431 | 236 | self.handlebuffer.append(file) | ||
3432 | 237 | else: | ||
3433 | 238 | try: | ||
3434 | 239 | if for_write: | ||
3435 | 240 | f = self._open(file, 'rb+') | ||
3436 | 241 | self.handles[file] = f | ||
3437 | 242 | self.whandles[file] = 1 | ||
3438 | 243 | self.lock_file(file, f) | ||
3439 | 244 | else: | ||
3440 | 245 | f = self._open(file, 'rb') | ||
3441 | 246 | self.handles[file] = f | ||
3442 | 247 | if self.lock_while_reading: | ||
3443 | 248 | self.lock_file(file, f) | ||
3444 | 249 | except (IOError, OSError), e: | ||
3445 | 250 | if DEBUG: | ||
3446 | 251 | print_exc() | ||
3447 | 252 | raise IOError('unable to open '+file+': '+str(e)) | ||
3448 | 253 | |||
3449 | 254 | if self.handlebuffer is not None: | ||
3450 | 255 | self.handlebuffer.append(file) | ||
3451 | 256 | if len(self.handlebuffer) > self.max_files_open: | ||
3452 | 257 | self._close(self.handlebuffer.pop(0)) | ||
3453 | 258 | |||
3454 | 259 | return self.handles[file] | ||
3455 | 260 | |||
3456 | 261 | |||
3457 | 262 | def _reset_ranges(self): | ||
3458 | 263 | self.ranges = [] | ||
3459 | 264 | for l in self.working_ranges: | ||
3460 | 265 | self.ranges.extend(l) | ||
3461 | 266 | self.begins = [i[0] for i in self.ranges] | ||
3462 | 267 | |||
3463 | 268 | def _intervals(self, pos, amount): | ||
3464 | 269 | r = [] | ||
3465 | 270 | stop = pos + amount | ||
3466 | 271 | p = bisect(self.begins, pos) - 1 | ||
3467 | 272 | while p < len(self.ranges): | ||
3468 | 273 | begin, end, offset, file = self.ranges[p] | ||
3469 | 274 | if begin >= stop: | ||
3470 | 275 | break | ||
3471 | 276 | r.append(( file, | ||
3472 | 277 | offset + max(pos, begin) - begin, | ||
3473 | 278 | offset + min(end, stop) - begin )) | ||
3474 | 279 | p += 1 | ||
3475 | 280 | return r | ||
3476 | 281 | |||
3477 | 282 | |||
3478 | 283 | def read(self, pos, amount, flush_first = False): | ||
3479 | 284 | r = PieceBuffer() | ||
3480 | 285 | for file, pos, end in self._intervals(pos, amount): | ||
3481 | 286 | if DEBUG: | ||
3482 | 287 | print 'reading '+file+' from '+str(pos)+' to '+str(end) | ||
3483 | 288 | self.lock.acquire() | ||
3484 | 289 | h = self._get_file_handle(file, False) | ||
3485 | 290 | if flush_first and self.whandles.has_key(file): | ||
3486 | 291 | h.flush() | ||
3487 | 292 | fsync(h) | ||
3488 | 293 | h.seek(pos) | ||
3489 | 294 | while pos < end: | ||
3490 | 295 | length = min(end-pos, MAXREADSIZE) | ||
3491 | 296 | data = h.read(length) | ||
3492 | 297 | if len(data) != length: | ||
3493 | 298 | raise IOError('error reading data from '+file) | ||
3494 | 299 | r.append(data) | ||
3495 | 300 | pos += length | ||
3496 | 301 | self.lock.release() | ||
3497 | 302 | return r | ||
3498 | 303 | |||
3499 | 304 | def write(self, pos, s): | ||
3500 | 305 | # might raise an IOError | ||
3501 | 306 | total = 0 | ||
3502 | 307 | for file, begin, end in self._intervals(pos, len(s)): | ||
3503 | 308 | if DEBUG: | ||
3504 | 309 | print 'writing '+file+' from '+str(pos)+' to '+str(end) | ||
3505 | 310 | self.lock.acquire() | ||
3506 | 311 | h = self._get_file_handle(file, True) | ||
3507 | 312 | h.seek(begin) | ||
3508 | 313 | h.write(s[total: total + end - begin]) | ||
3509 | 314 | self.lock.release() | ||
3510 | 315 | total += end - begin | ||
3511 | 316 | |||
3512 | 317 | def top_off(self): | ||
3513 | 318 | for begin, end, offset, file in self.ranges: | ||
3514 | 319 | l = offset + end - begin | ||
3515 | 320 | if l > self.tops.get(file, 0): | ||
3516 | 321 | self.lock.acquire() | ||
3517 | 322 | h = self._get_file_handle(file, True) | ||
3518 | 323 | h.seek(l-1) | ||
3519 | 324 | h.write(chr(0xFF)) | ||
3520 | 325 | self.lock.release() | ||
3521 | 326 | |||
3522 | 327 | def flush(self): | ||
3523 | 328 | # may raise IOError or OSError | ||
3524 | 329 | for file in self.whandles.keys(): | ||
3525 | 330 | self.lock.acquire() | ||
3526 | 331 | self.handles[file].flush() | ||
3527 | 332 | self.lock.release() | ||
3528 | 333 | |||
3529 | 334 | def close(self): | ||
3530 | 335 | for file, f in self.handles.items(): | ||
3531 | 336 | try: | ||
3532 | 337 | self.unlock_file(file, f) | ||
3533 | 338 | except: | ||
3534 | 339 | pass | ||
3535 | 340 | try: | ||
3536 | 341 | f.close() | ||
3537 | 342 | except: | ||
3538 | 343 | pass | ||
3539 | 344 | self.handles = {} | ||
3540 | 345 | self.whandles = {} | ||
3541 | 346 | self.handlebuffer = None | ||
3542 | 347 | |||
3543 | 348 | |||
3544 | 349 | def _get_disabled_ranges(self, f): | ||
3545 | 350 | if not self.file_ranges[f]: | ||
3546 | 351 | return ((),(),()) | ||
3547 | 352 | r = self.disabled_ranges[f] | ||
3548 | 353 | if r: | ||
3549 | 354 | return r | ||
3550 | 355 | start, end, offset, file = self.file_ranges[f] | ||
3551 | 356 | if DEBUG: | ||
3552 | 357 | print 'calculating disabled range for '+self.files[f][0] | ||
3553 | 358 | print 'bytes: '+str(start)+'-'+str(end) | ||
3554 | 359 | print 'file spans pieces '+str(int(start/self.piece_length))+'-'+str(int((end-1)/self.piece_length)+1) | ||
3555 | 360 | pieces = range( int(start/self.piece_length), | ||
3556 | 361 | int((end-1)/self.piece_length)+1 ) | ||
3557 | 362 | offset = 0 | ||
3558 | 363 | disabled_files = [] | ||
3559 | 364 | if len(pieces) == 1: | ||
3560 | 365 | if ( start % self.piece_length == 0 | ||
3561 | 366 | and end % self.piece_length == 0 ): # happens to be a single, | ||
3562 | 367 | # perfect piece | ||
3563 | 368 | working_range = [(start, end, offset, file)] | ||
3564 | 369 | update_pieces = [] | ||
3565 | 370 | else: | ||
3566 | 371 | midfile = os.path.join(self.bufferdir,str(f)) | ||
3567 | 372 | working_range = [(start, end, 0, midfile)] | ||
3568 | 373 | disabled_files.append((midfile, start, end)) | ||
3569 | 374 | length = end - start | ||
3570 | 375 | self.sizes[midfile] = length | ||
3571 | 376 | piece = pieces[0] | ||
3572 | 377 | update_pieces = [(piece, start-(piece*self.piece_length), length)] | ||
3573 | 378 | else: | ||
3574 | 379 | update_pieces = [] | ||
3575 | 380 | if start % self.piece_length != 0: # doesn't begin on an even piece boundary | ||
3576 | 381 | end_b = pieces[1]*self.piece_length | ||
3577 | 382 | startfile = os.path.join(self.bufferdir,str(f)+'b') | ||
3578 | 383 | working_range_b = [ ( start, end_b, 0, startfile ) ] | ||
3579 | 384 | disabled_files.append((startfile, start, end_b)) | ||
3580 | 385 | length = end_b - start | ||
3581 | 386 | self.sizes[startfile] = length | ||
3582 | 387 | offset = length | ||
3583 | 388 | piece = pieces.pop(0) | ||
3584 | 389 | update_pieces.append((piece, start-(piece*self.piece_length), length)) | ||
3585 | 390 | else: | ||
3586 | 391 | working_range_b = [] | ||
3587 | 392 | if f != len(self.files)-1 and end % self.piece_length != 0: | ||
3588 | 393 | # doesn't end on an even piece boundary | ||
3589 | 394 | start_e = pieces[-1] * self.piece_length | ||
3590 | 395 | endfile = os.path.join(self.bufferdir,str(f)+'e') | ||
3591 | 396 | working_range_e = [ ( start_e, end, 0, endfile ) ] | ||
3592 | 397 | disabled_files.append((endfile, start_e, end)) | ||
3593 | 398 | length = end - start_e | ||
3594 | 399 | self.sizes[endfile] = length | ||
3595 | 400 | piece = pieces.pop(-1) | ||
3596 | 401 | update_pieces.append((piece, 0, length)) | ||
3597 | 402 | else: | ||
3598 | 403 | working_range_e = [] | ||
3599 | 404 | if pieces: | ||
3600 | 405 | working_range_m = [ ( pieces[0]*self.piece_length, | ||
3601 | 406 | (pieces[-1]+1)*self.piece_length, | ||
3602 | 407 | offset, file ) ] | ||
3603 | 408 | else: | ||
3604 | 409 | working_range_m = [] | ||
3605 | 410 | working_range = working_range_b + working_range_m + working_range_e | ||
3606 | 411 | |||
3607 | 412 | if DEBUG: | ||
3608 | 413 | print str(working_range) | ||
3609 | 414 | print str(update_pieces) | ||
3610 | 415 | r = (tuple(working_range), tuple(update_pieces), tuple(disabled_files)) | ||
3611 | 416 | self.disabled_ranges[f] = r | ||
3612 | 417 | return r | ||
3613 | 418 | |||
3614 | 419 | |||
3615 | 420 | def set_bufferdir(self, dir): | ||
3616 | 421 | self.bufferdir = dir | ||
3617 | 422 | |||
3618 | 423 | def enable_file(self, f): | ||
3619 | 424 | if not self.disabled[f]: | ||
3620 | 425 | return | ||
3621 | 426 | self.disabled[f] = False | ||
3622 | 427 | r = self.file_ranges[f] | ||
3623 | 428 | if not r: | ||
3624 | 429 | return | ||
3625 | 430 | file = r[3] | ||
3626 | 431 | if not exists(file): | ||
3627 | 432 | h = open(file, 'wb+') | ||
3628 | 433 | h.flush() | ||
3629 | 434 | h.close() | ||
3630 | 435 | if not self.tops.has_key(file): | ||
3631 | 436 | self.tops[file] = getsize(file) | ||
3632 | 437 | if not self.mtimes.has_key(file): | ||
3633 | 438 | self.mtimes[file] = getmtime(file) | ||
3634 | 439 | self.working_ranges[f] = [r] | ||
3635 | 440 | |||
3636 | 441 | def disable_file(self, f): | ||
3637 | 442 | if self.disabled[f]: | ||
3638 | 443 | return | ||
3639 | 444 | self.disabled[f] = True | ||
3640 | 445 | r = self._get_disabled_ranges(f) | ||
3641 | 446 | if not r: | ||
3642 | 447 | return | ||
3643 | 448 | for file, begin, end in r[2]: | ||
3644 | 449 | if not os.path.isdir(self.bufferdir): | ||
3645 | 450 | os.makedirs(self.bufferdir) | ||
3646 | 451 | if not exists(file): | ||
3647 | 452 | h = open(file, 'wb+') | ||
3648 | 453 | h.flush() | ||
3649 | 454 | h.close() | ||
3650 | 455 | if not self.tops.has_key(file): | ||
3651 | 456 | self.tops[file] = getsize(file) | ||
3652 | 457 | if not self.mtimes.has_key(file): | ||
3653 | 458 | self.mtimes[file] = getmtime(file) | ||
3654 | 459 | self.working_ranges[f] = r[0] | ||
3655 | 460 | |||
3656 | 461 | reset_file_status = _reset_ranges | ||
3657 | 462 | |||
3658 | 463 | |||
3659 | 464 | def get_piece_update_list(self, f): | ||
3660 | 465 | return self._get_disabled_ranges(f)[1] | ||
3661 | 466 | |||
3662 | 467 | |||
3663 | 468 | def delete_file(self, f): | ||
3664 | 469 | try: | ||
3665 | 470 | os.remove(self.files[f][0]) | ||
3666 | 471 | except: | ||
3667 | 472 | pass | ||
3668 | 473 | |||
3669 | 474 | |||
3670 | 475 | ''' | ||
3671 | 476 | Pickled data format: | ||
3672 | 477 | |||
3673 | 478 | d['files'] = [ file #, size, mtime {, file #, size, mtime...} ] | ||
3674 | 479 | file # in torrent, and the size and last modification | ||
3675 | 480 | time for those files. Missing files are either empty | ||
3676 | 481 | or disabled. | ||
3677 | 482 | d['partial files'] = [ name, size, mtime... ] | ||
3678 | 483 | Names, sizes and last modification times of files containing | ||
3679 | 484 | partial piece data. Filenames go by the following convention: | ||
3680 | 485 | {file #, 0-based}{nothing, "b" or "e"} | ||
3681 | 486 | eg: "0e" "3" "4b" "4e" | ||
3682 | 487 | Where "b" specifies the partial data for the first piece in | ||
3683 | 488 | the file, "e" the last piece, and no letter signifying that | ||
3684 | 489 | the file is disabled but is smaller than one piece, and that | ||
3685 | 490 | all the data is cached inside so adjacent files may be | ||
3686 | 491 | verified. | ||
3687 | 492 | ''' | ||
3688 | 493 | def pickle(self): | ||
3689 | 494 | files = [] | ||
3690 | 495 | pfiles = [] | ||
3691 | 496 | for i in xrange(len(self.files)): | ||
3692 | 497 | if not self.files[i][1]: # length == 0 | ||
3693 | 498 | continue | ||
3694 | 499 | if self.disabled[i]: | ||
3695 | 500 | for file, start, end in self._get_disabled_ranges(i)[2]: | ||
3696 | 501 | pfiles.extend([basename(file),getsize(file),int(getmtime(file))]) | ||
3697 | 502 | continue | ||
3698 | 503 | file = self.files[i][0] | ||
3699 | 504 | files.extend([i,getsize(file),int(getmtime(file))]) | ||
3700 | 505 | return {'files': files, 'partial files': pfiles} | ||
3701 | 506 | |||
3702 | 507 | |||
3703 | 508 | def unpickle(self, data): | ||
3704 | 509 | # assume all previously-disabled files have already been disabled | ||
3705 | 510 | try: | ||
3706 | 511 | files = {} | ||
3707 | 512 | pfiles = {} | ||
3708 | 513 | l = data['files'] | ||
3709 | 514 | assert len(l) % 3 == 0 | ||
3710 | 515 | l = [l[x:x+3] for x in xrange(0,len(l),3)] | ||
3711 | 516 | for f, size, mtime in l: | ||
3712 | 517 | files[f] = (size, mtime) | ||
3713 | 518 | l = data.get('partial files',[]) | ||
3714 | 519 | assert len(l) % 3 == 0 | ||
3715 | 520 | l = [l[x:x+3] for x in xrange(0,len(l),3)] | ||
3716 | 521 | for file, size, mtime in l: | ||
3717 | 522 | pfiles[file] = (size, mtime) | ||
3718 | 523 | |||
3719 | 524 | valid_pieces = {} | ||
3720 | 525 | for i in xrange(len(self.files)): | ||
3721 | 526 | if self.disabled[i]: | ||
3722 | 527 | continue | ||
3723 | 528 | r = self.file_ranges[i] | ||
3724 | 529 | if not r: | ||
3725 | 530 | continue | ||
3726 | 531 | start, end, offset, file =r | ||
3727 | 532 | if DEBUG: | ||
3728 | 533 | print 'adding '+file | ||
3729 | 534 | for p in xrange( int(start/self.piece_length), | ||
3730 | 535 | int((end-1)/self.piece_length)+1 ): | ||
3731 | 536 | valid_pieces[p] = 1 | ||
3732 | 537 | |||
3733 | 538 | if DEBUG: | ||
3734 | 539 | print valid_pieces.keys() | ||
3735 | 540 | |||
3736 | 541 | def test(old, size, mtime): | ||
3737 | 542 | oldsize, oldmtime = old | ||
3738 | 543 | if size != oldsize: | ||
3739 | 544 | return False | ||
3740 | 545 | if mtime > oldmtime+1: | ||
3741 | 546 | return False | ||
3742 | 547 | if mtime < oldmtime-1: | ||
3743 | 548 | return False | ||
3744 | 549 | return True | ||
3745 | 550 | |||
3746 | 551 | for i in xrange(len(self.files)): | ||
3747 | 552 | if self.disabled[i]: | ||
3748 | 553 | for file, start, end in self._get_disabled_ranges(i)[2]: | ||
3749 | 554 | f1 = basename(file) | ||
3750 | 555 | if ( not pfiles.has_key(f1) | ||
3751 | 556 | or not test(pfiles[f1],getsize(file),getmtime(file)) ): | ||
3752 | 557 | if DEBUG: | ||
3753 | 558 | print 'removing '+file | ||
3754 | 559 | for p in xrange( int(start/self.piece_length), | ||
3755 | 560 | int((end-1)/self.piece_length)+1 ): | ||
3756 | 561 | if valid_pieces.has_key(p): | ||
3757 | 562 | del valid_pieces[p] | ||
3758 | 563 | continue | ||
3759 | 564 | file, size = self.files[i] | ||
3760 | 565 | if not size: | ||
3761 | 566 | continue | ||
3762 | 567 | if ( not files.has_key(i) | ||
3763 | 568 | or not test(files[i],getsize(file),getmtime(file)) ): | ||
3764 | 569 | start, end, offset, file = self.file_ranges[i] | ||
3765 | 570 | if DEBUG: | ||
3766 | 571 | print 'removing '+file | ||
3767 | 572 | for p in xrange( int(start/self.piece_length), | ||
3768 | 573 | int((end-1)/self.piece_length)+1 ): | ||
3769 | 574 | if valid_pieces.has_key(p): | ||
3770 | 575 | del valid_pieces[p] | ||
3771 | 576 | except: | ||
3772 | 577 | if DEBUG: | ||
3773 | 578 | print_exc() | ||
3774 | 579 | return [] | ||
3775 | 580 | |||
3776 | 581 | if DEBUG: | ||
3777 | 582 | print valid_pieces.keys() | ||
3778 | 583 | return valid_pieces.keys() | ||
3779 | 584 | |||
3780 | 585 | 0 | ||
3781 | === removed file '.pc/09_timtuckerfixes.dpatch/BitTornado/BT1/StreamCheck.py' | |||
3782 | --- .pc/09_timtuckerfixes.dpatch/BitTornado/BT1/StreamCheck.py 2010-03-21 14:36:30 +0000 | |||
3783 | +++ .pc/09_timtuckerfixes.dpatch/BitTornado/BT1/StreamCheck.py 1970-01-01 00:00:00 +0000 | |||
3784 | @@ -1,135 +0,0 @@ | |||
3785 | 1 | # Written by Bram Cohen | ||
3786 | 2 | # see LICENSE.txt for license information | ||
3787 | 3 | |||
3788 | 4 | from cStringIO import StringIO | ||
3789 | 5 | from binascii import b2a_hex | ||
3790 | 6 | from socket import error as socketerror | ||
3791 | 7 | from urllib import quote | ||
3792 | 8 | from traceback import print_exc | ||
3793 | 9 | import Connecter | ||
3794 | 10 | try: | ||
3795 | 11 | True | ||
3796 | 12 | except: | ||
3797 | 13 | True = 1 | ||
3798 | 14 | False = 0 | ||
3799 | 15 | |||
3800 | 16 | DEBUG = False | ||
3801 | 17 | |||
3802 | 18 | |||
3803 | 19 | protocol_name = 'BitTorrent protocol' | ||
3804 | 20 | option_pattern = chr(0)*8 | ||
3805 | 21 | |||
3806 | 22 | def toint(s): | ||
3807 | 23 | return long(b2a_hex(s), 16) | ||
3808 | 24 | |||
3809 | 25 | def tobinary(i): | ||
3810 | 26 | return (chr(i >> 24) + chr((i >> 16) & 0xFF) + | ||
3811 | 27 | chr((i >> 8) & 0xFF) + chr(i & 0xFF)) | ||
3812 | 28 | |||
3813 | 29 | hexchars = '0123456789ABCDEF' | ||
3814 | 30 | hexmap = [] | ||
3815 | 31 | for i in xrange(256): | ||
3816 | 32 | hexmap.append(hexchars[(i&0xF0)/16]+hexchars[i&0x0F]) | ||
3817 | 33 | |||
3818 | 34 | def tohex(s): | ||
3819 | 35 | r = [] | ||
3820 | 36 | for c in s: | ||
3821 | 37 | r.append(hexmap[ord(c)]) | ||
3822 | 38 | return ''.join(r) | ||
3823 | 39 | |||
3824 | 40 | def make_readable(s): | ||
3825 | 41 | if not s: | ||
3826 | 42 | return '' | ||
3827 | 43 | if quote(s).find('%') >= 0: | ||
3828 | 44 | return tohex(s) | ||
3829 | 45 | return '"'+s+'"' | ||
3830 | 46 | |||
3831 | 47 | def toint(s): | ||
3832 | 48 | return long(b2a_hex(s), 16) | ||
3833 | 49 | |||
3834 | 50 | # header, reserved, download id, my id, [length, message] | ||
3835 | 51 | |||
3836 | 52 | streamno = 0 | ||
3837 | 53 | |||
3838 | 54 | |||
3839 | 55 | class StreamCheck: | ||
3840 | 56 | def __init__(self): | ||
3841 | 57 | global streamno | ||
3842 | 58 | self.no = streamno | ||
3843 | 59 | streamno += 1 | ||
3844 | 60 | self.buffer = StringIO() | ||
3845 | 61 | self.next_len, self.next_func = 1, self.read_header_len | ||
3846 | 62 | |||
3847 | 63 | def read_header_len(self, s): | ||
3848 | 64 | if ord(s) != len(protocol_name): | ||
3849 | 65 | print self.no, 'BAD HEADER LENGTH' | ||
3850 | 66 | return len(protocol_name), self.read_header | ||
3851 | 67 | |||
3852 | 68 | def read_header(self, s): | ||
3853 | 69 | if s != protocol_name: | ||
3854 | 70 | print self.no, 'BAD HEADER' | ||
3855 | 71 | return 8, self.read_reserved | ||
3856 | 72 | |||
3857 | 73 | def read_reserved(self, s): | ||
3858 | 74 | return 20, self.read_download_id | ||
3859 | 75 | |||
3860 | 76 | def read_download_id(self, s): | ||
3861 | 77 | if DEBUG: | ||
3862 | 78 | print self.no, 'download ID ' + tohex(s) | ||
3863 | 79 | return 20, self.read_peer_id | ||
3864 | 80 | |||
3865 | 81 | def read_peer_id(self, s): | ||
3866 | 82 | if DEBUG: | ||
3867 | 83 | print self.no, 'peer ID' + make_readable(s) | ||
3868 | 84 | return 4, self.read_len | ||
3869 | 85 | |||
3870 | 86 | def read_len(self, s): | ||
3871 | 87 | l = toint(s) | ||
3872 | 88 | if l > 2 ** 23: | ||
3873 | 89 | print self.no, 'BAD LENGTH: '+str(l)+' ('+s+')' | ||
3874 | 90 | return l, self.read_message | ||
3875 | 91 | |||
3876 | 92 | def read_message(self, s): | ||
3877 | 93 | if not s: | ||
3878 | 94 | return 4, self.read_len | ||
3879 | 95 | m = s[0] | ||
3880 | 96 | if ord(m) > 8: | ||
3881 | 97 | print self.no, 'BAD MESSAGE: '+str(ord(m)) | ||
3882 | 98 | if m == Connecter.REQUEST: | ||
3883 | 99 | if len(s) != 13: | ||
3884 | 100 | print self.no, 'BAD REQUEST SIZE: '+str(len(s)) | ||
3885 | 101 | return 4, self.read_len | ||
3886 | 102 | index = toint(s[1:5]) | ||
3887 | 103 | begin = toint(s[5:9]) | ||
3888 | 104 | length = toint(s[9:]) | ||
3889 | 105 | print self.no, 'Request: '+str(index)+': '+str(begin)+'-'+str(begin)+'+'+str(length) | ||
3890 | 106 | elif m == Connecter.CANCEL: | ||
3891 | 107 | if len(s) != 13: | ||
3892 | 108 | print self.no, 'BAD CANCEL SIZE: '+str(len(s)) | ||
3893 | 109 | return 4, self.read_len | ||
3894 | 110 | index = toint(s[1:5]) | ||
3895 | 111 | begin = toint(s[5:9]) | ||
3896 | 112 | length = toint(s[9:]) | ||
3897 | 113 | print self.no, 'Cancel: '+str(index)+': '+str(begin)+'-'+str(begin)+'+'+str(length) | ||
3898 | 114 | elif m == Connecter.PIECE: | ||
3899 | 115 | index = toint(s[1:5]) | ||
3900 | 116 | begin = toint(s[5:9]) | ||
3901 | 117 | length = len(s)-9 | ||
3902 | 118 | print self.no, 'Piece: '+str(index)+': '+str(begin)+'-'+str(begin)+'+'+str(length) | ||
3903 | 119 | else: | ||
3904 | 120 | print self.no, 'Message '+str(ord(m))+' (length '+str(len(s))+')' | ||
3905 | 121 | return 4, self.read_len | ||
3906 | 122 | |||
3907 | 123 | def write(self, s): | ||
3908 | 124 | while True: | ||
3909 | 125 | i = self.next_len - self.buffer.tell() | ||
3910 | 126 | if i > len(s): | ||
3911 | 127 | self.buffer.write(s) | ||
3912 | 128 | return | ||
3913 | 129 | self.buffer.write(s[:i]) | ||
3914 | 130 | s = s[i:] | ||
3915 | 131 | m = self.buffer.getvalue() | ||
3916 | 132 | self.buffer.reset() | ||
3917 | 133 | self.buffer.truncate() | ||
3918 | 134 | x = self.next_func(m) | ||
3919 | 135 | self.next_len, self.next_func = x | ||
3920 | 136 | 0 | ||
3921 | === removed file '.pc/09_timtuckerfixes.dpatch/BitTornado/ConfigDir.py' | |||
3922 | --- .pc/09_timtuckerfixes.dpatch/BitTornado/ConfigDir.py 2010-03-21 14:36:30 +0000 | |||
3923 | +++ .pc/09_timtuckerfixes.dpatch/BitTornado/ConfigDir.py 1970-01-01 00:00:00 +0000 | |||
3924 | @@ -1,401 +0,0 @@ | |||
3925 | 1 | #written by John Hoffman | ||
3926 | 2 | |||
3927 | 3 | from inifile import ini_write, ini_read | ||
3928 | 4 | from bencode import bencode, bdecode | ||
3929 | 5 | from types import IntType, LongType, StringType, FloatType | ||
3930 | 6 | from CreateIcons import GetIcons, CreateIcon | ||
3931 | 7 | from parseargs import defaultargs | ||
3932 | 8 | from __init__ import product_name, version_short | ||
3933 | 9 | import sys,os | ||
3934 | 10 | from time import time, strftime | ||
3935 | 11 | |||
3936 | 12 | try: | ||
3937 | 13 | True | ||
3938 | 14 | except: | ||
3939 | 15 | True = 1 | ||
3940 | 16 | False = 0 | ||
3941 | 17 | |||
3942 | 18 | try: | ||
3943 | 19 | realpath = os.path.realpath | ||
3944 | 20 | except: | ||
3945 | 21 | realpath = lambda x:x | ||
3946 | 22 | OLDICONPATH = os.path.abspath(os.path.dirname(realpath(sys.argv[0]))) | ||
3947 | 23 | |||
3948 | 24 | DIRNAME = '.'+product_name | ||
3949 | 25 | |||
3950 | 26 | hexchars = '0123456789abcdef' | ||
3951 | 27 | hexmap = [] | ||
3952 | 28 | revmap = {} | ||
3953 | 29 | for i in xrange(256): | ||
3954 | 30 | x = hexchars[(i&0xF0)/16]+hexchars[i&0x0F] | ||
3955 | 31 | hexmap.append(x) | ||
3956 | 32 | revmap[x] = chr(i) | ||
3957 | 33 | |||
3958 | 34 | def tohex(s): | ||
3959 | 35 | r = [] | ||
3960 | 36 | for c in s: | ||
3961 | 37 | r.append(hexmap[ord(c)]) | ||
3962 | 38 | return ''.join(r) | ||
3963 | 39 | |||
3964 | 40 | def unhex(s): | ||
3965 | 41 | r = [ revmap[s[x:x+2]] for x in xrange(0, len(s), 2) ] | ||
3966 | 42 | return ''.join(r) | ||
3967 | 43 | |||
3968 | 44 | def copyfile(oldpath, newpath): # simple file copy, all in RAM | ||
3969 | 45 | try: | ||
3970 | 46 | f = open(oldpath,'rb') | ||
3971 | 47 | r = f.read() | ||
3972 | 48 | success = True | ||
3973 | 49 | except: | ||
3974 | 50 | success = False | ||
3975 | 51 | try: | ||
3976 | 52 | f.close() | ||
3977 | 53 | except: | ||
3978 | 54 | pass | ||
3979 | 55 | if not success: | ||
3980 | 56 | return False | ||
3981 | 57 | try: | ||
3982 | 58 | f = open(newpath,'wb') | ||
3983 | 59 | f.write(r) | ||
3984 | 60 | except: | ||
3985 | 61 | success = False | ||
3986 | 62 | try: | ||
3987 | 63 | f.close() | ||
3988 | 64 | except: | ||
3989 | 65 | pass | ||
3990 | 66 | return success | ||
3991 | 67 | |||
3992 | 68 | |||
3993 | 69 | class ConfigDir: | ||
3994 | 70 | |||
3995 | 71 | ###### INITIALIZATION TASKS ###### | ||
3996 | 72 | |||
3997 | 73 | def __init__(self, config_type = None): | ||
3998 | 74 | self.config_type = config_type | ||
3999 | 75 | if config_type: | ||
4000 | 76 | config_ext = '.'+config_type | ||
4001 | 77 | else: | ||
4002 | 78 | config_ext = '' | ||
4003 | 79 | |||
4004 | 80 | def check_sysvars(x): | ||
4005 | 81 | y = os.path.expandvars(x) | ||
4006 | 82 | if y != x and os.path.isdir(y): | ||
4007 | 83 | return y | ||
4008 | 84 | return None | ||
4009 | 85 | |||
4010 | 86 | for d in ['${APPDATA}', '${HOME}', '${HOMEPATH}', '${USERPROFILE}']: | ||
4011 | 87 | dir_root = check_sysvars(d) | ||
4012 | 88 | if dir_root: | ||
4013 | 89 | break | ||
4014 | 90 | else: | ||
4015 | 91 | dir_root = os.path.expanduser('~') | ||
4016 | 92 | if not os.path.isdir(dir_root): | ||
4017 | 93 | dir_root = os.path.abspath(os.path.dirname(sys.argv[0])) | ||
4018 | 94 | |||
4019 | 95 | dir_root = os.path.join(dir_root,DIRNAME) | ||
4020 | 96 | self.dir_root = dir_root | ||
4021 | 97 | |||
4022 | 98 | if not os.path.isdir(self.dir_root): | ||
4023 | 99 | os.mkdir(self.dir_root,0700) # exception if failed | ||
4024 | 100 | |||
4025 | 101 | self.dir_icons = os.path.join(dir_root,'icons') | ||
4026 | 102 | if not os.path.isdir(self.dir_icons): | ||
4027 | 103 | os.mkdir(self.dir_icons) | ||
4028 | 104 | for icon in GetIcons(): | ||
4029 | 105 | i = os.path.join(self.dir_icons,icon) | ||
4030 | 106 | if not os.path.exists(i): | ||
4031 | 107 | if not copyfile(os.path.join(OLDICONPATH,icon),i): | ||
4032 | 108 | CreateIcon(icon,self.dir_icons) | ||
4033 | 109 | |||
4034 | 110 | self.dir_torrentcache = os.path.join(dir_root,'torrentcache') | ||
4035 | 111 | if not os.path.isdir(self.dir_torrentcache): | ||
4036 | 112 | os.mkdir(self.dir_torrentcache) | ||
4037 | 113 | |||
4038 | 114 | self.dir_datacache = os.path.join(dir_root,'datacache') | ||
4039 | 115 | if not os.path.isdir(self.dir_datacache): | ||
4040 | 116 | os.mkdir(self.dir_datacache) | ||
4041 | 117 | |||
4042 | 118 | self.dir_piececache = os.path.join(dir_root,'piececache') | ||
4043 | 119 | if not os.path.isdir(self.dir_piececache): | ||
4044 | 120 | os.mkdir(self.dir_piececache) | ||
4045 | 121 | |||
4046 | 122 | self.configfile = os.path.join(dir_root,'config'+config_ext+'.ini') | ||
4047 | 123 | self.statefile = os.path.join(dir_root,'state'+config_ext) | ||
4048 | 124 | |||
4049 | 125 | self.TorrentDataBuffer = {} | ||
4050 | 126 | |||
4051 | 127 | |||
4052 | 128 | ###### CONFIG HANDLING ###### | ||
4053 | 129 | |||
4054 | 130 | def setDefaults(self, defaults, ignore=[]): | ||
4055 | 131 | self.config = defaultargs(defaults) | ||
4056 | 132 | for k in ignore: | ||
4057 | 133 | if self.config.has_key(k): | ||
4058 | 134 | del self.config[k] | ||
4059 | 135 | |||
4060 | 136 | def checkConfig(self): | ||
4061 | 137 | return os.path.exists(self.configfile) | ||
4062 | 138 | |||
4063 | 139 | def loadConfig(self): | ||
4064 | 140 | try: | ||
4065 | 141 | r = ini_read(self.configfile)[''] | ||
4066 | 142 | except: | ||
4067 | 143 | return self.config | ||
4068 | 144 | l = self.config.keys() | ||
4069 | 145 | for k,v in r.items(): | ||
4070 | 146 | if self.config.has_key(k): | ||
4071 | 147 | t = type(self.config[k]) | ||
4072 | 148 | try: | ||
4073 | 149 | if t == StringType: | ||
4074 | 150 | self.config[k] = v | ||
4075 | 151 | elif t == IntType or t == LongType: | ||
4076 | 152 | self.config[k] = long(v) | ||
4077 | 153 | elif t == FloatType: | ||
4078 | 154 | self.config[k] = float(v) | ||
4079 | 155 | l.remove(k) | ||
4080 | 156 | except: | ||
4081 | 157 | pass | ||
4082 | 158 | if l: # new default values since last save | ||
4083 | 159 | self.saveConfig() | ||
4084 | 160 | return self.config | ||
4085 | 161 | |||
4086 | 162 | def saveConfig(self, new_config = None): | ||
4087 | 163 | if new_config: | ||
4088 | 164 | for k,v in new_config.items(): | ||
4089 | 165 | if self.config.has_key(k): | ||
4090 | 166 | self.config[k] = v | ||
4091 | 167 | try: | ||
4092 | 168 | ini_write( self.configfile, self.config, | ||
4093 | 169 | 'Generated by '+product_name+'/'+version_short+'\n' | ||
4094 | 170 | + strftime('%x %X') ) | ||
4095 | 171 | return True | ||
4096 | 172 | except: | ||
4097 | 173 | return False | ||
4098 | 174 | |||
4099 | 175 | def getConfig(self): | ||
4100 | 176 | return self.config | ||
4101 | 177 | |||
4102 | 178 | |||
4103 | 179 | ###### STATE HANDLING ###### | ||
4104 | 180 | |||
4105 | 181 | def getState(self): | ||
4106 | 182 | try: | ||
4107 | 183 | f = open(self.statefile,'rb') | ||
4108 | 184 | r = f.read() | ||
4109 | 185 | except: | ||
4110 | 186 | r = None | ||
4111 | 187 | try: | ||
4112 | 188 | f.close() | ||
4113 | 189 | except: | ||
4114 | 190 | pass | ||
4115 | 191 | try: | ||
4116 | 192 | r = bdecode(r) | ||
4117 | 193 | except: | ||
4118 | 194 | r = None | ||
4119 | 195 | return r | ||
4120 | 196 | |||
4121 | 197 | def saveState(self, state): | ||
4122 | 198 | try: | ||
4123 | 199 | f = open(self.statefile,'wb') | ||
4124 | 200 | f.write(bencode(state)) | ||
4125 | 201 | success = True | ||
4126 | 202 | except: | ||
4127 | 203 | success = False | ||
4128 | 204 | try: | ||
4129 | 205 | f.close() | ||
4130 | 206 | except: | ||
4131 | 207 | pass | ||
4132 | 208 | return success | ||
4133 | 209 | |||
4134 | 210 | |||
4135 | 211 | ###### TORRENT HANDLING ###### | ||
4136 | 212 | |||
4137 | 213 | def getTorrents(self): | ||
4138 | 214 | d = {} | ||
4139 | 215 | for f in os.listdir(self.dir_torrentcache): | ||
4140 | 216 | f = os.path.basename(f) | ||
4141 | 217 | try: | ||
4142 | 218 | f, garbage = f.split('.') | ||
4143 | 219 | except: | ||
4144 | 220 | pass | ||
4145 | 221 | d[unhex(f)] = 1 | ||
4146 | 222 | return d.keys() | ||
4147 | 223 | |||
4148 | 224 | def getTorrentVariations(self, t): | ||
4149 | 225 | t = tohex(t) | ||
4150 | 226 | d = [] | ||
4151 | 227 | for f in os.listdir(self.dir_torrentcache): | ||
4152 | 228 | f = os.path.basename(f) | ||
4153 | 229 | if f[:len(t)] == t: | ||
4154 | 230 | try: | ||
4155 | 231 | garbage, ver = f.split('.') | ||
4156 | 232 | except: | ||
4157 | 233 | ver = '0' | ||
4158 | 234 | d.append(int(ver)) | ||
4159 | 235 | d.sort() | ||
4160 | 236 | return d | ||
4161 | 237 | |||
4162 | 238 | def getTorrent(self, t, v = -1): | ||
4163 | 239 | t = tohex(t) | ||
4164 | 240 | if v == -1: | ||
4165 | 241 | v = max(self.getTorrentVariations(t)) # potential exception | ||
4166 | 242 | if v: | ||
4167 | 243 | t += '.'+str(v) | ||
4168 | 244 | try: | ||
4169 | 245 | f = open(os.path.join(self.dir_torrentcache,t),'rb') | ||
4170 | 246 | r = bdecode(f.read()) | ||
4171 | 247 | except: | ||
4172 | 248 | r = None | ||
4173 | 249 | try: | ||
4174 | 250 | f.close() | ||
4175 | 251 | except: | ||
4176 | 252 | pass | ||
4177 | 253 | return r | ||
4178 | 254 | |||
4179 | 255 | def writeTorrent(self, data, t, v = -1): | ||
4180 | 256 | t = tohex(t) | ||
4181 | 257 | if v == -1: | ||
4182 | 258 | try: | ||
4183 | 259 | v = max(self.getTorrentVariations(t))+1 | ||
4184 | 260 | except: | ||
4185 | 261 | v = 0 | ||
4186 | 262 | if v: | ||
4187 | 263 | t += '.'+str(v) | ||
4188 | 264 | try: | ||
4189 | 265 | f = open(os.path.join(self.dir_torrentcache,t),'wb') | ||
4190 | 266 | f.write(bencode(data)) | ||
4191 | 267 | except: | ||
4192 | 268 | v = None | ||
4193 | 269 | try: | ||
4194 | 270 | f.close() | ||
4195 | 271 | except: | ||
4196 | 272 | pass | ||
4197 | 273 | return v | ||
4198 | 274 | |||
4199 | 275 | |||
4200 | 276 | ###### TORRENT DATA HANDLING ###### | ||
4201 | 277 | |||
4202 | 278 | def getTorrentData(self, t): | ||
4203 | 279 | if self.TorrentDataBuffer.has_key(t): | ||
4204 | 280 | return self.TorrentDataBuffer[t] | ||
4205 | 281 | t = os.path.join(self.dir_datacache,tohex(t)) | ||
4206 | 282 | if not os.path.exists(t): | ||
4207 | 283 | return None | ||
4208 | 284 | try: | ||
4209 | 285 | f = open(t,'rb') | ||
4210 | 286 | r = bdecode(f.read()) | ||
4211 | 287 | except: | ||
4212 | 288 | r = None | ||
4213 | 289 | try: | ||
4214 | 290 | f.close() | ||
4215 | 291 | except: | ||
4216 | 292 | pass | ||
4217 | 293 | self.TorrentDataBuffer[t] = r | ||
4218 | 294 | return r | ||
4219 | 295 | |||
4220 | 296 | def writeTorrentData(self, t, data): | ||
4221 | 297 | self.TorrentDataBuffer[t] = data | ||
4222 | 298 | try: | ||
4223 | 299 | f = open(os.path.join(self.dir_datacache,tohex(t)),'wb') | ||
4224 | 300 | f.write(bencode(data)) | ||
4225 | 301 | success = True | ||
4226 | 302 | except: | ||
4227 | 303 | success = False | ||
4228 | 304 | try: | ||
4229 | 305 | f.close() | ||
4230 | 306 | except: | ||
4231 | 307 | pass | ||
4232 | 308 | if not success: | ||
4233 | 309 | self.deleteTorrentData(t) | ||
4234 | 310 | return success | ||
4235 | 311 | |||
4236 | 312 | def deleteTorrentData(self, t): | ||
4237 | 313 | try: | ||
4238 | 314 | os.remove(os.path.join(self.dir_datacache,tohex(t))) | ||
4239 | 315 | except: | ||
4240 | 316 | pass | ||
4241 | 317 | |||
4242 | 318 | def getPieceDir(self, t): | ||
4243 | 319 | return os.path.join(self.dir_piececache,tohex(t)) | ||
4244 | 320 | |||
4245 | 321 | |||
4246 | 322 | ###### EXPIRATION HANDLING ###### | ||
4247 | 323 | |||
4248 | 324 | def deleteOldCacheData(self, days, still_active = [], delete_torrents = False): | ||
4249 | 325 | if not days: | ||
4250 | 326 | return | ||
4251 | 327 | exptime = time() - (days*24*3600) | ||
4252 | 328 | names = {} | ||
4253 | 329 | times = {} | ||
4254 | 330 | |||
4255 | 331 | for f in os.listdir(self.dir_torrentcache): | ||
4256 | 332 | p = os.path.join(self.dir_torrentcache,f) | ||
4257 | 333 | f = os.path.basename(f) | ||
4258 | 334 | try: | ||
4259 | 335 | f, garbage = f.split('.') | ||
4260 | 336 | except: | ||
4261 | 337 | pass | ||
4262 | 338 | try: | ||
4263 | 339 | f = unhex(f) | ||
4264 | 340 | assert len(f) == 20 | ||
4265 | 341 | except: | ||
4266 | 342 | continue | ||
4267 | 343 | if delete_torrents: | ||
4268 | 344 | names.setdefault(f,[]).append(p) | ||
4269 | 345 | try: | ||
4270 | 346 | t = os.path.getmtime(p) | ||
4271 | 347 | except: | ||
4272 | 348 | t = time() | ||
4273 | 349 | times.setdefault(f,[]).append(t) | ||
4274 | 350 | |||
4275 | 351 | for f in os.listdir(self.dir_datacache): | ||
4276 | 352 | p = os.path.join(self.dir_datacache,f) | ||
4277 | 353 | try: | ||
4278 | 354 | f = unhex(os.path.basename(f)) | ||
4279 | 355 | assert len(f) == 20 | ||
4280 | 356 | except: | ||
4281 | 357 | continue | ||
4282 | 358 | names.setdefault(f,[]).append(p) | ||
4283 | 359 | try: | ||
4284 | 360 | t = os.path.getmtime(p) | ||
4285 | 361 | except: | ||
4286 | 362 | t = time() | ||
4287 | 363 | times.setdefault(f,[]).append(t) | ||
4288 | 364 | |||
4289 | 365 | for f in os.listdir(self.dir_piececache): | ||
4290 | 366 | p = os.path.join(self.dir_piececache,f) | ||
4291 | 367 | try: | ||
4292 | 368 | f = unhex(os.path.basename(f)) | ||
4293 | 369 | assert len(f) == 20 | ||
4294 | 370 | except: | ||
4295 | 371 | continue | ||
4296 | 372 | for f2 in os.listdir(p): | ||
4297 | 373 | p2 = os.path.join(p,f2) | ||
4298 | 374 | names.setdefault(f,[]).append(p2) | ||
4299 | 375 | try: | ||
4300 | 376 | t = os.path.getmtime(p2) | ||
4301 | 377 | except: | ||
4302 | 378 | t = time() | ||
4303 | 379 | times.setdefault(f,[]).append(t) | ||
4304 | 380 | names.setdefault(f,[]).append(p) | ||
4305 | 381 | |||
4306 | 382 | for k,v in times.items(): | ||
4307 | 383 | if max(v) < exptime and not k in still_active: | ||
4308 | 384 | for f in names[k]: | ||
4309 | 385 | try: | ||
4310 | 386 | os.remove(f) | ||
4311 | 387 | except: | ||
4312 | 388 | try: | ||
4313 | 389 | os.removedirs(f) | ||
4314 | 390 | except: | ||
4315 | 391 | pass | ||
4316 | 392 | |||
4317 | 393 | |||
4318 | 394 | def deleteOldTorrents(self, days, still_active = []): | ||
4319 | 395 | self.deleteOldCacheData(days, still_active, True) | ||
4320 | 396 | |||
4321 | 397 | |||
4322 | 398 | ###### OTHER ###### | ||
4323 | 399 | |||
4324 | 400 | def getIconDir(self): | ||
4325 | 401 | return self.dir_icons | ||
4326 | 402 | 0 | ||
4327 | === removed file '.pc/09_timtuckerfixes.dpatch/BitTornado/RawServer.py' | |||
4328 | --- .pc/09_timtuckerfixes.dpatch/BitTornado/RawServer.py 2010-03-21 14:36:30 +0000 | |||
4329 | +++ .pc/09_timtuckerfixes.dpatch/BitTornado/RawServer.py 1970-01-01 00:00:00 +0000 | |||
4330 | @@ -1,195 +0,0 @@ | |||
4331 | 1 | # Written by Bram Cohen | ||
4332 | 2 | # see LICENSE.txt for license information | ||
4333 | 3 | |||
4334 | 4 | from bisect import insort | ||
4335 | 5 | from SocketHandler import SocketHandler, UPnP_ERROR | ||
4336 | 6 | import socket | ||
4337 | 7 | from cStringIO import StringIO | ||
4338 | 8 | from traceback import print_exc | ||
4339 | 9 | from select import error | ||
4340 | 10 | from threading import Thread, Event | ||
4341 | 11 | from time import sleep | ||
4342 | 12 | from clock import clock | ||
4343 | 13 | import sys | ||
4344 | 14 | try: | ||
4345 | 15 | True | ||
4346 | 16 | except: | ||
4347 | 17 | True = 1 | ||
4348 | 18 | False = 0 | ||
4349 | 19 | |||
4350 | 20 | |||
4351 | 21 | def autodetect_ipv6(): | ||
4352 | 22 | try: | ||
4353 | 23 | assert sys.version_info >= (2,3) | ||
4354 | 24 | assert socket.has_ipv6 | ||
4355 | 25 | socket.socket(socket.AF_INET6, socket.SOCK_STREAM) | ||
4356 | 26 | except: | ||
4357 | 27 | return 0 | ||
4358 | 28 | return 1 | ||
4359 | 29 | |||
4360 | 30 | def autodetect_socket_style(): | ||
4361 | 31 | if sys.platform.find('linux') < 0: | ||
4362 | 32 | return 1 | ||
4363 | 33 | else: | ||
4364 | 34 | try: | ||
4365 | 35 | f = open('/proc/sys/net/ipv6/bindv6only','r') | ||
4366 | 36 | dual_socket_style = int(f.read()) | ||
4367 | 37 | f.close() | ||
4368 | 38 | return int(not dual_socket_style) | ||
4369 | 39 | except: | ||
4370 | 40 | return 0 | ||
4371 | 41 | |||
4372 | 42 | |||
4373 | 43 | READSIZE = 32768 | ||
4374 | 44 | |||
4375 | 45 | class RawServer: | ||
4376 | 46 | def __init__(self, doneflag, timeout_check_interval, timeout, noisy = True, | ||
4377 | 47 | ipv6_enable = True, failfunc = lambda x: None, errorfunc = None, | ||
4378 | 48 | sockethandler = None, excflag = Event()): | ||
4379 | 49 | self.timeout_check_interval = timeout_check_interval | ||
4380 | 50 | self.timeout = timeout | ||
4381 | 51 | self.servers = {} | ||
4382 | 52 | self.single_sockets = {} | ||
4383 | 53 | self.dead_from_write = [] | ||
4384 | 54 | self.doneflag = doneflag | ||
4385 | 55 | self.noisy = noisy | ||
4386 | 56 | self.failfunc = failfunc | ||
4387 | 57 | self.errorfunc = errorfunc | ||
4388 | 58 | self.exccount = 0 | ||
4389 | 59 | self.funcs = [] | ||
4390 | 60 | self.externally_added = [] | ||
4391 | 61 | self.finished = Event() | ||
4392 | 62 | self.tasks_to_kill = [] | ||
4393 | 63 | self.excflag = excflag | ||
4394 | 64 | |||
4395 | 65 | if sockethandler is None: | ||
4396 | 66 | sockethandler = SocketHandler(timeout, ipv6_enable, READSIZE) | ||
4397 | 67 | self.sockethandler = sockethandler | ||
4398 | 68 | self.add_task(self.scan_for_timeouts, timeout_check_interval) | ||
4399 | 69 | |||
4400 | 70 | def get_exception_flag(self): | ||
4401 | 71 | return self.excflag | ||
4402 | 72 | |||
4403 | 73 | def _add_task(self, func, delay, id = None): | ||
4404 | 74 | assert float(delay) >= 0 | ||
4405 | 75 | insort(self.funcs, (clock() + delay, func, id)) | ||
4406 | 76 | |||
4407 | 77 | def add_task(self, func, delay = 0, id = None): | ||
4408 | 78 | assert float(delay) >= 0 | ||
4409 | 79 | self.externally_added.append((func, delay, id)) | ||
4410 | 80 | |||
4411 | 81 | def scan_for_timeouts(self): | ||
4412 | 82 | self.add_task(self.scan_for_timeouts, self.timeout_check_interval) | ||
4413 | 83 | self.sockethandler.scan_for_timeouts() | ||
4414 | 84 | |||
4415 | 85 | def bind(self, port, bind = '', reuse = False, | ||
4416 | 86 | ipv6_socket_style = 1, upnp = False): | ||
4417 | 87 | self.sockethandler.bind(port, bind, reuse, ipv6_socket_style, upnp) | ||
4418 | 88 | |||
4419 | 89 | def find_and_bind(self, minport, maxport, bind = '', reuse = False, | ||
4420 | 90 | ipv6_socket_style = 1, upnp = 0, randomizer = False): | ||
4421 | 91 | return self.sockethandler.find_and_bind(minport, maxport, bind, reuse, | ||
4422 | 92 | ipv6_socket_style, upnp, randomizer) | ||
4423 | 93 | |||
4424 | 94 | def start_connection_raw(self, dns, socktype, handler = None): | ||
4425 | 95 | return self.sockethandler.start_connection_raw(dns, socktype, handler) | ||
4426 | 96 | |||
4427 | 97 | def start_connection(self, dns, handler = None, randomize = False): | ||
4428 | 98 | return self.sockethandler.start_connection(dns, handler, randomize) | ||
4429 | 99 | |||
4430 | 100 | def get_stats(self): | ||
4431 | 101 | return self.sockethandler.get_stats() | ||
4432 | 102 | |||
4433 | 103 | def pop_external(self): | ||
4434 | 104 | while self.externally_added: | ||
4435 | 105 | (a, b, c) = self.externally_added.pop(0) | ||
4436 | 106 | self._add_task(a, b, c) | ||
4437 | 107 | |||
4438 | 108 | |||
4439 | 109 | def listen_forever(self, handler): | ||
4440 | 110 | self.sockethandler.set_handler(handler) | ||
4441 | 111 | try: | ||
4442 | 112 | while not self.doneflag.isSet(): | ||
4443 | 113 | try: | ||
4444 | 114 | self.pop_external() | ||
4445 | 115 | self._kill_tasks() | ||
4446 | 116 | if self.funcs: | ||
4447 | 117 | period = self.funcs[0][0] + 0.001 - clock() | ||
4448 | 118 | else: | ||
4449 | 119 | period = 2 ** 30 | ||
4450 | 120 | if period < 0: | ||
4451 | 121 | period = 0 | ||
4452 | 122 | events = self.sockethandler.do_poll(period) | ||
4453 | 123 | if self.doneflag.isSet(): | ||
4454 | 124 | return | ||
4455 | 125 | while self.funcs and self.funcs[0][0] <= clock(): | ||
4456 | 126 | garbage1, func, id = self.funcs.pop(0) | ||
4457 | 127 | if id in self.tasks_to_kill: | ||
4458 | 128 | pass | ||
4459 | 129 | try: | ||
4460 | 130 | # print func.func_name | ||
4461 | 131 | func() | ||
4462 | 132 | except (SystemError, MemoryError), e: | ||
4463 | 133 | self.failfunc(str(e)) | ||
4464 | 134 | return | ||
4465 | 135 | except KeyboardInterrupt: | ||
4466 | 136 | # self.exception(True) | ||
4467 | 137 | return | ||
4468 | 138 | except: | ||
4469 | 139 | if self.noisy: | ||
4470 | 140 | self.exception() | ||
4471 | 141 | self.sockethandler.close_dead() | ||
4472 | 142 | self.sockethandler.handle_events(events) | ||
4473 | 143 | if self.doneflag.isSet(): | ||
4474 | 144 | return | ||
4475 | 145 | self.sockethandler.close_dead() | ||
4476 | 146 | except (SystemError, MemoryError), e: | ||
4477 | 147 | self.failfunc(str(e)) | ||
4478 | 148 | return | ||
4479 | 149 | except error: | ||
4480 | 150 | if self.doneflag.isSet(): | ||
4481 | 151 | return | ||
4482 | 152 | except KeyboardInterrupt: | ||
4483 | 153 | # self.exception(True) | ||
4484 | 154 | return | ||
4485 | 155 | except: | ||
4486 | 156 | self.exception() | ||
4487 | 157 | if self.exccount > 10: | ||
4488 | 158 | return | ||
4489 | 159 | finally: | ||
4490 | 160 | # self.sockethandler.shutdown() | ||
4491 | 161 | self.finished.set() | ||
4492 | 162 | |||
4493 | 163 | def is_finished(self): | ||
4494 | 164 | return self.finished.isSet() | ||
4495 | 165 | |||
4496 | 166 | def wait_until_finished(self): | ||
4497 | 167 | self.finished.wait() | ||
4498 | 168 | |||
4499 | 169 | def _kill_tasks(self): | ||
4500 | 170 | if self.tasks_to_kill: | ||
4501 | 171 | new_funcs = [] | ||
4502 | 172 | for (t, func, id) in self.funcs: | ||
4503 | 173 | if id not in self.tasks_to_kill: | ||
4504 | 174 | new_funcs.append((t, func, id)) | ||
4505 | 175 | self.funcs = new_funcs | ||
4506 | 176 | self.tasks_to_kill = [] | ||
4507 | 177 | |||
4508 | 178 | def kill_tasks(self, id): | ||
4509 | 179 | self.tasks_to_kill.append(id) | ||
4510 | 180 | |||
4511 | 181 | def exception(self, kbint = False): | ||
4512 | 182 | if not kbint: | ||
4513 | 183 | self.excflag.set() | ||
4514 | 184 | self.exccount += 1 | ||
4515 | 185 | if self.errorfunc is None: | ||
4516 | 186 | print_exc() | ||
4517 | 187 | else: | ||
4518 | 188 | data = StringIO() | ||
4519 | 189 | print_exc(file = data) | ||
4520 | 190 | # print data.getvalue() # report exception here too | ||
4521 | 191 | if not kbint: # don't report here if it's a keyboard interrupt | ||
4522 | 192 | self.errorfunc(data.getvalue()) | ||
4523 | 193 | |||
4524 | 194 | def shutdown(self): | ||
4525 | 195 | self.sockethandler.shutdown() | ||
4526 | 196 | 0 | ||
4527 | === removed file '.pc/09_timtuckerfixes.dpatch/BitTornado/clock.py' | |||
4528 | --- .pc/09_timtuckerfixes.dpatch/BitTornado/clock.py 2010-03-21 14:36:30 +0000 | |||
4529 | +++ .pc/09_timtuckerfixes.dpatch/BitTornado/clock.py 1970-01-01 00:00:00 +0000 | |||
4530 | @@ -1,27 +0,0 @@ | |||
4531 | 1 | # Written by John Hoffman | ||
4532 | 2 | # see LICENSE.txt for license information | ||
4533 | 3 | |||
4534 | 4 | from time import * | ||
4535 | 5 | import sys | ||
4536 | 6 | |||
4537 | 7 | _MAXFORWARD = 100 | ||
4538 | 8 | _FUDGE = 1 | ||
4539 | 9 | |||
4540 | 10 | class RelativeTime: | ||
4541 | 11 | def __init__(self): | ||
4542 | 12 | self.time = time() | ||
4543 | 13 | self.offset = 0 | ||
4544 | 14 | |||
4545 | 15 | def get_time(self): | ||
4546 | 16 | t = time() + self.offset | ||
4547 | 17 | if t < self.time or t > self.time + _MAXFORWARD: | ||
4548 | 18 | self.time += _FUDGE | ||
4549 | 19 | self.offset += self.time - t | ||
4550 | 20 | return self.time | ||
4551 | 21 | self.time = t | ||
4552 | 22 | return t | ||
4553 | 23 | |||
4554 | 24 | if sys.platform != 'win32': | ||
4555 | 25 | _RTIME = RelativeTime() | ||
4556 | 26 | def clock(): | ||
4557 | 27 | return _RTIME.get_time() | ||
4558 | 28 | \ No newline at end of file | 0 | \ No newline at end of file |
4559 | 29 | 1 | ||
4560 | === removed file '.pc/09_timtuckerfixes.dpatch/BitTornado/download_bt1.py' | |||
4561 | --- .pc/09_timtuckerfixes.dpatch/BitTornado/download_bt1.py 2010-03-21 14:36:30 +0000 | |||
4562 | +++ .pc/09_timtuckerfixes.dpatch/BitTornado/download_bt1.py 1970-01-01 00:00:00 +0000 | |||
4563 | @@ -1,877 +0,0 @@ | |||
4564 | 1 | # Written by Bram Cohen | ||
4565 | 2 | # see LICENSE.txt for license information | ||
4566 | 3 | |||
4567 | 4 | from zurllib import urlopen | ||
4568 | 5 | from urlparse import urlparse | ||
4569 | 6 | from BT1.btformats import check_message | ||
4570 | 7 | from BT1.Choker import Choker | ||
4571 | 8 | from BT1.Storage import Storage | ||
4572 | 9 | from BT1.StorageWrapper import StorageWrapper | ||
4573 | 10 | from BT1.FileSelector import FileSelector | ||
4574 | 11 | from BT1.Uploader import Upload | ||
4575 | 12 | from BT1.Downloader import Downloader | ||
4576 | 13 | from BT1.HTTPDownloader import HTTPDownloader | ||
4577 | 14 | from BT1.Connecter import Connecter | ||
4578 | 15 | from RateLimiter import RateLimiter | ||
4579 | 16 | from BT1.Encrypter import Encoder | ||
4580 | 17 | from RawServer import RawServer, autodetect_ipv6, autodetect_socket_style | ||
4581 | 18 | from BT1.Rerequester import Rerequester | ||
4582 | 19 | from BT1.DownloaderFeedback import DownloaderFeedback | ||
4583 | 20 | from RateMeasure import RateMeasure | ||
4584 | 21 | from CurrentRateMeasure import Measure | ||
4585 | 22 | from BT1.PiecePicker import PiecePicker | ||
4586 | 23 | from BT1.Statistics import Statistics | ||
4587 | 24 | from ConfigDir import ConfigDir | ||
4588 | 25 | from bencode import bencode, bdecode | ||
4589 | 26 | from natpunch import UPnP_test | ||
4590 | 27 | from sha import sha | ||
4591 | 28 | from os import path, makedirs, listdir | ||
4592 | 29 | from parseargs import parseargs, formatDefinitions, defaultargs | ||
4593 | 30 | from socket import error as socketerror | ||
4594 | 31 | from random import seed | ||
4595 | 32 | from threading import Thread, Event | ||
4596 | 33 | from clock import clock | ||
4597 | 34 | from BTcrypto import CRYPTO_OK | ||
4598 | 35 | from __init__ import createPeerID | ||
4599 | 36 | |||
4600 | 37 | try: | ||
4601 | 38 | True | ||
4602 | 39 | except: | ||
4603 | 40 | True = 1 | ||
4604 | 41 | False = 0 | ||
4605 | 42 | |||
4606 | 43 | defaults = [ | ||
4607 | 44 | ('max_uploads', 7, | ||
4608 | 45 | "the maximum number of uploads to allow at once."), | ||
4609 | 46 | ('keepalive_interval', 120.0, | ||
4610 | 47 | 'number of seconds to pause between sending keepalives'), | ||
4611 | 48 | ('download_slice_size', 2 ** 14, | ||
4612 | 49 | "How many bytes to query for per request."), | ||
4613 | 50 | ('upload_unit_size', 1460, | ||
4614 | 51 | "when limiting upload rate, how many bytes to send at a time"), | ||
4615 | 52 | ('request_backlog', 10, | ||
4616 | 53 | "maximum number of requests to keep in a single pipe at once."), | ||
4617 | 54 | ('max_message_length', 2 ** 23, | ||
4618 | 55 | "maximum length prefix encoding you'll accept over the wire - larger values get the connection dropped."), | ||
4619 | 56 | ('ip', '', | ||
4620 | 57 | "ip to report you have to the tracker."), | ||
4621 | 58 | ('minport', 10000, 'minimum port to listen on, counts up if unavailable'), | ||
4622 | 59 | ('maxport', 60000, 'maximum port to listen on'), | ||
4623 | 60 | ('random_port', 1, 'whether to choose randomly inside the port range ' + | ||
4624 | 61 | 'instead of counting up linearly'), | ||
4625 | 62 | ('responsefile', '', | ||
4626 | 63 | 'file the server response was stored in, alternative to url'), | ||
4627 | 64 | ('url', '', | ||
4628 | 65 | 'url to get file from, alternative to responsefile'), | ||
4629 | 66 | ('crypto_allowed', int(CRYPTO_OK), | ||
4630 | 67 | 'whether to allow the client to accept encrypted connections'), | ||
4631 | 68 | ('crypto_only', 0, | ||
4632 | 69 | 'whether to only create or allow encrypted connections'), | ||
4633 | 70 | ('crypto_stealth', 0, | ||
4634 | 71 | 'whether to prevent all non-encrypted connection attempts; ' + | ||
4635 | 72 | 'will result in an effectively firewalled state on older trackers'), | ||
4636 | 73 | ('selector_enabled', 1, | ||
4637 | 74 | 'whether to enable the file selector and fast resume function'), | ||
4638 | 75 | ('expire_cache_data', 10, | ||
4639 | 76 | 'the number of days after which you wish to expire old cache data ' + | ||
4640 | 77 | '(0 = disabled)'), | ||
4641 | 78 | ('priority', '', | ||
4642 | 79 | 'a list of file priorities separated by commas, must be one per file, ' + | ||
4643 | 80 | '0 = highest, 1 = normal, 2 = lowest, -1 = download disabled'), | ||
4644 | 81 | ('saveas', '', | ||
4645 | 82 | 'local file name to save the file as, null indicates query user'), | ||
4646 | 83 | ('timeout', 300.0, | ||
4647 | 84 | 'time to wait between closing sockets which nothing has been received on'), | ||
4648 | 85 | ('timeout_check_interval', 60.0, | ||
4649 | 86 | 'time to wait between checking if any connections have timed out'), | ||
4650 | 87 | ('max_slice_length', 2 ** 17, | ||
4651 | 88 | "maximum length slice to send to peers, larger requests are ignored"), | ||
4652 | 89 | ('max_rate_period', 20.0, | ||
4653 | 90 | "maximum amount of time to guess the current rate estimate represents"), | ||
4654 | 91 | ('bind', '', | ||
4655 | 92 | 'comma-separated list of ips/hostnames to bind to locally'), | ||
4656 | 93 | # ('ipv6_enabled', autodetect_ipv6(), | ||
4657 | 94 | ('ipv6_enabled', 0, | ||
4658 | 95 | 'allow the client to connect to peers via IPv6'), | ||
4659 | 96 | ('ipv6_binds_v4', autodetect_socket_style(), | ||
4660 | 97 | "set if an IPv6 server socket won't also field IPv4 connections"), | ||
4661 | 98 | ('upnp_nat_access', 1, | ||
4662 | 99 | 'attempt to autoconfigure a UPnP router to forward a server port ' + | ||
4663 | 100 | '(0 = disabled, 1 = mode 1 [fast], 2 = mode 2 [slow])'), | ||
4664 | 101 | ('upload_rate_fudge', 5.0, | ||
4665 | 102 | 'time equivalent of writing to kernel-level TCP buffer, for rate adjustment'), | ||
4666 | 103 | ('tcp_ack_fudge', 0.03, | ||
4667 | 104 | 'how much TCP ACK download overhead to add to upload rate calculations ' + | ||
4668 | 105 | '(0 = disabled)'), | ||
4669 | 106 | ('display_interval', .5, | ||
4670 | 107 | 'time between updates of displayed information'), | ||
4671 | 108 | ('rerequest_interval', 5 * 60, | ||
4672 | 109 | 'time to wait between requesting more peers'), | ||
4673 | 110 | ('min_peers', 20, | ||
4674 | 111 | 'minimum number of peers to not do rerequesting'), | ||
4675 | 112 | ('http_timeout', 60, | ||
4676 | 113 | 'number of seconds to wait before assuming that an http connection has timed out'), | ||
4677 | 114 | ('max_initiate', 40, | ||
4678 | 115 | 'number of peers at which to stop initiating new connections'), | ||
4679 | 116 | ('check_hashes', 1, | ||
4680 | 117 | 'whether to check hashes on disk'), | ||
4681 | 118 | ('max_upload_rate', 0, | ||
4682 | 119 | 'maximum kB/s to upload at (0 = no limit, -1 = automatic)'), | ||
4683 | 120 | ('max_download_rate', 0, | ||
4684 | 121 | 'maximum kB/s to download at (0 = no limit)'), | ||
4685 | 122 | ('alloc_type', 'normal', | ||
4686 | 123 | 'allocation type (may be normal, background, pre-allocate or sparse)'), | ||
4687 | 124 | ('alloc_rate', 2.0, | ||
4688 | 125 | 'rate (in MiB/s) to allocate space at using background allocation'), | ||
4689 | 126 | ('buffer_reads', 1, | ||
4690 | 127 | 'whether to buffer disk reads'), | ||
4691 | 128 | ('write_buffer_size', 4, | ||
4692 | 129 | 'the maximum amount of space to use for buffering disk writes ' + | ||
4693 | 130 | '(in megabytes, 0 = disabled)'), | ||
4694 | 131 | ('breakup_seed_bitfield', 1, | ||
4695 | 132 | 'sends an incomplete bitfield and then fills with have messages, ' | ||
4696 | 133 | 'in order to get around stupid ISP manipulation'), | ||
4697 | 134 | ('snub_time', 30.0, | ||
4698 | 135 | "seconds to wait for data to come in over a connection before assuming it's semi-permanently choked"), | ||
4699 | 136 | ('spew', 0, | ||
4700 | 137 | "whether to display diagnostic info to stdout"), | ||
4701 | 138 | ('rarest_first_cutoff', 2, | ||
4702 | 139 | "number of downloads at which to switch from random to rarest first"), | ||
4703 | 140 | ('rarest_first_priority_cutoff', 5, | ||
4704 | 141 | 'the number of peers which need to have a piece before other partials take priority over rarest first'), | ||
4705 | 142 | ('min_uploads', 4, | ||
4706 | 143 | "the number of uploads to fill out to with extra optimistic unchokes"), | ||
4707 | 144 | ('max_files_open', 50, | ||
4708 | 145 | 'the maximum number of files to keep open at a time, 0 means no limit'), | ||
4709 | 146 | ('round_robin_period', 30, | ||
4710 | 147 | "the number of seconds between the client's switching upload targets"), | ||
4711 | 148 | ('super_seeder', 0, | ||
4712 | 149 | "whether to use special upload-efficiency-maximizing routines (only for dedicated seeds)"), | ||
4713 | 150 | ('security', 1, | ||
4714 | 151 | "whether to enable extra security features intended to prevent abuse"), | ||
4715 | 152 | ('max_connections', 0, | ||
4716 | 153 | "the absolute maximum number of peers to connect with (0 = no limit)"), | ||
4717 | 154 | ('auto_kick', 1, | ||
4718 | 155 | "whether to allow the client to automatically kick/ban peers that send bad data"), | ||
4719 | 156 | ('double_check', 1, | ||
4720 | 157 | "whether to double-check data being written to the disk for errors (may increase CPU load)"), | ||
4721 | 158 | ('triple_check', 0, | ||
4722 | 159 | "whether to thoroughly check data being written to the disk (may slow disk access)"), | ||
4723 | 160 | ('lock_files', 1, | ||
4724 | 161 | "whether to lock files the client is working with"), | ||
4725 | 162 | ('lock_while_reading', 0, | ||
4726 | 163 | "whether to lock access to files being read"), | ||
4727 | 164 | ('auto_flush', 0, | ||
4728 | 165 | "minutes between automatic flushes to disk (0 = disabled)"), | ||
4729 | 166 | ('dedicated_seed_id', '', | ||
4730 | 167 | "code to send to tracker identifying as a dedicated seed"), | ||
4731 | 168 | ] | ||
4732 | 169 | |||
4733 | 170 | argslistheader = 'Arguments are:\n\n' | ||
4734 | 171 | |||
4735 | 172 | |||
4736 | 173 | def _failfunc(x): | ||
4737 | 174 | print x | ||
4738 | 175 | |||
4739 | 176 | # old-style downloader | ||
4740 | 177 | def download(params, filefunc, statusfunc, finfunc, errorfunc, doneflag, cols, | ||
4741 | 178 | pathFunc = None, presets = {}, exchandler = None, | ||
4742 | 179 | failed = _failfunc, paramfunc = None): | ||
4743 | 180 | |||
4744 | 181 | try: | ||
4745 | 182 | config = parse_params(params, presets) | ||
4746 | 183 | except ValueError, e: | ||
4747 | 184 | failed('error: ' + str(e) + '\nrun with no args for parameter explanations') | ||
4748 | 185 | return | ||
4749 | 186 | if not config: | ||
4750 | 187 | errorfunc(get_usage()) | ||
4751 | 188 | return | ||
4752 | 189 | |||
4753 | 190 | myid = createPeerID() | ||
4754 | 191 | seed(myid) | ||
4755 | 192 | |||
4756 | 193 | rawserver = RawServer(doneflag, config['timeout_check_interval'], | ||
4757 | 194 | config['timeout'], ipv6_enable = config['ipv6_enabled'], | ||
4758 | 195 | failfunc = failed, errorfunc = exchandler) | ||
4759 | 196 | |||
4760 | 197 | upnp_type = UPnP_test(config['upnp_nat_access']) | ||
4761 | 198 | try: | ||
4762 | 199 | listen_port = rawserver.find_and_bind(config['minport'], config['maxport'], | ||
4763 | 200 | config['bind'], ipv6_socket_style = config['ipv6_binds_v4'], | ||
4764 | 201 | upnp = upnp_type, randomizer = config['random_port']) | ||
4765 | 202 | except socketerror, e: | ||
4766 | 203 | failed("Couldn't listen - " + str(e)) | ||
4767 | 204 | return | ||
4768 | 205 | |||
4769 | 206 | response = get_response(config['responsefile'], config['url'], failed) | ||
4770 | 207 | if not response: | ||
4771 | 208 | return | ||
4772 | 209 | |||
4773 | 210 | infohash = sha(bencode(response['info'])).digest() | ||
4774 | 211 | |||
4775 | 212 | d = BT1Download(statusfunc, finfunc, errorfunc, exchandler, doneflag, | ||
4776 | 213 | config, response, infohash, myid, rawserver, listen_port) | ||
4777 | 214 | |||
4778 | 215 | if not d.saveAs(filefunc): | ||
4779 | 216 | return | ||
4780 | 217 | |||
4781 | 218 | if pathFunc: | ||
4782 | 219 | pathFunc(d.getFilename()) | ||
4783 | 220 | |||
4784 | 221 | hashcheck = d.initFiles(old_style = True) | ||
4785 | 222 | if not hashcheck: | ||
4786 | 223 | return | ||
4787 | 224 | if not hashcheck(): | ||
4788 | 225 | return | ||
4789 | 226 | if not d.startEngine(): | ||
4790 | 227 | return | ||
4791 | 228 | d.startRerequester() | ||
4792 | 229 | d.autoStats() | ||
4793 | 230 | |||
4794 | 231 | statusfunc(activity = 'connecting to peers') | ||
4795 | 232 | |||
4796 | 233 | if paramfunc: | ||
4797 | 234 | paramfunc({ 'max_upload_rate' : d.setUploadRate, # change_max_upload_rate(<int KiB/sec>) | ||
4798 | 235 | 'max_uploads': d.setConns, # change_max_uploads(<int max uploads>) | ||
4799 | 236 | 'listen_port' : listen_port, # int | ||
4800 | 237 | 'peer_id' : myid, # string | ||
4801 | 238 | 'info_hash' : infohash, # string | ||
4802 | 239 | 'start_connection' : d._startConnection, # start_connection((<string ip>, <int port>), <peer id>) | ||
4803 | 240 | }) | ||
4804 | 241 | |||
4805 | 242 | rawserver.listen_forever(d.getPortHandler()) | ||
4806 | 243 | |||
4807 | 244 | d.shutdown() | ||
4808 | 245 | |||
4809 | 246 | |||
4810 | 247 | def parse_params(params, presets = {}): | ||
4811 | 248 | if len(params) == 0: | ||
4812 | 249 | return None | ||
4813 | 250 | config, args = parseargs(params, defaults, 0, 1, presets = presets) | ||
4814 | 251 | if args: | ||
4815 | 252 | if config['responsefile'] or config['url']: | ||
4816 | 253 | raise ValueError,'must have responsefile or url as arg or parameter, not both' | ||
4817 | 254 | if path.isfile(args[0]): | ||
4818 | 255 | config['responsefile'] = args[0] | ||
4819 | 256 | else: | ||
4820 | 257 | try: | ||
4821 | 258 | urlparse(args[0]) | ||
4822 | 259 | except: | ||
4823 | 260 | raise ValueError, 'bad filename or url' | ||
4824 | 261 | config['url'] = args[0] | ||
4825 | 262 | elif (config['responsefile'] == '') == (config['url'] == ''): | ||
4826 | 263 | raise ValueError, 'need responsefile or url, must have one, cannot have both' | ||
4827 | 264 | return config | ||
4828 | 265 | |||
4829 | 266 | |||
4830 | 267 | def get_usage(defaults = defaults, cols = 100, presets = {}): | ||
4831 | 268 | return (argslistheader + formatDefinitions(defaults, cols, presets)) | ||
4832 | 269 | |||
4833 | 270 | |||
4834 | 271 | def get_response(file, url, errorfunc): | ||
4835 | 272 | try: | ||
4836 | 273 | if file: | ||
4837 | 274 | h = open(file, 'rb') | ||
4838 | 275 | try: | ||
4839 | 276 | line = h.read(10) # quick test to see if responsefile contains a dict | ||
4840 | 277 | front,garbage = line.split(':',1) | ||
4841 | 278 | assert front[0] == 'd' | ||
4842 | 279 | int(front[1:]) | ||
4843 | 280 | except: | ||
4844 | 281 | errorfunc(file+' is not a valid responsefile') | ||
4845 | 282 | return None | ||
4846 | 283 | try: | ||
4847 | 284 | h.seek(0) | ||
4848 | 285 | except: | ||
4849 | 286 | try: | ||
4850 | 287 | h.close() | ||
4851 | 288 | except: | ||
4852 | 289 | pass | ||
4853 | 290 | h = open(file, 'rb') | ||
4854 | 291 | else: | ||
4855 | 292 | try: | ||
4856 | 293 | h = urlopen(url) | ||
4857 | 294 | except: | ||
4858 | 295 | errorfunc(url+' bad url') | ||
4859 | 296 | return None | ||
4860 | 297 | response = h.read() | ||
4861 | 298 | |||
4862 | 299 | except IOError, e: | ||
4863 | 300 | errorfunc('problem getting response info - ' + str(e)) | ||
4864 | 301 | return None | ||
4865 | 302 | try: | ||
4866 | 303 | h.close() | ||
4867 | 304 | except: | ||
4868 | 305 | pass | ||
4869 | 306 | try: | ||
4870 | 307 | try: | ||
4871 | 308 | response = bdecode(response) | ||
4872 | 309 | except: | ||
4873 | 310 | errorfunc("warning: bad data in responsefile") | ||
4874 | 311 | response = bdecode(response, sloppy=1) | ||
4875 | 312 | check_message(response) | ||
4876 | 313 | except ValueError, e: | ||
4877 | 314 | errorfunc("got bad file info - " + str(e)) | ||
4878 | 315 | return None | ||
4879 | 316 | |||
4880 | 317 | return response | ||
4881 | 318 | |||
4882 | 319 | |||
4883 | 320 | class BT1Download: | ||
4884 | 321 | def __init__(self, statusfunc, finfunc, errorfunc, excfunc, doneflag, | ||
4885 | 322 | config, response, infohash, id, rawserver, port, | ||
4886 | 323 | appdataobj = None): | ||
4887 | 324 | self.statusfunc = statusfunc | ||
4888 | 325 | self.finfunc = finfunc | ||
4889 | 326 | self.errorfunc = errorfunc | ||
4890 | 327 | self.excfunc = excfunc | ||
4891 | 328 | self.doneflag = doneflag | ||
4892 | 329 | self.config = config | ||
4893 | 330 | self.response = response | ||
4894 | 331 | self.infohash = infohash | ||
4895 | 332 | self.myid = id | ||
4896 | 333 | self.rawserver = rawserver | ||
4897 | 334 | self.port = port | ||
4898 | 335 | |||
4899 | 336 | self.info = self.response['info'] | ||
4900 | 337 | self.pieces = [self.info['pieces'][x:x+20] | ||
4901 | 338 | for x in xrange(0, len(self.info['pieces']), 20)] | ||
4902 | 339 | self.len_pieces = len(self.pieces) | ||
4903 | 340 | self.argslistheader = argslistheader | ||
4904 | 341 | self.unpauseflag = Event() | ||
4905 | 342 | self.unpauseflag.set() | ||
4906 | 343 | self.downloader = None | ||
4907 | 344 | self.storagewrapper = None | ||
4908 | 345 | self.fileselector = None | ||
4909 | 346 | self.super_seeding_active = False | ||
4910 | 347 | self.filedatflag = Event() | ||
4911 | 348 | self.spewflag = Event() | ||
4912 | 349 | self.superseedflag = Event() | ||
4913 | 350 | self.whenpaused = None | ||
4914 | 351 | self.finflag = Event() | ||
4915 | 352 | self.rerequest = None | ||
4916 | 353 | self.tcp_ack_fudge = config['tcp_ack_fudge'] | ||
4917 | 354 | |||
4918 | 355 | self.selector_enabled = config['selector_enabled'] | ||
4919 | 356 | if appdataobj: | ||
4920 | 357 | self.appdataobj = appdataobj | ||
4921 | 358 | elif self.selector_enabled: | ||
4922 | 359 | self.appdataobj = ConfigDir() | ||
4923 | 360 | self.appdataobj.deleteOldCacheData( config['expire_cache_data'], | ||
4924 | 361 | [self.infohash] ) | ||
4925 | 362 | |||
4926 | 363 | self.excflag = self.rawserver.get_exception_flag() | ||
4927 | 364 | self.failed = False | ||
4928 | 365 | self.checking = False | ||
4929 | 366 | self.started = False | ||
4930 | 367 | |||
4931 | 368 | self.picker = PiecePicker(self.len_pieces, config['rarest_first_cutoff'], | ||
4932 | 369 | config['rarest_first_priority_cutoff']) | ||
4933 | 370 | self.choker = Choker(config, rawserver.add_task, | ||
4934 | 371 | self.picker, self.finflag.isSet) | ||
4935 | 372 | |||
4936 | 373 | |||
4937 | 374 | def checkSaveLocation(self, loc): | ||
4938 | 375 | if self.info.has_key('length'): | ||
4939 | 376 | return path.exists(loc) | ||
4940 | 377 | for x in self.info['files']: | ||
4941 | 378 | if path.exists(path.join(loc, x['path'][0])): | ||
4942 | 379 | return True | ||
4943 | 380 | return False | ||
4944 | 381 | |||
4945 | 382 | |||
4946 | 383 | def saveAs(self, filefunc, pathfunc = None): | ||
4947 | 384 | try: | ||
4948 | 385 | def make(f, forcedir = False): | ||
4949 | 386 | if not forcedir: | ||
4950 | 387 | f = path.split(f)[0] | ||
4951 | 388 | if f != '' and not path.exists(f): | ||
4952 | 389 | makedirs(f) | ||
4953 | 390 | |||
4954 | 391 | if self.info.has_key('length'): | ||
4955 | 392 | file_length = self.info['length'] | ||
4956 | 393 | file = filefunc(self.info['name'], file_length, | ||
4957 | 394 | self.config['saveas'], False) | ||
4958 | 395 | if file is None: | ||
4959 | 396 | return None | ||
4960 | 397 | make(file) | ||
4961 | 398 | files = [(file, file_length)] | ||
4962 | 399 | else: | ||
4963 | 400 | file_length = 0L | ||
4964 | 401 | for x in self.info['files']: | ||
4965 | 402 | file_length += x['length'] | ||
4966 | 403 | file = filefunc(self.info['name'], file_length, | ||
4967 | 404 | self.config['saveas'], True) | ||
4968 | 405 | if file is None: | ||
4969 | 406 | return None | ||
4970 | 407 | |||
4971 | 408 | # if this path exists, and no files from the info dict exist, we assume it's a new download and | ||
4972 | 409 | # the user wants to create a new directory with the default name | ||
4973 | 410 | existing = 0 | ||
4974 | 411 | if path.exists(file): | ||
4975 | 412 | if not path.isdir(file): | ||
4976 | 413 | self.errorfunc(file + 'is not a dir') | ||
4977 | 414 | return None | ||
4978 | 415 | if len(listdir(file)) > 0: # if it's not empty | ||
4979 | 416 | for x in self.info['files']: | ||
4980 | 417 | if path.exists(path.join(file, x['path'][0])): | ||
4981 | 418 | existing = 1 | ||
4982 | 419 | if not existing: | ||
4983 | 420 | file = path.join(file, self.info['name']) | ||
4984 | 421 | if path.exists(file) and not path.isdir(file): | ||
4985 | 422 | if file[-8:] == '.torrent': | ||
4986 | 423 | file = file[:-8] | ||
4987 | 424 | if path.exists(file) and not path.isdir(file): | ||
4988 | 425 | self.errorfunc("Can't create dir - " + self.info['name']) | ||
4989 | 426 | return None | ||
4990 | 427 | make(file, True) | ||
4991 | 428 | |||
4992 | 429 | # alert the UI to any possible change in path | ||
4993 | 430 | if pathfunc != None: | ||
4994 | 431 | pathfunc(file) | ||
4995 | 432 | |||
4996 | 433 | files = [] | ||
4997 | 434 | for x in self.info['files']: | ||
4998 | 435 | n = file | ||
4999 | 436 | for i in x['path']: | ||
5000 | 437 | n = path.join(n, i) |
(copied from bug comment)
The changes look fine to me.
However, I don't think that your removal of the CVS directory will actually "stick". As I understand it, the bzr importer that maintains the lp:ubuntu/<package> branches basically does:
dget <uploaded package>
dpkg-source -x *.dsc
In doing so, the CVS dir will still be there. I'd advise to either: patches/ 32-remove- CVS-dir. patch -- this could be a pain with an active upstream)
- patch it out (with a debian/
- live with being unable to use debcommit
- fix debcommit to prefer .bzr over CVS