Subversion Repositories svnkaklik

Rev

Details | Last modification | View Log

Rev Author Line No. Line
36 kaklik 1
#!/usr/bin/env python
2
 
3
# Written by Bram Cohen
4
# see LICENSE.txt for license information
5
 
6
from BitTornado import PSYCO
7
if PSYCO.psyco:
8
    try:
9
        import psyco
10
        assert psyco.__version__ >= 0x010100f0
11
        psyco.full()
12
    except:
13
        pass
14
 
15
from BitTornado.download_bt1 import BT1Download, defaults, parse_params, get_usage, get_response
16
from BitTornado.RawServer import RawServer, UPnP_ERROR
17
from random import seed
18
from socket import error as socketerror
19
from BitTornado.bencode import bencode
20
from BitTornado.natpunch import UPnP_test
21
from threading import Event
22
from os.path import abspath
23
from os import getpid, remove
24
from sys import argv, stdout
25
import sys
26
from sha import sha
27
from time import strftime
28
from BitTornado.clock import clock
29
from BitTornado import createPeerID, version
30
 
31
assert sys.version >= '2', "Install Python 2.0 or greater"
32
try:
33
    True
34
except:
35
    True = 1
36
    False = 0
37
 
38
PROFILER = False
39
 
40
if __debug__: LOGFILE=open(argv[3]+"."+str(getpid()),"w")
41
 
42
def traceMsg(msg):
43
    try:
44
        if __debug__:
45
           LOGFILE.write(msg + "\n")
46
           LOGFILE.flush()
47
    except:
48
        return
49
 
50
def hours(n):
51
    if n == 0:
52
        return 'complete!'
53
    try:
54
        n = int(n)
55
        assert n >= 0 and n < 5184000  # 60 days
56
    except:
57
        return '<unknown>'
58
    m, s = divmod(n, 60)
59
    h, m = divmod(m, 60)
60
    d, h = divmod(h, 24)
61
    if d > 0:
62
        return '%dd %02d:%02d:%02d' % (d, h, m, s)
63
    else:
64
        return '%02d:%02d:%02d' % (h, m, s)
65
 
66
class HeadlessDisplayer:
67
    def __init__(self):
68
        self.done = False
69
        self.file = ''
70
        self.percentDone = ''
71
        self.timeEst = 'Connecting to Peers'
72
        self.downloadTo = ''
73
        self.downRate = ''
74
        self.upRate = ''
75
        self.shareRating = ''
76
        self.percentShare = ''
77
        self.upTotal = 0
78
        self.downTotal = 0
79
        self.seedStatus = ''
80
        self.peerStatus = ''
81
        self.seeds = ''
82
        self.peers = ''
83
        self.errors = []
84
        self.last_update_time = -1
85
        self.statFile = 'percent.txt'
86
        self.autoShutdown = 'False'
87
        self.user = 'unknown'
88
        self.size = 0
89
        self.shareKill = '100'
90
        self.distcopy = ''
91
        self.stoppedAt = ''
92
 
93
    def finished(self):
94
        if __debug__: traceMsg('finished - begin')
95
        self.done = True
96
        self.percentDone = '100'
97
        self.timeEst = 'Download Succeeded!'
98
        self.downRate = ''
99
        self.display()
100
        if self.autoShutdown == 'True':
101
            self.upRate = ''
102
            if self.stoppedAt == '':
103
                self.writeStatus()
104
            if __debug__: traceMsg('finished - end - raised ki')
105
            raise KeyboardInterrupt
106
        if __debug__: traceMsg('finished - end')
107
 
108
    def failed(self):
109
        if __debug__: traceMsg('failed - begin')
110
        self.done = True
111
        self.percentDone = '0'
112
        self.timeEst = 'Download Failed!'
113
        self.downRate = ''
114
        self.display()
115
        if self.autoShutdown == 'True':
116
            self.upRate = ''
117
            if self.stoppedAt == '':
118
                self.writeStatus()
119
            if __debug__:traceMsg('failed - end - raised ki')
120
            raise KeyboardInterrupt
121
        if __debug__: traceMsg('failed - end')
122
 
123
    def error(self, errormsg):
124
        self.errors.append(errormsg)
