~ubuntu-branches/ubuntu/oneiric/bittornado/oneiric

« back to all changes in this revision

Viewing changes to .pc/25_errors_in_error_handling.dpatch/btdownloadcurses.py

  • Committer: Bazaar Package Importer
  • Author(s): Cameron Dale
  • Date: 2010-03-21 14:36:30 UTC
  • Revision ID: james.westby@ubuntu.com-20100321143630-d1zk1zdasaf8125s
Tags: 0.3.18-10
* New patch from upstream's CVS to allow torrents that only have an
  announce list: 30_announce_list_only_torrents.dpatch (Closes: #551766)
* Fix a lot of lintian warnings
  - Update standards version to 3.8.4 (no changes)
* Fix for when compact_reqd is turned off:
  31_fix_for_compact_reqd_off.dpatch (Closes: #574860)
* Switch to the new "3.0 (quilt)" source format

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
 
 
3
# Written by Henry 'Pi' James
 
4
# see LICENSE.txt for license information
 
5
 
 
6
SPEW_SCROLL_RATE = 1
 
7
 
 
8
from BitTornado import PSYCO
 
9
if PSYCO.psyco:
 
10
    try:
 
11
        import psyco
 
12
        assert psyco.__version__ >= 0x010100f0
 
13
        psyco.full()
 
14
    except:
 
15
        pass
 
16
 
 
17
from BitTornado.download_bt1 import BT1Download, defaults, parse_params, get_usage, get_response
 
18
from BitTornado.RawServer import RawServer, UPnP_ERROR
 
19
from random import seed
 
20
from socket import error as socketerror
 
21
from BitTornado.bencode import bencode
 
22
from BitTornado.natpunch import UPnP_test
 
23
from threading import Event
 
24
from os.path import abspath
 
25
from signal import signal, SIGWINCH
 
26
from sha import sha
 
27
from sys import argv, exit
 
28
import sys
 
29
from time import time, strftime
 
30
from BitTornado.clock import clock
 
31
from BitTornado import createPeerID, version
 
32
from BitTornado.ConfigDir import ConfigDir
 
33
 
 
34
try:
 
35
    import curses
 
36
    import curses.panel
 
37
    from curses.wrapper import wrapper as curses_wrapper
 
38
    from signal import signal, SIGWINCH 
 
39
except:
 
40
    print 'Textmode GUI initialization failed, cannot proceed.'
 
41
    print
 
42
    print 'This download interface requires the standard Python module ' \
 
43
       '"curses", which is unfortunately not available for the native ' \
 
44
       'Windows port of Python. It is however available for the Cygwin ' \
 
45
       'port of Python, running on all Win32 systems (www.cygwin.com).'
 
46
    print
 
47
    print 'You may still use "btdownloadheadless.py" to download.'
 
48
    sys.exit(1)
 
49
 
 
50
assert sys.version >= '2', "Install Python 2.0 or greater"
 
51
try:
 
52
    True
 
53
except:
 
54
    True = 1
 
55
    False = 0
 
56
 
 
57
def fmttime(n):
 
58
    if n == 0:
 
59
        return 'download complete!'
 
60
    try:
 
61
        n = int(n)
 
62
        assert n >= 0 and n < 5184000  # 60 days
 
63
    except:
 
64
        return '<unknown>'
 
65
    m, s = divmod(n, 60)
 
66
    h, m = divmod(m, 60)
 
67
    return 'finishing in %d:%02d:%02d' % (h, m, s)
 
68
 
 
69
def fmtsize(n):
 
70
    s = str(n)
 
71
    size = s[-3:]
 
72
    while len(s) > 3:
 
73
        s = s[:-3]
 
74
        size = '%s,%s' % (s[-3:], size)
 
75
    if n > 999:
 
76
        unit = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
 
77
        i = 1
 
78
        while i + 1 < len(unit) and (n >> 10) >= 999:
 
79
            i += 1
 
80
            n >>= 10
 
81
        n = float(n) / (1 << 10)
 
82
        size = '%s (%.2f %s)' % (size, n, unit[i])
 
83
    return size
 
84
 
 
85
 
 
86
class CursesDisplayer:
 
87
    def __init__(self, scrwin, errlist, doneflag):
 
88
        self.scrwin = scrwin
 
89
        self.errlist = errlist
 
90
        self.doneflag = doneflag
 
91
        
 
92
        signal(SIGWINCH, self.winch_handler)
 
93
        self.changeflag = Event()
 
94
 
 
95
        self.done = 0
 
96
        self.file = ''
 
97
        self.fileSize = ''
 
98
        self.activity = ''
 
99
        self.status = ''
 
100
        self.progress = ''
 
101
        self.downloadTo = ''
 
102
        self.downRate = '---'
 
103
        self.upRate = '---'
 
104
        self.shareRating = ''
 
105
        self.seedStatus = ''
 
106
        self.peerStatus = ''
 
107
        self.errors = []
 
108
        self.last_update_time = 0
 
109
        self.spew_scroll_time = 0
 
110
        self.spew_scroll_pos = 0
 
111
 
 
112
        self._remake_window()
 
113
 
 
114
    def winch_handler(self, signum, stackframe):
 
115
        self.changeflag.set()
 
116
        curses.endwin()
 
117
        self.scrwin.refresh()
 
118
        self.scrwin = curses.newwin(0, 0, 0, 0)
 
119
        self._remake_window()
 
120
 
 
121
    def _remake_window(self):
 
122
        self.scrh, self.scrw = self.scrwin.getmaxyx()
 
123
        self.scrpan = curses.panel.new_panel(self.scrwin)
 
124
        self.labelh, self.labelw, self.labely, self.labelx = 11, 9, 1, 2
 
125
        self.labelwin = curses.newwin(self.labelh, self.labelw,
 
126
                                      self.labely, self.labelx)
 
127
        self.labelpan = curses.panel.new_panel(self.labelwin)
 
128
        self.fieldh, self.fieldw, self.fieldy, self.fieldx = (
 
129
                            self.labelh, self.scrw-2 - self.labelw-3,
 
130
                            1, self.labelw+3)
 
131
        self.fieldwin = curses.newwin(self.fieldh, self.fieldw,
 
132
                                      self.fieldy, self.fieldx)
 
133
        self.fieldwin.nodelay(1)
 
134
        self.fieldpan = curses.panel.new_panel(self.fieldwin)
 
135
        self.spewh, self.speww, self.spewy, self.spewx = (
 
136
            self.scrh - self.labelh - 2, self.scrw - 3, 1 + self.labelh, 2)
 
137
        self.spewwin = curses.newwin(self.spewh, self.speww,
 
138
                                     self.spewy, self.spewx)
 
139
        self.spewpan = curses.panel.new_panel(self.spewwin)
 
140
        try:
 
141
            self.scrwin.border(ord('|'),ord('|'),ord('-'),ord('-'),ord(' '),ord(' '),ord(' '),ord(' '))
 
142
        except:
 
143
            pass
 
144
        self.labelwin.addstr(0, 0, 'file:')
 
145
        self.labelwin.addstr(1, 0, 'size:')
 
146
        self.labelwin.addstr(2, 0, 'dest:')
 
147
        self.labelwin.addstr(3, 0, 'progress:')
 
148
        self.labelwin.addstr(4, 0, 'status:')
 
149
        self.labelwin.addstr(5, 0, 'dl speed:')
 
150
        self.labelwin.addstr(6, 0, 'ul speed:')
 
151
        self.labelwin.addstr(7, 0, 'sharing:')
 
152
        self.labelwin.addstr(8, 0, 'seeds:')
 
153
        self.labelwin.addstr(9, 0, 'peers:')
 
154
        curses.panel.update_panels()
 
155
        curses.doupdate()
 
156
        self.changeflag.clear()
 
157
 
 
158
 
 
159
    def finished(self):
 
160
        self.done = 1
 
161
        self.activity = 'download succeeded!'
 
162
        self.downRate = '---'
 
163
        self.display(fractionDone = 1)
 
164
 
 
165
    def failed(self):
 
166
        self.done = 1
 
167
        self.activity = 'download failed!'
 
168
        self.downRate = '---'
 
169
        self.display()
 
170
 
 
171
    def error(self, errormsg):
 
172
        newerrmsg = strftime('[%H:%M:%S] ') + errormsg
 
173
        self.errors.append(newerrmsg)
 
174
        self.errlist.append(newerrmsg)
 
175
        self.display()
 
176
 
 
177
    def display(self, dpflag = Event(), fractionDone = None, timeEst = None,
 
178
            downRate = None, upRate = None, activity = None,
 
179
            statistics = None, spew = None, **kws):
 
180
 
 
181
        inchar = self.fieldwin.getch()
 
182
        if inchar == 12: # ^L
 
183
            self._remake_window()
 
184
        elif inchar in (ord('q'),ord('Q')):
 
185
            self.doneflag.set()
 
186
 
 
187
        if activity is not None and not self.done:
 
188
            self.activity = activity
 
189
        elif timeEst is not None:
 
190
            self.activity = fmttime(timeEst)
 
191
        if self.changeflag.isSet():
 
192
            return
 
193
        if self.last_update_time + 0.1 > clock() and fractionDone not in (0.0, 1.0) and activity is not None:
 
194
            return
 
195
        self.last_update_time = clock()
 
196
        if fractionDone is not None:
 
197
            blocknum = int(self.fieldw * fractionDone)
 
198
            self.progress = blocknum * '#' + (self.fieldw - blocknum) * '_'
 
199
            self.status = '%s (%.1f%%)' % (self.activity, fractionDone * 100)
 
200
        else:
 
201
            self.status = self.activity
 
202
        if downRate is not None:
 
203
            self.downRate = '%.1f KB/s' % (float(downRate) / (1 << 10))
 
204
        if upRate is not None:
 
205
            self.upRate = '%.1f KB/s' % (float(upRate) / (1 << 10))
 
206
        if statistics is not None:
 
207
           if (statistics.shareRating < 0) or (statistics.shareRating > 100):
 
208
               self.shareRating = 'oo  (%.1f MB up / %.1f MB down)' % (float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20))
 
209
           else:
 
210
               self.shareRating = '%.3f  (%.1f MB up / %.1f MB down)' % (statistics.shareRating, float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20))
 
