00001
00002
00003
00004
00005
00006
00007 import sys
00008 import libtorrent as lt
00009 import time
00010 import os.path
00011 import sys
00012
00013 class WindowsConsole:
00014 def __init__(self):
00015 self.console = Console.getconsole()
00016
00017 def clear(self):
00018 self.console.page()
00019
00020 def write(self, str):
00021 self.console.write(str)
00022
00023 def sleep_and_input(self, seconds):
00024 time.sleep(seconds)
00025 if msvcrt.kbhit():
00026 return msvcrt.getch()
00027 return None
00028
00029 class UnixConsole:
00030 def __init__(self):
00031 self.fd = sys.stdin
00032 self.old = termios.tcgetattr(self.fd.fileno())
00033 new = termios.tcgetattr(self.fd.fileno())
00034 new[3] = new[3] & ~termios.ICANON
00035 new[6][termios.VTIME] = 0
00036 new[6][termios.VMIN] = 1
00037 termios.tcsetattr(self.fd.fileno(), termios.TCSADRAIN, new)
00038
00039 sys.exitfunc = self._onexit
00040
00041 def _onexit(self):
00042 termios.tcsetattr(self.fd.fileno(), termios.TCSADRAIN, self.old)
00043
00044 def clear(self):
00045 sys.stdout.write('\033[2J\033[0;0H')
00046 sys.stdout.flush()
00047
00048 def write(self, str):
00049 sys.stdout.write(str)
00050 sys.stdout.flush()
00051
00052 def sleep_and_input(self, seconds):
00053 read,_,_ = select.select([self.fd.fileno()], [], [], seconds)
00054 if len(read) > 0:
00055 return self.fd.read(1)
00056 return None
00057
00058 if os.name == 'nt':
00059 import Console
00060 import msvcrt
00061 else:
00062 import termios
00063 import select
00064
00065 class PythonExtension(lt.torrent_plugin):
00066 def __init__(self, alerts):
00067 lt.torrent_plugin.__init__(self)
00068 self.alerts = alerts
00069 self.alerts.append('PythonExtension')
00070 self.count = 0
00071
00072 def on_piece_pass(self, index):
00073 self.alerts.append('got piece %d' % index)
00074
00075 def on_piece_failed(self, index):
00076 self.alerts.append('failed piece %d' % index)
00077
00078 def tick(self):
00079 self.count += 1
00080 if self.count >= 10:
00081 self.count = 0
00082 self.alerts.append('PythonExtension tick')
00083
00084 def write_line(console, line):
00085 console.write(line)
00086
00087 def add_suffix(val):
00088 prefix = ['B', 'kB', 'MB', 'GB', 'TB']
00089 for i in range(len(prefix)):
00090 if abs(val) < 1000:
00091 if i == 0:
00092 return '%5.3g%s' % (val, prefix[i])
00093 else:
00094 return '%4.3g%s' % (val, prefix[i])
00095 val /= 1000
00096
00097 return '%6.3gPB' % val
00098
00099 def progress_bar(progress, width):
00100 progress_chars = int(progress * width + 0.5)
00101 return progress_chars * '#' + (width - progress_chars) * '-'
00102
00103 def print_peer_info(console, peers):
00104
00105 out = ' down (total ) up (total ) q r flags block progress client\n'
00106
00107 for p in peers:
00108
00109 out += '%s/s ' % add_suffix(p.down_speed)
00110 out += '(%s) ' % add_suffix(p.total_download)
00111 out += '%s/s ' % add_suffix(p.up_speed)
00112 out += '(%s) ' % add_suffix(p.total_upload)
00113 out += '%2d ' % p.download_queue_length
00114 out += '%2d ' % p.upload_queue_length
00115
00116 if p.flags & lt.peer_info.interesting: out += 'I'
00117 else: out += '.'
00118 if p.flags & lt.peer_info.choked: out += 'C'
00119 else: out += '.'
00120 if p.flags & lt.peer_info.remote_interested: out += 'i'
00121 else: out += '.'
00122 if p.flags & lt.peer_info.remote_choked: out += 'c'
00123 else: out += '.'
00124 if p.flags & lt.peer_info.supports_extensions: out += 'e'
00125 else: out += '.'
00126 if p.flags & lt.peer_info.local_connection: out += 'l'
00127 else: out += 'r'
00128 out += ' '
00129
00130 if p.downloading_piece_index >= 0:
00131 out += progress_bar(float(p.downloading_progress) / p.downloading_total, 15)
00132 else:
00133 out += progress_bar(0, 15)
00134 out += ' '
00135
00136 if p.flags & lt.peer_info.handshake:
00137 id = 'waiting for handshake'
00138 elif p.flags & lt.peer_info.connecting:
00139 id = 'connecting to peer'
00140 elif p.flags & lt.peer_info.queued:
00141 id = 'queued'
00142 else:
00143 id = p.client
00144
00145 out += '%s\n' % id[:10]
00146
00147 write_line(console, out)
00148
00149 def main():
00150 from optparse import OptionParser
00151
00152 parser = OptionParser()
00153
00154 parser.add_option('-p', '--port',
00155 type='int', help='set listening port')
00156
00157 parser.add_option('-r', '--ratio',
00158 type='float', help='set the preferred upload/download ratio. 0 means infinite. Values smaller than 1 are clamped to 1')
00159
00160 parser.add_option('-d', '--max-download-rate',
00161 type='float', help='the maximum download rate given in kB/s. 0 means infinite.')
00162
00163 parser.add_option('-u', '--max-upload-rate',
00164 type='float', help='the maximum upload rate given in kB/s. 0 means infinite.')
00165
00166 parser.add_option('-s', '--save-path',
00167 type='string', help='the path where the downloaded file/folder should be placed.')
00168
00169 parser.add_option('-a', '--allocation-mode',
00170 type='string', help='sets mode used for allocating the downloaded files on disk. Possible options are [full | compact]')
00171
00172 parser.set_defaults(
00173 port=6881
00174 , ratio=0
00175 , max_download_rate=0
00176 , max_upload_rate=0
00177 , save_path='./'
00178 , allocation_mode='compact'
00179 )
00180
00181 (options, args) = parser.parse_args()
00182
00183 if options.port < 0 or options.port > 65525:
00184 options.port = 6881
00185
00186 options.max_upload_rate *= 1000
00187 options.max_download_rate *= 1000
00188
00189 if options.max_upload_rate <= 0:
00190 options.max_upload_rate = -1
00191 if options.max_download_rate <= 0:
00192 options.max_download_rate = -1
00193
00194 compact_allocation = options.allocation_mode == 'compact'
00195
00196 settings = lt.session_settings()
00197 settings.user_agent = 'python_client/' + lt.version
00198
00199 ses = lt.session()
00200 ses.set_download_rate_limit(int(options.max_download_rate))
00201 ses.set_upload_rate_limit(int(options.max_upload_rate))
00202 ses.listen_on(options.port, options.port + 10)
00203 ses.set_settings(settings)
00204 ses.set_severity_level(lt.alert.severity_levels.info)
00205
00206 handles = []
00207 alerts = []
00208
00209
00210
00211
00212 for f in args:
00213 e = lt.bdecode(open(f, 'rb').read())
00214 info = lt.torrent_info(e)
00215 print 'Adding \'%s\'...' % info.name()
00216
00217 try:
00218 resume_data = lt.bdecode(open(
00219 os.path.join(options.save_path, info.name() + '.fastresume'), 'rb').read())
00220 except:
00221 resume_data = None
00222
00223 h = ses.add_torrent(info, options.save_path,
00224 resume_data=resume_data, compact_mode=compact_allocation)
00225
00226 handles.append(h)
00227
00228 h.set_max_connections(60)
00229 h.set_max_uploads(-1)
00230 h.set_ratio(options.ratio)
00231 h.set_sequenced_download_threshold(15)
00232
00233 if os.name == 'nt':
00234 console = WindowsConsole()
00235 else:
00236 console = UnixConsole()
00237
00238 alive = True
00239 while alive:
00240 console.clear()
00241
00242 out = ''
00243
00244 for h in handles:
00245 if h.has_metadata():
00246 name = h.torrent_info().name()[:40]
00247 else:
00248 name = '-'
00249 out += 'name: %-40s\n' % name
00250
00251 s = h.status()
00252
00253 if s.state != lt.torrent_status.seeding:
00254 state_str = ['queued', 'checking', 'connecting', 'downloading metadata', \
00255 'downloading', 'finished', 'seeding', 'allocating']
00256 out += state_str[s.state] + ' '
00257
00258 out += '%5.4f%% ' % (s.progress*100)
00259 out += progress_bar(s.progress, 49)
00260 out += '\n'
00261
00262 out += 'total downloaded: %d Bytes\n' % s.total_done
00263 out += 'peers: %d seeds: %d distributed copies: %d\n' % \
00264 (s.num_peers, s.num_seeds, s.distributed_copies)
00265 out += '\n'
00266
00267 out += 'download: %s/s (%s) ' \
00268 % (add_suffix(s.download_rate), add_suffix(s.total_download))
00269 out += 'upload: %s/s (%s) ' \
00270 % (add_suffix(s.upload_rate), add_suffix(s.total_upload))
00271 out += 'ratio: %s\n' % '0'
00272
00273 if s.state != lt.torrent_status.seeding:
00274 out += 'info-hash: %s\n' % h.info_hash()
00275 out += 'next announce: %s\n' % s.next_announce
00276 out += 'tracker: %s\n' % s.current_tracker
00277
00278 write_line(console, out)
00279
00280 peers = h.get_peer_info()
00281 print_peer_info(console, peers)
00282
00283 if True and s.state != lt.torrent_status.seeding:
00284 out = '\n'
00285 fp = h.file_progress()
00286 ti = h.torrent_info()
00287 for f,p in zip(ti.files(), fp):
00288 out += progress_bar(p, 20)
00289 out += ' ' + f.path + '\n'
00290 write_line(console, out)
00291
00292 write_line(console, 76 * '-' + '\n')
00293 write_line(console, '(q)uit), (p)ause), (u)npause), (r)eannounce\n')
00294 write_line(console, 76 * '-' + '\n')
00295
00296 while 1:
00297 a = ses.pop_alert()
00298 if not a: break
00299 alerts.append(a)
00300
00301 if len(alerts) > 8:
00302 del alerts[:len(alerts) - 8]
00303
00304 for a in alerts:
00305 if type(a) == str:
00306 write_line(console, a + '\n')
00307 else:
00308 write_line(console, a.msg() + '\n')
00309
00310 c = console.sleep_and_input(0.5)
00311
00312 if not c:
00313 continue
00314
00315 if c == 'r':
00316 for h in handles: h.force_reannounce()
00317 elif c == 'q':
00318 alive = False
00319 elif c == 'p':
00320 for h in handles: h.pause()
00321 elif c == 'u':
00322 for h in handles: h.resume()
00323
00324 for h in handles:
00325 if not h.is_valid() or not h.has_metadata():
00326 continue
00327 h.pause()
00328 data = lt.bencode(h.write_resume_data())
00329 open(os.path.join(options.save_path, h.torrent_info().name() + '.fastresume'), 'wb').write(data)
00330
00331 main()
00332