125
        self.display()
126
 
127
    def display(self, dpflag = Event(), fractionDone = None, timeEst = None, 
128
        downRate = None, upRate = None, activity = None,
129
        statistics = None,  **kws):
130
        if __debug__: traceMsg('display - begin')
131
        if self.last_update_time + 0.1 > clock() and fractionDone not in (0.0, 1.0) and activity is not None:
132
            return
133
        self.last_update_time = clock()
134
        if fractionDone is not None:
135
            self.percentDone = str(float(int(fractionDone * 1000)) / 10)
136
        if timeEst is not None:
137
            self.timeEst = hours(timeEst)
138
        if activity is not None and not self.done:
139
            self.timeEst = activity
140
        if downRate is not None:
141
            self.downRate = '%.1f kB/s' % (float(downRate) / (1 << 10))
142
        if upRate is not None:
143
            self.upRate = '%.1f kB/s' % (float(upRate) / (1 << 10))
144
        if statistics is not None:
145
           if (statistics.shareRating < 0) or (statistics.shareRating > 100):
146
               self.shareRating = 'oo  (%.1f MB up / %.1f MB down)' % (float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20))
147
               self.downTotal = statistics.downTotal
148
               self.upTotal = statistics.upTotal
149
           else:
150
               self.shareRating = '%.3f  (%.1f MB up / %.1f MB down)' % (statistics.shareRating, float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20))
151
               self.downTotal = statistics.downTotal
152
               self.upTotal = statistics.upTotal
153
           if not self.done:
154
              self.seedStatus = '%d seen now, plus %.3f distributed copies' % (statistics.numSeeds,0.001*int(1000*statistics.numCopies))
155
              self.seeds = (str(statistics.numSeeds))
156
           else:
157
              self.seedStatus = '%d seen recently, plus %.3f distributed copies' % (statistics.numOldSeeds,0.001*int(1000*statistics.numCopies))
158
              self.seeds = (str(statistics.numOldSeeds))
159
 
160
           self.peers = '%d' % (statistics.numPeers)
161
           self.distcopy = '%.3f' % (0.001*int(1000*statistics.numCopies))
162
           self.peerStatus = '%d seen now, %.1f%% done at %.1f kB/s' % (statistics.numPeers,statistics.percentDone,float(statistics.torrentRate) / (1 << 10))
163
 
164
        dpflag.set()
165
 
166
        if __debug__: traceMsg('display - prior to self.write')
167
 
168
        if self.stoppedAt == '': 
169
           self.writeStatus()
170
 
171
        if __debug__: traceMsg('display - end')
172
 
173
    def chooseFile(self, default, size, saveas, dir):
174
        if __debug__: traceMsg('chooseFile - begin')
175
 
176
        self.file = '%s (%.1f MB)' % (default, float(size) / (1 << 20))
177
        self.size = size
178
        if saveas != '':
179
            default = saveas
180
        self.downloadTo = abspath(default)
181
        if __debug__: traceMsg('chooseFile - end')
182
        return default
183
 
184
    def writeStatus(self):
185
        if __debug__: traceMsg('writeStatus - begin')
186
        downRate = self.downTotal
187
        die = False
188
 
189
        try:
190
            f=open(self.statFile,'r')
191
            running = f.read(1)
192
            f.close
193
        except:
194
            running = 0
195
            self.timeEst = 'Failed To Open StatFile'
196
            if __debug__: traceMsg('writeStatus - Failed to Open StatFile')
197
 
198
        if __debug__: traceMsg('writeStatus - running :' + str(running))
199
        if __debug__: traceMsg('writeStatus - stoppedAt :' + self.stoppedAt)
200
 
201
        if running == '0':
202
            if self.stoppedAt == '':
203
                if self.percentDone == '100':
204
                    self.stoppedAt = '100'
205
                else:
206
                    self.stoppedAt = str((float(self.percentDone)+100)*-1)
207
                    self.timeEst = 'Torrent Stopped'
208
            die = True
209
            self.upRate = ''
210
            self.downRate = ''
211
            self.percentDone = self.stoppedAt
212
        else:
213
            if downRate == 0 and self.upTotal > 0:
214
                downRate = self.size
215
            if self.done:
216
                self.percentDone = '100'
217
                downRate = self.size
218
                if self.autoShutdown == 'True':
219
                    running = '0'
220
            if self.upTotal > 0:
221
                self.percentShare = '%.1f' % ((float(self.upTotal)/float(downRate))*100)
222
            else:
223
                self.percentShare = '0.0'
224
            if self.done and self.percentShare is not '' and self.autoShutdown == 'False':
225
                if (float(self.percentShare) >= float(self.shareKill)) and (self.shareKill != '0'):
226
                    die = True
227
                    running = '0'
228
                    self.upRate = ''
229
            elif (not self.done) and (self.timeEst == 'complete!') and (self.percentDone == '100.0'):
230
                if (float(self.percentShare) >= float(self.shareKill)) and (self.shareKill != '0'):
231
                    die = True
232
                    running = '0'
233
                    self.upRate = ''
234
                    #self.finished()
235
 
236
        lcount = 0
237
 
238
        while 1:
239
            lcount += 1
240
            try:
241
                f=open(self.statFile,'w')
242
                f.write(running + '\n')
243
                f.write(self.percentDone + '\n')
244
                f.write(self.timeEst + '\n')
245
                f.write(self.downRate + '\n')
246
                f.write(self.upRate + '\n')
247
                f.write(self.user + '\n')
248
                f.write(self.seeds + '+' + self.distcopy + '\n')
249
                f.write(self.peers + '\n')
250
                f.write(self.percentShare + '\n')
251
                f.write(self.shareKill + '\n')
252
                f.write(str(self.upTotal) + '\n')
253
                f.write(str(self.downTotal) + '\n')
254
                f.write(str(self.size))
255
 
256
                try:
257
                    errs = []
258
                    errs = self.scrub_errs()
259
 
260
                    for errmsg in errs:
261
                        f.write('\n' + errmsg)
262
                except:
263
                    if __debug__: traceMsg('writeStatus - Failed during writing Errors')
264
                    pass
265
 
266
                f.flush()
267
                f.close()
268
 
269
                break
270
            except:
271
                if __debug__: traceMsg('writeStatus - Failed to Open StatFile for Writing')
272
                if lcount > 30:
273
                    break
274
                pass
275
 
276
        if die:
277
            if __debug__: traceMsg('writeStatus - dieing - raised ki')
278
            raise KeyboardInterrupt
279
 
280
    def newpath(self, path):
281
        self.downloadTo = path
282
 
283
    def scrub_errs(self):
284
        new_errors = []
285
 
286
        try:
287
            if self.errors:
288
                last_errMsg = ''
289
                errCount = 0
290
                for err in self.errors:
291
                    try:
292
                        if last_errMsg == '':
293
                            last_errMsg = err
294
                        elif last_errMsg == err:
295
                            errCount += 1
296
                        elif last_errMsg != err:
297
                            if errCount > 0:
298
                                new_errors.append(last_errMsg + ' (x' + str(errCount+1) + ')')
299
                            else:
300
                                new_errors.append(last_errMsg)
301
                            errCount = 0
302
                            last_errMsg = err
303
                    except:
304
                        if __debug__: traceMsg('scrub_errs - Failed scrub')
305
                        pass
306
 
307
            try:
308
                if len(new_errors) > 0:
309
                    if last_errMsg != new_errors[len(new_errors)-1]:
310
                        if errCount > 0:
311
                            new_errors.append(last_errMsg + ' (x' + str(errCount+1) + ')')
312
                        else:
313
                            new_errors.append(last_errMsg)
314
                    else:
315
                        if errCount > 0:
316
                            new_errors.append(last_errMsg + ' (x' + str(errCount+1) + ')')
317
                        else:
318
                            new_errors.append(last_errMsg)
319
            except:
320
                if __debug__: traceMsg('scrub_errs - Failed during scrub last Msg ')
321
                pass
322
 
323
            if len(self.errors) > 100:
324
                while len(self.errors) > 100 :
325
                    del self.errors[0:99]
326
                self.errors = new_errors
327
 
328
        except:
329
            if __debug__: traceMsg('scrub_errs - Failed during scrub Errors')