211
           if not self.done:
 
212
              self.seedStatus = '%d seen now, plus %.3f distributed copies' % (statistics.numSeeds,0.001*int(1000*statistics.numCopies2))
 
213
           else:
 
214
              self.seedStatus = '%d seen recently, plus %.3f distributed copies' % (statistics.numOldSeeds,0.001*int(1000*statistics.numCopies))
 
215
           self.peerStatus = '%d seen now, %.1f%% done at %.1f kB/s' % (statistics.numPeers,statistics.percentDone,float(statistics.torrentRate) / (1 << 10))
 
216
 
 
217
        self.fieldwin.erase()
 
218
        self.fieldwin.addnstr(0, 0, self.file, self.fieldw, curses.A_BOLD)
 
219
        self.fieldwin.addnstr(1, 0, self.fileSize, self.fieldw)
 
220
        self.fieldwin.addnstr(2, 0, self.downloadTo, self.fieldw)
 
221
        if self.progress:
 
222
            self.fieldwin.addnstr(3, 0, self.progress, self.fieldw, curses.A_BOLD)
 
223
        self.fieldwin.addnstr(4, 0, self.status, self.fieldw)
 
224
        self.fieldwin.addnstr(5, 0, self.downRate, self.fieldw)
 
225
        self.fieldwin.addnstr(6, 0, self.upRate, self.fieldw)
 
