diff --git a/bindings/python/client.py b/bindings/python/client.py index a8bf98161..3e07b9ec1 100755 --- a/bindings/python/client.py +++ b/bindings/python/client.py @@ -218,15 +218,12 @@ def main(): if options.max_download_rate <= 0: options.max_download_rate = -1 - settings = lt.session_settings() - settings.user_agent = 'python_client/' + lt.version - - ses = lt.session() - ses.set_download_rate_limit(int(options.max_download_rate)) - ses.set_upload_rate_limit(int(options.max_upload_rate)) - ses.listen_on(options.port, options.port + 10) - ses.set_settings(settings) - ses.set_alert_mask(0xfffffff) + ses = lt.session({'user_agent': 'python_client/' + lt.__version__, + 'listen_interfaces':'0.0.0.0:' + str(options.port), + 'download_rate_limit': int(options.max_download_rate), + 'upload_rate_limit': int(options.max_upload_rate), + 'alert_mask' : 0xfffffff + }) if options.proxy_host != '': ps = lt.proxy_settings() @@ -281,14 +278,13 @@ def main(): out = '' for h in handles: - if h.has_metadata(): - name = h.get_torrent_info().name()[:40] + s = h.status() + if s.has_metadata: + name = h.torrent_file().name()[:40] else: name = '-' out += 'name: %-40s\n' % name - s = h.status() - if s.state != lt.torrent_status.seeding: state_str = ['queued', 'checking', 'downloading metadata', 'downloading', 'finished', 'seeding', @@ -335,14 +331,7 @@ def main(): write_line(console, '(q)uit), (p)ause), (u)npause), (r)eannounce\n') write_line(console, 76 * '-' + '\n') - while 1: - a = ses.pop_alert() - if not a: - break - alerts.append(a) - - if len(alerts) > 8: - del alerts[:len(alerts) - 8] + alerts = ses.pop_alerts() for a in alerts: if type(a) == str: @@ -369,7 +358,7 @@ def main(): ses.pause() for h in handles: - if not h.is_valid() or not h.has_metadata(): + if not h.is_valid() or not s.has_metadata: continue data = lt.bencode(h.write_resume_data()) open(os.path.join(options.save_path, h.get_torrent_info().name() + diff --git a/bindings/python/make_torrent.py b/bindings/python/make_torrent.py index b83357260..e1cb6e99e 100755 --- a/bindings/python/make_torrent.py +++ b/bindings/python/make_torrent.py @@ -20,6 +20,11 @@ fs = libtorrent.file_storage() parent_input = os.path.split(input)[0] +# if we have a single file, use it because os.walk does not work on a single files +if os.path.isfile(input): + size = os.path.getsize(input) + fs.add_file(input, size) + for root, dirs, files in os.walk(input): # skip directories starting with . if os.path.split(root)[1][0] == '.': @@ -48,8 +53,8 @@ t = libtorrent.create_torrent(fs, 0, 4 * 1024 * 1024) t.add_tracker(sys.argv[2]) t.set_creator('libtorrent %s' % libtorrent.__version__) -libtorrent.set_piece_hashes(t, parent_input, lambda x: sys.stderr.write('.')) -sys.stderr.write('\n') +libtorrent.set_piece_hashes(t, parent_input, lambda x: sys.stdout.write('.')) +sys.stdout.write('\n') f = open('out.torrent', 'wb+') f.write(libtorrent.bencode(t.generate())) diff --git a/bindings/python/simple_client.py b/bindings/python/simple_client.py index 9722ba766..5c2ae4a98 100755 --- a/bindings/python/simple_client.py +++ b/bindings/python/simple_client.py @@ -8,14 +8,14 @@ import libtorrent as lt import time import sys -ses = lt.session() -ses.listen_on(6881, 6891) +ses = lt.session({'listen_interfaces':'0.0.0.0:6881'}) info = lt.torrent_info(sys.argv[1]) h = ses.add_torrent({'ti': info, 'save_path': '.'}) -print('starting', h.name()) +s = h.status() +print('starting', s.name) -while (not h.is_seed()): +while (not s.is_seeding): s = h.status() state_str = [ diff --git a/bindings/python/src/alert.cpp b/bindings/python/src/alert.cpp index 714769506..dc3b8991c 100644 --- a/bindings/python/src/alert.cpp +++ b/bindings/python/src/alert.cpp @@ -356,7 +356,7 @@ void bind_alert() class_, noncopyable>( "hash_failed_alert", no_init) - .def_readonly("piece_index", &hash_failed_alert::piece_index) + .add_property("piece_index", make_getter(&hash_failed_alert::piece_index, by_value())) ; class_, noncopyable>( @@ -389,13 +389,13 @@ void bind_alert() class_, noncopyable>( "piece_finished_alert", no_init) - .def_readonly("piece_index", &piece_finished_alert::piece_index) + .add_property("piece_index", make_getter(&piece_finished_alert::piece_index, by_value())) ; class_, noncopyable>( "block_finished_alert", no_init) - .def_readonly("block_index", &block_finished_alert::block_index) - .def_readonly("piece_index", &block_finished_alert::piece_index) + .add_property("block_index", make_getter(&block_finished_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&block_finished_alert::piece_index, by_value())) ; class_, noncopyable>( @@ -403,8 +403,8 @@ void bind_alert() #ifndef TORRENT_NO_DEPRECATE .def_readonly("peer_speedmsg", &block_downloading_alert::peer_speedmsg) #endif - .def_readonly("block_index", &block_downloading_alert::block_index) - .def_readonly("piece_index", &block_downloading_alert::piece_index) + .add_property("block_index", make_getter(&block_downloading_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&block_downloading_alert::piece_index, by_value())) ; class_, noncopyable>( @@ -693,20 +693,20 @@ void bind_alert() class_, noncopyable>( "request_dropped_alert", no_init) - .def_readonly("block_index", &request_dropped_alert::block_index) - .def_readonly("piece_index", &request_dropped_alert::piece_index) + .add_property("block_index", make_getter(&request_dropped_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&request_dropped_alert::piece_index, by_value())) ; class_, noncopyable>( "block_timeout_alert", no_init) - .def_readonly("block_index", &block_timeout_alert::block_index) - .def_readonly("piece_index", &block_timeout_alert::piece_index) + .add_property("block_index", make_getter(&block_timeout_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&block_timeout_alert::piece_index, by_value())) ; class_, noncopyable>( "unwanted_block_alert", no_init) - .def_readonly("block_index", &unwanted_block_alert::block_index) - .def_readonly("piece_index", &unwanted_block_alert::piece_index) + .add_property("block_index", make_getter(&unwanted_block_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&unwanted_block_alert::piece_index, by_value())) ; class_, noncopyable>( diff --git a/bindings/python/src/peer_info.cpp b/bindings/python/src/peer_info.cpp index 49ca230b8..f1bc9e763 100644 --- a/bindings/python/src/peer_info.cpp +++ b/bindings/python/src/peer_info.cpp @@ -47,6 +47,7 @@ list get_pieces(peer_info const& pi) return ret; } +using by_value = return_value_policy; void bind_peer_info() { scope pi = class_("peer_info") @@ -82,8 +83,8 @@ void bind_peer_info() .def_readonly("download_queue_length", &peer_info::download_queue_length) .def_readonly("upload_queue_length", &peer_info::upload_queue_length) .def_readonly("failcount", &peer_info::failcount) - .def_readonly("downloading_piece_index", &peer_info::downloading_piece_index) - .def_readonly("downloading_block_index", &peer_info::downloading_block_index) + .add_property("downloading_piece_index", make_getter(&peer_info::downloading_piece_index, by_value())) + .add_property("downloading_block_index", make_getter(&peer_info::downloading_block_index, by_value())) .def_readonly("downloading_progress", &peer_info::downloading_progress) .def_readonly("downloading_total", &peer_info::downloading_total) .def_readonly("client", &peer_info::client) diff --git a/bindings/python/src/torrent_handle.cpp b/bindings/python/src/torrent_handle.cpp index f17c21a64..a64f0641b 100644 --- a/bindings/python/src/torrent_handle.cpp +++ b/bindings/python/src/torrent_handle.cpp @@ -341,6 +341,7 @@ void add_piece(torrent_handle& th, piece_index_t piece, char const *data, int fl th.add_piece(piece, data, flags); } +using by_value = return_value_policy; void bind_torrent_handle() { // arguments are: number of seconds and tracker index @@ -473,7 +474,7 @@ void bind_torrent_handle() ; class_("pool_file_status") - .def_readonly("file_index", &pool_file_status::file_index) + .add_property("file_index", make_getter((&pool_file_status::file_index), by_value())) .def_readonly("last_use", &pool_file_status::last_use) .def_readonly("open_mode", &pool_file_status::open_mode) ; diff --git a/bindings/python/src/torrent_info.cpp b/bindings/python/src/torrent_info.cpp index 835d2b18b..4cfb62c40 100644 --- a/bindings/python/src/torrent_info.cpp +++ b/bindings/python/src/torrent_info.cpp @@ -208,6 +208,7 @@ std::shared_ptr bencoded_constructor1(entry const& ent) return bencoded_constructor0(ent, 0); } +using by_value = return_value_policy; void bind_torrent_info() { return_value_policy copy; @@ -218,7 +219,7 @@ void bind_torrent_info() #endif class_("file_slice") - .def_readwrite("file_index", &file_slice::file_index) + .add_property("file_index", make_getter((&file_slice::file_index), by_value())) .def_readwrite("offset", &file_slice::offset) .def_readwrite("size", &file_slice::size) ; diff --git a/bindings/python/test.py b/bindings/python/test.py index 8237dd197..6b44becc7 100644 --- a/bindings/python/test.py +++ b/bindings/python/test.py @@ -8,6 +8,13 @@ import time import os import shutil import binascii +import subprocess as sub +import sys + +# include terminal interface for travis parallel executions of scripts which use +# terminal features: fix multiple stdin assignment at termios.tcgetattr +if os.name != 'nt': + import pty class test_create_torrent(unittest.TestCase): @@ -263,6 +270,109 @@ class test_sha1hash(unittest.TestCase): class test_session(unittest.TestCase): + def test_post_session_stats(self): + s = lt.session({'alert_mask': lt.alert.category_t.stats_notification, 'enable_dht': False}) + s.post_session_stats() + a = s.wait_for_alert(1000) + self.assertTrue(isinstance(a, lt.session_stats_alert)) + self.assertTrue(isinstance(a.values, dict)) + self.assertTrue(len(a.values) > 0) + + def test_add_torrent(self): + s = lt.session({'alert_mask': lt.alert.category_t.stats_notification, 'enable_dht': False}) + h = s.add_torrent({'ti': lt.torrent_info('base.torrent'), + 'save_path': '.', + 'dht_nodes': [('1.2.3.4', 6881), ('4.3.2.1', 6881)], + 'http_seeds': ['http://test.com/seed'], + 'peers': [('5.6.7.8', 6881)], + 'banned_peers': [('8.7.6.5', 6881)], + 'file_priorities': [1,1,1,2,0]}) + + def test_unknown_settings(self): + try: + s = lt.session({'unexpected-key-name': 42}) + self.assertFalse('should have thrown an exception') + except KeyError as e: + print(e) + + def test_apply_settings(self): + + s = lt.session({'enable_dht': False}) + s.apply_settings({'num_want': 66, 'user_agent': 'test123'}) + self.assertEqual(s.get_settings()['num_want'], 66) + self.assertEqual(s.get_settings()['user_agent'], 'test123') + +class test_example_client(unittest.TestCase): + + def test_execute_client(self): + if os.name == 'nt': + # TODO: fix windows includes of client.py + return + my_stdin = sys.stdin + if os.name != 'nt': + master_fd, slave_fd = pty.openpty() + # slave_fd fix multiple stdin assignment at termios.tcgetattr + my_stdin = slave_fd + + process = sub.Popen( + [sys.executable,"client.py","url_seed_multi.torrent"], + stdin=my_stdin, stdout=sub.PIPE, stderr=sub.PIPE) + # python2 has no Popen.wait() timeout + time.sleep(5) + returncode = process.poll() + if returncode == None: + # this is an expected use-case + process.kill() + err = process.stderr.read().decode("utf-8") + self.assertEqual('', err, 'process throw errors: \n' + err) + # check error code if process did unexpected end + if returncode != None: + # in case of error return: output stdout if nothing was on stderr + if returncode != 0: + print("stdout:\n" + process.stdout.read().decode("utf-8")) + self.assertEqual(returncode, 0, "returncode: " + str(returncode) + "\n" + + "stderr: empty\n" + + "some configuration does not output errors like missing module members," + + "try to call it manually to get the error message\n") + + def test_execute_simple_client(self): + process = sub.Popen( + [sys.executable,"simple_client.py","url_seed_multi.torrent"], + stdout=sub.PIPE, stderr=sub.PIPE) + # python2 has no Popen.wait() timeout + time.sleep(5) + returncode = process.poll() + if returncode == None: + # this is an expected use-case + process.kill() + err = process.stderr.read().decode("utf-8") + self.assertEqual('', err, 'process throw errors: \n' + err) + # check error code if process did unexpected end + if returncode != None: + # in case of error return: output stdout if nothing was on stderr + if returncode != 0: + print("stdout:\n" + process.stdout.read().decode("utf-8")) + self.assertEqual(returncode, 0, "returncode: " + str(returncode) + "\n" + + "stderr: empty\n" + + "some configuration does not output errors like missing module members," + + "try to call it manually to get the error message\n") + + def test_execute_make_torrent(self): + process = sub.Popen( + [sys.executable,"make_torrent.py","url_seed_multi.torrent", + "http://test.com/test"], stdout=sub.PIPE, stderr=sub.PIPE) + returncode = process.wait() + # python2 has no Popen.wait() timeout + err = process.stderr.read().decode("utf-8") + self.assertEqual('', err, 'process throw errors: \n' + err) + # in case of error return: output stdout if nothing was on stderr + if returncode != 0: + print("stdout:\n" + process.stdout.read().decode("utf-8")) + self.assertEqual(returncode, 0, "returncode: " + str(returncode) + "\n" + + "stderr: empty\n" + + "some configuration does not output errors like missing module members," + + "try to call it manually to get the error message\n") + def test_post_session_stats(self): s = lt.session({'alert_mask': lt.alert.category_t.stats_notification, 'enable_dht': False})