330
            pass
331
 
332
        return new_errors
333
 
334
def run(autoDie,shareKill,statusFile,userName,params):
335
 
336
    if __debug__: traceMsg('run - begin')
337
 
338
    try:
339
        f=open(statusFile+".pid",'w')
340
        f.write(str(getpid()).strip() + "\n")
341
        f.flush()
342
        f.close()
343
    except:
344
        if __debug__: traceMsg('run - Failed to Create PID file')
345
        pass
346
 
347
    try:
348
 
349
        h = HeadlessDisplayer()
350
        h.statFile = statusFile
351
        h.autoShutdown = autoDie
352
        h.shareKill = shareKill
353
        h.user = userName
354
 
355
        while 1:
356
            try:
357
                config = parse_params(params)
358
            except ValueError, e:
359
                print 'error: ' + str(e) + '\nrun with no args for parameter explanations'
360
                break
361
            if not config:
362
                print get_usage()
363
                break
364
 
365
            myid = createPeerID()
366
            seed(myid)
367
 
368
            doneflag = Event()
369
            def disp_exception(text):
370
                print text
371
            rawserver = RawServer(doneflag, config['timeout_check_interval'],
372
                              config['timeout'], ipv6_enable = config['ipv6_enabled'],
373
                              failfunc = h.failed, errorfunc = disp_exception)
374
            upnp_type = UPnP_test(config['upnp_nat_access'])
375
            while True:
376
                try:
377
                    listen_port = rawserver.find_and_bind(config['minport'], config['maxport'],
378
                                config['bind'], ipv6_socket_style = config['ipv6_binds_v4'],
379
                                upnp = upnp_type, randomizer = config['random_port'])
380
                    break
381
                except socketerror, e:
382
                    if upnp_type and e == UPnP_ERROR:
383
                        print 'WARNING: COULD NOT FORWARD VIA UPnP'
384
                        upnp_type = 0
385
                        continue
386
                    print "error: Couldn't listen - " + str(e)
387
                    h.failed()
388
                    return
389
 
390
            response = get_response(config['responsefile'], config['url'], h.error)
391
            if not response:
392
                break
393
 
394
            infohash = sha(bencode(response['info'])).digest()
395
 
396
            dow = BT1Download(h.display, h.finished, h.error, disp_exception, doneflag,
397
                        config, response, infohash, myid, rawserver, listen_port)
398
 
399
            if not dow.saveAs(h.chooseFile, h.newpath): 
400
                break
401
 
402
            if not dow.initFiles(old_style = True): 
403
                break
404
 
405
            if not dow.startEngine():
406
                dow.shutdown()
407
                break
408
            dow.startRerequester()
409
            dow.autoStats()
410
 
411
            if not dow.am_I_finished():
412
                h.display(activity = 'connecting to peers')
413
 
414
            rawserver.listen_forever(dow.getPortHandler())
415
            h.display(activity = 'shutting down')
416
            dow.shutdown()
417
            break
418
 
419
        try: 
420
            rawserver.shutdown()
421
        except: 
422
            pass
423
 
424
        if not h.done: 
425
            h.failed()
426
 
427
    finally:
428
        if __debug__: traceMsg('run - removing PID file :'+statusFile+".pid")
429
 
430
        remove(statusFile+".pid")
431
 
432
    if __debug__: traceMsg('run - end')
433
 
434
 
435
if __name__ == '__main__':
436
    if argv[1:] == ['--version']:
437
        print version
438
        sys.exit(0)
439
 
440
    if PROFILER:
441
        import profile, pstats
442
        p = profile.Profile()
443
        p.runcall(run, argv[1],argv[2],argv[3],argv[4],argv[5:])
444
        log = open('profile_data.'+strftime('%y%m%d%H%M%S')+'.txt','a')
445
        normalstdout = sys.stdout
446
        sys.stdout = log
447
#        pstats.Stats(p).strip_dirs().sort_stats('cumulative').print_stats()
448
        pstats.Stats(p).strip_dirs().sort_stats('time').print_stats()
449
        sys.stdout = normalstdout
450
    else:
451
        run(argv[1],argv[2],argv[3],argv[4],argv[5:])