226
        self.fieldwin.addnstr(7, 0, self.shareRating, self.fieldw)
 
227
        self.fieldwin.addnstr(8, 0, self.seedStatus, self.fieldw)
 
228
        self.fieldwin.addnstr(9, 0, self.peerStatus, self.fieldw)
 
229
 
 
230
        self.spewwin.erase()
 
231
 
 
232
        if not spew:
 
233
            errsize = self.spewh
 
234
            if self.errors:
 
235
                self.spewwin.addnstr(0, 0, "error(s):", self.speww, curses.A_BOLD)
 
236
                errsize = len(self.errors)
 
237
                displaysize = min(errsize, self.spewh)
 
238
                displaytop = errsize - displaysize
 
239
                for i in range(displaysize):
 
240
                    self.spewwin.addnstr(i, self.labelw, self.errors[displaytop + i],
 
241
                                 self.speww-self.labelw-1, curses.A_BOLD)
 
242
        else:
 
243
            if self.errors:
 
244
                self.spewwin.addnstr(0, 0, "error:", self.speww, curses.A_BOLD)
 
245
                self.spewwin.addnstr(0, self.labelw, self.errors[-1],
 
246
                                 self.speww-self.labelw-1, curses.A_BOLD)
 
247
            self.spewwin.addnstr(2, 0, "  #     IP                 Upload           Download     Completed  Speed", self.speww, curses.A_BOLD)
 
248
 
 
249
 
 
250
            if self.spew_scroll_time + SPEW_SCROLL_RATE < clock():
 
251
                self.spew_scroll_time = clock()
 
252
                if len(spew) > self.spewh-5 or self.spew_scroll_pos > 0:
 
253
                    self.spew_scroll_pos += 1
 
254
            if self.spew_scroll_pos > len(spew):
 
255
                self.spew_scroll_pos = 0
 
256
 
 
257
            for i in range(len(spew)):
 
258
                spew[i]['lineno'] = i+1
 
259
            spew.append({'lineno': None})
 
260
            spew = spew[self.spew_scroll_pos:] + spew[:self.spew_scroll_pos]                
 
261
            
 
262
            for i in range(min(self.spewh - 5, len(spew))):
 
263
                if not spew[i]['lineno']:
 
264
                    continue
 
265
                self.spewwin.addnstr(i+3, 0, '%3d' % spew[i]['lineno'], 3)
 
266
                self.spewwin.addnstr(i+3, 4, spew[i]['ip']+spew[i]['direction'], 16)
 
267
                if spew[i]['uprate'] > 100:
 
268
                    self.spewwin.addnstr(i+3, 20, '%6.0f KB/s' % (float(spew[i]['uprate']) / 1000), 11)
 
269
                self.spewwin.addnstr(i+3, 32, '-----', 5)
 
270
                if spew[i]['uinterested'] == 1:
 
271
                    self.spewwin.addnstr(i+3, 33, 'I', 1)
 
272
                if spew[i]['uchoked'] == 1:
 
273
                    self.spewwin.addnstr(i+3, 35, 'C', 1)
 
274
                if spew[i]['downrate'] > 100:
 
275
                    self.spewwin.addnstr(i+3, 38, '%6.0f KB/s' % (float(spew[i]['downrate']) / 1000), 11)
 
276
                self.spewwin.addnstr(i+3, 50, '-------', 7)
 
277
                if spew[i]['dinterested'] == 1:
 
278
                    self.spewwin.addnstr(i+3, 51, 'I', 1)
 
279
                if spew[i]['dchoked'] == 1:
 
280
                    self.spewwin.addnstr(i+3, 53, 'C', 1)
 
281
                if spew[i]['snubbed'] == 1:
 
282
                    self.spewwin.addnstr(i+3, 55, 'S', 1)
 
283
                self.spewwin.addnstr(i+3, 58, '%5.1f%%' % (float(int(spew[i]['completed']*1000))/10), 6)
 
284
                if spew[i]['speed'] is not None:
 
285
                    self.spewwin.addnstr(i+3, 64, '%5.0f KB/s' % (float(spew[i]['speed'])/1000), 10)
 
286
 
 
287
            if statistics is not None:
 
288
                self.spewwin.addnstr(self.spewh-1, 0,
 
289
                        'downloading %d pieces, have %d fragments, %d of %d pieces completed'
 
290
                        % ( statistics.storage_active, statistics.storage_dirty,
 
291
                            statistics.storage_numcomplete,
 
292
                            statistics.storage_totalpieces ), self.speww-1 )
 
293
 
 
294
        curses.panel.update_panels()
 
295
        curses.doupdate()
 
296
        dpflag.set()
 
297
 
 
298
    def chooseFile(self, default, size, saveas, dir):
 
299
        self.file = default
 
300
        self.fileSize = fmtsize(size)
 
301
        if saveas == '':
 
302
            saveas = default
 
303
        self.downloadTo = abspath(saveas)
 
304
        return saveas
 
305
 
 
306
def run(scrwin, errlist, params):
 
307
    doneflag = Event()
 
308
    d = CursesDisplayer(scrwin, errlist, doneflag)
 
309
    try:
 
310
        while 1:
 
311
            configdir = ConfigDir('downloadcurses')
 
312
            defaultsToIgnore = ['responsefile', 'url', 'priority']
 
313
            configdir.setDefaults(defaults,defaultsToIgnore)
 
314
            configdefaults = configdir.loadConfig()
 
315
            defaults.append(('save_options',0,
 
316
             "whether to save the current options as the new default configuration " +
 
317
             "(only for btdownloadcurses.py)"))
 
318
            try:
 
319
                config = parse_params(params, configdefaults)
 
320
            except ValueError, e:
 
321
                d.error('error: ' + str(e) + '\nrun with no args for parameter explanations')
 
322
                break
 
323
            if not config:
 
324
                d.error(get_usage(defaults, d.fieldw, configdefaults))
 
325
                break
 
326
            if config['save_options']:
 
327
                configdir.saveConfig(config)
 
328
            configdir.deleteOldCacheData(config['expire_cache_data'])
 
329
 
 
330
            myid = createPeerID()
 
331
            seed(myid)
 
332
 
 
333
            rawserver = RawServer(doneflag, config['timeout_check_interval'],
 
334
                                  config['timeout'], ipv6_enable = config['ipv6_enabled'],
 
335
                                  failfunc = d.failed, errorfunc = d.error)
 
336
 
 
337
            upnp_type = 0
 
338
            while True:
 
339
                try:
 
340
                    listen_port = rawserver.find_and_bind(config['minport'], config['maxport'],
 
341
                                    config['bind'], ipv6_socket_style = config['ipv6_binds_v4'],
 
342
                                    upnp = upnp_type, randomizer = config['random_port'])
 
343
                    break
 
344
                except socketerror, e:
 
345
                    if upnp_type and e == UPnP_ERROR:
 
346
                        d.error('WARNING: COULD NOT FORWARD VIA UPnP')
 
347
                        upnp_type = 0
 
348
                        continue
 
349
                    d.error("Couldn't listen - " + str(e))
 
350
                    d.failed()
 
351
                    return
 
352
 
 
353
            response = get_response(config['responsefile'], config['url'], d.error)
 
354
            if not response:
 
355
                break
 
356
 
 
357
            infohash = sha(bencode(response['info'])).digest()
 
358
            
 
359
            dow = BT1Download(d.display, d.finished, d.error, d.error, doneflag,
 
360
                            config, response, infohash, myid, rawserver, listen_port,
 
361
                            configdir)
 
362
            
 
363
            if not dow.saveAs(d.chooseFile):
 
364
                break
 
365
 
 
366
            if not dow.initFiles(old_style = True):
 
367
                break
 
368
            if not dow.startEngine():
 
369
                dow.shutdown()
 
370
                break
 
371
            dow.startRerequester()
 
372
            dow.autoStats()
 
373
 
 
374
            if not dow.am_I_finished():
 
375
                d.display(activity = 'connecting to peers')
 
376
            rawserver.listen_forever(dow.getPortHandler())
 
377
            d.display(activity = 'shutting down')
 
378
            dow.shutdown()
 
379
            break
 
380
 
 
381
    except KeyboardInterrupt:
 
382
        # ^C to exit.. 
 
383
        pass 
 
384
    try:
 
385
        rawserver.shutdown()
 
386
    except:
 
387
        pass
 
388
    if not d.done:
 
389
        d.failed()
 
390
 
 
391
 
 
392
if __name__ == '__main__':
 
393
    if argv[1:] == ['--version']:
 
394
        print version
 
395
        exit(0)
 
396
    if len(argv) <= 1:
 
397
        print "Usage: btdownloadcurses.py <global options>\n"
 
398
        print get_usage(defaults)
 
399
        exit(1)
 
400
 
 
401
    errlist = []
 
402
    curses_wrapper(run, errlist, argv[1:])
 
403
 
 
404
    if errlist:
 
405
        print "These errors occurred during execution:"
 
406
        for error in errlist:
 
407
            print error
 
408